refactor(cleanup): simplify pass on the R-a diff

engine-compare imports the coordinator's title limit and trim instead
of a copy whose reason went with Arm B; the autonomy gate exports one
parser and its test runs in-process with two CLI runs; one import
scanner in lib-imports; the fenced-section walker shared from
tests/helpers/md-section.mjs instead of six copies; plan-gate hash
in-process; living-paths scans once; routing drops the orchestrator
filter whose files are gone; small leftovers removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-11-07 13:31:30 +01:00
commit efe981f7bc
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
18 changed files with 161 additions and 358 deletions

View file

@ -5,110 +5,58 @@
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { execFileSync, spawnSync } from 'node:child_process';
import { spawnSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { findSection } from '../../lib/parsers/plan-schema.mjs';
import { fileURLToPath } from 'node:url';
import { parseGatesArgs } from '../../lib/util/autonomy-gate.mjs';
import { sectionText } from '../helpers/md-section.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..');
const SHIM = join(ROOT, 'lib', 'util', 'autonomy-gate.mjs');
const GATES_USAGE = 'usage: --gates takes true, false, closed or open';
const USAGE = 'usage: autonomy-gate.mjs [--gates [true|false|closed|open]]';
function runShim(args) {
try {
const out = execFileSync(process.execPath, [SHIM, ...args], {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'pipe'],
});
return { code: 0, out };
} catch (e) {
return { code: e.status ?? 1, out: e.stdout?.toString() ?? '' };
}
}
// The exit code is asserted before any JSON.parse, so a crash or a usage
// error never reads as a wrong value.
function shimGates(args) {
const r = runShim(args);
assert.equal(r.code, 0, `expected exit 0 for ${JSON.stringify(args)}, got ${r.code}: ${r.out}`);
const parsed = JSON.parse(r.out.trim());
assert.deepEqual(parsed, { ok: true, gates: parsed.gates });
assert.equal(typeof parsed.gates, 'boolean', `gates must be a boolean: ${r.out}`);
return parsed.gates;
}
function shimUsage(args) {
const r = runShim(args);
assert.equal(r.code, 1, `expected exit 1 for ${JSON.stringify(args)}, got ${r.code}: ${r.out}`);
const parsed = JSON.parse(r.out.trim());
assert.equal(parsed.ok, false);
assert.equal(Object.keys(parsed).sort().join(','), 'error,ok');
return parsed.error;
}
const read = (rel) => readFileSync(join(ROOT, rel), 'utf8');
// ---- the exported parser ----------------------------------------------------
test('gatesValue maps true|closed to on, false|open to off, anything else to undefined', async () => {
const mod = await import(pathToFileURL(SHIM).href);
assert.equal(typeof mod.gatesValue, 'function', 'autonomy-gate.mjs must export gatesValue');
for (const v of ['true', 'closed']) assert.equal(mod.gatesValue(v), true, v);
for (const v of ['false', 'open']) assert.equal(mod.gatesValue(v), false, v);
for (const v of ['TRUE', 'yes', 'adaptive', '', undefined]) assert.equal(mod.gatesValue(v), undefined, String(v));
test('parseGatesArgs: bare --gates and true|closed are on, false|open and no flag are off, in both forms', () => {
const on = (argv) => assert.deepEqual(parseGatesArgs(argv), { ok: true, gates: true }, JSON.stringify(argv));
const off = (argv) => assert.deepEqual(parseGatesArgs(argv), { ok: true, gates: false }, JSON.stringify(argv));
on(['--gates']);
off([]);
for (const v of ['true', 'closed']) { on(['--gates', v]); on([`--gates=${v}`]); }
for (const v of ['false', 'open']) { off(['--gates', v]); off([`--gates=${v}`]); }
});
test('the module is the flag parser only: no state machine is exported', async () => {
const mod = await import(pathToFileURL(SHIM).href);
for (const gone of ['STATES', 'EVENTS', 'transition', 'isTerminal']) {
assert.equal(mod[gone], undefined, `autonomy-gate.mjs still exports ${gone}`);
test('parseGatesArgs: any other value is the exact --gates usage error', () => {
for (const bad of [['--gates', 'TRUE'], ['--gates', 'yes'], ['--gates', 'adaptive'], ['--gates=maybe'], ['--gates=']]) {
assert.deepEqual(parseGatesArgs(bad), { ok: false, error: GATES_USAGE }, bad.join(' '));
}
});
test('parseGatesArgs: any other argument (the old --state/--event too) is the exact usage error', () => {
assert.deepEqual(parseGatesArgs(['--gates', '--state', 'idle']), { ok: false, error: USAGE });
});
// ---- the CLI ----------------------------------------------------------------
test('CLI: bare --gates → {"ok":true,"gates":true}, exit 0', () => {
assert.equal(shimGates(['--gates']), true);
const runShim = (args) => spawnSync(process.execPath, [SHIM, ...args], { encoding: 'utf8' });
test('CLI: --gates closed prints {"ok":true,"gates":true}, exit 0', () => {
const r = runShim(['--gates', 'closed']);
assert.equal(r.status, 0, r.stdout + r.stderr);
assert.equal(r.stdout, '{"ok":true,"gates":true}\n');
});
test('CLI: no flag → {"ok":true,"gates":false}, exit 0', () => {
assert.equal(shimGates([]), false);
test('CLI: a bad value prints the usage error as JSON, exit 1', () => {
const r = runShim(['--gates', 'maybe']);
assert.equal(r.status, 1, r.stdout + r.stderr);
assert.equal(r.stdout, `${JSON.stringify({ ok: false, error: GATES_USAGE })}\n`);
});
test('CLI: --gates true and --gates closed → gates true', () => {
assert.equal(shimGates(['--gates', 'true']), true);
assert.equal(shimGates(['--gates', 'closed']), true);
});
test('CLI: --gates false and --gates open → gates false', () => {
assert.equal(shimGates(['--gates', 'false']), false);
assert.equal(shimGates(['--gates', 'open']), false);
});
test('CLI: the one-token --gates=<value> form is read as --gates <value>', () => {
assert.equal(shimGates(['--gates=true']), true);
assert.equal(shimGates(['--gates=closed']), true);
assert.equal(shimGates(['--gates=false']), false);
assert.equal(shimGates(['--gates=open']), false);
});
for (const bad of [['--gates', 'TRUE'], ['--gates', 'yes'], ['--gates', 'adaptive'], ['--gates=maybe'], ['--gates=']]) {
test(`CLI: ${bad.join(' ')} → the exact usage error, exit 1`, () => {
assert.equal(shimUsage(bad), GATES_USAGE);
});
}
for (const old of [
['--state', 'idle', '--event', 'start', '--gates', 'true'],
['--state', 'idle', '--event', 'start'],
['--gates', '--state', 'idle'],
['--event', 'start'],
]) {
test(`CLI: the old state-machine arguments are a usage error (${old.join(' ')}), exit 1`, () => {
assert.match(shimUsage(old), /^usage:/);
});
}
// ---- the four commands' --gates rule and CLI line ----------------------------
// Each of the four commands holds one rule: the next token is the flag's value
// only when it is true, false, closed or open; the CLI maps it, run once; a
@ -124,14 +72,12 @@ const GATES_RULE = [
'On exit 1 or no JSON (a broken shim; the four values cannot cause it), print the error in one line and map in prose instead: bare, `true`, `closed` → true; `false`, `open` → false. The run goes on either way.',
];
const commandText = (cmd) => readFileSync(join(ROOT, 'commands', `${cmd}.md`), 'utf8');
/** Whitespace (line breaks too) collapsed to one space. */
const flat = (s) => s.replace(/\s+/g, ' ').trim();
/** The command's one CLI line, `[value]` filled in with `value` (left out when ''). */
function gatesLine(cmd, value) {
const hits = commandText(cmd).split('\n')
const hits = read(`commands/${cmd}.md`).split('\n')
.filter((l) => /^\s*node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/util\/autonomy-gate\.mjs\b/.test(l));
assert.equal(hits.length, 1, `${cmd}: expected exactly one autonomy-gate.mjs line; found ${hits.length}`);
const l = hits[0].trim();
@ -155,7 +101,7 @@ function lineGates(cmdLine) {
for (const cmd of GATES_COMMANDS) {
test(`[#1] commands/${cmd}.md holds the shared --gates rule and a CLI line that maps closed to gates true`, () => {
const text = commandText(cmd);
const text = read(`commands/${cmd}.md`);
const f = flat(text);
for (const s of GATES_RULE) assert.ok(f.includes(s), `${cmd}: missing the shared --gates sentence: ${s}`);
assert.doesNotMatch(text, /--state \w+ --event \w+/, `${cmd}: the state-machine line is still there`);
@ -178,27 +124,8 @@ test('every command\'s CLI line maps a bare --gates to true and open to false',
// flag and pauses on nothing; the docs give the four values and no strictness
// policy (fix round 4-5a, #1).
const read = (rel) => readFileSync(join(ROOT, rel), 'utf8');
const EXEC = 'commands/trekexecute.md';
/**
* The text of a heading's section, up to the next heading of the same or a
* higher level outside a fence. The heading must exist.
*/
function section(text, heading) {
const level = heading.match(/^#+/)[0].length;
const s = findSection(text, heading.replace(/^#+ /, ''), level);
assert.ok(s, `missing ${heading}`);
const lines = s.text.split('\n');
let fenced = false;
for (let i = 1; i < lines.length; i++) {
if (/^\s{0,3}(`{3,}|~{3,})/.test(lines[i])) fenced = !fenced;
const m = !fenced && lines[i].match(/^(#+) /);
if (m && m[1].length < level) return lines.slice(0, i).join('\n');
}
return s.text;
}
const GATES_ONE_PAUSE = '`gates_mode = true` adds one pause, before the auto chain starts (Phase 5); the chain does not pass '
+ '`--gates` on to `/trekresearch` or `/trekplan`. Default `gates_mode = false` skips that pause.';
@ -210,14 +137,14 @@ test('[#1] sibling: commands/trekbrief.md promises no execute pause and no pause
});
test('[#1] sibling: /trekbrief Phase 1 says --gates adds one pause, before the auto chain', () => {
const p1 = flat(section(read('commands/trekbrief.md'), '## Phase 1 — Parse mode and validate input'));
const p1 = flat(sectionText(read('commands/trekbrief.md'), '## Phase 1 — Parse mode and validate input'));
assert.ok(p1.includes(GATES_ONE_PAUSE), 'Phase 1 does not say --gates adds one pause before the auto chain');
assert.ok(!p1.includes('every phase boundary in the downstream pipeline'), 'Phase 1 still promises a pause at every downstream phase boundary');
assert.ok(!p1.includes('runs continuously until the main-merge gate'), 'Phase 1 still says the auto chain runs until the main-merge gate');
});
test('[#1] sibling: /trekbrief Phase 5 says the auto chain ends at the plan, never at the main-merge gate', () => {
const p5 = flat(section(read('commands/trekbrief.md'), '## Phase 5 — Auto-orchestration opt-in (if research_topics > 0)'));
const p5 = flat(sectionText(read('commands/trekbrief.md'), '## Phase 5 — Auto-orchestration opt-in (if research_topics > 0)'));
assert.ok(p5.includes('The chain ends when `/trekplan` has written the plan (Step 6e); it never starts `/trekexecute`.'),
'Phase 5 does not say the chain ends when /trekplan has written the plan');
assert.ok(!p5.includes('The chain stops only at the main-merge gate'), 'Phase 5 still says the chain stops only at the main-merge gate');
@ -263,7 +190,7 @@ test('[#1] sibling: docs/command-modes.md gives every --gates row the four value
test('[#1] sibling: README.md gives the four values, and no strictness table', () => {
const text = read('README.md');
const sec = flat(section(text, '### Autonomy mode (`--gates`, v3.4.0)'));
const sec = flat(sectionText(text, '### Autonomy mode (`--gates`, v3.4.0)'));
assert.doesNotMatch(text, /\{open\|closed\|adaptive\}/);
assert.doesNotMatch(sec, /\| Value \| Behavior \|/, 'the strictness table is still there');
assert.doesNotMatch(sec, /`adaptive` \(default\)/);

View file

@ -288,22 +288,20 @@ test('Phase 9 prose mandates parallel single-message dispatch + inline dedup', (
// The read-only reviewers (plan-critic, scope-guardian: Read/Glob/Grep, no Write)
// cannot write the temp files the old prose told them to. Pin the A′ fix so the
// broken /tmp-write instruction cannot creep back:
// 1. Phase-9 prose in BOTH docs must use --stdin, not the dead /tmp/*-out.json
// file path the reviewers can't produce.
// 1. The Phase-9 prose of commands/trekplan.md must use --stdin, not the dead
// /tmp/*-out.json file path the reviewers can't produce.
// 2. BOTH reviewer agents must declare a machine-readable json findings block
// with the keys the dedup helper consumes (agent + findings + rule_key).
test('S23: Phase-9 dedup hand-off uses --stdin and the reviewers emit a json findings block', () => {
const cmd = read('commands/trekplan.md');
for (const [name, doc] of [['commands/trekplan.md', cmd]]) {
assert.ok(
doc.includes('--stdin'),
`${name} Phase 9 must pipe reviewer findings into plan-review-dedup.mjs via --stdin (reviewers are read-only)`,
);
assert.ok(
!doc.includes('/tmp/plan-critic-out.json') && !doc.includes('/tmp/scope-guardian-out.json'),
`${name} must NOT instruct the read-only reviewers to Write /tmp/*-out.json — they have no Write tool (defect #1)`,
);
}
assert.ok(
cmd.includes('--stdin'),
'commands/trekplan.md Phase 9 must pipe reviewer findings into plan-review-dedup.mjs via --stdin (reviewers are read-only)',
);
assert.ok(
!cmd.includes('/tmp/plan-critic-out.json') && !cmd.includes('/tmp/scope-guardian-out.json'),
'commands/trekplan.md must NOT instruct the read-only reviewers to Write /tmp/*-out.json — they have no Write tool (defect #1)',
);
for (const agentFile of ['agents/plan-critic.md', 'agents/scope-guardian.md']) {
const a = read(agentFile);
assert.ok(

View file

@ -291,9 +291,7 @@ test('[ENG] a finding returned under a trimmed title is not put back twice; an e
// --- the lib row rule ------------------------------------------------
test('[ENG] lib rowOf and trimmedTitle are exported; an untitled finding gets a key no titled row has', () => {
assert.equal(typeof compareMod.rowOf, 'function', 'engine-compare.mjs exports rowOf');
assert.equal(typeof compareMod.trimmedTitle, 'function', 'engine-compare.mjs exports trimmedTitle');
test('[ENG] lib rowOf: an untitled finding gets a key no titled row has', () => {
const untitled = { file: 'src/a.mjs', line: 1, rule_key: 'CODE_REVIEW_DEFECT' };
assert.notEqual(compareMod.rowOf(untitled), compareMod.rowOf({ ...untitled, title: '' }), 'an untitled finding gets a key no titled row has');
});

View file

@ -5,8 +5,7 @@
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { readFileSync, readdirSync } from 'node:fs';
import { dirname, join, relative, resolve, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
@ -14,30 +13,36 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const read = (rel) => readFileSync(join(ROOT, rel), 'utf8');
// ---- lib/ never imports from scripts/ ----------------------------------------
/**
* The import specifiers in `text` (a file at `file`) that resolve into the
* repo's scripts/ directory: static and side-effect imports, re-exports and
* dynamic imports with a string literal.
* The import specifiers in `text`: static and side-effect imports, re-exports
* and dynamic imports with a string literal.
*/
function importsIntoScripts(file, text) {
function specifiers(text) {
const re = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)|\b(?:import|export)\s+(?:[^'";]*?\s+from\s+)?['"]([^'"]+)['"]/g;
const scripts = join(ROOT, 'scripts') + sep;
const hits = [];
for (const m of text.matchAll(re)) {
const spec = m[1] ?? m[2];
if (!spec.startsWith('.') && !spec.startsWith('/')) continue;
if (resolve(dirname(file), spec).startsWith(scripts)) hits.push(spec);
}
return hits;
return [...text.matchAll(re)].map((m) => m[1] ?? m[2]);
}
/** Every .mjs file under `dir`, recursively. */
/**
* `file: specifier` for each import of `files` (repo-relative) that resolves
* into one of `dirs` of the repo. `textOf` reads a file's text.
*/
function forbiddenImports(files, dirs, textOf = read) {
const out = [];
for (const rel of files) {
for (const spec of specifiers(textOf(rel))) {
if (!spec.startsWith('.') && !spec.startsWith('/')) continue;
const target = relative(ROOT, resolve(ROOT, dirname(rel), spec)).split(sep).join('/');
if (dirs.some((d) => target === d || target.startsWith(`${d}/`))) out.push(`${rel}: ${spec}`);
}
}
return out;
}
/** Every .mjs file under the repo-relative `dir`, recursively, repo-relative. */
function mjsFiles(dir) {
const out = [];
for (const e of readdirSync(dir, { withFileTypes: true })) {
const p = join(dir, e.name);
for (const e of readdirSync(join(ROOT, dir), { withFileTypes: true })) {
const p = `${dir}/${e.name}`;
if (e.isDirectory()) out.push(...mjsFiles(p));
else if (e.name.endsWith('.mjs')) out.push(p);
}
@ -45,74 +50,23 @@ function mjsFiles(dir) {
}
test('arch: lib/ never imports from scripts/, and the scan reports a planted import (control)', () => {
const planted = importsIntoScripts(join(ROOT, 'lib', 'review', 'x.mjs'), "import x from '../../scripts/release-check.mjs';\n");
assert.deepEqual(planted, ['../../scripts/release-check.mjs'], 'control: the scan does not report a planted import from scripts/');
const files = mjsFiles(join(ROOT, 'lib'));
const planted = "import x from '../../scripts/release-check.mjs';\n";
assert.deepEqual(forbiddenImports(['lib/review/x.mjs'], ['scripts'], () => planted),
['lib/review/x.mjs: ../../scripts/release-check.mjs'], 'control: the scan does not report a planted import from scripts/');
const files = mjsFiles('lib');
assert.ok(files.length > 10, `the scan found only ${files.length} files under lib/`);
const bad = files.flatMap((f) => importsIntoScripts(f, readFileSync(f, 'utf8')).map((s) => `${f.slice(ROOT.length + 1)}: ${s}`));
assert.deepEqual(bad, [], 'lib/ imports from scripts/');
assert.deepEqual(forbiddenImports(files, ['scripts']), [], 'lib/ imports from scripts/');
});
// ---- the review modules import no scripts/ or hooks/ code ---------------------
const REVIEW_LIB = [
'lib/review/review-answers.mjs', 'lib/review/pre-existing.mjs', 'lib/review/review-rounds.mjs',
'lib/review/review-policy.mjs', 'lib/review/engine-compare.mjs', 'lib/review/instruction-scope.mjs',
'lib/review/review-finalize.mjs', 'lib/review/merge-evidence.mjs', 'lib/util/doors.mjs',
];
const FORBIDDEN_DIRS = ['scripts', 'hooks'];
const PLANTED = "import { checkReview } from '../../scripts/release-check.mjs';\n";
/** The specifiers of a module's import lines: `import … from`, bare `import '…'`, `export … from`, `import('…')`. */
function importSpecifiers(src) {
const out = [];
for (const m of src.matchAll(/^[ \t]*import\b(?!\s*\()[^;]*?(['"])([^'"\n]+)\1/gm)) out.push(m[2]);
for (const m of src.matchAll(/^[ \t]*export\s*(?:\*|\{)[^;]*?\bfrom\s*(['"])([^'"\n]+)\1/gm)) out.push(m[2]);
for (const m of src.matchAll(/\bimport\s*\(\s*(['"])([^'"\n]+)\1\s*\)/g)) out.push(m[2]);
return out;
}
/** `file: specifier` for each import of `files` (paths under `root`) that resolves into scripts/ or hooks/ of `root`. */
function forbiddenImports(root, files) {
const out = [];
for (const rel of files) {
const abs = join(root, rel);
for (const spec of importSpecifiers(readFileSync(abs, 'utf8'))) {
if (!spec.startsWith('.') && !spec.startsWith('/')) continue;
const target = relative(root, resolve(dirname(abs), spec)).split(sep).join('/');
if (FORBIDDEN_DIRS.some((d) => target === d || target.startsWith(`${d}/`))) out.push(`${rel}: ${spec}`);
}
}
return out;
}
test('arch: the review modules import no scripts/ or hooks/ code', () => {
assert.deepEqual([...REVIEW_LIB].sort(), [
'lib/review/engine-compare.mjs', 'lib/review/instruction-scope.mjs', 'lib/review/merge-evidence.mjs',
'lib/review/pre-existing.mjs', 'lib/review/review-answers.mjs', 'lib/review/review-finalize.mjs',
'lib/review/review-policy.mjs', 'lib/review/review-rounds.mjs', 'lib/util/doors.mjs',
], 'the scanner reads exactly these nine files');
for (const rel of REVIEW_LIB) assert.ok(existsSync(join(ROOT, rel)), `missing file ${rel}`);
// The real files: nothing.
assert.deepEqual(forbiddenImports(ROOT, REVIEW_LIB), [], 'a review module imports scripts/ or hooks/ code');
const tmp = mkdtempSync(join(tmpdir(), 'lib-imports-'));
try {
for (const rel of REVIEW_LIB) {
mkdirSync(dirname(join(tmp, rel)), { recursive: true });
writeFileSync(join(tmp, rel), read(rel));
}
// Control: the copy reads like the real files.
assert.deepEqual(forbiddenImports(tmp, REVIEW_LIB), [], 'Control: the clean copy reports nothing');
// A planted import of a scripts/ module is reported.
writeFileSync(join(tmp, REVIEW_LIB[0]), PLANTED + read(REVIEW_LIB[0]));
assert.deepEqual(forbiddenImports(tmp, REVIEW_LIB), [`${REVIEW_LIB[0]}: ../../scripts/release-check.mjs`],
'the scanner reports the planted import of ../../scripts/release-check.mjs');
// The clean copy again: nothing.
writeFileSync(join(tmp, REVIEW_LIB[0]), read(REVIEW_LIB[0]));
assert.deepEqual(forbiddenImports(tmp, REVIEW_LIB), [], 'the clean copy again reports nothing');
} finally {
rmSync(tmp, { recursive: true, force: true });
}
test('arch: the review modules import no hooks/ code, and the scan reports a planted import (control)', () => {
const planted = (rel) => (rel === REVIEW_LIB[0] ? "await import('../../hooks/scripts/pre-bash-executor.mjs');\n" : '') + read(rel);
assert.deepEqual(forbiddenImports(REVIEW_LIB, ['hooks'], planted),
[`${REVIEW_LIB[0]}: ../../hooks/scripts/pre-bash-executor.mjs`], 'control: the scan does not report a planted import from hooks/');
assert.deepEqual(forbiddenImports(REVIEW_LIB, ['hooks']), [], 'a review module imports hooks/ code');
});

View file

@ -41,7 +41,7 @@ const SCOPE_DIRS = ['agents/', 'commands/', 'skills/', 'templates/', 'docs/'];
const SCOPE_ROOT = new Set(['README.md', 'CLAUDE.md', 'CONTRIBUTING.md', 'SECURITY.md']);
// Living tracked text in scope (see the header).
export function inScope(rel) {
function inScope(rel) {
if (!rel.endsWith('.md') || /\.local\./.test(rel)) return false;
if (rel === 'CHANGELOG.md') return false;
return SCOPE_ROOT.has(rel) || SCOPE_DIRS.some(d => rel.startsWith(d));
@ -49,7 +49,7 @@ export function inScope(rel) {
// Every plugin path a text names, as { line, path } with path relative to the
// repository root. `rel` is the text's own path, for relative links.
export function extractPaths(text, rel) {
function extractPaths(text, rel) {
const out = [];
const lines = text.split('\n');
lines.forEach((line, i) => {
@ -78,7 +78,7 @@ export function extractPaths(text, rel) {
}
// "file:line path" for every named path that does not exist under root.
export function deadPaths(root, files) {
function deadPaths(root, files) {
const dead = [];
for (const rel of files) {
const text = readFileSync(join(root, rel), 'utf-8');
@ -89,9 +89,15 @@ export function deadPaths(root, files) {
return dead;
}
function trackedFiles(root) {
const out = execFileSync('git', ['ls-files', '-z'], { cwd: root, encoding: 'utf-8' });
return out.split('\0').filter(Boolean);
let scan = null;
/** The tracked files and every dead hit in the in-scope ones (EXEMPT not applied), computed once. */
function repoScan() {
if (!scan) {
const files = execFileSync('git', ['ls-files', '-z'], { cwd: ROOT, encoding: 'utf-8' }).split('\0').filter(Boolean);
scan = { files, dead: deadPaths(ROOT, files.filter(inScope)) };
}
return scan;
}
// Named exceptions: "file path" -> reason. Each must still be dead (a stale
@ -156,19 +162,19 @@ test('scope keeps living docs and drops history and target-repo text', () => {
});
test('living tracked text names only paths that exist', () => {
const files = trackedFiles(ROOT);
const { files, dead } = repoScan();
assert.ok(files.length > 100, `git ls-files returned ${files.length} files; refusing to pass on an empty scan`);
const hits = deadPaths(ROOT, files.filter(inScope)).filter(h => !EXEMPT.has(exemptKey(h)));
const hits = dead.filter(h => !EXEMPT.has(exemptKey(h)));
assert.deepEqual(hits, [], `${hits.length} dead path reference(s):\n${hits.join('\n')}`);
});
test('every EXEMPT entry is still a dead reference in a tracked file', () => {
const tracked = new Set(trackedFiles(ROOT));
const { files, dead } = repoScan();
const tracked = new Set(files);
const deadKeys = new Set(dead.map(exemptKey));
for (const key of EXEMPT.keys()) {
const [rel, path] = key.split(' ');
const rel = key.split(' ')[0];
assert.ok(tracked.has(rel), `EXEMPT names untracked ${rel}`);
const dead = deadPaths(ROOT, [rel]).map(exemptKey);
assert.ok(dead.includes(key), `EXEMPT entry "${key}" is no longer dead; remove it`);
assert.ok(!existsSync(join(ROOT, path)), `EXEMPT path ${path} exists`);
assert.ok(deadKeys.has(key), `EXEMPT entry "${key}" is no longer dead; remove it`);
}
});

View file

@ -12,7 +12,8 @@ import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
import { MAX_ROUNDS } from '../../lib/validators/plan-gate.mjs';
import { DESIGN_SUBSECTIONS, OPTIONAL_STEP_FIELDS } from '../../lib/validators/plan-validator.mjs';
import { findSection, findSteps, PLAN_VERSION_CURRENT } from '../../lib/parsers/plan-schema.mjs';
import { findSteps, PLAN_VERSION_CURRENT } from '../../lib/parsers/plan-schema.mjs';
import { sectionText } from '../helpers/md-section.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const read = rel => readFileSync(join(ROOT, rel), 'utf-8');
@ -134,24 +135,6 @@ test('the verification CLIs credit what they borrow and say they install nothing
/** Whitespace (line breaks too) collapsed to one space. */
const flat = (s) => s.replace(/\s+/g, ' ').trim();
/**
* The text of a heading's section (level from its #-count), up to the next
* heading of the same or a higher level outside a fence. The heading must exist.
*/
function sectionText(text, heading) {
const level = heading.match(/^#+/)[0].length;
const s = findSection(text, heading.replace(/^#+ /, ''), level);
assert.ok(s, `missing ${heading}`);
const lines = s.text.split('\n');
let fenced = false;
for (let i = 1; i < lines.length; i++) {
if (/^\s{0,3}(`{3,}|~{3,})/.test(lines[i])) fenced = !fenced;
const m = !fenced && lines[i].match(/^(#+) /);
if (m && m[1].length < level) return lines.slice(0, i).join('\n');
}
return s.text;
}
/**
* From the one line of `rel` holding `marker` through the end of the next
* blank-line-separated paragraph after the marker's own paragraph, flattened.

View file

@ -21,7 +21,7 @@ import { validateFindings } from '../../lib/review/findings-schema.mjs';
import { lintSkill } from '../../lib/skills/skill-lint.mjs';
import * as routing from '../../lib/skills/routing.mjs';
import { childEnv } from '../helpers/child-env.mjs';
import * as timing from '../helpers/timing.mjs';
import { CONTROL_RUN } from '../helpers/timing.mjs';
const awaitImportSync = () => routing;
@ -1148,12 +1148,10 @@ test('review 630 r9 / 7b3750e9 plan-schema.mjs:372: a plain path that ends with
// control run. Its numbers live in tests/helpers/timing.mjs (CONTROL_RUN): the
// timeout leaves headroom over a slow start, the hang outlasts it. Every run
// but the control starts slowly on purpose (slowStartMs), so a timeout without
// headroom fails here on demand, not only under load. BEFORE_703D87E1 is the
// record of the numbers before the fix, taken only when the export is absent.
const BEFORE_703D87E1 = Object.freeze({ timeoutMs: 2000, hangMs: 6000, slowStartMs: 2500 });
// headroom fails here on demand, not only under load.
test('review 630 r9 / brief note 3 + 703d87e1: red proof reports a control run it stopped as not-run, in the control run, when every other run starts slowly', async () => {
const c = timing.CONTROL_RUN ?? BEFORE_703D87E1;
const c = CONTROL_RUN;
const { proveRed } = await import('../../lib/verification/red-proof.mjs');
const repo = gitRepo('r630-r9-control-');
try {

View file

@ -11,7 +11,7 @@ import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { findSection } from '../../lib/parsers/plan-schema.mjs';
import { sectionText } from '../helpers/md-section.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const read = (rel) => readFileSync(join(ROOT, rel), 'utf8');
@ -63,24 +63,6 @@ test('run coupling: the writing-criteria quiet case is owned by run, with no sta
// also one with no app to launch (the "no — {reason}" form).
const EXEC = 'commands/trekexecute.md';
/**
* The text of a heading's section (level from its #-count), up to the next
* heading of the same or a higher level outside a fence. The heading must exist.
*/
function sectionText(text, heading) {
const level = heading.match(/^#+/)[0].length;
const s = findSection(text, heading.replace(/^#+ /, ''), level);
assert.ok(s, `missing ${heading}`);
const lines = s.text.split('\n');
let fenced = false;
for (let i = 1; i < lines.length; i++) {
if (/^\s{0,3}(`{3,}|~{3,})/.test(lines[i])) fenced = !fenced;
const m = !fenced && lines[i].match(/^(#+) /);
if (m && m[1].length < level) return lines.slice(0, i).join('\n');
}
return s.text;
}
/** Phase 8 of /trekexecute, read up to Phase 9 (its report fence holds its own `## ` heading). */
function phase8(text) {
const start = text.indexOf('## Phase 8 — Final report');

View file

@ -107,13 +107,11 @@ test('routing: rank puts the matching description first, and a no-overlap prompt
assert.ok(rank('zebra quantum', corpus).every((r) => r.score === 0));
});
test('routing: the reader takes block-scalar agent descriptions and skips orchestrator reference docs', () => {
test('routing: the reader takes block-scalar agent descriptions', () => {
withPlugin({
agents: { 'plan-critic': 'Use this agent when a plan needs adversarial review.' },
commands: { trekplan: 'Deep implementation planning from a task brief.' },
}, (root) => {
writeFileSync(join(root, 'agents', 'planning-orchestrator.md'),
'---\nname: planning-orchestrator\ndescription: Reference document, not a spawnable capability.\n---\n');
const ids = loadEntries(root).map((e) => e.id).sort();
assert.deepEqual(ids, ['agent:plan-critic', 'command:trekplan']);
assert.equal(loadEntries(root).find((e) => e.id === 'agent:plan-critic').description.trim(),