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

@ -268,6 +268,13 @@ const SHELL_OPERATORS = /[;&|<>]|\$\(|`/;
// Read-only git subcommands: they report, they do not write a repository.
const READONLY_GIT = new Set(['status', 'log', 'diff', 'show', 'ls-files']);
// A word /bin/sh hands to git unchanged (refs like HEAD~1 and HEAD^, formats like %h, paths).
const GIT_LITERAL_WORD = /^[A-Za-z0-9_@%^~:.,=+/-]+$/;
// `--output`, `--output=<file>`, or an abbreviation of it git may accept.
function writesOutput(word) {
const name = word.split('=')[0];
return name.length >= 5 && '--output'.startsWith(name);
}
const deny = (reason) => ({ allowed: false, reason });
const permit = () => ({ allowed: true, reason: '' });
@ -317,9 +324,15 @@ const RUNNERS = {
'/bin/bash': (t) => (isTestScript(t[1]) ? permit() : deny('/bin/bash, but not a script under tests/')),
git: (t) => {
if (!READONLY_GIT.has(t[1])) return deny(`git, but not a read-only subcommand first (${[...READONLY_GIT].join('|')})`);
// `--output` makes diff/log/show write their output to a file (release
// review of 6.3.0, 2026-10-10: a plan's check could overwrite any file).
if (t.some((w) => w === '--output' || w.startsWith('--output='))) return deny('git with --output writes a file, so it is not a read-only check');
// The command runs through /bin/sh, which unquotes and expands what it is
// given, so a check on the words here holds only for words the shell passes
// through unchanged: no quote, backslash, `$`, glob or brace. Then
// `--output` (and an abbreviation of it) is refused: it makes diff/log/show
// write to a file (release review of 6.3.0, 2026-10-10: a plan's check
// could overwrite any file, first plainly, then through quoting).
const odd = t.find((w) => !GIT_LITERAL_WORD.test(w));
if (odd !== undefined) return deny(`git with a word the shell would rewrite (${odd}): only plain words are checked`);
if (t.some(writesOutput)) return deny('git with --output writes a file, so it is not a read-only check');
return permit();
},
};