Test Coverage Loop - Measure, Triage, Fix (Deliberate)
You drive a deliberate loop that raises a project's test coverage to its requirement: run the capture, work the weak points by tier, reconcile the docs, loop until the requirement holds or the loop plateaus. Coverage lives OUTSIDE the build flows - implementers write each task's tests as part of done, but nothing in a build run measures coverage - so this loop is the user-controlled cadence where the accumulated gap gets worked, exactly like project-architecture-quality-loop for architecture. The named tradeoff of that model: a feature can land below the requirement between runs - this loop is where it catches up.
Execution modes
When dispatch is available, ask ONE question before ANALYZE, via AskUserQuestion - work the fixes in the current session (INLINE), or dispatch the stack seats (DELEGATED)? - then hold the answer for the run; no dispatch capability (a Cursor session, a non-stack project) is INLINE without asking. INLINE works every fix yourself in this session. DELEGATED dispatches each test-writing brief to the matching <stack>-implementer seat (up to 3 at once), routes a red build/test to the matching resolver, and runs a substantial refactor through the stack's designer -> implementers -> verifier vertical (this skill's own references/domain-trio-protocol.md). In DELEGATED mode, the mode ask's answer IS the dispatch consent for small test-writing briefs: write the <docs-path>/flow/APPROVAL stamp (APPROVED small-tier - "<the answer, verbatim>") the moment that answer lands, and rewrite it as substantial-tier plan approvals arrive - the dispatch guard is deliberately tier-blind, so an unstamped small-brief dispatch just bounces. A surface with no matching seat runs INLINE regardless. In BOTH modes the instrumented run stays the capture's, in the main session - a coverage run never enters a dispatch brief (seat gates are fast; the capture owns the measurement).
The loop
1. ANALYZE
Run the project-test-coverage-analyzer capture. It is manual-only, so the Skill tool will refuse it: Read its SKILL.md from the installed skills directory (.claude/skills/ / .cursor/skills/) and follow its protocol in-session. The Read is step zero of ANALYZE, not optional homework - a round that has not Read the file this session has not run the capture, whatever it measured (measured five times in one project, fresh invocations included, not just resumes: every unread round hand-rolled its own aggregation, so no two rounds' numbers were comparable - one round's ad-hoc dig did surface ~248 uncovered lines the coverage tool's class-level aggregation had been hiding, a find the protocoled capture makes systematic instead of lucky). It owns the detection, the instrumented runs, and the judgment, and it writes <docs-path>/test-coverage/COVERAGE.md. That doc is this loop's work list: the weak points, each carrying a tier (the routing key) and a simplify-testing action. The requirement and exclusions come from the doc; changing either is a user decision recorded there, never a loop shortcut.
2. TRIAGE + FIX by tier
Confirm the green baseline (build + tests) first, then take the weak points in leverage order:
- small (missing tests on existing seams) - a scoped brief: the module, the uncovered symbols, the behavior each test must pin. Write them INLINE, or DELEGATED dispatch the stack's implementer with the brief - the brief states
memory: none (a scoped test brief has no serena hand-off; one that does hand a note names it literally, read side included - memory hygiene: references/domain-trio-protocol.md). Tests assert behavior - a test written to touch lines is gamed coverage, not progress.
- substantial (a testability refactor, or 'no test infrastructure') - designer-led with approval: turn the simplify-testing action into a plan, get the user's approval before building (a refactor for testability is still a refactor) - present the plan, ask via AskUserQuestion, approve-and-build vs changes-needed (plain-text options where the harness lacks the tool) - DELEGATED, record the approving answer: write
<docs-path>/flow/APPROVAL, first line APPROVED <plan id> - "<the user's words, verbatim>" - the selected answer IS those words (the dispatch hook blocks an unstamped implementer; delete the file when the run completes). Write the stamp at the ABSOLUTE path $CLAUDE_PROJECT_DIR/<docs-path>/flow/APPROVAL with the Write tool - .claude/ is a protected path, so the first write in a session prompts; take the prompt's 'allow Claude to edit its own settings for this session' option and the rest of the run is free (no settings key can pre-approve it: permissions.allow is not consulted for protected paths); a relative write follows whatever cwd the shell drifted to and the dispatch then bounces. The stamp belongs to the session that dispatches - written when its own decision lands, deleted at its own close; an earlier session's leftover stamp is not consent. If BOTH the Write tool and an absolute-path Bash write are refused by the harness's classifier, stop and put the choice through AskUserQuestion (retry the stamp, or run this stage inline) rather than retrying blind or dispatching around the gate. Then build and gate it - INLINE yourself, DELEGATED through the domain trio. When 'no test infrastructure' is on the list it goes FIRST - nothing else is measurable until a harness exists: propose the stack's house default (plain JS/TS: Vitest; .NET: xUnit; Angular: the CLI's own unit-test builder - the defaults the house testing skills record), and on approval wire the runner + coverage output and land the first tests.
- structural - do NOT auto-apply. Present the gap, the reasoning, and the rework, then ask the decision via AskUserQuestion - apply (routes as substantial) vs decline vs defer (plain-text options where the harness lacks the tool); declined -> flagged in the final report.
Keep build and tests green across the round - a red routes to the matching resolver (DELEGATED) or is fixed inline before the next weak point.
A fix that rewrites or deletes an EXISTING spec flagged red-check pending (the capture digest's suite-quality label - the spec's name promises a behavior the audit says it never guards) runs the red-check FIRST: temporarily break that behavior and run the spec - still green proves the flag, fix the spec and require RED against the same break before restoring; goes red means a false positive - leave the spec and report the disproof in the round outcome so the step-3 reconcile records red-check: disproved <date> on the entry (the doc is the capture's to write; later captures honor the recorded disproof instead of re-flagging) (measured: ~1 in 20 flags was false, caught only by this check; the label has no other enforcement point - this step is it).
3. UPDATE DOCS
Re-run the capture: <docs-path>/test-coverage/COVERAGE.md and the raw results reconcile with what landed - closed weak points drop off, the numbers refresh, anything the fixes exposed is added. The doc is regenerated by the capture, never hand-edited here. COVERAGE.md is the only doc this loop maintains - <docs-path>/architecture/ASSESSMENT.md is the architecture capture's, reconciled at its own cadence, never touched from here.
4. LOOP or STOP
Decide off the reconciled numbers the step-3 capture just produced, not by eye - they are already in this context (re-read <docs-path>/test-coverage/COVERAGE.md only after a compaction or when the in-context picture is genuinely stale, never as a per-round habit). On a LOOP verdict, run the fresh-session ask (Bounded and honest) before the next round - the resume invocation names the rounds already consumed so the 3-round cap survives a resume. The round verdict line carries the ask's receipt - next: fresh-session | continue - "<the user's answer, verbatim>" - and that receipt can only come from the AskUserQuestion answer; a verdict line without it is an unfinished step 4 (measured: the step-not-advice form alone was still skipped - one session ran rounds 4 and 5 inside the same 13h chat with only a prose question between them, 79.3M cache-read vs 9.4M for the round that owned a fresh session):
- SATISFIED - every measured surface meets the requirement, and only user-declined structural items or recorded overrides remain.
- PLATEAU - the weak-point set equals the previous round's and none is now resolvable - stop rather than re-run identically.
- CAPPED - the improve-round cap is reached (see Bounded and honest).
- BLOCKED - only structural gaps remain and the user has not approved a rework.
Then the final report - one line per field, a table for the per-surface numbers (the close is an answer like any other and the answer-length hook blocks a wall of prose; tables are exempt): the outcome and round; per-surface coverage before -> after; each weak point resolved (tier + the change that closed it); the deferred items; the reconciled docs' state; the build + tests baseline at stop; and memories purged: <names|none>, fold-first (the trio reference's receipt, restated here because loop rounds measured 0 purges with the rule unloaded). When the report closes with uncommitted work and no next round queued, the commit decision goes through AskUserQuestion (commit now / hold) - never a prose 'your call' bullet (measured: three such bullets in one session drifted unresolved for 5h while the uncommitted set grew 16 -> 23 files). The same close names what this round's runs started and still have up - a Docker container or compose stack, seeded integration-test data, a background process - and puts tear-down-vs-keep through the same AskUserQuestion call (teardown recommended; started-nothing is said plainly, and what the round did not start is never touched).
Example
INLINE, one round over a two-surface workspace:
- ANALYZE - the capture writes COVERAGE.md: the .NET API at 84% vs 90% (two small weak points - uncovered error branches in
InvoiceService - and one substantial: PaymentGateway news up its HttpClient, untestable), the Angular app at 91% - meets.
- TRIAGE + FIX - green baseline confirmed. Small: write the four branch tests, suite green. Substantial: plan the seam (inject the handler), get approval, apply + test.
- UPDATE DOCS - re-run the capture: the .NET surface reads 92%, the closed weak points drop off.
- LOOP or STOP - every surface meets the requirement -> SATISFIED; report.
Bounded and honest
- Hard cap: 3 improve rounds - each round re-runs the instrumented capture; do not loop chasing the last decimal.
- Each round boundary is a fresh-session resume point - the reconciled
<docs-path>/test-coverage/COVERAGE.md is the handoff; measured, carried-forward conversation (not tool output) dominates session cost. A step, not advice (the advice form was ignored in 4 of 4 audited long sessions): at each LOOP decision, ask via AskUserQuestion - resume in a fresh session from the reconciled doc (recommended) vs continue here; on 'fresh', end the turn with a RESUME BLOCK the new session can start from alone: the exact invocation (naming the rounds already consumed so the 3-round cap survives), the remaining weak points in tier order, and the docs to read first (the reconciled <docs-path>/test-coverage/COVERAGE.md - its ## Resume section RANGED, never the whole file (measured: whole-file round-start reads cost ~15-16k tokens each on a grown doc, ~24% of it needed) - the requirement and exclusions it records, and .claude/skills/project-test-coverage-analyzer/SKILL.md (.cursor/skills/ on Cursor) - the ANALYZE step's protocol owner, which a fresh session has never read; measured: two resumed rounds skipped it and reconciled by convention-copying instead).
- The percentage is the proxy, pinned behavior is the goal: never pad with assertion-free tests, never weaken or delete an existing test (one exception: a spec the step-2 red-check just PROVED vacuous - the check exists to authorize exactly that rewrite or delete), never widen exclusions or lower the requirement to reach SATISFIED - those two belong to the user, recorded in the doc.
- A testability refactor stays behavior-preserving - the existing suite is green before and after - and the tier on each weak point is the routing authority: no silent upgrade of a small gap into a rewrite, no downgrade of a structural one past its approval gate.
Rules
- The main session is the only orchestrator; the seats it dispatches carry no Agent tool, so the fan-out stays flat.
- The instrumented run never enters a seat's dispatch brief - measurement is the capture's, in the main session, in both modes.
- Implementers keep writing each task's tests in every build flow (the
.claude/rules/baseline-quality-gates.md done bar) - this loop is the deliberate catch-up cadence, not a substitute for that.
- Keep this skill orchestration only: measurement judgment lives in the capture; build knowledge lives in the domain seats and house skills. A pure code-quality polish is
project-quality-loop; architecture weaknesses are project-architecture-quality-loop's.
1---2name: project-test-coverage-loop3description: The deliberate coverage analyze-triage-fix loop. Runs the project-test-coverage-analyzer capture, works COVERAGE.md's weak points by tier - missing tests as scoped implementer briefs, testability refactors designer-led with plan approval first, structural gaps flagged for a user decision and never auto-applied, 'no test infrastructure' as the first substantial fix - re-runs the capture to reconcile the docs, and loops until the requirement holds or the loop plateaus. Manual, /-only. Triggers on 'run the test coverage loop' or 'raise the test coverage'. NOT for a measure-only run with no fixes (/project-test-coverage-analyzer alone), a code-quality polish (project-quality-loop), or architecture weaknesses (project-architecture-quality-loop).4---56# Test Coverage Loop - Measure, Triage, Fix (Deliberate)78You drive a deliberate loop that raises a project's test coverage to its requirement: run the capture, work the weak points by tier, reconcile the docs, loop until the requirement holds or the loop plateaus. Coverage lives OUTSIDE the build flows - implementers write each task's tests as part of done, but nothing in a build run measures coverage - so this loop is the user-controlled cadence where the accumulated gap gets worked, exactly like `project-architecture-quality-loop` for architecture. The named tradeoff of that model: a feature can land below the requirement between runs - this loop is where it catches up.910## Execution modes11When dispatch is available, ask ONE question before ANALYZE, via AskUserQuestion - work the fixes in the current session (INLINE), or dispatch the stack seats (DELEGATED)? - then hold the answer for the run; no dispatch capability (a Cursor session, a non-stack project) is INLINE without asking. INLINE works every fix yourself in this session. DELEGATED dispatches each test-writing brief to the matching `<stack>-implementer` seat (up to 3 at once), routes a red build/test to the matching resolver, and runs a substantial refactor through the stack's designer -> implementers -> verifier vertical (this skill's own `references/domain-trio-protocol.md`). In DELEGATED mode, the mode ask's answer IS the dispatch consent for small test-writing briefs: write the `<docs-path>/flow/APPROVAL` stamp (`APPROVED small-tier - "<the answer, verbatim>"`) the moment that answer lands, and rewrite it as substantial-tier plan approvals arrive - the dispatch guard is deliberately tier-blind, so an unstamped small-brief dispatch just bounces. A surface with no matching seat runs INLINE regardless. In BOTH modes the instrumented run stays the capture's, in the main session - a coverage run never enters a dispatch brief (seat gates are fast; the capture owns the measurement).1213## The loop1415### 1. ANALYZE16Run the `project-test-coverage-analyzer` capture. It is manual-only, so the Skill tool will refuse it: Read its `SKILL.md` from the installed skills directory (`.claude/skills/` / `.cursor/skills/`) and follow its protocol in-session. The Read is step zero of ANALYZE, not optional homework - a round that has not Read the file this session has not run the capture, whatever it measured (measured five times in one project, fresh invocations included, not just resumes: every unread round hand-rolled its own aggregation, so no two rounds' numbers were comparable - one round's ad-hoc dig did surface ~248 uncovered lines the coverage tool's class-level aggregation had been hiding, a find the protocoled capture makes systematic instead of lucky). It owns the detection, the instrumented runs, and the judgment, and it writes `<docs-path>/test-coverage/COVERAGE.md`. That doc is this loop's work list: the weak points, each carrying a tier (the routing key) and a simplify-testing action. The requirement and exclusions come from the doc; changing either is a user decision recorded there, never a loop shortcut.1718### 2. TRIAGE + FIX by tier19Confirm the green baseline (build + tests) first, then take the weak points in leverage order:2021- **small** (missing tests on existing seams) - a scoped brief: the module, the uncovered symbols, the behavior each test must pin. Write them INLINE, or DELEGATED dispatch the stack's implementer with the brief - the brief states `memory: none` (a scoped test brief has no serena hand-off; one that does hand a note names it literally, read side included - memory hygiene: `references/domain-trio-protocol.md`). Tests assert behavior - a test written to touch lines is gamed coverage, not progress.22- **substantial** (a testability refactor, or 'no test infrastructure') - designer-led with approval: turn the simplify-testing action into a plan, **get the user's approval before building** (a refactor for testability is still a refactor) - present the plan, ask via AskUserQuestion, approve-and-build vs changes-needed (plain-text options where the harness lacks the tool) - DELEGATED, record the approving answer: write `<docs-path>/flow/APPROVAL`, first line `APPROVED <plan id> - "<the user's words, verbatim>"` - the selected answer IS those words (the dispatch hook blocks an unstamped implementer; delete the file when the run completes). Write the stamp at the ABSOLUTE path `$CLAUDE_PROJECT_DIR/<docs-path>/flow/APPROVAL` with the Write tool - `.claude/` is a protected path, so the first write in a session prompts; take the prompt's 'allow Claude to edit its own settings for this session' option and the rest of the run is free (no settings key can pre-approve it: `permissions.allow` is not consulted for protected paths); a relative write follows whatever cwd the shell drifted to and the dispatch then bounces. The stamp belongs to the session that dispatches - written when its own decision lands, deleted at its own close; an earlier session's leftover stamp is not consent. If BOTH the Write tool and an absolute-path Bash write are refused by the harness's classifier, stop and put the choice through AskUserQuestion (retry the stamp, or run this stage inline) rather than retrying blind or dispatching around the gate. Then build and gate it - INLINE yourself, DELEGATED through the domain trio. When 'no test infrastructure' is on the list it goes FIRST - nothing else is measurable until a harness exists: propose the stack's house default (plain JS/TS: Vitest; .NET: xUnit; Angular: the CLI's own unit-test builder - the defaults the house testing skills record), and on approval wire the runner + coverage output and land the first tests.23- **structural** - do NOT auto-apply. Present the gap, the reasoning, and the rework, then ask the decision via AskUserQuestion - apply (routes as substantial) vs decline vs defer (plain-text options where the harness lacks the tool); declined -> flagged in the final report.2425Keep build and tests green across the round - a red routes to the matching resolver (DELEGATED) or is fixed inline before the next weak point.2627A fix that rewrites or deletes an EXISTING spec flagged `red-check pending` (the capture digest's suite-quality label - the spec's name promises a behavior the audit says it never guards) runs the red-check FIRST: temporarily break that behavior and run the spec - still green proves the flag, fix the spec and require RED against the same break before restoring; goes red means a false positive - leave the spec and report the disproof in the round outcome so the step-3 reconcile records `red-check: disproved <date>` on the entry (the doc is the capture's to write; later captures honor the recorded disproof instead of re-flagging) (measured: ~1 in 20 flags was false, caught only by this check; the label has no other enforcement point - this step is it).2829### 3. UPDATE DOCS30Re-run the capture: `<docs-path>/test-coverage/COVERAGE.md` and the raw results reconcile with what landed - closed weak points drop off, the numbers refresh, anything the fixes exposed is added. The doc is regenerated by the capture, never hand-edited here. COVERAGE.md is the only doc this loop maintains - `<docs-path>/architecture/ASSESSMENT.md` is the architecture capture's, reconciled at its own cadence, never touched from here.3132### 4. LOOP or STOP33Decide off the reconciled numbers the step-3 capture just produced, not by eye - they are already in this context (re-read `<docs-path>/test-coverage/COVERAGE.md` only after a compaction or when the in-context picture is genuinely stale, never as a per-round habit). On a LOOP verdict, run the fresh-session ask (Bounded and honest) before the next round - the resume invocation names the rounds already consumed so the 3-round cap survives a resume. The round verdict line carries the ask's receipt - `next: fresh-session | continue - "<the user's answer, verbatim>"` - and that receipt can only come from the AskUserQuestion answer; a verdict line without it is an unfinished step 4 (measured: the step-not-advice form alone was still skipped - one session ran rounds 4 and 5 inside the same 13h chat with only a prose question between them, 79.3M cache-read vs 9.4M for the round that owned a fresh session):3435- **SATISFIED** - every measured surface meets the requirement, and only user-declined structural items or recorded overrides remain.36- **PLATEAU** - the weak-point set equals the previous round's and none is now resolvable - stop rather than re-run identically.37- **CAPPED** - the improve-round cap is reached (see Bounded and honest).38- **BLOCKED** - only structural gaps remain and the user has not approved a rework.3940Then the final report - one line per field, a table for the per-surface numbers (the close is an answer like any other and the answer-length hook blocks a wall of prose; tables are exempt): the outcome and round; per-surface coverage before -> after; each weak point resolved (tier + the change that closed it); the deferred items; the reconciled docs' state; the build + tests baseline at stop; and `memories purged: <names|none>`, fold-first (the trio reference's receipt, restated here because loop rounds measured 0 purges with the rule unloaded). When the report closes with uncommitted work and no next round queued, the commit decision goes through AskUserQuestion (commit now / hold) - never a prose 'your call' bullet (measured: three such bullets in one session drifted unresolved for 5h while the uncommitted set grew 16 -> 23 files). The same close names what this round's runs started and still have up - a Docker container or compose stack, seeded integration-test data, a background process - and puts tear-down-vs-keep through the same AskUserQuestion call (teardown recommended; started-nothing is said plainly, and what the round did not start is never touched).4142## Example43INLINE, one round over a two-surface workspace:441. **ANALYZE** - the capture writes COVERAGE.md: the .NET API at 84% vs 90% (two small weak points - uncovered error branches in `InvoiceService` - and one substantial: `PaymentGateway` news up its `HttpClient`, untestable), the Angular app at 91% - meets.452. **TRIAGE + FIX** - green baseline confirmed. Small: write the four branch tests, suite green. Substantial: plan the seam (inject the handler), get approval, apply + test.463. **UPDATE DOCS** - re-run the capture: the .NET surface reads 92%, the closed weak points drop off.474. **LOOP or STOP** - every surface meets the requirement -> **SATISFIED**; report.4849## Bounded and honest50- **Hard cap: 3 improve rounds** - each round re-runs the instrumented capture; do not loop chasing the last decimal.51- **Each round boundary is a fresh-session resume point** - the reconciled `<docs-path>/test-coverage/COVERAGE.md` is the handoff; measured, carried-forward conversation (not tool output) dominates session cost. A step, not advice (the advice form was ignored in 4 of 4 audited long sessions): at each LOOP decision, ask via AskUserQuestion - resume in a fresh session from the reconciled doc (recommended) vs continue here; on 'fresh', end the turn with a RESUME BLOCK the new session can start from alone: the exact invocation (naming the rounds already consumed so the 3-round cap survives), the remaining weak points in tier order, and the docs to read first (the reconciled `<docs-path>/test-coverage/COVERAGE.md` - its `## Resume` section RANGED, never the whole file (measured: whole-file round-start reads cost ~15-16k tokens each on a grown doc, ~24% of it needed) - the requirement and exclusions it records, and `.claude/skills/project-test-coverage-analyzer/SKILL.md` (`.cursor/skills/` on Cursor) - the ANALYZE step's protocol owner, which a fresh session has never read; measured: two resumed rounds skipped it and reconciled by convention-copying instead).52- The percentage is the proxy, pinned behavior is the goal: never pad with assertion-free tests, never weaken or delete an existing test (one exception: a spec the step-2 red-check just PROVED vacuous - the check exists to authorize exactly that rewrite or delete), never widen exclusions or lower the requirement to reach SATISFIED - those two belong to the user, recorded in the doc.53- A testability refactor stays behavior-preserving - the existing suite is green before and after - and the tier on each weak point is the routing authority: no silent upgrade of a small gap into a rewrite, no downgrade of a structural one past its approval gate.5455## Rules56- The main session is the only orchestrator; the seats it dispatches carry no Agent tool, so the fan-out stays flat.57- The instrumented run never enters a seat's dispatch brief - measurement is the capture's, in the main session, in both modes.58- Implementers keep writing each task's tests in every build flow (the `.claude/rules/baseline-quality-gates.md` done bar) - this loop is the deliberate catch-up cadence, not a substitute for that.59- Keep this skill orchestration only: measurement judgment lives in the capture; build knowledge lives in the domain seats and house skills. A pure code-quality polish is `project-quality-loop`; architecture weaknesses are `project-architecture-quality-loop`'s.