gsd-pi 2.70.0 → 2.70.1-dev.bef631a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/dist/loader.js +4 -0
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -2
  3. package/dist/resources/extensions/gsd/auto-model-selection.js +33 -19
  4. package/dist/resources/extensions/gsd/auto-prompts.js +7 -3
  5. package/dist/resources/extensions/gsd/auto-start.js +28 -12
  6. package/dist/resources/extensions/gsd/auto.js +12 -8
  7. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -0
  8. package/dist/resources/extensions/gsd/commands-handlers.js +22 -8
  9. package/dist/resources/extensions/gsd/doctor-engine-checks.js +12 -0
  10. package/dist/resources/extensions/gsd/doctor-format.js +2 -0
  11. package/dist/resources/extensions/gsd/guided-flow.js +33 -20
  12. package/dist/resources/extensions/gsd/init-wizard.js +3 -11
  13. package/dist/resources/extensions/gsd/pre-execution-checks.js +5 -3
  14. package/dist/resources/extensions/gsd/prompts/discuss.md +31 -13
  15. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +34 -0
  16. package/dist/resources/extensions/gsd/validate-directory.js +30 -12
  17. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +56 -0
  18. package/dist/resources/extensions/gsd/workflow-mcp.js +12 -1
  19. package/dist/resources/extensions/slash-commands/audit.js +2 -1
  20. package/dist/resources/extensions/subagent/isolation.js +4 -2
  21. package/dist/update-check.d.ts +1 -0
  22. package/dist/update-check.js +30 -27
  23. package/dist/update-cmd.js +3 -11
  24. package/dist/web/standalone/.next/BUILD_ID +1 -1
  25. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  26. package/dist/web/standalone/.next/build-manifest.json +4 -4
  27. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  28. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  29. package/dist/web/standalone/.next/required-server-files.json +4 -4
  30. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  31. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  41. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  57. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  69. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  89. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  99. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  105. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  121. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  125. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/index.html +1 -1
  135. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  136. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  137. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  138. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  140. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/page.js +2 -2
  142. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  144. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  145. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  146. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/middleware.js +2 -2
  149. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  151. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  152. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  153. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  154. package/dist/web/standalone/.next/static/chunks/2826.dd3dc8bbd3025fa5.js +9 -0
  155. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  156. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  157. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  158. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  159. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  160. package/dist/web/standalone/.next/static/chunks/{webpack-6e4d7e9a4f57bed4.js → webpack-b868033a5834586d.js} +1 -1
  161. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  162. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  163. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  164. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  165. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  166. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  167. package/dist/web/standalone/server.js +1 -1
  168. package/dist/web-mode.js +4 -0
  169. package/package.json +11 -11
  170. package/packages/mcp-server/dist/workflow-tools.d.ts +2 -0
  171. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  172. package/packages/mcp-server/dist/workflow-tools.js +35 -3
  173. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  174. package/packages/mcp-server/src/import-candidates.test.ts +48 -0
  175. package/packages/mcp-server/src/workflow-tools.ts +34 -1
  176. package/packages/pi-agent-core/dist/agent.d.ts +8 -0
  177. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  178. package/packages/pi-agent-core/dist/agent.js +3 -0
  179. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  180. package/packages/pi-agent-core/src/agent.test.ts +82 -0
  181. package/packages/pi-agent-core/src/agent.ts +12 -0
  182. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +1 -0
  183. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/lsp/config.js +38 -15
  185. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
  188. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  190. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +3 -1
  191. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  192. package/packages/pi-coding-agent/package.json +1 -1
  193. package/packages/pi-coding-agent/src/core/lsp/config.ts +43 -17
  194. package/packages/pi-coding-agent/src/core/sdk.ts +8 -0
  195. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +7 -5
  196. package/pkg/package.json +1 -1
  197. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +229 -2
  198. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +205 -0
  199. package/src/resources/extensions/gsd/auto-model-selection.ts +39 -25
  200. package/src/resources/extensions/gsd/auto-prompts.ts +7 -3
  201. package/src/resources/extensions/gsd/auto-start.ts +37 -14
  202. package/src/resources/extensions/gsd/auto.ts +12 -8
  203. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
  204. package/src/resources/extensions/gsd/commands-handlers.ts +22 -7
  205. package/src/resources/extensions/gsd/doctor-engine-checks.ts +14 -0
  206. package/src/resources/extensions/gsd/doctor-format.ts +1 -0
  207. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  208. package/src/resources/extensions/gsd/guided-flow.ts +36 -17
  209. package/src/resources/extensions/gsd/init-wizard.ts +3 -13
  210. package/src/resources/extensions/gsd/pre-execution-checks.ts +6 -3
  211. package/src/resources/extensions/gsd/prompts/discuss.md +31 -13
  212. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +9 -0
  213. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +43 -0
  214. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +207 -0
  215. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +48 -1
  216. package/src/resources/extensions/gsd/tests/resource-loader-import-path.test.ts +8 -7
  217. package/src/resources/extensions/gsd/tests/validate-directory.test.ts +33 -1
  218. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +87 -1
  219. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +76 -0
  220. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +180 -1
  221. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +22 -0
  222. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +60 -25
  223. package/src/resources/extensions/gsd/validate-directory.ts +33 -11
  224. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +76 -0
  225. package/src/resources/extensions/gsd/workflow-mcp.ts +16 -1
  226. package/src/resources/extensions/slash-commands/audit.ts +2 -1
  227. package/src/resources/extensions/subagent/isolation.ts +4 -3
  228. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +0 -9
  229. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  230. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  231. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  232. /package/dist/web/standalone/.next/static/{Nl6lg7zP5dNgNBV1107v1 → UlX0WGGZ8aBPN0uSZ5Ki4}/_buildManifest.js +0 -0
  233. /package/dist/web/standalone/.next/static/{Nl6lg7zP5dNgNBV1107v1 → UlX0WGGZ8aBPN0uSZ5Ki4}/_ssgManifest.js +0 -0
@@ -83,7 +83,7 @@ import { join } from "node:path";
83
83
  import { sep as pathSep } from "node:path";
84
84
 
85
85
  import { resolveProjectRootDbPath } from "./bootstrap/dynamic-tools.js";
86
- import { resolveDefaultSessionModel } from "./preferences-models.js";
86
+ import { resolveDefaultSessionModel, resolveDynamicRoutingConfig } from "./preferences-models.js";
87
87
  import type { WorktreeResolver } from "./worktree-resolver.js";
88
88
 
89
89
  export interface BootstrapDeps {
@@ -335,19 +335,9 @@ export async function bootstrapAutoSession(
335
335
  }
336
336
  }
337
337
 
338
- if (ctx.model?.provider === "claude-code") {
339
- try {
340
- const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
341
- const result = ensureProjectWorkflowMcpConfig(base);
342
- if (result.status !== "unchanged") {
343
- ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
344
- }
345
- } catch (err) {
346
- ctx.ui.notify(
347
- `Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
348
- "warning",
349
- );
350
- }
338
+ {
339
+ const { prepareWorkflowMcpForProject } = await import("./workflow-mcp-auto-prep.js");
340
+ prepareWorkflowMcpForProject(ctx, base);
351
341
  }
352
342
 
353
343
  // Initialize GitServiceImpl
@@ -778,6 +768,39 @@ export async function bootstrapAutoSession(
778
768
  : "Will loop until milestone complete.";
779
769
  ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
780
770
 
771
+ // Show dynamic routing status so users know upfront if models will be
772
+ // downgraded for simple tasks (#3962).
773
+ // Use the same effective logic as selectAndApplyModel: check flat-rate
774
+ // provider suppression and resolve the actual ceiling model.
775
+ const routingConfig = resolveDynamicRoutingConfig();
776
+ const startModelLabel = s.autoModeStartModel
777
+ ? `${s.autoModeStartModel.provider}/${s.autoModeStartModel.id}`
778
+ : ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "default";
779
+
780
+ // Flat-rate providers (e.g. GitHub Copilot, claude-code) suppress routing
781
+ // at dispatch time (#3453) — reflect that in the banner.
782
+ const { isFlatRateProvider } = await import("./auto-model-selection.js");
783
+ const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
784
+ const effectivelyEnabled = routingConfig.enabled
785
+ && !(effectiveProvider && isFlatRateProvider(effectiveProvider));
786
+
787
+ // The actual ceiling may come from tier_models.heavy, not the start model.
788
+ const effectiveCeiling = (routingConfig.enabled && routingConfig.tier_models?.heavy)
789
+ ? routingConfig.tier_models.heavy
790
+ : startModelLabel;
791
+
792
+ if (effectivelyEnabled) {
793
+ ctx.ui.notify(
794
+ `Dynamic routing: enabled — simple tasks may use cheaper models (ceiling: ${effectiveCeiling})`,
795
+ "info",
796
+ );
797
+ } else {
798
+ ctx.ui.notify(
799
+ `Dynamic routing: disabled — all tasks will use ${startModelLabel}`,
800
+ "info",
801
+ );
802
+ }
803
+
781
804
  updateSessionLock(
782
805
  lockBase(),
783
806
  "starting",
@@ -125,9 +125,9 @@ import {
125
125
  } from "./metrics.js";
126
126
  import { setLogBasePath, logWarning, logError } from "./workflow-logger.js";
127
127
  import { homedir } from "node:os";
128
- import { join, dirname } from "node:path";
128
+ import { join } from "node:path";
129
+ import { pathToFileURL } from "node:url";
129
130
  import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
130
- import { createRequire } from "node:module";
131
131
  import { atomicWriteSync } from "./atomic-write.js";
132
132
  import {
133
133
  autoCommitCurrentBranch,
@@ -1334,13 +1334,17 @@ export async function startAuto(
1334
1334
  restoreHookState(s.basePath);
1335
1335
  // Re-sync managed resources on resume so long-lived auto sessions pick up
1336
1336
  // bundled extension updates before resume-time verification/state logic runs.
1337
+ // GSD_PKG_ROOT is set by loader.ts and points to the gsd-pi package root.
1338
+ // The relative import ("../../../resource-loader.js") only works from the source
1339
+ // tree; deployed extensions live at ~/.gsd/agent/extensions/gsd/ where the
1340
+ // relative path resolves to ~/.gsd/agent/resource-loader.js which doesn't exist.
1341
+ // Using GSD_PKG_ROOT constructs a correct absolute path in both contexts (#3949).
1337
1342
  const agentDir = process.env.GSD_CODING_AGENT_DIR || join(process.env.GSD_HOME || homedir(), ".gsd", "agent");
1338
- // Resolve resource-loader from the gsd-pi package root — the relative
1339
- // "../../../resource-loader.js" path only works from the source tree but
1340
- // breaks when extensions are deployed to ~/.gsd/agent/extensions/gsd/.
1341
- const _req = createRequire(import.meta.url);
1342
- const pkgRoot = dirname(_req.resolve("gsd-pi/package.json"));
1343
- const { initResources } = await import(join(pkgRoot, "dist", "resource-loader.js"));
1343
+ const pkgRoot = process.env.GSD_PKG_ROOT;
1344
+ const resourceLoaderPath = pkgRoot
1345
+ ? pathToFileURL(join(pkgRoot, "dist", "resource-loader.js")).href
1346
+ : new URL("../../../resource-loader.js", import.meta.url).href;
1347
+ const { initResources } = await import(resourceLoaderPath);
1344
1348
  initResources(agentDir);
1345
1349
  // Open the project DB before rebuild/derive so resume uses DB-backed
1346
1350
  // state instead of falling back to stale markdown parsing (#2940).
@@ -45,6 +45,8 @@ export function registerHooks(pi: ExtensionAPI): void {
45
45
  resetToolCallLoopGuard();
46
46
  resetAskUserQuestionsCache();
47
47
  await syncServiceTierStatus(ctx);
48
+ const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
49
+ prepareWorkflowMcpForProject(ctx, process.cwd());
48
50
 
49
51
  // Apply show_token_cost preference (#1515)
50
52
  try {
@@ -85,6 +87,8 @@ export function registerHooks(pi: ExtensionAPI): void {
85
87
  resetAskUserQuestionsCache();
86
88
  clearDiscussionFlowState();
87
89
  await syncServiceTierStatus(ctx);
90
+ const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
91
+ prepareWorkflowMcpForProject(ctx, process.cwd());
88
92
  loadToolApiKeys();
89
93
  });
90
94
 
@@ -25,6 +25,26 @@ import { getAutoWorktreePath } from "./auto-worktree.js";
25
25
  import { projectRoot } from "./commands/context.js";
26
26
  import { loadPrompt } from "./prompt-loader.js";
27
27
 
28
+ const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/gsd-pi/latest";
29
+ const UPDATE_FETCH_TIMEOUT_MS = 5000;
30
+
31
+ async function fetchLatestVersionForCommand(): Promise<string | null> {
32
+ const controller = new AbortController();
33
+ const timeout = setTimeout(() => controller.abort(), UPDATE_FETCH_TIMEOUT_MS);
34
+
35
+ try {
36
+ const res = await fetch(UPDATE_REGISTRY_URL, { signal: controller.signal });
37
+ if (!res.ok) return null;
38
+ const data = (await res.json()) as { version?: string };
39
+ const latest = typeof data.version === "string" ? data.version.trim().replace(/^v/, "") : "";
40
+ return latest.length > 0 ? latest : null;
41
+ } catch {
42
+ return null;
43
+ } finally {
44
+ clearTimeout(timeout);
45
+ }
46
+ }
47
+
28
48
  export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
29
49
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
30
50
  const workflow = readFileSync(workflowPath, "utf-8");
@@ -394,13 +414,8 @@ export async function handleUpdate(ctx: ExtensionCommandContext): Promise<void>
394
414
 
395
415
  ctx.ui.notify(`Current version: v${current}\nChecking npm registry...`, "info");
396
416
 
397
- let latest: string;
398
- try {
399
- latest = execSync(`npm view ${NPM_PACKAGE} version`, {
400
- encoding: "utf-8",
401
- stdio: ["ignore", "pipe", "ignore"],
402
- }).trim();
403
- } catch {
417
+ const latest = await fetchLatestVersionForCommand();
418
+ if (!latest) {
404
419
  ctx.ui.notify("Failed to reach npm registry. Check your network connection.", "error");
405
420
  return;
406
421
  }
@@ -13,6 +13,20 @@ export async function checkEngineHealth(
13
13
  issues: DoctorIssue[],
14
14
  fixesApplied: string[],
15
15
  ): Promise<void> {
16
+ const dbPath = join(basePath, ".gsd", "gsd.db");
17
+
18
+ if (!isDbAvailable() && existsSync(dbPath)) {
19
+ issues.push({
20
+ severity: "warning",
21
+ code: "db_unavailable",
22
+ scope: "project",
23
+ unitId: "project",
24
+ message: "Database unavailable — using filesystem state derivation (degraded mode). State queries may be slower and less reliable.",
25
+ file: ".gsd/gsd.db",
26
+ fixable: false,
27
+ });
28
+ }
29
+
16
30
  // ── DB constraint violation detection (full doctor only, not pre-dispatch per D-10) ──
17
31
  try {
18
32
  if (isDbAvailable()) {
@@ -2,6 +2,7 @@ import type { DoctorIssue, DoctorIssueCode, DoctorReport, DoctorSummary } from "
2
2
 
3
3
  function matchesScope(unitId: string, scope?: string): boolean {
4
4
  if (!scope) return true;
5
+ if (unitId === "project" || unitId === "environment") return true;
5
6
  return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`);
6
7
  }
7
8
 
@@ -78,6 +78,7 @@ export type DoctorIssueCode =
78
78
  | "db_orphaned_slice"
79
79
  | "db_done_task_no_summary"
80
80
  | "db_duplicate_id"
81
+ | "db_unavailable"
81
82
  | "projection_drift";
82
83
 
83
84
  /**
@@ -48,6 +48,7 @@ import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
48
48
  import {
49
49
  getWorkflowTransportSupportError,
50
50
  getRequiredWorkflowToolsForGuidedUnit,
51
+ supportsStructuredQuestions,
51
52
  } from "./workflow-mcp.js";
52
53
  import {
53
54
  runPreparation,
@@ -294,6 +295,7 @@ async function dispatchWorkflow(
294
295
  const result = await selectAndApplyModel(
295
296
  ctx, pi, unitType, /* unitId */ "", /* basePath */ process.cwd(),
296
297
  prefs, /* verbose */ false, /* autoModeStartModel */ null,
298
+ /* retryContext */ undefined, /* isAutoMode */ false,
297
299
  );
298
300
  if (result.appliedModel) {
299
301
  debugLog("guided-flow-model-applied", {
@@ -367,6 +369,20 @@ async function dispatchWorkflow(
367
369
  }
368
370
  }
369
371
 
372
+ function getStructuredQuestionsAvailability(
373
+ pi: ExtensionAPI,
374
+ ctx: ExtensionContext | undefined,
375
+ ): "true" | "false" {
376
+ if (!ctx) return "false";
377
+
378
+ const provider = ctx.model?.provider;
379
+ const authMode = provider ? ctx.modelRegistry.getProviderAuthMode(provider) : undefined;
380
+ return supportsStructuredQuestions(pi.getActiveTools(), {
381
+ authMode,
382
+ baseUrl: ctx.model?.baseUrl,
383
+ }) ? "true" : "false";
384
+ }
385
+
370
386
  /**
371
387
  * Resolve a model ID string to a model object from available models.
372
388
  * Handles "provider/model" and bare ID formats.
@@ -410,8 +426,9 @@ function resolveAvailableModel<T extends { id: string; provider: string }>(
410
426
  * Build the discuss-and-plan prompt for a new milestone.
411
427
  * Used by all three "new milestone" paths (first ever, no active, all complete).
412
428
  */
413
- function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string, preparationContext?: string): string {
429
+ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string, pi: ExtensionAPI, ctx: ExtensionCommandContext, preparationContext?: string): string {
414
430
  const milestoneRel = `.gsd/milestones/${nextId}`;
431
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
415
432
  const inlinedTemplates = [
416
433
  inlineTemplate("project", "Project"),
417
434
  inlineTemplate("requirements", "Requirements"),
@@ -423,6 +440,7 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string,
423
440
  milestoneId: nextId,
424
441
  preamble,
425
442
  preparationContext: preparationContext ?? "",
443
+ structuredQuestionsAvailable,
426
444
  contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
427
445
  roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
428
446
  inlinedTemplates,
@@ -470,6 +488,7 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
470
488
  */
471
489
  async function prepareAndBuildDiscussPrompt(
472
490
  ctx: ExtensionCommandContext,
491
+ pi: ExtensionAPI,
473
492
  nextId: string,
474
493
  preamble: string,
475
494
  basePath: string,
@@ -504,7 +523,7 @@ async function prepareAndBuildDiscussPrompt(
504
523
  }
505
524
  }
506
525
 
507
- return buildDiscussPrompt(nextId, preamble, basePath, preparationContext);
526
+ return buildDiscussPrompt(nextId, preamble, basePath, pi, ctx, preparationContext);
508
527
  }
509
528
 
510
529
  /**
@@ -739,7 +758,7 @@ export async function showDiscuss(
739
758
 
740
759
  if (choice === "discuss_draft") {
741
760
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
742
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
761
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
743
762
  const basePrompt = loadPrompt("guided-discuss-milestone", {
744
763
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
745
764
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
@@ -752,7 +771,7 @@ export async function showDiscuss(
752
771
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
753
772
  } else if (choice === "discuss_fresh") {
754
773
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
755
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
774
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
756
775
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
757
776
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
758
777
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
@@ -764,7 +783,7 @@ export async function showDiscuss(
764
783
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
765
784
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
766
785
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
767
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
786
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
768
787
  }
769
788
  return;
770
789
  }
@@ -910,7 +929,7 @@ export async function showDiscuss(
910
929
  if (confirm !== "rediscuss") continue;
911
930
  }
912
931
 
913
- const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
932
+ const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
914
933
  const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
915
934
  await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
916
935
 
@@ -1020,7 +1039,7 @@ async function dispatchDiscussForMilestone(
1020
1039
  ].join("\n")
1021
1040
  : "";
1022
1041
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1023
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1042
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1024
1043
  const basePrompt = loadPrompt("guided-discuss-milestone", {
1025
1044
  milestoneId: mid,
1026
1045
  milestoneTitle,
@@ -1169,7 +1188,7 @@ async function handleMilestoneActions(
1169
1188
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1170
1189
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1171
1190
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1172
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1191
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1173
1192
  `New milestone ${nextId}.`,
1174
1193
  basePath
1175
1194
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1359,7 +1378,7 @@ export async function showSmartEntry(
1359
1378
  if (isFirst) {
1360
1379
  // First ever — skip wizard, just ask directly
1361
1380
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1362
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1381
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1363
1382
  `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
1364
1383
  basePath
1365
1384
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1380,7 +1399,7 @@ export async function showSmartEntry(
1380
1399
 
1381
1400
  if (choice === "new_milestone") {
1382
1401
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1383
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1402
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1384
1403
  `New milestone ${nextId}.`,
1385
1404
  basePath
1386
1405
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1419,7 +1438,7 @@ export async function showSmartEntry(
1419
1438
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1420
1439
 
1421
1440
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1422
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1441
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1423
1442
  `New milestone ${nextId}.`,
1424
1443
  basePath
1425
1444
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1461,7 +1480,7 @@ export async function showSmartEntry(
1461
1480
 
1462
1481
  if (choice === "discuss_draft") {
1463
1482
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1464
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1483
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1465
1484
  const basePrompt = loadPrompt("guided-discuss-milestone", {
1466
1485
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1467
1486
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -1474,7 +1493,7 @@ export async function showSmartEntry(
1474
1493
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1475
1494
  } else if (choice === "discuss_fresh") {
1476
1495
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1477
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1496
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1478
1497
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1479
1498
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1480
1499
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
@@ -1486,7 +1505,7 @@ export async function showSmartEntry(
1486
1505
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1487
1506
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1488
1507
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1489
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1508
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1490
1509
  `New milestone ${nextId}.`,
1491
1510
  basePath
1492
1511
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1572,7 +1591,7 @@ export async function showSmartEntry(
1572
1591
  }), "gsd-run", ctx, "plan-milestone");
1573
1592
  } else if (choice === "discuss") {
1574
1593
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1575
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1594
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1576
1595
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1577
1596
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1578
1597
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -1583,7 +1602,7 @@ export async function showSmartEntry(
1583
1602
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1584
1603
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1585
1604
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1586
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1605
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1587
1606
  `New milestone ${nextId}.`,
1588
1607
  basePath
1589
1608
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1712,7 +1731,7 @@ export async function showSmartEntry(
1712
1731
  }),
1713
1732
  }), "gsd-run", ctx, "plan-slice");
1714
1733
  } else if (choice === "discuss") {
1715
- const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1734
+ const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
1716
1735
  await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
1717
1736
  } else if (choice === "research") {
1718
1737
  const researchTemplates = inlineTemplate("research", "Research");
@@ -274,19 +274,9 @@ export async function showProjectInit(
274
274
  // Non-fatal — STATE.md will be regenerated on next /gsd invocation
275
275
  }
276
276
 
277
- if (ctx.model?.provider === "claude-code") {
278
- try {
279
- const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
280
- const result = ensureProjectWorkflowMcpConfig(basePath);
281
- if (result.status !== "unchanged") {
282
- ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
283
- }
284
- } catch (err) {
285
- ctx.ui.notify(
286
- `Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
287
- "warning",
288
- );
289
- }
277
+ {
278
+ const { prepareWorkflowMcpForProject } = await import("./workflow-mcp-auto-prep.js");
279
+ prepareWorkflowMcpForProject(ctx, basePath);
290
280
  }
291
281
 
292
282
  ctx.ui.notify("GSD initialized. Starting your first milestone...", "info");
@@ -20,6 +20,8 @@ import { resolve } from "node:path";
20
20
  import type { TaskRow } from "./gsd-db.ts";
21
21
  import type { PreExecutionCheckJSON } from "./verification-evidence.ts";
22
22
 
23
+ const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
24
+
23
25
  // ─── Result Types ────────────────────────────────────────────────────────────
24
26
 
25
27
  export interface PreExecutionResult {
@@ -126,9 +128,10 @@ async function checkPackageOnNpm(
126
128
  timeoutMs = 5000
127
129
  ): Promise<{ exists: boolean; error?: string }> {
128
130
  return new Promise((resolve) => {
129
- const child = spawn("npm", ["view", packageName, "name"], {
131
+ const child = spawn(NPM_COMMAND, ["view", packageName, "name"], {
130
132
  stdio: ["ignore", "pipe", "pipe"],
131
133
  timeout: timeoutMs,
134
+ shell: process.platform === "win32",
132
135
  });
133
136
 
134
137
  let stdout = "";
@@ -263,9 +266,9 @@ function extractPathFromAnnotation(raw: string): string {
263
266
  const trimmed = raw.trim();
264
267
  if (!trimmed) return trimmed;
265
268
 
266
- const backtickMatch = trimmed.match(/^`([^`]+)`(?:\s+[—–-]\s+.*)?$/);
269
+ const backtickMatch = trimmed.match(/^(`+)([^`]+)\1(?:(?:\s+[—–-]\s+.+)|(?:\s+\([^()]+\)))?$/);
267
270
  if (backtickMatch) {
268
- return backtickMatch[1].trim();
271
+ return backtickMatch[2].trim();
269
272
  }
270
273
 
271
274
  const annotatedMatch = trimmed.match(/^(.+?)\s+[—–-]\s+.+$/);
@@ -49,6 +49,26 @@ This happens ONCE, before the first round. The goal: your first questions should
49
49
 
50
50
  For subsequent rounds, continue investigating between rounds — check docs, search, or scout as needed to make each round's questions smarter. But the first-round investigation is mandatory and explicit. Distribute searches across turns rather than clustering them in one turn.
51
51
 
52
+ ## Question Rounds
53
+
54
+ Ask **1–3 questions per round**. Keep each round tightly focused on one or two of the depth checklist dimensions — do not try to cover all six in one round.
55
+
56
+ **If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions` for each round. 1–3 questions per call, each as a separate question object. Keep option labels short (3–5 words). Always include a freeform "Other / let me explain" option. When the user picks that option or writes a long freeform answer, switch to plain text follow-up for that thread before resuming structured questions. **IMPORTANT: Call `ask_user_questions` exactly once per turn. Never make multiple calls with the same or overlapping questions — wait for the user's response before asking the next round.**
57
+
58
+ **If `{{structuredQuestionsAvailable}}` is `false`:** ask questions in plain text. Keep each round to 1–3 focused questions. Wait for answers before asking the next round.
59
+
60
+ After each answer set, investigate further if any answer opens a new unknown, then ask the next round.
61
+
62
+ ### Round cadence
63
+
64
+ After each round of answers, decide whether you already have enough depth to write strong output.
65
+
66
+ - **Incremental persistence:** After every 2 question rounds, silently save a `{{milestoneId}}-CONTEXT-DRAFT.md` using `gsd_summary_save` with `artifact_type: "CONTEXT-DRAFT"` and `milestone_id: "{{milestoneId}}"`. This protects confirmed work against session crashes. Do NOT mention this save to the user.
67
+ - If not ready, continue to the next round immediately. Do **not** ask a meta "ready to wrap up?" question after every round.
68
+ - **Depth-matching rule:** Simple, well-defined work needs fewer rounds — maybe 1–2. Large, ambiguous visions need more — maybe 4+. Do not pad rounds to hit a number. Stop when the Depth Enforcement checklist below is fully satisfied.
69
+ - Do not count the reflection step as a question round. Rounds start after reflection is confirmed.
70
+ - When you genuinely believe the depth checklist is satisfied, move to the Depth Verification step below. Do not ask a separate "ready to wrap up?" gate — the depth verification IS the gate.
71
+
52
72
  ## Questioning Philosophy
53
73
 
54
74
  You are a thinking partner, not an interviewer.
@@ -94,29 +114,27 @@ Do NOT offer to proceed until ALL of the following are satisfied. Track these in
94
114
 
95
115
  Before offering to proceed, demonstrate absorption: reference specific things the user emphasized, specific terminology they used, specific nuance they sharpened — and show how those shaped your understanding. Synthesize, don't recite. "Your emphasis on X led me to prioritize Y over Z" is good. "You said X, you said Y, you said Z" is not. The user should feel heard in the specifics, not just acknowledged in the abstract.
96
116
 
97
- **Questioning depth should match scope.** Simple, well-defined work needs fewer rounds — maybe 1-2. Large, ambiguous visions need more — maybe 4+. Don't pad rounds to hit a number. Stop when the depth checklist is satisfied and you genuinely understand the work.
98
-
99
- Do not count the reflection step as a question round. Rounds start after reflection is confirmed.
100
-
101
117
  ## Depth Verification
102
118
 
103
119
  Before moving to the wrap-up gate, present a structured depth summary as a checkpoint.
104
120
 
105
121
  **Print the summary as normal chat text first** — this is where the formatting renders properly. Structure the summary across the depth checklist dimensions using the user's own terminology and framing. Cover: what you understood them to be building, what shaped your understanding most (their emphasis, constraints, concerns), and any areas where you're least confident in your understanding.
106
122
 
107
- **Then** use `ask_user_questions` with a short confirmation question — NOT the summary itself. The question field is designed for single sentences, not multi-paragraph summaries.
123
+ **Then confirm:**
108
124
 
109
- **Convention:** The question ID must contain `depth_verification` (e.g., `depth_verification_confirm`). This naming convention enables downstream mechanical detection of this step.
125
+ **If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions` with:
126
+ - header: "Depth Check"
127
+ - question: "Did I capture the depth right?"
128
+ - options: "Yes, you got it (Recommended)", "Not quite — let me clarify"
129
+ - **The question ID must contain `depth_verification`** (e.g., `depth_verification_confirm`) — this naming convention enables downstream mechanical detection and the write-gate.
110
130
 
111
- Example flow:
112
- 1. Print in chat: the full depth summary with markdown formatting (headers, bold, bullets)
113
- 2. Call `ask_user_questions` with: header "Depth Check", question "Did I capture the depth right?", options "Yes, you got it (Recommended)" and "Not quite — let me clarify"
131
+ **If `{{structuredQuestionsAvailable}}` is `false`:** ask in plain text: "Did I capture that correctly? If not, tell me what I missed." Wait for explicit confirmation before proceeding. **The same non-bypassable gate applies to the plain-text path** — if the user does not respond, gives an ambiguous answer, or does not explicitly confirm, you MUST re-ask. Never rationalize past a missing confirmation.
114
132
 
115
133
  If they clarify, absorb the correction and re-verify.
116
134
 
117
135
  The depth verification is the required write-gate. Do **not** add another meta "ready to proceed?" checkpoint immediately after it unless there is still material ambiguity.
118
136
 
119
- **CRITICAL — Non-bypassable gate:** The system mechanically blocks CONTEXT.md writes until the user selects the "(Recommended)" option. If the user declines, cancels, or the tool fails, you MUST re-ask — never rationalize past the block ("tool not responding, I'll proceed" is forbidden). The gate exists to protect the user's work; treat a block as an instruction, not an obstacle to work around.
137
+ **CRITICAL — Non-bypassable gate:** The system mechanically blocks CONTEXT.md writes until the user selects the "(Recommended)" option (structured path) or explicitly confirms (plain-text path). If the user declines, cancels, does not respond, or the tool fails, you MUST re-ask — never rationalize past the block ("tool not responding, I'll proceed" is forbidden). The gate exists to protect the user's work; treat a block as an instruction, not an obstacle to work around.
120
138
 
121
139
  ## Wrap-up Gate
122
140
 
@@ -244,7 +262,7 @@ If a milestone has no dependencies, omit the frontmatter. The dependency chain f
244
262
 
245
263
  #### Phase 3: Sequential readiness gate for remaining milestones
246
264
 
247
- For each remaining milestone **one at a time, in sequence**, decide the most likely readiness mode from the evidence you already have, then use `ask_user_questions` to let the user correct that recommendation. **Non-bypassable:** If `ask_user_questions` fails, errors, returns no response, or the user's response does not match a provided option, you MUST re-ask — never rationalize past the block or auto-select a readiness mode. Present three options:
265
+ For each remaining milestone **one at a time, in sequence**, decide the most likely readiness mode from the evidence you already have, then present the three options below to the user. **If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions`. **If `{{structuredQuestionsAvailable}}` is `false`:** present the options as a plain-text numbered list and ask the user to type their choice. **Non-bypassable:** If the user does not respond, gives an ambiguous answer, or the tool fails, you MUST re-ask — never rationalize past the block or auto-select a readiness mode. Present three options:
248
266
 
249
267
  - **"Discuss now"** — The user wants to conduct a focused discussion for this milestone in the current session, while the context from the broader discussion is still fresh. Proceed with a focused discussion for this milestone (reflection → investigation → questioning → depth verification). When the discussion concludes, write a full `CONTEXT.md`. Then move to the gate for the next milestone.
250
268
  - **"Write draft for later"** — This milestone has seed material from the current conversation but needs its own dedicated discussion in a future session. Write a `CONTEXT-DRAFT.md` capturing the seed material (what was discussed, key ideas, provisional scope, open questions). Mark it clearly as a draft, not a finalized context. **What happens downstream:** When auto-mode reaches this milestone, it pauses and notifies the user: "M00x has draft context — needs discussion. Run /gsd." The `/gsd` wizard shows a "Discuss from draft" option that seeds the new discussion with this draft, so nothing from the current conversation is lost. After the dedicated discussion produces a full CONTEXT.md, the draft file is automatically deleted.
@@ -256,9 +274,9 @@ Before writing each milestone's CONTEXT.md (whether primary or secondary), you M
256
274
 
257
275
  1. **Read the actual code** for every file or module you reference. Confirm APIs exist, check what functions actually do, identify phantom capabilities (code that exists but isn't wired up).
258
276
  2. **Check for stale assumptions** — the codebase changes. Verify referenced modules still work as described.
259
- 3. **Present findings** — use `ask_user_questions` with a question ID containing BOTH `depth_verification` AND the milestone ID (e.g., `depth_verification_M002`). Present: what you're about to write, key technical findings from investigation, risks the code review surfaced.
277
+ 3. **Present findings** — **If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions` with a question ID containing BOTH `depth_verification` AND the milestone ID (e.g., `depth_verification_M002`). Present: what you're about to write, key technical findings from investigation, risks the code review surfaced. **If `{{structuredQuestionsAvailable}}` is `false`:** present the same findings in plain text and ask for explicit confirmation before proceeding.
260
278
 
261
- **The system mechanically blocks CONTEXT.md writes until the per-milestone depth verification passes.** Each milestone needs its own verification — one global verification does not unlock all milestones.
279
+ **The system mechanically blocks CONTEXT.md writes until the per-milestone depth verification passes** (structured path: user selects "(Recommended)" option; plain-text path: user explicitly confirms). Each milestone needs its own verification — one global verification does not unlock all milestones.
262
280
 
263
281
  **Why sequential, not batch:** After writing the primary milestone's context and roadmap, the agent still has context window capacity. Asking one milestone at a time lets the user decide per-milestone whether to invest that remaining capacity in a focused discussion now, or defer to a future session. A batch question ("Ready/Draft/Queue for M002, M003, M004?") forces the user to decide everything upfront without knowing how much session capacity remains.
264
282
 
@@ -27,10 +27,19 @@ describe("discuss incremental persistence (#2152)", () => {
27
27
  assert.match(content, /Incremental persistence/, "should have incremental persistence section");
28
28
  });
29
29
 
30
+ test("new-project discuss prompt includes CONTEXT-DRAFT save instruction", () => {
31
+ const content = readFileSync(join(promptsDir, "discuss.md"), "utf-8");
32
+ assert.match(content, /CONTEXT-DRAFT/, "should mention CONTEXT-DRAFT");
33
+ assert.match(content, /Incremental persistence/, "should have incremental persistence section");
34
+ assert.match(content, /gsd_summary_save/, "should use gsd_summary_save tool");
35
+ });
36
+
30
37
  test("drafts are saved silently without user notification", () => {
31
38
  const milestone = readFileSync(join(promptsDir, "guided-discuss-milestone.md"), "utf-8");
32
39
  const slice = readFileSync(join(promptsDir, "guided-discuss-slice.md"), "utf-8");
40
+ const discuss = readFileSync(join(promptsDir, "discuss.md"), "utf-8");
33
41
  assert.match(milestone, /Do NOT mention this save to the user/);
34
42
  assert.match(slice, /Do NOT mention this to the user/);
43
+ assert.match(discuss, /Do NOT mention this save to the user/);
35
44
  });
36
45
  });
@@ -0,0 +1,43 @@
1
+ import { afterEach, test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { closeDatabase } from "../gsd-db.ts";
4
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ import { filterDoctorIssues } from "../doctor-format.ts";
8
+ import { checkEngineHealth } from "../doctor-engine-checks.ts";
9
+
10
+ afterEach(() => {
11
+ closeDatabase();
12
+ });
13
+
14
+ test("filterDoctorIssues keeps project and environment issues in scoped reports", () => {
15
+ const issues = [
16
+ { severity: "error", code: "env_dependencies", scope: "project", unitId: "environment", message: "node_modules missing", fixable: false },
17
+ { severity: "warning", code: "db_unavailable", scope: "project", unitId: "project", message: "DB unavailable", fixable: false },
18
+ { severity: "warning", code: "state_file_missing", scope: "slice", unitId: "M016/S01", message: "slice warning", fixable: false },
19
+ ] as const;
20
+
21
+ const filtered = filterDoctorIssues([...issues], { scope: "M016", includeWarnings: true });
22
+ assert.deepEqual(
23
+ filtered.map((issue) => issue.unitId),
24
+ ["environment", "project", "M016/S01"],
25
+ );
26
+ });
27
+
28
+ test("checkEngineHealth reports db_unavailable when gsd.db exists but the DB is closed", async (t) => {
29
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-db-unavailable-"));
30
+ t.after(() => rmSync(base, { recursive: true, force: true }));
31
+
32
+ const gsdDir = join(base, ".gsd");
33
+ mkdirSync(gsdDir, { recursive: true });
34
+ writeFileSync(join(gsdDir, "gsd.db"), "");
35
+
36
+ const issues: any[] = [];
37
+ await checkEngineHealth(base, issues, []);
38
+
39
+ const dbIssue = issues.find((issue) => issue.code === "db_unavailable");
40
+ assert.ok(dbIssue, "doctor should surface degraded DB mode when a DB file exists");
41
+ assert.equal(dbIssue.unitId, "project");
42
+ assert.equal(dbIssue.file, ".gsd/gsd.db");
43
+ });