merge: main (7a skills workshop, 9 rettepakke, 7b researching-sources, 8 release runbook) into wave2-kobling

Union lines resolved: CLAUDE.md command table keeps main's /trekresearch
and /trekplan rows and this branch's /trekexecute row. README's
architecture block now counts the three skills main ships (it said 2
after 7b added researching-sources).

Contracts run against the real ones on the merged tree: K10/K11 (plan
validator refuses a piped Regression:, warns on an unknown On failure:;
regression-check passes/fails/absent/unrunnable), S2 (skills/test-first
and skills/debugging exist, voyage:<name> form as 7a chose), K7 (real
review_version 1.1 WARN -> 0, a BLOCK copy -> 1, a 1.0 review with no
verdict -> 2). npm test: 2102 / 2100 pass / 0 fail / 2 skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-09 21:25:18 +02:00
commit 3c18fdb5f7
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
83 changed files with 4293 additions and 71 deletions

View file

@ -22,6 +22,14 @@
// criteria runner's allowlist, and each `End-to-end:` command also stands
// under `## Verification`, where Phase 7 runs it.
// Strict mode reports these as errors; --soft downgrades them to warnings.
//
// Every plan version (the fields are new; a plan without them is unchanged):
// - K10: the optional step field `Regression:` holds ONE command the
// criteria runner's allowlist runs (first backticked span, no pipe, no
// `&&`); the executor runs it green before the step's checkpoint. One that
// can never run is an error (a warning under --soft).
// - K11: `On failure:` opens with revert | retry | skip | escalate, the words
// /trekexecute reads; any other opening is a warning.
import { readFileSync, existsSync, realpathSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
@ -29,6 +37,7 @@ import {
sliceSteps, validatePlanHeadings, extractPlanVersion, extractPlanCeremony,
parseStepFields, findSection, splitSections, findModuleCycle, parseMermaid, fencedBlocks,
versionAtLeast, PLAN_VERSION_CURRENT, PLAN_VERSIONS_SUPPORTED, CEREMONY_VALUES,
ON_FAILURE_ACTIONS, onFailureAction,
} from '../parsers/plan-schema.mjs';
import { validateAllManifests } from '../parsers/manifest-yaml.mjs';
import { parseDocument } from '../util/frontmatter.mjs';
@ -125,6 +134,36 @@ function checkRunnable(text, sections, opts) {
return out;
}
/**
* K10 and K11. `Verify:` is both the step's one test command and its proof of
* red, so a step whose red test is a shell script could not also run the frozen
* suite before its checkpoint; the plan wrote "run npm test before the
* checkpoint" in prose that nothing enforced (voyage-lekerepo trial 2,
* 2026-10-08). `Regression:` is that command, checked like `Verify:`.
*/
function checkRecovery(sections, opts) {
const errors = [];
const warnings = [];
for (const s of sections) {
const f = parseStepFields(s.body);
if (f.has('Regression')) {
const r = runnableCheck(firstCommand(f.get('Regression').text), opts);
if (!r.runnable) {
errors.push(issue('PLAN_STEP_REGRESSION_NOT_RUNNABLE',
`Step ${s.n}: \`Regression:\` is not one runnable command (${r.reason})`,
'Backtick ONE command the criteria runner allows, unpiped and without `&&` (`npm test`); ' +
`the executor runs it green before the checkpoint. ${WAY_IN}`));
}
}
if (f.has('On failure') && onFailureAction(f.get('On failure').text) === null) {
warnings.push(issue('PLAN_STEP_ON_FAILURE_UNKNOWN',
`Step ${s.n}: \`On failure:\` does not open with a recovery word the executor reads`,
`Open with one of ${ON_FAILURE_ACTIONS.join(' | ')}, then the specifics (\`retry once, then escalate\`).`));
}
}
return { errors, warnings };
}
/** Table body rows (skips the header and the |---| separator). */
function tableRows(text) {
const rows = text.split('\n').filter(l => l.trim().startsWith('|'));
@ -230,6 +269,11 @@ export function validatePlanContent(rawText, opts = {}) {
warnings.push(issue('PLAN_VERSION_MISMATCH', `plan_version=${planVersion}, current target is ${PLAN_VERSION_CURRENT}`));
}
const recovery = checkRecovery(sections, { cwd: opts.cwd });
if (strict) errors.push(...recovery.errors);
else warnings.push(...recovery.errors);
warnings.push(...recovery.warnings);
const ceremony = extractPlanCeremony(text);
if (versionAtLeast(planVersion, '1.8')) {
const v18 = [...checkSteps(sections), ...checkRunnable(text, sections, { cwd: opts.cwd })];