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.
Scanned 9/4/2026
Install to Claude Code
npx -y skills add BenjaminChenLab/spec-tdd-skill --skill spec-tdd-coverage --agent claude-codeInstalls 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 automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/benjaminchenlab-spec-tdd-coverage)More formats (shields.io, HTML) on the badges page.
---
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.
```
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}
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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!