ship-loop-test — jest test suite child loop
Activation
- Standalone:
/loop ship-loop-test
- Orchestrated: invoked by
ship-loop-master when build.state === 'green' AND (test.state !== 'green' OR test.last_run > 30 min ago)
Pre-condition gate
Read ship-loop-state.json. If layers.build.state !== 'green':
- Skip this run; write event
{"reason":"build_not_green","skipped":true}
- Return to master without changing test state
Process
Step 1: Run jest
cd D:/Synthex
npm test 2>&1 | tee /tmp/ship-loop-test-output.log
Parse output for:
Test Suites: X failed, Y skipped, Z passed, T total line
Tests: A failed, B skipped, C todo, D passed, E total line
- All
^FAIL lines (failing test file paths)
- For each failure, extract first 5 lines of error context
Step 2: Update state
Atomic update to layers.test:
{
"state": "green" | "red",
"last_run": "<iso>",
"retries": <int>,
"details": {
"suites": { "passed": <int>, "failed": <int>, "skipped": <int>, "total": <int> },
"tests": { "passed": <int>, "failed": <int>, "todo": <int>, "skipped": <int>, "total": <int> },
"failing_suites": [
{ "path": "tests/...", "first_error": "..." }
]
}
}
state === 'green' IFF suites.failed === 0 AND tests.failed === 0.
Step 3: Recovery sub-loop on red
If state === 'red' AND retries < 1:
For each failing suite (process up to 3 per tick to avoid runaway):
- Match error against
recovery-recipes.md (priorities below)
- If match: apply recipe, increment
retries, re-run JUST that suite via npx jest --config jest.worktree.cjs <path>, update state
- If no match OR retry-also-fails: append to
escalations.md:## P1 — Test failure: <suite path>
- When: <iso>
- First error: <verbatim>
- Recipe attempted: <# or "no match">
- Repro: `cd D:/Synthex && npx jest --config jest.worktree.cjs <path>`
Recipe priorities for this loop
Most likely matches:
- #3 (uncrypto/ESM) —
SyntaxError: Unexpected token 'export'
- #6 (
fail() deprecation) — ReferenceError: fail is not defined
- #7 (auth ratchet breach) —
Auth coverage ratchet breached
- #9 (worktree pollution) — failing path starts with
.claude/worktrees/
Bespoke recovery: missing mock
If error matches Cannot find module '<pkg>' AND <pkg> is in package.json AND a similar mock exists at tests/__mocks__/:
Verification
- Insert a failing test (
expect(true).toBe(false)) in tests/unit/foo.test.ts; expect first run red, no recipe match, escalation written
- Add
import 'uncrypto' to a test file and remove uncrypto from transformIgnorePatterns; expect recipe #3 to match, recipe applied, retry succeeds
Out of scope
- Coverage threshold validation (defer to CI; jest config already has thresholds)
- Flaky test detection (would need re-run statistics — Phase 2 enhancement)
- E2E playwright tests (excluded per
testPathIgnorePatterns in jest configs)
1---2name: ship-loop-test3description: Jest test suite child loop. Runs `npm test` (uses jest.worktree.cjs per package.json), records pass/fail counts and failing suite paths into ship-loop-state.json. Only fires if last ship-loop-build was green. Applies one recovery recipe (jest config / mock generation / worktree cleanup) on failure, retries once, escalates. Use standalone via /loop ship-loop-test or wired into the master orchestrator.4---56# ship-loop-test — jest test suite child loop78## Activation910- Standalone: `/loop ship-loop-test`11- Orchestrated: invoked by `ship-loop-master` when `build.state === 'green' AND (test.state !== 'green' OR test.last_run > 30 min ago)`1213## Pre-condition gate1415Read `ship-loop-state.json`. If `layers.build.state !== 'green'`:1617- Skip this run; write event `{"reason":"build_not_green","skipped":true}`18- Return to master without changing test state1920## Process2122### Step 1: Run jest2324```bash25cd D:/Synthex26npm test 2>&1 | tee /tmp/ship-loop-test-output.log27```2829Parse output for:3031- `Test Suites: X failed, Y skipped, Z passed, T total` line32- `Tests: A failed, B skipped, C todo, D passed, E total` line33- All `^FAIL ` lines (failing test file paths)34- For each failure, extract first 5 lines of error context3536### Step 2: Update state3738Atomic update to `layers.test`:3940```json41{42 "state": "green" | "red",43 "last_run": "<iso>",44 "retries": <int>,45 "details": {46 "suites": { "passed": <int>, "failed": <int>, "skipped": <int>, "total": <int> },47 "tests": { "passed": <int>, "failed": <int>, "todo": <int>, "skipped": <int>, "total": <int> },48 "failing_suites": [49 { "path": "tests/...", "first_error": "..." }50 ]51 }52}53```5455`state === 'green'` IFF `suites.failed === 0 AND tests.failed === 0`.5657### Step 3: Recovery sub-loop on red5859If `state === 'red'` AND `retries < 1`:6061For each failing suite (process up to 3 per tick to avoid runaway):62631. Match error against `recovery-recipes.md` (priorities below)642. If match: apply recipe, increment `retries`, re-run JUST that suite via `npx jest --config jest.worktree.cjs <path>`, update state653. If no match OR retry-also-fails: append to `escalations.md`:66 ```md67 ## P1 — Test failure: <suite path>6869 - When: <iso>70 - First error: <verbatim>71 - Recipe attempted: <# or "no match">72 - Repro: `cd D:/Synthex && npx jest --config jest.worktree.cjs <path>`73 ```7475## Recipe priorities for this loop7677Most likely matches:7879- #3 (uncrypto/ESM) — `SyntaxError: Unexpected token 'export'`80- #6 (`fail()` deprecation) — `ReferenceError: fail is not defined`81- #7 (auth ratchet breach) — `Auth coverage ratchet breached`82- #9 (worktree pollution) — failing path starts with `.claude/worktrees/`8384### Bespoke recovery: missing mock8586If error matches `Cannot find module '<pkg>'` AND `<pkg>` is in `package.json` AND a similar mock exists at `tests/__mocks__/`:8788- Auto-create `tests/__mocks__/<pkg>.js` with skeleton:89 ```js90 // Auto-generated mock by ship-loop-test recovery91 module.exports = {};92 module.exports.default = {};93 ```94- Re-run; if still fails, escalate (mock needs hand-curation)9596## Verification9798- Insert a failing test (`expect(true).toBe(false)`) in `tests/unit/foo.test.ts`; expect first run red, no recipe match, escalation written99- Add `import 'uncrypto'` to a test file and remove uncrypto from `transformIgnorePatterns`; expect recipe #3 to match, recipe applied, retry succeeds100101## Out of scope102103- Coverage threshold validation (defer to CI; jest config already has thresholds)104- Flaky test detection (would need re-run statistics — Phase 2 enhancement)105- E2E playwright tests (excluded per `testPathIgnorePatterns` in jest configs)