voyage/tests/validators/brief-review-stamp.test.mjs
Kjell Tore Guttormsen 1b5218e8f4
fix(plan): every exploration agent runs; the brief is reviewed again only when it changed
voyage-lekerepo trial (2026-10-06) point 14, operator decision 2026-10-08
(«alle agenter, ny briefsjekk bare ved endring»):

- trekplan: the "Adaptive" hard rule (a 10-file project does not need 7
  agents) contradicted Phase 5 ("All agents run for all codebase sizes");
  it is gone, in line with quality before cost.
- brief-review-stamp.mjs: /trekbrief stamps the brief body when its
  brief-reviewer gate passes; /trekplan Phase 4b skips its own review only
  while the stamp is current. Stale (edited) or absent (another producer,
  or a gate that did not pass) reviews as before. Additive optional
  frontmatter fields, documented in Handover 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 07:01:44 +02:00

92 lines
4.3 KiB
JavaScript

// tests/validators/brief-review-stamp.test.mjs
// Operator decision 2026-10-08 (voyage-lekerepo trial point 14): /trekplan
// Phase 4b re-ran brief-reviewer right after /trekbrief had run it on the same
// text. Now /trekbrief stamps the body it reviewed (on a passed gate), and
// /trekplan reviews again only when the stamp is absent or the body changed —
// a brief from any other producer has no stamp and is always reviewed.
// Same decision: every exploration agent runs at every codebase size; the
// contradicting "Adaptive" rule is gone.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { spawnSync } from 'node:child_process';
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { stampBriefReview, checkBriefReview } from '../../lib/validators/brief-review-stamp.mjs';
import { stampIntentApproval } from '../../lib/validators/intent-approval.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..');
const CLI = join(ROOT, 'lib', 'validators', 'brief-review-stamp.mjs');
const BRIEF = [
'---', 'type: trekbrief', 'brief_version: "2.2"', 'task: t', 'slug: s', '---', '',
'## TL;DR', '', 'A toy.', '', '## Intent', '', 'Why it matters.', '', '## Goal', '', 'What.', '',
'## Success Criteria', '', '- `npm test` exits 0.', '',
].join('\n');
test('stamp, then check: current', () => {
const s = stampBriefReview(BRIEF);
assert.equal(s.stamped, true);
assert.match(s.text, /^brief_reviewed_hash: "sha256:[0-9a-f]{64}"$/m);
assert.equal(checkBriefReview(s.text).state, 'current');
});
test('no stamp: absent (another producer\'s brief is always reviewed)', () => {
assert.equal(checkBriefReview(BRIEF).state, 'absent');
});
test('a changed body after the stamp: stale', () => {
const s = stampBriefReview(BRIEF).text;
assert.equal(checkBriefReview(s.replace('Why it matters.', 'Why it matters a lot.')).state, 'stale');
assert.equal(checkBriefReview(s + '\n## Non-Goals\n\n- none\n').state, 'stale');
});
test('frontmatter changes (the intent approval stamp) and CRLF keep the review current', () => {
const s = stampBriefReview(BRIEF).text;
const approved = stampIntentApproval(s).text;
assert.notEqual(approved, s);
assert.equal(checkBriefReview(approved).state, 'current');
assert.equal(checkBriefReview(s.replace(/\n/g, '\r\n')).state, 'current');
});
test('re-stamping replaces the old stamp, never adds a second', () => {
const twice = stampBriefReview(stampBriefReview(BRIEF).text).text;
assert.equal(twice.match(/^brief_reviewed_hash:/gm).length, 1);
});
test('CLI: --stamp writes the file, --check exits 0 current / 1 stale or absent / 2 usage', () => {
const dir = mkdtempSync(join(tmpdir(), 'voyage-brief-stamp-'));
try {
const p = join(dir, 'brief.md');
writeFileSync(p, BRIEF);
const run = (...a) => spawnSync(process.execPath, [CLI, ...a], { encoding: 'utf8' });
assert.equal(run('--check', p).status, 1);
assert.equal(run('--stamp', p).status, 0);
assert.equal(run('--check', p).status, 0);
writeFileSync(p, readFileSync(p, 'utf8').replace('What.', 'What else.'));
const c = run('--check', '--json', p);
assert.equal(c.status, 1);
assert.equal(JSON.parse(c.stdout).state, 'stale');
assert.equal(run('--check').status, 2);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('prose: /trekbrief stamps after a passed gate; /trekplan Phase 4b checks before it reviews again', () => {
const brief = readFileSync(join(ROOT, 'commands', 'trekbrief.md'), 'utf8');
const plan = readFileSync(join(ROOT, 'commands', 'trekplan.md'), 'utf8');
assert.match(brief, /brief-review-stamp\.mjs --stamp/);
const p4b = plan.slice(plan.indexOf('## Phase 4b'), plan.indexOf('## Phase 5'));
assert.match(p4b, /brief-review-stamp\.mjs --check/);
});
test('prose: every exploration agent runs at every codebase size; no "Adaptive" rule contradicts it', () => {
const plan = readFileSync(join(ROOT, 'commands', 'trekplan.md'), 'utf8');
assert.match(plan, /All agents run for all codebase sizes/);
assert.doesNotMatch(plan, /\*\*Adaptive\*\*/);
assert.doesNotMatch(plan, /does not need 7 exploration agents/);
});