gsd-pi 2.67.0-dev.5399650 → 2.67.0-dev.fe39184
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/README.md +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -70
- package/dist/resources/extensions/gsd/auto-start.js +12 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
- package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
- package/dist/resources/extensions/gsd/init-wizard.js +15 -12
- package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +64 -24
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- 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 +13 -13
- 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/6502.5dcdcf1e1432e20d.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-42a66876b763aa26.js} +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +10 -4
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.ts +13 -2
- package/packages/pi-agent-core/dist/agent-loop.js +14 -6
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
- package/packages/pi-agent-core/src/agent-loop.ts +20 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.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 +19 -0
- 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.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -12
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -15
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +190 -93
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
- package/src/resources/extensions/gsd/auto-start.ts +15 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
- package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
- package/src/resources/extensions/gsd/init-wizard.ts +17 -11
- package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +178 -17
- package/src/resources/extensions/gsd/workflow-mcp.ts +76 -23
- package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
- package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
- package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +0 -121
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → gbSATDX4Jt2ufxzUr5nYm}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → gbSATDX4Jt2ufxzUr5nYm}/_ssgManifest.js +0 -0
|
@@ -4,15 +4,15 @@ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "nod
|
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import {
|
|
7
|
+
makeStreamExhaustedErrorMessage,
|
|
7
8
|
buildPromptFromContext,
|
|
8
|
-
buildFinalClaudeCodeContent,
|
|
9
9
|
buildSdkOptions,
|
|
10
|
+
extractToolResultsFromSdkUserMessage,
|
|
10
11
|
getClaudeLookupCommand,
|
|
11
|
-
makeStreamExhaustedErrorMessage,
|
|
12
12
|
parseClaudeLookupOutput,
|
|
13
|
-
sanitizeClaudeCodeStreamingEvent,
|
|
14
13
|
} from "../stream-adapter.ts";
|
|
15
|
-
import type {
|
|
14
|
+
import type { Context, Message } from "@gsd/pi-ai";
|
|
15
|
+
import type { SDKUserMessage } from "../sdk-types.ts";
|
|
16
16
|
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
// Existing tests — exhausted stream fallback (#2575)
|
|
@@ -108,6 +108,65 @@ describe("stream-adapter — full context prompt (#2859)", () => {
|
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
describe("stream-adapter — Claude Code external tool results", () => {
|
|
112
|
+
test("extractToolResultsFromSdkUserMessage maps tool_result content to tool payloads", () => {
|
|
113
|
+
const message: SDKUserMessage = {
|
|
114
|
+
type: "user",
|
|
115
|
+
session_id: "sess-1",
|
|
116
|
+
parent_tool_use_id: "tool-bash-1",
|
|
117
|
+
message: {
|
|
118
|
+
role: "user",
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: "tool_result",
|
|
122
|
+
tool_use_id: "tool-bash-1",
|
|
123
|
+
content: "line 1\nline 2",
|
|
124
|
+
is_error: false,
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
131
|
+
assert.deepEqual(results, [
|
|
132
|
+
{
|
|
133
|
+
toolUseId: "tool-bash-1",
|
|
134
|
+
result: {
|
|
135
|
+
content: [{ type: "text", text: "line 1\nline 2" }],
|
|
136
|
+
details: {},
|
|
137
|
+
isError: false,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("extractToolResultsFromSdkUserMessage falls back to tool_use_result", () => {
|
|
144
|
+
const message: SDKUserMessage = {
|
|
145
|
+
type: "user",
|
|
146
|
+
session_id: "sess-1",
|
|
147
|
+
parent_tool_use_id: "tool-read-1",
|
|
148
|
+
message: { role: "user", content: [] },
|
|
149
|
+
tool_use_result: {
|
|
150
|
+
tool_use_id: "tool-read-1",
|
|
151
|
+
content: "file contents",
|
|
152
|
+
is_error: true,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const results = extractToolResultsFromSdkUserMessage(message);
|
|
157
|
+
assert.deepEqual(results, [
|
|
158
|
+
{
|
|
159
|
+
toolUseId: "tool-read-1",
|
|
160
|
+
result: {
|
|
161
|
+
content: [{ type: "text", text: "file contents" }],
|
|
162
|
+
details: {},
|
|
163
|
+
isError: true,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
111
170
|
describe("stream-adapter — session persistence (#2859)", () => {
|
|
112
171
|
test("buildSdkOptions enables persistSession by default", () => {
|
|
113
172
|
const options = buildSdkOptions("claude-sonnet-4-20250514", "test prompt");
|
|
@@ -149,18 +208,15 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
149
208
|
process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
|
|
150
209
|
|
|
151
210
|
const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
cwd: "/tmp/project",
|
|
162
|
-
},
|
|
163
|
-
});
|
|
211
|
+
const mcpServers = options.mcpServers as Record<string, any>;
|
|
212
|
+
assert.ok(mcpServers?.["gsd-workflow"], "expected gsd-workflow server config");
|
|
213
|
+
const srv = mcpServers["gsd-workflow"];
|
|
214
|
+
assert.equal(srv.command, "node");
|
|
215
|
+
assert.deepEqual(srv.args, ["packages/mcp-server/dist/cli.js"]);
|
|
216
|
+
assert.equal(srv.cwd, "/tmp/project");
|
|
217
|
+
assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
|
|
218
|
+
assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
219
|
+
assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
|
|
164
220
|
} finally {
|
|
165
221
|
process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
|
|
166
222
|
process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
|
|
@@ -170,7 +226,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
170
226
|
}
|
|
171
227
|
});
|
|
172
228
|
|
|
173
|
-
test("buildSdkOptions
|
|
229
|
+
test("buildSdkOptions auto-discovers bundled MCP server even without env hints", () => {
|
|
174
230
|
const prev = {
|
|
175
231
|
GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
|
|
176
232
|
GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
|
|
@@ -190,7 +246,13 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
190
246
|
process.chdir(emptyDir);
|
|
191
247
|
const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
|
|
192
248
|
process.chdir(originalCwd);
|
|
193
|
-
|
|
249
|
+
// The bundled CLI may or may not be discoverable depending on
|
|
250
|
+
// whether the build output exists relative to import.meta.url.
|
|
251
|
+
// Either outcome is valid — the key invariant is no crash.
|
|
252
|
+
const mcpServers = (options as any).mcpServers;
|
|
253
|
+
if (mcpServers) {
|
|
254
|
+
assert.ok(mcpServers["gsd-workflow"], "if present, must be gsd-workflow");
|
|
255
|
+
}
|
|
194
256
|
rmSync(emptyDir, { recursive: true, force: true });
|
|
195
257
|
} finally {
|
|
196
258
|
process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
|
|
@@ -227,18 +289,15 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
227
289
|
const resolvedRepoDir = realpathSync(repoDir);
|
|
228
290
|
|
|
229
291
|
const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
cwd: resolvedRepoDir,
|
|
240
|
-
},
|
|
241
|
-
});
|
|
292
|
+
const mcpServers = options.mcpServers as Record<string, any>;
|
|
293
|
+
assert.ok(mcpServers?.["gsd-workflow"], "expected gsd-workflow server config");
|
|
294
|
+
const srv = mcpServers["gsd-workflow"];
|
|
295
|
+
assert.equal(srv.command, process.execPath);
|
|
296
|
+
assert.deepEqual(srv.args, [realpathSync(resolve(repoDir, "packages", "mcp-server", "dist", "cli.js"))]);
|
|
297
|
+
assert.equal(srv.cwd, resolvedRepoDir);
|
|
298
|
+
assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
|
|
299
|
+
assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
300
|
+
assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, resolvedRepoDir);
|
|
242
301
|
} finally {
|
|
243
302
|
process.chdir(originalCwd);
|
|
244
303
|
rmSync(repoDir, { recursive: true, force: true });
|
|
@@ -252,92 +311,6 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
252
311
|
});
|
|
253
312
|
});
|
|
254
313
|
|
|
255
|
-
describe("stream-adapter — final content filtering (#3861)", () => {
|
|
256
|
-
test("buildFinalClaudeCodeContent strips intermediate tool calls from the final assistant message", () => {
|
|
257
|
-
const finalContent = buildFinalClaudeCodeContent(
|
|
258
|
-
[
|
|
259
|
-
{ type: "toolCall", id: "tc_1", name: "Read", arguments: {} },
|
|
260
|
-
{ type: "thinking", thinking: "Planning next step" },
|
|
261
|
-
{ type: "text", text: "Done." },
|
|
262
|
-
] as any,
|
|
263
|
-
"",
|
|
264
|
-
"",
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
assert.deepEqual(finalContent, [
|
|
268
|
-
{ type: "thinking", thinking: "Planning next step" },
|
|
269
|
-
{ type: "text", text: "Done." },
|
|
270
|
-
]);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test("buildFinalClaudeCodeContent falls back to cached text when the final turn only had tool calls", () => {
|
|
274
|
-
const finalContent = buildFinalClaudeCodeContent(
|
|
275
|
-
[
|
|
276
|
-
{ type: "toolCall", id: "tc_2", name: "Edit", arguments: { file_path: "app.ts" } },
|
|
277
|
-
] as any,
|
|
278
|
-
"",
|
|
279
|
-
"User-facing answer",
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
assert.deepEqual(finalContent, [{ type: "text", text: "User-facing answer" }]);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe("stream-adapter — streaming content filtering follow-up (#3867)", () => {
|
|
287
|
-
function makePartial(content: AssistantMessage["content"]): AssistantMessage {
|
|
288
|
-
return {
|
|
289
|
-
role: "assistant",
|
|
290
|
-
content,
|
|
291
|
-
api: "anthropic-messages",
|
|
292
|
-
provider: "claude-code",
|
|
293
|
-
model: "claude-sonnet-4-20250514",
|
|
294
|
-
usage: {
|
|
295
|
-
input: 0,
|
|
296
|
-
output: 0,
|
|
297
|
-
cacheRead: 0,
|
|
298
|
-
cacheWrite: 0,
|
|
299
|
-
totalTokens: 0,
|
|
300
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
301
|
-
},
|
|
302
|
-
stopReason: "stop",
|
|
303
|
-
timestamp: Date.now(),
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
test("sanitizeClaudeCodeStreamingEvent strips tool calls from streamed partials and remaps contentIndex", () => {
|
|
308
|
-
const event = sanitizeClaudeCodeStreamingEvent({
|
|
309
|
-
type: "text_delta",
|
|
310
|
-
contentIndex: 2,
|
|
311
|
-
delta: "Done.",
|
|
312
|
-
partial: makePartial([
|
|
313
|
-
{ type: "toolCall", id: "tc_1", name: "ToolSearch", arguments: {} },
|
|
314
|
-
{ type: "thinking", thinking: "Planning next step" },
|
|
315
|
-
{ type: "text", text: "Done." },
|
|
316
|
-
] as any),
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
assert.ok(event, "text events should still be forwarded");
|
|
320
|
-
assert.equal(event!.type, "text_delta");
|
|
321
|
-
assert.equal((event! as any).contentIndex, 1);
|
|
322
|
-
assert.deepEqual((event! as any).partial.content, [
|
|
323
|
-
{ type: "thinking", thinking: "Planning next step" },
|
|
324
|
-
{ type: "text", text: "Done." },
|
|
325
|
-
]);
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test("sanitizeClaudeCodeStreamingEvent suppresses internal tool streaming events entirely", () => {
|
|
329
|
-
const event = sanitizeClaudeCodeStreamingEvent({
|
|
330
|
-
type: "toolcall_start",
|
|
331
|
-
contentIndex: 0,
|
|
332
|
-
partial: makePartial([
|
|
333
|
-
{ type: "toolCall", id: "tc_1", name: "Bash", arguments: {} },
|
|
334
|
-
] as any),
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
assert.equal(event, null);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
|
|
341
314
|
describe("stream-adapter — Windows Claude path lookup (#3770)", () => {
|
|
342
315
|
test("getClaudeLookupCommand uses where on Windows", () => {
|
|
343
316
|
assert.equal(getClaudeLookupCommand("win32"), "where claude");
|
|
@@ -340,6 +340,21 @@ export async function bootstrapAutoSession(
|
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
if (ctx.model?.provider === "claude-code") {
|
|
344
|
+
try {
|
|
345
|
+
const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
|
|
346
|
+
const result = ensureProjectWorkflowMcpConfig(base);
|
|
347
|
+
if (result.status !== "unchanged") {
|
|
348
|
+
ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
ctx.ui.notify(
|
|
352
|
+
`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
353
|
+
"warning",
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
343
358
|
// Initialize GitServiceImpl
|
|
344
359
|
s.gitService = new GitServiceImpl(
|
|
345
360
|
s.basePath,
|
|
@@ -909,4 +924,3 @@ export async function bootstrapAutoSession(
|
|
|
909
924
|
throw err;
|
|
910
925
|
}
|
|
911
926
|
}
|
|
912
|
-
|
|
@@ -70,7 +70,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
|
|
|
70
70
|
{ cmd: "templates", desc: "List available workflow templates" },
|
|
71
71
|
{ cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
|
|
72
72
|
{ cmd: "fast", desc: "Toggle OpenAI service tier (on/off/flex/status)" },
|
|
73
|
-
{ cmd: "mcp", desc: "MCP server status and
|
|
73
|
+
{ cmd: "mcp", desc: "MCP server status, connectivity, and local config bootstrap (status, check, init)" },
|
|
74
74
|
{ cmd: "rethink", desc: "Conversational project reorganization — reorder, park, discard, add milestones" },
|
|
75
75
|
{ cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
|
|
76
76
|
{ cmd: "codebase", desc: "Generate, refresh, and inspect the codebase map cache (.gsd/CODEBASE.md)" },
|
|
@@ -201,6 +201,7 @@ const NESTED_COMPLETIONS: CompletionMap = {
|
|
|
201
201
|
mcp: [
|
|
202
202
|
{ cmd: "status", desc: "Show all MCP server statuses (default)" },
|
|
203
203
|
{ cmd: "check", desc: "Detailed status for a specific server" },
|
|
204
|
+
{ cmd: "init", desc: "Write .mcp.json for the local GSD workflow MCP server" },
|
|
204
205
|
],
|
|
205
206
|
doctor: [
|
|
206
207
|
{ cmd: "fix", desc: "Auto-fix detected issues" },
|
|
@@ -60,7 +60,7 @@ export function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
60
60
|
" /gsd hooks Show post-unit hook configuration",
|
|
61
61
|
" /gsd extensions Manage extensions [list|enable|disable|info]",
|
|
62
62
|
" /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
|
|
63
|
-
" /gsd mcp MCP server status and connectivity [status|check <server
|
|
63
|
+
" /gsd mcp MCP server status and connectivity [status|check <server>|init [dir]]",
|
|
64
64
|
"",
|
|
65
65
|
"MAINTENANCE",
|
|
66
66
|
" /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
|
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
* /gsd mcp — Overview of all servers (alias: /gsd mcp status)
|
|
8
8
|
* /gsd mcp status — Same as bare /gsd mcp
|
|
9
9
|
* /gsd mcp check <srv> — Detailed status for a specific server
|
|
10
|
+
* /gsd mcp init [dir] — Write project-local GSD workflow MCP config
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
13
14
|
|
|
14
15
|
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
+
import { join, resolve } from "node:path";
|
|
17
|
+
|
|
18
|
+
import { ensureProjectWorkflowMcpConfig } from "./mcp-project-config.js";
|
|
16
19
|
|
|
17
20
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
18
21
|
|
|
@@ -28,6 +31,28 @@ export interface McpServerDetail extends McpServerStatus {
|
|
|
28
31
|
tools: string[];
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
export function formatMcpInitResult(
|
|
35
|
+
status: "created" | "updated" | "unchanged",
|
|
36
|
+
configPath: string,
|
|
37
|
+
targetPath: string,
|
|
38
|
+
): string {
|
|
39
|
+
const summary =
|
|
40
|
+
status === "created"
|
|
41
|
+
? "Created project MCP config."
|
|
42
|
+
: status === "updated"
|
|
43
|
+
? "Updated project MCP config."
|
|
44
|
+
: "Project MCP config is already up to date.";
|
|
45
|
+
|
|
46
|
+
return [
|
|
47
|
+
summary,
|
|
48
|
+
"",
|
|
49
|
+
`Project: ${targetPath}`,
|
|
50
|
+
`Config: ${configPath}`,
|
|
51
|
+
"",
|
|
52
|
+
"Claude Code can now load the GSD workflow MCP server from this folder.",
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
// ─── Config reader (standalone — does not import mcp-client internals) ──────
|
|
32
57
|
|
|
33
58
|
interface McpServerRawConfig {
|
|
@@ -94,6 +119,7 @@ export function formatMcpStatusReport(servers: McpServerStatus[]): string {
|
|
|
94
119
|
"No MCP servers configured.",
|
|
95
120
|
"",
|
|
96
121
|
"Add servers to .mcp.json or .gsd/mcp.json to enable MCP integrations.",
|
|
122
|
+
"Tip: run /gsd mcp init . to write the local GSD workflow MCP config.",
|
|
97
123
|
"See: https://modelcontextprotocol.io/quickstart",
|
|
98
124
|
].join("\n");
|
|
99
125
|
}
|
|
@@ -153,12 +179,31 @@ export async function handleMcpStatus(
|
|
|
153
179
|
args: string,
|
|
154
180
|
ctx: ExtensionCommandContext,
|
|
155
181
|
): Promise<void> {
|
|
156
|
-
const trimmed = args.trim()
|
|
182
|
+
const trimmed = args.trim();
|
|
183
|
+
const lowered = trimmed.toLowerCase();
|
|
157
184
|
const configs = readMcpConfigs();
|
|
158
185
|
|
|
186
|
+
// /gsd mcp init [dir]
|
|
187
|
+
if (!lowered || lowered === "status") {
|
|
188
|
+
// handled below
|
|
189
|
+
} else if (lowered === "init" || lowered.startsWith("init ")) {
|
|
190
|
+
const rawPath = trimmed.slice("init".length).trim();
|
|
191
|
+
const targetPath = resolve(rawPath || ".");
|
|
192
|
+
try {
|
|
193
|
+
const result = ensureProjectWorkflowMcpConfig(targetPath);
|
|
194
|
+
ctx.ui.notify(formatMcpInitResult(result.status, result.configPath, targetPath), "info");
|
|
195
|
+
} catch (err) {
|
|
196
|
+
ctx.ui.notify(
|
|
197
|
+
`Failed to prepare MCP config for ${targetPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
198
|
+
"error",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
159
204
|
// /gsd mcp check <server>
|
|
160
|
-
if (
|
|
161
|
-
const serverName =
|
|
205
|
+
if (lowered.startsWith("check ")) {
|
|
206
|
+
const serverName = trimmed.slice("check ".length).trim();
|
|
162
207
|
const config = configs.find((c) => c.name === serverName);
|
|
163
208
|
if (!config) {
|
|
164
209
|
const available = configs.map((c) => c.name).join(", ") || "(none)";
|
|
@@ -202,7 +247,7 @@ export async function handleMcpStatus(
|
|
|
202
247
|
}
|
|
203
248
|
|
|
204
249
|
// /gsd mcp or /gsd mcp status
|
|
205
|
-
if (!
|
|
250
|
+
if (!lowered || lowered === "status") {
|
|
206
251
|
// Build status for each server
|
|
207
252
|
const statuses: McpServerStatus[] = [];
|
|
208
253
|
|
|
@@ -239,9 +284,10 @@ export async function handleMcpStatus(
|
|
|
239
284
|
|
|
240
285
|
// Unknown subcommand
|
|
241
286
|
ctx.ui.notify(
|
|
242
|
-
"Usage: /gsd mcp [status|check <server
|
|
287
|
+
"Usage: /gsd mcp [status|check <server>|init [dir]]\n\n" +
|
|
243
288
|
" status Show all MCP server statuses (default)\n" +
|
|
244
|
-
" check <server> Detailed status for a specific server"
|
|
289
|
+
" check <server> Detailed status for a specific server\n" +
|
|
290
|
+
" init [dir] Write .mcp.json for the local GSD workflow MCP server",
|
|
245
291
|
"warning",
|
|
246
292
|
);
|
|
247
293
|
}
|
|
@@ -10,7 +10,7 @@ import { deriveState, isMilestoneComplete } from "./state.js";
|
|
|
10
10
|
import { listWorktrees, resolveGitDir, worktreesDir } from "./worktree-manager.js";
|
|
11
11
|
import { abortAndReset } from "./git-self-heal.js";
|
|
12
12
|
import { RUNTIME_EXCLUSION_PATHS, resolveMilestoneIntegrationBranch, writeIntegrationBranch } from "./git-service.js";
|
|
13
|
-
import { nativeIsRepo, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch,
|
|
13
|
+
import { nativeIsRepo, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
|
|
14
14
|
import { getAllWorktreeHealth } from "./worktree-health.js";
|
|
15
15
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
16
16
|
|
|
@@ -386,19 +386,19 @@ export async function checkGitHealth(
|
|
|
386
386
|
code: "stale_uncommitted_changes",
|
|
387
387
|
scope: "project",
|
|
388
388
|
unitId: "project",
|
|
389
|
-
message: `Uncommitted changes detected with no commit in ${mins} minute${mins === 1 ? "" : "s"} (threshold: ${thresholdMinutes}m). Snapshotting
|
|
389
|
+
message: `Uncommitted changes detected with no commit in ${mins} minute${mins === 1 ? "" : "s"} (threshold: ${thresholdMinutes}m). Snapshotting tracked files.`,
|
|
390
390
|
fixable: true,
|
|
391
391
|
});
|
|
392
392
|
|
|
393
393
|
if (shouldFix("stale_uncommitted_changes")) {
|
|
394
394
|
try {
|
|
395
|
-
|
|
395
|
+
nativeAddTracked(basePath);
|
|
396
396
|
const commitMsg = `gsd snapshot: uncommitted changes after ${mins}m inactivity`;
|
|
397
397
|
const result = nativeCommit(basePath, commitMsg);
|
|
398
398
|
if (result) {
|
|
399
399
|
fixesApplied.push(`created gsd snapshot after ${mins}m of uncommitted changes`);
|
|
400
400
|
} else {
|
|
401
|
-
fixesApplied.push("gsd snapshot skipped — nothing to commit after staging
|
|
401
|
+
fixesApplied.push("gsd snapshot skipped — nothing to commit after staging tracked files");
|
|
402
402
|
}
|
|
403
403
|
} catch {
|
|
404
404
|
fixesApplied.push("failed to create gsd snapshot commit");
|
|
@@ -21,8 +21,8 @@ import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.j
|
|
|
21
21
|
import { abortAndReset } from "./git-self-heal.js";
|
|
22
22
|
import { rebuildState } from "./doctor.js";
|
|
23
23
|
import { deriveState } from "./state.js";
|
|
24
|
-
import {
|
|
25
|
-
import { nativeIsRepo, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch,
|
|
24
|
+
import { resolveMilestoneIntegrationBranch } from "./git-service.js";
|
|
25
|
+
import { nativeIsRepo, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
|
|
26
26
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
27
27
|
import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
28
28
|
|
|
@@ -312,7 +312,7 @@ export async function preDispatchHealthGate(basePath: string): Promise<PreDispat
|
|
|
312
312
|
if (minutesSinceCommit >= thresholdMinutes) {
|
|
313
313
|
const mins = Math.floor(minutesSinceCommit);
|
|
314
314
|
try {
|
|
315
|
-
|
|
315
|
+
nativeAddTracked(basePath);
|
|
316
316
|
const commitMsg = `gsd snapshot: pre-dispatch, uncommitted changes after ${mins}m inactivity`;
|
|
317
317
|
const result = nativeCommit(basePath, commitMsg);
|
|
318
318
|
if (result) {
|
|
@@ -238,15 +238,11 @@ export async function showProjectInit(
|
|
|
238
238
|
// Initialize SQLite database so GSD starts in full-capability mode (#3880).
|
|
239
239
|
// Without this, isDbAvailable() returns false and GSD enters degraded
|
|
240
240
|
// markdown-only mode until a tool handler happens to call ensureDbOpen().
|
|
241
|
-
let dbReady = false;
|
|
242
241
|
try {
|
|
243
242
|
const { ensureDbOpen } = await import("./bootstrap/dynamic-tools.js");
|
|
244
|
-
|
|
243
|
+
await ensureDbOpen(basePath);
|
|
245
244
|
} catch {
|
|
246
|
-
//
|
|
247
|
-
}
|
|
248
|
-
if (!dbReady) {
|
|
249
|
-
ctx.ui.notify("Warning: database initialization failed — GSD will run in degraded mode until the next /gsd invocation.", "warning");
|
|
245
|
+
// Non-fatal — DB creation failure should not block project init
|
|
250
246
|
}
|
|
251
247
|
|
|
252
248
|
// Ensure .gitignore
|
|
@@ -267,7 +263,6 @@ export async function showProjectInit(
|
|
|
267
263
|
// Write initial STATE.md so it exists before the first /gsd invocation.
|
|
268
264
|
// The explicit /gsd init path (ops.ts) returns without entering showSmartEntry(),
|
|
269
265
|
// which would otherwise generate STATE.md at guided-flow.ts:1358.
|
|
270
|
-
let stateReady = false;
|
|
271
266
|
try {
|
|
272
267
|
const { deriveState } = await import("./state.js");
|
|
273
268
|
const { buildStateMarkdown } = await import("./doctor.js");
|
|
@@ -275,12 +270,23 @@ export async function showProjectInit(
|
|
|
275
270
|
const { resolveGsdRootFile } = await import("./paths.js");
|
|
276
271
|
const state = await deriveState(basePath);
|
|
277
272
|
await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
|
|
278
|
-
stateReady = true;
|
|
279
273
|
} catch {
|
|
280
|
-
//
|
|
274
|
+
// Non-fatal — STATE.md will be regenerated on next /gsd invocation
|
|
281
275
|
}
|
|
282
|
-
|
|
283
|
-
|
|
276
|
+
|
|
277
|
+
if (ctx.model?.provider === "claude-code") {
|
|
278
|
+
try {
|
|
279
|
+
const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
|
|
280
|
+
const result = ensureProjectWorkflowMcpConfig(basePath);
|
|
281
|
+
if (result.status !== "unchanged") {
|
|
282
|
+
ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
|
|
283
|
+
}
|
|
284
|
+
} catch (err) {
|
|
285
|
+
ctx.ui.notify(
|
|
286
|
+
`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
287
|
+
"warning",
|
|
288
|
+
);
|
|
289
|
+
}
|
|
284
290
|
}
|
|
285
291
|
|
|
286
292
|
ctx.ui.notify("GSD initialized. Starting your first milestone...", "info");
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { assertSafeDirectory } from "./validate-directory.js";
|
|
6
|
+
import { detectWorkflowMcpLaunchConfig } from "./workflow-mcp.js";
|
|
7
|
+
|
|
8
|
+
export const GSD_WORKFLOW_MCP_SERVER_NAME = "gsd-workflow";
|
|
9
|
+
|
|
10
|
+
export interface ProjectMcpServerConfig {
|
|
11
|
+
command?: string;
|
|
12
|
+
args?: string[];
|
|
13
|
+
cwd?: string;
|
|
14
|
+
env?: Record<string, string>;
|
|
15
|
+
url?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface EnsureProjectWorkflowMcpConfigResult {
|
|
19
|
+
configPath: string;
|
|
20
|
+
serverName: string;
|
|
21
|
+
status: "created" | "updated" | "unchanged";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface McpConfigFile {
|
|
25
|
+
mcpServers?: Record<string, ProjectMcpServerConfig>;
|
|
26
|
+
servers?: Record<string, ProjectMcpServerConfig>;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveBundledGsdCliPath(env: NodeJS.ProcessEnv = process.env): string | null {
|
|
31
|
+
const explicit = env.GSD_CLI_PATH?.trim() || env.GSD_BIN_PATH?.trim();
|
|
32
|
+
if (explicit) return explicit;
|
|
33
|
+
|
|
34
|
+
const candidates = [
|
|
35
|
+
resolve(fileURLToPath(new URL("../../../../scripts/dev-cli.js", import.meta.url))),
|
|
36
|
+
resolve(fileURLToPath(new URL("../../../../dist/loader.js", import.meta.url))),
|
|
37
|
+
resolve(fileURLToPath(new URL("../../../loader.js", import.meta.url))),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
if (existsSync(candidate)) return candidate;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildProjectWorkflowMcpServerConfig(
|
|
48
|
+
projectRoot: string,
|
|
49
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
50
|
+
): ProjectMcpServerConfig {
|
|
51
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
52
|
+
const gsdCliPath = resolveBundledGsdCliPath(env);
|
|
53
|
+
const launch = detectWorkflowMcpLaunchConfig(resolvedProjectRoot, {
|
|
54
|
+
...env,
|
|
55
|
+
...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath, GSD_BIN_PATH: gsdCliPath } : {}),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!launch) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"Unable to resolve the GSD workflow MCP server. Build this checkout or install gsd-mcp-server on PATH.",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
command: launch.command,
|
|
66
|
+
...(launch.args && launch.args.length > 0 ? { args: launch.args } : {}),
|
|
67
|
+
...(launch.cwd ? { cwd: launch.cwd } : {}),
|
|
68
|
+
...(launch.env ? { env: launch.env } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readExistingConfig(configPath: string): McpConfigFile {
|
|
73
|
+
if (!existsSync(configPath)) return {};
|
|
74
|
+
|
|
75
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(raw) as McpConfigFile;
|
|
78
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
79
|
+
} catch (err) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Failed to parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function ensureProjectWorkflowMcpConfig(
|
|
87
|
+
projectRoot: string,
|
|
88
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
89
|
+
): EnsureProjectWorkflowMcpConfigResult {
|
|
90
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
91
|
+
assertSafeDirectory(resolvedProjectRoot);
|
|
92
|
+
|
|
93
|
+
const configPath = resolve(resolvedProjectRoot, ".mcp.json");
|
|
94
|
+
const existing = readExistingConfig(configPath);
|
|
95
|
+
const desiredServer = buildProjectWorkflowMcpServerConfig(resolvedProjectRoot, env);
|
|
96
|
+
const previousServers = existing.mcpServers ?? {};
|
|
97
|
+
const nextServers = {
|
|
98
|
+
...previousServers,
|
|
99
|
+
[GSD_WORKFLOW_MCP_SERVER_NAME]: desiredServer,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const alreadyPresent = existsSync(configPath);
|
|
103
|
+
const unchanged =
|
|
104
|
+
JSON.stringify(previousServers[GSD_WORKFLOW_MCP_SERVER_NAME] ?? null)
|
|
105
|
+
=== JSON.stringify(desiredServer)
|
|
106
|
+
&& existing.mcpServers !== undefined;
|
|
107
|
+
|
|
108
|
+
if (unchanged) {
|
|
109
|
+
return {
|
|
110
|
+
configPath,
|
|
111
|
+
serverName: GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
112
|
+
status: "unchanged",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const nextConfig: McpConfigFile = {
|
|
117
|
+
...existing,
|
|
118
|
+
mcpServers: nextServers,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
configPath,
|
|
125
|
+
serverName: GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
126
|
+
status: alreadyPresent ? "updated" : "created",
|
|
127
|
+
};
|
|
128
|
+
}
|