1. A step of only tests can never be proven red. One reading, stepTestsOnly (lib/parsers/plan-schema.mjs, test paths from the new lib/util/test-path.mjs): the plan validator warns PLAN_STEP_TESTS_ONLY, red-proof --plan/--step adds tests_only to a not-red-at-base, and /trekexecute and the step helper send such a step straight up to the plan instead of into fix rounds that cannot succeed. /trekplan, the plan template and plan-critic (dimension 8, still 12) say the test belongs in the step that builds the behaviour. 2. Timing tests under load: the hook helper's kill limit is 30 s and per call overridable; SC #14 passes on the fastest of up to three runs against the same 800 ms limit (tests/helpers/timing.mjs). 3. The example plan's On failure lines open with a K11 word. 4. /trekreview saves the reviewer's whole answer for the findings check and names FINDING_PROSE_LINE_OUT_OF_RANGE (error: re-ask; warning: report). 5. The criteria runner's commands run without NODE_TEST_CONTEXT (commandEnv, moved to lib/util/command-env.mjs). 6. The resolver returns effort only when the brief's phase signal sets one; the four commands said it always did. Also: the K2 hook tests used fixed session ids in a per-user temp dir, so two suites running at once took each other's approval records; the ids are now unique per run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
131 lines
6.9 KiB
JavaScript
131 lines
6.9 KiB
JavaScript
// tests/lib/retterunde-prose-pins.test.mjs
|
|
// Prose pins for the fix round before 6.3.0 (order 10) whose fix is text a
|
|
// session reads. Counted as doc pins by lib/util/test-census.mjs (the file name).
|
|
// 1b A step of only tests can never be proven red: the plan text, the
|
|
// template and plan-critic say the test belongs in the step that builds
|
|
// the behaviour (voyage-lekerepo trial 3, 2026-10-09, finding 1).
|
|
// 1c The executor and the step helper pass `--plan`/`--step` to red-proof
|
|
// and send a `tests_only` `not-red-at-base` up to the plan, not into fix
|
|
// rounds that cannot succeed.
|
|
// 4 /trekreview names FINDING_PROSE_LINE_OUT_OF_RANGE and hands the check
|
|
// the reviewer's whole answer, not only its JSON block.
|
|
// 6 The resolver returns `effort` only when the brief's phase signal sets
|
|
// one; no command says it always does.
|
|
|
|
import { test } from 'node:test';
|
|
import { strict as assert } from 'node:assert';
|
|
import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } 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 heading's section (level from its #-count), up to the next one outside a fence. */
|
|
function section(text, heading) {
|
|
const level = heading.match(/^#+/)[0].length;
|
|
const s = findSection(text, heading.replace(/^#+ /, ''), level);
|
|
assert.ok(s, `missing ${heading}`);
|
|
return s.text;
|
|
}
|
|
|
|
const RULE = /test\s+for\s+a\s+behaviou?r\s+belongs\s+in\s+the\s+step\s+that\s+builds\s+it/i;
|
|
|
|
// --- 1b --------------------------------------------------------------------------
|
|
|
|
test('1b: /trekplan says the test belongs in the step that builds the behaviour, and names the warning', () => {
|
|
const plan = read('commands/trekplan.md');
|
|
assert.match(plan, RULE);
|
|
// At the vertical-slice rule and at the step field list.
|
|
assert.match(plan, /Steps are vertical slices[\s\S]{0,900}only tests[\s\S]{0,300}never be proven red/i);
|
|
assert.match(plan, /\*\*Test first:\*\* — [^\n]*\n[^\n]*step that builds/i);
|
|
assert.match(plan, /PLAN_STEP_TESTS_ONLY/);
|
|
});
|
|
|
|
test('1b: the plan template carries the same rule', () => {
|
|
const t = read('templates/plan-template.md');
|
|
assert.match(t, RULE);
|
|
assert.match(t, /only tests[\s\S]{0,200}never be proven red/i);
|
|
});
|
|
|
|
test('1b: plan-critic has it as a finding under Verification gaps, and still 12 dimensions', () => {
|
|
const critic = read('agents/plan-critic.md');
|
|
const d8 = section(critic, '### 8. Verification gaps');
|
|
assert.match(d8, /PLAN_STEP_TESTS_ONLY/);
|
|
assert.match(d8, /only tests[\s\S]{0,400}\*\*major\*\*/i);
|
|
assert.equal((critic.match(/^### \d+\. /gm) ?? []).length, 12);
|
|
});
|
|
|
|
// --- 1c --------------------------------------------------------------------------
|
|
|
|
test('1c: the executor runs red-proof with --plan/--step and sends a tests_only not-red-at-base up to the plan', () => {
|
|
const ex = read('commands/trekexecute.md');
|
|
assert.match(ex, /red-proof\.mjs --base \{start_sha\} --cmd "\{Verify command\}" --tests [^\n]*--plan "\{plan_path\}" --step \{N\}[^\n]*--json/);
|
|
assert.match(ex, /`not-red-at-base`, `tests_only: true`[^\n]*straight up to the plan \(Sub-step F, `fix_rounds = 4`, stop class 4\)/);
|
|
// The helper's gate, as the executor describes it, carries the same flags.
|
|
assert.match(ex, /red-proof\.mjs --red-only --base \{start_sha\} [^\n]*--plan "\{plan_path\}" --step \{N\}/);
|
|
// The other not-red-at-base keeps its fix round.
|
|
assert.match(ex, /`not-red-at-base`, otherwise[^\n]*finding for Sub-step F/);
|
|
});
|
|
|
|
test('1c: the step helper passes --plan/--step to its red gate and does not rewrite a tests_only test', () => {
|
|
const h = read('agents/step-implementer.md');
|
|
assert.match(h, /red-proof\.mjs --red-only [^\n]*--plan "\{plan\}" --step \{step\}/);
|
|
assert.match(h, /`tests_only: true`[\s\S]{0,300}do not rewrite/i);
|
|
});
|
|
|
|
// --- 4 ---------------------------------------------------------------------------
|
|
|
|
test('4: /trekreview names FINDING_PROSE_LINE_OUT_OF_RANGE: re-ask on the error, report the warning', () => {
|
|
const r = read('commands/trekreview.md');
|
|
assert.match(r, /FINDING_PROSE_LINE_OUT_OF_RANGE/);
|
|
assert.match(r, /FINDING_PROSE_LINE_OUT_OF_RANGE[\s\S]{0,600}re-ask/i);
|
|
assert.match(r, /FINDING_PROSE_LINE_OUT_OF_RANGE[\s\S]{0,900}warning[\s\S]{0,300}report/i);
|
|
});
|
|
|
|
test('4: /trekreview saves the reviewer\'s whole answer for the check, not only the JSON block', () => {
|
|
const r = read('commands/trekreview.md');
|
|
assert.match(r, /\{project_dir\}\/\.review-out-\{reviewer\}\.md/);
|
|
assert.match(r, /whole answer[\s\S]{0,300}not only the\s+(JSON\s+)?block/i);
|
|
assert.match(r, /findings-schema\.mjs --json --repo-root [^\n]*"\{project_dir\}\/\.review-out-\{reviewer\}\.md"/);
|
|
assert.doesNotMatch(r, /Collect each reviewer's trailing JSON block/);
|
|
});
|
|
|
|
test('4: what the check is given decides: the whole answer warns on a prose citation, the block alone cannot', () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'retterunde-review-'));
|
|
try {
|
|
mkdirSync(join(dir, 'src'));
|
|
writeFileSync(join(dir, 'src', 'a.mjs'), 'export const a = 1;\n');
|
|
const block = '```json\n{"reviewer":"code-correctness-reviewer","findings":[]}\n```\n';
|
|
writeFileSync(join(dir, 'whole.md'), `The bug is at \`src/a.mjs:40\`.\n\n${block}`);
|
|
writeFileSync(join(dir, 'block.md'), block);
|
|
const cli = (f) => spawnSync(process.execPath,
|
|
[join(ROOT, 'lib', 'review', 'findings-schema.mjs'), '--json', '--repo-root', dir, join(dir, f)], { encoding: 'utf8' });
|
|
const whole = JSON.parse(cli('whole.md').stdout);
|
|
assert.deepEqual(whole.warnings.map((w) => w.code), ['FINDING_PROSE_LINE_OUT_OF_RANGE']);
|
|
assert.deepEqual(JSON.parse(cli('block.md').stdout).warnings, []);
|
|
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
});
|
|
|
|
// --- 6 ---------------------------------------------------------------------------
|
|
|
|
const RESOLVER_COMMANDS = ['trekplan', 'trekexecute', 'trekresearch', 'trekreview'];
|
|
|
|
test('6: no command says the resolver always returns {effort, model, source}', () => {
|
|
for (const f of readdirSync(join(ROOT, 'commands')).filter((x) => x.endsWith('.md'))) {
|
|
const t = read(`commands/${f}`);
|
|
assert.doesNotMatch(t, /\{effort, model, source\}/, f);
|
|
}
|
|
});
|
|
|
|
test('6: each resolver-using command says `effort` comes only from the brief signal, else standard', () => {
|
|
for (const c of RESOLVER_COMMANDS) {
|
|
const t = read(`commands/${c}.md`);
|
|
const hits = t.match(/\{model, source\}[^\n]*\n?[^\n]*effort[^\n]*only when the brief's phase signal sets one/g) ?? [];
|
|
assert.ok(hits.length >= 1, `${c}: no statement that effort is only present with a brief signal`);
|
|
assert.match(t, /without it[^\n]*\n?[^\n]*`?standard`?/i, c);
|
|
}
|
|
});
|