From 8f2e120a29ed405b7649ba24c28806a7ceca5d99 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 19:04:25 -0700 Subject: [PATCH] fix(gsd): tighten verifyExpectedArtifact to prevent rogue-write false positives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes to fail-closed when gsd_complete_task didn't actually run: 1. Legacy branch: require checked checkbox (- [x] **T01:) instead of accepting heading-style matches that only prove the task was planned 2. No plan file: return false instead of falling through 3. DB available but task row missing: return false instead of treating as verified — if the DB is up and the task isn't there, the completion tool never ran Closes #3607 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-recovery.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/resources/extensions/gsd/auto-recovery.ts b/src/resources/extensions/gsd/auto-recovery.ts index 86c9d70be..1fabee1a1 100644 --- a/src/resources/extensions/gsd/auto-recovery.ts +++ b/src/resources/extensions/gsd/auto-recovery.ts @@ -286,7 +286,7 @@ export function verifyExpectedArtifact( if (!hasCheckboxTask && !hasHeadingTask) return false; } - // execute-task: DB status is authoritative. Fall back to heading-style plan + // execute-task: DB status is authoritative. Fall back to checked-checkbox // detection when the DB is unavailable (unmigrated projects). if (unitType === "execute-task") { const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId); @@ -297,20 +297,22 @@ export function verifyExpectedArtifact( if (dbTask.status !== "complete" && dbTask.status !== "done") return false; } else if (!isDbAvailable()) { // LEGACY: Pre-migration fallback for projects without DB. - // Fall back to plan heading check (format detection, not reconciliation). - // Heading-style entries (### T01 --) count as verified because the - // summary file existence (checked above) is the real signal. + // Require a CHECKED checkbox — a bare heading or unchecked checkbox + // does not prove gsd_complete_task ran. Summary file on disk alone + // is not sufficient evidence (could be a rogue write) (#3607). const planAbs = resolveSliceFile(base, mid, sid, "PLAN"); if (planAbs && existsSync(planAbs)) { const planContent = readFileSync(planAbs, "utf-8"); const escapedTid = tid.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const hdRe = new RegExp(`^#{2,4}\\s+${escapedTid}\\s*(?:--|—|:)`, "m"); const cbRe = new RegExp(`^- \\[[xX]\\] \\*\\*${escapedTid}:`, "m"); - if (!hdRe.test(planContent) && !cbRe.test(planContent)) return false; + if (!cbRe.test(planContent)) return false; + } else { + return false; // no plan file → cannot verify } + } else { + // DB available but task row not found — completion tool never ran (#3607) + return false; } - // else: DB available but task not found — summary file exists (checked above), - // so treat as verified (task may not be imported yet) } }