fix: the findings of the release review of 6.3.0

The review of v6.2.0..f669b87 (BLOCK: 1 BLOCKER, 12 MAJOR, 1 MINOR), each
fixed behind a test that failed first, or, for the two missing tests, a test
shown to fail on a stub of the branch it covers.

- Review steps appended by /trekexecute Phase 7.2 are held to the step rules
  on any plan version: plan-validator --steps-from <N> (on a plan below 1.8 a
  template review step validated and was executed).
- The guard refuses where no one can be asked also when its rules will not
  load; the wave merge path names that refusal instead of MERGE_REFUSED.
  Tests for the classifier-error refusal and post-approval-log's own check.
- release-check: the version files may change only in their version after
  the review (JSON compared without it), README diff lines are read inside
  hunks only, an unreadable diff is exit 2, and npm test runs without
  NODE_TEST_CONTEXT.
- What counts as a step of only tests: a note in parentheses is not a path,
  and a data file under specs/ is not a test. End-to-end: rides on the last
  step that builds behaviour (validator hint, trekplan, template).
- A removed line is cited as <rev>:<path>:<line> (findings-schema hint,
  trekreview re-ask).
- skill-lint counts a trailing newline as no line; regression-check takes
  commandEnv from lib/util/command-env.mjs; CLAUDE.md names four mode-read
  exemptions.
- /security-review, older than v6.2.0: the criteria runner refuses git
  --output, which writes a file.

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

View file

@ -109,11 +109,12 @@ test('review 8: a command nobody was asked about is not logged, even if it reads
// Operator decision 2026-10-09: where no person can be asked, the guard refuses
// a merge into main or a push (exit 2) instead of asking, so the call never runs
// and nothing reaches the approval log. post-approval-log's own check of the
// recorded mode stays as a second line.
// recorded mode stays as a second line (its own test below).
const NO_PROMPT_MODE = 'bypassPermissions';
test('uncertain (no-prompt mode): the push to main is refused, and no approval is logged', async () => {
const plugin = pluginCopy({ 'lib/util/approval-log.mjs': 'stub' });
const repo = makeRepo('main');
const base = { session_id: `s-b-${RUN}`, tool_use_id: 'toolu_b', cwd: repo, tool_name: 'Bash', tool_input: { command: 'git push origin main' }, permission_mode: 'bypassPermissions' };
const base = { session_id: `s-b-${RUN}`, tool_use_id: 'toolu_b', cwd: repo, tool_name: 'Bash', tool_input: { command: 'git push origin main' }, permission_mode: NO_PROMPT_MODE };
const pre = await runHook(join(plugin, 'hooks', 'scripts', 'pre-bash-executor.mjs'), { ...base, hook_event_name: 'PreToolUse' });
assert.equal(pre.code, 2, `refused, not asked: ${JSON.stringify(pre)}`);
const log = join(tmp(), 'stub.jsonl');
@ -121,6 +122,25 @@ test('uncertain (no-prompt mode): the push to main is refused, and no approval i
assert.equal(readStubLog(log).length, 0);
});
// The second line on its own (release review of 6.3.0, 2026-10-10): a record
// that says no person was asked is never logged, whoever wrote it.
test('uncertain (no-prompt mode): a recorded question no one saw is not logged', async () => {
const plugin = pluginCopy({ 'lib/util/approval-log.mjs': 'stub' });
const repo = makeRepo('main');
const { writeApprovalRecord } = await import(join(plugin, 'hooks', 'lib', 'approval-record.mjs'));
const ids = { session_id: `s-r-${RUN}`, tool_use_id: 'toolu_r' };
assert.ok(writeApprovalRecord(ids.session_id, ids.tool_use_id, {
command: 'git push origin main', permission_mode: NO_PROMPT_MODE,
matches: [{ kind: 'merge', name: 'push to main', subject: repo }], asked_at: new Date().toISOString(),
}), 'the record is written');
const log = join(tmp(), 'stub.jsonl');
const r = await runHookWithEnv(join(plugin, 'hooks', 'scripts', 'post-approval-log.mjs'), {
...ids, hook_event_name: 'PostToolUse', cwd: repo, tool_name: 'Bash', tool_input: { command: 'git push origin main' },
}, { STUB_LOG: log });
assert.match(parseJsonOut(r.stdout).hookSpecificOutput.additionalContext, /not logged/i);
assert.equal(readStubLog(log).length, 0);
});
// 9. Classification
const kinds = (cmd, branch = 'feat', cwd) => classifyGitApproval(cmd, { branch, cwd }).map((m) => m.kind);
for (const cmd of ['git tag -v v1', 'git tag --verify v1', 'git push -n', 'git push --dry-run origin main']) {