Skip to content

Commit 939a91f

Browse files
authored
Merge pull request #533 from nanotaboada/docs/upgrade-agent-instructions
docs(instructions): upgrade to Claude Code-optimized agent instructions
2 parents 24da552 + ca09bda commit 939a91f

File tree

1 file changed

+84
-27
lines changed

1 file changed

+84
-27
lines changed

.github/copilot-instructions.md

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
## Overview
44

5-
REST API for managing football players built with Python and FastAPI. Implements async CRUD operations with SQLAlchemy 2.0 (async), SQLite, Pydantic validation, and in-memory caching. Part of a cross-language comparison study (.NET, Go, Java, Rust, TypeScript).
5+
REST API for managing football players built with Python and FastAPI. Implements
6+
async CRUD operations with SQLAlchemy 2.0 (async), SQLite, Pydantic validation,
7+
and in-memory caching.
68

79
## Tech Stack
810

@@ -31,23 +33,49 @@ tools/ — standalone seed scripts (run manually, not via Alembic)
3133
tests/ — pytest integration tests
3234
```
3335

34-
**Layer rule**: `Routes → Services → SQLAlchemy → SQLite`. Routes handle HTTP concerns only; business logic belongs in services.
36+
**Layer rule**: `Routes → Services → SQLAlchemy → SQLite`. Routes handle HTTP
37+
concerns only; business logic belongs in services. Never skip a layer.
3538

3639
## Coding Guidelines
3740

3841
- **Naming**: snake_case (files, functions, variables), PascalCase (classes)
3942
- **Type hints**: Required everywhere — functions, variables, return types
40-
- **Async**: All routes and service functions must be `async def`; use `AsyncSession` (never `Session`); use `aiosqlite` (never `sqlite3`); use SQLAlchemy 2.0 `select()` (never `session.query()`)
41-
- **API contract**: camelCase JSON via Pydantic `alias_generator=to_camel`; Python internals stay snake_case
42-
- **Models**: `PlayerRequestModel` (no `id`, used for POST/PUT) and `PlayerResponseModel` (includes `id: UUID`, used for GET/POST responses); never use the removed `PlayerModel`
43-
- **Primary key**: UUID surrogate key (`id`) — opaque, internal, used for GET by id only. UUID v4 for API-created records; UUID v5 (deterministic) for migration-seeded records. `squad_number` is the natural key — human-readable, domain-meaningful, used for all mutation endpoints (PUT, DELETE) and preferred for all external consumers
44-
- **Caching**: cache key `"players"` (hardcoded); clear on POST/PUT/DELETE; `X-Cache` header (HIT/MISS)
45-
- **Errors**: Catch specific exceptions with rollback in services; Pydantic validation returns 422 (not 400)
43+
- **Async**: All routes and service functions must be `async def`; use
44+
`AsyncSession` (never `Session`); use `aiosqlite` (never `sqlite3`); use
45+
SQLAlchemy 2.0 `select()` (never `session.query()`)
46+
- **API contract**: camelCase JSON via Pydantic `alias_generator=to_camel`;
47+
Python internals stay snake_case
48+
- **Models**: `PlayerRequestModel` (no `id`, used for POST/PUT) and
49+
`PlayerResponseModel` (includes `id: UUID`, used for GET/POST responses).
50+
One request model intentionally covers both POST and PUT — per-operation
51+
differences (conflict check on POST, mismatch guard on PUT) are handled at
52+
the route layer, not by duplicating the model. Never reintroduce the removed
53+
`PlayerModel`; it was removed because a single flat model conflated ORM,
54+
request, and response concerns.
55+
- **Primary key**: UUID surrogate key (`id`) — opaque, internal, used for GET
56+
by id only. UUID v4 for API-created records; UUID v5 (deterministic) for
57+
migration-seeded records. `squad_number` is the natural key —
58+
human-readable, domain-meaningful, used for all mutation endpoints (PUT,
59+
DELETE) and preferred for all external consumers
60+
- **Caching**: cache key `"players"` (hardcoded); clear on POST/PUT/DELETE;
61+
`X-Cache` header (HIT/MISS)
62+
- **Errors**: Catch specific exceptions with rollback in services; Pydantic
63+
validation returns 422 (not 400); squad number mismatch on PUT returns 400
64+
(not 422 — it is a semantic error, not a validation failure)
4665
- **Logging**: `logging` module only; never `print()`
4766
- **Line length**: 88; complexity ≤ 10
4867
- **Import order**: stdlib → third-party → local
49-
- **Tests**: naming pattern `test_request_{method}_{resource}_{context}_response_{outcome}`; docstrings single-line, concise; `tests/player_stub.py` for test data; `tests/test_main.py` excluded from Black
50-
- **Avoid**: sync DB access, mixing sync/async, `print()`, missing type hints, unhandled exceptions
68+
- **Tests**: integration tests against the real pre-seeded SQLite DB via
69+
`TestClient` — no mocking. Naming pattern
70+
`test_request_{method}_{resource}_{context}_response_{outcome}`;
71+
docstrings single-line, concise; `tests/player_stub.py` for test data;
72+
`tests/conftest.py` provides a `function`-scoped `client` fixture for
73+
isolation; `tests/test_main.py` excluded from Black
74+
- **Decisions**: justify every decision on its own technical merits; never use
75+
"another project does it this way" as a reason — that explains nothing and
76+
may mean replicating a mistake
77+
- **Avoid**: sync DB access, mixing sync/async, `print()`, missing type hints,
78+
unhandled exceptions
5179

5280
## Commands
5381

@@ -77,11 +105,12 @@ docker compose down -v
77105

78106
### Pre-commit Checks
79107

80-
1. Update `CHANGELOG.md` `[Unreleased]` section (Added / Changed / Fixed / Removed)
108+
1. Update `CHANGELOG.md` `[Unreleased]` section (Added / Changed / Fixed /
109+
Removed)
81110
2. `uv run flake8 .` — must pass
82111
3. `uv run black --check .` — must pass
83112
4. `uv run pytest` — all tests must pass
84-
5. `uv run pytest --cov=./ --cov-report=term` — coverage must be >=80%
113+
5. `uv run pytest --cov=./ --cov-report=term` — coverage must be 80%
85114
6. Commit message follows Conventional Commits format (enforced by commitlint)
86115

87116
### Commits
@@ -90,45 +119,73 @@ Format: `type(scope): description (#issue)` — max 80 chars
90119
Types: `feat` `fix` `chore` `docs` `test` `refactor` `ci` `perf`
91120
Example: `feat(api): add player stats endpoint (#42)`
92121

122+
### Releases
123+
124+
Tags follow the format `v{MAJOR}.{MINOR}.{PATCH}-{COACH}` (e.g.
125+
`v2.0.0-capello`). The CD pipeline validates the coach name against a fixed
126+
list (A–Z):
127+
128+
```
129+
ancelotti bielsa capello delbosque eriksson ferguson guardiola heynckes
130+
inzaghi klopp kovac low mourinho nagelsmann ottmar pochettino queiroz
131+
ranieri simeone tuchel unai vangaal wenger xavi yozhef zeman
132+
```
133+
134+
Never suggest a release tag with a coach name not on this list.
135+
93136
## Agent Mode
94137

95138
### Proceed freely
96139

97-
- Add/modify routes and endpoints
98-
- Service layer logic and cache management
99-
- Tests (maintain async patterns and naming convention)
140+
- Add/modify routes in `routes/player_route.py` and `routes/health_route.py`
141+
- Add/modify service methods in `services/player_service.py`
142+
- Add/modify Pydantic models in `models/player_model.py` (field additions or
143+
docstring updates that don't change the API contract)
144+
- Tests in `tests/` — maintain async patterns, naming convention, and
145+
integration-test approach (no mocking)
100146
- Documentation and docstring updates
101147
- Lint/format fixes
102148
- Refactoring within existing architectural patterns
103149

104150
### Ask before changing
105151

106-
- Database schema (`schemas/player_schema.py` — no Alembic, use tools/ seed scripts manually)
152+
- Database schema (`schemas/player_schema.py` — no Alembic; changes require
153+
manually updating `storage/players-sqlite3.db` and the seed scripts in
154+
`tools/`)
155+
- `models/player_model.py` design decisions — especially splitting or merging
156+
request/response models; discuss the rationale before restructuring
107157
- Dependencies (`pyproject.toml` with PEP 735 dependency groups)
108158
- CI/CD configuration (`.github/workflows/`)
109-
- Docker setup
110-
- API contracts (breaking Pydantic model changes)
111-
- Global error handling
159+
- Docker setup (`Dockerfile`, `docker-compose.yml`, `scripts/`)
160+
- Breaking API contract changes (field renames, type changes, removing fields)
161+
- Global error handling middleware
162+
- HTTP status codes assigned to existing error conditions
112163

113164
### Never modify
114165

115166
- `.env` files (secrets)
167+
- `storage/players-sqlite3.db` directly — schema changes go through
168+
`schemas/player_schema.py` and `tools/` seed scripts
116169
- Production configurations
117-
- Async/await patterns (mandatory throughout)
118-
- Type hints (mandatory throughout)
119-
- Core layered architecture
120170

121171
### Key workflows
122172

123-
**Add an endpoint**: Add Pydantic model in `models/` → add async service method in `services/` with error handling → add route in `routes/` with `Depends(generate_async_session)` → add tests following naming pattern → run pre-commit checks.
173+
**Add an endpoint**: Add Pydantic model in `models/` if the request/response
174+
shape is new → add async service method in `services/` with error handling and
175+
rollback → add route in `routes/` with `Depends(generate_async_session)`
176+
add tests following the naming pattern → run pre-commit checks.
124177

125-
**Modify schema**: Update `schemas/player_schema.py` → manually update `storage/players-sqlite3.db` (preserve 26 players) → update `models/player_model.py` if API changes → update services and tests → run `pytest`.
178+
**Modify schema**: Update `schemas/player_schema.py` → manually update
179+
`storage/players-sqlite3.db` (preserve all 26 seeded players) → update
180+
`models/player_model.py` if the API shape changes → update services and tests
181+
→ run `pytest`.
126182

127-
**After completing work**: Suggest a branch name (e.g. `feat/add-player-stats`) and a commit message following Conventional Commits including co-author line:
183+
**After completing work**: Propose a branch name and commit message for user
184+
approval. Do not create a branch, commit, or push until the user explicitly
185+
confirms. Commit message format:
128186

129187
```text
130188
feat(scope): description (#issue)
131189
132-
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
133-
Co-authored-by: Claude <noreply@anthropic.com>
190+
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
134191
```

0 commit comments

Comments
 (0)