--- name: agile-13-sprint-closeout description: "Mandatory end-of-sprint epic gate, 3 lenses: engineer (smoke + integration), architect (Confluence specs vs delivered code), tech lead (deep severity-graded review of all sprint diffs). Triggers: sprint closeout, close sprint, /sprint-closeout. After last merge + agile-12-tech-debt-sweep, before retro." --- # agile_13_sprint_closeout **Mandatory final step of every sprint** — after the last story merges to `main`, after `agile-12-tech-debt-sweep`, before `agile-15-retro`. The bug class it catches is *"all unit + integration tests pass and production is broken"*: every story-level AC met in isolation while the wired-together system carries a silent regression nobody exercised. Three lenses, each catching a class of failure invisible to the others. **A single Critical finding from any lens blocks closeout** — engineer-lens green is not enough. 1. **Engineer** — does the wired-together system actually work on a freshly rebuilt dev stack? (Phases 0–2, 5–6) 2. **Architect / PM** — does the delivered code match the documented intent? (Phase 3) 3. **Tech Lead** — does it hold up under an impartial deep review? (Phase 4) Phases 3 and 4 are read-only and independent — run them concurrently, and alongside a stack rebuild already in progress; only Phases 5–6 need the stack to themselves. **This is the third and broadest review layer, by a different role than the per-PR reviews.** The author self-reviewed each change (`implement-review`) and an independent reviewer gated each PR (`merge-review-pr`) — both *per PR*. This pass asks whether the **whole sprint, wired together**, is aligned with the sprint/epic goal and the documented product and architecture intent. The per-PR reviews could not see system-level drift; do not assume they covered it. **Non-goals:** rubber-stamping tickets because their story-level ACs are ticked; trusting integration tests that bypass the broker; skipping the dev-stack smoke because CI was green; deferring to "the author probably had a reason". The skill is expected to take 30–60 minutes — that beats finding the bug, the silent product drift, or the load-bearing dead code a month later. **Prerequisite: `agile-12-tech-debt-sweep` must have run** and its approved fixes applied. Closeout's smoke replays the user flow described in `CLAUDE.md`, so `CLAUDE.md` must accurately reflect main — no stale tree comments, no useless CI workflows skewing the pipeline, no leaked personal tags — and any prebuild-image extractions must have landed before closeout exercises the new stack. ## Configuration From the consumer repo's `CLAUDE.md` / `AGENTS.md`: **`cloudId`** (required); **`confluence-project-root`** — page id or title of the folder holding Vision Doc / PRD / Design Brief / ADR / Roadmap / Retrospectives / Closeouts (required for Phases 3 and 7); **`done-status-name`** — the project-local terminal state ("Done", "Terminé(e)", "Closed"); **lint / unit / integration commands**, called as opaque commands; **dev-stack bring-up commands**, typically `docker compose down && docker compose up -d --build --wait` plus migration. **Input:** epic key from args (else inferred from sprint context, or ask). Optional `--skip product` (Phase 3 — greenfield repo with no Confluence specs) and `--skip techlead` (Phase 4 — a mid-sprint sanity check only; a real closeout never skips it). Use sparingly; the default is all lenses. ## Phase 0 — Load epic spec Read the epic in full via `mcp__atlassian__getJiraIssue` — summary, description, scope, epic-level ACs, dependencies. The epic is the spec. Then `mcp__atlassian__searchJiraIssuesUsingJql` with `"Epic Link" = ORDER BY key ASC` and build a child table (key, status, type, summary). **Every child must be Done, or carry an explicit reason it is deferred** — any child in a non-terminal column blocks closeout. Oversized JQL result → extract with `jq -r '.issues.nodes[] | "\(.key)\t\(.fields.status.name)\t\(.fields.issuetype.name)\t\(.fields.summary)"' `. ## Phase 1 — Map epic ACs to code + tests Per epic-level AC, produce a matrix of the **code site(s)** (`file:line`), the **unit test(s)** exercising it at function level, and the **integration test(s)** exercising it against a real stack (DB, broker, gateway). An AC with no integration test — or only eager-execution / direct-call tests that bypass the broker — is a red flag: record it and design a Phase 6 smoke that exercises the missing path. Failure modes worth hunting specifically: **cross-service dispatch** (service A calls `send_task("service_b.task")` with nothing testing broker → queue → consumer, leaving the routing config untested — a `.ping` round-trip is worth adding); **API field-name drift** (verify subgraph fields via real introspection *through the gateway*, not unit tests on the subgraph); **beat/cron wiring** (the scheduled task name must resolve in the worker include path — check via the scheduler's introspection or the live container log); **auth and admin gates** reachable from the gateway's forwarded-header path. **A data-driven AC needs evidence from data nobody seeded.** A fixture built to demonstrate a feature can differ from production in exactly the dimension that matters, so a green seeded path shows the code runs — not that it runs on real input. Confirm at least one unseeded record satisfies the AC before recording it as evidenced; where none exists yet, that absence *is* the AC's status, and the fixture does not stand in for it. ## Phase 2 — Static cross-checks Lint exits 0 across all source paths. The unit suite passes with coverage at or above the configured threshold. **Doc drift:** every new test file appears in the test-suite `CLAUDE.md` tree, coverage table, and run-command section; every new service or convention is reflected in the relevant `CLAUDE.md`. **Schedule sanity:** scheduled tasks reference names that resolve in worker modules, each with any required gate (market-hour, business-day). ## Phase 3 — Architecture + Product alignment (Architect / PM lens) Wear the architect and PM hat, not the engineer hat: integration tests stay green while the system silently ships out-of-scope features, drops promised scope, or violates an ADR invariant. **3.1 — Load the spec corpus** from `confluence-project-root`: **Vision Doc** (principles, KPIs, hard constraints), **PRD** (scope, out-of-scope list, business goals), **Design Brief / Specs UI** (UI epics), **ADR** (decisions + invariants), **Roadmap** (the iteration goal for the sprint being closed), and any per-epic design doc linked from the ticket. **Read each in full, not in summary** — a drift catch requires knowing what the spec actually says, not your memory of it. A missing or stale doc is a Minor finding ("PRD has no out-of-scope list — cannot verify scope creep"). **3.2 — Build the alignment table:** | Source | Statement | Code site (or "not implemented") | Status | |---|---|---|---| | Vision Doc principle #N | "No automated execution" | `backend/api_signal/…` — explicit confirmation modal | ✅ Aligned | | PRD §X | "Out of scope: per-user OHLCV" | `backend/api_data/…` — system-scoped table | ✅ Aligned | | ADR-04 | "Services never call each other directly" | `backend/shared/celery_app.py:send_task` only | ✅ Aligned | | PRD §Y | "Iteration goal: signal feed live" | not implemented | ❌ Drifted — scope dropped without doc update | | Roadmap Iteration N | "Capacity = 28 pts" | delivered 32 pts | ⚠️ Minor — over-delivery, retro signal | **3.3 — Categorise.** **Critical drift** — code violates an ADR invariant, ships an out-of-scope feature without a doc update, or breaks a Vision Doc principle: blocks closeout; file a bug and fix or roll back. **Minor drift** — over/under delivery against the iteration goal, a stale spec doc, an intentional but undocumented design deviation: retro input, does not block. **3.4 — Cross-epic consistency.** With multiple epics in one sprint, walk the surface they share (data model, API contract, auth boundary, UI navigation). Two epics each passing their own ACs while together breaking a shared invariant is the failure this step exists for. ## Phase 4 — Tech Lead deep code review (impartial) Flag every issue you would flag if a stranger wrote this code. **4.1 — Scope the diff.** Find the sprint-start commit (`git log --merges --first-parent --since="" --format='%H %s' main`), then `git diff --name-only ..HEAD`. **Read every changed file in full** — the diff hides surroundings, and bugs hide in surroundings. **4.2 — Lenses, all of them:** - **Correctness** — logic errors, edge cases, null handling, type contracts, model ↔ migration ↔ test consistency. - **Security** — input validation at boundaries, hardcoded secrets, SQL injection via interpolation, auth gate placement, header-trust assumptions. - **Architecture invariants** — every invariant from the root and sub `CLAUDE.md` (data scoping, naming, async patterns, federation rules, no cross-service imports, append-only tables). - **Naming + conventions** — case style per language, explicit constraint names, file/function naming. - **Test coverage depth, not count** — does each test actually exercise its claimed AC? Reasonable mock placement? Negative paths? Order-dependence risk (`sys.modules` substring cleanup, module-level state, env leaks across tests)? Hardcoded forward calendar dates that will rot? - **Documentation drift** — file annotations, coverage tables, run-command sections, stale AC text. - **DRY + readability** — copy-paste ≥3 lines worth extracting, magic numbers, contradicted comments, unused imports, misleading names, dead branches. - **Performance** — N+1 queries, unbounded queries with no `LIMIT`, missing indexes on hot paths, sync calls inside async resolvers, blocking I/O in the event loop. - **Operational** — structured logging present? Errors propagated explicitly (no silent `except: pass`)? Restart-safe (idempotent handlers, no lost in-flight state)? Observability for new code paths? - **Migrations** — `server_default` uses `text()` not `func.literal()`; drop order reverses create order; FK targets schema-qualified; hypertable created after its base table. **4.3 — Severity, one per finding.** **Critical** — a runtime error, data corruption, security issue, autogenerate drift, or architecture-invariant breach; blocks closeout. **Minor** — misleading docs, a wrong port in a docstring, a missing run command, a stale AC description, worthwhile cleanup; files as cleanup tickets. **Nit** — style preference, not load-bearing; listed for the team to decide. **Nits are valid output here** — closeout is the exhaustive pass (merge-train review is deliberately two-tier). Don't promote nits to Minor to feel rigorous, or drop them to feel kind. **4.4 — Output** a numbered, severity-grouped list, Critical first, each with `file:line` + a one-sentence root cause + a one-sentence fix: ``` ### Critical 1. `backend/api_X/resolvers/Y.py:42` — SQL injection via f-string interpolation of a user-supplied filter. Fix: `text("… :filter").bindparams(filter=…)`. ### Minor 1. `backend/api_X/…/Z.py:88` — 12-line copy-paste of the error handling in `W.py:120`. Fix: extract `_handle_subgraph_error()`. ### Nit 1. `frontend/src/…/A.tsx:55` — magic `4000` (toast duration) repeated in 3 components. Fix: `TOAST_DURATION_MS`. ``` **4.5 — Impartiality is this lens's whole job.** Flag anything that would confuse a stranger in six months; dismissing an intentional finding is the team's call, not yours. ## Phase 5 — Integration suite vs a fresh testing stack Tear the testing stack down **with volumes** (a stale DB schema is the most common silent-failure cause), rebuild it from scratch (`docker compose … up -d --build --wait` or the project's equivalent — cached images defeat the purpose), then run the full integration suite from the repo root with the standard env block. If a session-start fixture rebuilds the stack, do not skip it; the rebuild *is* the point. Every test must pass; record the runtime. A flake gets one retry, then a follow-up ticket. ## Phase 6 — Dev stack smoke test (the part that catches the silent bugs) 1. **`docker compose down -v`** — cached state from prior runs is the most common silent-failure cause. 2. **Rebuild from scratch** per the documented bring-up commands. **Rebuild the migration runner too, not just the app images.** A containerized migrate step run against a stale/cached runner applies whatever revisions were baked into that image — so a migration added this sprint silently no-ops, the runner reports success, and the freshly-built app then hits a schema missing the new objects. Force the runner to carry current code (`--build`), then **confirm the head actually advanced** by querying the migration-version table. Never trust a green migrate log alone. 3. **Reach the stack via its documented DNS hostname, not `localhost`.** A `localhost:` smoke bypasses Traefik routing, DNS resolution, and healthcheck propagation — exactly the layers most likely to be silently broken for every other operator on the LAN. If the repo's `CLAUDE.md` declares a `TRAEFIK_DOMAIN`, the smoke **must** use it: ```bash curl -fsS http:///health curl -fsS http://api./health curl -fsS http://traefik. >/dev/null && echo "traefik dashboard reachable" ``` 4. **Forge an admin credential** from the dev secret in `.env` if the flow needs auth (adapt to the project's scheme): ```python from jose import jwt, datetime secret = Path(".env").read_text().split("JWT_SECRET=")[1].split()[0] jwt.encode({"sub":"smoke","user_id":"","is_admin":True, "exp": datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(hours=1)}, secret, algorithm="HS256") ``` 5. **Run the actual user flow the epic delivers**, against the DNS hostname. Not "API introspection works" — introspection passes whenever the federation is wired and proves nothing about whether the underlying task executes. If Phase 3 surfaced a documented-but-untested path, exercise it here. 6. **Monitor workers + scheduler for the whole smoke window** with a filter wide enough to catch every terminal signature — silence is only success if the filter would have fired on a crash: ``` docker compose logs -f --since=2m 2>&1 | grep -E --line-buffered "Traceback|Error|ERROR|FAILED|crashed|" ``` 7. **Confirm the side-effects in the DB** — `docker compose exec -T psql -U -d -c "