fix(execute): the Phase 8 payload goes on stdin; /trekreview findings closed
/trekreview of 5680591..84a0621 (verdict WARN: 0 BLOCKER, 3 MAJOR, 1 MINOR,
1 MAJOR suppressed as unverified for length only). All closed red -> green:
- Suppressed MAJOR 1d0171a7 (verified live): Phase 8 spliced {plan-path}
into a single-quoted --payload. A lone quote broke the shell parse (event
lost, no written:false to report); a quoted pair ran a command
substitution and still reported written:true. event-emit now reads
`--payload -` from stdin and the three Phase 8 blocks pass the payload
through a quoted heredoc. Checked under bash and zsh.
- 570ba19b: the SC5 set-step extractor read line by line and never saw the
wrapped Step 0 sentinel (`blocked`); it now matches across the break and
the test asserts `blocked` is read.
- c69a865a: the Phase 9 line_ref in jsonl-schemas.md was 9 lines off. Both
trekexecute rows are now pinned to the exact span by a test.
- 0004fa23 (MINOR): the gates_mode_flag_override block says what to do on
written:false (print the reason, continue).
- emit() docstring no longer says failures are swallowed silently.
- 7ffaede5: chosen to KEEP the prose-only legacy `passed` tolerance, because
a public-plugin run started under the 6.1.0 prose would otherwise re-run
every finished step on resume; recorded as an accepted deviation in the
(local-only) brief. No code change.
Suite 1400 (1398/0/2), was 1393.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
84a0621fcc
commit
cb68c4f125
7 changed files with 130 additions and 28 deletions
|
|
@ -2067,6 +2067,38 @@ test('order fields: the trekbrief-stats row\'s line_ref still points at the Phas
|
|||
assert.ok(at >= Number(m[1]) && at <= Number(m[2]), `line_ref ${ref} does not contain the stats-append line (line ${at})`);
|
||||
});
|
||||
|
||||
/** The jsonl-schemas fixture row whose schema_id cell is `id`, split into cells. */
|
||||
function schemaRow(id) {
|
||||
const row = read('tests/fixtures/jsonl-schemas.md').split('\n').find((l) => l.startsWith(`| ${id} |`));
|
||||
assert.ok(row, `jsonl-schemas.md is missing the \`${id}\` row`);
|
||||
return row.split('|').map((c) => c.trim());
|
||||
}
|
||||
|
||||
// The Phase 9 line_ref was measured, then more prose landed above Phase 9 and
|
||||
// nothing re-measured it. Pinned to the span it names: heading to closing fence.
|
||||
test('the trekexecute Phase 9 row\'s line_ref spans the Phase 9 heading to the end of its stdin block', () => {
|
||||
const ref = schemaRow('trekexecute-stats (Phase 9 record)')[4];
|
||||
const m = ref.match(/^trekexecute\.md:(\d+)-(\d+)$/);
|
||||
assert.ok(m, `line_ref "${ref}" is not trekexecute.md:A-B`);
|
||||
const lines = read('commands/trekexecute.md').split('\n');
|
||||
const heading = lines.findIndex((l) => /^## Phase 9\b/.test(l)) + 1;
|
||||
const append = lines.findIndex((l) => /^\s*node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/stats\/stats-append\.mjs trekexecute\b/.test(l)) + 1;
|
||||
const fence = lines.findIndex((l, i) => i >= append && l.trim() === '```') + 1;
|
||||
assert.ok(heading > 0 && append > heading && fence > append, 'Phase 9 heading, stats-append line and closing fence');
|
||||
assert.equal(ref, `trekexecute.md:${heading}-${fence}`);
|
||||
});
|
||||
|
||||
test('the autonomy-events row\'s line_ref spans emit() in event-emit.mjs', () => {
|
||||
const ref = schemaRow('trekexecute-stats (autonomy events)')[4];
|
||||
const m = ref.match(/^event-emit\.mjs:(\d+)-(\d+)$/);
|
||||
assert.ok(m, `line_ref "${ref}" is not event-emit.mjs:A-B`);
|
||||
const lines = read('lib/stats/event-emit.mjs').split('\n');
|
||||
const start = lines.findIndex((l) => l.startsWith('export function emit(')) + 1;
|
||||
const end = lines.findIndex((l, i) => i >= start && l === '}') + 1;
|
||||
assert.ok(start > 0 && end > start, 'emit() and its closing brace');
|
||||
assert.equal(ref, `event-emit.mjs:${start}-${end}`);
|
||||
});
|
||||
|
||||
test('order fields: docs/architecture.md Stats names them and the order-id join', () => {
|
||||
const text = read('docs/architecture.md');
|
||||
const stats = text.slice(text.indexOf('\nStats:'), text.indexOf('\n## Terminology'));
|
||||
|
|
|
|||
|
|
@ -198,6 +198,42 @@ test('CLI shim with malformed --payload returns reason payload-not-json (exit 1)
|
|||
}
|
||||
});
|
||||
|
||||
// `--payload -` reads the payload from stdin, so the command prose can pass it
|
||||
// through a quoted heredoc: the shell expands nothing, and a plan path with a
|
||||
// quote character can no longer close a single-quoted argument.
|
||||
test('CLI shim with --payload - reads the payload from stdin, quote characters and all', () => {
|
||||
const home = tmp();
|
||||
try {
|
||||
const plan = ".claude/projects/it's a'$(touch PWNED)'b/plan.md";
|
||||
const r = spawnSync(process.execPath, [SHIM, '--event', 'main-merge-gate', '--payload', '-'],
|
||||
{ encoding: 'utf-8', env: isolatedEnv(home), input: JSON.stringify({ plan, wave_count: 1 }) });
|
||||
assert.equal(r.status, 0, r.stdout + r.stderr);
|
||||
const [rec] = records(join(home, DATA_REL, 'trekexecute-stats.jsonl'));
|
||||
assert.deepEqual(rec.payload, { plan, wave_count: 1 });
|
||||
} finally {
|
||||
rmSync(home, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('CLI shim with --payload - and a stdin that is not JSON returns payload-not-json (exit 1)', () => {
|
||||
const home = tmp();
|
||||
try {
|
||||
const r = spawnSync(process.execPath, [SHIM, '--event', 'main-merge-gate', '--payload', '-'],
|
||||
{ encoding: 'utf-8', env: isolatedEnv(home), input: 'not-json{{' });
|
||||
assert.equal(r.status, 1, r.stdout + r.stderr);
|
||||
assert.equal(JSON.parse(r.stdout.trim()).reason, 'payload-not-json');
|
||||
assert.ok(!existsSync(join(home, DATA_REL, 'trekexecute-stats.jsonl')));
|
||||
} finally {
|
||||
rmSync(home, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// The emit() docstring said failures are "swallowed silently" after the contract
|
||||
// became {written:false, reason}.
|
||||
test('emit() is not documented as swallowing failures silently', () => {
|
||||
assert.doesNotMatch(readFileSync(SHIM, 'utf-8'), /swallowed silently/);
|
||||
});
|
||||
|
||||
test('CLI: with CLAUDE_PLUGIN_DATA empty the event lands under HOME', () => {
|
||||
const home = tmp();
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -357,12 +357,13 @@ function fencedBlocksWith(sec, needle) {
|
|||
}
|
||||
|
||||
/** Fill the prose placeholders an executor fills in, then run the block. */
|
||||
function runBlock(block, home) {
|
||||
function runBlock(block, home, { planPath = '.claude/projects/2026-10-05-x/plan.md', cwd } = {}) {
|
||||
const cmd = block.split('${CLAUDE_PLUGIN_ROOT}').join(ROOT)
|
||||
.split('{plan-path}').join('.claude/projects/2026-10-05-x/plan.md')
|
||||
.split('{plan-path}').join(planPath)
|
||||
.split('{wave-count}').join('1');
|
||||
return spawnSync('bash', ['-c', cmd], {
|
||||
encoding: 'utf8',
|
||||
cwd,
|
||||
env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home },
|
||||
});
|
||||
}
|
||||
|
|
@ -409,6 +410,31 @@ test('Phase 8: each event block, run as written, lands one known event', () => {
|
|||
}
|
||||
});
|
||||
|
||||
// The plan path is operator-chosen (positional [plan.md], --project <dir>). Spliced
|
||||
// into a single-quoted argument, a lone quote broke the shell parse (event lost,
|
||||
// no written:false to report) and a quoted pair let a command substitution run.
|
||||
test('Phase 8: a plan path with a quote character lands literally and the shell runs nothing in it', () => {
|
||||
const blocks = fencedBlocksWith(section(TE, 'Phase 8'), 'event-emit.mjs');
|
||||
assert.equal(blocks.length, 3, 'main-merge-gate, main-merge-approved, main-merge-declined');
|
||||
for (const plan of [".claude/projects/2026-10-05-it's/plan.md", ".claude/projects/a'$(touch PWNED)'b/plan.md"]) {
|
||||
for (const block of blocks) {
|
||||
const home = mkdtempSync(join(tmpdir(), 'stats-landing-'));
|
||||
try {
|
||||
const r = runBlock(block, home, { planPath: plan, cwd: home });
|
||||
assert.ok(!existsSync(join(home, 'PWNED')), `the shell ran a command from the plan path:\n${block}`);
|
||||
assert.equal(r.status, 0, `${block}\n${r.stdout}${r.stderr}`);
|
||||
const [rec] = records(join(home, DATA_REL, 'trekexecute-stats.jsonl'));
|
||||
assert.equal(rec.payload.plan, plan);
|
||||
} finally { rmSync(home, { recursive: true, force: true }); }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('gates_mode_flag_override: a written:false is printed in one line and the run continues', () => {
|
||||
const flat = section(TE, 'Composition rule (v5.1)').replace(/\s+/g, ' ');
|
||||
assert.match(flat, /On `"written": false` \(exit 1\), print its `reason` in one line and continue/);
|
||||
});
|
||||
|
||||
test('Phase 8: a written:false is printed in one line and the run continues', () => {
|
||||
const sec = section(TE, 'Phase 8');
|
||||
const flat = sec.replace(/\s+/g, ' ');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue