@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.
Files changed (232) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +167 -16
  3. package/dist/resources/extensions/gsd/auto/phases.js +4 -3
  4. package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +39 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
  7. package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
  8. package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
  9. package/dist/resources/extensions/gsd/auto-start.js +94 -15
  10. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
  11. package/dist/resources/extensions/gsd/auto.js +22 -4
  12. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
  13. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
  16. package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
  17. package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
  18. package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
  19. package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
  20. package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
  21. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
  22. package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
  23. package/dist/resources/extensions/gsd/config-overlay.js +2 -1
  24. package/dist/resources/extensions/gsd/error-classifier.js +2 -1
  25. package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
  26. package/dist/resources/extensions/gsd/gsd-db.js +37 -4
  27. package/dist/resources/extensions/gsd/guided-flow.js +1 -1
  28. package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
  29. package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
  30. package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
  31. package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
  32. package/dist/resources/extensions/gsd/prompts/system.md +3 -1
  33. package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
  34. package/dist/resources/extensions/gsd/skill-activation.js +20 -3
  35. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +4 -2
  36. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
  37. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
  38. package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
  39. package/dist/resources/extensions/gsd/state.js +15 -12
  40. package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
  41. package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
  42. package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
  43. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  44. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
  45. package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
  46. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
  47. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -1
  48. package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
  49. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
  50. package/dist/resources/extensions/mcp-client/manager.js +31 -1
  51. package/dist/web/standalone/.next/BUILD_ID +1 -1
  52. package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
  53. package/dist/web/standalone/.next/build-manifest.json +2 -2
  54. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  55. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.html +1 -1
  72. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app-paths-manifest.json +4 -4
  79. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  80. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  82. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  83. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  84. package/package.json +2 -2
  85. package/packages/cloud-mcp-gateway/package.json +2 -2
  86. package/packages/contracts/dist/workflow.d.ts +14 -0
  87. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  88. package/packages/contracts/dist/workflow.js +16 -0
  89. package/packages/contracts/dist/workflow.js.map +1 -1
  90. package/packages/contracts/package.json +1 -1
  91. package/packages/daemon/package.json +4 -4
  92. package/packages/gsd-agent-core/package.json +5 -5
  93. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  94. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  95. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
  96. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  97. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
  98. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  99. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +72 -31
  100. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  101. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.d.ts.map +1 -1
  102. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
  103. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
  104. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  106. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  108. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
  110. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
  112. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
  113. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
  114. package/packages/gsd-agent-modes/package.json +7 -7
  115. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  116. package/packages/mcp-server/dist/workflow-tools.js +82 -0
  117. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  118. package/packages/mcp-server/package.json +3 -3
  119. package/packages/native/package.json +1 -1
  120. package/packages/pi-agent-core/package.json +1 -1
  121. package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
  122. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  123. package/packages/pi-ai/dist/image-models.generated.js +15 -0
  124. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  125. package/packages/pi-ai/dist/models.generated.d.ts +338 -17
  126. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  127. package/packages/pi-ai/dist/models.generated.js +412 -112
  128. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  129. package/packages/pi-ai/package.json +1 -1
  130. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  131. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  133. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  134. package/packages/pi-coding-agent/package.json +7 -7
  135. package/packages/pi-tui/dist/terminal.d.ts +1 -0
  136. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  137. package/packages/pi-tui/dist/terminal.js +8 -4
  138. package/packages/pi-tui/dist/terminal.js.map +1 -1
  139. package/packages/pi-tui/package.json +1 -1
  140. package/packages/rpc-client/package.json +2 -2
  141. package/pkg/package.json +1 -1
  142. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +196 -16
  143. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
  144. package/src/resources/extensions/gsd/auto/phases.ts +5 -3
  145. package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
  146. package/src/resources/extensions/gsd/auto-dispatch.ts +48 -0
  147. package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
  148. package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
  149. package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
  150. package/src/resources/extensions/gsd/auto-start.ts +112 -17
  151. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
  152. package/src/resources/extensions/gsd/auto.ts +35 -3
  153. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
  154. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
  155. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
  156. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
  157. package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
  158. package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
  159. package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
  160. package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
  161. package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
  162. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
  163. package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
  164. package/src/resources/extensions/gsd/config-overlay.ts +3 -1
  165. package/src/resources/extensions/gsd/error-classifier.ts +2 -1
  166. package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
  167. package/src/resources/extensions/gsd/gsd-db.ts +41 -6
  168. package/src/resources/extensions/gsd/guided-flow.ts +1 -1
  169. package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
  170. package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
  171. package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
  172. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  173. package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
  174. package/src/resources/extensions/gsd/prompts/system.md +3 -1
  175. package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
  176. package/src/resources/extensions/gsd/skill-activation.ts +20 -2
  177. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +4 -2
  178. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
  179. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
  180. package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
  181. package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
  182. package/src/resources/extensions/gsd/state.ts +16 -12
  183. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
  184. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
  185. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
  186. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
  187. package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
  188. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
  189. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
  190. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +50 -13
  191. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -0
  192. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
  193. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
  194. package/src/resources/extensions/gsd/tests/gsd-rebuild.test.ts +199 -0
  195. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
  196. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
  197. package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
  198. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
  199. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
  200. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
  201. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
  202. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
  203. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
  204. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
  205. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
  206. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
  207. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
  208. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
  209. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +6 -2
  210. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
  211. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
  212. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
  213. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
  214. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
  215. package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
  216. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +17 -2
  217. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
  218. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
  219. package/src/resources/extensions/gsd/tool-presentation-plan.ts +167 -0
  220. package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
  221. package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
  222. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  223. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
  224. package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
  225. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
  226. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -1
  227. package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
  228. package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
  229. package/src/resources/extensions/mcp-client/manager.ts +33 -1
  230. package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
  231. /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
  232. /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 mcp_discover("${server.name}") to connect and list tools.`);
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
- // Try to get connection/tool info from the mcp-client module if available
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
- toolCount: toolNames.length,
332
- tools: toolNames,
333
- error,
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
- // Build status for each server
346
- const statuses: McpServerStatus[] = [];
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
- toolCount,
372
- error,
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, ["full", "small", "min", "off"], "full");
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 gsd_reassess_roadmap to add remediation slices, then re-run /gsd auto.",
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 !== "full") toggleRows.push({ label: "widget_mode", value: 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
- * Used by park/unpark to keep the DB in sync with the filesystem marker.
1671
- * See: https://github.com/open-gsd/gsd-pi/issues/2694
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
- currentDb.prepare(
1676
- `UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`,
1677
- ).run({ ":status": status, ":completed_at": completedAt ?? null, ":id": milestoneId });
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 unchanged =
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 (unchanged) {
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: "full". */
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;