// 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); });