fix: the trial's mechanical faults -- approval label, import path, stats, annotate path, red kind
voyage-lekerepo end-to-end trial (2026-10-06), report points 8, 10, 12, 15, 16:
- approval-log: «Approve (Recommended)» / «Godkjenn (anbefalt)» is the
approval it labels; the suffix never turns a hollow answer into a yes.
- trekexecute Phase 8: the state-file block imports atomic-write through
${CLAUDE_PLUGIN_ROOT}; a relative import only ran with cwd = plugin root
(the test runs the block from another directory).
- trekreview, trekresearch, trekcontinue: stats go through stats-append like
the other commands, and a failed write is reported, never skipped
silently (closes the open STATE finding for all three). The schema
fixture's stale line refs for these and trekplan are re-measured.
- annotate prints an absolute path, so file://{$ANNOT_HTML} is a live link.
- red-proof: `red_kind` = load-error | test-failure on a proven red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
609eb35015
commit
81dc5af680
9 changed files with 191 additions and 24 deletions
|
|
@ -242,8 +242,15 @@ const HOLLOW = [
|
|||
];
|
||||
|
||||
/** @returns {{ approved: boolean, reason: 'explicit'|'empty'|'hollow'|'not-explicit' }} */
|
||||
// AskUserQuestion's own guidance puts «(Recommended)» after the label it
|
||||
// recommends, so the operator's real «Approve (Recommended)» was refused in the
|
||||
// voyage-lekerepo trial (2026-10-06). The suffix is stripped; what is left must
|
||||
// still be an explicit approval on its own.
|
||||
const RECOMMENDED_SUFFIX = /\s*\((?:recommended|anbefalt)\)\s*$/u;
|
||||
|
||||
export function isExplicitApproval(answer) {
|
||||
const t = String(answer ?? '').trim().toLowerCase().replace(/[\s.!]+$/u, '').replace(/\s+/g, ' ');
|
||||
const t = String(answer ?? '').trim().toLowerCase().replace(/[\s.!]+$/u, '')
|
||||
.replace(RECOMMENDED_SUFFIX, '').replace(/[\s.!]+$/u, '').replace(/\s+/g, ' ');
|
||||
if (!t) return { approved: false, reason: 'empty' };
|
||||
if (EXPLICIT.has(t)) return { approved: true, reason: 'explicit' };
|
||||
if (HOLLOW.some((h) => t === h || (h.includes(' ') && t.includes(h)))) return { approved: false, reason: 'hollow' };
|
||||
|
|
|
|||
|
|
@ -79,12 +79,22 @@ export function repoRoot(dir) {
|
|||
return top.status === 0 ? top.stdout.trim() : null;
|
||||
}
|
||||
|
||||
// A red that is the test file failing to LOAD (an import the start point
|
||||
// lacks) rather than a named test failing. Still a red — the change is what is
|
||||
// missing — but the result says which kind, in a field (`red_kind`), instead
|
||||
// of leaving it to a reader of `tap` (null without a TAP reporter: the
|
||||
// voyage-lekerepo trial's step 1, 2026-10-06).
|
||||
const LOAD_ERROR = /SyntaxError|ERR_MODULE_NOT_FOUND|does not provide an export named|Cannot find module|ERR_REQUIRE_ESM|ModuleNotFoundError|ImportError/;
|
||||
|
||||
function run(cmd, cwd, timeoutMs) {
|
||||
const r = spawnSync('/bin/sh', ['-c', cmd], { cwd, env: commandEnv(), encoding: 'utf8', timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 });
|
||||
const output = `${r.stdout ?? ''}${r.stderr ?? ''}`;
|
||||
return { exit: r.status, signal: r.signal ?? null, tap: tap(output), tail: output.slice(-TAIL), spawnError: r.error ? r.error.message : null };
|
||||
return { exit: r.status, signal: r.signal ?? null, tap: tap(output), tail: output.slice(-TAIL), spawnError: r.error ? r.error.message : null, loadError: LOAD_ERROR.test(output) };
|
||||
}
|
||||
|
||||
/** What the red at the start point was: the test file failing to load, or a test failing. */
|
||||
const redKind = (base) => (base.loadError ? 'load-error' : 'test-failure');
|
||||
|
||||
const notRun = (reason, extra = {}) => ({ status: 'not-run', reason, ...extra });
|
||||
|
||||
// A shell that could not find or execute the command (126/127), a signal (the
|
||||
|
|
@ -188,7 +198,7 @@ export function proveRed(o) {
|
|||
if (baseBroken) return notRun(`at the start point: ${baseBroken}`, { base, result, linked });
|
||||
const out = { base_sha: baseSha, linked, base, result };
|
||||
if (base.exit === 0) return { status: 'rejected', reason: 'not-red-at-base', ...out };
|
||||
if (o.redOnly) return { status: 'proven', reason: 'red at the start point (--red-only: the result was not run)', base_sha: baseSha, linked, base };
|
||||
if (o.redOnly) return { status: 'proven', reason: 'red at the start point (--red-only: the result was not run)', red_kind: redKind(base), base_sha: baseSha, linked, base };
|
||||
if (result.exit !== 0) return { status: 'rejected', reason: 'not-green-at-result', ...out };
|
||||
|
||||
// Control: the same copy with the whole change laid on. Red here means the
|
||||
|
|
@ -198,7 +208,7 @@ export function proveRed(o) {
|
|||
if (control.exit !== 0) {
|
||||
return notRun('the control run (start-point copy + the change) stayed red: the start point failed for an environment reason, not for the missing change', { ...out, control });
|
||||
}
|
||||
return { status: 'proven', reason: 'red at the start point, green at the result, green in the control', ...out, control };
|
||||
return { status: 'proven', reason: 'red at the start point, green at the result, green in the control', red_kind: redKind(base), ...out, control };
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
|
|
@ -234,7 +244,7 @@ export function main(argv) {
|
|||
if (json) process.stdout.write(JSON.stringify(r, null, 2) + '\n');
|
||||
else {
|
||||
const counts = (x) => (x?.tap ? ` (tap: ${x.tap.fail} of ${x.tap.tests} failed)` : '');
|
||||
process.stdout.write(`red proof: ${r.status.toUpperCase()} - ${r.reason}\n`);
|
||||
process.stdout.write(`red proof: ${r.status.toUpperCase()} - ${r.reason}${r.red_kind ? ` (red: ${r.red_kind})` : ''}\n`);
|
||||
if (r.base) process.stdout.write(` start point ${r.base_sha ?? o.base}: exit ${r.base.exit}${counts(r.base)}\n`);
|
||||
if (r.result) process.stdout.write(` result: exit ${r.result.exit}${counts(r.result)}\n`);
|
||||
if (r.control) process.stdout.write(` control: exit ${r.control.exit}${counts(r.control)}\n`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue