@opengsd/gsd-pi 1.1.1-dev.616a1a1 → 1.1.1-dev.74e8dd1
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/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +167 -16
- package/dist/resources/extensions/gsd/auto/phases.js +4 -3
- package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
- package/dist/resources/extensions/gsd/auto-dispatch.js +39 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
- package/dist/resources/extensions/gsd/auto-start.js +94 -15
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
- package/dist/resources/extensions/gsd/auto.js +22 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
- package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
- package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
- package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +2 -1
- package/dist/resources/extensions/gsd/error-classifier.js +2 -1
- package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
- package/dist/resources/extensions/gsd/gsd-db.js +37 -4
- package/dist/resources/extensions/gsd/guided-flow.js +1 -1
- package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
- package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
- package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/dist/resources/extensions/gsd/prompts/system.md +3 -1
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
- package/dist/resources/extensions/gsd/skill-activation.js +20 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +4 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
- package/dist/resources/extensions/gsd/state.js +15 -12
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
- package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
- package/dist/resources/extensions/mcp-client/manager.js +31 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- 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 +4 -4
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-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/package.json +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +14 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +16 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +72 -31
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +82 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +15 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +338 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +412 -112
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/terminal.d.ts +1 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +8 -4
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +196 -16
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
- package/src/resources/extensions/gsd/auto/phases.ts +5 -3
- package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
- package/src/resources/extensions/gsd/auto-start.ts +112 -17
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
- package/src/resources/extensions/gsd/auto.ts +35 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
- package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
- package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
- package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +3 -1
- package/src/resources/extensions/gsd/error-classifier.ts +2 -1
- package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
- package/src/resources/extensions/gsd/gsd-db.ts +41 -6
- package/src/resources/extensions/gsd/guided-flow.ts +1 -1
- package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
- package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/src/resources/extensions/gsd/prompts/system.md +3 -1
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
- package/src/resources/extensions/gsd/skill-activation.ts +20 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +4 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
- package/src/resources/extensions/gsd/state.ts +16 -12
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
- package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +50 -13
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/gsd-rebuild.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
- package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +17 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +167 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
- package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
- package/src/resources/extensions/mcp-client/manager.ts +33 -1
- package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
|
@@ -7,6 +7,7 @@
|
|
|
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 discover [srv] — Connect and list tools for a server
|
|
10
11
|
* /gsd mcp test <srv> — Test handshake + tools/list for a server
|
|
11
12
|
* /gsd mcp enable <srv> / disable <srv> — Toggle local server exposure
|
|
12
13
|
* /gsd mcp import <srv> [as <name>] — Copy a discovered server into local config
|
|
@@ -34,6 +35,7 @@ export interface McpServerStatus {
|
|
|
34
35
|
name: string;
|
|
35
36
|
transport: ManagedMcpTransport;
|
|
36
37
|
connected: boolean;
|
|
38
|
+
available?: boolean;
|
|
37
39
|
toolCount: number;
|
|
38
40
|
error: string | undefined;
|
|
39
41
|
disabled?: boolean;
|
|
@@ -45,6 +47,8 @@ export interface McpServerDetail extends McpServerStatus {
|
|
|
45
47
|
tools: string[];
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
const MCP_STATUS_PROBE_TIMEOUT_MS = 10_000;
|
|
51
|
+
|
|
48
52
|
export function hasHostMcpTool(systemPrompt: string, serverName: string): boolean {
|
|
49
53
|
const marker = `mcp__${serverName}__`;
|
|
50
54
|
return systemPrompt.includes(marker);
|
|
@@ -89,13 +93,15 @@ export function formatMcpStatusReport(servers: McpServerStatus[]): string {
|
|
|
89
93
|
const lines: string[] = [`MCP Server Status — ${servers.length} server(s)\n`];
|
|
90
94
|
|
|
91
95
|
for (const s of servers) {
|
|
92
|
-
const icon = s.disabled ? "⊘" : s.error ? "✗" : s.connected ? "✓" : "○";
|
|
96
|
+
const icon = s.disabled ? "⊘" : s.error ? "✗" : s.connected || s.available ? "✓" : "○";
|
|
93
97
|
const status = s.disabled
|
|
94
98
|
? "disabled"
|
|
95
99
|
: s.error
|
|
96
100
|
? `error: ${s.error}`
|
|
97
101
|
: s.connected
|
|
98
102
|
? `connected — ${s.toolCount} tools`
|
|
103
|
+
: s.available
|
|
104
|
+
? `available — ${s.toolCount} tools`
|
|
99
105
|
: "disconnected";
|
|
100
106
|
const warningText = s.envWarnings?.length ? ` — ${s.envWarnings.length} warning(s)` : "";
|
|
101
107
|
lines.push(` ${icon} ${s.name} (${s.transport}) — ${status}${warningText}`);
|
|
@@ -103,8 +109,8 @@ export function formatMcpStatusReport(servers: McpServerStatus[]): string {
|
|
|
103
109
|
|
|
104
110
|
lines.push("");
|
|
105
111
|
lines.push("Use /gsd mcp check <server> for details on a specific server.");
|
|
112
|
+
lines.push("Use /gsd mcp discover <server> to connect and list tools.");
|
|
106
113
|
lines.push("Use /gsd mcp test <server> to verify handshake and tool discovery.");
|
|
107
|
-
lines.push("Use mcp_discover to connect and list tools for a server.");
|
|
108
114
|
|
|
109
115
|
return lines.join("\n");
|
|
110
116
|
}
|
|
@@ -120,8 +126,8 @@ export function formatMcpServerDetail(server: McpServerDetail): string {
|
|
|
120
126
|
} else if (server.error) {
|
|
121
127
|
lines.push(` Status: error`);
|
|
122
128
|
lines.push(` Error: ${server.error}`);
|
|
123
|
-
} else if (server.connected) {
|
|
124
|
-
lines.push(` Status: connected`);
|
|
129
|
+
} else if (server.connected || server.available) {
|
|
130
|
+
lines.push(` Status: ${server.connected ? "connected" : "available"}`);
|
|
125
131
|
lines.push(` Tools: ${server.toolCount}`);
|
|
126
132
|
if (server.tools.length > 0) {
|
|
127
133
|
lines.push("");
|
|
@@ -133,7 +139,7 @@ export function formatMcpServerDetail(server: McpServerDetail): string {
|
|
|
133
139
|
} else {
|
|
134
140
|
lines.push(` Status: disconnected`);
|
|
135
141
|
lines.push("");
|
|
136
|
-
lines.push(` Run
|
|
142
|
+
lines.push(` Run /gsd mcp discover ${server.name} to connect and list tools.`);
|
|
137
143
|
}
|
|
138
144
|
|
|
139
145
|
if (server.envWarnings?.length) {
|
|
@@ -167,10 +173,93 @@ export function formatMcpConnectionTestResult(result: ManagedMcpConnectionTestRe
|
|
|
167
173
|
].join("\n");
|
|
168
174
|
}
|
|
169
175
|
|
|
176
|
+
export function formatMcpDiscoveryResult(result: ManagedMcpConnectionTestResult): string {
|
|
177
|
+
if (result.ok) {
|
|
178
|
+
return [
|
|
179
|
+
`MCP discovery completed for ${result.server}.`,
|
|
180
|
+
"",
|
|
181
|
+
`Transport: ${result.transport}`,
|
|
182
|
+
`Tools: ${result.toolCount}`,
|
|
183
|
+
...(result.tools.length > 0 ? ["", "Discovered tools:", ...result.tools.map((tool) => ` - ${tool}`)] : []),
|
|
184
|
+
"",
|
|
185
|
+
`Call with: mcp_call(server="${result.server}", tool="<tool_name>", args={...})`,
|
|
186
|
+
].join("\n");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return [
|
|
190
|
+
`MCP discovery failed for ${result.server}.`,
|
|
191
|
+
"",
|
|
192
|
+
`Transport: ${result.transport}`,
|
|
193
|
+
`Error: ${result.error ?? "Unknown error"}`,
|
|
194
|
+
...(result.warnings.length > 0 ? ["", "Warnings:", ...result.warnings.map((warning) => ` - ${warning}`)] : []),
|
|
195
|
+
].join("\n");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function readLiveConnectionStatus(serverName: string): Promise<{
|
|
199
|
+
connected: boolean;
|
|
200
|
+
tools: string[];
|
|
201
|
+
error?: string;
|
|
202
|
+
}> {
|
|
203
|
+
try {
|
|
204
|
+
const mcpClient = await import("../mcp-client/index.js");
|
|
205
|
+
const mod = mcpClient as Record<string, unknown>;
|
|
206
|
+
if (typeof mod.getConnectionStatus === "function") {
|
|
207
|
+
return (mod.getConnectionStatus as (name: string) => { connected: boolean; tools: string[]; error?: string })(serverName);
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
// mcp-client may not expose status helpers in some hosts.
|
|
211
|
+
}
|
|
212
|
+
return { connected: false, tools: [] };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function shouldProbeConfiguredServer(config: { disabled?: boolean; transport: ManagedMcpTransport; envWarnings?: string[] }): boolean {
|
|
216
|
+
return !config.disabled && config.transport === "stdio" && (config.envWarnings?.length ?? 0) === 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function resolveMcpRuntimeStatus(
|
|
220
|
+
config: {
|
|
221
|
+
name: string;
|
|
222
|
+
transport: ManagedMcpTransport;
|
|
223
|
+
disabled?: boolean;
|
|
224
|
+
envWarnings?: string[];
|
|
225
|
+
},
|
|
226
|
+
systemPrompt: string,
|
|
227
|
+
): Promise<{
|
|
228
|
+
connected: boolean;
|
|
229
|
+
available: boolean;
|
|
230
|
+
tools: string[];
|
|
231
|
+
error?: string;
|
|
232
|
+
}> {
|
|
233
|
+
const live = await readLiveConnectionStatus(config.name);
|
|
234
|
+
let connected = live.connected;
|
|
235
|
+
let tools = live.tools;
|
|
236
|
+
let error = live.error;
|
|
237
|
+
|
|
238
|
+
if (!connected && !error && hasHostMcpTool(systemPrompt, config.name)) connected = true;
|
|
239
|
+
|
|
240
|
+
if (!connected && !error && shouldProbeConfiguredServer(config)) {
|
|
241
|
+
const probed = await testMcpServerConnection(config.name, { timeoutMs: MCP_STATUS_PROBE_TIMEOUT_MS });
|
|
242
|
+
if (probed.ok) {
|
|
243
|
+
return {
|
|
244
|
+
connected: false,
|
|
245
|
+
available: true,
|
|
246
|
+
tools: probed.tools,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
error = probed.error;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
connected,
|
|
254
|
+
available: false,
|
|
255
|
+
tools,
|
|
256
|
+
error,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
170
259
|
// ─── Command handler ────────────────────────────────────────────────────────
|
|
171
260
|
|
|
172
261
|
/**
|
|
173
|
-
* Handle `/gsd mcp [status|check <server
|
|
262
|
+
* Handle `/gsd mcp [status|check <server>|discover [server]|...]`.
|
|
174
263
|
*/
|
|
175
264
|
export async function handleMcpStatus(
|
|
176
265
|
args: string,
|
|
@@ -200,6 +289,28 @@ export async function handleMcpStatus(
|
|
|
200
289
|
return;
|
|
201
290
|
}
|
|
202
291
|
|
|
292
|
+
// /gsd mcp discover [server]
|
|
293
|
+
if (lowered === "discover" || lowered.startsWith("discover ")) {
|
|
294
|
+
const requestedServerName = trimmed.slice("discover".length).trim();
|
|
295
|
+
let serverName = requestedServerName;
|
|
296
|
+
if (!serverName) {
|
|
297
|
+
if (configs.length === 1) {
|
|
298
|
+
serverName = configs[0]!.name;
|
|
299
|
+
} else {
|
|
300
|
+
const available = configs.map((config) => config.name).join(", ") || "(none)";
|
|
301
|
+
ctx.ui.notify(
|
|
302
|
+
`Usage: /gsd mcp discover <server>\n\nAvailable: ${available}`,
|
|
303
|
+
"warning",
|
|
304
|
+
);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const result = await testMcpServerConnection(serverName);
|
|
310
|
+
ctx.ui.notify(formatMcpDiscoveryResult(result), result.ok ? "info" : "warning");
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
203
314
|
// /gsd mcp test <server>
|
|
204
315
|
if (lowered.startsWith("test ")) {
|
|
205
316
|
const serverName = trimmed.slice("test ".length).trim();
|
|
@@ -304,33 +415,17 @@ export async function handleMcpStatus(
|
|
|
304
415
|
return;
|
|
305
416
|
}
|
|
306
417
|
|
|
307
|
-
|
|
308
|
-
let connected = false;
|
|
309
|
-
let toolNames: string[] = [];
|
|
310
|
-
let error: string | undefined;
|
|
311
|
-
try {
|
|
312
|
-
const mcpClient = await import("../mcp-client/index.js");
|
|
313
|
-
// Access the module's connection state if exported; fall back gracefully
|
|
314
|
-
const mod = mcpClient as Record<string, unknown>;
|
|
315
|
-
if (typeof mod.getConnectionStatus === "function") {
|
|
316
|
-
const status = (mod.getConnectionStatus as (name: string) => { connected: boolean; tools: string[]; error?: string })(serverName);
|
|
317
|
-
connected = status.connected;
|
|
318
|
-
toolNames = status.tools;
|
|
319
|
-
error = status.error;
|
|
320
|
-
}
|
|
321
|
-
} catch {
|
|
322
|
-
// mcp-client may not expose status helpers — that's fine
|
|
323
|
-
}
|
|
324
|
-
if (!connected && !error && hasHostMcpTool(systemPrompt, serverName)) connected = true;
|
|
418
|
+
const runtime = await resolveMcpRuntimeStatus(config, systemPrompt);
|
|
325
419
|
|
|
326
420
|
ctx.ui.notify(
|
|
327
421
|
formatMcpServerDetail({
|
|
328
422
|
name: config.name,
|
|
329
423
|
transport: config.transport,
|
|
330
|
-
connected,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
424
|
+
connected: runtime.connected,
|
|
425
|
+
available: runtime.available,
|
|
426
|
+
toolCount: runtime.tools.length,
|
|
427
|
+
tools: runtime.tools,
|
|
428
|
+
error: runtime.error,
|
|
334
429
|
disabled: config.disabled,
|
|
335
430
|
sourcePath: config.sourcePath,
|
|
336
431
|
envWarnings: config.envWarnings,
|
|
@@ -342,39 +437,20 @@ export async function handleMcpStatus(
|
|
|
342
437
|
|
|
343
438
|
// /gsd mcp or /gsd mcp status
|
|
344
439
|
if (!lowered || lowered === "status") {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
for (const config of configs) {
|
|
349
|
-
let connected = false;
|
|
350
|
-
let toolCount = 0;
|
|
351
|
-
let error: string | undefined;
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
const mcpClient = await import("../mcp-client/index.js");
|
|
355
|
-
const mod = mcpClient as Record<string, unknown>;
|
|
356
|
-
if (typeof mod.getConnectionStatus === "function") {
|
|
357
|
-
const status = (mod.getConnectionStatus as (name: string) => { connected: boolean; tools: string[]; error?: string })(config.name);
|
|
358
|
-
connected = status.connected;
|
|
359
|
-
toolCount = status.tools.length;
|
|
360
|
-
error = status.error;
|
|
361
|
-
}
|
|
362
|
-
} catch {
|
|
363
|
-
// Fall back to unknown state
|
|
364
|
-
}
|
|
365
|
-
if (!connected && !error && hasHostMcpTool(systemPrompt, config.name)) connected = true;
|
|
366
|
-
|
|
367
|
-
statuses.push({
|
|
440
|
+
const statuses: McpServerStatus[] = await Promise.all(configs.map(async (config) => {
|
|
441
|
+
const runtime = await resolveMcpRuntimeStatus(config, systemPrompt);
|
|
442
|
+
return {
|
|
368
443
|
name: config.name,
|
|
369
444
|
transport: config.transport,
|
|
370
|
-
connected,
|
|
371
|
-
|
|
372
|
-
|
|
445
|
+
connected: runtime.connected,
|
|
446
|
+
available: runtime.available,
|
|
447
|
+
toolCount: runtime.tools.length,
|
|
448
|
+
error: runtime.error,
|
|
373
449
|
disabled: config.disabled,
|
|
374
450
|
sourcePath: config.sourcePath,
|
|
375
451
|
envWarnings: config.envWarnings,
|
|
376
|
-
}
|
|
377
|
-
}
|
|
452
|
+
};
|
|
453
|
+
}));
|
|
378
454
|
|
|
379
455
|
const warningLines = [
|
|
380
456
|
...management.warnings,
|
|
@@ -389,9 +465,10 @@ export async function handleMcpStatus(
|
|
|
389
465
|
|
|
390
466
|
// Unknown subcommand
|
|
391
467
|
ctx.ui.notify(
|
|
392
|
-
"Usage: /gsd mcp [status|check <server>|test <server>|enable <server>|disable <server>|delete <server> --confirm|import <server> [as <name>]|init [dir]]\n\n" +
|
|
468
|
+
"Usage: /gsd mcp [status|check <server>|discover [server]|test <server>|enable <server>|disable <server>|delete <server> --confirm|import <server> [as <name>]|init [dir]]\n\n" +
|
|
393
469
|
" status Show all MCP server statuses (default)\n" +
|
|
394
470
|
" check <server> Detailed status for a specific server\n" +
|
|
471
|
+
" discover [server] Connect and list tools for a server\n" +
|
|
395
472
|
" test <server> Verify MCP handshake and tools/list\n" +
|
|
396
473
|
" enable <server> Enable a local GSD-managed server\n" +
|
|
397
474
|
" disable <server> Disable a local GSD-managed server\n" +
|
|
@@ -23,6 +23,9 @@ import {
|
|
|
23
23
|
import { loadFile, saveFile, splitFrontmatter, parseFrontmatterMap } from "./files.js";
|
|
24
24
|
import { runClaudeImportFlow } from "./claude-import.js";
|
|
25
25
|
|
|
26
|
+
const DEFAULT_WIDGET_MODE = "small";
|
|
27
|
+
const WIDGET_MODE_OPTIONS = [DEFAULT_WIDGET_MODE, "full", "min", "off"] as const;
|
|
28
|
+
|
|
26
29
|
/** Extract body content after frontmatter closing delimiter, or null if none. */
|
|
27
30
|
function extractBodyAfterFrontmatter(content: string): string | null {
|
|
28
31
|
const closingIdx = content.indexOf("\n---", content.indexOf("---"));
|
|
@@ -1558,7 +1561,7 @@ async function configureAdvanced(ctx: ExtensionCommandContext, prefs: Record<str
|
|
|
1558
1561
|
prefs.min_request_interval_ms = minRequestInterval;
|
|
1559
1562
|
}
|
|
1560
1563
|
|
|
1561
|
-
const widget = await promptEnum(ctx, "Auto-mode widget display", prefs.widget_mode,
|
|
1564
|
+
const widget = await promptEnum(ctx, "Auto-mode widget display", prefs.widget_mode, WIDGET_MODE_OPTIONS, DEFAULT_WIDGET_MODE);
|
|
1562
1565
|
if (widget !== undefined) prefs.widget_mode = widget;
|
|
1563
1566
|
|
|
1564
1567
|
const experimental = (prefs.experimental as Record<string, unknown> | undefined) ?? {};
|
|
@@ -238,7 +238,7 @@ export async function handleVerdict(
|
|
|
238
238
|
|
|
239
239
|
if (effectiveVerdict === "needs-remediation") {
|
|
240
240
|
ctx.ui.notify(
|
|
241
|
-
"Follow up with
|
|
241
|
+
"Follow up with /gsd dispatch reassess to add remediation slices, then re-run /gsd auto.",
|
|
242
242
|
"info",
|
|
243
243
|
);
|
|
244
244
|
}
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
resolveAutoSupervisorConfig,
|
|
24
24
|
} from "./preferences.js";
|
|
25
25
|
|
|
26
|
+
const DEFAULT_WIDGET_MODE = "small";
|
|
27
|
+
|
|
26
28
|
// ─── Data Collection ──────────────────────────────────────────────────────
|
|
27
29
|
|
|
28
30
|
interface ConfigSection {
|
|
@@ -160,7 +162,7 @@ function collectConfigSections(): ConfigSection[] {
|
|
|
160
162
|
if (prefs?.service_tier) toggleRows.push({ label: "service_tier", value: prefs.service_tier });
|
|
161
163
|
if (prefs?.search_provider && prefs.search_provider !== "auto") toggleRows.push({ label: "search_provider", value: prefs.search_provider });
|
|
162
164
|
if (prefs?.context_selection) toggleRows.push({ label: "context_selection", value: prefs.context_selection });
|
|
163
|
-
if (prefs?.widget_mode && prefs.widget_mode !==
|
|
165
|
+
if (prefs?.widget_mode && prefs.widget_mode !== DEFAULT_WIDGET_MODE) toggleRows.push({ label: "widget_mode", value: prefs.widget_mode });
|
|
164
166
|
if (prefs?.experimental?.rtk) toggleRows.push({ label: "experimental.rtk", value: "on" });
|
|
165
167
|
if (toggleRows.length > 0) sections.push({ title: "Toggles", rows: toggleRows });
|
|
166
168
|
|
|
@@ -47,9 +47,10 @@ export function resetRetryState(state: RetryState): void {
|
|
|
47
47
|
const PERMANENT_RE = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|billing|quota exceeded|account/i;
|
|
48
48
|
// Include provider-specific quota-window phrasing like:
|
|
49
49
|
// - "You've hit your limit"
|
|
50
|
+
// - "You've reached your limit"
|
|
50
51
|
// - "usage limit" / "quota reached"
|
|
51
52
|
// - "out of extra usage"
|
|
52
|
-
const RATE_LIMIT_RE = /rate.?limit|too many requests|429|hit your limit|usage limit|out of extra usage|quota (?:reached|hit)|limit.*resets?/i;
|
|
53
|
+
const RATE_LIMIT_RE = /rate.?limit|too many requests|429|(?:hit|reached) your (?:\w+ )?limit|(?:usage|session|weekly|daily|monthly|quota) limit|out of extra usage|quota (?:reached|hit)|limit.*resets?/i;
|
|
53
54
|
// OpenRouter affordability-style quota errors should be treated as transient
|
|
54
55
|
// so core retry logic can lower maxTokens and continue in-session.
|
|
55
56
|
const AFFORDABILITY_RE = /requires more credits|can only afford|insufficient credits|not enough credits|fewer max_tokens/i;
|
|
@@ -20,6 +20,8 @@ export interface ExecSandboxRequest {
|
|
|
20
20
|
script: string;
|
|
21
21
|
/** Optional purpose/label recorded in meta.json. */
|
|
22
22
|
purpose?: string;
|
|
23
|
+
/** Optional structured metadata recorded in meta.json. */
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
23
25
|
/** Per-invocation timeout in ms. Clamped to `clamp_timeout_ms`. */
|
|
24
26
|
timeout_ms?: number;
|
|
25
27
|
}
|
|
@@ -315,6 +317,7 @@ function writeMeta(
|
|
|
315
317
|
id: result.id,
|
|
316
318
|
runtime: result.runtime,
|
|
317
319
|
purpose: request.purpose ?? null,
|
|
320
|
+
...(request.metadata ? { metadata: request.metadata } : {}),
|
|
318
321
|
script_chars: request.script.length,
|
|
319
322
|
started_at: now.toISOString(),
|
|
320
323
|
finished_at: new Date(now.getTime() + result.duration_ms).toISOString(),
|
|
@@ -328,6 +331,7 @@ function writeMeta(
|
|
|
328
331
|
stderr_truncated: result.stderr_truncated,
|
|
329
332
|
stdout_path: result.stdout_path,
|
|
330
333
|
stderr_path: result.stderr_path,
|
|
334
|
+
...(request.metadata ? { metadata: request.metadata } : {}),
|
|
331
335
|
};
|
|
332
336
|
writeFileSync(path, `${JSON.stringify(meta, null, 2)}\n`);
|
|
333
337
|
}
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
import { rowToGate } from "./db-gate-rows.js";
|
|
56
56
|
import { rowToArtifact, rowToMilestone, type ArtifactRow, type MilestoneRow } from "./db-milestone-artifact-rows.js";
|
|
57
57
|
import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
|
|
58
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
58
59
|
import {
|
|
59
60
|
applyMigrationV2Artifacts,
|
|
60
61
|
applyMigrationV3Memories,
|
|
@@ -1665,16 +1666,50 @@ export function setMilestoneQueueOrder(order: string[]): void {
|
|
|
1665
1666
|
}
|
|
1666
1667
|
}
|
|
1667
1668
|
|
|
1669
|
+
function getMilestoneStatusForUpdate(milestoneId: string): string | null {
|
|
1670
|
+
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1671
|
+
const row = currentDb.prepare("SELECT status FROM milestones WHERE id = :id").get({ ":id": milestoneId });
|
|
1672
|
+
return typeof row?.["status"] === "string" ? row["status"] : null;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
function writeMilestoneStatus(milestoneId: string, status: string, completedAt?: string | null): void {
|
|
1676
|
+
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1677
|
+
currentDb.prepare(
|
|
1678
|
+
`UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`,
|
|
1679
|
+
).run({ ":status": status, ":completed_at": completedAt ?? null, ":id": milestoneId });
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1668
1682
|
/**
|
|
1669
1683
|
* Update a milestone's status in the database.
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1684
|
+
*
|
|
1685
|
+
* Generic status updates may close milestones, park/unpark open milestones, or
|
|
1686
|
+
* advance planned milestones. They may not reopen a closed milestone; callers
|
|
1687
|
+
* must use reopenMilestoneStatus(), which is reserved for gsd_milestone_reopen.
|
|
1672
1688
|
*/
|
|
1673
1689
|
export function updateMilestoneStatus(milestoneId: string, status: string, completedAt?: string | null): void {
|
|
1674
1690
|
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1691
|
+
const currentStatus = getMilestoneStatusForUpdate(milestoneId);
|
|
1692
|
+
if (currentStatus && isClosedStatus(currentStatus) && !isClosedStatus(status)) {
|
|
1693
|
+
throw new Error(
|
|
1694
|
+
`Cannot update closed milestone ${milestoneId} from ${currentStatus} to ${status}; use gsd_milestone_reopen for an explicit reopen.`,
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
writeMilestoneStatus(milestoneId, status, completedAt);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Explicit closed -> active transition for gsd_milestone_reopen only.
|
|
1702
|
+
*/
|
|
1703
|
+
export function reopenMilestoneStatus(milestoneId: string): void {
|
|
1704
|
+
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1705
|
+
const currentStatus = getMilestoneStatusForUpdate(milestoneId);
|
|
1706
|
+
if (!currentStatus) {
|
|
1707
|
+
throw new Error(`Cannot reopen missing milestone ${milestoneId}`);
|
|
1708
|
+
}
|
|
1709
|
+
if (!isClosedStatus(currentStatus)) {
|
|
1710
|
+
throw new Error(`Cannot reopen milestone ${milestoneId} from status ${currentStatus}; milestone is not closed.`);
|
|
1711
|
+
}
|
|
1712
|
+
writeMilestoneStatus(milestoneId, "active", null);
|
|
1678
1713
|
}
|
|
1679
1714
|
|
|
1680
1715
|
export function getActiveMilestoneFromDb(): MilestoneRow | null {
|
|
@@ -2708,7 +2743,7 @@ export function deleteArtifactByPath(path: string): void {
|
|
|
2708
2743
|
|
|
2709
2744
|
/**
|
|
2710
2745
|
* Drop hierarchy rows in dependency order inside a transaction. Used by
|
|
2711
|
-
* `gsd recover` to rebuild engine state from markdown.
|
|
2746
|
+
* `gsd recover --confirm` to rebuild engine state from markdown.
|
|
2712
2747
|
*/
|
|
2713
2748
|
export function clearEngineHierarchy(): void {
|
|
2714
2749
|
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
@@ -2374,7 +2374,7 @@ export async function showSmartEntry(
|
|
|
2374
2374
|
if (result.action === "recovery-required") {
|
|
2375
2375
|
ctx.ui.notify(
|
|
2376
2376
|
result.message ??
|
|
2377
|
-
`Markdown planning artifacts do not match the authoritative DB. Run \`${result.recoveryCommand ?? "/gsd recover"}\` to import markdown explicitly.`,
|
|
2377
|
+
`Markdown planning artifacts do not match the authoritative DB. Run \`${result.recoveryCommand ?? "/gsd recover --confirm"}\` to import markdown explicitly.`,
|
|
2378
2378
|
"warning",
|
|
2379
2379
|
);
|
|
2380
2380
|
}
|
|
@@ -40,9 +40,11 @@ function collectServerEntries(servers: unknown): DiscoveredMcpServer[] {
|
|
|
40
40
|
export function discoverMcpServers(projectDir: string): DiscoveredMcpServer[] {
|
|
41
41
|
const mcpJsonPath = resolve(projectDir, ".mcp.json");
|
|
42
42
|
const settingsPath = resolve(projectDir, ".claude", "settings.json");
|
|
43
|
+
const localSettingsPath = resolve(projectDir, ".claude", "settings.local.json");
|
|
43
44
|
|
|
44
45
|
const mcpJson = readJsonFile(mcpJsonPath) as McpJsonFile | undefined;
|
|
45
46
|
const settings = readJsonFile(settingsPath, true) as ClaudeSettingsFile | undefined;
|
|
47
|
+
const localSettings = readJsonFile(localSettingsPath, true) as ClaudeSettingsFile | undefined;
|
|
46
48
|
|
|
47
49
|
const seen = new Set<string>();
|
|
48
50
|
const discovered: DiscoveredMcpServer[] = [];
|
|
@@ -50,6 +52,7 @@ export function discoverMcpServers(projectDir: string): DiscoveredMcpServer[] {
|
|
|
50
52
|
...collectServerEntries(mcpJson?.mcpServers),
|
|
51
53
|
...collectServerEntries(mcpJson?.servers),
|
|
52
54
|
...collectServerEntries(settings?.mcpServers),
|
|
55
|
+
...collectServerEntries(localSettings?.mcpServers),
|
|
53
56
|
]) {
|
|
54
57
|
if (seen.has(entry.name)) continue;
|
|
55
58
|
seen.add(entry.name);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { basename, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -36,6 +36,12 @@ interface McpConfigFile {
|
|
|
36
36
|
[key: string]: unknown;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
interface ClaudeCodeLocalSettingsFile {
|
|
40
|
+
enabledMcpjsonServers?: unknown;
|
|
41
|
+
disabledMcpjsonServers?: unknown;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
export function resolveBundledGsdCliPath(env: NodeJS.ProcessEnv = process.env): string | null {
|
|
40
46
|
const explicit = env.GSD_CLI_PATH?.trim() || env.GSD_BIN_PATH?.trim();
|
|
41
47
|
if (explicit) return explicit;
|
|
@@ -223,6 +229,78 @@ function readExistingConfig(configPath: string): McpConfigFile {
|
|
|
223
229
|
}
|
|
224
230
|
}
|
|
225
231
|
|
|
232
|
+
function readExistingClaudeCodeSettings(settingsPath: string): ClaudeCodeLocalSettingsFile {
|
|
233
|
+
if (!existsSync(settingsPath)) return {};
|
|
234
|
+
|
|
235
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
236
|
+
try {
|
|
237
|
+
const parsed = JSON.parse(raw) as ClaudeCodeLocalSettingsFile;
|
|
238
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
239
|
+
} catch (err) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`Failed to parse ${settingsPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function ensureClaudeCodeMcpJsonServersEnabled(
|
|
247
|
+
projectRoot: string,
|
|
248
|
+
serverNames: string[],
|
|
249
|
+
): boolean {
|
|
250
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
251
|
+
assertSafeDirectory(resolvedProjectRoot);
|
|
252
|
+
|
|
253
|
+
const targetServerNames = [...new Set(serverNames.filter((name) => name.trim().length > 0))];
|
|
254
|
+
if (targetServerNames.length === 0) return false;
|
|
255
|
+
|
|
256
|
+
const settingsDir = resolve(resolvedProjectRoot, ".claude");
|
|
257
|
+
const settingsPath = resolve(settingsDir, "settings.local.json");
|
|
258
|
+
const existing = readExistingClaudeCodeSettings(settingsPath);
|
|
259
|
+
|
|
260
|
+
const enabled = Array.isArray(existing.enabledMcpjsonServers)
|
|
261
|
+
? [...existing.enabledMcpjsonServers]
|
|
262
|
+
: [];
|
|
263
|
+
const enabledNames = new Set(enabled.filter((value): value is string => typeof value === "string"));
|
|
264
|
+
let changed = !Array.isArray(existing.enabledMcpjsonServers);
|
|
265
|
+
|
|
266
|
+
for (const serverName of targetServerNames) {
|
|
267
|
+
if (!enabledNames.has(serverName)) {
|
|
268
|
+
enabled.push(serverName);
|
|
269
|
+
enabledNames.add(serverName);
|
|
270
|
+
changed = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let nextDisabled = existing.disabledMcpjsonServers;
|
|
275
|
+
if (Array.isArray(existing.disabledMcpjsonServers)) {
|
|
276
|
+
const blockedNames = new Set(targetServerNames);
|
|
277
|
+
const filtered = existing.disabledMcpjsonServers.filter((value) => !blockedNames.has(String(value)));
|
|
278
|
+
if (filtered.length !== existing.disabledMcpjsonServers.length) {
|
|
279
|
+
nextDisabled = filtered;
|
|
280
|
+
changed = true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!changed) return false;
|
|
285
|
+
|
|
286
|
+
const nextSettings: ClaudeCodeLocalSettingsFile = {
|
|
287
|
+
...existing,
|
|
288
|
+
enabledMcpjsonServers: enabled,
|
|
289
|
+
...(Array.isArray(existing.disabledMcpjsonServers) ? { disabledMcpjsonServers: nextDisabled } : {}),
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
293
|
+
writeFileSync(settingsPath, `${JSON.stringify(nextSettings, null, 2)}\n`, "utf-8");
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function ensureClaudeCodeMcpJsonServerEnabled(
|
|
298
|
+
projectRoot: string,
|
|
299
|
+
serverName: string,
|
|
300
|
+
): boolean {
|
|
301
|
+
return ensureClaudeCodeMcpJsonServersEnabled(projectRoot, [serverName]);
|
|
302
|
+
}
|
|
303
|
+
|
|
226
304
|
export function ensureProjectWorkflowMcpConfig(
|
|
227
305
|
projectRoot: string,
|
|
228
306
|
env: NodeJS.ProcessEnv = process.env,
|
|
@@ -241,14 +319,25 @@ export function ensureProjectWorkflowMcpConfig(
|
|
|
241
319
|
const desiredServerNames = Object.keys(desiredServers);
|
|
242
320
|
|
|
243
321
|
const alreadyPresent = existsSync(configPath);
|
|
244
|
-
const
|
|
322
|
+
const mcpConfigUnchanged =
|
|
245
323
|
desiredServerNames.every((serverName) => (
|
|
246
324
|
JSON.stringify(previousServers[serverName] ?? null)
|
|
247
325
|
=== JSON.stringify(desiredServers[serverName])
|
|
248
326
|
))
|
|
249
327
|
&& existing.mcpServers !== undefined;
|
|
250
328
|
|
|
251
|
-
if (
|
|
329
|
+
if (!mcpConfigUnchanged) {
|
|
330
|
+
const nextConfig: McpConfigFile = {
|
|
331
|
+
...existing,
|
|
332
|
+
mcpServers: nextServers,
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const localSettingsChanged = ensureClaudeCodeMcpJsonServersEnabled(resolvedProjectRoot, desiredServerNames);
|
|
339
|
+
|
|
340
|
+
if (mcpConfigUnchanged && !localSettingsChanged) {
|
|
252
341
|
return {
|
|
253
342
|
configPath,
|
|
254
343
|
serverName: workflowServerName,
|
|
@@ -257,13 +346,6 @@ export function ensureProjectWorkflowMcpConfig(
|
|
|
257
346
|
};
|
|
258
347
|
}
|
|
259
348
|
|
|
260
|
-
const nextConfig: McpConfigFile = {
|
|
261
|
-
...existing,
|
|
262
|
-
mcpServers: nextServers,
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
|
|
266
|
-
|
|
267
349
|
return {
|
|
268
350
|
configPath,
|
|
269
351
|
serverName: workflowServerName,
|
|
@@ -118,10 +118,10 @@ export async function checkMarkdownHierarchyAgainstDb(
|
|
|
118
118
|
markdown,
|
|
119
119
|
beforeDb,
|
|
120
120
|
afterDb: beforeDb,
|
|
121
|
-
recoveryCommand: "/gsd recover",
|
|
121
|
+
recoveryCommand: "/gsd recover --confirm",
|
|
122
122
|
message:
|
|
123
123
|
`Markdown planning artifacts (${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T) ` +
|
|
124
124
|
`do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). ` +
|
|
125
|
-
"Runtime startup will not import markdown automatically; run `/gsd recover` if markdown should repopulate the database.",
|
|
125
|
+
"Runtime startup will not import markdown automatically; run `/gsd recover --confirm` if markdown should repopulate the database.",
|
|
126
126
|
};
|
|
127
127
|
}
|
|
@@ -423,7 +423,7 @@ export interface GSDPreferences {
|
|
|
423
423
|
search_provider?: "brave" | "tavily" | "ollama" | "native" | "auto";
|
|
424
424
|
/** Context selection mode for file inlining. "full" inlines entire files, "smart" uses semantic chunking. Default derived from token profile. */
|
|
425
425
|
context_selection?: ContextSelectionMode;
|
|
426
|
-
/** Default widget display mode for auto-mode dashboard. "full" | "small" | "min" | "off". Default: "
|
|
426
|
+
/** Default widget display mode for auto-mode dashboard. "full" | "small" | "min" | "off". Default: "small". */
|
|
427
427
|
widget_mode?: "full" | "small" | "min" | "off";
|
|
428
428
|
/** Reactive (graph-derived parallel) task execution within slices. Disabled by default. */
|
|
429
429
|
reactive_execution?: ReactiveExecutionConfig;
|