feat(hooks): the merge question says how the run was tested and how it is undone

This commit is contained in:
Kjell Tore Guttormsen 2026-11-02 21:23:24 +01:00
commit ec5b6483ec
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
10 changed files with 1371 additions and 6 deletions

View file

@ -32,6 +32,10 @@
// DPR /trekreview Phase 4 says a changed instruction file at any depth,
// `docs/**` included, gets deep review, after the triage table and
// with its two pinned rows untouched.
// MEV /trekexecute Phase 8 writes the merge evidence file in a Bash call of
// its own before the merge call, and goes on when it is not written;
// docs/HANDOVER-CONTRACTS.md documents the file as internal: its path
// under the git common dir, never tracked, not part of Handover 5.
// The helpers below are copied, never imported from an earlier prose test.
// Fix the SOURCE a pin guards, not the pin.
@ -395,3 +399,47 @@ test('[DPR] /trekreview Phase 4 says a changed instruction file at any depth, do
assert.ok(lines.includes('| `summary-only` | `docs/**`, Markdown files at the repo root other than `CLAUDE.md` and `AGENTS.md` (README, CHANGELOG), `tests/fixtures/**`. |'),
'Control: the summary-only row is unchanged');
});
// --- MEV: the merge question's evidence -------------------------------------------
const PHASE_8 = '## Phase 8 — Final report';
const EVIDENCE_CMD = 'node ${CLAUDE_PLUGIN_ROOT}/lib/review/merge-evidence.mjs write --repo . --branch "{run branch}" --project "{project_dir}" --json';
test('[MEV] Phase 8 writes the evidence file before the merge call', () => {
const p8 = section(read(EXEC_CMD), PHASE_8);
const open = p8.indexOf('**2. Open: the merge, and the one question.**');
assert.ok(open >= 0, 'Phase 8 has its "2. Open" part');
const lines = p8.split('\n');
const at = lines.findIndex((l) => l.trim() === EVIDENCE_CMD);
assert.ok(at >= 0, `Phase 8 runs the evidence writer: ${EVIDENCE_CMD}`);
// A Bash call of its own: the command is the one line of its fenced block.
assert.match(lines[at - 1].trim(), /^```bash$/, 'the writer opens its own bash block');
assert.equal(lines[at + 1].trim(), '```', 'the writer is the only line of its block');
const writer = p8.indexOf(EVIDENCE_CMD);
assert.ok(writer > open, 'the writer runs in "2. Open"');
const merges = [p8.indexOf('merge --no-ff "{run branch}"'), p8.indexOf('git switch "{main}"')];
for (const m of merges) assert.ok(m > writer, 'the writer runs before each merge block');
assert.ok(sentences(section(read(EXEC_CMD), PHASE_8)).some((s) => s.includes('On `"written": false` (exit 1), print its `reason` in one line and continue')
&& /no run evidence/.test(s)), 'a refused write is said in one line and the run goes on; the question then says "no run evidence"');
// Control: the merge blocks are unchanged.
assert.ok(p8.includes(' if git -C "{main worktree path}" merge --no-ff "{run branch}" -m "merge: trekexecute {slug}"; then\n echo "MERGED"'),
'Control: the worktree merge block is unchanged');
assert.ok(p8.includes(' git switch "{main}" || { echo "MERGE_REFUSED"; exit 1; }\n if git merge --no-ff "{run branch}" -m "merge: trekexecute {slug}"; then'),
'Control: the switch merge block is unchanged');
});
test('[MEV] docs/HANDOVER-CONTRACTS.md documents the evidence file as internal', () => {
const text = read(CONTRACTS);
const note = section(text, '## Internal files');
const f = flat(note);
assert.ok(f.includes('`<git common dir>/voyage/merge-evidence/<branch head sha>.json`'), 'the note names the path under the git common dir');
assert.match(f, /never tracked/, 'never tracked');
assert.match(f, /never published/, 'never published');
assert.match(f, /not part of Handover 5/, 'not part of Handover 5');
for (const field of ['`v`', '`branch`', '`branch_head_sha`', '`written_at`', '`end_to_end`', '`verification`', '`review`', '`door`']) {
assert.ok(f.includes(field), `the note names the field ${field}`);
}
assert.ok(f.includes('lib/review/merge-evidence.mjs') && f.includes('hooks/lib/merge-evidence.mjs'), 'the note names the writer and the reader');
// Control: Handover 5 does not name the file.
assert.doesNotMatch(section(text, '## Handover 5'), /merge-evidence/, 'Control: Handover 5 is unchanged');
});