// tests/lib/engine-findings.test.mjs // Claude Code's /code-review and /security-review as the engine (ordre 5, P11): // their transcribed findings become a coordinator payload, and meet Voyage's // brief control in the same dedup + verdict. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { toReviewerPayload, ENGINES } from '../../lib/review/engine-findings.mjs'; import { runContract } from '../../lib/review/coordinator-contract.mjs'; import { validateFindings } from '../../lib/review/findings-schema.mjs'; test('engine-findings — a /code-review finding becomes a MAJOR CODE_REVIEW_DEFECT', () => { const r = toReviewerPayload({ engine: 'code-review', findings: [ { file: './lib/a.mjs', line: 12, summary: 'Off-by-one drops the last page', failure_scenario: '10 items, page size 5 → page 2 empty', category: 'correctness' }, ] }); assert.equal(r.ok, true, r.errors.join('\n')); assert.equal(r.payload.reviewer, ENGINES['code-review'].reviewer); const [f] = r.payload.findings; assert.deepEqual([f.rule_key, f.severity, f.file, f.line], ['CODE_REVIEW_DEFECT', 'MAJOR', 'lib/a.mjs', 12]); assert.match(f.detail, /page 2 empty/); assert.equal(validateFindings(r.payload).valid, true); }); test('engine-findings — a /security-review finding becomes a BLOCKER SECURITY_REVIEW_VULNERABILITY', () => { const r = toReviewerPayload({ engine: 'security-review', findings: [ { file: 'lib/q.mjs', line: 4, summary: 'SQL built from request body' }, ] }); assert.equal(r.ok, true); assert.equal(r.payload.findings[0].severity, 'BLOCKER'); }); test('engine-findings — a narrower catalogue key given by the orchestrator is kept; an unknown one falls back', () => { const r = toReviewerPayload({ engine: 'code-review', findings: [ { file: 'a.mjs', line: 1, summary: 's', rule_key: 'MISSING_TEST' }, { file: 'b.mjs', line: 1, summary: 's', rule_key: 'MADE_UP' }, ] }); assert.deepEqual(r.payload.findings.map((f) => f.rule_key), ['MISSING_TEST', 'CODE_REVIEW_DEFECT']); }); test('engine-findings — a file outside the review scope is listed, never dropped silently', () => { const r = toReviewerPayload({ engine: 'security-review', findings: [ { file: 'in.mjs', line: 1, summary: 'a' }, { file: 'old/out.mjs', line: 1, summary: 'b' }, ] }, { scopeFiles: ['in.mjs'] }); assert.equal(r.payload.findings.length, 1); assert.equal(r.out_of_scope.length, 1); assert.equal(r.out_of_scope[0].file, 'old/out.mjs'); }); test('engine-findings — empty file or summary is an input error, unknown engine too', () => { assert.equal(toReviewerPayload({ engine: 'code-review', findings: [{ file: '', summary: 'x' }] }).ok, false); assert.equal(toReviewerPayload({ engine: 'code-review', findings: [{ file: 'a', summary: '' }] }).ok, false); assert.equal(toReviewerPayload({ engine: 'simplify', findings: [] }).ok, false); }); test('engine-findings — engine and brief control meet in one verdict; engine + Voyage on the same triplet collapse', () => { const engine = toReviewerPayload({ engine: 'code-review', findings: [ { file: 'lib/a.mjs', line: 12, summary: 'No test covers the new branch', rule_key: 'MISSING_TEST' }, ] }).payload; const conformance = { reviewer: 'brief-conformance-reviewer', findings: [ { file: 'lib/a.mjs', line: 12, rule_key: 'MISSING_TEST', severity: 'MAJOR', title: 'SC2 untested' }, { file: 'lib/b.mjs', line: 3, rule_key: 'UNIMPLEMENTED_CRITERION', severity: 'BLOCKER', title: 'SC3 missing' }, ] }; const result = runContract([engine, conformance], { expectedReviewers: [engine.reviewer, 'brief-conformance-reviewer'] }); assert.equal(result.verdict, 'BLOCK', 'the brief control stops'); assert.equal(result.findings.length, 2); const a = result.findings.find((f) => f.file === 'lib/a.mjs'); assert.deepEqual([...a.raised_by].sort(), ['brief-conformance-reviewer', 'claude-code-review']); }); test('engine-findings — a "narrower" key can never lower a security BLOCKER', () => { const r = toReviewerPayload({ engine: 'security-review', findings: [ { file: 'a.mjs', line: 1, summary: 's', rule_key: 'MISSING_ERROR_HANDLING' }, // MINOR → refused { file: 'b.mjs', line: 1, summary: 's', rule_key: 'SECURITY_INJECTION' }, // BLOCKER → kept ] }); assert.deepEqual(r.payload.findings.map((f) => [f.rule_key, f.severity]), [['SECURITY_REVIEW_VULNERABILITY', 'BLOCKER'], ['SECURITY_INJECTION', 'BLOCKER']]); }); test('engine-findings — absolute paths under the repo root match the scope; a numeric-string line is kept', () => { const r = toReviewerPayload({ engine: 'code-review', findings: [ { file: '/abs/repo/lib/a.mjs', line: '12', summary: 's' }, ] }, { scopeFiles: ['lib/a.mjs'], repoRoot: '/abs/repo' }); assert.equal(r.payload.findings.length, 1); assert.equal(r.payload.findings[0].file, 'lib/a.mjs'); assert.equal(r.payload.findings[0].line, 12); }); test('engine-findings — an empty scope list is an error, never "everything out of scope"', () => { const r = toReviewerPayload({ engine: 'security-review', findings: [{ file: 'a.mjs', summary: 's' }] }, { scopeFiles: [] }); assert.equal(r.ok, false); }); test('engine-findings — an out-of-scope engine finding forbids ALLOW and is declined to judge', () => { const r = toReviewerPayload({ engine: 'security-review', findings: [{ file: 'old/x.mjs', line: 2, summary: 'leak' }] }, { scopeFiles: ['in.mjs'] }); const result = runContract( [r.payload, { reviewer: 'brief-conformance-reviewer', findings: [] }], { expectedReviewers: [r.payload.reviewer, 'brief-conformance-reviewer'], engineOutOfScope: r.out_of_scope }, ); assert.equal(result.verdict, 'WARN'); assert.deepEqual(result.declined_to_judge.map((d) => d.reason), ['engine:out-of-scope']); }); test('engine-findings — an engine that was expected but never reported forbids ALLOW', () => { const result = runContract([{ reviewer: 'brief-conformance-reviewer', findings: [] }], { expectedReviewers: ['brief-conformance-reviewer', ENGINES['security-review'].reviewer] }); assert.equal(result.verdict, 'WARN'); assert.deepEqual(result.missing_reviewers, ['claude-security-review']); });