diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..09c596f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,19 @@ +This repository is a **Copier template**, not a regular application. Read +`CONTRIBUTING.md` before changing anything; its guidance is imported below. + +@CONTRIBUTING.md + +## Key rules for agents working on the template + +- Edit the generated project under `template/`. The repository **root is + author-facing** (`copier.yml`, this file, `CONTRIBUTING.md`, `tests/`, + `.github/`, and the template-meta ADRs in `docs/adr/`) and is never rendered + into a generated project. +- Files ending in `.jinja` are rendered by Copier; use variables like + `{{ package_name }}` and `{{ project_name }}`. Wrap literal GitHub Actions + `${{ ... }}` in `{% raw %}{% endraw %}` inside `.jinja` files. Do not template + `uv.lock` (a post-copy task regenerates it). +- Validate every change by **baking**: `uv sync` then `uv run pytest` runs the + `database × include_user_example` matrix and the offline quality gate. +- The generated project's own agent guidance is `template/AGENTS.md` + (auto-loaded in generated projects via `template/CLAUDE.md`). diff --git a/template/AGENTS.md b/template/AGENTS.md index d322bd1..2f60b48 100644 --- a/template/AGENTS.md +++ b/template/AGENTS.md @@ -1,6 +1,6 @@ # Cosmic Python Engineer Instructions -This repository is a reusable Python standard for people and coding agents. +This project follows a Cosmic Python standard for people and coding agents. Work as a Cosmic Python engineer: preserve the domain-first philosophy from *Architecture Patterns with Python* while using the modern decisions recorded in [docs/adr/README.md](docs/adr/README.md). @@ -29,6 +29,25 @@ an ADR, run `make adr-check`. - Keep changes focused. Do not refactor unrelated code while completing a task. - Record a new ADR when changing a project-wide architectural default. +## Async and Persistence + +- The application is **async end to end** + ([ADR 0017](docs/adr/0017-async-persistence-by-default.md)). Routes, handlers, + the message bus, repositories, query readers, and the unit of work are `async`; + persistence uses `create_async_engine` and `AsyncSession`. Keep **domain objects + synchronous** — business rules perform no I/O. +- Never block the event loop inside `async def` (no synchronous DB driver, no + blocking call). `await` database work; offload genuinely blocking work to a + thread only with a documented reason. +- The default database is **PostgreSQL** via `asyncpg`; **SQLite** via `aiosqlite` + is the optional alternative + ([ADR 0018](docs/adr/0018-postgresql-default-with-pgvector.md)). Select the + driver through the database URL; do not hardcode a dialect in adapters. +- The Docker image bundles pgvector. Before using a `Vector` column, add + `CREATE EXTENSION IF NOT EXISTS vector` in an Alembic migration. +- Manage schema with Alembic (`make migrate`); reserve + `DATABASE_AUTO_CREATE_SCHEMA` for demos and tests. + ## Python Style - Write modern typed Python supported by the project version. @@ -42,6 +61,8 @@ an ADR, run `make adr-check`. - Use Pydantic models for boundary validation, settings, and immutable command or event schemas, not as a substitute for behavior-rich domain modeling. - Keep SQLAlchemy models and session usage inside persistence adapters. +- Use `async def` and `await` for I/O paths (adapters, handlers, routes) and an + `AsyncSession` — never a synchronous `Session` — in the request path. ## Architecture Workflow @@ -53,7 +74,8 @@ When implementing a feature: 4. Reuse a command as a boundary schema when contracts match; translate when they differ. 5. Orchestrate the use case in an application handler. -6. Access persistence through repositories and a unit of work. +6. Access persistence through async repositories and an async unit of work + (`async with uow: ... await uow.commit()`). 7. Keep write commands focused on one aggregate root by default. 8. Use reader ports and read models for query-only paths. 9. Wire dependencies in the composition root. @@ -65,15 +87,22 @@ versioned integration events before publishing public broker contracts. ## Testing Style -- Use pytest. +- Use pytest. Async tests run under `pytest-asyncio` (`asyncio_mode = "auto"`), + so write `async def` tests and `await` the code under test. - Write test scenarios using GIVEN / WHEN / THEN structure. - Use uppercase `GIVEN`, `WHEN`, and `THEN` in test docstrings. - Use `# GIVEN`, `# WHEN`, and `# THEN` comments when they improve the body of a non-trivial test. -- Prefer fast unit tests for domain behavior and handlers. -- Use fakes for owned ports such as repositories and units of work. -- Add integration tests for SQLAlchemy mappings, repositories, migrations, - transaction semantics, and external adapters. +- The **100% coverage gate comes from `tests/unit` + `tests/e2e` only** + ([ADR 0019](docs/adr/0019-coverage-from-unit-and-e2e-tests.md)). `tests/unit` + use plain objects, fakes, and mocked `AsyncSession`/ports; `tests/e2e` drive + routes through the ASGI app on **in-memory async SQLite** with no external + infrastructure. +- `tests/integration` exercise a **real PostgreSQL** (Docker). Mark every such + test `@pytest.mark.integration`; they are **excluded from coverage** and run in + a separate CI stage (`make integration`). Never rely on them for the gate. +- Prefer fast unit tests for domain behavior and handlers; use fakes for owned + ports such as repositories and units of work. - Keep end-to-end tests focused on critical wiring and user-visible behavior. ## Verification @@ -82,13 +111,15 @@ Use the project commands exposed by the `Makefile` and uv. For a code change, run the relevant focused tests first, then the available project checks: ```bash -make lint -make test -uv run pyrefly check +make lint # ruff check + ruff format --check + pyrefly +make cover # unit + e2e at 100% coverage (the gate) +make integration # PostgreSQL integration tier (requires Docker) ``` -If a documented check is not yet wired into the evolving scaffold, say so -clearly and add the missing wiring when the task calls for it. +`make cover` is the coverage gate; `make integration` needs a running +PostgreSQL and is not part of the coverage percentage. If a documented check is +not yet wired into the evolving scaffold, say so clearly and add the missing +wiring when the task calls for it. ## Git diff --git a/template/CLAUDE.md b/template/CLAUDE.md new file mode 100644 index 0000000..e5d9997 --- /dev/null +++ b/template/CLAUDE.md @@ -0,0 +1,5 @@ +The canonical agent instructions for this project live in `AGENTS.md` (the +cross-tool standard). They are imported below so Claude Code loads them +automatically. + +@AGENTS.md