voyage-lekerepo trial 2 (2026-10-08), report section A; the operator decided
they are fixed before the next release:
- A1: a /code-review finding whose category is simplification, efficiency,
style or readability enters as CODE_REVIEW_IMPROVEMENT (MINOR), not MAJOR.
- A2: recommended_action is the engine's own fix when transcribed, otherwise
a test-first step; never the failure scenario dressed as a remedy.
- A3: a /code-review finding outside the exact range it was given is declined
and listed, but no longer holds ALLOW back; /security-review's still does.
- A4: the manifest must_contain recipe uses grep -E -e ... -- so a pattern
like --sep is not read as an option.
- A5: indented sub-points belong to their criterion (counted), and a
criterion with sub-points and no command warns BRIEF_SC_SUBPOINTS.
- A6: a brief requiring bash 3.2 whose criterion runs plain bash warns
BRIEF_SC_BASH_NOT_PINNED; /trekbrief proposes /bin/bash.
- A7: the plan template's and /trekplan's checkpoints stage the step's files.
Tests fail on 5f9faee first (tests/lib/trial2-fixes.test.mjs, 11 of 14).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
188 lines
8.9 KiB
JavaScript
188 lines
8.9 KiB
JavaScript
// tests/lib/review-findings-kept.test.mjs
|
|
// The review keeps the findings that are right (voyage-lekerepo end-to-end
|
|
// trial, 2026-10-06, report points 1, 5 and 11):
|
|
// 1. every /code-review finding was set aside as `actionability:empty`,
|
|
// because the converter wrote `Resolve: <title>` — the title again;
|
|
// 5. the only brief-anchored MAJOR was lost: the reviewer cited the line in
|
|
// the diff patch (194) for a 124-line file, and the schema let it through;
|
|
// 11. the coordinator has no shell, so it cannot compute finding ids; the
|
|
// orchestrator filled `@@ID_1@@` placeholders by hand, undocumented, and
|
|
// the coordinator's doc named a 4-field id the code never computes.
|
|
|
|
import { test } from 'node:test';
|
|
import { strict as assert } from 'node:assert';
|
|
import { mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { toReviewerPayload } from '../../lib/review/engine-findings.mjs';
|
|
import { validateFindings, validateReviewerOutput } from '../../lib/review/findings-schema.mjs';
|
|
import { computeFindingId } from '../../lib/parsers/finding-id.mjs';
|
|
import { fillFindingIds } from '../../lib/review/fill-finding-ids.mjs';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = join(HERE, '..', '..');
|
|
const read = (p) => readFileSync(join(ROOT, p), 'utf8');
|
|
|
|
// --- 1. an engine finding carries an action, not its title again -------------
|
|
|
|
test('engine finding: recommended_action is an imperative built from the failure scenario, never the title again', () => {
|
|
const r = toReviewerPayload({
|
|
engine: 'code-review',
|
|
findings: [{ file: 'bin/slug.mjs', line: 12, summary: 'trailing --max is read as text', failure_scenario: '`slug "x" --max` prints `x---max` instead of exiting 2' }],
|
|
});
|
|
assert.equal(r.ok, true, r.errors?.join('\n'));
|
|
const f = r.payload.findings[0];
|
|
assert.doesNotMatch(f.recommended_action, /^Resolve:/);
|
|
assert.notEqual(f.recommended_action.replace(/^\w+\s*/, ''), f.title);
|
|
// Trial 2 (2026-10-08): the action is a test-first step, not the scenario
|
|
// dressed as a remedy (tests/lib/trial2-fixes.test.mjs, A2).
|
|
assert.match(f.recommended_action, /^Write a test /);
|
|
assert.match(f.recommended_action, /bin\/slug\.mjs:12/);
|
|
assert.match(f.recommended_action, /prints `x---max`/);
|
|
});
|
|
|
|
test('engine finding without a failure scenario still gets an action that is not the title', () => {
|
|
const r = toReviewerPayload({ engine: 'security-review', findings: [{ file: 'a.mjs', line: 0, summary: 'token logged' }] });
|
|
const f = r.payload.findings[0];
|
|
assert.match(f.recommended_action, /^Write a test /);
|
|
assert.notEqual(f.recommended_action, `Resolve: ${f.title}`);
|
|
});
|
|
|
|
test('coordinator: Actionability does not set an engine finding aside', () => {
|
|
const doc = read('agents/review-coordinator.md');
|
|
const row = doc.split('\n').find((l) => l.startsWith('| Actionability |'));
|
|
assert.ok(row, 'Actionability row');
|
|
assert.match(doc, /carries `engine`[^\n]*(?:\n[^\n]*){0,4}never set aside for Actionability/);
|
|
});
|
|
|
|
// --- 5. a line number is the file's, not the patch's --------------------------
|
|
|
|
function repo() {
|
|
const dir = mkdtempSync(join(tmpdir(), 'voyage-lines-'));
|
|
mkdirSync(join(dir, 'tests'));
|
|
writeFileSync(join(dir, 'tests', 'cli.test.mjs'), Array.from({ length: 124 }, (_, i) => `// ${i + 1}`).join('\n') + '\n');
|
|
return dir;
|
|
}
|
|
|
|
const finding = (line, file = 'tests/cli.test.mjs') => ({
|
|
reviewer: 'brief-conformance-reviewer',
|
|
findings: [{ id: 'x', severity: 'MAJOR', rule_key: 'MISSING_TEST', file, line, title: 't', detail: 'd', recommended_action: 'Add a test' }],
|
|
});
|
|
|
|
test('schema with a repo root: a line past the end of the file is an error the reviewer is re-asked about', () => {
|
|
const dir = repo();
|
|
try {
|
|
const r = validateFindings(finding(194), { repoRoot: dir });
|
|
assert.equal(r.valid, false);
|
|
const e = r.errors.find((x) => x.code === 'FINDING_LINE_OUT_OF_RANGE');
|
|
assert.ok(e, JSON.stringify(r.errors));
|
|
assert.match(e.message, /194/);
|
|
assert.match(e.message, /124/);
|
|
assert.match(e.hint, /not the line in the diff/);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('schema with a repo root: the last line, line 0 and a file absent from the tree are not range errors', () => {
|
|
const dir = repo();
|
|
try {
|
|
assert.equal(validateFindings(finding(124), { repoRoot: dir }).valid, true);
|
|
assert.equal(validateFindings(finding(0), { repoRoot: dir }).valid, true);
|
|
assert.equal(validateFindings(finding(500, 'src/deleted.mjs'), { repoRoot: dir }).valid, true);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('schema without a repo root behaves as before', () => {
|
|
assert.equal(validateFindings(finding(194)).valid, true);
|
|
});
|
|
|
|
test('schema CLI: --repo-root reaches the range check', () => {
|
|
const dir = repo();
|
|
try {
|
|
const out = join(dir, 'reviewer.md');
|
|
writeFileSync(out, '```json\n' + JSON.stringify(finding(194)) + '\n```\n');
|
|
const r = spawnSync(process.execPath, [join(ROOT, 'lib/review/findings-schema.mjs'), '--json', '--repo-root', dir, out], { encoding: 'utf8' });
|
|
assert.equal(r.status, 1, r.stdout + r.stderr);
|
|
assert.match(r.stdout, /FINDING_LINE_OUT_OF_RANGE/);
|
|
assert.equal(validateReviewerOutput(readFileSync(out, 'utf8')).valid, true);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('reviewers are told to cite the file\'s own line, never the patch\'s', () => {
|
|
for (const a of ['agents/brief-conformance-reviewer.md', 'agents/code-correctness-reviewer.md']) {
|
|
assert.match(read(a), /never the line number in the diff/, a);
|
|
}
|
|
assert.match(read('commands/trekreview.md'), /findings-schema\.mjs --json --repo-root/);
|
|
});
|
|
|
|
// --- 11. ids are computed by code, not by the coordinator ---------------------
|
|
|
|
function review(findings, extra = '') {
|
|
const ids = findings.map((_, i) => ` - @@ID_${i + 1}@@`).join('\n');
|
|
const sections = findings.map((f, i) => `### @@ID_${i + 1}@@\n- file: ${f.file}\n- line: ${f.line}\n- rule_key: ${f.rule_key}\n`).join('\n');
|
|
const json = { verdict: 'WARN', findings: findings.map((f, i) => ({ id: `@@ID_${i + 1}@@`, ...f })) };
|
|
return `---\ntype: trekreview\nverdict: WARN\nfindings:\n${ids}\n---\n\n## Findings (MAJOR)\n\n${sections}\n${extra}\n\`\`\`json\n${JSON.stringify(json, null, 2)}\n\`\`\`\n`;
|
|
}
|
|
|
|
const F1 = { file: 'tests/cli.test.mjs', line: 40, rule_key: 'MISSING_TEST', severity: 'MAJOR' };
|
|
const F2 = { file: 'bin/slug.mjs', line: 12, rule_key: 'CODE_REVIEW_DEFECT', severity: 'MAJOR' };
|
|
|
|
test('fill-finding-ids: every @@ID_n@@ becomes the id computed over (file, line, rule_key) of JSON finding n', () => {
|
|
const r = fillFindingIds(review([F1, F2]));
|
|
assert.equal(r.ok, true, r.errors.join('\n'));
|
|
const id1 = computeFindingId(F1.file, F1.line, F1.rule_key);
|
|
const id2 = computeFindingId(F2.file, F2.line, F2.rule_key);
|
|
assert.doesNotMatch(r.text, /@@ID_/);
|
|
assert.match(r.text, new RegExp(`findings:\\n - ${id1}\\n - ${id2}\\n`));
|
|
assert.match(r.text, new RegExp(`### ${id2}`));
|
|
assert.match(r.text, new RegExp(`"id": "${id1}"`));
|
|
});
|
|
|
|
test('fill-finding-ids: a placeholder with no JSON finding behind it is an error, not a guess', () => {
|
|
const r = fillFindingIds(review([F1], 'See also @@ID_7@@.'));
|
|
assert.equal(r.ok, false);
|
|
assert.match(r.errors.join('\n'), /@@ID_7@@/);
|
|
});
|
|
|
|
test('fill-finding-ids: a 40-hex id that is not the computed one is an error', () => {
|
|
const text = review([F1]).replaceAll('@@ID_1@@', 'a'.repeat(40));
|
|
const r = fillFindingIds(text);
|
|
assert.equal(r.ok, false);
|
|
assert.match(r.errors.join('\n'), /does not match/);
|
|
});
|
|
|
|
test('fill-finding-ids: an already-correct review passes unchanged', () => {
|
|
const once = fillFindingIds(review([F1, F2]));
|
|
const twice = fillFindingIds(once.text);
|
|
assert.equal(twice.ok, true, twice.errors.join('\n'));
|
|
assert.equal(twice.text, once.text);
|
|
});
|
|
|
|
test('fill-finding-ids CLI rewrites the file in place and exits 0; 1 on an unresolved placeholder', () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'voyage-ids-'));
|
|
try {
|
|
const p = join(dir, 'review.md');
|
|
writeFileSync(p, review([F1]));
|
|
const cli = join(ROOT, 'lib/review/fill-finding-ids.mjs');
|
|
assert.equal(spawnSync(process.execPath, [cli, p]).status, 0);
|
|
assert.doesNotMatch(readFileSync(p, 'utf8'), /@@ID_/);
|
|
writeFileSync(p, review([F1], '@@ID_3@@'));
|
|
assert.equal(spawnSync(process.execPath, [cli, p]).status, 1);
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('the coordinator writes @@ID_n@@ placeholders and the doc names the three-field id the code computes', () => {
|
|
const doc = read('agents/review-coordinator.md');
|
|
assert.doesNotMatch(doc, /\(file, line, rule_key, title\)/);
|
|
assert.match(doc, /@@ID_<n>@@/);
|
|
assert.match(read('commands/trekreview.md'), /fill-finding-ids\.mjs/);
|
|
});
|