From e94bda817fd553925635d14cf68357263a349f62 Mon Sep 17 00:00:00 2001 From: deseltrus <101901449+deseltrus@users.noreply.github.com> Date: Sat, 21 Mar 2026 19:38:27 +0100 Subject: [PATCH] fix: auto-dispatch discussion instead of hard-stopping on needs-discussion phase (#1820) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a milestone has a CONTEXT-DRAFT.md (from multi-milestone queue creation) or no CONTEXT.md at all, auto-mode previously hard-stopped with a message telling the user to 'Run /gsd to discuss.' This created a dead end because: 1. /gsd (bare) routes to startAuto(step:true), which hits the same stop rule 2. /gsd auto also hits the same stop rule 3. Only /gsd discuss works, but the stop message doesn't mention it This change replaces both hard-stop rules with discuss-milestone dispatch: - 'needs-discussion → stop' becomes 'needs-discussion → discuss-milestone' - 'pre-planning (no context) → stop' becomes 'pre-planning (no context) → discuss-milestone' The new discuss-milestone unit type: - Uses the guided-discuss-milestone prompt template - Inlines CONTEXT-DRAFT.md as seed material when present - Interviews the user and writes CONTEXT.md - After CONTEXT.md exists, deriveState() returns 'pre-planning' and the normal research → plan → execute pipeline continues automatically Supporting changes: - auto-prompts.ts: new buildDiscussMilestonePrompt() function - auto-dashboard.ts: discuss-milestone labels for status display - complexity-classifier.ts: discuss-milestone classified as 'standard' tier - auto-recovery.ts: expected artifact = CONTEXT.md for discuss-milestone --- .../extensions/gsd/auto-dashboard.ts | 3 ++ src/resources/extensions/gsd/auto-dispatch.ts | 23 ++++++++------- src/resources/extensions/gsd/auto-prompts.ts | 28 +++++++++++++++++++ src/resources/extensions/gsd/auto-recovery.ts | 6 ++++ .../extensions/gsd/complexity-classifier.ts | 3 +- 5 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/resources/extensions/gsd/auto-dashboard.ts b/src/resources/extensions/gsd/auto-dashboard.ts index 65146c3f7..ddedc466f 100644 --- a/src/resources/extensions/gsd/auto-dashboard.ts +++ b/src/resources/extensions/gsd/auto-dashboard.ts @@ -67,6 +67,7 @@ export interface AutoDashboardData { export function unitVerb(unitType: string): string { if (unitType.startsWith("hook/")) return `hook: ${unitType.slice(5)}`; switch (unitType) { + case "discuss-milestone": return "discussing"; case "research-milestone": case "research-slice": return "researching"; case "plan-milestone": @@ -84,6 +85,7 @@ export function unitVerb(unitType: string): string { export function unitPhaseLabel(unitType: string): string { if (unitType.startsWith("hook/")) return "HOOK"; switch (unitType) { + case "discuss-milestone": return "DISCUSS"; case "research-milestone": return "RESEARCH"; case "research-slice": return "RESEARCH"; case "plan-milestone": return "PLAN"; @@ -108,6 +110,7 @@ function peekNext(unitType: string, state: GSDState): string { const sid = state.activeSlice?.id ?? ""; if (unitType.startsWith("hook/")) return `continue ${sid}`; switch (unitType) { + case "discuss-milestone": return "research or plan milestone"; case "research-milestone": return "plan milestone roadmap"; case "plan-milestone": return "plan or execute first slice"; case "research-slice": return `plan ${sid}`; diff --git a/src/resources/extensions/gsd/auto-dispatch.ts b/src/resources/extensions/gsd/auto-dispatch.ts index 986c295db..4f84e973e 100644 --- a/src/resources/extensions/gsd/auto-dispatch.ts +++ b/src/resources/extensions/gsd/auto-dispatch.ts @@ -27,6 +27,7 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { hasImplementationArtifacts } from "./auto-recovery.js"; import { + buildDiscussMilestonePrompt, buildResearchMilestonePrompt, buildPlanMilestonePrompt, buildResearchSlicePrompt, @@ -210,27 +211,29 @@ const DISPATCH_RULES: DispatchRule[] = [ }, }, { - name: "needs-discussion → stop", - match: async ({ state, mid, midTitle }) => { + name: "needs-discussion → discuss-milestone", + match: async ({ state, mid, midTitle, basePath }) => { if (state.phase !== "needs-discussion") return null; return { - action: "stop", - reason: `${mid}: ${midTitle} has draft context from a prior discussion — needs its own discussion before planning.\nRun /gsd to discuss.`, - level: "warning", + action: "dispatch", + unitType: "discuss-milestone", + unitId: mid, + prompt: await buildDiscussMilestonePrompt(mid, midTitle, basePath), }; }, }, { - name: "pre-planning (no context) → stop", - match: async ({ state, mid, basePath }) => { + name: "pre-planning (no context) → discuss-milestone", + match: async ({ state, mid, midTitle, basePath }) => { if (state.phase !== "pre-planning") return null; const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT"); const hasContext = !!(contextFile && (await loadFile(contextFile))); if (hasContext) return null; // fall through to next rule return { - action: "stop", - reason: "No context or roadmap yet. Run /gsd to discuss first.", - level: "warning", + action: "dispatch", + unitType: "discuss-milestone", + unitId: mid, + prompt: await buildDiscussMilestonePrompt(mid, midTitle, basePath), }; }, }, diff --git a/src/resources/extensions/gsd/auto-prompts.ts b/src/resources/extensions/gsd/auto-prompts.ts index f891039f9..94d24facf 100644 --- a/src/resources/extensions/gsd/auto-prompts.ts +++ b/src/resources/extensions/gsd/auto-prompts.ts @@ -767,6 +767,34 @@ export async function checkNeedsRunUat( // ─── Prompt Builders ────────────────────────────────────────────────────── +/** + * Build a prompt for the discuss-milestone unit type. + * Loads the guided-discuss-milestone template and inlines the CONTEXT-DRAFT + * as a seed when present. The discussion agent interviews the user, writes + * a full CONTEXT.md, and the phase transitions to pre-planning automatically. + */ +export async function buildDiscussMilestonePrompt(mid: string, midTitle: string, base: string): Promise { + const discussTemplates = inlineTemplate("context", "Context"); + + const basePrompt = loadPrompt("guided-discuss-milestone", { + milestoneId: mid, + milestoneTitle: midTitle, + inlinedTemplates: discussTemplates, + structuredQuestionsAvailable: "true", + commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.", + }); + + // If a CONTEXT-DRAFT.md exists, append it as seed material + const draftPath = resolveMilestoneFile(base, mid, "CONTEXT-DRAFT"); + const draftContent = draftPath ? await loadFile(draftPath) : null; + + if (draftContent) { + return `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\nThe following draft was captured from a prior multi-milestone discussion. Use it as seed material — the user has already provided this context. Start with a brief reflection on what the draft covers, then probe for any gaps or open questions before writing the full CONTEXT.md.\n\n${draftContent}`; + } + + return basePrompt; +} + export async function buildResearchMilestonePrompt(mid: string, midTitle: string, base: string): Promise { const contextPath = resolveMilestoneFile(base, mid, "CONTEXT"); const contextRel = relMilestoneFile(base, mid, "CONTEXT"); diff --git a/src/resources/extensions/gsd/auto-recovery.ts b/src/resources/extensions/gsd/auto-recovery.ts index b33e53088..8d4fe0df9 100644 --- a/src/resources/extensions/gsd/auto-recovery.ts +++ b/src/resources/extensions/gsd/auto-recovery.ts @@ -63,6 +63,10 @@ export function resolveExpectedArtifactPath( const mid = parts[0]!; const sid = parts[1]; switch (unitType) { + case "discuss-milestone": { + const dir = resolveMilestonePath(base, mid); + return dir ? join(dir, buildMilestoneFileName(mid, "CONTEXT")) : null; + } case "research-milestone": { const dir = resolveMilestonePath(base, mid); return dir ? join(dir, buildMilestoneFileName(mid, "RESEARCH")) : null; @@ -441,6 +445,8 @@ export function diagnoseExpectedArtifact( const mid = parts[0]; const sid = parts[1]; switch (unitType) { + case "discuss-milestone": + return `${relMilestoneFile(base, mid!, "CONTEXT")} (milestone context from discussion)`; case "research-milestone": return `${relMilestoneFile(base, mid!, "RESEARCH")} (milestone research)`; case "plan-milestone": diff --git a/src/resources/extensions/gsd/complexity-classifier.ts b/src/resources/extensions/gsd/complexity-classifier.ts index 17f2bc190..6e117cccd 100644 --- a/src/resources/extensions/gsd/complexity-classifier.ts +++ b/src/resources/extensions/gsd/complexity-classifier.ts @@ -35,7 +35,8 @@ const UNIT_TYPE_TIERS: Record = { "complete-slice": "light", "run-uat": "light", - // Tier 2 — Standard: research, routine planning + // Tier 2 — Standard: research, routine planning, discussion + "discuss-milestone": "standard", "research-milestone": "standard", "research-slice": "standard", "plan-milestone": "standard",