From 79da90edde99c808256ab4f74524a88d4bc5d3e1 Mon Sep 17 00:00:00 2001 From: NilsR0711 <115017963+NilsR0711@users.noreply.github.com> Date: Sat, 28 Mar 2026 01:10:10 +0100 Subject: [PATCH] fix(auto-dispatch): widen operational verification gate regex (fixes #2866) (#2898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three defects in the completing-milestone dispatch guard caused false positive blocks on valid validation output: 1. Single-line constraint: [^\n]* stopped at newlines, missing verdicts on subsequent lines. Fixed with [\s\S]{0,500}? (bounded lazy match). 2. Missing keywords: 'satisfied' and 'partially' were absent from the alternation. LLMs commonly write 'PARTIALLY SATISFIED' or 'FULLY SATISFIED'. Added both. 3. Markdown bold delimiters: **Operational** blocked [\s:] after the word. The new [\s\S] class handles any character including *. Also adds SATISFIED to the structuredMatch includes check, and ✅ to the prose regex (overlaps with #2862). Includes 8 regression test cases covering multi-line formats, satisfied keyword variants, markdown bold tables, and checkmark emoji. --- src/resources/extensions/gsd/auto-dispatch.ts | 6 +-- .../tests/validation-gate-patterns.test.ts | 46 ++++++++++++++++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/resources/extensions/gsd/auto-dispatch.ts b/src/resources/extensions/gsd/auto-dispatch.ts index 59fb2ac19..91918938f 100644 --- a/src/resources/extensions/gsd/auto-dispatch.ts +++ b/src/resources/extensions/gsd/auto-dispatch.ts @@ -677,13 +677,13 @@ export const DISPATCH_RULES: DispatchRule[] = [ if (validationPath) { const validationContent = await loadFile(validationPath); if (validationContent) { - // Accept either the structured template format (table with MET/N/A) + // Accept either the structured template format (table with MET/N/A/SATISFIED) // or prose evidence patterns the validation agent may emit. const structuredMatch = validationContent.includes("Operational") && - (validationContent.includes("MET") || validationContent.includes("N/A")); + (validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED")); const proseMatch = - /[Oo]perational[\s:][^\n]*(?:pass|verified|confirmed|met|complete|true|yes|addressed|covered|n\/a|not\s+applicable)/i.test(validationContent); + /[Oo]perational[\s\S]{0,500}?(?:✅|pass|verified|confirmed|met|complete|true|yes|addressed|covered|satisfied|partially|n\/a|not[\s-]+applicable)/i.test(validationContent); const hasOperationalCheck = structuredMatch || proseMatch; if (!hasOperationalCheck) { return { diff --git a/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts b/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts index 56b46b799..722c4701f 100644 --- a/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +++ b/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts @@ -23,9 +23,9 @@ import assert from "node:assert/strict"; function hasOperationalEvidence(validationContent: string): boolean { const structuredMatch = validationContent.includes("Operational") && - (validationContent.includes("MET") || validationContent.includes("N/A")); + (validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED")); const proseMatch = - /[Oo]perational[\s:][^\n]*(?:pass|verified|confirmed|met|complete|true|yes|addressed|covered|n\/a|not\s+applicable)/i.test( + /[Oo]perational[\s\S]{0,500}?(?:✅|pass|verified|confirmed|met|complete|true|yes|addressed|covered|satisfied|partially|n\/a|not[\s-]+applicable)/i.test( validationContent, ); return structuredMatch || proseMatch; @@ -104,6 +104,48 @@ test('prose: "Operational: complete" passes', () => { assert.ok(hasOperationalEvidence(content)); }); +// ─── Issue #2862: checkmark emoji ──────────────────────────────────────────── + +test('prose: "Operational: ✅" checkmark emoji passes (issue #2862)', () => { + const content = `- **Operational:** ✅ DECISIONS.md documents D009-D013`; + assert.ok(hasOperationalEvidence(content)); +}); + +// ─── Issue #2866: multi-line, "satisfied", markdown bold ───────────────────── + +test('multi-line: verdict on next line after Operational heading passes (issue #2866)', () => { + const content = `### Operational Verification +All endpoints responsive. Health checks pass.`; + assert.ok(hasOperationalEvidence(content)); +}); + +test('prose: "PARTIALLY SATISFIED" passes (issue #2866)', () => { + const content = `Operational class: ⚠️ PARTIALLY SATISFIED`; + assert.ok(hasOperationalEvidence(content)); +}); + +test('prose: "FULLY SATISFIED" passes (issue #2866)', () => { + const content = `**Operational**: FULLY SATISFIED — all monitoring in place.`; + assert.ok(hasOperationalEvidence(content)); +}); + +test('structured: Operational + SATISFIED passes (issue #2866)', () => { + const content = `| Criteria | Status | +| Operational | SATISFIED |`; + assert.ok(hasOperationalEvidence(content)); +}); + +test('table with markdown bold: **Operational** passes (issue #2866)', () => { + const content = `| **Operational** | ⚠️ Partially satisfied — monitoring gap noted |`; + assert.ok(hasOperationalEvidence(content)); +}); + +test('multi-line: Operational label and "confirmed" separated by line break passes (issue #2866)', () => { + const content = `## Operational +Smoke tests confirmed all services healthy after deploy.`; + assert.ok(hasOperationalEvidence(content)); +}); + // ─── Rejection cases ───────────────────────────────────────────────────────── test("no operational evidence: unrelated content fails", () => {