tdd
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.
What it does
Section titled “What it does”This is the test-first gate — no feature code exists before it clears its first phase. The feature command routes here after your spec is approved, and the fix and refactor commands route here directly. It turns the spec (or the fix’s regression obligation) into a list of concrete, verifiable claims, forces a failing test for each one before any implementation, then verifies coverage and cleanliness before the change is eligible for the commit gate.
Phases
Section titled “Phases”- Derive every obligation the change must satisfy from the spec, the contract, and — where relevant — the security boundary, before any code is written.
- Write a failing test for each obligation, confirming it fails for the right reason and that every pre-existing test still passes.
- Write the minimum implementation that satisfies those tests, without weakening any assertion.
- Walk the obligation list again, moving each to genuinely covered by a real passing test, or looping back to write one for anything missing.
- Confirm coverage meets the project’s current maturity-scaled threshold.
- Run lint and type-checking with zero errors outstanding.
A clean run leaves every obligation covered, coverage at threshold, and lint clean — clearing the path to the commit gate. Any obligation that cannot be tied to a real test, or any test whose assertion had to be weakened to pass, keeps the change from moving forward.
| Gate | When | Effect |
|---|---|---|
| obligation scan | before any code is written | every verifiable claim about the change — spec, contract, or security — must be listed with a source and status before implementation starts |
| red | before implementation | a failing test must exist for every obligation, failing for the right reason, with every pre-existing test still green |
| obligation verify | after the implementation is green | every obligation must move to a genuinely covered state backed by a real passing test, or the workflow loops back to write one |
Related
Section titled “Related”Source
Section titled “Source”Source — plugins/ca/skills/tdd/SKILL.md (v2.9.1)
---name: tdddescription: 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:
- `${CLAUDE_PROJECT_DIR}/.codearbiter/CONTEXT.md` — the `stage:` frontmatter (the maturity value) and project context.- `${CLAUDE_PROJECT_DIR}/.codearbiter/tech-stack.md` — test, coverage, and lint invocations; file layout; mock patterns.- `${CLAUDE_PROJECT_DIR}/.codearbiter/coding-standards.md` — style, structure, naming. Required for Phase 3.- `${CLAUDE_PROJECT_DIR}/.codearbiter/specs/<slug>.md` — the approved spec, when `/feature` produced one. It is the primary obligation source.- `${CLAUDE_PROJECT_DIR}/.codearbiter/security-controls.md` — only when the change touches a security boundary (auth, crypto, secrets, a trust boundary). Optional; absent on most changes.- `${CLAUDE_PROJECT_DIR}/.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 passingtest 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 theacceptance criteria of an already-approved spec (full-lane spec or small-lane mini-spec) — the userapproved that list once; do not re-ask. **User review is required** only for obligations derivedBEYOND 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 areSMARTS-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 thatobligation `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; asnapshot so broad it asserts nothing; coupling to an implementation detail instead of observablebehavior; 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 everyobligation `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, nogold-plating — to the conventions in `coding-standards.md`. Run the full suite. A broken pre-existingtest is a regression: fix it.
Gate: full suite green, reached by satisfying the Phase 2 tests — not by weakening them. A test'sassertions MUST be unchanged between red and green; only fixtures and setup may move. A relaxedassertion 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 testthat exercises the claim) or `MISSING` (no test truly covers it). For security-relevant orcontract-critical logic, dispatch the `coverage-auditor` agent(`${CLAUDE_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-runPhase 3 — and loops until it is `COVERED`.
**Stakes:** when you block on a `MISSING` obligation, state what the untested seam leaves exposed, notjust that it is `MISSING` — one line naming the consequence: "this error path is untested; a 500 herewould 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 promotiongate. The threshold table is the shared `${CLAUDE_PLUGIN_ROOT}/includes/maturity-coverage.md` (thesingle source of truth, also used by `refactor` Phase 2).
Run the coverage command from `tech-stack.md`. Below the maturity threshold → add tests until it is met.
**Stakes:** when coverage blocks below threshold, name the class of code left dark — the paths a laterregression could rot unnoticed — not just "below threshold." The number is the rule; the untested pathsare why it matters.
Gate: threshold met for the current maturity value.
## Phase 6 — Lint · gate: BLOCK
Run lint, and the type-check if the project is statically typed, from `tech-stack.md`. Resolve everyerror.
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 (`${CLAUDE_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.