import type { On } from 'claude-code' import { expect, mock, test, type Engine } from 'claude-code/testing' import { decide, heuristic, onFail, parseBrief, parseVerdict, rewriteTier, skipReason, validTimeout, verifierFor, } from '../hooks/router' const DEV = '/home/homelab/projects/goodwe/clones/SAFETY-95-dev' const REVIEW = '/home/homelab/projects/goodwe/clones/SAFETY-95-review' const TEST = '/home/homelab/projects/goodwe/clones/TEST-23-test' // ---------- the brief (C2) ---------- test('brief: clone path, ticket, role and base branch are read', async () => { const b = parseBrief(`Work in ${DEV}/ only. Base branch: release/1.0.\nExpected: ...`) expect(b).toEqual({ clone: DEV, ticket: 'SAFETY-95', role: 'dev', baseBranch: 'release/1.0' }) expect(parseBrief(`Clone: \`${TEST}\``).role).toBe('test') expect(parseBrief('PR against `release/1.1`, no clone given').baseBranch).toBe('release/1.1') expect(parseBrief('just look around').clone).toBe(undefined) }) test('fix 1: tickets with extra parts parse, the role is the last part', async () => { const p = '/home/homelab/projects/goodwe/clones/OPT-11-P4-dev' expect(parseBrief(`Work in ${p}/ only.`)).toMatchObject({ clone: p, ticket: 'OPT-11-P4', role: 'dev' }) expect(parseBrief('Clone: /x/clones/OPT-11-P4-R2-test/').ticket).toBe('OPT-11-P4-R2') expect(parseBrief('Clone: /x/clones/OPT-11-P4-R2-test/').role).toBe('test') }) test('fix 2: the Clone: line decides, whatever the brief mentions first', async () => { const brief = `Verify the work of ${DEV}/ on the rig.\nClone: ${TEST}/\nBase branch: release/1.0.` expect(parseBrief(brief)).toMatchObject({ clone: TEST, role: 'test', baseBranch: 'release/1.0' }) // an explicit role wins over the path expect(parseBrief(`Clone: \`/x/clones/OPT-11-P4\` (Role: review)`)).toMatchObject({ clone: '/x/clones/OPT-11-P4', role: 'review' }) // (Role: ...) wins over the role in the path expect(parseBrief(`Clone: ${DEV}/ (Role: review)`).role).toBe('review') // a list item or quote marker in front of the line is fine expect(parseBrief(`- Clone: ${DEV}`).role).toBe('dev') // "Clone:" inside a sentence is not the line, even at a line's end expect(parseBrief(`Results are in the dev agent's Clone: ${DEV}\nRun the rig in ${TEST}/`).isAmbiguous).toBe(true) }) test('fix 2: several clones and no Clone: line gives no role, and routes to opus', async () => { const b = parseBrief(`Compare ${DEV}/ with ${TEST}/.`) expect(b.role).toBe(undefined) expect(b.isAmbiguous).toBe(true) expect(decide({ prompt: 'x', respectExplicit: false, isAmbiguous: true, named: 'haiku' })).toMatchObject({ kind: 'fixed', tier: 'opus' }) expect(decide({ prompt: 'x', respectExplicit: false, isAmbiguous: true, named: 'sonnet' })).toMatchObject({ tier: 'opus' }) expect(decide({ prompt: 'x', respectExplicit: false, isAmbiguous: true, named: 'fable' })).toMatchObject({ tier: 'fable' }) // the same path twice is one clone, not ambiguous expect(parseBrief(`Work in ${DEV}/. Commit in ${DEV}.`).role).toBe('dev') }) // ---------- who picks the writer (C4) ---------- test('C4: review and test agents keep the model they are given', async () => { for (const role of ['review', 'test'] as const) for (const named of ['haiku', 'sonnet', 'opus'] as const) expect(decide({ role, named, prompt: 'x', respectExplicit: false })).toEqual({ kind: 'fixed', tier: named, reason: `named model kept for ${role} agent`, }) }) test('C4: unnamed review and test agents run on opus', async () => { expect(decide({ role: 'test', prompt: 'run the rig', respectExplicit: false })).toMatchObject({ tier: 'opus' }) expect(decide({ role: 'review', prompt: 'review', respectExplicit: false })).toMatchObject({ tier: 'opus' }) }) test('C4: dev agents are routed even when opus is named, and that is logged', async () => { const d = decide({ role: 'dev', named: 'opus', prompt: 'Expected: clamp to 2000 W', respectExplicit: true }) expect(d.kind).toBe('classify') expect(d.opusRequested).toBe(true) expect(decide({ role: 'dev', named: 'sonnet', prompt: 'x', respectExplicit: true }).opusRequested).toBe(false) }) test('C4: respectExplicit only applies to agents without a role', async () => { expect(decide({ named: 'haiku', prompt: 'x', respectExplicit: true })).toMatchObject({ kind: 'fixed', tier: 'haiku' }) expect(decide({ named: 'haiku', prompt: 'x', respectExplicit: false }).kind).toBe('classify') }) test('C4: open design goes to opus, a decided brief is left to the classifier', async () => { expect(heuristic(undefined, 'MAINT-13: decide how the staleness guard should recover', 'dev')?.tier).toBe('opus') expect(heuristic(undefined, 'In the asyncio control loop, clamp the integrator; Expected: test_clamp passes', 'dev')).toBe(undefined) }) test('C4: narrowed light patterns: "docs" or "run tests" in a brief never mean haiku', async () => { expect(heuristic(undefined, 'Update the docs and run the tests after changing the setpoint logic')).toBe(undefined) expect(heuristic(undefined, 'Find where the MQTT discovery payload is built')?.tier).toBe('haiku') // a dev brief never takes the light path, however light it reads expect(heuristic(undefined, 'Find where the MQTT discovery payload is built', 'dev')).toBe(undefined) expect(heuristic('Explore', 'anything', 'dev')).toBe(undefined) }) // ---------- what gets verified (C1, C5) ---------- test('only finished dev rounds below opus with a clone are verified', async () => { expect(skipReason({ role: 'dev', clone: DEV, tier: 'sonnet' }, 'answer')).toBe(undefined) expect(skipReason({ role: 'dev', clone: DEV, tier: 'haiku' }, 'answer')).toBe(undefined) expect(skipReason({ role: 'dev', clone: DEV, tier: 'opus' }, 'answer')).toBe('opus-unverified') expect(skipReason({ role: 'dev', tier: 'sonnet' }, 'answer')).toBe('no-clone') expect(skipReason({ role: 'test', clone: TEST, tier: 'sonnet' }, 'answer')).toBe('test-agent') expect(skipReason({ role: 'review', clone: REVIEW, tier: 'sonnet' }, 'answer')).toBe('review-agent') expect(skipReason({ role: 'dev', clone: DEV, tier: 'sonnet' }, 'error')).toBe('agent-ended') expect(skipReason({ role: 'dev', clone: DEV, tier: 'sonnet' }, 'aborted')).toBe('agent-ended') }) test('the verifier is opus below opus, and never the writer', async () => { expect(verifierFor('haiku')).toBe('opus') expect(verifierFor('sonnet')).toBe('opus') for (const w of ['haiku', 'sonnet', 'opus', 'fable'] as const) expect(verifierFor(w)).not.toBe(w) }) test('C5: first fail resumes, second starts a stronger agent, then hand back', async () => { expect(onFail(1, 'sonnet', 2, false)).toEqual({ kind: 'resume' }) expect(onFail(2, 'sonnet', 2, false)).toEqual({ kind: 'new-agent', tier: 'opus' }) expect(onFail(3, 'opus', 2, false)).toEqual({ kind: 'hand-back' }) expect(onFail(2, 'opus', 2, false)).toEqual({ kind: 'hand-back' }) // nothing above opus expect(onFail(2, 'opus', 2, true)).toEqual({ kind: 'new-agent', tier: 'fable' }) expect(onFail(1, 'sonnet', 0, false)).toEqual({ kind: 'hand-back' }) expect(rewriteTier('haiku', false)).toBe('sonnet') }) // ---------- reading the verdict (TOOL-02) ---------- // The SAFETY-129 reply shape: a fenced block, then prose that contains a `}`. const SAFETY_129 = 'Checked the diff against the brief.\n\n```json\n{"verdict": "PASS", "issues": [], "rewrite_brief": ""}\n```\n\n' + 'Notes: the guard keeps `if (stale) { hold() }` as before, and the new test covers it.' test('TOOL-02: fenced JSON followed by prose with a } reads as PASS', async () => { expect(parseVerdict(SAFETY_129)).toEqual({ isReadable: true, pass: true, issues: [], brief: '' }) }) test('TOOL-02: the fenced answer wins over an earlier object in the prose', async () => { const r = parseVerdict('Last round said {"verdict":"FAIL","issues":["x"]}. Fixed now:\n```json\n{"verdict":"PASS","issues":[]}\n```') expect(r).toMatchObject({ isReadable: true, pass: true }) }) test('TOOL-02: bare JSON reads, with or without prose around it', async () => { expect(parseVerdict('{"verdict":"PASS","issues":[]}')).toMatchObject({ isReadable: true, pass: true }) expect(parseVerdict('Verdict below. {"verdict": "pass"} That is all {really}.')).toMatchObject({ isReadable: true, pass: true }) }) test('TOOL-02: FAIL keeps its issues and brief', async () => { const r = parseVerdict('```json\n{"verdict":"FAIL","issues":["loop.py:40 no clamp"],"rewrite_brief":"clamp it"}\n```') expect(r).toEqual({ isReadable: true, pass: false, issues: ['loop.py:40 no clamp'], brief: 'clamp it' }) }) test('TOOL-02: braces inside strings do not end the object', async () => { const r = parseVerdict('{"verdict":"FAIL","issues":["handler } has a stray brace","quote \\" and }"]} trailing {x}') expect(r).toMatchObject({ isReadable: true, pass: false }) if (r.isReadable) expect(r.issues).toEqual(['handler } has a stray brace', 'quote " and }']) }) test('TOOL-02: the first object with a verdict wins; other objects are skipped', async () => { const r = parseVerdict('Config seen: {"limit": 2000}. Verdict: {"verdict":"FAIL","issues":["x"]} and later {"verdict":"PASS"}') expect(r).toMatchObject({ isReadable: true, pass: false }) }) test('TOOL-02: a fence without a verdict falls through to the text', async () => { const r = parseVerdict('```json\n{"limit": 2000}\n```\nResult: {"verdict":"PASS"}') expect(r).toMatchObject({ isReadable: true, pass: true }) }) test('TOOL-02: garbage, prose and odd verdicts are unreadable, never a FAIL', async () => { for (const t of ['looks fine', 'verdict: pass', '{"verdict":"MAYBE"}', '{"issues":[]}', '{broken json', '']) { const r = parseVerdict(t) expect(r.isReadable).toBe(false) } const long = parseVerdict('x'.repeat(500)) expect(long.isReadable ? '' : long.preview).toBe('x'.repeat(300)) }) // ---------- the hooks, with the engine mocked beneath ---------- type World = { spawned: { model?: string; prompt: string }[] verifier: string[] verdicts: string[] told: string[] diffs: string[] diffBases: string[] testCwd: string[] testArgv: string[][] /** How the next test run ends: exit code (124 = timed out), or a throw. */ testEnds: (number | 'throw')[] /** Whether `timeout` fired on each test run (it then says so on stderr). */ testFired: boolean[] /** The timeout line arrives in two stderr pieces. */ testSplitLine?: boolean /** A test itself prints the timeout line, on stdout. */ testLineOnStdout?: boolean snap: number } const USAGE = { input_tokens: 1, output_tokens: 1, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 } const ok = (stdout: string, exitCode = 0) => ({ value: { exitCode, stdout, stderr: '', isStdoutTruncated: false, isStderrTruncated: false }, }) function world(on: On, w: World) { mock.store(on) const clock = mock.clock(on) on('ui.status', async () => ({ value: undefined })) on('ui.toast', async () => ({ value: undefined })) on('model.complete', async (_$, e) => { if (e.system?.startsWith('You route')) return { value: { isAnswered: true as const, text: 'sonnet', usage: USAGE } } w.verifier.push(e.model) return { value: { isAnswered: true as const, text: w.verdicts.shift() ?? '{"verdict":"PASS"}', usage: USAGE } } }) on('process.run', async (_$, e) => { const a = e.argv.join(' ') const cwd = String(e.init?.cwd ?? '') if (cwd !== DEV) return ok('', 128) // not a git repo: the session folder if (a === 'git merge-base origin/release/1.0 HEAD') return ok('mergebase\n') if (a === 'git stash create') return ok(`snap${++w.snap}\n`) if (a.startsWith('git diff ')) { w.diffBases.push(e.argv[2] ?? '') return ok(w.diffs.shift() ?? '') } return ok('') }) on('process.spawn', async function* (_$, e) { w.testCwd.push(String(e.cwd ?? '')) w.testArgv.push([...e.argv]) const end = w.testEnds.shift() ?? 0 const fired = w.testFired.shift() ?? false if (end === 'throw') throw new Error('docker: not found') yield { stream: 'stdout' as const, text: end === 0 ? '67 passed\n' : 'test_modes_p4 ... still running\n' } if (fired) yield { stream: 'stderr' as const, text: "timeout: sending signal TERM to command 'sh'\n" } if (w.testSplitLine) { yield { stream: 'stderr' as const, text: 'timeout: sending sig' } yield { stream: 'stderr' as const, text: "nal TERM to command 'sh'\n" } } if (w.testLineOnStdout) yield { stream: 'stdout' as const, text: 'assert "timeout: sending signal" in log\n' } return { value: { code: end, signal: null } } }) on('agent.spawn', async (_$, e) => { w.spawned.push({ model: e.model, prompt: e.prompt }) return { model: e.model ?? 'inherit', agentId: `agent-${w.spawned.length}` } }) on('turn.complete', async (_$, e) => ({ text: e.answer })) on('prompt.submit', async (_$, e) => { w.told.push(e.text) return { text: e.text } }) return clock } const fresh = (): World => ({ spawned: [], verifier: [], verdicts: [], told: [], diffs: [], diffBases: [], testCwd: [], testArgv: [], testEnds: [], testFired: [], snap: 0 }) async function finish($: Engine, clock: { advance: (ms: number) => Promise }, agentId: string) { await ($.turn as unknown as { complete: (e: unknown) => Promise }).complete({ answer: 'done', durationMs: 1, isAborted: false, turnId: 't', agentId, reason: 'answer' }) await clock.advance(1) } const devBrief = (extra = '') => `SAFETY-95: clamp the integrator. Work in ${DEV}/. Base branch: release/1.0.\nExpected: test_clamp passes.${extra}` test('acceptance 1+2: a background dev agent is verified in its clone, each round on its own diff', { options: { testCommand: 'pytest -q' } }, async ($, on) => { const w = fresh() w.diffs = ['diff --git a/loop.py', 'diff --git a/loop.py (round 2)'] w.verdicts = ['{"verdict":"FAIL","issues":["loop.py:40 no clamp on negative"]}', '{"verdict":"PASS"}'] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp', model: 'opus' } as never) expect(w.spawned[0]?.model).toBe('sonnet') // routed despite the named opus await finish($, clock, r.agentId as string) expect(w.diffBases[0]).toBe('mergebase') // first round: from the merge base, in the clone expect(w.testCwd).toEqual([DEV]) expect(w.verifier).toEqual(['opus']) expect(w.told[0]).toContain('FAIL') // round 2: the resumed agent finishes again (same agentId) await finish($, clock, r.agentId as string) expect(w.diffBases[1]).toBe('snap1') // only what this round added expect(w.told[1]).toContain('PASS') }) test('acceptance 3: review and test agents keep a named opus; dev briefs below opus are verified by opus', async ($, on) => { const w = fresh() const clock = world(on, w) await $.agent.spawn({ prompt: `Review PR. Clone ${REVIEW}/`, description: 'review', model: 'opus' } as never) await $.agent.spawn({ prompt: `Run the rig test. Clone ${TEST}/`, description: 'rig', model: 'opus' } as never) const t = await $.agent.spawn({ prompt: `Run the rig test. Clone ${TEST}/`, description: 'rig2' } as never) expect(w.spawned.map(s => s.model)).toEqual(['opus', 'opus', 'opus']) await finish($, clock, t.agentId as string) expect(w.verifier).toEqual([]) // test agents are not verified expect(w.told).toEqual([]) }) test('acceptance 4: the first FAIL asks to resume the same agent', async ($, on) => { const w = fresh() w.diffs = ['diff'] w.verdicts = ['{"verdict":"FAIL","issues":["x"]}'] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.told[0]).toContain(`Resume agent ${r.agentId} with SendMessage`) expect(w.told[0]).not.toContain('[router-rewrite') }) test('C5: the second FAIL starts a stronger agent in the same clone', async ($, on) => { const w = fresh() w.diffs = ['d1', 'd2'] w.verdicts = ['{"verdict":"FAIL","issues":["x"]}', '{"verdict":"FAIL","issues":["y"]}'] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) await finish($, clock, r.agentId as string) const id = w.told[1]?.match(/\[router-rewrite (T\d+)\]/)?.[1] expect(id).toBeDefined() await $.agent.spawn({ prompt: `[router-rewrite ${id}]`, description: 'rewrite' } as never) expect(w.spawned[1]?.model).toBe('opus') expect(w.spawned[1]?.prompt).toContain(`Their work is in the clone ${DEV}: continue from it`) expect(w.spawned[1]?.prompt).toContain('- y') }) test('C2: a dev brief with no clone is never diffed in the session folder', async ($, on) => { const w = fresh() const clock = world(on, w) const r = await $.agent.spawn({ prompt: 'Clamp the integrator in clones/ somewhere. Expected: ...', description: 'x' } as never) await finish($, clock, r.agentId as string) expect(w.diffBases).toEqual([]) expect(w.verifier).toEqual([]) }) test('fix 2 end to end: a test brief that mentions the dev clone first keeps its model and is not verified', async ($, on) => { const w = fresh() const clock = world(on, w) const r = await $.agent.spawn({ prompt: `Run the OPT-11 P4 rig test against the work in ${DEV}/.\nClone: /home/homelab/projects/goodwe/clones/OPT-11-P4-test/`, description: 'rig', model: 'opus', } as never) expect(w.spawned[0]?.model).toBe('opus') await finish($, clock, r.agentId as string) expect(w.verifier).toEqual([]) expect(w.diffBases).toEqual([]) }) test('fix 2 end to end: two clones and no Clone: line run on opus, unverified', async ($, on) => { const w = fresh() const clock = world(on, w) const r = await $.agent.spawn({ prompt: `Compare ${DEV}/ with ${TEST}/ and report.`, description: 'cmp', model: 'haiku' } as never) expect(w.spawned[0]?.model).toBe('opus') await finish($, clock, r.agentId as string) expect(w.verifier).toEqual([]) }) test('a brief without a role is routed but never tracked or verified', async ($, on) => { const w = fresh() const clock = world(on, w) const r = await $.agent.spawn({ prompt: 'Find where the MQTT discovery payload is built', description: 'look' } as never) expect(w.spawned[0]?.model).toBe('haiku') await finish($, clock, r.agentId as string) expect(w.verifier).toEqual([]) expect(w.told).toEqual([]) }) // ---------- the test run ---------- test('tests stream with a real time limit, not the ten-minute command cap', { options: { testCommand: 'make verifier-tests', testTimeoutSec: 1800 } }, async ($, on) => { const w = fresh() w.diffs = ['diff'] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.testArgv[0]).toEqual(['timeout', '--verbose', '--kill-after=30', '1800', 'sh', '-c', 'make verifier-tests']) expect(w.testCwd).toEqual([DEV]) expect(w.verifier).toEqual(['opus']) }) test('a test timeout is NOT VERIFIED, never a FAIL: no verifier call, no resume, no fail counted', { options: { testCommand: 'make t', testTimeoutSec: 60 } }, async ($, on) => { const w = fresh() w.diffs = ['diff', 'diff 2'] w.testEnds = [124, 0] w.testFired = [true, false] w.verdicts = ['{"verdict":"FAIL","issues":["x"]}'] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual([]) expect(w.told[0]).toContain('NOT VERIFIED - the tests did not finish within 60 s') expect(w.told[0]).not.toContain('Resume agent') // the next round's FAIL is the first one counted: it resumes, it does not rewrite await finish($, clock, r.agentId as string) expect(w.told[1]).toContain('Resume agent') }) test('a kill after the grace period also counts as a timeout', { options: { testCommand: 'make t' } }, async ($, on) => { const w = fresh() w.diffs = ['diff'] w.testEnds = [137] w.testFired = [true] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual([]) expect(w.told[0]).toContain('did not finish') }) test('a test command that cannot run (exit 127) is NOT VERIFIED, not a FAIL', { options: { testCommand: 'make t' } }, async ($, on) => { const w = fresh() w.diffs = ['diff'] w.testEnds = [127] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual([]) expect(w.told[0]).toContain('could not run') }) test('a test command that cannot run (a start failure) is NOT VERIFIED, not a FAIL', { options: { testCommand: 'make t' } }, async ($, on) => { const w = fresh() w.diffs = ['diff'] w.testEnds = ['throw' as const] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual([]) expect(w.told[0]).toContain('could not run') }) test('failing tests are evidence: the verifier sees them and decides', { options: { testCommand: 'make t' } }, async ($, on) => { const w = fresh() w.diffs = ['diff'] w.testEnds = [1] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual(['opus']) }) test('an early 137 (out of memory) is a failed run for the verifier, not a timeout', { options: { testCommand: 'make t', testTimeoutSec: 1800 } }, async ($, on) => { const w = fresh() w.diffs = ['diff'] w.testEnds = [137] w.testFired = [false] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual(['opus']) expect(w.told[0] ?? '').not.toContain('NOT VERIFIED') }) test('an invalid testTimeoutSec falls back to 1800', async () => { for (const bad of [0, -5, Number.NaN, 'abc', undefined, null]) expect(validTimeout(bad)).toBe(1800) expect(validTimeout(900)).toBe(900) expect(validTimeout('600')).toBe(600) }) test('a command that exits 124 by itself is a failed run, not a timeout', { options: { testCommand: 'make t' } }, async ($, on) => { const w = fresh() w.diffs = ['diff'] w.testEnds = [124] w.testFired = [false] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual(['opus']) }) test('the timeout line still counts when it arrives split across pieces', { options: { testCommand: 'make t', testTimeoutSec: 60 } }, async ($, on) => { const w = fresh() w.diffs = ['diff'] w.testEnds = [124] w.testSplitLine = true const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual([]) expect(w.told[0]).toContain('did not finish') }) test('the timeout line on stdout (a test printing it) is not a fired timeout', { options: { testCommand: 'make t' } }, async ($, on) => { const w = fresh() w.diffs = ['diff'] w.testEnds = [1] w.testLineOnStdout = true const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual(['opus']) }) // ---------- TOOL-02 end to end ---------- test('TOOL-02: the SAFETY-129 reply passes the round, no rewrite', async ($, on) => { const w = fresh() w.diffs = ['diff'] w.verdicts = [SAFETY_129] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual(['opus']) expect(w.told[0]).toContain('PASS from verifier opus') }) test('TOOL-02: an unreadable reply is asked again once, and a readable answer then counts', async ($, on) => { const w = fresh() w.diffs = ['diff'] w.verdicts = ['I think it is fine overall.', '{"verdict":"FAIL","issues":["x"]}'] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual(['opus', 'opus']) expect(w.told[0]).toContain('Resume agent') }) test('TOOL-02: unreadable twice is handed back, no fail counted, no rewrite', async ($, on) => { const w = fresh() w.diffs = ['diff', 'diff 2'] w.verdicts = ['Looks good to me.', 'Still prose, sorry.', '{"verdict":"FAIL","issues":["y"]}'] const clock = world(on, w) const r = await $.agent.spawn({ prompt: devBrief(), description: 'clamp' } as never) await finish($, clock, r.agentId as string) expect(w.verifier).toEqual(['opus', 'opus']) expect(w.told[0]).toContain('NOT VERIFIED - verdict unreadable') expect(w.told[0]).toContain('Start of its reply: Looks good to me.') expect(w.told[0]).not.toContain('[router-rewrite') expect(w.told[0]).not.toContain('Resume agent') // the next round's FAIL is the first one counted: it resumes, it does not rewrite await finish($, clock, r.agentId as string) expect(w.told[1]).toContain('Resume agent') expect(w.told[1]).not.toContain('[router-rewrite') })