The minor points (B8-B12, C13, C14, C16, C17) of the second voyage-lekerepo trial (2026-10-08), each verified against the code and started with a test that failed first. - K10: optional step field `Regression:` - one command the criteria runner allows, unpiped; plan-validator refuses one that can never run (PLAN_STEP_REGRESSION_NOT_RUNNABLE). Any plan_version, no bump. - K11: `On failure:` opens with revert | retry | skip | escalate, or the validator warns (PLAN_STEP_ON_FAILURE_UNKNOWN); onFailureAction in plan-schema. - B8 (operator decision 2026-10-08): minors open after the last critic round are listed numbered in the Phase 10 summary; the ones the operator picks are fixed and get one more round (--extra-round when the 3 are spent). - B11: plan-review dedup merges two reviewers' findings on the same file:line with the same content (trial pair: Jaccard 0.44, overlap 0.83); the Jaccard threshold for every other pair is unchanged. - B12: /trekbrief Step 4c says the reviewer runs in the background and is waited for, not "foreground, blocking". - C13: the Phase 4 size count includes .mjs, .cjs, .mts, .cts. - C14: --evidence shows the first output line on PASS rows. - C16: findings-schema checks path:line citations in finding prose (error) and in the prose around the block (warning) against the file. - C17: git-historian gets the brief, never proposes changing a frozen file, and names the remote from `git remote -v`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
6.5 KiB
JavaScript
130 lines
6.5 KiB
JavaScript
// tests/lib/trial2-minor-prose-pins.test.mjs
|
|
// Prose pins for the minor points of the second voyage-lekerepo trial
|
|
// (2026-10-08, report parts B and C) whose fix is text a session reads.
|
|
// Counted as doc pins by lib/util/test-census.mjs (the file name).
|
|
// B8 minors left after the last critic round have a way to be fixed
|
|
// (operator decision 2026-10-08: listed in the summary, the operator picks).
|
|
// B9 K10 `Regression:` is described where plans are written and reviewed.
|
|
// B10 K11 the four `On failure:` words are named where plans are written.
|
|
// B12 /trekbrief Step 4c no longer claims a foreground, blocking spawn.
|
|
// C13 the codebase-size count in /trekplan Phase 4 counts .mjs/.cjs/.mts/.cts.
|
|
// C17 git-historian leaves the brief's frozen files alone and names the
|
|
// remote from `git remote -v`.
|
|
|
|
import { test } from 'node:test';
|
|
import { strict as assert } from 'node:assert';
|
|
import { readFileSync, mkdtempSync, writeFileSync, rmSync } 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 { findSection } from '../../lib/parsers/plan-schema.mjs';
|
|
|
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
const read = (p) => readFileSync(join(ROOT, p), 'utf8');
|
|
|
|
/** The text of a level-2 section, up to the next `## ` heading outside a fence. */
|
|
function section(text, heading) {
|
|
const s = findSection(text, heading.replace(/^## /, ''), 2);
|
|
assert.ok(s, `missing ${heading}`);
|
|
return s.text;
|
|
}
|
|
|
|
// --- B8 ------------------------------------------------------------------------
|
|
|
|
test('B8: a PASS with minors sends them to the Phase 10 summary, numbered, for the operator to pick', () => {
|
|
const plan = read('commands/trekplan.md');
|
|
const p9 = section(plan, '## Phase 9 — Adversarial review (bounded critic loop)');
|
|
assert.match(p9, /\*\*PASS\*\*[\s\S]{0,600}minors[\s\S]{0,300}Phase 10/i);
|
|
const p10 = section(plan, '## Phase 10 — Present and refine');
|
|
assert.match(p10, /\*\*Open minors:\*\*/);
|
|
assert.match(p10, /numbered/i);
|
|
});
|
|
|
|
test('B8: fixing picked minors after the last round runs one more round with --extra-round, the pick being the yes', () => {
|
|
const p10 = section(read('commands/trekplan.md'), '## Phase 10 — Present and refine');
|
|
assert.match(p10, /--extra-round/);
|
|
assert.match(p10, /pick(ed|s)?\s+(is|counts as)\s+(the|their)\s+yes/i);
|
|
});
|
|
|
|
// --- B9 / B10 --------------------------------------------------------------------
|
|
|
|
test('B9: /trekplan Phase 8 names `Regression:` as an allowed optional field and says what it is for', () => {
|
|
const p8 = section(read('commands/trekplan.md'), '## Phase 8 — Deep planning');
|
|
assert.match(p8, /`Regression:`/);
|
|
assert.match(p8, /PLAN_STEP_REGRESSION_NOT_RUNNABLE/);
|
|
assert.match(p8, /\*\*Regression:\*\* `npm test`/, 'the canonical example shows the field');
|
|
assert.match(p8, /before (the|its) checkpoint/i);
|
|
});
|
|
|
|
test('B9: the plan template shows `Regression:` as optional', () => {
|
|
const t = read('templates/plan-template.md');
|
|
assert.match(t, /- \*\*Regression:\*\* `[^\n]*\*\(optional\)\*/);
|
|
assert.match(t, /`Regression:` \(ONE backticked command/);
|
|
});
|
|
|
|
test('B9: plan-critic knows the field and does not demand the suite in `Verify:`', () => {
|
|
const c = read('agents/plan-critic.md');
|
|
assert.match(c, /`Regression:`/);
|
|
assert.match(c, /(never|do\s+not)\s+(demand|require)\s+`npm test`\s+in\s+`Verify:`/i);
|
|
});
|
|
|
|
test('B9/B10: Handover 4 documents `Regression:` (K10) and the `On failure:` words (K11)', () => {
|
|
const h4 = section(read('docs/HANDOVER-CONTRACTS.md'), '## Handover 4 — `plan.md` → execute');
|
|
for (const code of ['PLAN_STEP_REGRESSION_NOT_RUNNABLE', 'PLAN_STEP_ON_FAILURE_UNKNOWN']) assert.match(h4, new RegExp(code));
|
|
assert.match(h4, /K10/);
|
|
assert.match(h4, /K11/);
|
|
assert.match(h4, /revert[^\n]*retry[^\n]*skip[^\n]*escalate/);
|
|
});
|
|
|
|
test('B10: /trekplan Phase 8 says `On failure:` opens with one of the four words', () => {
|
|
const p8 = section(read('commands/trekplan.md'), '## Phase 8 — Deep planning');
|
|
assert.match(p8, /On failure:\*\*[^\n]*(opens|starts) with/i);
|
|
assert.match(p8, /PLAN_STEP_ON_FAILURE_UNKNOWN/);
|
|
});
|
|
|
|
// --- B12 -------------------------------------------------------------------------
|
|
|
|
test('B12: /trekbrief Step 4c does not call the brief-reviewer spawn foreground or blocking, and waits for it', () => {
|
|
const t = read('commands/trekbrief.md');
|
|
const start = t.indexOf('**Step 4c — Launch brief-reviewer**');
|
|
const end = t.indexOf('**Step 4d — Parse JSON scores**');
|
|
assert.ok(start !== -1 && end > start);
|
|
const step = t.slice(start, end);
|
|
assert.doesNotMatch(step, /foreground, blocking/);
|
|
assert.match(step, /wait\s+for\s+(its|the agent's)\s+(result|completion)/i);
|
|
assert.match(step, /(background|asynchronous)/i, 'says how the spawn actually runs');
|
|
});
|
|
|
|
// --- C13 -------------------------------------------------------------------------
|
|
|
|
test('C13: the Phase 4 size count counts .mjs, .cjs, .mts and .cts files', () => {
|
|
const p4 = section(read('commands/trekplan.md'), '## Phase 4 — Codebase sizing');
|
|
const cmd = p4.match(/```\n(find [^\n]+)\n```/);
|
|
assert.ok(cmd, 'the find command stands in a fence of its own');
|
|
const dir = mkdtempSync(join(tmpdir(), 'voyage-sizing-'));
|
|
try {
|
|
for (const f of ['a.mjs', 'b.cjs', 'c.mts', 'd.cts', 'e.ts', 'skip.txt']) writeFileSync(join(dir, f), 'x\n');
|
|
const r = spawnSync('/bin/sh', ['-c', cmd[1]], { cwd: dir, encoding: 'utf8' });
|
|
assert.equal(r.status, 0, r.stderr);
|
|
assert.equal(r.stdout.trim(), '5');
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
// --- C17 -------------------------------------------------------------------------
|
|
|
|
test('C17: git-historian reads the brief, never proposes changing a file it freezes', () => {
|
|
const g = read('agents/git-historian.md');
|
|
assert.match(g, /brief/i);
|
|
assert.match(g, /(never|do not) (propose|suggest|recommend)[^\n]*(change|edit)[^\n]*(frozen|unchanged)|(frozen|unchanged)[^\n]*(never|do not) (propose|suggest|recommend)/i);
|
|
const p5 = section(read('commands/trekplan.md'), '## Phase 5 — Parallel exploration (specialized agents + research)');
|
|
assert.match(p5, /\*\*git-historian\*\*[\s\S]{0,400}\{brief_path\}/, 'the spawn hands it the brief');
|
|
});
|
|
|
|
test('C17: git-historian names the remote from `git remote -v`, never by guess', () => {
|
|
const g = read('agents/git-historian.md');
|
|
assert.match(g, /`git remote -v`/);
|
|
assert.match(g, /local (path|repository|bare)/i);
|
|
});
|