fix claude-code discuss question fallback

This commit is contained in:
Jeremy 2026-04-10 19:12:45 -05:00
parent 7ee1fa0c46
commit 5a940856c1
4 changed files with 64 additions and 9 deletions

View file

@ -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: "",
});

View file

@ -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");

View file

@ -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",

View file

@ -348,6 +348,21 @@ export function usesWorkflowMcpTransport(
return authMode === "externalCli" && typeof baseUrl === "string" && baseUrl.startsWith("local://");
}
export function supportsStructuredQuestions(
activeTools: string[],
options: Pick<WorkflowCapabilityOptions, "authMode" | "baseUrl"> = {},
): 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[],