refactor(simplify): one source per rule, shared test helpers, a faster isolation test [skip-docs]

/simplify over a97efb6..HEAD (the plan_version 1.9 / trial-4 delivery).

- coordinator-contract: engine names come from engine-findings ENGINES;
  one markedMajorCodeReview predicate under both lowering checks;
  isEngineFinding everywhere; the unreachable branch (c) of
  illegalBriefLowering folded into (e).
- armB workflow: RULE_KEYS = Object.keys(RULE_TIERS); the engine map is a
  literal pinned equal to ENGINES by the workflow test.
- plan-schema: usesFilesGrammar() is the one 1.9 check (validator, sweep,
  testFirstFiles, stepTestsOnlyFromFields); opensEntry built on opensChunk.
- plan-validator: one issue builder for the Files and Test first grammar.
- plan-scope-sweep: the 1.9 and the 6.2.0 reader are one if/else.
- artifact-commit: TRAILER_RE/trailerProblem no longer exported (no
  importer); the order comment says the CLI finds the repo root first.
- tests: the lock drivers in session-id-isolation (d) run side by side per
  mode (~100 s -> ~30 s); a shared artifact-repo helper; severityRank and
  extractFindingsBlock reused; childEnv(env) without a spread; one copy of
  the PLAN_VERSION_CURRENT check.

Plan readers: validator errors/warnings, plannedPaths, fieldPaths,
testFirstFiles and stepTestsOnlyFromFields compared byte for byte against
HEAD over 510 plans / 1692 steps plus 210 synthetic 1.7-1.9 cases: same.
Suite 2384 (2382/0/2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-12 21:06:47 +02:00
commit ed6699da58
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
23 changed files with 201 additions and 204 deletions

View file

@ -138,7 +138,7 @@ function makeRepo(opts = {}) {
}
function runBlock(script, { cwd, env }) {
return spawnSync('bash', ['-c', script], { cwd, env: childEnv({ ...env }), encoding: 'utf8' });
return spawnSync('bash', ['-c', script], { cwd, env: childEnv(env), encoding: 'utf8' });
}
function copyBlockEnv(repo, pathPrefixDir) {

View file

@ -0,0 +1,41 @@
// tests/helpers/artifact-repo.mjs
// A throwaway target repo for the artifact-commit tests
// (tests/lib/artifact-commit.test.mjs, tests/lib/artifact-commit-trailer.test.mjs):
// one commit, no git hooks, a project folder holding brief.md and, unless
// `withLog` is false, an approval-log entry for it.
import { spawnSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { childEnv } from './child-env.mjs';
const LOG = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'lib', 'util', 'approval-log.mjs');
const H1 = 'c'.repeat(64);
export function git(cwd, ...args) {
return spawnSync('git', args, { cwd, encoding: 'utf8', env: childEnv() });
}
/** @returns {{ d: string, brief: string }} the repo and its brief.md */
export function artifactRepo({ prefix = 'artifact-commit-', project = '2026-10-06-toggle', withLog = true } = {}) {
const d = mkdtempSync(join(tmpdir(), prefix));
git(d, 'init', '-q');
git(d, 'config', 'user.name', 'Test');
git(d, 'config', 'user.email', 'test@example.invalid');
git(d, 'config', 'commit.gpgsign', 'false');
mkdirSync(join(d, '.hooks-none'));
git(d, 'config', 'core.hooksPath', '.hooks-none');
writeFileSync(join(d, 'README.md'), 'x\n');
git(d, 'add', 'README.md');
git(d, 'commit', '-q', '-m', 'init');
const proj = join(d, '.claude', 'projects', project);
mkdirSync(proj, { recursive: true });
writeFileSync(join(proj, 'brief.md'), '# brief\n');
if (withLog) {
spawnSync(process.execPath, [LOG, 'append', '--kind', 'intent', '--subject', join(proj, 'brief.md'), '--sha256', H1],
{ cwd: d, env: childEnv() });
}
return { d, brief: join(proj, 'brief.md') };
}

View file

@ -38,7 +38,7 @@ export function runHook(scriptPath, input) {
*/
export function runHookWithEnv(scriptPath, input, envOverrides, opts = {}) {
return new Promise((resolve) => {
const env = childEnv({ ...envOverrides });
const env = childEnv(envOverrides);
const child = execFile(
'node',
[scriptPath],

View file

@ -178,7 +178,7 @@ test('review 5: a guard module that cannot load makes the Bash guard ask, never
});
function lockIn(repo, rel, env = {}) {
const r = spawnSync(process.execPath, [LOCK, 'lock', '--files', rel, '--step', '1'], { cwd: repo, encoding: 'utf8', env: childEnv({ ...env }) });
const r = spawnSync(process.execPath, [LOCK, 'lock', '--files', rel, '--step', '1'], { cwd: repo, encoding: 'utf8', env: childEnv(env) });
assert.equal(r.status, 0, r.stderr);
}

View file

@ -68,7 +68,7 @@ function jsRepo({ cycle = false, binary = true } = {}) {
}
function run(args, env = {}) {
const r = spawnSync('node', [RUNNER, ...args], { encoding: 'utf-8', env: childEnv({ ...env }) });
const r = spawnSync('node', [RUNNER, ...args], { encoding: 'utf-8', env: childEnv(env) });
let json = null;
try { json = JSON.parse(r.stdout); } catch { /* asserted by caller */ }
return { code: r.status, json, out: r.stdout, err: r.stderr };

View file

@ -10,46 +10,18 @@
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { spawnSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { readFileSync, readdirSync, realpathSync, rmSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { childEnv } from '../helpers/child-env.mjs';
import { artifactRepo, git } from '../helpers/artifact-repo.mjs';
import { commitArtifacts } from '../../lib/util/artifact-commit.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..');
const CLI = join(ROOT, 'lib', 'util', 'artifact-commit.mjs');
const LOG = join(ROOT, 'lib', 'util', 'approval-log.mjs');
const H1 = 'c'.repeat(64);
const TRAILER = 'Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>';
function git(cwd, ...args) {
return spawnSync('git', args, { cwd, encoding: 'utf8', env: childEnv() });
}
// Same shape as the repo() factory in tests/lib/artifact-commit.test.mjs.
function repo({ withLog = true } = {}) {
const d = mkdtempSync(join(tmpdir(), 'artifact-trailer-'));
git(d, 'init', '-q');
git(d, 'config', 'user.name', 'Test');
git(d, 'config', 'user.email', 'test@example.invalid');
git(d, 'config', 'commit.gpgsign', 'false');
mkdirSync(join(d, '.hooks-none'));
git(d, 'config', 'core.hooksPath', '.hooks-none');
writeFileSync(join(d, 'README.md'), 'x\n');
git(d, 'add', 'README.md');
git(d, 'commit', '-q', '-m', 'init');
const proj = join(d, '.claude', 'projects', '2026-10-12-trailer');
mkdirSync(proj, { recursive: true });
writeFileSync(join(proj, 'brief.md'), '# brief\n');
if (withLog) {
spawnSync(process.execPath, [LOG, 'append', '--kind', 'intent', '--subject', join(proj, 'brief.md'), '--sha256', H1],
{ cwd: d, env: childEnv() });
}
return { d, brief: join(proj, 'brief.md') };
}
function run(args, cwd) {
return spawnSync(process.execPath, [CLI, ...args], { cwd, encoding: 'utf8', env: childEnv() });
}
@ -57,7 +29,7 @@ function run(args, cwd) {
const head = (d) => git(d, 'rev-parse', 'HEAD').stdout.trim();
test('a passed trailer ends the commit message and git parses it as a trailer', () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo({ prefix: 'artifact-trailer-', project: '2026-10-12-trailer' });
try {
const r = run(['--store', 'local', '--artifact', brief, '--message', 'docs(voyage): approve intent trailer',
'--trailer', TRAILER], d);
@ -81,7 +53,7 @@ const REFUSED = [
for (const [name, value] of REFUSED) {
test(`refused with exit 1, nothing committed: ${name}`, () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo({ prefix: 'artifact-trailer-', project: '2026-10-12-trailer' });
try {
const before = head(d);
const r = run(['--store', 'local', '--artifact', brief, '--message', 'x', '--trailer', value], d);
@ -95,7 +67,7 @@ for (const [name, value] of REFUSED) {
}
test('a call without a trailer exits 1 with HEAD unchanged', () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo({ prefix: 'artifact-trailer-', project: '2026-10-12-trailer' });
try {
const before = head(d);
const r = run(['--store', 'local', '--artifact', brief, '--message', 'x'], d);
@ -107,7 +79,7 @@ test('a call without a trailer exits 1 with HEAD unchanged', () => {
});
test('the trailer check comes before any git call: "nothing to commit" does not mask a missing trailer', () => {
const { d, brief } = repo({ withLog: false });
const { d, brief } = artifactRepo({ prefix: 'artifact-trailer-', project: '2026-10-12-trailer', withLog: false });
try {
const r = run(['--store', 'local', '--artifact', brief, '--message', 'x'], d);
assert.equal(r.status, 1, r.stdout + r.stderr);
@ -116,7 +88,7 @@ test('the trailer check comes before any git call: "nothing to commit" does not
});
test('commitArtifacts takes the trailer too, and refuses without it', () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo({ prefix: 'artifact-trailer-', project: '2026-10-12-trailer' });
try {
const repoDir = realpathSync(d);
const before = head(d);
@ -132,7 +104,7 @@ test('commitArtifacts takes the trailer too, and refuses without it', () => {
});
test('usage before trailer: a missing --store is still exit 2, with or without a trailer', () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo({ prefix: 'artifact-trailer-', project: '2026-10-12-trailer' });
try {
assert.equal(run(['--artifact', brief, '--message', 'x'], d).status, 2);
assert.equal(run(['--artifact', brief, '--message', 'x', '--trailer', TRAILER], d).status, 2);

View file

@ -6,41 +6,18 @@
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { spawnSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { artifactRepo, git } from '../helpers/artifact-repo.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..');
const CLI = join(ROOT, 'lib', 'util', 'artifact-commit.mjs');
const LOG = join(ROOT, 'lib', 'util', 'approval-log.mjs');
const H1 = 'c'.repeat(64);
// Every call passes a valid trailer (the CLI refuses a call without one).
const TRAILER = ['--trailer', 'Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>'];
function git(cwd, ...args) {
return spawnSync('git', args, { cwd, encoding: 'utf8' });
}
function repo() {
const d = mkdtempSync(join(tmpdir(), 'artifact-commit-'));
git(d, 'init', '-q');
git(d, 'config', 'user.name', 'Test');
git(d, 'config', 'user.email', 'test@example.invalid');
git(d, 'config', 'commit.gpgsign', 'false');
mkdirSync(join(d, '.hooks-none'));
git(d, 'config', 'core.hooksPath', '.hooks-none');
writeFileSync(join(d, 'README.md'), 'x\n');
git(d, 'add', 'README.md');
git(d, 'commit', '-q', '-m', 'init');
const proj = join(d, '.claude', 'projects', '2026-10-06-toggle');
mkdirSync(proj, { recursive: true });
writeFileSync(join(proj, 'brief.md'), '# brief\n');
spawnSync(process.execPath, [LOG, 'append', '--kind', 'intent', '--subject', join(proj, 'brief.md'), '--sha256', H1], { cwd: d });
return { d, brief: join(proj, 'brief.md') };
}
function run(args, cwd) {
return spawnSync(process.execPath, [CLI, ...args], { cwd, encoding: 'utf8' });
}
@ -48,7 +25,7 @@ function run(args, cwd) {
const committedFiles = (d) => git(d, 'show', '--name-only', '--format=', 'HEAD').stdout.trim().split('\n').filter(Boolean);
test('store git: the brief and the log land in one commit, and nothing else the operator staged', () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo();
try {
writeFileSync(join(d, 'unrelated.txt'), 'staged by the operator\n');
git(d, 'add', 'unrelated.txt');
@ -63,7 +40,7 @@ test('store git: the brief and the log land in one commit, and nothing else the
});
test('store local: only the log is committed, and the project folder is kept out of git locally', () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo();
try {
const r = run(['--store', 'local', '--artifact', brief, '--message', 'docs(voyage): approve intent toggle', ...TRAILER], d);
assert.equal(r.status, 0, r.stdout + r.stderr);
@ -80,7 +57,7 @@ test('store local: only the log is committed, and the project folder is kept out
});
test('nothing new to commit is exit 0 with committed:false (a re-run is not an error)', () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo();
try {
assert.equal(run(['--store', 'git', '--artifact', brief, '--message', 'one', ...TRAILER], d).status, 0);
const r = run(['--store', 'git', '--artifact', brief, '--message', 'two', ...TRAILER], d);
@ -90,7 +67,7 @@ test('nothing new to commit is exit 0 with committed:false (a re-run is not an e
});
test('a path the repo ignores is not forced in: exit 1 with the reason', () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo();
try {
writeFileSync(join(d, '.gitignore'), '.claude/\n');
const r = run(['--store', 'git', '--artifact', brief, '--message', 'x', ...TRAILER], d);
@ -109,7 +86,7 @@ test('not a git repository: exit 2 (could not run), never a silent pass', () =>
});
test('a missing or unknown --store is a usage error (exit 2)', () => {
const { d, brief } = repo();
const { d, brief } = artifactRepo();
try {
assert.equal(run(['--artifact', brief, '--message', 'x', ...TRAILER], d).status, 2);
assert.equal(run(['--store', 'cloud', '--artifact', brief, '--message', 'x', ...TRAILER], d).status, 2);

View file

@ -15,12 +15,7 @@ import { stepTestsOnly } from '../../lib/parsers/plan-schema.mjs';
const step = (files) => `- **Files:** ${files}\n- **Changes:** x\n`;
// A 1.8 plan whose step 2 carries `files` as its `Files:` field.
function planWith(files) {
const plan = buildPlan({ steps: [{}, { file: 'src/mod2.mjs', endToEnd: 'bash tests/e2e.sh' }] });
const line = '- **Files:** `src/mod2.mjs`';
assert.ok(plan.includes(line), 'the builder writes step 2\'s Files: line');
return plan.replace(line, `- **Files:** ${files}`);
}
const planWith = (files) => buildPlan({ steps: [{}, { file: 'src/mod2.mjs', filesRaw: files, endToEnd: 'bash tests/e2e.sh' }] });
const testsOnlyWarned = (files) => validatePlanContent(planWith(files)).warnings
.some((w) => w.code === 'PLAN_STEP_TESTS_ONLY' && /Step 2\b/.test(w.message));

View file

@ -11,7 +11,6 @@ import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { PLAN_VERSION_CURRENT } from '../../lib/parsers/plan-schema.mjs';
import { validatePlanContent } from '../../lib/validators/plan-validator.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
@ -61,10 +60,6 @@ const claudeRow = () => {
return m[0];
};
test('PLAN_VERSION_CURRENT is 1.9', () => {
assert.equal(PLAN_VERSION_CURRENT, '1.9');
});
test('Handover 4 names plan_version 1.9 and the "file first on every line" rule', () => {
const h4 = handover4();
assert.match(h4, /plan_version 1\.9/);

View file

@ -41,7 +41,7 @@ function runShim(args, env) {
const out = execFileSync(process.execPath, [SHIM, ...args], {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'pipe'],
env: childEnv({ ...env }),
env: childEnv(env),
});
return { code: 0, out };
} catch (e) {

View file

@ -179,8 +179,11 @@ test('trekreview — the write-outside-project_dir exception names only the mark
test('armB workflow — its inlined rule-key enum equals the catalogue (no drift)', () => {
const src = read('scripts/trekreview-armB.workflow.mjs');
const block = src.slice(src.indexOf('const RULE_KEYS = ['), src.indexOf('];', src.indexOf('const RULE_KEYS = [')));
const keys = [...block.matchAll(/'([A-Z_]+)'/g)].map((m) => m[1]).sort();
assert.ok(src.includes('const RULE_KEYS = Object.keys(RULE_TIERS);'), 'the enum is the keys of the inlined RULE_TIERS');
const start = src.indexOf('const RULE_TIERS = {');
assert.ok(start !== -1, 'the script carries a RULE_TIERS literal block');
const block = src.slice(start, src.indexOf('};', start));
const keys = [...block.matchAll(/([A-Z_]+):\s*'[A-Z]+'/g)].map((m) => m[1]).sort();
assert.deepEqual(keys, [...RULE_KEYS].sort());
});

View file

@ -27,7 +27,7 @@
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { spawnSync } from 'node:child_process';
import { spawn, spawnSync } from 'node:child_process';
import { readFileSync, readdirSync, existsSync, mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from 'node:fs';
import { join, dirname, relative, sep } from 'node:path';
import { tmpdir } from 'node:os';
@ -165,7 +165,30 @@ const NOT_DRIVERS = new Map([
]);
const DRIVER_RE = /test-lock\.mjs|stop-test-lock/;
test('(d) the lock-driving test files run green and leave no pointer for a made-up id', { timeout: TIMEOUT }, () => {
// One nested `node --test` run of `rel`, with its own made-up id and private
// TMPDIR. Resolves to the problems it shows.
function runDriver(rel, mode, pre) {
const id = madeUpId();
const tmp = privateTmp();
return new Promise((resolve) => {
const child = spawn(process.execPath, [...pre, '--test', '--test-reporter=tap', rel],
{ cwd: ROOT, env: isolatedEnv(id, tmp), timeout: TIMEOUT });
let out = '';
child.stdout.on('data', (d) => { out += d; });
child.stderr.on('data', (d) => { out += d; });
child.on('close', (status, signal) => {
const problems = [];
const m = out.match(/^# tests (\d+)$/m);
if (status !== 0) problems.push(`${rel} [${mode}]: exit ${status}${signal ? ` (${signal})` : ''}`);
if (!m || Number(m[1]) === 0) problems.push(`${rel} [${mode}]: reported no tests`);
if (existsSync(pointerPath(tmp, id))) problems.push(`${rel} [${mode}]: left a session pointer for the made-up id`);
rmSync(tmp, { recursive: true, force: true });
resolve(problems);
});
});
}
test('(d) the lock-driving test files run green and leave no pointer for a made-up id', { timeout: TIMEOUT }, async () => {
const all = walk(join(ROOT, 'tests'), (n) => n.endsWith('.test.mjs')).filter((f) => f !== SELF);
const matching = all.filter((rel) => DRIVER_RE.test(readFileSync(join(ROOT, rel), 'utf8')));
for (const [rel, why] of NOT_DRIVERS) {
@ -175,24 +198,12 @@ test('(d) the lock-driving test files run green and leave no pointer for a made-
const drivers = matching.filter((rel) => !NOT_DRIVERS.has(rel));
assert.ok(drivers.length >= 5, `only ${drivers.length} lock-driving test files found: ${drivers.join(', ')}`);
const flags = preloadFlags();
// The files of one mode run side by side (as `node --test` runs files); the
// two modes run one after the other.
const problems = [];
for (const rel of drivers) {
for (const [mode, pre] of [['test command', flags], ['direct', []]]) {
const id = madeUpId();
const tmp = privateTmp();
try {
const r = spawnSync(process.execPath, [...pre, '--test', '--test-reporter=tap', rel],
{ cwd: ROOT, encoding: 'utf8', env: isolatedEnv(id, tmp), timeout: TIMEOUT });
const out = `${r.stdout || ''}\n${r.stderr || ''}`;
const m = out.match(/^# tests (\d+)$/m);
if (r.status !== 0) problems.push(`${rel} [${mode}]: exit ${r.status}${r.signal ? ` (${r.signal})` : ''}`);
if (!m || Number(m[1]) === 0) problems.push(`${rel} [${mode}]: reported no tests`);
if (existsSync(pointerPath(tmp, id))) problems.push(`${rel} [${mode}]: left a session pointer for the made-up id`);
} finally {
rmSync(tmp, { recursive: true, force: true });
}
}
for (const [mode, pre] of [['test command', preloadFlags()], ['direct', []]]) {
const runs = await Promise.all(drivers.map((rel) => runDriver(rel, mode, pre)));
problems.push(...runs.flat());
}
assert.deepEqual(problems, []);
});

View file

@ -101,7 +101,7 @@ function fill(script, values) {
const BASH = existsSync('/bin/bash') ? '/bin/bash' : 'bash';
function runBash(script, cwd, env = {}) {
return spawnSync(BASH, ['-c', script], { cwd, encoding: 'utf-8', env: childEnv({ ...env }) });
return spawnSync(BASH, ['-c', script], { cwd, encoding: 'utf-8', env: childEnv(env) });
}
function gitc(cwd, ...args) {
@ -556,7 +556,7 @@ test('Check 4 and Step 5 pass a first run (no worktrees yet) under zsh as well a
const step5 = fill(bashBlock('### Step 5 — Cleanup'), { slug: 'demo' });
for (const shell of [BASH, ZSH]) {
for (const [name, block] of [['Check 4', check4], ['Step 5', step5]]) {
const r = spawnSync(shell, ['-c', block], { cwd: root, encoding: 'utf-8', env: childEnv({ ...env }) });
const r = spawnSync(shell, ['-c', block], { cwd: root, encoding: 'utf-8', env: childEnv(env) });
assert.equal(r.status, 0, `${name} under ${shell}: ${r.stderr}`);
assert.doesNotMatch(r.stderr, /no matches found/, `${name} under ${shell}`);
}

View file

@ -17,7 +17,7 @@ const HERE = dirname(fileURLToPath(import.meta.url));
const CLI = join(HERE, '..', '..', 'lib', 'util', 'test-lock.mjs');
function run(cwd, args, env = {}) {
const r = spawnSync(process.execPath, [CLI, ...args], { cwd, encoding: 'utf8', env: childEnv({ ...env }) });
const r = spawnSync(process.execPath, [CLI, ...args], { cwd, encoding: 'utf8', env: childEnv(env) });
return { code: r.status, stdout: r.stdout, stderr: r.stderr };
}

View file

@ -19,6 +19,7 @@ import { readFileSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import * as m from '../../lib/review/coordinator-contract.mjs';
import { extractFindingsBlock } from '../../lib/review/findings-schema.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const GATE = join(ROOT, 'lib', 'review', 'verdict-gate.mjs');
@ -289,7 +290,7 @@ test('verdict gate — exit 0 on the legal brief lowering', () => {
test('verdict gate — exit 2 on the illegal brief lowering, the reason naming the finding id', () => {
assert.ok(existsSync(ILLEGAL_FIXTURE), 'tests/fixtures/review-brief-conflict-illegal.md exists');
const text = readFileSync(ILLEGAL_FIXTURE, 'utf-8');
const json = JSON.parse(text.slice(text.lastIndexOf('```json') + 7, text.lastIndexOf('```')));
const json = JSON.parse(extractFindingsBlock(text));
assert.equal(json.verdict, 'ALLOW', 'the fixture claims ALLOW');
const id = json.findings[0].id;
assert.match(id, /^[0-9a-f]{40}$/);

View file

@ -9,7 +9,8 @@ import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { illegalBriefLowering } from '../../lib/review/coordinator-contract.mjs';
import { illegalBriefLowering, severityRank as rank } from '../../lib/review/coordinator-contract.mjs';
import { ENGINES } from '../../lib/review/engine-findings.mjs';
import { RULE_CATALOGUE } from '../../lib/review/rule-catalogue.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
@ -92,8 +93,6 @@ test('armB — quick mode expects only the /code-review engine', async () => {
// checks the floor raised a finding exactly where that value is true — plus
// case 7, where a false `engine` label alone raises.
const RANK = ['BLOCKER', 'MAJOR', 'MINOR', 'SUGGESTION'];
const rank = (s) => (RANK.indexOf(s) === -1 ? RANK.length : RANK.indexOf(s));
const MARK = 'Non-Goal: no new runtime dependency';
const FILE = 'src/a.mjs';
const LINE = 10;
@ -301,6 +300,15 @@ test('armB brief rule — RULE_TIERS in the script equals the catalogue (same ke
assert.deepEqual(tiers, catalogue);
});
test('armB brief rule — ENGINE_OF_PAYLOAD in the script maps each ENGINES reviewer name to its engine', () => {
const start = SRC.indexOf('const ENGINE_OF_PAYLOAD = {');
assert.ok(start !== -1, 'the script carries an ENGINE_OF_PAYLOAD literal block');
const block = SRC.slice(start, SRC.indexOf('};', start));
const map = Object.fromEntries([...block.matchAll(/'([a-z-]+)':\s*'([a-z-]+)'/g)].map((m) => [m[1], m[2]]));
const expected = Object.fromEntries(Object.entries(ENGINES).map(([engine, spec]) => [spec.reviewer, engine]));
assert.deepEqual(map, expected);
});
test('armB brief rule — VERDICT_SCHEMA declares engine, contradicts_brief and brief_lowered_from; the prompt states the rule', async () => {
const c = await floorCase({
codeFindings: [F('CODE_REVIEW_DEFECT', 'MAJOR')],

View file

@ -79,7 +79,7 @@ function commandLine(rel, verb) {
function run(line, subs, env = {}) {
let cmd = line.split('${CLAUDE_PLUGIN_ROOT}').join(ROOT);
for (const [k, v] of Object.entries(subs)) cmd = cmd.split(k).join(v);
return spawnSync('bash', ['-c', cmd], { encoding: 'utf8', env: childEnv({ ...env }) });
return spawnSync('bash', ['-c', cmd], { encoding: 'utf8', env: childEnv(env) });
}
function gate(briefPath) {