From 5affaaf7346b8ca4fe2f996aff792ffb743135a0 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 19:52:55 -0700 Subject: [PATCH 1/3] fix(gsd): allow milestone completion when validation skipped by preference The completing-milestone dispatch guard blocked completion when operational verification was planned but the validation was intentionally skipped by a budget profile preference. The guard now detects skip-by-preference markers in the validation content and allows completion to proceed. Closes #3399 Closes #3344 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-dispatch.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/auto-dispatch.ts b/src/resources/extensions/gsd/auto-dispatch.ts index ccc3921e6..6c738df9e 100644 --- a/src/resources/extensions/gsd/auto-dispatch.ts +++ b/src/resources/extensions/gsd/auto-dispatch.ts @@ -693,6 +693,10 @@ export const DISPATCH_RULES: DispatchRule[] = [ if (validationPath) { const validationContent = await loadFile(validationPath); if (validationContent) { + // Allow completion when validation was intentionally skipped by + // preference/budget profile (#3399, #3344). + const skippedByPreference = /skip(?:ped)?[\s\-]+(?:by|per|due to)\s+(?:preference|budget|profile)/i.test(validationContent); + // Accept either the structured template format (table with MET/N/A/SATISFIED) // or prose evidence patterns the validation agent may emit. const structuredMatch = @@ -700,7 +704,7 @@ export const DISPATCH_RULES: DispatchRule[] = [ (validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED")); const proseMatch = /[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; + const hasOperationalCheck = skippedByPreference || structuredMatch || proseMatch; if (!hasOperationalCheck) { return { action: "stop" as const, From ef68e37e7959fd792663c9cb57771bf54ae8de75 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:26:33 -0700 Subject: [PATCH 2/3] test: add regression test for skipped validation completion Co-Authored-By: Claude Opus 4.6 (1M context) --- .../skipped-validation-completion.test.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts diff --git a/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts b/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts new file mode 100644 index 000000000..ddf4b9054 --- /dev/null +++ b/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts @@ -0,0 +1,39 @@ +/** + * Regression test for #3698 — allow milestone completion when validation + * was skipped by preference + * + * When validation is skipped due to user preference (e.g. budget profile), + * auto-dispatch should recognize the "skipped by preference" pattern and + * allow completion instead of treating it as a missing validation. + */ + +import { describe, test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const autoDispatchSrc = readFileSync( + join(__dirname, '..', 'auto-dispatch.ts'), + 'utf-8', +); + +describe('skipped validation completion (#3698)', () => { + test('skippedByPreference regex detection exists', () => { + assert.match(autoDispatchSrc, /skippedByPreference/, + 'skippedByPreference variable should exist in auto-dispatch.ts'); + }); + + test('regex matches skip-by-preference patterns', () => { + assert.match(autoDispatchSrc, /skip(?:ped)?\[\\s\\-\]\+(?:by\|per\|due to)/, + 'should have regex matching "skipped by/per/due to" patterns'); + }); + + test('skippedByPreference feeds into operational check', () => { + assert.match(autoDispatchSrc, /hasOperationalCheck\s*=\s*skippedByPreference/, + 'skippedByPreference should be part of hasOperationalCheck'); + }); +}); From fa344e79a0629ddad65b0a530f079cbc08832585 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:55:55 -0700 Subject: [PATCH 3/3] fix(test): escape regex metacharacters in skip-by-preference pattern test The test regex used unescaped (?:...) groups which were interpreted as regex syntax instead of matching the literal source text. Escape parens to match the actual regex pattern in the source code. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../extensions/gsd/tests/skipped-validation-completion.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts b/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts index ddf4b9054..927eb3a57 100644 --- a/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +++ b/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts @@ -28,7 +28,7 @@ describe('skipped validation completion (#3698)', () => { }); test('regex matches skip-by-preference patterns', () => { - assert.match(autoDispatchSrc, /skip(?:ped)?\[\\s\\-\]\+(?:by\|per\|due to)/, + assert.match(autoDispatchSrc, /skip\(\?:ped\)\?\[\\s\\-\]\+\(\?:by\|per\|due to\)/, 'should have regex matching "skipped by/per/due to" patterns'); });