Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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`).
55 changes: 43 additions & 12 deletions template/AGENTS.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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

Expand Down
5 changes: 5 additions & 0 deletions template/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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
Loading