Test Loop
One shared skeleton, two modes. Eliminates wrong-approach / buggy-first-pass friction and regression-on-refactor risk by forcing tests-before-code and CI parity before shipping.
Shared skeleton
- Write tests (mode-specific — see below)
- Commit the tests as a baseline, separate from any behavior change
- Change code in small increments
- Keep tests green after every increment
- Full CI parity gate before PR:
tsc -b && npm test && npm run lint
Mode selection
- Bug report, issue number, “fix X is broken” → bugfix mode
- Rename/restructure/extract/split/upgrade with no intended behavior change → refactor mode
- Ambiguous → ask the user which one before writing anything
Mode: bugfix (failing-test-first)
Phase 1 — RED
- Read issue, reproduce bug locally.
- Write a test that captures expected behavior. Test MUST fail.
- Run
tsc -b && npm test— confirm the test fails for the RIGHT reason (not a compile error, not a wrong assertion). - Commit failing test alone:
test: failing case for #N.
Phase 2 — GREEN
- Implement MINIMAL fix. No refactor, no adjacent cleanup.
- After every edit run full CI parity:
tsc -b && npm test && npm run lint - Loop until all three pass. NEVER skip a step.
- 3 failed attempts → STOP, explain blocker, ask user.
Phase 3 — SHIP
- Commit fix separate from test:
fix: <one line> (#N). git branch --show-currentbefore push.gh pr createreferencing issue.
Mode: refactor (characterization-first)
For refactors with no intended behavior change: renaming/restructuring, splitting a god component, extracting a hook/util, deduping logic, migrating styles/theme without altering rendered output, or a contained library/version upgrade.
Not for: bug fixes (use bugfix mode above), feature adds, or a mechanical rename across files already covered by existing tests.
Step 1 — Identify target + capture surface
Ask the user (if not stated) for:
- Target file/dir
- Public surface to preserve: rendered output, network calls, exposed API, side effects
Step 2 — Write characterization tests
Generate tests that PIN current behavior:
- React component: Playwright snapshot of rendered DOM in 2–3 prop states + assertion on emitted network calls
- Pure function: Vitest cases for every observed input → output pair from existing usage (grep callers)
- Service/class: spec covering each public method’s happy path + error paths
Tests MUST run green against unchanged code. If they fail on unchanged code, the tests are wrong — fix them before continuing.
Step 3 — Baseline commit
git checkout -b test/<slug>-baseline
git add <test files>
git commit -m "test(baseline): characterization tests for <target> before refactor"
Step 4 — Iterate refactor
Loop, max 20 iterations:
- Make one small refactor increment
- Run full suite:
tsc -b && npm test && npm run lint - Green → commit
refactor: <description>and continue - Red → diagnose ROOT CAUSE (not symptom):
- Behavior actually changed? → revert + rethink
- Baseline test was brittle (timing, ordering)? → harden test in a separate commit, then continue
- Cap at 20 iterations. If goal not reached, surface to user with a diff summary.
Step 5 — Final diff report
Refactor: <slug>
Baseline tests: <count>
Iterations: <n>/20
Net diff: <files changed>, +<add>/-<del>
Behavior delta: NONE (all baseline tests passing)
Subagent routing (refactor mode)
executor(sonnet) — write tests + refactor incrementsverifier(sonnet) — run suites + parse failures- Escalate to
opusonly after 2 sonnet root-cause attempts on the same failure.
Hard Rules (both modes)
- Use
tsc -b(CI parity), NEVERtsc --noEmit. - NEVER edit
prisma/schema.prismawithout explicit user auth. - NEVER bypass hooks (
--no-verify,[skip-verify]) without user say-so. - NEVER claim done without pasting the exit codes of
tsc -b && npm test && npm run lint. - Root-cause failures, never symptom-patch tests to make them pass.
- Bugfix mode: 3 failed fix attempts → STOP, explain blocker, ask user.
- Refactor mode: NEVER skip the baseline test step — “I’ll just be careful” is how regressions ship. Baseline tests are committed in their OWN branch/commit before any refactor commit, reviewable separately. If the 20-iteration cap is hit, surface partial progress — do not silently push half-finished work.