refactor(simplify): one plan read in red proof, one list marker, one put-back rule [skip-docs]
/simplify over ed6699d..HEAD (closing review of the remediation): - red-proof reads and parses the step once (planStep) for the test list and tests_only; normTest goes, plan-schema exports normPlanPath, also used by plan-scope-sweep. - plan-schema builds LIST_ITEM and the 1.9 File: line from one LIST_MARKER; the below-1.9 File: regex ends in a lookahead instead of slicing back one. - the workflow script hoists tripletOf and puts back through dedupByTriplet. - coordinator-contract keeps the rank in its JSDoc only; engine-findings drops a redundant String(). - tests: reuse knownIssues, trekplan19Block, step1Fields, one red-proof cli helper with childEnv, one payload-pair builder; drop a test the decision-20 definition test covers. No behaviour change. Suite 2462 (2460/0/2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d7db590c26
commit
bcf3adbba7
11 changed files with 90 additions and 127 deletions
|
|
@ -146,6 +146,14 @@ test('HANDOVER-CONTRACTS: the design-part line and invariant 5 say "1.8 or later
|
|||
assert.ok(!bullet.includes('its `Test first:` file,'), 'invariant 7 no longer says "its `Test first:` file"');
|
||||
});
|
||||
|
||||
const trekplan19Block = () => {
|
||||
const p = read('commands/trekplan.md');
|
||||
const h19 = p.indexOf('**plan_version 1.9 additions**');
|
||||
assert.ok(h19 >= 0, 'commands/trekplan.md has the "**plan_version 1.9 additions**" block');
|
||||
const end = p.indexOf('\n>\n> **', h19);
|
||||
return p.slice(h19, end < 0 ? undefined : end);
|
||||
};
|
||||
|
||||
test('/trekplan: the 1.8 additions heading says "1.8 or later", and a separate 1.9 additions block follows it', () => {
|
||||
const p = read('commands/trekplan.md');
|
||||
const h18 = p.indexOf('**plan_version 1.8 or later additions**');
|
||||
|
|
@ -153,9 +161,7 @@ test('/trekplan: the 1.8 additions heading says "1.8 or later", and a separate 1
|
|||
assert.ok(!p.includes('**plan_version 1.8 additions**'), 'the old 1.8-only heading is gone');
|
||||
const h19 = p.indexOf('**plan_version 1.9 additions**');
|
||||
assert.ok(h19 > h18, 'a separate "**plan_version 1.9 additions**" block stands after the 1.8 block');
|
||||
const end = p.indexOf('\n>\n> **', h19);
|
||||
const block = p.slice(h19, end < 0 ? undefined : end);
|
||||
assert.ok(block.includes(ANNOTATION), `the 1.9 block says "${ANNOTATION}"`);
|
||||
assert.ok(trekplan19Block().includes(ANNOTATION), `the 1.9 block says "${ANNOTATION}"`);
|
||||
});
|
||||
|
||||
// Step 12: the docs follow the 1.9 fixes. Operator decision 20 (12.10): the
|
||||
|
|
@ -181,14 +187,6 @@ const invariant7 = () => {
|
|||
return h4.slice(m7.index, m8 ? m7.index + m8.index : undefined);
|
||||
};
|
||||
|
||||
const trekplan19Block = () => {
|
||||
const p = read('commands/trekplan.md');
|
||||
const h19 = p.indexOf('**plan_version 1.9 additions**');
|
||||
assert.ok(h19 >= 0, 'commands/trekplan.md has the "**plan_version 1.9 additions**" block');
|
||||
const end = p.indexOf('\n>\n> **', h19);
|
||||
return p.slice(h19, end < 0 ? undefined : end);
|
||||
};
|
||||
|
||||
const template19Paragraph = () => {
|
||||
const t = read('templates/plan-template.md');
|
||||
const start = t.indexOf('From plan_version 1.9 the rule is the file first on every line');
|
||||
|
|
@ -275,27 +273,7 @@ test('CHANGELOG: parseDoors reads the Unreleased section\'s door lines, none bad
|
|||
`one door line names plan_version 1.9: ${JSON.stringify(d.entries.map((e) => e.text))}`);
|
||||
});
|
||||
|
||||
test('CHANGELOG: the Unreleased section\'s Known issues name the session split', () => {
|
||||
const { heading, body } = firstChangelogSection();
|
||||
assert.equal(heading, UNRELEASED);
|
||||
const lines = body.split('\n');
|
||||
const start = lines.findIndex((l) => /^### Known issues\s*$/.test(l));
|
||||
assert.ok(start >= 0, 'the Unreleased section has a "### Known issues" heading');
|
||||
let end = lines.length;
|
||||
for (let i = start + 1; i < lines.length; i++) {
|
||||
if (/^###? /.test(lines[i])) { end = i; break; }
|
||||
}
|
||||
const known = lines.slice(start + 1, end).join('\n');
|
||||
assert.match(known, /session split/, `Known issues name the session split: ${JSON.stringify(known)}`);
|
||||
});
|
||||
|
||||
// Step 14 of the remediation: the CHANGELOG records the review fixes. The door
|
||||
// line for the Test first files names operator decision 20 (the backticked paths
|
||||
// right after File:), the workflow put-back names operator decision 23, the
|
||||
// engine-provenance entry states the tier rank instead of "on a severity tie",
|
||||
// Known issues name the default path's verdict gate (operator decision 25), and
|
||||
// the intro's measurement names the remediation's Step 13 commit. Word
|
||||
// boundaries keep "operator decision 2026-10-12" from counting as decision 20.
|
||||
// The Unreleased section's `### Known issues` body, up to the next heading.
|
||||
const knownIssues = (body) => {
|
||||
const lines = body.split('\n');
|
||||
const start = lines.findIndex((l) => /^### Known issues\s*$/.test(l));
|
||||
|
|
@ -307,6 +285,20 @@ const knownIssues = (body) => {
|
|||
return lines.slice(start + 1, end).join('\n');
|
||||
};
|
||||
|
||||
test('CHANGELOG: the Unreleased section\'s Known issues name the session split', () => {
|
||||
const { heading, body } = firstChangelogSection();
|
||||
assert.equal(heading, UNRELEASED);
|
||||
const known = knownIssues(body);
|
||||
assert.match(known, /session split/, `Known issues name the session split: ${JSON.stringify(known)}`);
|
||||
});
|
||||
|
||||
// Step 14 of the remediation: the CHANGELOG records the review fixes. The door
|
||||
// line for the Test first files names operator decision 20 (the backticked paths
|
||||
// right after File:), the workflow put-back names operator decision 23, the
|
||||
// engine-provenance entry states the tier rank instead of "on a severity tie",
|
||||
// Known issues name the default path's verdict gate (operator decision 25), and
|
||||
// the intro's measurement names the remediation's Step 13 commit. Word
|
||||
// boundaries keep "operator decision 2026-10-12" from counting as decision 20.
|
||||
test('CHANGELOG: the door lines name operator decisions 20 and 23, and none says "every test file on the Test first File line"', () => {
|
||||
const { heading, body } = firstChangelogSection();
|
||||
assert.equal(heading, UNRELEASED);
|
||||
|
|
|
|||
|
|
@ -53,8 +53,9 @@ function addTest(dir) {
|
|||
writeFileSync(join(dir, 'tests', 'sum.test.mjs'), SUM_TEST);
|
||||
}
|
||||
|
||||
/** The CLI with the child environment the suite requires (tests/helpers/child-env.mjs). */
|
||||
function cli(args) {
|
||||
const r = spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8' });
|
||||
const r = spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', env: childEnv() });
|
||||
return { status: r.status, stdout: r.stdout, stderr: r.stderr };
|
||||
}
|
||||
|
||||
|
|
@ -293,12 +294,6 @@ function planFile(dir, opts = {}, version = '1.8') {
|
|||
return p;
|
||||
}
|
||||
|
||||
/** The CLI with the child environment the suite requires (tests/helpers/child-env.mjs). */
|
||||
function cliPlan(args) {
|
||||
const r = spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', env: childEnv() });
|
||||
return { status: r.status, stdout: r.stdout, stderr: r.stderr };
|
||||
}
|
||||
|
||||
test('red proof --plan/--step: a --tests that differs from the step\'s Test first File line is not-run, exit 2 (tests-mismatch)', () => {
|
||||
const { dir, base } = repo('a - b');
|
||||
try {
|
||||
|
|
@ -307,7 +302,7 @@ test('red proof --plan/--step: a --tests that differs from the step\'s Test firs
|
|||
const plan = planFile(dir);
|
||||
for (const given of ['tests/other.test.mjs', 'tests/sum.test.mjs,tests/other.test.mjs']) {
|
||||
for (const mode of [['--red-only'], []]) {
|
||||
const c = cliPlan([...mode, '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--tests', given, '--plan', plan, '--step', '1', '--json']);
|
||||
const c = cli([...mode, '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--tests', given, '--plan', plan, '--step', '1', '--json']);
|
||||
assert.equal(c.status, 2, `${given} ${mode.join(' ')}: ${c.stdout}${c.stderr}`);
|
||||
const out = JSON.parse(c.stdout);
|
||||
assert.equal(out.status, 'not-run', JSON.stringify(out));
|
||||
|
|
@ -328,17 +323,17 @@ test('red proof --plan/--step without --tests: the step\'s Test first file is th
|
|||
try {
|
||||
addTest(dir); // test written, nothing built yet: red at the start point
|
||||
const plan = planFile(dir);
|
||||
const red = cliPlan(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--plan', plan, '--step', '1', '--json']);
|
||||
const red = cli(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--plan', plan, '--step', '1', '--json']);
|
||||
assert.equal(red.status, 0, red.stdout + red.stderr);
|
||||
assert.equal(JSON.parse(red.stdout).status, 'proven');
|
||||
// Built: the full run proves red, green and the control from the plan's list alone.
|
||||
writeFileSync(join(dir, 'lib', 'sum.mjs'), 'export function sum(a, b) { return a + b; }\n');
|
||||
const full = cliPlan(['--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--plan', plan, '--step', '1', '--json']);
|
||||
const full = cli(['--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--plan', plan, '--step', '1', '--json']);
|
||||
assert.equal(full.status, 0, full.stdout + full.stderr);
|
||||
assert.equal(JSON.parse(full.stdout).status, 'proven');
|
||||
// A step whose plan list is empty still needs --tests.
|
||||
const none = planFile(dir, { testFirst: false }, '1.7');
|
||||
const empty = cliPlan(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--plan', none, '--step', '1', '--json']);
|
||||
const empty = cli(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--plan', none, '--step', '1', '--json']);
|
||||
assert.equal(empty.status, 2, empty.stdout + empty.stderr);
|
||||
assert.match(JSON.parse(empty.stdout).reason, /no test files given/);
|
||||
} finally { rmSync(dir, { recursive: true, force: true }); }
|
||||
|
|
@ -350,8 +345,8 @@ test('red proof --plan/--step: a --tests equal to the plan\'s list (normalised)
|
|||
addTest(dir);
|
||||
const plan = planFile(dir);
|
||||
for (const given of ['tests/sum.test.mjs', './tests/sum.test.mjs']) {
|
||||
const before = cliPlan(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--tests', given, '--json']);
|
||||
const withPlan = cliPlan(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--tests', given, '--plan', plan, '--step', '1', '--json']);
|
||||
const before = cli(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--tests', given, '--json']);
|
||||
const withPlan = cli(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--tests', given, '--plan', plan, '--step', '1', '--json']);
|
||||
assert.equal(withPlan.status, 0, withPlan.stdout + withPlan.stderr);
|
||||
assert.equal(withPlan.status, before.status);
|
||||
const a = JSON.parse(withPlan.stdout);
|
||||
|
|
@ -371,10 +366,10 @@ test('red proof --plan/--step: a 1.8 File line naming a source in its descriptio
|
|||
// point and the start point would go green (not-red-at-base).
|
||||
writeFileSync(join(dir, 'lib', 'sum.mjs'), 'export function sum(a, b) { return a + b; }\n');
|
||||
const plan = planFile(dir, { testFirstRaw: ' - File: `tests/sum.test.mjs` (existing) - covers `lib/sum.mjs`' });
|
||||
const derived = cliPlan(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--plan', plan, '--step', '1', '--json']);
|
||||
const derived = cli(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--plan', plan, '--step', '1', '--json']);
|
||||
assert.equal(derived.status, 0, derived.stdout + derived.stderr);
|
||||
assert.equal(JSON.parse(derived.stdout).status, 'proven');
|
||||
const given = cliPlan(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--tests', 'tests/sum.test.mjs', '--plan', plan, '--step', '1', '--json']);
|
||||
const given = cli(['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--tests', 'tests/sum.test.mjs', '--plan', plan, '--step', '1', '--json']);
|
||||
assert.equal(given.status, 0, given.stdout + given.stderr);
|
||||
assert.doesNotMatch(JSON.parse(given.stdout).reason, /tests-mismatch/);
|
||||
} finally { rmSync(dir, { recursive: true, force: true }); }
|
||||
|
|
@ -390,12 +385,12 @@ test('red proof --plan/--step: an empty plan list (1.7 without Test first, 1.8 F
|
|||
plans.push(readFileSync(planFile(dir, { testFirst: false }, '1.7'), 'utf8'));
|
||||
plans.push(readFileSync(planFile(dir, { testFirstRaw: ' - File: see `tests/sum.test.mjs` for the test' }, '1.8'), 'utf8'));
|
||||
const args = ['--red-only', '--cwd', dir, '--base', base, '--cmd', TEST_CMD, '--tests', 'tests/other.test.mjs', '--json'];
|
||||
const before = cliPlan(args);
|
||||
const before = cli(args);
|
||||
const b = JSON.parse(before.stdout);
|
||||
for (const content of plans) {
|
||||
const p = join(dir, 'plan.local.md');
|
||||
writeFileSync(p, content);
|
||||
const c = cliPlan([...args, '--plan', p, '--step', '1']);
|
||||
const c = cli([...args, '--plan', p, '--step', '1']);
|
||||
assert.equal(c.status, before.status, c.stdout + c.stderr);
|
||||
const out = JSON.parse(c.stdout);
|
||||
assert.doesNotMatch(out.reason, /tests-mismatch/, JSON.stringify(out));
|
||||
|
|
|
|||
|
|
@ -75,14 +75,6 @@ test('a code file beside the test on the File: line still makes the step not tes
|
|||
assert.equal(m.stepTestsOnly(body).testsOnly, false);
|
||||
});
|
||||
|
||||
test('prose: the executor and the step helper name every file on the Test first File: line', () => {
|
||||
const phrase = 'every file on the `Test first:` `File:` line';
|
||||
for (const rel of [['commands', 'trekexecute.md'], ['agents', 'step-implementer.md']]) {
|
||||
const text = readFileSync(join(ROOT, ...rel), 'utf8');
|
||||
assert.ok(text.includes(phrase), `${rel.join('/')} says "${phrase}"`);
|
||||
}
|
||||
});
|
||||
|
||||
// ---- plan_version 1.9 (plan step 5) ----------------------------------------
|
||||
// From 1.9 a `Test first:` `File:` line is every line that opens with `File:`
|
||||
// (after an optional list marker and `**`), backticked or not, and its rest is
|
||||
|
|
|
|||
|
|
@ -498,12 +498,16 @@ test('decision 13 — an engine payload decides the engine: a /security-review p
|
|||
assertAllOutputLegal(result);
|
||||
});
|
||||
|
||||
// A reviewer MISSING_TEST and a marked /code-review MAJOR on the same triplet, in `order`.
|
||||
function reviewerAndMarkedEngine(order, reviewerExtra = {}) {
|
||||
const reviewerPayload = { reviewer: 'code-correctness-reviewer', findings: [reviewerCopy({ rule_key: 'MISSING_TEST', ...reviewerExtra })] };
|
||||
const enginePayload = { reviewer: 'claude-code-review', findings: [codeReviewDefect({ rule_key: 'MISSING_TEST', contradicts_brief: NON_GOAL })] };
|
||||
return order === 'reviewer first' ? [reviewerPayload, enginePayload] : [enginePayload, reviewerPayload];
|
||||
}
|
||||
|
||||
for (const order of ['reviewer first', 'engine first']) {
|
||||
test(`decision 13 — a tie between a marked /code-review MAJOR and a reviewer MAJOR keeps the reviewer (${order}) → WARN`, () => {
|
||||
const reviewerPayload = { reviewer: 'code-correctness-reviewer', findings: [reviewerCopy({ rule_key: 'MISSING_TEST' })] };
|
||||
const enginePayload = { reviewer: 'claude-code-review', findings: [codeReviewDefect({ rule_key: 'MISSING_TEST', contradicts_brief: NON_GOAL })] };
|
||||
const payloads = order === 'reviewer first' ? [reviewerPayload, enginePayload] : [enginePayload, reviewerPayload];
|
||||
const result = m.runContract(payloads);
|
||||
const result = m.runContract(reviewerAndMarkedEngine(order));
|
||||
assert.equal(result.findings.length, 1);
|
||||
const out = result.findings[0];
|
||||
assert.equal(out.severity, 'MAJOR', 'the reviewer agent asserted MAJOR; the engine mark cannot lower it');
|
||||
|
|
@ -516,15 +520,9 @@ for (const order of ['reviewer first', 'engine first']) {
|
|||
});
|
||||
}
|
||||
|
||||
function minorReviewerAndMarkedEngine(order) {
|
||||
const reviewerPayload = { reviewer: 'code-correctness-reviewer', findings: [reviewerCopy({ rule_key: 'MISSING_TEST', severity: 'MINOR' })] };
|
||||
const enginePayload = { reviewer: 'claude-code-review', findings: [codeReviewDefect({ rule_key: 'MISSING_TEST', contradicts_brief: NON_GOAL })] };
|
||||
return order === 'reviewer first' ? [reviewerPayload, enginePayload] : [enginePayload, reviewerPayload];
|
||||
}
|
||||
|
||||
for (const order of ['reviewer first', 'engine first']) {
|
||||
test(`bc7f4e23 — a marked /code-review MAJOR over a reviewer MINOR on the same triplet keeps the reviewer at MAJOR (${order}) → WARN`, () => {
|
||||
const result = m.runContract(minorReviewerAndMarkedEngine(order));
|
||||
const result = m.runContract(reviewerAndMarkedEngine(order, { severity: 'MINOR' }));
|
||||
assert.equal(result.findings.length, 1);
|
||||
const out = result.findings[0];
|
||||
assert.equal('engine' in out, false, 'the survivor is the reviewer finding');
|
||||
|
|
@ -536,7 +534,7 @@ for (const order of ['reviewer first', 'engine first']) {
|
|||
});
|
||||
|
||||
test(`bc7f4e23 — dedupByTriplet(ingest(...)) keeps the reviewer finding at the group's MAJOR over a marked /code-review MAJOR (${order})`, () => {
|
||||
const { findings } = m.ingest(minorReviewerAndMarkedEngine(order));
|
||||
const { findings } = m.ingest(reviewerAndMarkedEngine(order, { severity: 'MINOR' }));
|
||||
const out = m.dedupByTriplet(findings);
|
||||
assert.equal(out.length, 1);
|
||||
assert.equal('engine' in out[0], false, 'the reviewer-agent finding survives the tier tie');
|
||||
|
|
|
|||
|
|
@ -234,6 +234,7 @@ test('1.9: the red proof\'s tests_only reads Files by the grammar (CLI, temp git
|
|||
// no test. Below 1.9 the step check is read as before (decision 16).
|
||||
const planTestFirst19 = (version, line) =>
|
||||
buildPlan({ version, steps: [{ testFirstRaw: line }, { endToEnd: 'bash tests/e2e.sh' }] });
|
||||
const step1Fields = (text) => schema.parseStepFields(text.split('### Step 1:')[1].split('### Step 2:')[0]);
|
||||
|
||||
test('1.9: a Test first whose File: stands mid-line names no test - PLAN_STEP_NO_TEST_FIRST under --strict, a warning under --soft', () => {
|
||||
for (const line of [
|
||||
|
|
@ -242,7 +243,7 @@ test('1.9: a Test first whose File: stands mid-line names no test - PLAN_STEP_NO
|
|||
]) {
|
||||
const text = planTestFirst19('1.9', line);
|
||||
assert.ok(text.includes(`${line}\n`), 'the builder writes the raw File line');
|
||||
assert.deepEqual(schema.testFirstFiles(schema.parseStepFields(text.split('### Step 1:')[1].split('### Step 2:')[0]), '1.9'), [],
|
||||
assert.deepEqual(schema.testFirstFiles(step1Fields(text), '1.9'), [],
|
||||
'the executor reads no test file here');
|
||||
const r = validatePlanContent(text, { strict: true });
|
||||
assert.equal(r.valid, false, `${line}: ${errCodes(r).join(',')}`);
|
||||
|
|
@ -274,7 +275,6 @@ test('below 1.9: the step check reads a mid-line File: as before', () => {
|
|||
// `File:` line names its test (reviews b4fa1927, 4b6460e5) ----
|
||||
|
||||
const WRAPPED = '`tests/a.test.mjs`,\n `src/a.mjs`';
|
||||
const step1Fields = (text) => schema.parseStepFields(text.split('### Step 1:')[1].split('### Step 2:')[0]);
|
||||
|
||||
test('1.9: a Files list wrapped after a comma names both files and no issue', () => {
|
||||
const r = entryPaths(WRAPPED);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue