tdd · git:20260727.2d90a2c · 2026-07-27 · sha256 641d02e3778dd9e1
tdd git:20260727.2d90a2cA
Immutable. This exact content is served forever at /api/v1/blob/641d02e3778dd9e1.
--- name: tdd description: The test-first gate. Routed to by /feature (after the spec is approved), /fix, and /refactor before any implementation code is written. Six gated phases — obligation scan, red, green, obligation verify, coverage, lint. No feature code exists before Phase 1 clears; nothing reaches commit-gate until all six are green. --- # tdd Test-first, or it does not ship. Routed to by `/feature` (after spec approval), `/fix`, and `/refactor`. ## Pre-flight Read these, or STOP and surface the gap — never guess a command or a threshold: - `<project-root>/.codearbiter/CONTEXT.md` — the `stage:` frontmatter (the maturity value) and project context. - `<project-root>/.codearbiter/tech-stack.md` — test, coverage, and lint invocations; file layout; mock patterns. - `<project-root>/.codearbiter/coding-standards.md` — style, structure, naming. Required for Phase 3. - `<project-root>/.codearbiter/specs/<slug>.md` — the approved spec, when `/feature` produced one. It is the primary obligation source. - `<project-root>/.codearbiter/security-controls.md` — only when the change touches a security boundary (auth, crypto, secrets, a trust boundary). Optional; absent on most changes. - `<project-root>/.codearbiter/code-map.md` — if present, a coarse concern→path→role map to orient before writing tests and code. Absent is fine — it is read-on-demand, populated by context-creation or commit-gate heal. ## Phase 1 — Obligation scan · gate: BLOCK An **obligation** is one verifiable claim about the change: (a) a unique ID, (b) a source citation, (c) a status. Status moves `OPEN → MAPPED → COVERED`; an obligation Phase 4 cannot tie to a passing test is `MISSING`. "We should test X" is not an obligation. Derive every obligation before any code is written, and record each as `ID · source · OPEN`: - **Spec** — one obligation per acceptance criterion in the approved spec. - **Contract** — API and input-validation invariants, error responses, boundary conditions. - **Security** — only when `security-controls.md` applies: the assertion that the security-relevant boundary holds. Gate: the obligation list is complete. **Auto-pass** when every obligation maps one-to-one onto the acceptance criteria of an already-approved spec (full-lane spec or small-lane mini-spec) — the user approved that list once; do not re-ask. **User review is required** only for obligations derived BEYOND the spec (Contract and Security rows): surface just those additions, not the whole list. Under `/sprint`, spec-derived obligations auto-pass the same way and beyond-spec additions are SMARTS-decided and logged like any other auto-decision. A partial list never passes either way. ## Phase 2 — Red · gate: BLOCK Write one or more failing tests per obligation. Bind each test ID to its obligation ID and move that obligation `OPEN → MAPPED`. Run the test command from `tech-stack.md`. - Every new test MUST fail, and fail **for the right reason** — the assertion, not an import error or a typo. A new test that passes with no implementation is wrong; fix it before continuing. - Every pre-existing test MUST stay green. One that breaks here is a conflict — stop and surface it. Reject the standard traps: asserting on a mock instead of behavior; a test that can never fail; a snapshot so broad it asserts nothing; coupling to an implementation detail instead of observable behavior; asserting on the framework's behavior rather than your own. Gate: the runner confirms new tests red (for the right reason) and existing tests green, with every obligation `MAPPED` to a failing test. No implementation code is written until this gate clears. ## Phase 3 — Green · gate: BLOCK Write the **minimum** implementation that satisfies the Phase 2 tests — no speculative logic, no gold-plating — to the conventions in `coding-standards.md`. Run the full suite. A broken pre-existing test is a regression: fix it. Gate: full suite green, reached by satisfying the Phase 2 tests — not by weakening them. A test's assertions MUST be unchanged between red and green; only fixtures and setup may move. A relaxed assertion is a gate violation. ## Phase 4 — Obligation verify · gate: BLOCK Walk the Phase 1 list item by item. Each `MAPPED` obligation moves to `COVERED` (a real passing test that exercises the claim) or `MISSING` (no test truly covers it). For security-relevant or contract-critical logic, dispatch the `coverage-auditor` agent (`<plugin-root>/agents/coverage-auditor.md`) to confirm the tests exercise the claim. A `MISSING` obligation returns the workflow to Phase 2 — author a correct failing test, then re-run Phase 3 — and loops until it is `COVERED`. **Stakes:** when you block on a `MISSING` obligation, state what the untested seam leaves exposed, not just that it is `MISSING` — one line naming the consequence: "this error path is untested; a 500 here would reach users silently." The block is the rule; the stakes are why it is worth the friction. Gate: every obligation `COVERED`, each backed by a passing test. Any `MISSING` blocks Phase 5. ## Phase 5 — Coverage · gate: BLOCK Coverage scales with the maturity value (`stage:` in `CONTEXT.md`) — a rigor knob, not a promotion gate. The threshold table is the shared `<plugin-root>/includes/maturity-coverage.md` (the single source of truth, also used by `refactor` Phase 2). Run the coverage command from `tech-stack.md`. **Lines and branches must both clear the threshold** — a report satisfying one and not the other does not pass (issue #507). Below it on either → add tests until both are met. Where the surface has no coverage tooling at all, take the no-tooling exemption in `<plugin-root>/includes/maturity-coverage.md` — which requires QUOTING the `tech-stack.md` Coverage section that omits a command for this surface — and pass the phase on the Phase 4 obligation verify alone. Without that citation the phase STOPs: "I could not find the command" and "this surface has none" look identical from here and demand opposite responses. Do NOT invent a command, and do NOT silently skip the phase as though it had been run — a gate that cannot execute still reads as satisfied, which is worse than an absent one. **Stakes:** when coverage blocks below threshold, name the class of code left dark — the paths a later regression could rot unnoticed — not just "below threshold." The number is the rule; the untested paths are why it matters. Gate: threshold met on BOTH lines and branches for the current maturity value, or the no-tooling exemption taken WITH its citation. A test added only to move the percentage fails this gate in spirit — it converts an honest red into a green that asserts nothing. ## Phase 6 — Lint · gate: BLOCK Run lint, and the type-check if the project is statically typed, from `tech-stack.md`. Resolve every error. Gate: clean lint and type-check, zero errors — this is what clears the path to `commit-gate`. "Mostly passes" is not passing. ## Hard rules - MUST NOT skip, suppress, or comment out a test to clear any gate. - MUST NOT mark an obligation `COVERED` without a passing test that exercises the claim. - MUST NOT lower a coverage threshold without a decision recorded in `CONTEXT.md`. - MUST NOT inline-suppress a lint rule without a written reason, and never to bypass a security-relevant rule. - MUST NOT guess the test, coverage, or lint command — read `tech-stack.md` or STOP. - MUST, at exit, run the follow-up harvest (`<plugin-root>/includes/harvest.md`) over any `[NEEDS-TRIAGE]` raised this run — batch-confirm promoting work to `open-tasks.md` and decisions to `open-questions.md` so nothing languishes; nothing auto-promotes interactively.