// tests/lib/independent-review-fixes.test.mjs // Findings of the independent review of c56f852..2d29966 (2026-10-08), each // verified by the reviewer by running it; each test fails on that code. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { spawnSync } from 'node:child_process'; import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; import { runHook } from '../helpers/hook-helper.mjs'; import { makeRepo, parseJsonOut } from '../hooks/guard-helpers.mjs'; import { recordVerdict, planHash, acceptOpen, gatePlan } from '../../lib/validators/plan-gate.mjs'; import { stampBriefReview, checkBriefReview } from '../../lib/validators/brief-review-stamp.mjs'; import { stampIntentApproval } from '../../lib/validators/intent-approval.mjs'; import { buildPlan } from '../helpers/plan-v18-builder.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const LOCK = join(ROOT, 'lib', 'util', 'test-lock.mjs'); const STOP = join(ROOT, 'hooks', 'scripts', 'stop-test-lock.mjs'); const SESS = `sess-review-${process.pid}-${Date.now()}`; // --- 1 + 5: the green mark runs the step's OWN test command, stored at lock time function lockRepo(withCmd) { const repo = makeRepo('main'); mkdirSync(join(repo, 'tests'), { recursive: true }); writeFileSync(join(repo, 'tests', 'a.test.mjs'), "import { test } from 'node:test';\nimport assert from 'node:assert';\ntest('a', () => assert.equal(1, 2));\n"); const args = ['lock', '--files', 'tests/a.test.mjs', '--step', '1']; if (withCmd) args.push('--cmd', 'node --test tests/a.test.mjs'); const r = spawnSync(process.execPath, [LOCK, ...args], { cwd: repo, encoding: 'utf8', env: { ...process.env, CLAUDE_CODE_SESSION_ID: SESS } }); assert.equal(r.status, 0, r.stderr); return repo; } const lockCli = (repo, args) => spawnSync(process.execPath, [LOCK, ...args], { cwd: repo, encoding: 'utf8', env: { ...process.env, CLAUDE_CODE_SESSION_ID: SESS } }); test('green: another allowed command (git status) can never mark a red step green', async () => { const repo = lockRepo(true); assert.notEqual(lockCli(repo, ['green', '--cmd', 'git status']).status, 0); assert.equal(lockCli(repo, ['green']).status, 1, 'the stored test command is red'); assert.equal(JSON.parse(lockCli(repo, ['status']).stdout).green_at, null); const stop = await runHook(STOP, { hook_event_name: 'Stop', session_id: SESS, cwd: repo, stop_hook_active: false }); assert.equal(parseJsonOut(stop.stdout)?.decision, 'block'); }); test('green: a lock that names no test command is never marked green (exit 2)', () => { const repo = lockRepo(false); assert.equal(lockCli(repo, ['green', '--cmd', 'node --test tests/a.test.mjs']).status, 2); }); test('green without --cmd runs the stored command; status shows it', () => { const repo = lockRepo(true); writeFileSync(join(repo, 'tests', 'a.test.mjs'), "import { test } from 'node:test';\ntest('a', () => {});\n"); assert.equal(lockCli(repo, ['green']).status, 0); assert.equal(JSON.parse(lockCli(repo, ['status']).stdout).cmd, 'node --test tests/a.test.mjs'); }); test('prose: the helper locks with --cmd, and D1 calls green without re-quoting the command', () => { assert.match(readFileSync(join(ROOT, 'agents', 'step-implementer.md'), 'utf8'), /test-lock\.mjs lock --files --step \{step\} --cmd "\{Verify command\}"/); const ex = readFileSync(join(ROOT, 'commands', 'trekexecute.md'), 'utf8'); assert.match(ex, /test-lock\.mjs green`?\n/); assert.doesNotMatch(ex, /test-lock\.mjs green --cmd/); }); // --- 2: --accept-open only after the last round ------------------------------- const BOTH = [{ agent: 'plan-critic', present: true }, { agent: 'scope-guardian', present: true }]; test('--accept-open refuses while rounds are left: revise instead', () => { const t = buildPlan(); const r = recordVerdict(t, { findings: [{ id: 'm', severity: 'major', text: 'weak test' }], sources: BOTH }, { reviewed: planHash(t) }); const a = acceptOpen(r.text); assert.equal(a.code, 1); assert.match(a.message, /round\(s\) left/); assert.equal(gatePlan(r.text).code, 1); }); // --- 3: --repo-root never swallows a flag or accepts a missing root ---------- test('findings-schema: --repo-root followed by a flag, or naming no directory, is a usage error (exit 2)', () => { const dir = mkdtempSync(join(tmpdir(), 'voyage-root-')); try { const out = join(dir, 'r.md'); writeFileSync(out, '```json\n{"reviewer":"x","findings":[]}\n```\n'); const cli = (...a) => spawnSync(process.execPath, [join(ROOT, 'lib', 'review', 'findings-schema.mjs'), ...a], { encoding: 'utf8' }); assert.equal(cli('--repo-root', '--json', out).status, 2); assert.equal(cli('--repo-root', join(dir, 'nope'), out).status, 2); assert.equal(cli('--repo-root', dir, out).status, 0); } finally { rmSync(dir, { recursive: true, force: true }); } }); // --- 4: the stamp covers the frontmatter the reviewer judges ----------------- const BRIEF = ['---', 'type: trekbrief', 'framing: preserve', 'research_topics: 2', '---', '', '## Intent', '', 'Why.', '', '## Goal', '', 'What.', ''].join('\n'); test('brief stamp: changing framing or research_topics after the review makes it stale', () => { const s = stampBriefReview(BRIEF).text; assert.equal(checkBriefReview(s.replace('framing: preserve', 'framing: replace')).state, 'stale'); assert.equal(checkBriefReview(s.replace('research_topics: 2', 'research_topics: 0')).state, 'stale'); assert.equal(checkBriefReview(stampIntentApproval(s).text).state, 'current', 'the intent stamp still keeps it current'); }); // --- 6: red_kind does not read an assertion message as a load error ---------- test('red proof: a named test whose assertion mentions SyntaxError is a test-failure', async () => { const { proveRed } = await import('../../lib/verification/red-proof.mjs'); const repo = makeRepo('main'); mkdirSync(join(repo, 'lib')); writeFileSync(join(repo, 'lib', 'p.mjs'), 'export function parse(s) { return s; }\n'); spawnSync('git', ['add', 'lib/p.mjs'], { cwd: repo }); spawnSync('git', ['commit', '-q', '-m', 'base'], { cwd: repo }); const base = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: repo, encoding: 'utf8' }).stdout.trim(); mkdirSync(join(repo, 'tests')); writeFileSync(join(repo, 'tests', 'p.test.mjs'), "import { test } from 'node:test';\nimport assert from 'node:assert';\nimport { parse } from '../lib/p.mjs';\ntest('throws on bad input', () => { assert.throws(() => parse('{'), SyntaxError); });\n"); writeFileSync(join(repo, 'lib', 'p.mjs'), 'export function parse(s) { return JSON.parse(s); }\n'); const r = proveRed({ cwd: repo, base, cmd: 'node --test tests/p.test.mjs', tests: ['tests/p.test.mjs'] }); assert.equal(r.status, 'proven', JSON.stringify(r).slice(0, 400)); assert.equal(r.red_kind, 'test-failure'); });