Use when delegating test-first feature work to a subagent and you need EVIDENCE the new code is fully branch-covered — not just passing tests. Use when "good coverage" / "high coverage" matters, when weak or incomplete unit tests are a risk, or when spec-tdd's green-tests-but-no-coverage-evidence gap matters.
Installs into .claude/skills of the current project.
Are you the author of Spec Tdd Coverage?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/benjaminchenlab-spec-tdd-coverage)
---
name: spec-tdd-coverage
description: Use when delegating test-first feature work to a subagent and you need EVIDENCE the new code is fully branch-covered — not just passing tests. Use when "good coverage" / "high coverage" matters, when weak or incomplete unit tests are a risk, or when spec-tdd's green-tests-but-no-coverage-evidence gap matters.
---
# spec-tdd-coverage
**REQUIRED BACKGROUND:** Understand `spec-tdd` first. This is spec-tdd with a coverage discipline layered on — it inherits the two-tier agent-boundary split, "must be RED first," and Phase-3 don't-trust-self-report. Only read this when you already know spec-tdd.
## Overview
spec-tdd, plus: the subagent **declares a case-list BEFORE implementing** and **reports per-class branch coverage after**; the orchestrator **gap-checks** the case-list against the impl's real branches.
**Core principle: coverage must be measured and reported evidence — never a hopeful side-effect of green tests.**
## The gap this closes
spec-tdd's handoff says *"add your own unit tests"* — no case-list, no coverage report. Observed baseline: a diligent subagent wrote **18 green tests and hit 100% branch coverage, yet reported none of it.** Coverage was *luck*, not *evidence*, and the orchestrator had no case-list to audit. A less-diligent subagent on a branchier function leaves holes with nobody the wiser — spec-tdd Phase 3 audits only the acceptance test, so unit coverage is a blind spot.
## Pre-flight — orchestrator tier check (I21)
Before any work, check the model THIS session runs as. A run's judgment — the test/spec, the verification, the failure routing — executes entirely in the orchestrator's own context; I19 pins every dispatch tier, but nothing can upgrade the session itself. **Top tier in use, or no higher tier exists → silent, move on.** Otherwise surface this ONE ask and stop for the answer:
> ⚠ **Orchestrator tier check** — this session runs a non-top model, and a run's planning / verification / routing all execute on it. **Upgrade** → run `/model`, pick the top tier, say "go" (the same conversation continues). **Ignore** → continue at this tier; the decline is disclosed in the final report.
Arrived from a front-end that already surfaced this check? Skip it — never re-ask (a handoff-recorded decline rides into your final-report disclosure).
## The 3 Phases (delta vs spec-tdd in **bold**)
### Phase 1 — Orchestrator writes the acceptance test
> **Arrived from `grill-spec-tdd` / `adversarial-grill-spec-tdd`?** The acceptance test is already written and RED — skip to Phase 2 (still note the branch/exception surface for your gap-check).
As spec-tdd: ground it, write behavioral black-box tests, **MUST be RED — incl. the RED-purity check and the Phase-1 encoding-audit dispatch** (fresh-context review of the test vs the settled spec, before Phase 2; skip for grill arrivals) — and spec-tdd's **spec-doc persistence prompt** applies (no spec/plan/blueprint doc → ask once, default YES). **Note the branch/exception surface you expect** — it feeds your Phase-3 gap-check.
### Phase 2 — Delegate to subagent (extended handoff)
**SPEC-INTEGRITY snapshot (before dispatch):** hash the acceptance test (`sha256sum <file>` / `certutil -hashfile <file> SHA256` / `git hash-object <file>`) and record it — the immutability baseline you verify in Phase 3.
**Dispatch model: MID tier, stated on the dispatch (I19)** — the acceptance test + top-tier reviewers enforce quality; an unstated model silently inherits the session's most expensive.
**SCRATCH ROOT:** fill the handoff's SCRATCH ROOT with the absolute path of the tree the implementer works in — the repo root, or the unit's scratch-copy root in a parallel wave (a copy carries no `.git`, so the handoff's check falls back to a `pwd`-inside confirmation there).
```
TASK: Implement {feature} so the acceptance test passes. Do NOT modify it — it is hashed and verified byte-for-byte on return;
if it looks wrong, STOP and report — never silently weaken it.
If the acceptance test cannot compile or pass because of a defect in the TEST itself (wrong constructor arity,
ambiguous overload matchers, unused stubs under strict stubs, assertions contradicting the spec), STOP and
report it as SPEC-DEFECT with evidence — do NOT change production code to make a defective test compile or
pass. Production changes that exist solely to accommodate a test defect count as a FAILED run. Reporting a
genuine SPEC-DEFECT is the correct outcome, not a failure to implement — you will not be penalized for it.
An arity/signature error on a symbol the requirement itself explicitly changes is NOT a test defect —
implement the change.
ACCEPTANCE TEST (RED): {file}
INTENT: {1–3 sentences}
READ FIRST: {entity/service/repo/pattern paths}
SCRATCH ROOT: {absolute path of the tree you work in} — every `.spec-tdd/`
path you write (your run log) is this prefix joined with a `/`, PASTED
VERBATIM — never retype it, never resolve it against your own cwd, never
hand-compose any other absolute form; mkdir -p plus output-redirect makes
a typo'd path silently succeed, materializing a parallel tree no one sees
(2026-09-18 W22 incident). Before the first such write, confirm you are
working inside SCRATCH ROOT (in a git repo/worktree: `git rev-parse
--show-toplevel` must equal it; otherwise `pwd` must be inside it) — a
mismatch is a STOP-and-report, never a best-effort guess.
BEFORE ANY IMPL — write a CASE-LIST of every branch + boundary you will cover:
happy path; each branch (if/loop/null-guard/early-return/catch);
boundaries (empty, single, max, off-by-one, sign, rounding edges);
exception/invalid/null/NaN/non-finite inputs. Return it FIRST.
Every case NAMES the branch it covers (method + condition) — the mapping
is cross-checked both ways in Phase 3; a case that maps to nothing is filler.
DO:
1) make the acceptance test GREEN
2) write unit tests red→green, one per case
3) run the coverage tool; quote, FOR THE NEW/CHANGED CLASS(ES) ONLY
(never whole-module): line % AND branch %, plus every uncovered line with
a one-line justification (dead code / defensive / unreachable / SHOULD-test).
Tools: Java → ./gradlew :Core:jacocoTestReport ;
JS → node --test --experimental-test-coverage ; Python → pytest --cov
CIRCUIT BREAKER: STOP if either fires — (a) the test still fails after 3 repair attempts, OR (b) **the same root cause appears on ANY two attempts** — same failing file:line AND same failing assertion (not necessarily consecutive; not a rephrased free-text trace). Don't burn a third attempt re-trying one identical misdiagnosis — each attempt must rest on a DIFFERENT root cause. Report a short structured diagnosis — tag it env/dependency (ERR-01), logic violation (ERR-02), or syntax/compile (ERR-03) — with a TRUNCATED trace and expected-vs-actual. Don't keep retrying.
RETURN: case-list + impl/unit-test paths + notable decisions (every deviation
from the spec'd shape: symbols added/renamed/moved beyond the spec)
+ ONE status line per command (command + pass/fail counts), with the FULL
output in a scratch log file (e.g. `.spec-tdd/<feature>-run.log` — working
notes, never committed; hand its path)
+ per-class coverage excerpt (branch %, uncovered lines + justifications).
Status lines read from real runs — the orchestrator re-runs and compares
everything itself (I5/I19).
If you stopped on SPEC-DEFECT, return the defect report with evidence instead.
```
### Phase 3 — Orchestrator verifies
spec-tdd's checks (incl. the **SPEC-INTEGRITY hash check**, the **SPEC-DEFECT sweep** — diff the returned production changes against the spec; a compat ctor / renamed public method / logic beyond scope passing green is a test-defect accommodation: fix the test (re-hash, note the correction), restore production — and the **SPEC/TEST/IMPL failure routing**), PLUS:
1. **GAP-CHECK — bidirectional, machine-derived.** Derive the branch inventory from the coverage report's per-method branch detail where the tool provides it (a machine-generated list, not a free-form code read); where it doesn't (e.g. line-only reporters), derive it from the impl's own branch statements — but still check BOTH ways against the case-list: every branch is claimed by ≥1 case (unclaimed branch → add a case), and every case maps to a real branch (an unmapped case = invented filler — cut it or map it). **Branch gaps and unmapped cases go back to the implementer as ONE targeted re-dispatch — the case-list is its artifact (I6); the orchestrator never writes its cases.** *(Same agent wrote case-list and impl, so this cross-context check stops a case-list that quietly mirrors the impl — and the mapping requirement stops the reverse, a case-list padded with cases that cover nothing.)*
2. **Re-run the coverage tool yourself and compare the numbers** against the returned excerpt — a full compare, not a spot-check; any mismatch = self-report fails. Don't trust self-report.
3. **Insist on BRANCH %, not line %.** Line % can read 100% while branches are uncovered (probe: line 100% / branch 66% on the same file).
4. Surface **both** the acceptance test **and** the case-list to the user.
## Risk-tier (scale to stakes)
- **Critical path** (money / auth / data-loss): full phases + property tests + gap-check + coverage re-run with full numeric compare.
- **General feature**: phases 1–3 incl. case-list + coverage excerpt.
- **Throwaway / demo**: minimal — acceptance test + compile. **No case-list, no coverage burden.**
## Common Mistakes
| Mistake | Fix |
|---|---|
| Orchestrator writes the case-list | No — it belongs to the **subagent** (before impl). Orchestrator only gap-checks; dictating unit cases blurs the agent boundary. |
| Case-list written after impl | Must be **before** impl, else it's reverse-engineered to match what got built — a unit-level green lie. Require it as the first RETURN artifact. |
| Subagent quotes whole-module % | Require **per-class for NEW/changed classes only**. Whole-module % is diluted by existing untested code. |
| Trusting line % | Require **branch %**. line 100% can hide uncovered branches. |
| "100%" with no uncovered-line list | Require the list + justification **even when empty** ("none" stated, not omitted). |
| Trusting self-reported coverage | Re-run the coverage tool yourself in Phase 3 and **full-compare** the numbers against the excerpt — not a spot-check. |
| % gate incentivizes low-value tests | The **case-list** is the gate, not %. Each case must map to a real branch. |
| GAP-CHECK done as a free-form code read ("I read the impl — looks covered") | Derive the branch inventory from the coverage report's per-method branch detail and check BOTH directions: branch→case AND case→branch. The case↔branch mapping makes the check countable, not vibes. |
| Coverage evidence green, but the returned production diff never checked against the spec | Inherited SPEC-DEFECT sweep (spec-tdd Phase 3): a compat ctor / renamed method / logic beyond scope passing green = a bent production — fix the test (re-hash, note), restore production. |
## Red Flags — STOP
- Impl + tests returned but **no case-list**.
- Coverage excerpt shows **whole-module %** or **omits branch %**.
- **Uncovered lines with no justification.**
- Case-list appears **after** impl, or mirrors impl branches with no gap-check.
- Case-list entries **name no branch** — no mapping to cross-check either direction.