blob: c776a2a7e358cd432e5195fbe1b4943b09ceaa39 [file]
import assert from 'node:assert/strict'
import { readdirSync, readFileSync } from 'node:fs'
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
const source = readFileSync(new URL('../code-verify.js', import.meta.url), 'utf8')
const workflowBody = source.replace(/^export /m, '')
const RESULT = {
type: 'object', additionalProperties: false,
required: ['answer'],
properties: { answer: { type: 'string' } },
}
const answers = {
codex: { answer: 'codex' },
claude: { answer: 'claude' },
}
async function run(args, provided = answers) {
const calls = []
let parallelCalls = 0
const agent = async (prompt, options) => {
const provider = options.agentType === 'code-verifier' ? 'claude'
: options.agentType === 'codex-code-verifier' ? 'codex' : 'unknown'
calls.push({ provider, prompt, options })
const answer = provided[provider]
if (answer instanceof Error) throw answer
if (answer && typeof answer.answer !== 'string') throw new Error('schema mismatch')
return structuredClone(answer)
}
const parallel = async (thunks) => {
parallelCalls++
return Promise.all(thunks.map(fn => Promise.resolve().then(fn).catch(() => null)))
}
const fn = new AsyncFunction(
'args', 'agent', 'pipeline', 'parallel', 'phase', 'log', 'workflow', 'budget',
workflowBody,
)
const result = await fn(args, agent, null, parallel, () => {}, () => {}, null, null)
return { result, calls, parallelCalls }
}
let failed = 0
async function check(name, fn) {
try {
await fn()
console.log(` ok ${name}`)
} catch (error) {
failed++
console.log(` FAIL ${name}\n ${error.stack || error}`)
}
}
await check('defaults to codex', async () => {
const { result, calls, parallelCalls } = await run({ prompt: 'review', schema: RESULT })
assert.deepEqual(result, answers.codex)
assert.deepEqual(calls.map(c => c.provider), ['codex'])
assert.equal(parallelCalls, 0)
assert.equal(calls[0].options.agentType, 'codex-code-verifier')
assert.deepEqual(calls[0].options.schema, RESULT)
assert.deepEqual(JSON.parse(calls[0].prompt), { prompt: 'review', schema: RESULT })
})
await check('bridge owns the Codex subprocess contract', async () => {
const bridge = readFileSync(new URL('../../agents/codex-code-verifier.md', import.meta.url), 'utf8')
assert.match(bridge, /model: haiku/)
assert.match(bridge, /effort: low/)
assert.match(bridge, /\.codex\/agents\/code-verifier\.toml/)
assert.match(bridge, /tomllib/)
assert.match(bridge, /timeout 600s codex exec/)
assert.match(bridge, /--sandbox read-only/)
assert.match(bridge, /--output-schema/)
assert.match(bridge, /--output-last-message/)
})
await check('selects claude', async () => {
const { result, calls, parallelCalls } = await run(
{ prompt: 'review', schema: RESULT, provider: 'claude', label: 'check' })
assert.deepEqual(result, answers.claude)
assert.deepEqual(calls.map(c => c.provider), ['claude'])
assert.equal(parallelCalls, 0)
assert.equal(calls[0].prompt, 'review')
assert.equal(calls[0].options.label, 'check:claude')
assert.deepEqual(calls[0].options.schema, RESULT)
})
await check('runs both independently', async () => {
const { result, calls, parallelCalls } = await run(
{ prompt: 'review', schema: RESULT, provider: 'both' })
assert.deepEqual(result, { codex: answers.codex, claude: answers.claude })
assert.deepEqual(calls.map(c => c.provider).sort(), ['claude', 'codex'])
assert.equal(parallelCalls, 1)
})
await check('rejects invalid input before dispatch', async () => {
for (const args of [null, {}, { prompt: '', schema: RESULT }, { prompt: 'x', schema: [] }]) {
await assert.rejects(run(args), /args must be/)
}
for (const provider of ['', 'auto']) await assert.rejects(
run({ prompt: 'review', schema: RESULT, provider }),
/provider must be codex, claude, or both/,
)
})
await check('fails closed when codex dies', async () => {
await assert.rejects(
run({ prompt: 'review', schema: RESULT }, { ...answers, codex: null }),
/codex verifier failed/,
)
await assert.rejects(
run({ prompt: 'review', schema: RESULT }, { ...answers, codex: new Error('broken') }),
/codex verifier failed: broken/,
)
await assert.rejects(
run({ prompt: 'review', schema: RESULT, provider: 'both' },
{ ...answers, codex: new Error('broken') }),
/codex verifier failed/,
)
await assert.rejects(
run({ prompt: 'review', schema: RESULT }, { ...answers, codex: { answer: 1 } }),
/codex verifier failed: schema mismatch/,
)
})
await check('only validate bypasses the router for one-level nesting', async () => {
const dir = new URL('../', import.meta.url)
assert.ok(readdirSync(dir).includes('code-verify.js'), 'workflow scan must target its parent directory')
const offenders = readdirSync(dir)
.filter(name => name.endsWith('.js') && !['code-verify.js', 'validate.js'].includes(name))
.filter(name => /agentType:\s*['"](?:codex-)?code-verifier['"]/.test(
readFileSync(new URL(name, dir), 'utf8')))
assert.deepEqual(offenders, [])
})
await check('router rejections preserve caller null-result contracts', async () => {
for (const name of ['fanout-dev.js', 'pr-babysit.js']) {
const src = readFileSync(new URL(`../${name}`, import.meta.url), 'utf8')
assert.match(src, /workflow\('code-verify',[\s\S]*?\}\)\.catch\(\(\) => null\)\s*\.then/)
}
})
await check('driver review drops a failed routed scanner', async () => {
const src = readFileSync(new URL('../driver-review.js', import.meta.url), 'utf8').replace(/^export /m, '')
const fn = new AsyncFunction(
'args', 'agent', 'pipeline', 'parallel', 'phase', 'log', 'workflow', 'budget', src)
const pipeline = async (items, ...stages) => Promise.all(items.map(async item => {
let value = item
for (const stage of stages) value = await stage(value, item)
return value
}))
const result = await fn(
{ dirs: ['src/portable/test'], dimensions: ['correctness'] },
null, pipeline, null, () => {}, () => {}, async () => { throw new Error('verifier died') }, null,
)
assert.deepEqual(result, [])
})
await check('validate dispatches directly to stay within one workflow level', async () => {
const src = readFileSync(new URL('../validate.js', import.meta.url), 'utf8')
assert.equal((src.match(/workflow\(['"]code-verify['"]/g) || []).length, 0)
assert.match(src, /const reviewProvider = reviewStageNames\.length === 2 \? 'both'/)
assert.match(src, /agentType:\s*'codex-code-verifier'/)
assert.match(src, /agentType:\s*'code-verifier'/)
assert.doesNotMatch(src, /codex review --base/)
assert.doesNotMatch(src, /model:\s*['"]opus['"][^}]*schema:\s*REVIEW/)
})
await check('validate keeps provider results and gates separate', async () => {
const src = readFileSync(new URL('../validate.js', import.meta.url), 'utf8').replace(/^export /m, '')
const calls = []
let failCodex = false
let blocking = false
const fn = new AsyncFunction(
'args', 'agent', 'pipeline', 'parallel', 'phase', 'log', 'workflow', 'budget', src)
const agent = async (prompt, options) => {
calls.push(options.agentType)
if (options.agentType === 'code-verifier') return {
pass: true, detail: 'claude',
findings: [{ file: 'a.c', line: 1,
severity: blocking ? 'CONFIRMED P1 safety' : 'CONFIRMED P2 correctness', summary: 'bug' }],
}
if (options.agentType === 'codex-code-verifier') return failCodex ? null : {
pass: true, detail: 'codex',
findings: [{ file: 'b.c', line: 2,
severity: blocking ? 'CONFIRMED P1 safety' : 'CONFIRMED P1 quality', summary: 'bug' }],
}
assert.equal(options.agentType, 'builder')
return { board: 'test', pass: true, builtCount: 1, failures: [] }
}
const workflow = async () => { throw new Error('validate cannot nest a workflow') }
const parallel = thunks => Promise.all(thunks.map(run => run()))
const result = await fn(
{ boards: ['test'], skip: ['unit', 'size', 'pvs'], reviewProvider: 'both', maxCycles: 1 },
agent, null, parallel, () => {}, () => {}, workflow, null,
)
assert.deepEqual(calls.sort(), ['builder', 'code-verifier', 'codex-code-verifier'])
assert.equal(result.stages.find(s => s.stage === 'review').pass, true)
assert.equal(result.stages.find(s => s.stage === 'codex').pass, true)
blocking = true
const blocked = await fn(
{ boards: ['test'], skip: ['unit', 'size', 'pvs'], reviewProvider: 'both', maxCycles: 1 },
agent, null, parallel, () => {}, () => {}, workflow, null,
)
assert.equal(blocked.stages.find(s => s.stage === 'review').pass, false)
assert.equal(blocked.stages.find(s => s.stage === 'codex').pass, false)
failCodex = true
const partial = await fn(
{ boards: ['test'], skip: ['unit', 'size', 'pvs'], reviewProvider: 'both', maxCycles: 1 },
agent, null, parallel, () => {}, () => {}, workflow, null,
)
assert.equal(partial.stages.find(s => s.stage === 'review').detail, 'claude')
assert.equal(partial.stages.find(s => s.stage === 'codex').detail, 'stage agent died')
})
await check('validate reviews with codex unless claude is explicitly selected', async () => {
const src = readFileSync(new URL('../validate.js', import.meta.url), 'utf8').replace(/^export /m, '')
const fn = new AsyncFunction(
'args', 'agent', 'pipeline', 'parallel', 'phase', 'log', 'workflow', 'budget', src)
const parallel = thunks => Promise.all(thunks.map(run => run()))
const runValidate = async extra => {
const calls = []
const agent = async (prompt, options) => {
calls.push(options.agentType)
if (options.agentType === 'builder') return { board: 'test', pass: true, builtCount: 1, failures: [] }
return { pass: true, detail: options.agentType, findings: [] }
}
const result = await fn(
{ boards: ['test'], skip: ['unit', 'size', 'pvs'], maxCycles: 1, ...extra },
agent, null, parallel, () => {}, () => {}, null, null,
)
return { calls, result }
}
// the default must never spend a Claude review
for (const extra of [{}, { reviewProvider: 'codex' }]) {
const { calls, result } = await runValidate(extra)
assert.deepEqual(calls.sort(), ['builder', 'codex-code-verifier'])
assert.equal(result.stages.some(s => s.stage === 'review'), false)
assert.equal(result.stages.find(s => s.stage === 'codex').pass, true)
}
const claudeOnly = await runValidate({ reviewProvider: 'claude' })
assert.deepEqual(claudeOnly.calls.sort(), ['builder', 'code-verifier'])
assert.equal(claudeOnly.result.stages.some(s => s.stage === 'codex'), false)
const both = await runValidate({ reviewProvider: 'both' })
assert.deepEqual(both.calls.sort(), ['builder', 'code-verifier', 'codex-code-verifier'])
// skip still turns a selected reviewer off
const skipped = await runValidate({ reviewProvider: 'both', skip: ['unit', 'size', 'pvs', 'review'] })
assert.deepEqual(skipped.calls.sort(), ['builder', 'codex-code-verifier'])
// selecting a reviewer and skipping it is contradictory input, not silence
await assert.rejects(
runValidate({ reviewProvider: 'claude', skip: ['unit', 'size', 'pvs', 'review'] }),
/reviewProvider "claude" is cancelled by skip/,
)
await assert.rejects(
runValidate({ reviewProvider: 'both', skip: ['unit', 'size', 'pvs', 'review', 'codex'] }),
/reviewProvider "both" is cancelled by skip/,
)
// skipping the default reviewer is legal but leaves nothing reviewing the diff
const noReview = await runValidate({ skip: ['unit', 'size', 'pvs', 'codex'] })
assert.deepEqual(noReview.calls.sort(), ['builder'])
assert.equal(noReview.result.stages.some(s => s.stage === 'review' || s.stage === 'codex'), false)
for (const reviewProvider of ['', 'auto', 'CODEX']) {
await assert.rejects(runValidate({ reviewProvider }), /reviewProvider must be codex, claude, or both/)
}
})
await check('full-check forwards the reviewer selection to validate', async () => {
const src = readFileSync(new URL('../full-check.js', import.meta.url), 'utf8').replace(/^export /m, '')
const fn = new AsyncFunction(
'args', 'agent', 'pipeline', 'parallel', 'phase', 'log', 'workflow', 'budget', src)
const seen = []
const workflow = async (name, workflowArgs) => {
seen.push({ name, args: workflowArgs })
return { pass: true }
}
await fn({ boards: ['test'], reviewProvider: 'both' },
null, null, null, () => {}, () => {}, workflow, null)
assert.deepEqual(seen.map(s => s.name), ['validate'])
assert.equal(seen[0].args.reviewProvider, 'both')
assert.deepEqual(seen[0].args.boards, ['test'])
})
console.log(failed ? `\n${failed} FAILED` : '\nall checks passed')
process.exit(failed ? 1 : 0)