From e5330ee08263f547ac9d8e5cd22b19d8c107d5e6 Mon Sep 17 00:00:00 2001 From: mastertyko <11311479+mastertyko@users.noreply.github.com> Date: Wed, 25 Mar 2026 21:19:00 +0100 Subject: [PATCH] fix(auto): skip CONTEXT-DRAFT warning for completed/parked milestones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-flight milestone queue check in auto-start warns about every CONTEXT-DRAFT.md it finds, regardless of milestone status. A completed milestone with a leftover CONTEXT-DRAFT.md triggers a spurious warning on every session start — noise with no actionable meaning. Add a status guard that skips completed and parked milestones before checking for CONTEXT-DRAFT files. When the DB is unavailable, fall back to the existing warn-on-all behavior (safe default). Closes #2473 --- src/resources/extensions/gsd/auto-start.ts | 8 +- .../preflight-context-draft-filter.test.ts | 115 ++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/resources/extensions/gsd/tests/preflight-context-draft-filter.test.ts diff --git a/src/resources/extensions/gsd/auto-start.ts b/src/resources/extensions/gsd/auto-start.ts index 655c0d69e..e47dc5069 100644 --- a/src/resources/extensions/gsd/auto-start.ts +++ b/src/resources/extensions/gsd/auto-start.ts @@ -58,7 +58,7 @@ import { initRoutingHistory } from "./routing-history.js"; import { restoreHookState, resetHookState } from "./post-unit-hooks.js"; import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js"; import { snapshotSkills } from "./skill-discovery.js"; -import { isDbAvailable } from "./gsd-db.js"; +import { isDbAvailable, getMilestone } from "./gsd-db.js"; import { hideFooter } from "./auto-dashboard.js"; import { debugLog, @@ -683,6 +683,12 @@ export async function bootstrapAutoSession( if (milestoneIds.length > 1) { const issues: string[] = []; for (const id of milestoneIds) { + // Skip completed/parked milestones — a leftover CONTEXT-DRAFT.md + // on a finished milestone is harmless residue, not an actionable warning. + if (isDbAvailable()) { + const ms = getMilestone(id); + if (ms?.status === "complete" || ms?.status === "parked") continue; + } const draft = resolveMilestoneFile(base, id, "CONTEXT-DRAFT"); if (draft) issues.push( diff --git a/src/resources/extensions/gsd/tests/preflight-context-draft-filter.test.ts b/src/resources/extensions/gsd/tests/preflight-context-draft-filter.test.ts new file mode 100644 index 000000000..6c1e59b67 --- /dev/null +++ b/src/resources/extensions/gsd/tests/preflight-context-draft-filter.test.ts @@ -0,0 +1,115 @@ +/** + * Regression test for #2473: Pre-flight CONTEXT-DRAFT warning should skip + * completed and parked milestones. + * + * The pre-flight loop in auto-start.ts warns about CONTEXT-DRAFT.md files + * so the user knows which milestones will pause for discussion. But completed + * milestones with leftover CONTEXT-DRAFT.md files are not actionable — the + * warning is noise. + * + * This test exercises the filtering logic directly: given a set of milestones + * with CONTEXT-DRAFT files, only active/pending ones should produce warnings. + */ +import { describe, test, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; + +import { + openDatabase, + closeDatabase, + isDbAvailable, + insertMilestone, + getMilestone, +} from "../gsd-db.ts"; +import { resolveMilestoneFile } from "../paths.ts"; + +describe("pre-flight CONTEXT-DRAFT filter (#2473)", () => { + let tmpBase: string; + let gsd: string; + + beforeEach(() => { + tmpBase = mkdtempSync(join(tmpdir(), "gsd-preflight-draft-")); + gsd = join(tmpBase, ".gsd"); + + // Create milestone directories with CONTEXT-DRAFT files + for (const id of ["M001", "M002", "M003"]) { + const msDir = join(gsd, "milestones", id); + mkdirSync(msDir, { recursive: true }); + writeFileSync(join(msDir, `${id}-CONTEXT-DRAFT.md`), `# ${id}: Draft\n`); + } + + // Open DB and insert milestones with different statuses + const dbPath = join(gsd, "gsd.db"); + openDatabase(dbPath); + insertMilestone({ id: "M001", title: "Complete milestone", status: "complete" }); + insertMilestone({ id: "M002", title: "Active milestone", status: "active" }); + insertMilestone({ id: "M003", title: "Parked milestone", status: "parked" }); + }); + + afterEach(() => { + closeDatabase(); + rmSync(tmpBase, { recursive: true, force: true }); + }); + + test("completed milestone is skipped — no warning emitted", () => { + assert.ok(isDbAvailable(), "DB should be available"); + const ms = getMilestone("M001"); + assert.equal(ms?.status, "complete"); + }); + + test("parked milestone is skipped — no warning emitted", () => { + const ms = getMilestone("M003"); + assert.equal(ms?.status, "parked"); + }); + + test("active milestone with CONTEXT-DRAFT produces warning", () => { + const ms = getMilestone("M002"); + assert.equal(ms?.status, "active"); + + const draft = resolveMilestoneFile(tmpBase, "M002", "CONTEXT-DRAFT"); + assert.ok(draft, "CONTEXT-DRAFT file should be found for active milestone"); + }); + + test("full pre-flight filter produces warnings only for active milestones", () => { + const milestoneIds = ["M001", "M002", "M003"]; + const issues: string[] = []; + + for (const id of milestoneIds) { + // Replicate the fixed pre-flight logic from auto-start.ts + if (isDbAvailable()) { + const ms = getMilestone(id); + if (ms?.status === "complete" || ms?.status === "parked") continue; + } + const draft = resolveMilestoneFile(tmpBase, id, "CONTEXT-DRAFT"); + if (draft) { + issues.push(`${id}: has CONTEXT-DRAFT.md (will pause for discussion)`); + } + } + + assert.equal(issues.length, 1, "only one warning should be emitted"); + assert.match(issues[0], /M002/, "warning should be for the active milestone only"); + }); + + test("when DB is unavailable, all milestones with CONTEXT-DRAFT produce warnings (safe fallback)", () => { + closeDatabase(); + assert.ok(!isDbAvailable(), "DB should be unavailable after close"); + + const milestoneIds = ["M001", "M002", "M003"]; + const issues: string[] = []; + + for (const id of milestoneIds) { + if (isDbAvailable()) { + const ms = getMilestone(id); + if (ms?.status === "complete" || ms?.status === "parked") continue; + } + const draft = resolveMilestoneFile(tmpBase, id, "CONTEXT-DRAFT"); + if (draft) { + issues.push(`${id}: has CONTEXT-DRAFT.md (will pause for discussion)`); + } + } + + assert.equal(issues.length, 3, "all milestones should warn when DB is unavailable"); + }); +});