From 5a940856c1df63667fcd7d216c7b2bcf44618ac9 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Fri, 10 Apr 2026 19:12:45 -0500 Subject: [PATCH] fix claude-code discuss question fallback --- src/resources/extensions/gsd/auto-prompts.ts | 2 +- src/resources/extensions/gsd/guided-flow.ts | 31 ++++++++++++++----- .../extensions/gsd/tests/workflow-mcp.test.ts | 25 +++++++++++++++ src/resources/extensions/gsd/workflow-mcp.ts | 15 +++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/resources/extensions/gsd/auto-prompts.ts b/src/resources/extensions/gsd/auto-prompts.ts index 5e8bff3c4..86fbec9a3 100644 --- a/src/resources/extensions/gsd/auto-prompts.ts +++ b/src/resources/extensions/gsd/auto-prompts.ts @@ -997,7 +997,7 @@ export async function buildDiscussMilestonePrompt(mid: string, midTitle: string, milestoneId: mid, milestoneTitle: midTitle, inlinedTemplates: discussTemplates, - structuredQuestionsAvailable: "true", + structuredQuestionsAvailable: "false", commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.", fastPathInstruction: "", }); diff --git a/src/resources/extensions/gsd/guided-flow.ts b/src/resources/extensions/gsd/guided-flow.ts index d3b6836e4..6a0122188 100644 --- a/src/resources/extensions/gsd/guided-flow.ts +++ b/src/resources/extensions/gsd/guided-flow.ts @@ -48,6 +48,7 @@ import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js"; import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForGuidedUnit, + supportsStructuredQuestions, } from "./workflow-mcp.js"; import { runPreparation, @@ -367,6 +368,20 @@ async function dispatchWorkflow( } } +function getStructuredQuestionsAvailability( + pi: ExtensionAPI, + ctx: ExtensionContext | undefined, +): "true" | "false" { + if (!ctx) return "false"; + + const provider = ctx.model?.provider; + const authMode = provider ? ctx.modelRegistry.getProviderAuthMode(provider) : undefined; + return supportsStructuredQuestions(pi.getActiveTools(), { + authMode, + baseUrl: ctx.model?.baseUrl, + }) ? "true" : "false"; +} + /** * Resolve a model ID string to a model object from available models. * Handles "provider/model" and bare ID formats. @@ -739,7 +754,7 @@ export async function showDiscuss( if (choice === "discuss_draft") { const discussMilestoneTemplates = inlineTemplate("context", "Context"); - const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false"; + const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx); const basePrompt = loadPrompt("guided-discuss-milestone", { milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable, commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`), @@ -752,7 +767,7 @@ export async function showDiscuss( await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone"); } else if (choice === "discuss_fresh") { const discussMilestoneTemplates = inlineTemplate("context", "Context"); - const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false"; + const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx); pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() }); await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", { milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable, @@ -910,7 +925,7 @@ export async function showDiscuss( if (confirm !== "rediscuss") continue; } - const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false"; + const sqAvail = getStructuredQuestionsAvailability(pi, ctx); const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail }); await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice"); @@ -1020,7 +1035,7 @@ async function dispatchDiscussForMilestone( ].join("\n") : ""; const discussMilestoneTemplates = inlineTemplate("context", "Context"); - const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false"; + const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx); const basePrompt = loadPrompt("guided-discuss-milestone", { milestoneId: mid, milestoneTitle, @@ -1461,7 +1476,7 @@ export async function showSmartEntry( if (choice === "discuss_draft") { const discussMilestoneTemplates = inlineTemplate("context", "Context"); - const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false"; + const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx); const basePrompt = loadPrompt("guided-discuss-milestone", { milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable, commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`), @@ -1474,7 +1489,7 @@ export async function showSmartEntry( await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone"); } else if (choice === "discuss_fresh") { const discussMilestoneTemplates = inlineTemplate("context", "Context"); - const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false"; + const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx); pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() }); await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", { milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable, @@ -1572,7 +1587,7 @@ export async function showSmartEntry( }), "gsd-run", ctx, "plan-milestone"); } else if (choice === "discuss") { const discussMilestoneTemplates = inlineTemplate("context", "Context"); - const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false"; + const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx); await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", { milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable, commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`), @@ -1712,7 +1727,7 @@ export async function showSmartEntry( }), }), "gsd-run", ctx, "plan-slice"); } else if (choice === "discuss") { - const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false"; + const sqAvail = getStructuredQuestionsAvailability(pi, ctx); await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice"); } else if (choice === "research") { const researchTemplates = inlineTemplate("research", "Research"); diff --git a/src/resources/extensions/gsd/tests/workflow-mcp.test.ts b/src/resources/extensions/gsd/tests/workflow-mcp.test.ts index 8e0575096..8309e1af5 100644 --- a/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +++ b/src/resources/extensions/gsd/tests/workflow-mcp.test.ts @@ -13,6 +13,7 @@ import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, getRequiredWorkflowToolsForGuidedUnit, + supportsStructuredQuestions, usesWorkflowMcpTransport, } from "../workflow-mcp.ts"; @@ -291,6 +292,30 @@ test("usesWorkflowMcpTransport matches local externalCli providers", () => { assert.equal(usesWorkflowMcpTransport("oauth", "local://custom"), false); }); +test("supportsStructuredQuestions disables structured ask flow on workflow MCP transports", () => { + assert.equal( + supportsStructuredQuestions(["ask_user_questions"], { + authMode: "externalCli", + baseUrl: "local://claude-code", + }), + false, + ); + assert.equal( + supportsStructuredQuestions(["ask_user_questions"], { + authMode: "oauth", + baseUrl: "https://api.anthropic.com", + }), + true, + ); + assert.equal( + supportsStructuredQuestions([], { + authMode: "oauth", + baseUrl: "https://api.anthropic.com", + }), + false, + ); +}); + test("transport compatibility passes when required tools fit current MCP surface", () => { const error = getWorkflowTransportSupportError( "claude-code", diff --git a/src/resources/extensions/gsd/workflow-mcp.ts b/src/resources/extensions/gsd/workflow-mcp.ts index 809aa1543..f3e1def53 100644 --- a/src/resources/extensions/gsd/workflow-mcp.ts +++ b/src/resources/extensions/gsd/workflow-mcp.ts @@ -348,6 +348,21 @@ export function usesWorkflowMcpTransport( return authMode === "externalCli" && typeof baseUrl === "string" && baseUrl.startsWith("local://"); } +export function supportsStructuredQuestions( + activeTools: string[], + options: Pick = {}, +): boolean { + if (!activeTools.includes("ask_user_questions")) return false; + + // Workflow MCP currently exposes ask_user_questions via MCP form elicitation. + // Local external CLI transports such as Claude Code can invoke the tool, but + // do not reliably complete that elicitation round-trip yet, so guided discuss + // prompts must fall back to plain-text questioning. + if (usesWorkflowMcpTransport(options.authMode, options.baseUrl)) return false; + + return true; +} + export function getWorkflowTransportSupportError( provider: string | undefined, requiredTools: string[],