fix(review): a past-the-end line makes a finding unverified, never refuted, and the Declined to Judge copy-through is pinned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-23 17:13:03 +02:00
commit 7175783ce5
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
4 changed files with 87 additions and 6 deletions

View file

@ -1323,3 +1323,62 @@ test('[F9] sibling: templates/trekreview-template.md', () => {
assert.ok(cov.includes('| lib/foo.mjs | deep-review | default for changed code |'), 'the template has no deep-review default row');
assert.ok(cov.includes('| docs/guide.md | summary-only | docs/** is summary-only |'), 'the template has no docs summary-only row');
});
// --- CL: a line number alone never refutes a finding ------------------------
// Pass 2's Accuracy row dropped a finding as refuted (`accuracy:refuted`) for an
// "impossibly large line number", so a finding whose claim was never checked
// weighed nothing and could not hold ALLOW back. Only a citation that escapes
// the repo root or a negative line refutes; a line past the end of the file
// makes the finding `unverified` (`accuracy:line-out-of-range`): the citation
// is wrong, the claim is unchecked. The lib side is pinned in
// tests/lib/coordinator-contract.test.mjs.
//
// --- DJ: the Declined to Judge copy-through is held -------------------------
// A reviewer's own `### Declined to Judge` lines reach review.md only through
// three sentences: /trekreview Phase 6 passes them to the coordinator, the
// coordinator's Input names them, and body section 9 copies them. Each is
// pinned by its own flattened regex. These pass at ab6b32c by design (no text
// changed); the round's red script proves them red by deleting the sentences.
const COORD = 'agents/review-coordinator.md';
/** The one Pass 2 table row whose first cell is `Accuracy`. */
function accuracyRow() {
const p2 = section(read(COORD), '### Pass 2 — HubSpot Judge filters (3 criteria)');
const hits = p2.split('\n').filter((l) => /^\| Accuracy \|/.test(l));
assert.equal(hits.length, 1, `expected exactly one Accuracy row in Pass 2; found ${hits.length}`);
return hits[0];
}
test('[CL] the Accuracy row refutes only a root escape or a negative line; a line past the end is unverified (accuracy:line-out-of-range)', () => {
const row = accuracyRow();
assert.match(row, /dropped|accuracy:refuted/, 'control: the Accuracy row still drops something as refuted');
for (const phrase of [/impossibly large line number/, /≤ file line count/]) {
assert.doesNotMatch(row, phrase, `the Accuracy row still pairs ${phrase} with dropped / accuracy:refuted`);
}
assert.match(row, /Path traversal escape or negative line \| \*\*dropped\*\* \(`accuracy:refuted`\)/,
'the Accuracy row does not drop exactly a root escape or a negative line as refuted');
assert.match(row, /A line past the end of the file[^|]*the citation is wrong, the claim is unchecked[^|]*`unverified` \(`accuracy:line-out-of-range`\)/,
'the Accuracy row does not send a line past the end of the file to unverified (accuracy:line-out-of-range)');
const why = flat(section(read(COORD), '### Pass 2 — HubSpot Judge filters (3 criteria)'));
assert.match(why, /Accuracy does: a citation that escapes the repo root, or a negative line, refutes the finding as a claim about this codebase\. A line past the end of the file does not: the citation is wrong, the claim is unchecked\./,
'the paragraph after the table still lets a line number alone refute');
});
test('[DJ] /trekreview Phase 6 passes each reviewer\'s Declined to Judge lines verbatim for the coordinator to copy', () => {
const p6 = flat(section(read(REVIEW), '## Phase 6 — Coordinator dedup + verdict'));
assert.match(p6, /pass each reviewer's `### Declined to Judge` lines verbatim from its saved `\{project_dir\}\/\.review-out-\{reviewer\}\.md`: the coordinator copies them into `## Declined to Judge`, so nothing a reviewer set aside disappears\./,
'Phase 6 no longer hands the reviewers\' Declined to Judge lines to the coordinator');
});
test('[DJ] review-coordinator Input: the reviewers\' Declined to Judge lines are copied into body section 9, not ruled on', () => {
const input = flat(section(read(COORD), '## Input'));
assert.match(input, /\*\*Reviewers' Declined to Judge lines\*\* — each reviewer's own `### Declined to Judge` list, verbatim\. You copy them into `## Declined to Judge` \(body section 9\); you do not rule on them\./,
'the coordinator\'s Input no longer names the reviewers\' Declined to Judge lines to copy');
});
test('[DJ] review-coordinator body section 9 copies every reviewer Declined to Judge line as {reviewer}: {line}, outside the JSON array and the verdict', () => {
const body = flat(section(read(COORD), '### Body sections (in order)'));
assert.match(body, /then every line a reviewer wrote under its own `### Declined to Judge`, copied as `\{reviewer\}: \{line\}` \(they are not findings: they stay out of the JSON `declined_to_judge` array and do not change the verdict\)/,
'body section 9 no longer copies the reviewers\' Declined to Judge lines');
});