gsd-pi 2.70.1-dev.3591dcf → 2.70.1-dev.6504106
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.
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +129 -30
- package/dist/resources/extensions/get-secrets-from-user.js +17 -1
- package/dist/resources/extensions/gsd/auto-start.js +3 -11
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -0
- package/dist/resources/extensions/gsd/guided-flow.js +12 -10
- package/dist/resources/extensions/gsd/init-wizard.js +3 -11
- package/dist/resources/extensions/gsd/prompts/discuss.md +31 -13
- package/dist/resources/extensions/gsd/state.js +234 -332
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +34 -0
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +56 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2826.dd3dc8bbd3025fa5.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-6e4d7e9a4f57bed4.js → webpack-b868033a5834586d.js} +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/env-writer.d.ts +39 -0
- package/packages/mcp-server/dist/env-writer.d.ts.map +1 -0
- package/packages/mcp-server/dist/env-writer.js +158 -0
- package/packages/mcp-server/dist/env-writer.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts +11 -2
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +102 -2
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/src/env-writer.test.ts +280 -0
- package/packages/mcp-server/src/env-writer.ts +183 -0
- package/packages/mcp-server/src/secure-env-collect.test.ts +265 -0
- package/packages/mcp-server/src/server.ts +137 -3
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +310 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +19 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +50 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +158 -23
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +58 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +370 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +58 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-input.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +189 -29
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +66 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +1 -1
- package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +1 -0
- package/packages/pi-tui/dist/components/__tests__/input.test.js +9 -0
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +66 -0
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -0
- package/packages/pi-tui/dist/components/input.d.ts +2 -0
- package/packages/pi-tui/dist/components/input.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/input.js +7 -4
- package/packages/pi-tui/dist/components/input.js.map +1 -1
- package/packages/pi-tui/dist/components/markdown.d.ts +3 -0
- package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/markdown.js +17 -1
- package/packages/pi-tui/dist/components/markdown.js.map +1 -1
- package/packages/pi-tui/src/components/__tests__/input.test.ts +11 -0
- package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +75 -0
- package/packages/pi-tui/src/components/input.ts +7 -4
- package/packages/pi-tui/src/components/markdown.ts +22 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +166 -31
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +145 -0
- package/src/resources/extensions/get-secrets-from-user.ts +24 -1
- package/src/resources/extensions/gsd/auto-start.ts +3 -13
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
- package/src/resources/extensions/gsd/guided-flow.ts +12 -9
- package/src/resources/extensions/gsd/init-wizard.ts +3 -13
- package/src/resources/extensions/gsd/prompts/discuss.md +31 -13
- package/src/resources/extensions/gsd/state.ts +274 -344
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +436 -0
- package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/secure-env-collect.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +155 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +22 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +60 -25
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +76 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +1 -1
- package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +0 -9
- /package/dist/web/standalone/.next/static/{KdlODhIktLmeRKpLpHdKb → T3hWbQ-WQBDEIGoaOOfEo}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{KdlODhIktLmeRKpLpHdKb → T3hWbQ-WQBDEIGoaOOfEo}/_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(
|
|
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.
|
|
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));
|