gsd-pi 2.70.1-dev.3591dcf → 2.70.1-dev.bef631a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +2 -0
  2. package/dist/resources/extensions/gsd/auto-start.js +3 -11
  3. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -0
  4. package/dist/resources/extensions/gsd/guided-flow.js +12 -10
  5. package/dist/resources/extensions/gsd/init-wizard.js +3 -11
  6. package/dist/resources/extensions/gsd/prompts/discuss.md +31 -13
  7. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +34 -0
  8. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +56 -0
  9. package/dist/resources/extensions/gsd/workflow-mcp.js +1 -1
  10. package/dist/web/standalone/.next/BUILD_ID +1 -1
  11. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  12. package/dist/web/standalone/.next/build-manifest.json +3 -3
  13. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  14. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/index.html +1 -1
  32. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  39. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  40. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  42. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  43. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  44. package/dist/web/standalone/.next/static/chunks/2826.dd3dc8bbd3025fa5.js +9 -0
  45. package/dist/web/standalone/.next/static/chunks/{webpack-6e4d7e9a4f57bed4.js → webpack-b868033a5834586d.js} +1 -1
  46. package/package.json +1 -1
  47. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +2 -0
  48. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +33 -0
  49. package/src/resources/extensions/gsd/auto-start.ts +3 -13
  50. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
  51. package/src/resources/extensions/gsd/guided-flow.ts +12 -9
  52. package/src/resources/extensions/gsd/init-wizard.ts +3 -13
  53. package/src/resources/extensions/gsd/prompts/discuss.md +31 -13
  54. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +9 -0
  55. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +76 -0
  56. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +155 -1
  57. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +22 -0
  58. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +60 -25
  59. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +76 -0
  60. package/src/resources/extensions/gsd/workflow-mcp.ts +1 -1
  61. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +0 -9
  62. /package/dist/web/standalone/.next/static/{KdlODhIktLmeRKpLpHdKb → UlX0WGGZ8aBPN0uSZ5Ki4}/_buildManifest.js +0 -0
  63. /package/dist/web/standalone/.next/static/{KdlODhIktLmeRKpLpHdKb → UlX0WGGZ8aBPN0uSZ5Ki4}/_ssgManifest.js +0 -0
@@ -6,6 +6,7 @@ import { tmpdir } from "node:os";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
8
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
9
+ import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
9
10
 
10
11
  import {
11
12
  buildWorkflowMcpServers,
@@ -20,10 +21,20 @@ import {
20
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
21
22
  const gsdDir = join(__dirname, "..");
22
23
 
24
+ type ElicitPayload = {
25
+ message: string;
26
+ requestedSchema: { properties: Record<string, unknown>; required?: string[] };
27
+ };
28
+
23
29
  function readSrc(file: string): string {
24
30
  return readFileSync(join(gsdDir, file), "utf-8");
25
31
  }
26
32
 
33
+ function extractElicitPayload(request: unknown): ElicitPayload {
34
+ const payload = (request as { params?: unknown }).params ?? request;
35
+ return payload as ElicitPayload;
36
+ }
37
+
27
38
  test("guided execute-task requires canonical task completion tool", () => {
28
39
  assert.deepEqual(getRequiredWorkflowToolsForGuidedUnit("execute-task"), ["gsd_task_complete"]);
29
40
  });
@@ -185,7 +196,26 @@ test("workflow MCP launch config reaches mutation tools over stdio", async () =>
185
196
  assert.match(launch.env?.NODE_OPTIONS ?? "", /resolve-ts\.mjs/);
186
197
  }
187
198
 
188
- const client = new Client({ name: "workflow-mcp-transport-test", version: "1.0.0" });
199
+ const client = new Client(
200
+ { name: "workflow-mcp-transport-test", version: "1.0.0" },
201
+ { capabilities: { elicitation: {} } },
202
+ );
203
+ client.setRequestHandler(ElicitRequestSchema, async (request) => {
204
+ const elicitation = extractElicitPayload(request as unknown);
205
+
206
+ assert.match(elicitation.message, /Please answer the following question/);
207
+ assert.ok(elicitation.requestedSchema.properties.transport_mode);
208
+ assert.ok(elicitation.requestedSchema.properties["transport_mode__note"]);
209
+ assert.ok(elicitation.requestedSchema.required?.includes("transport_mode"));
210
+
211
+ return {
212
+ action: "accept",
213
+ content: {
214
+ transport_mode: "None of the above",
215
+ transport_mode__note: "Need Windows-safe MCP elicitation.",
216
+ },
217
+ };
218
+ });
189
219
  const transport = new StdioClientTransport({
190
220
  command: launch.command,
191
221
  args: launch.args,
@@ -207,6 +237,38 @@ test("workflow MCP launch config reaches mutation tools over stdio", async () =>
207
237
  "expected workflow MCP surface to expose ask_user_questions",
208
238
  );
209
239
 
240
+ const askResult = await client.callTool(
241
+ {
242
+ name: "ask_user_questions",
243
+ arguments: {
244
+ questions: [
245
+ {
246
+ id: "transport_mode",
247
+ header: "Transport",
248
+ question: "How should the workflow prompt be delivered?",
249
+ options: [
250
+ { label: "Local UI", description: "Use the host tool UI." },
251
+ { label: "Remote UI", description: "Use a remote response channel." },
252
+ ],
253
+ },
254
+ ],
255
+ },
256
+ },
257
+ undefined,
258
+ { timeout: 30_000 },
259
+ );
260
+ assert.equal(askResult.isError, undefined);
261
+ assert.equal(
262
+ ((askResult.content as Array<{ text?: string }>)?.[0])?.text ?? "",
263
+ JSON.stringify({
264
+ answers: {
265
+ transport_mode: {
266
+ answers: ["None of the above", "user_note: Need Windows-safe MCP elicitation."],
267
+ },
268
+ },
269
+ }),
270
+ );
271
+
210
272
  const milestoneResult = await client.callTool(
211
273
  {
212
274
  name: "gsd_plan_milestone",
@@ -286,6 +348,93 @@ test("workflow MCP launch config reaches mutation tools over stdio", async () =>
286
348
  }
287
349
  });
288
350
 
351
+ test("workflow MCP ask_user_questions uses stdio elicitation round-trip", async () => {
352
+ const projectRoot = mkdtempSync(join(tmpdir(), "gsd-workflow-elicit-"));
353
+ mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
354
+
355
+ const launch = detectWorkflowMcpLaunchConfig(projectRoot, {});
356
+ assert.ok(launch, "expected a workflow MCP launch config");
357
+
358
+ const client = new Client(
359
+ { name: "workflow-mcp-elicit-test", version: "1.0.0" },
360
+ { capabilities: { elicitation: {} } },
361
+ );
362
+ let requestSeen: {
363
+ message: string;
364
+ requestedSchema: { properties: Record<string, unknown>; required?: string[] };
365
+ } | null = null;
366
+
367
+ client.setRequestHandler(ElicitRequestSchema, async (request) => {
368
+ const params = extractElicitPayload(request as unknown);
369
+
370
+ requestSeen = params;
371
+
372
+ return {
373
+ action: "accept",
374
+ content: {
375
+ deployment: "None of the above",
376
+ deployment__note: "Need hybrid deployment.",
377
+ },
378
+ };
379
+ });
380
+
381
+ const transport = new StdioClientTransport({
382
+ command: launch.command,
383
+ args: launch.args,
384
+ env: { ...process.env, ...launch.env } as Record<string, string>,
385
+ cwd: launch.cwd,
386
+ stderr: "pipe",
387
+ });
388
+
389
+ try {
390
+ await client.connect(transport, { timeout: 30_000 });
391
+
392
+ const result = await client.callTool(
393
+ {
394
+ name: "ask_user_questions",
395
+ arguments: {
396
+ questions: [
397
+ {
398
+ id: "deployment",
399
+ header: "Deploy",
400
+ question: "Where will this run?",
401
+ options: [
402
+ { label: "Cloud", description: "Managed hosting." },
403
+ { label: "On-prem", description: "Runs in customer infrastructure." },
404
+ ],
405
+ },
406
+ ],
407
+ },
408
+ },
409
+ undefined,
410
+ { timeout: 30_000 },
411
+ );
412
+
413
+ assert.ok(requestSeen, "expected stdio transport to forward an elicitation request");
414
+ const seen = requestSeen as ElicitPayload;
415
+ assert.match(seen.message, /Please answer the following question/);
416
+ assert.ok(seen.requestedSchema.properties.deployment);
417
+ assert.ok(seen.requestedSchema.properties.deployment__note);
418
+ assert.ok(seen.requestedSchema.required?.includes("deployment"));
419
+
420
+ const content = (result as { content: Array<{ type: string; text?: string }> }).content;
421
+ const text = content.find((item: { type: string; text?: string }) => item.type === "text");
422
+ assert.ok(text && "text" in text);
423
+ assert.equal(
424
+ text.text,
425
+ JSON.stringify({
426
+ answers: {
427
+ deployment: {
428
+ answers: ["None of the above", "user_note: Need hybrid deployment."],
429
+ },
430
+ },
431
+ }),
432
+ );
433
+ } finally {
434
+ await client.close();
435
+ }
436
+ });
437
+
289
438
  test("usesWorkflowMcpTransport matches local externalCli providers", () => {
290
439
  assert.equal(usesWorkflowMcpTransport("externalCli", "local://claude-code"), true);
291
440
  assert.equal(usesWorkflowMcpTransport("externalCli", "https://api.example.com"), false);
@@ -539,3 +688,8 @@ test("auto phases source enforces workflow compatibility preflight", () => {
539
688
  assert.match(src, /getWorkflowTransportSupportError/);
540
689
  assert.match(src, /workflow-capability/);
541
690
  });
691
+
692
+ test("workflow transport error guidance includes /gsd mcp init hint", () => {
693
+ const src = readSrc("workflow-mcp.ts");
694
+ assert.match(src, /Please run \/gsd mcp init \./);
695
+ });
@@ -256,6 +256,28 @@ test("executePlanSlice writes task planning state and rendered plan artifacts",
256
256
  }
257
257
  });
258
258
 
259
+ test("executePlanSlice marks validation failures with isError", async () => {
260
+ const base = makeTmpBase();
261
+ try {
262
+ openTestDb(base);
263
+
264
+ const result = await inProjectDir(base, () => executePlanSlice({
265
+ milestoneId: "M001",
266
+ sliceId: "S01",
267
+ goal: "Trigger validation failure for empty tasks.",
268
+ tasks: [],
269
+ }, base));
270
+
271
+ assert.equal(result.isError, true);
272
+ assert.equal(result.details.operation, "plan_slice");
273
+ assert.match(String(result.details.error), /validation failed: tasks must be a non-empty array/);
274
+ assert.match(result.content[0].text, /Error planning slice:/);
275
+ } finally {
276
+ closeDatabase();
277
+ cleanup(base);
278
+ }
279
+ });
280
+
259
281
  test("executeSliceComplete coerces string enrichment entries and writes summary/UAT artifacts", async () => {
260
282
  const base = makeTmpBase();
261
283
  try {
@@ -38,6 +38,7 @@ export function isSupportedSummaryArtifactType(
38
38
  export interface ToolExecutionResult {
39
39
  content: Array<{ type: "text"; text: string }>;
40
40
  details: Record<string, unknown>;
41
+ isError?: boolean;
41
42
  }
42
43
 
43
44
  export interface SummarySaveParams {
@@ -57,13 +58,15 @@ export async function executeSummarySave(
57
58
  return {
58
59
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot save artifact." }],
59
60
  details: { operation: "save_summary", error: "db_unavailable" },
60
- };
61
+ isError: true,
62
+ };
61
63
  }
62
64
  if (!isSupportedSummaryArtifactType(params.artifact_type)) {
63
65
  return {
64
66
  content: [{ type: "text", text: `Error: Invalid artifact_type "${params.artifact_type}". Must be one of: ${SUPPORTED_SUMMARY_ARTIFACT_TYPES.join(", ")}` }],
65
67
  details: { operation: "save_summary", error: "invalid_artifact_type" },
66
- };
68
+ isError: true,
69
+ };
67
70
  }
68
71
  const contextGuard = shouldBlockContextArtifactSaveInSnapshot(
69
72
  loadWriteGateSnapshot(basePath),
@@ -75,7 +78,8 @@ export async function executeSummarySave(
75
78
  return {
76
79
  content: [{ type: "text", text: `Error saving artifact: ${contextGuard.reason ?? "context write blocked"}` }],
77
80
  details: { operation: "save_summary", error: "context_write_blocked" },
78
- };
81
+ isError: true,
82
+ };
79
83
  }
80
84
  try {
81
85
  let relativePath: string;
@@ -108,7 +112,8 @@ export async function executeSummarySave(
108
112
  return {
109
113
  content: [{ type: "text", text: `Error saving artifact: ${msg}` }],
110
114
  details: { operation: "save_summary", error: msg },
111
- };
115
+ isError: true,
116
+ };
112
117
  }
113
118
  }
114
119
 
@@ -163,7 +168,8 @@ export async function executeTaskComplete(
163
168
  return {
164
169
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot complete task." }],
165
170
  details: { operation: "complete_task", error: "db_unavailable" },
166
- };
171
+ isError: true,
172
+ };
167
173
  }
168
174
  try {
169
175
  const coerced = { ...params };
@@ -176,6 +182,7 @@ export async function executeTaskComplete(
176
182
  return {
177
183
  content: [{ type: "text", text: `Error completing task: ${result.error}` }],
178
184
  details: { operation: "complete_task", error: result.error },
185
+ isError: true,
179
186
  };
180
187
  }
181
188
  return {
@@ -194,7 +201,8 @@ export async function executeTaskComplete(
194
201
  return {
195
202
  content: [{ type: "text", text: `Error completing task: ${msg}` }],
196
203
  details: { operation: "complete_task", error: msg },
197
- };
204
+ isError: true,
205
+ };
198
206
  }
199
207
  }
200
208
 
@@ -207,7 +215,8 @@ export async function executeSliceComplete(
207
215
  return {
208
216
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot complete slice." }],
209
217
  details: { operation: "complete_slice", error: "db_unavailable" },
210
- };
218
+ isError: true,
219
+ };
211
220
  }
212
221
  try {
213
222
  const splitPair = (s: string): [string, string] => {
@@ -257,6 +266,7 @@ export async function executeSliceComplete(
257
266
  return {
258
267
  content: [{ type: "text", text: `Error completing slice: ${result.error}` }],
259
268
  details: { operation: "complete_slice", error: result.error },
269
+ isError: true,
260
270
  };
261
271
  }
262
272
  return {
@@ -275,7 +285,8 @@ export async function executeSliceComplete(
275
285
  return {
276
286
  content: [{ type: "text", text: `Error completing slice: ${msg}` }],
277
287
  details: { operation: "complete_slice", error: msg },
278
- };
288
+ isError: true,
289
+ };
279
290
  }
280
291
  }
281
292
 
@@ -288,7 +299,8 @@ export async function executeCompleteMilestone(
288
299
  return {
289
300
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot complete milestone." }],
290
301
  details: { operation: "complete_milestone", error: "db_unavailable" },
291
- };
302
+ isError: true,
303
+ };
292
304
  }
293
305
  try {
294
306
  const sanitized = sanitizeCompleteMilestoneParams(params);
@@ -297,6 +309,7 @@ export async function executeCompleteMilestone(
297
309
  return {
298
310
  content: [{ type: "text", text: `Error completing milestone: ${result.error}` }],
299
311
  details: { operation: "complete_milestone", error: result.error },
312
+ isError: true,
300
313
  };
301
314
  }
302
315
  return {
@@ -313,7 +326,8 @@ export async function executeCompleteMilestone(
313
326
  return {
314
327
  content: [{ type: "text", text: `Error completing milestone: ${msg}` }],
315
328
  details: { operation: "complete_milestone", error: msg },
316
- };
329
+ isError: true,
330
+ };
317
331
  }
318
332
  }
319
333
 
@@ -326,7 +340,8 @@ export async function executeValidateMilestone(
326
340
  return {
327
341
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot validate milestone." }],
328
342
  details: { operation: "validate_milestone", error: "db_unavailable" },
329
- };
343
+ isError: true,
344
+ };
330
345
  }
331
346
  try {
332
347
  const result = await handleValidateMilestone(params, basePath);
@@ -334,6 +349,7 @@ export async function executeValidateMilestone(
334
349
  return {
335
350
  content: [{ type: "text", text: `Error validating milestone: ${result.error}` }],
336
351
  details: { operation: "validate_milestone", error: result.error },
352
+ isError: true,
337
353
  };
338
354
  }
339
355
  return {
@@ -351,7 +367,8 @@ export async function executeValidateMilestone(
351
367
  return {
352
368
  content: [{ type: "text", text: `Error validating milestone: ${msg}` }],
353
369
  details: { operation: "validate_milestone", error: msg },
354
- };
370
+ isError: true,
371
+ };
355
372
  }
356
373
  }
357
374
 
@@ -364,7 +381,8 @@ export async function executeReassessRoadmap(
364
381
  return {
365
382
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot reassess roadmap." }],
366
383
  details: { operation: "reassess_roadmap", error: "db_unavailable" },
367
- };
384
+ isError: true,
385
+ };
368
386
  }
369
387
  try {
370
388
  const result = await handleReassessRoadmap(params, basePath);
@@ -372,6 +390,7 @@ export async function executeReassessRoadmap(
372
390
  return {
373
391
  content: [{ type: "text", text: `Error reassessing roadmap: ${result.error}` }],
374
392
  details: { operation: "reassess_roadmap", error: result.error },
393
+ isError: true,
375
394
  };
376
395
  }
377
396
  return {
@@ -390,7 +409,8 @@ export async function executeReassessRoadmap(
390
409
  return {
391
410
  content: [{ type: "text", text: `Error reassessing roadmap: ${msg}` }],
392
411
  details: { operation: "reassess_roadmap", error: msg },
393
- };
412
+ isError: true,
413
+ };
394
414
  }
395
415
  }
396
416
 
@@ -403,7 +423,8 @@ export async function executeSaveGateResult(
403
423
  return {
404
424
  content: [{ type: "text", text: "Error: GSD database is not available." }],
405
425
  details: { operation: "save_gate_result", error: "db_unavailable" },
406
- };
426
+ isError: true,
427
+ };
407
428
  }
408
429
 
409
430
  const validGates = ["Q3", "Q4", "Q5", "Q6", "Q7", "Q8"];
@@ -411,7 +432,8 @@ export async function executeSaveGateResult(
411
432
  return {
412
433
  content: [{ type: "text", text: `Error: Invalid gateId "${params.gateId}". Must be one of: ${validGates.join(", ")}` }],
413
434
  details: { operation: "save_gate_result", error: "invalid_gate_id" },
414
- };
435
+ isError: true,
436
+ };
415
437
  }
416
438
 
417
439
  const validVerdicts = ["pass", "flag", "omitted"];
@@ -419,7 +441,8 @@ export async function executeSaveGateResult(
419
441
  return {
420
442
  content: [{ type: "text", text: `Error: Invalid verdict "${params.verdict}". Must be one of: ${validVerdicts.join(", ")}` }],
421
443
  details: { operation: "save_gate_result", error: "invalid_verdict" },
422
- };
444
+ isError: true,
445
+ };
423
446
  }
424
447
 
425
448
  try {
@@ -443,7 +466,8 @@ export async function executeSaveGateResult(
443
466
  return {
444
467
  content: [{ type: "text", text: `Error saving gate result: ${msg}` }],
445
468
  details: { operation: "save_gate_result", error: msg },
446
- };
469
+ isError: true,
470
+ };
447
471
  }
448
472
  }
449
473
 
@@ -456,7 +480,8 @@ export async function executePlanMilestone(
456
480
  return {
457
481
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot plan milestone." }],
458
482
  details: { operation: "plan_milestone", error: "db_unavailable" },
459
- };
483
+ isError: true,
484
+ };
460
485
  }
461
486
  try {
462
487
  const result = await handlePlanMilestone(params, basePath);
@@ -464,6 +489,7 @@ export async function executePlanMilestone(
464
489
  return {
465
490
  content: [{ type: "text", text: `Error planning milestone: ${result.error}` }],
466
491
  details: { operation: "plan_milestone", error: result.error },
492
+ isError: true,
467
493
  };
468
494
  }
469
495
  return {
@@ -480,7 +506,8 @@ export async function executePlanMilestone(
480
506
  return {
481
507
  content: [{ type: "text", text: `Error planning milestone: ${msg}` }],
482
508
  details: { operation: "plan_milestone", error: msg },
483
- };
509
+ isError: true,
510
+ };
484
511
  }
485
512
  }
486
513
 
@@ -493,7 +520,8 @@ export async function executePlanSlice(
493
520
  return {
494
521
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot plan slice." }],
495
522
  details: { operation: "plan_slice", error: "db_unavailable" },
496
- };
523
+ isError: true,
524
+ };
497
525
  }
498
526
  try {
499
527
  const result = await handlePlanSlice(params, basePath);
@@ -501,6 +529,7 @@ export async function executePlanSlice(
501
529
  return {
502
530
  content: [{ type: "text", text: `Error planning slice: ${result.error}` }],
503
531
  details: { operation: "plan_slice", error: result.error },
532
+ isError: true,
504
533
  };
505
534
  }
506
535
  return {
@@ -519,7 +548,8 @@ export async function executePlanSlice(
519
548
  return {
520
549
  content: [{ type: "text", text: `Error planning slice: ${msg}` }],
521
550
  details: { operation: "plan_slice", error: msg },
522
- };
551
+ isError: true,
552
+ };
523
553
  }
524
554
  }
525
555
 
@@ -532,7 +562,8 @@ export async function executeReplanSlice(
532
562
  return {
533
563
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot replan slice." }],
534
564
  details: { operation: "replan_slice", error: "db_unavailable" },
535
- };
565
+ isError: true,
566
+ };
536
567
  }
537
568
  try {
538
569
  const result = await handleReplanSlice(params, basePath);
@@ -540,6 +571,7 @@ export async function executeReplanSlice(
540
571
  return {
541
572
  content: [{ type: "text", text: `Error replanning slice: ${result.error}` }],
542
573
  details: { operation: "replan_slice", error: result.error },
574
+ isError: true,
543
575
  };
544
576
  }
545
577
  return {
@@ -558,7 +590,8 @@ export async function executeReplanSlice(
558
590
  return {
559
591
  content: [{ type: "text", text: `Error replanning slice: ${msg}` }],
560
592
  details: { operation: "replan_slice", error: msg },
561
- };
593
+ isError: true,
594
+ };
562
595
  }
563
596
  }
564
597
 
@@ -576,6 +609,7 @@ export async function executeMilestoneStatus(
576
609
  return {
577
610
  content: [{ type: "text", text: "Error: GSD database is not available." }],
578
611
  details: { operation: "milestone_status", error: "db_unavailable" },
612
+ isError: true,
579
613
  };
580
614
  }
581
615
 
@@ -624,6 +658,7 @@ export async function executeMilestoneStatus(
624
658
  return {
625
659
  content: [{ type: "text", text: `Error querying milestone status: ${msg}` }],
626
660
  details: { operation: "milestone_status", error: msg },
627
- };
661
+ isError: true,
662
+ };
628
663
  }
629
664
  }
@@ -0,0 +1,76 @@
1
+ import type { ExtensionContext } from "@gsd/pi-coding-agent";
2
+
3
+ import {
4
+ type EnsureProjectWorkflowMcpConfigResult,
5
+ ensureProjectWorkflowMcpConfig,
6
+ } from "./mcp-project-config.js";
7
+ import { usesWorkflowMcpTransport } from "./workflow-mcp.js";
8
+
9
+ interface WorkflowMcpAutoPrepContext {
10
+ model?: { provider?: string; baseUrl?: string };
11
+ modelRegistry?: {
12
+ getProviderAuthMode?: (provider: string) => string;
13
+ isProviderRequestReady?: (provider: string) => boolean;
14
+ };
15
+ ui?: Pick<ExtensionContext["ui"], "notify">;
16
+ }
17
+
18
+ function getAuthModeSafe(
19
+ ctx: WorkflowMcpAutoPrepContext,
20
+ provider: string | undefined,
21
+ ): string | undefined {
22
+ if (!provider) return undefined;
23
+ const getAuthMode = ctx.modelRegistry?.getProviderAuthMode;
24
+ if (typeof getAuthMode !== "function") return undefined;
25
+ try {
26
+ return getAuthMode(provider);
27
+ } catch {
28
+ return undefined;
29
+ }
30
+ }
31
+
32
+ function hasClaudeCodeProvider(ctx: WorkflowMcpAutoPrepContext): boolean {
33
+ return getAuthModeSafe(ctx, "claude-code") === "externalCli";
34
+ }
35
+
36
+ function isClaudeCodeProviderReady(ctx: WorkflowMcpAutoPrepContext): boolean {
37
+ const readyCheck = ctx.modelRegistry?.isProviderRequestReady;
38
+ if (typeof readyCheck !== "function") return false;
39
+ try {
40
+ return readyCheck("claude-code");
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ export function shouldAutoPrepareWorkflowMcp(ctx: WorkflowMcpAutoPrepContext): boolean {
47
+ const provider = ctx.model?.provider;
48
+ const baseUrl = ctx.model?.baseUrl;
49
+ const authMode = getAuthModeSafe(ctx, provider);
50
+
51
+ if (usesWorkflowMcpTransport(authMode as any, baseUrl)) return true;
52
+ if (provider === "claude-code") return true;
53
+ if (hasClaudeCodeProvider(ctx)) return true;
54
+ return isClaudeCodeProviderReady(ctx);
55
+ }
56
+
57
+ export function prepareWorkflowMcpForProject(
58
+ ctx: WorkflowMcpAutoPrepContext,
59
+ projectRoot: string,
60
+ ): EnsureProjectWorkflowMcpConfigResult | null {
61
+ if (!shouldAutoPrepareWorkflowMcp(ctx)) return null;
62
+
63
+ try {
64
+ const result = ensureProjectWorkflowMcpConfig(projectRoot);
65
+ if (result.status !== "unchanged") {
66
+ ctx.ui?.notify?.(`Claude Code MCP prepared at ${result.configPath}`, "info");
67
+ }
68
+ return result;
69
+ } catch (err) {
70
+ ctx.ui?.notify?.(
71
+ `Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}. Detected Claude Code model but no workflow MCP. Please run /gsd mcp init . from your project root.`,
72
+ "warning",
73
+ );
74
+ return null;
75
+ }
76
+ }
@@ -379,7 +379,7 @@ export function getWorkflowTransportSupportError(
379
379
  const providerLabel = `"${provider}"`;
380
380
 
381
381
  if (!launch) {
382
- return `Provider ${providerLabel} cannot run ${surface}${unitLabel}: the GSD workflow MCP server is not configured or discoverable. Configure GSD_WORKFLOW_MCP_COMMAND, build packages/mcp-server/dist/cli.js, or install gsd-mcp-server on PATH.`;
382
+ return `Provider ${providerLabel} cannot run ${surface}${unitLabel}: the GSD workflow MCP server is not configured or discoverable. Detected Claude Code model but no workflow MCP. Please run /gsd mcp init . from your project root. You can also configure GSD_WORKFLOW_MCP_COMMAND, build packages/mcp-server/dist/cli.js, or install gsd-mcp-server on PATH.`;
383
383
  }
384
384
 
385
385
  const missing = [...new Set(requiredTools)].filter((tool) => !MCP_WORKFLOW_TOOL_SURFACE.has(tool));