From b116bb07b96a85590af4dd8e68a69b5e64144bda Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 18 May 2026 22:54:23 -0400 Subject: [PATCH] feat(sdk): flag bracketed-placeholder result values (#3184) Cycle 13 of ~15: 'result: [pending]' (template placeholder copy-pasted without being filled) was previously dropped by the \w+ regex. Now flagged as BRACKETED_PLACEHOLDER. De-conflicted against ORPHAN_ITEM_MISSING_RESULT so an item with a bracketed result doesn't double-report. --- sdk/src/query/phase-uat-passed.test.ts | 29 ++++++++++++++++++++++++++ sdk/src/query/phase-uat-passed.ts | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/sdk/src/query/phase-uat-passed.test.ts b/sdk/src/query/phase-uat-passed.test.ts index 19cc73cbd..0de42700c 100644 --- a/sdk/src/query/phase-uat-passed.test.ts +++ b/sdk/src/query/phase-uat-passed.test.ts @@ -306,6 +306,35 @@ result: pass } }); + it("emits BRACKETED_PLACEHOLDER reason when result value is wrapped in brackets", async () => { + const localTmp = await mkdtemp(join(tmpdir(), 'gsd-uat-c13-')); + try { + const phaseDir = join(localTmp, '.planning', 'phases', '05-bracketed-placeholder'); + await mkdir(phaseDir, { recursive: true }); + const content = `--- +status: complete +phase: 5 +source: roadmap +started: 2026-05-18T00:00:00Z +updated: 2026-05-18T00:00:00Z +--- + +### 1. Forgot to fill in result +expected: thing +result: [pending] +`; + await writeFile(join(phaseDir, '05-HUMAN-UAT.md'), content); + + const result = await isPhaseUatPassed(localTmp, '5'); + expect(result.passed).toBe(false); + expect(result.items.length).toBe(0); + expect(result.reasons.length).toBe(1); + expect(result.reasons[0].code).toBe(REASON_CODE.BRACKETED_PLACEHOLDER); + } finally { + await rm(localTmp, { recursive: true, force: true }); + } + }); + it("emits ORPHAN_ITEM_MISSING_RESULT reason for headings missing the result field", async () => { const localTmp = await mkdtemp(join(tmpdir(), 'gsd-uat-c12-')); try { diff --git a/sdk/src/query/phase-uat-passed.ts b/sdk/src/query/phase-uat-passed.ts index 645b10ce2..af563f5d7 100644 --- a/sdk/src/query/phase-uat-passed.ts +++ b/sdk/src/query/phase-uat-passed.ts @@ -170,7 +170,7 @@ export async function isPhaseUatPassed( while ((bm = BRACK_RESULT_PATTERN.exec(strippedBody)) !== null) { // find nearest preceding heading const before = strippedBody.slice(0, bm.index); - const headingMatch = before.match(/###\s*(\d+)\.\s*([^\n]+)\s*$/); + const headingMatch = before.match(/###\s*(\d+)\.\s*([^\n]+)\s*$/m); if (headingMatch) { const num = parseInt(headingMatch[1], 10); const name = headingMatch[2].trim();