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

@ -102,7 +102,10 @@ export function buildCorpus(entries) {
const df = new Map();
for (const tf of docs.values()) for (const term of tf.keys()) df.set(term, (df.get(term) || 0) + 1);
const n = docs.size;
return { docs, idf: (term) => Math.log(1 + n / (1 + (df.get(term) || 0))), entries };
const idf = (term) => Math.log(1 + n / (1 + (df.get(term) || 0)));
// The corpus never changes: its vectors are built once, not per prompt or pair.
const vecs = new Map([...docs].map(([id, tf]) => [id, vec(tf, idf)]));
return { docs, idf, vecs, entries };
}
function vec(tf, idf) {
@ -122,7 +125,7 @@ function cosine(a, b) {
export function rank(prompt, corpus) {
const pv = vec(termFreq(tokenize(prompt)), corpus.idf);
const out = [];
for (const [id, tf] of corpus.docs) out.push({ id, score: cosine(pv, vec(tf, corpus.idf)) });
for (const [id, v] of corpus.vecs) out.push({ id, score: cosine(pv, v) });
return out.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
}
@ -132,7 +135,7 @@ export function collisions(corpus, t = THRESHOLDS) {
const out = [];
for (let i = 0; i < ids.length; i++) {
for (let j = i + 1; j < ids.length; j++) {
const sim = cosine(vec(corpus.docs.get(ids[i]), corpus.idf), vec(corpus.docs.get(ids[j]), corpus.idf));
const sim = cosine(corpus.vecs.get(ids[i]), corpus.vecs.get(ids[j]));
if (sim >= t.COLLISION_ERROR) out.push({ a: ids[i], b: ids[j], sim, level: 'error' });
else if (sim >= t.COLLISION_WARN) out.push({ a: ids[i], b: ids[j], sim, level: 'warn' });
}
@ -205,8 +208,11 @@ export function loadCases(root) {
export function checkRouting(root, t = THRESHOLDS) {
const entries = loadEntries(root);
const corpus = buildCorpus(entries);
const byName = new Map(entries.map((e) => [e.name, e]));
const skills = entries.filter((e) => e.kind === 'skill');
// A skill is looked up among skills only: an agent or command with the same
// name must not take its place. An owner may be any kind; a skill wins a tie.
const skillByName = new Map(skills.map((e) => [e.name, e]));
const byName = new Map([...entries.filter((e) => e.kind !== 'skill'), ...skills].map((e) => [e.name, e]));
const cases = loadCases(root);
const errors = [];
const warnings = [];
@ -220,7 +226,7 @@ export function checkRouting(root, t = THRESHOLDS) {
for (const c of cases) {
if (!c.from) errors.push({ code: 'ROUTING_CASE_NO_SOURCE', case: c.case, message: `${c.case}: tag the event the case came from (from=...)` });
const self = c.skill && byName.get(c.skill);
const self = c.skill && skillByName.get(c.skill);
if (!self || self.kind !== 'skill') {
errors.push({ code: 'ROUTING_UNKNOWN_SKILL', case: c.case, message: `${c.case}: skill=${c.skill} is not a skill in skills/` });
continue;