From 6c59cd2d33678e57368d9dc280930748b028c1be Mon Sep 17 00:00:00 2001 From: garrytan Date: Mon, 2 Nov 2026 23:26:37 +0000 Subject: [PATCH] fix(review): JSON findings state no Blocks field; the code-class FIXABLE check takes only a Blocks: field (GST-1031 review pass 2) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- lib/review-ledger.ts | 7 +++---- test/review-gst1031.test.ts | 6 +++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/review-ledger.ts b/lib/review-ledger.ts index 9667de4a2..663139421 100644 --- a/lib/review-ledger.ts +++ b/lib/review-ledger.ts @@ -59,7 +59,6 @@ const NUMBERED_HEAD = /^(?:#{1,6}\s+|\*\*\s*)\d+[.)]\s/; const stronger = (a: string, b: string) => ((rank(a) ?? 0) >= (rank(b) ?? 0) ? a : b); /** A prose voice's `Blocks: yes` (GSTA-167 normalizer v2): the finding is CRITICAL whatever its label; `Blocks: no` never lowers one. */ const BLOCKS_YES = /(?]\\s+|\\d+[.)]\\s+)?\\**\\s*Sever const FIELD_LABELS = new RegExp(ANY_LABEL.source, 'gi'); const LEAD_LABEL = new RegExp(`^(?:[^\\p{L}\\p{N}\\s]\\s*|\\d+[.)]\\s*)*(?:\\w[\\w./-]*:\\d+(?:[-–:,]\\s*\\d+)*[^\\p{L}\\p{N}\\s]*\\s+(?:[^\\p{L}\\p{N}\\s]\\s*)*)?(${LABELS})(?=[*_\`\\])]|\\s*[:|—–]|\\s+-\\s|\\s*$|(?<=[A-Z]{3})[\\s,(.])`, 'u'); const STATED_BLOCKS = /(? { const said = [...raw.matchAll(BLOCKS_FIELD)].map(m => m[1]!.toLowerCase()); return said.length > 0 && !said.includes('yes'); }; +export const statesNoBlocks = (raw: string) => { if (raw.startsWith('{')) return false; const said = [...raw.matchAll(BLOCKS_FIELD)].map(m => m[1]!.toLowerCase()); return said.length > 0 && !said.includes('yes'); }; /** * A summary candidate (GST-1031): a prose finding whose every severity word is denied ("so nothing is CRITICAL", "No CRITICAL issues") * or names a fix ("the test covers the CRITICAL fix"), with no lead label, no Severity or Blocks field, no FIXABLE or INVESTIGATE tag, @@ -209,7 +208,7 @@ export function parseFindings(reader: string, output: string, fixableRaises = tr } if (!fresh && !current && (!LIST_ITEM.test(head) || locus)) head = t; } - const unblocked = (f: Finding) => fixableRaises || role !== 'adversarial-native' || !(f.fixable || f.label.toUpperCase() === 'FIXABLE' || /(? fixableRaises || role !== 'adversarial-native' || !(f.fixable || f.label.toUpperCase() === 'FIXABLE' || /(? { const ls = [...raw.matchAll(SEVERITY_FIELD)].flatMap(m => (m[1] ?? m[2]!).match(FIELD_LABELS)!); return ls.length === 1 && !!rank(ls[0]); }; return out.map((f, i) => { diff --git a/test/review-gst1031.test.ts b/test/review-gst1031.test.ts index 5b53fab23..32a5a2412 100644 --- a/test/review-gst1031.test.ts +++ b/test/review-gst1031.test.ts @@ -115,7 +115,11 @@ describe('the blocker count keys on Blocks (GST-1031 case 3)', () => { expect([out, c.lines[0]!.stated?.blocks, ledgerBlockers(path.join(d, 'l.jsonl')).map(b => b.id)]).toEqual([out, undefined, ['outside-voice#1']]); fs.rmSync(d, { recursive: true, force: true }); } - expect(labels('adversarial-native', '**1. Race in a.ts:3. FIXABLE.**\nIt blocks no attacker.\nSeverity: Low.', true)).toEqual(['CRITICAL']); + for (const raises of [true, false]) expect(labels('adversarial-native', '**1. Race in a.ts:3. FIXABLE.**\nIt blocks no attacker.\nSeverity: Low.', raises)).toEqual(['CRITICAL']); + const d = fs.mkdtempSync(path.join(os.tmpdir(), 'gst1031j-')); + collect(path.join(d, 'l.jsonl'), 'specialist', JSON.stringify({ severity: 'CRITICAL', path: 'lib/auth.ts', line: 9, summary: 'the old guard, which a reader marked `Blocks: no`, is gone so any caller gets admin' }) + '\n', path.join(d, 'm.jsonl')); + expect(ledgerBlockers(path.join(d, 'l.jsonl')).map(b => b.id)).toEqual(['specialist#1']); + fs.rmSync(d, { recursive: true, force: true }); }); test('classify and the row count key on Blocks, and a raw holding both Blocks: yes and no blocks', () => { expect(isBlocking({ severity: 'CRITICAL', stated: { severity: 'Critical', blocks: 'no' } })).toBe(false);