# AGENTS.md — operating rules for this repository ## 1. Read this order, every time 1. `.agents/PLAN.md` — master design. Its **Architectural Anchors** table holds the **LOCKED DECISIONS**, which are binding on every task and phase. 2. This file. 3. `.agents/phases/todo//00_phase.md`, then the task file you are executing. 4. The user story referenced by the phase (`.agents/user_stories/US-XX_*.md`). 5. `.agents/phases/complete/*/00_phase.md` for what already exists (do not re-build it). ## 2. The phased protocol - Phase state lives in files, not chat. Pending work: `.agents/phases/todo/NN_name/` (`00_phase.md` + `NN_task.md`). Finished work: `.agents/phases/complete/`. - Execution is owned by the `phased-execution` skill (`run-task.sh`, `run-phase.sh`, `auto-phase.sh`). Each task runs in a fresh subprocess and must pass `.agents/validate.sh` before it moves to `complete/`. - New work is authored by the `phase-authoring` skill as a phase directory — not implemented straight from chat. - **Never** modify `.agents/PLAN.md` or anything under `.agents/phases/complete/`. - **Ask the user** before editing an existing file in `.agents/phases/todo/`. - Do not renumber or reuse phase/task numbers. ## 3. LOCKED DECISIONS (summary — the table in PLAN.md is authoritative) - **Python 3.12+ / uv / FastAPI / native WebSockets** (`uvicorn[standard]`). No Socket.IO, no SSE, no WebRTC, no REST for live game traffic. - **Server-authoritative fixed-tick simulation** (20 Hz) with client interpolation. The browser never decides anything true. `src/undercooked/sim/` is pure: **no FastAPI, no asyncio, no I/O, no wall-clock, no `random` without the round RNG**. - **No database, no ORM, no Alembic.** Live state lives in the room supervisor; results/scoreboards live in memory and die with the process. User-approved deviation from the default FastAPI stack. - **Data-driven content.** Levels/recipes/hazards come from YAML in `levels/` loaded through validated Pydantic models. No hard-coded level or recipe lists in Python. - **Client: Canvas 2D + vanilla ES modules + this repo's own SVG art.** No bundler, no framework, no CDN, no analytics, no external fonts. - **Tests: pytest + Playwright.** One Playwright suite per user story; multi-player stories use two browser contexts against one room. - Quality gate: ruff + pyright + full pytest, **>90% coverage** on new/modified code, **entire suite 100% passing** before a phase may complete or commit. ## 4. Definition of done (every task) - [ ] Task's own work exists at the named paths and is importable/runnable. - [ ] Tests written for the task's logic (unit for pure logic, integration for rooms/protocol, e2e for user-visible flows) and passing. - [ ] **Full** project suite passes (`uv run pytest`), coverage gate met — including failures the task did not cause: fix them, do not skip, xfail, or delete tests. - [ ] `uv run ruff check .` and `uv run pyright` report zero errors. - [ ] `uv run undercooked-levels validate` passes if any level/content file changed. - [ ] `README.md` and `AGENTS.md` updated when the change is user-visible (setup, commands, config, protocol, features, conventions). Every phase's final pass re-reviews both **in full, project-wide** and fixes inconsistencies, including pre-existing ones. - [ ] App still launches: `uv run undercooked` serves `http://127.0.0.1:8000/healthz` with `200` and the client at `/`. - [ ] No behavior regression in any completed phase. ## 5. Hard rules - **No external CDNs or remote assets.** `tests/unit/test_no_external_assets.py` fails the build on `http://`/`https://`/`//cdn` references in `app/static/` and templates. CSP is `default-src 'self'` (plus `ws:`/`wss:` same-origin). - **No new technology** (dependency, framework, service) without explicit written user permission recorded in the phase's `00_phase.md` and signed off into PLAN.md. - **The simulation is the single source of truth.** Client-side values are guesses. Never trust inbound coordinates, scores, timers, or level ids — validate every inbound field against the room's state (`server/protocol.py`). - **Anonymous by design.** No accounts, no emails, no cookies with personal data. Identity = a random per-browser token (`secrets.token_urlsafe`) held in `sessionStorage`. Names are sanitized, length-capped, non-authoritative. - **Frame and rate limits are enforced, not suggested**: `max_message_bytes`, `max_inputs_per_s`, room caps, and message-type whitelists per room state. - **Protocol changes need a version bump**: editing a wire message means editing `src/undercooked/server/protocol.py`, `docs/PROTOCOL.md`, and the client in the same task, and bumping `PROTOCOL_VERSION` (PLAN §11). - **Determinism is a contract.** `sim/step.py` must produce identical state given the same seed and input script — the replay tests in `tests/sim/` are the guard. - **ntfy is outbound-only.** Never use it to ask questions or wait for approval. If a task is ambiguous, make the best judgment consistent with PLAN.md, or stop and let the user rewrite the task. - **Commits** are made by the pipeline harness (`PHASE_COMMIT=1`), one atomic commit per completed phase, `--no-gpg-sign`, subject `phase: `. Child executors never run `git add`/`commit`/`mv`/`rm`/`stash`. When a human asks for a commit, use the `git-commit` skill (`--no-gpg-sign`, `LLM-Generated: true` trailer). - Do not commit `.env`, logs, coverage output, `.agents/phase-sessions/`, `.agents/pipeline.log`, or `.agents/reports/` (all gitignored). ## 6. Where things go | Change | Location | |---|---| | Pure game rules (movement, stations, orders, scoring, hazards) | `src/undercooked/sim/` | | Level/recipe content | `levels/*.yaml` | | Rooms, lobby lifecycle, session records | `src/undercooked/server/rooms.py`, `room.py` | | Wire messages + validation | `src/undercooked/server/protocol.py`, `docs/PROTOCOL.md` | | HTTP app, static serving, headers | `src/undercooked/server/app.py` | | Renderer, HUD, input, interpolation | `app/static/js/`, `app/static/assets/` | | User-visible flow contract | `.agents/user_stories/US-XX_*.md` | Tests mirror that: `tests/unit/`, `tests/server/` (HTTP routes and stores), `tests/integration/` (live uvicorn + WebSocket clients), `tests/sim/` (headless determinism and replays), `tests/e2e/` (Playwright, named `test_usNN_*.py`, marked `e2e`).