Auto Test
Overview
Drive a codebase from "some/unknown test health" to "a green suite that meaningfully covers the target",
autonomously and safely. The value is the combination: it loops (find gaps → write → run → fix →
re-run) but it stops honestly (bounded, fails closed) and it doesn't cheat (every test is proven able
to fail, the suite is never gamed green). It runs the converge-loop until-green pattern, gates each new
test through adversarial-verify (mutation check), and checkpoints via checkpoint-resume.
When to Use
- After
auto-build / any implementation, to cover the behavior that was just written
- To raise real coverage on a specific risky module (auth, money, data migrations, parsers)
- To add a regression test that reproduces a reported bug before it's fixed (the Prove-It pattern)
- To de-flake an unreliable suite (
auto-test flaky)
When NOT to use: writing tests test-first while implementing new behavior (that's auto-build's
RED→GREEN loop); a repo with no runner configured (set one up first);
pure-config/docs changes with no behavioral surface.
Phase 0: Ground the run — scope, runner, baseline, methodology
Before writing anything:
- Resolve scope (
$scope): a path/module, the current diff (default — git diff against the base),
or flaky (stabilize mode). Narrow, targeted scope beats "test the whole repo."
- Detect the runner + coverage tool — read the repo:
package.json scripts, pytest.ini,
Cargo.toml, a Makefile. Capture the exact command to run the whole suite and to run a single
file/test (you'll use the single-test form for tight loops). If none exists, STOP and say so.
- Baseline the signal — run the suite ONCE. Record: pass/fail counts, which tests fail, and (if
available) current coverage on the scope. This is
converge-loop iteration 0. If it's already green
with the target covered, there may be nothing to do — say so, don't invent busywork.
- Load our test-quality bar — this skill carries its own standard for what a good test is (see
references/test-quality-bar.md): the test pyramid (≈80/15/5), test sizes, DAMP-over-DRY,
state-not-interaction assertions, real-implementations-over-mocks, and the anti-patterns to avoid.
Apply it directly; it is self-contained, not a pointer to another collection.
- Open a checkpoint — start a
checkpoint-resume run (.ulpi/runs/<id>.json) with one unit per
target behavior/file. On resume, skip units already done.
Success criteria: scope fixed; exact suite + single-test commands known; a concrete baseline
(counts + failing tests + coverage) recorded; checkpoint open.
Phase 1: Find the gaps — the work list
Identify the behaviors that lack a meaningful test (not just uncovered lines):
- diff the scope against the tests that touch it; list public functions/branches/error paths with no
assertion behind them;
- prioritize by risk — untested error/edge paths, money/auth/data-mutation code, and recently changed
code rank above cosmetic getters;
- for
flaky mode, instead identify the tests that fail intermittently (run the suite N times, collect
the non-deterministic failures) — those are the units.
For a large scope, fan the discovery out with fan-out-work (one agent per module) and merge the gap
list. Record each gap as a checkpoint unit.
Success criteria: a prioritized, de-duplicated list of concrete missing tests (or flaky tests),
each an addressable unit.
Phase 2: Write one meaningful test — and prove it can fail
Per unit (smallest first), write ONE focused test, then verify it's real BEFORE trusting it:
- Write it following the methodology — Arrange-Act-Assert, a descriptive name that reads like a spec,
one concept per test, state-based assertions, real implementations over mocks. For a bug repro, write
it to FAIL against current code (RED).
- Mutation-check it (
adversarial-verify for tests): make a small, targeted break in the code under
test (flip a comparison, drop a write, return a wrong constant) and re-run just this test — it MUST go
red. Restore the code — it MUST go green. A test that stays green on the broken code is a tautology:
reject it, rewrite it to actually assert the behavior. For heavy verification, delegate the
mutation-probe to a subagent.
- Classify a genuine failure — if, against the correct code, the test fails, decide: is the TEST
wrong (fix the test) or is the CODE wrong (a real bug — surface it; fix only if in scope, never by
asserting the buggy output)?
Success criteria: each added test provably fails when its target is broken and passes when it's
correct; any real bug the test exposed is recorded, not papered over.
Phase 3: Converge the suite to green
Run the converge-loop until-green pattern over the whole target, with its full termination set (done =
suite exits 0 over the scope; maxIterations; token budget; maxStall=2):
- after each added/fixed test, run the suite (single-file form for speed during the loop; full suite at
round boundaries to catch regressions);
- if a change regresses another test, revert and reconsider — never ratchet the suite backwards;
- if a unit can't be made green in
MAX_FIX (≈3) attempts, mark it blocked with the reason and move
on — do not spin;
- update the checkpoint as each unit reaches
done / blocked.
Success criteria: the scoped suite is green (or the loop terminated honestly with the specific
blocked units named); no regressions introduced.
Phase 4: De-flake (stabilize mode, or any flake surfaced)
For any test that passes/fails non-deterministically:
- reproduce by running it in a loop / with randomized order; find the root cause class — shared state,
time/timezone, ordering, real network, unawaited async;
- fix the ROOT (isolate state, inject the clock, await properly, fake the boundary) — never "fix" a flake
by adding a retry or a sleep to mask it;
- confirm stability: N consecutive green runs (and randomized order) before calling it fixed.
Success criteria: previously-flaky tests pass deterministically across repeated + reordered runs; no
flake masked by retries/sleeps.
Phase 5: Report
Finalize the checkpoint and report honestly (see Output Contract). Include the before→after signal
(counts + coverage delta), the tests added, any real bugs surfaced, and any blocked units.
Common Rationalizations
| Rationalization |
Reality |
| "The suite is green, we're done." |
Green via a tautological or skipped test is a false signal. Green + mutation-proven + nothing skipped is done. |
| "This test passes immediately, ship it." |
A test that passes on the first run may test nothing. Mutation-check it: break the target — if it stays green, it's vacuous. |
| "The test fails, let me relax the assertion to match." |
If the code output is wrong, that's a bug to surface, not an assertion to loosen. Loosening hides the defect. |
| "I'll just skip the failing test to get green." |
Skipping is faking the done-condition — the exact cardinal sin. A skipped test is an untested behavior wearing a green badge. |
| "It's flaky, add a retry." |
A retry masks a real race/state bug that will bite in production. Fix the root; retries are not stabilization. |
| "Coverage is at 90%, good enough." |
Coverage counts lines executed, not behaviors asserted. Ten vacuous tests raise the number and prove nothing. |
| "I ran the tests earlier, they're fine." |
After any code change, the earlier run is stale. Re-run after the change; read the actual exit code. |
Red Flags
- The suite went green in the same edit that deleted/
skipped/.only'd a test.
- A newly added test passes against a deliberately broken version of the code.
- An assertion was changed to match the code's current (possibly wrong) output.
- A flake "fixed" by a
sleep, a retry wrapper, or an increased timeout.
- Coverage % climbing while assertions are vacuous (
toBeDefined, not.toThrow on everything).
- "All tests pass" reported without a suite run in the transcript.
- The loop is on its 6th iteration re-trying the same failing approach (thrash — stop and escalate).
Enforcement (deterministic, not prose)
While this skill is active, a skill-scoped PreToolUse hook runs scripts/guard-test-integrity.sh on
every Edit/Write: adding a .only/.skip/xit/xdescribe/.todo/@pytest.mark.skip/@unittest.skip/
#[ignore] marker or a @ts-ignore/@ts-expect-error/eslint-disable/# type: ignore suppression to a
test file is BLOCKED at the tool layer. That stops the skip/only/ignore/suppression class of gaming at
the Edit/Write layer (a raw-Bash write of the same marker sidesteps this Edit/Write-scoped hook — caught,
like the vectors below, by mutation-check discipline). The OTHER cheat vectors this skill forbids (deleting a test, a vacuous
expect(true), weakening an assertion, masking a flake with sleeps) are not statically detectable at the
edit layer — they stay enforced by the mutation-check discipline and the fail-closed contract above, not
by this hook. Genuine, user-approved weakening goes through the explicit escape hatch —
touch <project>/.ulpi/allow-test-weaken opens a 2-minute approval window (then expires;
AUTO_TEST_ALLOW_WEAKEN=1 exists for settings-level use) — with the reason stated in the reply.
Guardrails
- Never weaken, skip, delete, or
.only tests to reach green. Fail closed instead.
- Never count a test that can't fail — mutation-check every addition.
- Never rewrite a test to assert buggy behavior; surface the bug.
- Never mask a flake with sleeps/retries/timeouts; fix the root cause.
- Never chase a coverage number with vacuous tests.
- Never report green without a final real suite run and its exit code.
- Keep each iteration small and measured (one test/behavior); revert any regression immediately.
- Escalate ambiguous expected-behavior questions instead of guessing.
When To Load References
converge-loop (skill) — the until-green loop with the termination set + anti-thrash. The engine of
Phase 3.
adversarial-verify (skill) — the mutation-check / tautology-rejection gate for Phase 2.
checkpoint-resume (skill) — the durable run state for skip-done resume.
fan-out-work (skill) — parallel gap discovery / test writing over a large scope.
references/test-quality-bar.md — OUR standard for a good test: pyramid, sizes, DAMP,
state-not-interaction, real-over-mocks, and the anti-patterns. Load in Phase 0.
Verification
Before reporting done, confirm:
Output Contract
Report:
- scope + suite command used; baseline → final signal (pass/fail counts; coverage delta when the repo tracks coverage)
- tests added (by behavior), each noted mutation-verified
- real bugs surfaced (and whether fixed in scope or handed off)
- flakes stabilized (root cause + how) — if any
- loop outcome: converged, or the honest list of blocked/failing units with reasons
- checkpoint file path (durable record; resume-able)
1---2name: auto-test3description: Raise test health to a green, MEANINGFUL suite: find untested behaviors, write real tests, loop-until-green — with every added test MUTATION-CHECKED (break the code, the test must fail; tautologies rejected). Fails closed: never games the suite green (a skill-scoped hook mechanically blocks .skip/.only/suppressions in test files). Checkpointed and resumable. Use after a build, on a risky module, for a bug repro, or to de-flake (auto-test flaky).4---56<EXTREMELY-IMPORTANT>7A green suite is worthless if it was made green by cheating, and dangerous if its tests don't actually8test anything. Non-negotiable:91. NEVER make the suite pass by weakening the signal: no deleting/`skip`/`xit`/`.only`, no10 `expect(true)`, no loosening an assertion to match wrong output, no commenting out a failing test, no11 raising a timeout to paper over a real hang. If a test is genuinely wrong, fixing it is a real,12 explained change — not a silencing to escape the loop.132. NEVER add a test that can't fail. Every added test is mutation-checked: break the code under test, the14 test MUST go red; restore it, the test MUST go green. A test that passes on a broken implementation is15 a tautology and is rejected, not counted as coverage.163. FAIL CLOSED. If the loop hits its iteration/budget cap without a green suite, report `converged:false`17 with the exact failing tests. A red suite is NEVER reported as done, and "should pass now" is not a18 pass — re-run and read the exit code.194. Distinguish a test that reveals a real bug from a test that is itself wrong. If a new characterization20 test fails because the CODE is wrong, that is a finding to surface (or fix, if in scope) — do NOT21 rewrite the test to assert the buggy behavior.225. Coverage is a means, not the goal. Never chase a coverage % with vacuous tests. One meaningful test of23 a real behavior beats ten that assert nothing.246. ESCALATE, don't guess. Ambiguous expected behavior (is this output the bug or the spec?), a test that25 needs a product decision, or a flaky failure rooted in infra → stop and surface it.26</EXTREMELY-IMPORTANT>2728# Auto Test2930## Overview3132Drive a codebase from "some/unknown test health" to "a green suite that meaningfully covers the target",33autonomously and safely. The value is the combination: it *loops* (find gaps → write → run → fix →34re-run) but it *stops honestly* (bounded, fails closed) and it *doesn't cheat* (every test is proven able35to fail, the suite is never gamed green). It runs the `converge-loop` until-green pattern, gates each new36test through `adversarial-verify` (mutation check), and checkpoints via `checkpoint-resume`.3738## When to Use3940- After `auto-build` / any implementation, to cover the behavior that was just written41- To raise real coverage on a specific risky module (auth, money, data migrations, parsers)42- To add a regression test that reproduces a reported bug before it's fixed (the Prove-It pattern)43- To de-flake an unreliable suite (`auto-test flaky`)4445**When NOT to use:** writing tests test-first *while* implementing new behavior (that's `auto-build`'s46RED→GREEN loop); a repo with no runner configured (set one up first);47pure-config/docs changes with no behavioral surface.4849## Phase 0: Ground the run — scope, runner, baseline, methodology5051Before writing anything:52531. **Resolve scope** (`$scope`): a path/module, the current diff (default — `git diff` against the base),54 or `flaky` (stabilize mode). Narrow, targeted scope beats "test the whole repo."552. **Detect the runner + coverage tool** — read the repo: `package.json` scripts, `pytest.ini`,56 `Cargo.toml`, a `Makefile`. Capture the exact command to run the whole suite and to run a single57 file/test (you'll use the single-test form for tight loops). If none exists, STOP and say so.583. **Baseline the signal** — run the suite ONCE. Record: pass/fail counts, which tests fail, and (if59 available) current coverage on the scope. This is `converge-loop` iteration 0. If it's already green60 with the target covered, there may be nothing to do — say so, don't invent busywork.614. **Load our test-quality bar** — this skill carries its own standard for what a good test is (see62 `references/test-quality-bar.md`): the test pyramid (≈80/15/5), test sizes, DAMP-over-DRY,63 state-not-interaction assertions, real-implementations-over-mocks, and the anti-patterns to avoid.64 Apply it directly; it is self-contained, not a pointer to another collection.655. **Open a checkpoint** — start a `checkpoint-resume` run (`.ulpi/runs/<id>.json`) with one unit per66 target behavior/file. On resume, skip units already `done`.6768**Success criteria:** scope fixed; exact suite + single-test commands known; a concrete baseline69(counts + failing tests + coverage) recorded; checkpoint open.7071## Phase 1: Find the gaps — the work list7273Identify the *behaviors* that lack a meaningful test (not just uncovered lines):7475- diff the scope against the tests that touch it; list public functions/branches/error paths with no76 assertion behind them;77- prioritize by risk — untested error/edge paths, money/auth/data-mutation code, and recently changed78 code rank above cosmetic getters;79- for `flaky` mode, instead identify the tests that fail intermittently (run the suite N times, collect80 the non-deterministic failures) — those are the units.8182For a large scope, fan the discovery out with `fan-out-work` (one agent per module) and merge the gap83list. Record each gap as a checkpoint unit.8485**Success criteria:** a prioritized, de-duplicated list of concrete missing tests (or flaky tests),86each an addressable unit.8788## Phase 2: Write one meaningful test — and prove it can fail8990Per unit (smallest first), write ONE focused test, then verify it's real BEFORE trusting it:91921. **Write** it following the methodology — Arrange-Act-Assert, a descriptive name that reads like a spec,93 one concept per test, state-based assertions, real implementations over mocks. For a bug repro, write94 it to FAIL against current code (RED).952. **Mutation-check it** (`adversarial-verify` for tests): make a small, targeted break in the code under96 test (flip a comparison, drop a write, return a wrong constant) and re-run just this test — it MUST go97 red. Restore the code — it MUST go green. A test that stays green on the broken code is a tautology:98 reject it, rewrite it to actually assert the behavior. For heavy verification, delegate the99 mutation-probe to a subagent.1003. **Classify a genuine failure** — if, against the *correct* code, the test fails, decide: is the TEST101 wrong (fix the test) or is the CODE wrong (a real bug — surface it; fix only if in scope, never by102 asserting the buggy output)?103104**Success criteria:** each added test provably fails when its target is broken and passes when it's105correct; any real bug the test exposed is recorded, not papered over.106107## Phase 3: Converge the suite to green108109Run the `converge-loop` until-green pattern over the whole target, with its full termination set (done =110suite exits 0 over the scope; maxIterations; token budget; maxStall=2):111112- after each added/fixed test, run the suite (single-file form for speed during the loop; full suite at113 round boundaries to catch regressions);114- if a change regresses another test, revert and reconsider — never ratchet the suite backwards;115- if a unit can't be made green in `MAX_FIX` (≈3) attempts, mark it `blocked` with the reason and move116 on — do not spin;117- update the checkpoint as each unit reaches `done` / `blocked`.118119**Success criteria:** the scoped suite is green (or the loop terminated honestly with the specific120blocked units named); no regressions introduced.121122## Phase 4: De-flake (stabilize mode, or any flake surfaced)123124For any test that passes/fails non-deterministically:125126- reproduce by running it in a loop / with randomized order; find the root cause class — shared state,127 time/timezone, ordering, real network, unawaited async;128- fix the ROOT (isolate state, inject the clock, await properly, fake the boundary) — never "fix" a flake129 by adding a retry or a sleep to mask it;130- confirm stability: N consecutive green runs (and randomized order) before calling it fixed.131132**Success criteria:** previously-flaky tests pass deterministically across repeated + reordered runs; no133flake masked by retries/sleeps.134135## Phase 5: Report136137Finalize the checkpoint and report honestly (see Output Contract). Include the before→after signal138(counts + coverage delta), the tests added, any real bugs surfaced, and any blocked units.139140## Common Rationalizations141142| Rationalization | Reality |143|---|---|144| "The suite is green, we're done." | Green via a tautological or skipped test is a false signal. Green + mutation-proven + nothing skipped is done. |145| "This test passes immediately, ship it." | A test that passes on the first run may test nothing. Mutation-check it: break the target — if it stays green, it's vacuous. |146| "The test fails, let me relax the assertion to match." | If the code output is wrong, that's a bug to surface, not an assertion to loosen. Loosening hides the defect. |147| "I'll just skip the failing test to get green." | Skipping is faking the done-condition — the exact cardinal sin. A skipped test is an untested behavior wearing a green badge. |148| "It's flaky, add a retry." | A retry masks a real race/state bug that will bite in production. Fix the root; retries are not stabilization. |149| "Coverage is at 90%, good enough." | Coverage counts lines executed, not behaviors asserted. Ten vacuous tests raise the number and prove nothing. |150| "I ran the tests earlier, they're fine." | After any code change, the earlier run is stale. Re-run after the change; read the actual exit code. |151152## Red Flags153154- The suite went green in the same edit that deleted/`skip`ped/`.only`'d a test.155- A newly added test passes against a deliberately broken version of the code.156- An assertion was changed to match the code's current (possibly wrong) output.157- A flake "fixed" by a `sleep`, a retry wrapper, or an increased timeout.158- Coverage % climbing while assertions are vacuous (`toBeDefined`, `not.toThrow` on everything).159- "All tests pass" reported without a suite run in the transcript.160- The loop is on its 6th iteration re-trying the same failing approach (thrash — stop and escalate).161162## Enforcement (deterministic, not prose)163164While this skill is active, a skill-scoped PreToolUse hook runs `scripts/guard-test-integrity.sh` on165every Edit/Write: adding a `.only`/`.skip`/`xit`/`xdescribe`/`.todo`/`@pytest.mark.skip`/`@unittest.skip`/166`#[ignore]` marker or a `@ts-ignore`/`@ts-expect-error`/`eslint-disable`/`# type: ignore` suppression to a167test file is BLOCKED at the tool layer. That stops the skip/only/ignore/suppression class of gaming at168the Edit/Write layer (a raw-Bash write of the same marker sidesteps this Edit/Write-scoped hook — caught,169like the vectors below, by mutation-check discipline). The OTHER cheat vectors this skill forbids (deleting a test, a vacuous170`expect(true)`, weakening an assertion, masking a flake with sleeps) are not statically detectable at the171edit layer — they stay enforced by the mutation-check discipline and the fail-closed contract above, not172by this hook. Genuine, user-approved weakening goes through the explicit escape hatch —173`touch <project>/.ulpi/allow-test-weaken` opens a 2-minute approval window (then expires;174`AUTO_TEST_ALLOW_WEAKEN=1` exists for settings-level use) — with the reason stated in the reply.175176## Guardrails177178- Never weaken, skip, delete, or `.only` tests to reach green. Fail closed instead.179- Never count a test that can't fail — mutation-check every addition.180- Never rewrite a test to assert buggy behavior; surface the bug.181- Never mask a flake with sleeps/retries/timeouts; fix the root cause.182- Never chase a coverage number with vacuous tests.183- Never report green without a final real suite run and its exit code.184- Keep each iteration small and measured (one test/behavior); revert any regression immediately.185- Escalate ambiguous expected-behavior questions instead of guessing.186187## When To Load References188189- `converge-loop` (skill) — the until-green loop with the termination set + anti-thrash. The engine of190 Phase 3.191- `adversarial-verify` (skill) — the mutation-check / tautology-rejection gate for Phase 2.192- `checkpoint-resume` (skill) — the durable run state for skip-done resume.193- `fan-out-work` (skill) — parallel gap discovery / test writing over a large scope.194- `references/test-quality-bar.md` — OUR standard for a good test: pyramid, sizes, DAMP,195 state-not-interaction, real-over-mocks, and the anti-patterns. Load in Phase 0.196197## Verification198199Before reporting done, confirm:200201- [ ] The scoped suite passes on a fresh, real run (exit code read, not assumed)202- [ ] Every test added this run was mutation-checked (fails on broken code, passes on correct code)203- [ ] No test was skipped, deleted, `.only`'d, or weakened to reach green204- [ ] Any real bug a test exposed is surfaced (and fixed only if in scope — never asserted-as-correct)205- [ ] Flaky tests (if any) pass deterministically across repeated + reordered runs, with no masking206- [ ] Coverage delta (if tracked) reflects real behaviors, not vacuous assertions207- [ ] The checkpoint file reflects the final per-unit state; blocked units are named with reasons208209## Output Contract210211Report:2122131. scope + suite command used; baseline → final signal (pass/fail counts; coverage delta when the repo tracks coverage)2142. tests added (by behavior), each noted mutation-verified2153. real bugs surfaced (and whether fixed in scope or handed off)2164. flakes stabilized (root cause + how) — if any2175. loop outcome: converged, or the honest list of blocked/failing units with reasons2186. checkpoint file path (durable record; resume-able)