fix: round 2 of the release review of 6.3.0

Round 2 (v6.2.0..0d044f6, BLOCK: 2 BLOCKER, 11 MAJOR, 1 MINOR), each fixed
behind a test that failed first.

- The git runner checks only words /bin/sh passes through unchanged (no
  quote, backslash, $, glob or brace) and refuses --output and its
  abbreviations: round 1's --output refusal was bypassed by quoting.
- fieldPaths: backticked paths are kept as written, parentheses and all; in
  plain text every path-like word counts, and a parenthesised note does not.
- plan-validator --steps-from reads the plan's ## Verification for an
  appended End-to-end step, and warns instead of failing under --soft.
- regression-check prints progress_status, the value progress.json takes;
  /trekexecute copies it.
- release-check: a plugin in a subdirectory is checked in its own directory,
  the suite is not run when a cheaper check already makes the answer exit 2,
  and a bad argument or a crash is exit 2.
- Skill routing looks a skill up among skills only, and builds the corpus
  vectors once.
- /trekexecute Phase 7.2 says plainly that remediation steps in the review
  template's own form stop with a re-plan; making them runnable is a later
  order (operator 2026-10-10).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-10 22:43:38 +02:00
commit ed87cc668d
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
10 changed files with 263 additions and 70 deletions

View file

@ -285,22 +285,30 @@ export function onFailureAction(text) {
// ---- a step that holds nothing but tests -----------------------------------
/**
* The paths a field names. Each entry (comma- or line-separated, a sub-bullet's
* `-` dropped) gives its backticked spans, or else its first word: a plain
* path beside a backticked one is still a path (independent review, order 10).
* A parenthesised note is not an entry: "`a.test.mjs` (new: covers x, y)"
* names one path, not "y)" too (release review of 6.3.0, 2026-10-10).
* The paths a field names, in order. A backticked span is a path as written,
* parentheses and all (`app/(auth)/x.sh`). In the plain text around the spans
* a parenthesised note is not a path ("(new: covers x, y)"), and every word
* that looks like a path is one ("tests/a.test.mjs + src/b.mjs" names both);
* an entry (comma- or line-separated) with no such word gives its first word,
* so a bare `Makefile` is still a path. Release review of 6.3.0, rounds 1-2.
*/
const PATH_WORD = /^(?:[\w@.-]+\/)+[\w@.-]+$|^[\w@-][\w@.-]*\.[A-Za-z0-9]+$/;
function fieldPaths(text) {
const spans = [];
const masked = String(text ?? '').replace(/`([^`\n]+)`/g, (_, p) => `\u0000${spans.push(p.trim()) - 1}\u0000`);
const out = [];
for (const raw of String(text ?? '').replace(/\([^()\n]*\)/g, ' ').split(/[,\n]/)) {
for (const raw of masked.replace(/\([^()\n]*\)/g, ' ').split(/[,\n]/)) {
const entry = raw.trim().replace(/^[-*]\s+/, '');
if (entry === '') continue;
const spans = [...entry.matchAll(/`([^`\n]+)`/g)].map((m) => m[1].trim());
if (spans.length > 0) out.push(...spans);
else out.push(entry.split(/\s+/)[0]);
const found = [];
for (const word of entry.split(/\s+/)) {
const span = /^\u0000(\d+)\u0000$/.exec(word);
if (span) found.push(spans[Number(span[1])]);
else if (PATH_WORD.test(word.replace(/[.;:]+$/, ''))) found.push(word.replace(/[.;:]+$/, ''));
}
out.push(...(found.length > 0 ? found : [entry.split(/\s+/)[0]]));
}
return out.filter(Boolean);
return out.filter((p) => p && !p.includes('\u0000'));
}
// A document is never the test here: `docs/specs/api.md` sits under a `specs/`