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", () => {