docs(changelog): fix round 4.5a

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-24 20:11:22 +02:00
commit e351e7c29d
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
2 changed files with 159 additions and 0 deletions

View file

@ -29,6 +29,76 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
the description of the page and the "revises freehand" loop left the three commands. The call the description of the page and the "revises freehand" loop left the three commands. The call
to `scripts/annotate.mjs`, the not-a-gate line and the printed links stay. The printed report now to `scripts/annotate.mjs`, the not-a-gate line and the printed links stay. The printed report now
says a Question is answered and Fix and Change notes revise the file. says a Question is answered and Fix and Change notes revise the file.
- **`/trekendsession` is operator-only, and the planning and research spawn sites follow the profile.** The model can no longer start `/trekendsession` (`disable-model-invocation: true`). With `--project`, where the resolver runs, `/trekplan` and `/trekresearch` pass the resolved model at their spawn sites, so the `economy` profile gives Sonnet helpers unless the brief's phase signal for that phase names a model; without a resolved model the agent's own `model:` applies (opus for Claude Code's built-in agents), and `/trekexecute`'s step helper and controller stay on opus.
### Fixed
Fix round 4.5a (decisions 57, 67 and 68): the round's 30 findings, where a Voyage control could be bypassed or was too lax, or where the text gave a guarantee no code held. Each is fixed, and each has a test that fails on the code before it, except DJ, whose sentences already stood and whose pins fail when they are deleted (`tests/fix-round-4-5a-red.sh` proves every other id red at the round's start point, and DJ under its two mutations).
**Execute**
- **The merge lock refuses a review of an older commit** (R6). `lib/review/verdict-gate.mjs` takes `--repo <dir>`: an ALLOW or WARN review then opens only when its `scope_sha_end` is HEAD, or an ancestor of HEAD whose later commits (side branches included) change nothing but the review lessons and the `CLAUDE.md` pointer; anything else, a failed git call included, exits 2 with a reason that starts `review-stale` (the new `lib/review/reviewed-commit.mjs`). `/trekexecute` Phase 7.2 and the main-merge lock run the gate with `--repo .`, so the merge stays closed with the decline reason `review-stale`, also on `--resume`.
- **`/trekexecute` runs the brief check it promised** (finding #33). With `--project` and a `brief.md`, Phase 1 runs the brief validator and the phase-model resolver (`lib/profiles/resolver.mjs --resolve-phase-model`) as `/trekplan` does, uses the effort it returns, and halts only on `BRIEF_V51_MISSING_SIGNALS`. Low effort runs the plan as one session, step after step; high effort changes nothing, and the text that set `gates_mode` from the effort and its `gates_mode_flag_override` record are gone.
- **`/trekexecute` names and reads `--profile` and `--gates`** (finding #30). The argument hint, the usage block and Phase 1 carry both flags, Phase 1 strips `--profile <name>` before it reads the plan path, and the text says the profile changes no model here: the step helper and the controller stay on opus.
- **The completion gate says which of its checks are commands and which are the executor's judgement** (finding #28). It no longer calls the counter check and the audit deterministic, and its two dead line references are gone.
- **The final report says whether the app was seen with Claude Code's `run`** (decision 67). A ``**Seen with `run`:**`` line after End to end and a `run_seen` key in the summary JSON; the verdict is still the `End-to-end:` command.
- **The park step no longer leaves an earlier attempt's files in the tree with exit 0** (6.5.0 Known issue: the park step). `lib/util/park-attempt-files.mjs` takes a file registered with `git add --intent-to-add` out of the index and parks it, strips colour codes from the diff and blocks on a non-empty diff with no readable header, reads the paths from the repository's top level whatever directory it runs in, and exits 2 when one of its git calls fails (a non-zero `git cat-file -e` reads as "not in HEAD"), before any file is moved. "Back to planning" saves the upward diff with `--no-color` and `core.quotepath=off`.
**Gates**
- **`--gates closed` turns gating on, and a bare `--gates` no longer swallows the next flag** (finding #1). The shim (`lib/util/autonomy-gate.mjs`) reads bare, `true` and `closed` as on and `false` and `open` as off, takes any other value as a usage error (exit 1), and leaves a following `--flag` in place. Before, only `true` was on.
- **`--gates` means one thing in every command** (finding #1). `/trekbrief`, `/trekresearch`, `/trekplan` and `/trekexecute` share one rule and run the shim once to map the value. `/trekexecute` maps the value, and no phase of it pauses on it; its one stop is the merge into main. The executor strictness table (`adaptive`, `closed`, `open`) left README and `docs/`, and `/trekbrief` no longer promises pauses in the executor.
**Brief**
- **A brief with no research topics ends with its next steps** (finding #47). `/trekbrief` prints the manual path (`/trekplan --project`, then `/trekexecute --project`) and stops instead of entering the auto-only Phase 6, which now runs only after the operator opted into auto mode and Phase 4h approved the intent.
**Plan**
- **"execute" in `/trekplan` no longer builds the plan in the planning session** (finding #52). It prints the `/trekexecute` line and stops, so the approval check, the test lock, the step helpers and the review run; "team" and leaving plan mode do the same.
- **The planning and research spawn sites take the resolved model** (findings #48 and #12). `/trekplan` passes `phase_signal_result.model` at its agent spawns, with the agent's own `model:` as the fallback and opus for the built-in Explore deep-dives; `/trekresearch`'s Phase 5 follow-ups do the same, with opus as the fallback.
- **No turn budget is promised** (finding #57). The sentences that scaled `maxTurns` by codebase size left `/trekplan`, `/trekresearch` and the planning reference document; nothing set such a budget.
**Research**
- **The contrarian gets a conclusion to attack** (finding #113). `/trekresearch` launches `contrarian-researcher` as a second wave after every other agent of wave 1 returns, local ones included, with wave 1's leading answer, the claims for it and the alternatives it dismissed, taken from external findings only and with local parts removed; with `--engine deep-research` it runs after the engine returns.
**Review**
- **Core code outside the old deep-review patterns is now deep-reviewed** (6.5.0 Known issue: trial-5 finding 9). `/trekreview` triage makes deep review the default for every changed file it does not skip or mark summary-only, `CLAUDE.md`, `AGENTS.md` and `.claude/rules/**` included; summary-only is for `docs/**`, root Markdown other than those two, and `tests/fixtures/**`. The review orchestrator, README, the review template and the workflow's fallback say the same.
- **A line past the end of the file no longer refutes a finding** (6.5.0 Known issue: the coordinator's line numbers). The coordinator's Accuracy rule refutes only a citation that escapes the repo root or a negative line; a line past the end of the file goes to `unverified` with the new reason `accuracy:line-out-of-range`: the citation is wrong, the claim is unchecked.
- **Deleting the Declined to Judge copy-through now fails a test** (6.5.0 Known issue: the Declined to Judge copy-through). The sentences in `/trekreview` Phase 6 and in the coordinator's Input and body section 9 that carry each reviewer's Declined to Judge lines into `review.md` are pinned, and the round's red script deletes those sentences and sees the pins fail.
- **Which row a finding joins no longer depends on the order the reviewers' payloads are read** (6.5.0 Known issue: the row order). Inside one file, line and rule key, `dedupByTriplet` groups the findings in two passes, title first and the untitled ones last, each in a fixed order, and a full tie picks its survivor in that order too; the coordinator's Pass 1 text and the workflow's copy follow the same rule. The rows and their `#k` ids still stand in input order.
- **On the `--workflow` path, the severity floor and the put-back match each finding to its own row** (6.5.0 Known issue: the workflow floor and put-back). A returned finding is matched to the row with its triplet and title; with no title match, to the only row on its triplet, else to the strictest one there, never a lower one, and `engine` is read from that row only. Each engine row no returned finding carries is put back at its own severity, one copy per row.
**Continue**
- **`/trekcontinue` runs the arg parser instead of naming a JavaScript function** (finding #66). Phase 0 runs `lib/parsers/arg-parser.mjs --command trekcontinue -- $ARGUMENTS` and reads its JSON; the review orchestrator's parser line also passes its arguments after `--`.
- **`/trekcontinue` finds the master prompt file at the repo root, and the consistency check reads the state file** (finding #102). The master file is looked for at `git rev-parse --show-toplevel`, not an undefined plugin root. The consistency call passes `--state-file` when the project's session state exists, and the validator now takes that value as the flag's own instead of a third path, and is valid only when both prompt files are valid.
**Endsession**
- **`/trekendsession` parses its arguments with the arg parser** (finding #15). A slug or label that holds `-h` is no help request: help comes only on `--help` or a first argument that is exactly `-h`, and Phase 2 reads the brief path and the label from the parser's positionals.
- **Only the operator starts `/trekendsession`** (finding #67). Its frontmatter sets `disable-model-invocation: true`, and CLAUDE.md names it as the one command that does.
**Agents**
- **A reviewer drops a finding only with a trace** (finding #111). code-correctness-reviewer and brief-conformance-reviewer emit an off-tier finding at the catalogue's severity and drop it only when they can say why it is wrong, as one Declined to Judge line with that reason; `voyage:reviewing-adversarially` says the same, and a rule key outside the catalogue is described as routed to `unverified`, as the coordinator contract does.
- **research-scout has the Tavily tools its "Try Tavily first" needs** (finding #24). Its tools now hold `mcp__tavily__tavily_search` and `mcp__tavily__tavily_research`, in docs-researcher's order.
- **code-correctness-reviewer no longer cites a "MANDATORY OVERRIDE rule above" that is not there** (finding #23). The sentence states the rule itself.
**Docs**
- **Nothing sends a reader to run the planning reference document** (findings #2 and #26). `/trekexecute`, `/trekbrief`, brief-reviewer, session-decomposer, `docs/HANDOVER-CONTRACTS.md` and `docs/architecture.md` name `/trekplan` where they named `planning-orchestrator`.
- **The three reference documents hold read-only tools** (finding #26). planning-orchestrator, research-orchestrator and review-orchestrator list only `Read`, `Glob` and `Grep`.
**Stats**
- **No command promises a stats key its record does not write** (finding #46). The "Stats records emit `profile` …" sentences left `/trekbrief`, `/trekplan`, `/trekexecute`, `/trekreview` and `/trekcontinue`; `/trekresearch` keeps only `engine`, which its record writes; `docs/profiles.md`, `docs/operations.md` and README no longer say a profile reaches the stats records.
### Known issues
- **A plan's `profile:` field has no writer.** README, `docs/`, `/trekexecute` and `/trekcontinue` say a plan carries or passes on a profile, but no command writes `profile:` to a plan; the plan validator's `MANIFEST_PROFILE_DRIFT` check reads it, and `resolveTrekcontinueProfile` has no caller outside its tests. Left whole for a later hardening part, which chooses between writing the field and taking the promises out.
### Doors ### Doors
@ -36,6 +106,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- two-way: the coupling to Claude Code's `run` and the built-in owner list (R2) — a revert removes the three sentences and makes `owner=run` an unknown owner again; the criteria runner and `End-to-end:` are unchanged. - two-way: the coupling to Claude Code's `run` and the built-in owner list (R2) — a revert removes the three sentences and makes `owner=run` an unknown owner again; the criteria runner and `End-to-end:` are unchanged.
- two-way: skill annotating and its pointers in three commands (order B) — revert the commit; the - two-way: skill annotating and its pointers in three commands (order B) — revert the commit; the
script, the page, the stored notes and the approval checks are unchanged. script, the page, the stored notes and the approval checks are unchanged.
- two-way: fix round 4.5a (decisions 57, 67, 68) — a revert restores the old texts and checks
## v6.5.0 — 2026-10-18 — Three skills for brief, criteria and review, English labels, and three trial-5 fixes ## v6.5.0 — 2026-10-18 — Three skills for brief, criteria and review, English labels, and three trial-5 fixes

View file

@ -22,6 +22,7 @@ import { tmpdir } from 'node:os';
import { dirname, join, resolve, sep } from 'node:path'; import { dirname, join, resolve, sep } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process'; import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { findSection } from '../../lib/parsers/plan-schema.mjs'; import { findSection } from '../../lib/parsers/plan-schema.mjs';
import { readFrontmatterFields } from '../../lib/skills/skill-frontmatter.mjs'; import { readFrontmatterFields } from '../../lib/skills/skill-frontmatter.mjs';
import { childEnv } from '../helpers/child-env.mjs'; import { childEnv } from '../helpers/child-env.mjs';
@ -1483,3 +1484,90 @@ test('red script: the id list is the round\'s 30 ids, in the brief\'s order', ()
assert.ok(m, `the IDS= line is not one single-quoted list: ${lines[0]}`); assert.ok(m, `the IDS= line is not one single-quoted list: ${lines[0]}`);
assert.deepEqual(m[1].trim().split(/\s+/), RED_IDS); assert.deepEqual(m[1].trim().split(/\s+/), RED_IDS);
}); });
// --- CHANGELOG: what the round fixed ----------------------------------------
// Untagged: the CHANGELOG text is the round's report, not one id's fix. The
// round's section is found by its title, as tests/helpers/changelog-section.mjs
// finds the 6.4.0 delivery's: `## Unreleased — <title>` before the release,
// `## vX.Y.Z — <date> — <title>` after it, exactly once, up to the next `## `
// heading. The released 6.5.0 section must stay byte-equal: its sha256 (the
// heading line through the line before the next `## ` heading, joined with
// `\n`) was computed once from `git show ab6b32c:CHANGELOG.md` with this
// slice; the test reads no git history.
const CHANGELOG = 'CHANGELOG.md';
const ROUND_TITLE = "The annotating skill, the run coupling and P10's model-free part";
const ROUND_HEADING = new RegExp(`^## (?:Unreleased|v\\d+\\.\\d+\\.\\d+ — \\d{4}-\\d{2}-\\d{2}) — ${ROUND_TITLE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`);
const V650_SHA256 = '1df8fd0fe7730204740ca509c8fceb6f9d331859b98ce5c1f8e62b9551ce3274';
/** The CHANGELOG lines from the one heading `isHeading` accepts to the line before the next `## ` heading. */
function changelogPart(isHeading, what) {
const lines = read(CHANGELOG).split('\n');
const at = [];
lines.forEach((l, i) => { if (isHeading(l.replace(/\s+$/, ''))) at.push(i); });
assert.equal(at.length, 1, `${CHANGELOG} has the ${what} section exactly once (found ${at.length})`);
let end = lines.length;
for (let i = at[0] + 1; i < lines.length; i++) if (/^## /.test(lines[i])) { end = i; break; }
return lines.slice(at[0], end).join('\n');
}
const roundPart = () => changelogPart((l) => ROUND_HEADING.test(l), `"${ROUND_TITLE}"`);
/** The `### ` subsection headings of the round's section, in order. */
const roundSubsections = () => roundPart().split('\n').filter((l) => /^### /.test(l)).map((l) => l.replace(/\s+$/, ''));
/** The bullets of one `### ` subsection of the round's section, each flattened to one line. */
function roundBullets(heading) {
const lines = section(roundPart(), heading).split('\n').slice(1);
const out = [];
for (const l of lines) {
if (/^- /.test(l)) out.push(l);
else if (out.length && l.trim() !== '') out[out.length - 1] += ` ${l}`;
}
return out.map(flat);
}
const FIXED_KNOWN_ISSUES = [
['trial-5 finding 9 (core code never deep-reviewed)', /trial-5 finding 9/],
['the park step', /park step/],
['the workflow floor and put-back', /`--workflow`.*floor.*put-back|`--workflow`.*put-back.*floor/],
['the row order (which row a finding joins)', /which row a finding joins/i],
['the coordinator\'s line numbers (a line past the end refuted)', /line (?:number )?past the end of the file.*(?:refuted|refutes)/],
['the Declined to Judge copy-through', /Declined to Judge.*`review\.md`/],
];
test('CHANGELOG: the round\'s section has ### Fixed between ### Changed and ### Doors, and ### Known issues after ### Fixed', () => {
const subs = roundSubsections();
for (const h of ['### Changed', '### Fixed', '### Known issues', '### Doors']) {
assert.equal(subs.filter((s) => s === h).length, 1, `the round's section must have "${h}" once; it has ${JSON.stringify(subs)}`);
}
const at = (h) => subs.indexOf(h);
assert.ok(at('### Changed') < at('### Fixed') && at('### Fixed') < at('### Doors'),
`### Fixed must stand between ### Changed and ### Doors: ${JSON.stringify(subs)}`);
assert.ok(at('### Fixed') < at('### Known issues'), `### Known issues must come after ### Fixed: ${JSON.stringify(subs)}`);
});
test('CHANGELOG: ### Fixed names each of the six 6.5.0 Known issues the round fixed, each as a 6.5.0 Known issue', () => {
const bullets = roundBullets('### Fixed');
const known = bullets.filter((b) => /6\.5\.0 Known issue/.test(b));
const missing = FIXED_KNOWN_ISSUES.filter(([, re]) => !known.some((b) => re.test(b))).map(([name]) => name);
assert.deepEqual(missing, [], `### Fixed names no entry marked "6.5.0 Known issue" for: ${missing.join('; ')}`);
});
test('CHANGELOG: ### Known issues names that a plan\'s profile: field has no writer, and names /trekexecute and /trekcontinue', () => {
const hits = roundBullets('### Known issues').filter((b) => b.includes("**A plan's `profile:` field has no writer.**"));
assert.equal(hits.length, 1, `expected one Known issues line on the plan's profile: field; found ${hits.length}`);
assert.ok(hits[0].includes('`/trekexecute` and `/trekcontinue`'), `the line does not name \`/trekexecute\` and \`/trekcontinue\`: ${hits[0]}`);
});
test('CHANGELOG: ### Doors has the two-way line for fix round 4.5a', () => {
const hits = roundBullets('### Doors').filter((b) => b.startsWith('- two-way: fix round 4.5a'));
assert.deepEqual(hits, ['- two-way: fix round 4.5a (decisions 57, 67, 68) — a revert restores the old texts and checks'],
'the Doors part has no single two-way line for fix round 4.5a');
});
test('CHANGELOG: the released v6.5.0 section is byte-equal to its text at ab6b32c (sha256)', () => {
const part = changelogPart((l) => l.startsWith('## v6.5.0 — '), 'v6.5.0');
assert.equal(createHash('sha256').update(part, 'utf8').digest('hex'), V650_SHA256,
'the released v6.5.0 section changed; edit only the round\'s section');
});