/SKILL.md
Senior Software Architect for HexCore — a Python framework for Hexagonal Architecture and Domain-Driven Design. Use when building or reviewing code that uses HexCore.
npx skillsauth add indroic/hexcore-skill hexcoreInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill empowers the agent to act as a Senior Architect specialized in HexCore v2.0.x, a Python framework (Python >=3.12) for Hexagonal Architecture and Domain-Driven Design.
Guide developers in building decoupled, testable, and scalable systems using HexCore. Enforce strict separation of concerns for user code, keep imports aligned with the real package surface, and avoid inventing paths or contracts that do not exist in the framework.
Use these exact paths for code generation and review. Do not invent paths.
| Component | Import Path |
| :--- | :--- |
| BaseEntity, AbstractModelMeta | hexcore.domain.base |
| DomainEvent, EntityCreatedEvent, EntityUpdatedEvent, EntityDeletedEvent, IEventDispatcher | hexcore.domain.events |
| IBaseRepository | hexcore.domain.repositories |
| IUnitOfWork | hexcore.domain.uow |
| BaseDomainService | hexcore.domain.services |
| InactiveEntityException | hexcore.domain.exceptions |
| PermissionsRegistry, TokenClaims | hexcore.domain.auth |
| Component | Import Path |
| :--- | :--- |
| DTO | hexcore.application.dtos.base |
| UseCase | hexcore.application.use_cases.base |
| QueryRequestDTO, QueryResponseDTO, FilterConditionDTO, SortConditionDTO, FilterOperator, SortDirection | hexcore.application.dtos.query |
| QueryEntitiesUseCase, ListEntitiesUseCase, SearchEntitiesUseCase | hexcore.application.use_cases.query |
| Component | Import Path |
| :--- | :--- |
| BaseModel (SQLAlchemy ORM) | hexcore.infrastructure.repositories.orms.sqlalchemy |
| BaseDocument (Beanie ODM) | hexcore.infrastructure.repositories.orms.beanie |
| BaseSQLAlchemyRepository, BaseBeanieRepository | hexcore.infrastructure.repositories.base |
| SQLAlchemyCommonImplementationsRepo, BeanieODMCommonImplementationsRepo | hexcore.infrastructure.repositories.implementations |
| SqlAlchemyUnitOfWork, NoSqlUnitOfWork | hexcore.infrastructure.uow |
| get_repository | hexcore.infrastructure.uow.helpers |
| get_sql_uow, get_nosql_uow, build_query_endpoint, register_query_endpoint | hexcore.infrastructure.api.utils |
| cycle_protection_resolver | hexcore.infrastructure.repositories.decorators |
| register_entity_on_uow | hexcore.infrastructure.uow.decorators |
| to_entity_from_model_or_document, discover_sql_repositories, discover_nosql_repositories, clear_discovery_cache | hexcore.infrastructure.repositories.utils |
| init_beanie_documents | hexcore.infrastructure.repositories.orms.beanie.utils |
| MemoryCache | hexcore.infrastructure.cache.cache_backends.memory |
| RedisCache | hexcore.infrastructure.cache.cache_backends.redis |
| InMemoryEventDispatcher | hexcore.infrastructure.events.events_backends.memory |
| Component | Import Path |
| :--- | :--- |
| ServerConfig, LazyConfig | hexcore.config |
| FieldResolversType, FieldSerializersType | hexcore.types |
Infrastructure -> Application -> Domain. Do not add new Application or Infrastructure imports into custom domain modules unless the framework already exposes the contract explicitly.async with uow:.BaseEntity.UnitOfWork, not on concrete infrastructure repositories.UseCase classes receive DTOs and return DTOs. Never pass a BaseEntity across an application boundary.BaseEntity already provides id, created_at, updated_at, and is_active. Do not redeclare them in subclasses.QueryEntitiesUseCase.SqlAlchemyUnitOfWork.commit() and NoSqlUnitOfWork.commit() already dispatch collected domain events and then clear tracked entities. Do not manually call dispatch_events() or collect_domain_events() from application code unless you are implementing a new infrastructure adapter.get_by_id, list_all, save, or delete in concrete repositories. Add only specialized query methods.entity_cls, model_cls or document_cls, not_found_exception, fields_resolvers, and fields_serializers with those exact names.HexCore supports both hexagonal and vertical-slice layouts. These are illustrative structures, not hard requirements. Do not assume a fixed src/ tree or a single canonical package root.
src/domain/{module}/
├── entities.py
├── repositories.py
├── services.py
├── value_objects.py
├── events.py
├── enums.py
└── exceptions.py
src/application/{module}/
├── dtos.py
└── use_cases/
├── create_{entity}.py
├── update_{entity}.py
├── delete_{entity}.py
└── get_{entity}.py
src/infrastructure/{module}/
├── models.py
└── repositories.py
src/features/{module}/
├── domain/
├── application/
└── infrastructure/
src/shared/
├── domain/
├── application/
└── infrastructure/
When generating or reviewing code, follow the repository discovery paths and package root that the project actually configures.
If the workspace uses a flat package, a custom root, or nested feature folders, adapt the examples above instead of forcing a src/-based structure.
Every business operation is a dedicated UseCase class. Avoid shared mutation use cases.
from hexcore.application.use_cases.base import UseCase
from hexcore.application.dtos.base import DTO
class CreateUserUseCase(UseCase["CreateUserCommand", "UserResponse"]):
async def execute(self, command: CreateUserCommand) -> UserResponse:
...
UseCase is generic: UseCase[T, R] where T is the input DTO and R is the output DTO.async def execute(self, command: T) -> R.from uuid import UUID
from hexcore.application.dtos.base import DTO
from hexcore.application.use_cases.base import UseCase
from hexcore.infrastructure.uow import SqlAlchemyUnitOfWork
class CreateUserCommand(DTO):
name: str
email: str
class UserResponse(DTO):
id: UUID
name: str
email: str
class CreateUserUseCase(UseCase[CreateUserCommand, UserResponse]):
def __init__(self, service: UserService, uow: SqlAlchemyUnitOfWork) -> None:
self.service = service
self.uow = uow
async def execute(self, command: CreateUserCommand) -> UserResponse:
async with self.uow:
user = await self.service.create_user(name=command.name, email=command.email)
await self.uow.commit()
return UserResponse(id=user.id, name=user.name, email=user.email)
Use this pattern for list, search, filter, sort, and pagination flows.
from hexcore.application.dtos.query import QueryRequestDTO, QueryResponseDTO
from hexcore.application.use_cases.query import QueryEntitiesUseCase
class ListUsersUseCase(QueryEntitiesUseCase[User]):
async def execute(self, command: QueryRequestDTO) -> QueryResponseDTO:
return await super().execute(command)
Rules for query use cases:
QueryRequestDTO and QueryResponseDTO for read endpoints that need search, filters, sort, or pagination.build_query_endpoint(...) for simple FastAPI endpoints.register_query_endpoint(...) when you want to attach the endpoint directly to an APIRouter.query_all(...), BaseDomainService.list_entities(...) should prefer that path.query_all(...), BaseDomainService.query_entities(...) is the fallback.from hexcore.domain.services import BaseDomainService
class UserService(BaseDomainService):
def __init__(self, user_repo: IUserRepository) -> None:
self._user_repo = user_repo
super().__init__()
async def create_user(self, name: str, email: str) -> User:
user = User(name=name, email=email)
user.register_event(UserCreatedEvent(entity_id=user.id))
await self._user_repo.save(user)
return user
from hexcore.infrastructure.repositories.orms.sqlalchemy import BaseModel
class UserModel(BaseModel):
__tablename__ = "users"
name: str
email: str
SQLAlchemyCommonImplementationsRepoSQLAlchemyCommonImplementationsRepo expects these properties from HasBasicArgs:
| Property | Type | Purpose |
| :--- | :--- | :--- |
| entity_cls | type[T] | Domain entity class |
| model_cls | type[M] | SQLAlchemy model class |
| not_found_exception | type[Exception] | Raised when an entity is not found |
| fields_resolvers | FieldResolversType | None | Async resolvers for model -> entity mapping |
| fields_serializers | FieldSerializersType | None | Custom serializers for entity -> model mapping |
from hexcore.domain.uow import IUnitOfWork
from hexcore.infrastructure.repositories.implementations import SQLAlchemyCommonImplementationsRepo
class UserRepository(SQLAlchemyCommonImplementationsRepo[User, UserModel], IUserRepository):
def __init__(self, uow: IUnitOfWork) -> None:
super().__init__(uow)
@property
def entity_cls(self) -> type[User]:
return User
@property
def model_cls(self) -> type[UserModel]:
return UserModel
@property
def not_found_exception(self) -> type[Exception]:
return UserNotFoundException
@property
def fields_resolvers(self) -> FieldResolversType | None:
return None
@property
def fields_serializers(self) -> FieldSerializersType | None:
return None
SqlAlchemyUnitOfWork is built around an SQLAlchemy AsyncSession. Construct the session with async_sessionmaker and wire the UoW as a dependency.
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from hexcore.config import LazyConfig
config = LazyConfig.get_config()
engine = create_async_engine(config.async_sql_database_url, echo=config.debug)
async_session_factory = async_sessionmaker(engine, expire_on_commit=False)
from fastapi import Depends
from hexcore.infrastructure.uow import SqlAlchemyUnitOfWork
async def get_uow() -> AsyncGenerator[SqlAlchemyUnitOfWork, None]:
async with async_session_factory() as session:
yield SqlAlchemyUnitOfWork(session=session)
from fastapi import APIRouter, Depends
router = APIRouter(prefix="/users", tags=["users"])
@router.post("/", response_model=UserResponse)
async def create_user(
command: CreateUserCommand,
use_case: CreateUserUseCase = Depends(get_create_user_use_case),
) -> UserResponse:
return await use_case.execute(command)
Use BeanieODMCommonImplementationsRepo for MongoDB-backed repositories.
from hexcore.infrastructure.repositories.orms.beanie import BaseDocument
class UserDocument(BaseDocument):
name: str
email: str
from hexcore.infrastructure.repositories.implementations import BeanieODMCommonImplementationsRepo
class UserRepository(BeanieODMCommonImplementationsRepo[User, UserDocument], IUserRepository):
def __init__(self, uow: IUnitOfWork) -> None:
super().__init__(uow)
@property
def entity_cls(self) -> type[User]:
return User
@property
def document_cls(self) -> type[UserDocument]:
return UserDocument
@property
def not_found_exception(self) -> type[Exception]:
return UserNotFoundException
@property
def fields_resolvers(self) -> FieldResolversType | None:
return None
@property
def fields_serializers(self) -> FieldSerializersType | None:
return None
Beanie repositories must track entities manually so the UoW can dispatch their domain events.
uow.collect_entity(entity) when you save an entity that emitted events.@register_entity_on_uow decorator on repository save methods.LazyConfig.get_config() resolves configuration in this order:
HEXCORE_CONFIG_MODULEHEXCORE_CONFIG_MODULESLazyConfig.set_config_modules(...)configThe default discovery target is the root config.py. If you use another module path, configure it explicitly.
ServerConfigfrom pathlib import Path
from pydantic import ConfigDict
from hexcore.config import ServerConfig
from hexcore.domain.events import IEventDispatcher
from hexcore.infrastructure.cache import ICache
from hexcore.infrastructure.cache.cache_backends.memory import MemoryCache
from hexcore.infrastructure.events.events_backends.memory import InMemoryEventDispatcher
class ProjectConfig(ServerConfig):
base_dir: Path = Path(".")
host: str = "0.0.0.0"
port: int = 8000
debug: bool = False
sql_database_url: str = "sqlite:///./db.sqlite3"
async_sql_database_url: str = "sqlite+aiosqlite:///./db.sqlite3"
mongo_database_url: str = "mongodb://localhost:27017"
async_mongo_database_url: str = "mongodb+async://localhost:27017"
mongo_db_name: str = "my_db"
mongo_uri: str = "mongodb://localhost:27017/my_db"
redis_uri: str = "redis://localhost:6379/0"
redis_host: str = "localhost"
redis_port: int = 6379
redis_db: int = 0
redis_cache_duration: int = 300
allow_origins: list[str] = ["https://myapp.com"]
allow_credentials: bool = True
allow_methods: list[str] = ["*"]
allow_headers: list[str] = ["*"]
cache_backend: ICache = MemoryCache()
event_dispatcher: IEventDispatcher = InMemoryEventDispatcher()
model_config = ConfigDict(arbitrary_types_allowed=True)
config = ProjectConfig()
For production, swap MemoryCache for RedisCache and replace InMemoryEventDispatcher with a real broker-backed dispatcher.
When converting models or documents to entities with nested relationships:
FieldResolversType for async model/document -> entity attribute mapping.@cycle_protection_resolver to prevent infinite recursion in circular relations.to_entity_from_model_or_document from hexcore.infrastructure.repositories.utils as the central conversion utility.is_nosql=True when converting Beanie documents.QueryRequestDTO at the application boundary.query_all(...) for SQLAlchemy and Beanie repositories.BaseDomainService.query_entities(...) only when the repository does not implement query_all(...).IN and NOT_IN may split comma-separated values; text operators must preserve the raw string.HexCore projects may live in different layouts. Do not assume a fixed src/ tree or a legacy package path.
SqlAlchemyUnitOfWork automatically tracks entities via session.new, session.dirty, and session.deleted once set_domain_entity() has been used on the ORM model.NoSqlUnitOfWork requires explicit entity tracking via uow.collect_entity(entity) or @register_entity_on_uow.DomainEvent subclasses through BaseEntity.await uow.commit().ServerConfig.event_dispatcher.ICache interface.MemoryCache for RedisCache without changing application code.ServerConfig.cache_backend.| Prohibition | Reason |
| :--- | :--- |
| Re-declaring id, created_at, updated_at, or is_active in entity subclasses | Already provided by BaseEntity |
| Injecting repositories directly into business UseCase classes | Business use cases should depend on domain services plus a UoW |
| Treating the query use case exception as a rule for mutation use cases | QueryEntitiesUseCase is a read-side helper only |
| Calling session.commit() outside a UnitOfWork | Breaks transactional integrity |
| Calling repo.save() outside an async with uow: block | Leaves changes untracked and uncommitted |
| Manually calling dispatch_events() or collect_domain_events() from application code | The UoW already owns commit-time event dispatch |
| Reimplementing get_by_id, list_all, save, or delete in a concrete repo | Already implemented by the base class |
| Instantiating domain events manually inside a UseCase | Events should be emitted by entities and persisted through the UoW flow |
| Assuming hexcore.infrastructure.cache.cache_backends reexports the concrete cache classes | Import the concrete backend modules directly |
hexcore init # Scaffold a new project
hexcore create-domain-module # Generate 7 standard files for a new domain module
hexcore make-migrations # Generate Alembic migration scripts
hexcore migrate # Apply pending database migrations
hexcore test # Run pytest suite
hexcore init supports the hexagonal and vertical-slice templates.
domain/{module}/entities.py.domain/{module}/repositories.py.domain/{module}/services.py.application/{module}/dtos.py.UseCase per business operation in application/{module}/use_cases/.QueryEntitiesUseCase for read/list/search/filter/sort/pagination flows.BaseModel or BaseDocument in infrastructure.SQLAlchemyCommonImplementationsRepo or BeanieODMCommonImplementationsRepo.config = ServerConfig(...) in the root config.py or another module selected by LazyConfig.hexcore make-migrations and hexcore migrate when the schema changes.hexcore test.Before providing code, verify:
UseCase class with execute(command: InputDTO) -> OutputDTO?BaseEntity?QueryRequestDTO used at the boundary and QueryEntitiesUseCase used appropriately?async with uow:?entity_cls, model_cls or document_cls, not_found_exception, fields_resolvers, and fields_serializers?SqlAlchemyUnitOfWork built from an AsyncSession created by async_sessionmaker?is_nosql=True passed to the entity converter?uow.collect_entity() or @register_entity_on_uow?config = ServerConfig(...) defined in the default root module or a module resolved by LazyConfig?development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
development
Run, watch, debug, and extend OpenClaw QA testing with qa-lab and qa-channel. Use when Codex needs to execute the repo-backed QA suite, inspect live QA artifacts, debug failing scenarios, add new QA scenarios, or explain the OpenClaw QA workflow. Prefer the live OpenAI lane with regular openai/gpt-5.4 in fast mode; do not use gpt-5.4-pro or gpt-5.4-mini unless the user explicitly overrides that policy.
development
End-to-end Parallels smoke, upgrade, and rerun workflow for OpenClaw across macOS, Windows, and Linux guests. Use when Codex needs to run, rerun, debug, or interpret VM-based install, onboarding, gateway smoke tests, latest-release-to-main upgrade checks, fresh snapshot retests, or optional Discord roundtrip verification under Parallels.