gsd-pi 2.70.1 → 2.71.0-dev.06b86c6

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 (335) hide show
  1. package/README.md +24 -17
  2. package/dist/cli.js +12 -3
  3. package/dist/mcp-server.js +6 -6
  4. package/dist/provider-migrations.d.ts +10 -0
  5. package/dist/provider-migrations.js +12 -0
  6. package/dist/resource-loader.js +136 -13
  7. package/dist/resources/GSD-WORKFLOW.md +1 -1
  8. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +129 -30
  9. package/dist/resources/extensions/get-secrets-from-user.js +17 -1
  10. package/dist/resources/extensions/gsd/auto-start.js +4 -12
  11. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  12. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +6 -0
  13. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
  14. package/dist/resources/extensions/gsd/commands/context.js +15 -6
  15. package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
  16. package/dist/resources/extensions/gsd/custom-workflow-engine.js +16 -12
  17. package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
  18. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  19. package/dist/resources/extensions/gsd/file-lock.js +60 -0
  20. package/dist/resources/extensions/gsd/guided-flow.js +12 -10
  21. package/dist/resources/extensions/gsd/init-wizard.js +3 -11
  22. package/dist/resources/extensions/gsd/notification-store.js +21 -1
  23. package/dist/resources/extensions/gsd/notification-widget.js +1 -1
  24. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
  25. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  26. package/dist/resources/extensions/gsd/prompts/discuss.md +33 -13
  27. package/dist/resources/extensions/gsd/prompts/execute-task.md +20 -19
  28. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  29. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  30. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  31. package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
  32. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  33. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  34. package/dist/resources/extensions/gsd/state.js +234 -332
  35. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +34 -0
  36. package/dist/resources/extensions/gsd/workflow-events.js +25 -13
  37. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +56 -0
  38. package/dist/resources/extensions/gsd/workflow-mcp.js +1 -1
  39. package/dist/resources/skills/create-skill/SKILL.md +2 -0
  40. package/dist/web/standalone/.next/BUILD_ID +1 -1
  41. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  42. package/dist/web/standalone/.next/build-manifest.json +4 -4
  43. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  44. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  45. package/dist/web/standalone/.next/required-server-files.json +3 -3
  46. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  47. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  57. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  73. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  85. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  105. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  121. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  137. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  141. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/index.html +1 -1
  151. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  152. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  153. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  154. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  155. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  156. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  157. package/dist/web/standalone/.next/server/app/page.js +2 -2
  158. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  160. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  161. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  162. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/middleware.js +2 -2
  165. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  167. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  168. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  169. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  170. package/dist/web/standalone/.next/static/chunks/2826.dd3dc8bbd3025fa5.js +9 -0
  171. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  172. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  173. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  174. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  175. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  176. package/dist/web/standalone/.next/static/chunks/{webpack-6e4d7e9a4f57bed4.js → webpack-b868033a5834586d.js} +1 -1
  177. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  178. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  179. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  180. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  181. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  182. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  183. package/dist/web/standalone/server.js +1 -1
  184. package/package.json +1 -1
  185. package/packages/mcp-server/dist/env-writer.d.ts +39 -0
  186. package/packages/mcp-server/dist/env-writer.d.ts.map +1 -0
  187. package/packages/mcp-server/dist/env-writer.js +158 -0
  188. package/packages/mcp-server/dist/env-writer.js.map +1 -0
  189. package/packages/mcp-server/dist/server.d.ts +11 -2
  190. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  191. package/packages/mcp-server/dist/server.js +102 -2
  192. package/packages/mcp-server/dist/server.js.map +1 -1
  193. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  194. package/packages/mcp-server/dist/workflow-tools.js +21 -11
  195. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  196. package/packages/mcp-server/src/env-writer.test.ts +280 -0
  197. package/packages/mcp-server/src/env-writer.ts +183 -0
  198. package/packages/mcp-server/src/secure-env-collect.test.ts +265 -0
  199. package/packages/mcp-server/src/server.ts +137 -3
  200. package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
  201. package/packages/mcp-server/src/workflow-tools.ts +31 -11
  202. package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  203. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  204. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
  205. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  206. package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
  207. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  208. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
  209. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
  210. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  211. package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
  212. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  213. package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
  214. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
  215. package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
  216. package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
  217. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts +2 -0
  218. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts.map +1 -0
  219. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +388 -0
  220. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -0
  221. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  222. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  224. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +19 -2
  225. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +50 -1
  227. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts +1 -0
  229. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  230. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js +1 -0
  231. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js.map +1 -1
  232. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  233. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +168 -23
  234. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  235. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  236. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  237. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  238. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +6 -0
  239. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  240. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +58 -2
  241. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  242. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
  243. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  244. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +1 -0
  245. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  246. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  247. package/packages/pi-coding-agent/package.json +1 -1
  248. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +468 -0
  249. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  250. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +58 -2
  251. package/packages/pi-coding-agent/src/modes/interactive/components/extension-input.ts +2 -0
  252. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +198 -29
  253. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
  254. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +66 -2
  255. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +1 -1
  256. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +1 -0
  257. package/packages/pi-tui/dist/components/__tests__/input.test.js +9 -0
  258. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  259. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts +2 -0
  260. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts.map +1 -0
  261. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +66 -0
  262. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -0
  263. package/packages/pi-tui/dist/components/input.d.ts +2 -0
  264. package/packages/pi-tui/dist/components/input.d.ts.map +1 -1
  265. package/packages/pi-tui/dist/components/input.js +7 -4
  266. package/packages/pi-tui/dist/components/input.js.map +1 -1
  267. package/packages/pi-tui/dist/components/markdown.d.ts +3 -0
  268. package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
  269. package/packages/pi-tui/dist/components/markdown.js +17 -1
  270. package/packages/pi-tui/dist/components/markdown.js.map +1 -1
  271. package/packages/pi-tui/src/components/__tests__/input.test.ts +11 -0
  272. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +75 -0
  273. package/packages/pi-tui/src/components/input.ts +7 -4
  274. package/packages/pi-tui/src/components/markdown.ts +22 -1
  275. package/pkg/package.json +1 -1
  276. package/src/resources/GSD-WORKFLOW.md +1 -1
  277. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +166 -31
  278. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +145 -0
  279. package/src/resources/extensions/get-secrets-from-user.ts +24 -1
  280. package/src/resources/extensions/gsd/auto-start.ts +4 -14
  281. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  282. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
  283. package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
  284. package/src/resources/extensions/gsd/commands/context.ts +16 -5
  285. package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
  286. package/src/resources/extensions/gsd/custom-workflow-engine.ts +19 -14
  287. package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
  288. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  289. package/src/resources/extensions/gsd/file-lock.ts +59 -0
  290. package/src/resources/extensions/gsd/guided-flow.ts +12 -9
  291. package/src/resources/extensions/gsd/init-wizard.ts +3 -13
  292. package/src/resources/extensions/gsd/notification-store.ts +19 -1
  293. package/src/resources/extensions/gsd/notification-widget.ts +1 -1
  294. package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
  295. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  296. package/src/resources/extensions/gsd/prompts/discuss.md +33 -13
  297. package/src/resources/extensions/gsd/prompts/execute-task.md +20 -19
  298. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  299. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  300. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  301. package/src/resources/extensions/gsd/prompts/queue.md +3 -2
  302. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  303. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  304. package/src/resources/extensions/gsd/state.ts +274 -344
  305. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
  306. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
  307. package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
  308. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +436 -0
  309. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +9 -0
  310. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  311. package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
  312. package/src/resources/extensions/gsd/tests/file-lock.test.ts +103 -0
  313. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
  314. package/src/resources/extensions/gsd/tests/notification-store.test.ts +17 -0
  315. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +25 -0
  316. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
  317. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
  318. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  319. package/src/resources/extensions/gsd/tests/secure-env-collect.test.ts +45 -0
  320. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
  321. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
  322. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +76 -0
  323. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +155 -1
  324. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +22 -0
  325. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +60 -25
  326. package/src/resources/extensions/gsd/workflow-events.ts +34 -25
  327. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +76 -0
  328. package/src/resources/extensions/gsd/workflow-mcp.ts +1 -1
  329. package/src/resources/skills/create-skill/SKILL.md +2 -0
  330. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +0 -9
  331. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  332. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  333. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  334. /package/dist/web/standalone/.next/static/{9pw9EXtXjdM7EFrCXUEPf → dYVdRaunb2ZSEA8fjkT-V}/_buildManifest.js +0 -0
  335. /package/dist/web/standalone/.next/static/{9pw9EXtXjdM7EFrCXUEPf → dYVdRaunb2ZSEA8fjkT-V}/_ssgManifest.js +0 -0
@@ -11,6 +11,7 @@ import {
11
11
  extractToolResultsFromSdkUserMessage,
12
12
  getClaudeLookupCommand,
13
13
  parseAskUserQuestionsElicitation,
14
+ parseTextInputElicitation,
14
15
  parseClaudeLookupOutput,
15
16
  roundResultToElicitationContent,
16
17
  } from "../stream-adapter.ts";
@@ -220,6 +221,35 @@ describe("stream-adapter — session persistence (#2859)", () => {
220
221
  assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
221
222
  assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
222
223
  assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
224
+ assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
225
+ } finally {
226
+ process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
227
+ process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
228
+ process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
229
+ process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
230
+ process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
231
+ }
232
+ });
233
+
234
+ test("buildSdkOptions disables AskUserQuestion for custom workflow MCP server names", () => {
235
+ const prev = {
236
+ GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
237
+ GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
238
+ GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
239
+ GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
240
+ GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
241
+ };
242
+ try {
243
+ process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
244
+ process.env.GSD_WORKFLOW_MCP_NAME = "custom-workflow";
245
+ process.env.GSD_WORKFLOW_MCP_ARGS = JSON.stringify(["packages/mcp-server/dist/cli.js"]);
246
+ process.env.GSD_WORKFLOW_MCP_ENV = JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" });
247
+ process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
248
+
249
+ const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
250
+ const mcpServers = options.mcpServers as Record<string, any>;
251
+ assert.ok(mcpServers?.["custom-workflow"], "expected custom workflow server config");
252
+ assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
223
253
  } finally {
224
254
  process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
225
255
  process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
@@ -255,6 +285,9 @@ describe("stream-adapter — session persistence (#2859)", () => {
255
285
  const mcpServers = (options as any).mcpServers;
256
286
  if (mcpServers) {
257
287
  assert.ok(mcpServers["gsd-workflow"], "if present, must be gsd-workflow");
288
+ assert.deepEqual((options as any).disallowedTools, ["AskUserQuestion"]);
289
+ } else {
290
+ assert.deepEqual((options as any).disallowedTools, ["AskUserQuestion"]);
258
291
  }
259
292
  rmSync(emptyDir, { recursive: true, force: true });
260
293
  } finally {
@@ -301,6 +334,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
301
334
  assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
302
335
  assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
303
336
  assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, resolvedRepoDir);
337
+ assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
304
338
  } finally {
305
339
  process.chdir(originalCwd);
306
340
  rmSync(repoDir, { recursive: true, force: true });
@@ -481,6 +515,117 @@ describe("stream-adapter — MCP elicitation bridge", () => {
481
515
  },
482
516
  });
483
517
  });
518
+
519
+ test("parseTextInputElicitation recognizes secure free-text MCP forms", () => {
520
+ const request = {
521
+ serverName: "gsd-workflow",
522
+ message: "Enter values for environment variables.",
523
+ mode: "form" as const,
524
+ requestedSchema: {
525
+ type: "object" as const,
526
+ properties: {
527
+ TEST_PASSWORD: {
528
+ type: "string",
529
+ title: "TEST_PASSWORD",
530
+ description: "Format: min 8 characters\nLeave empty to skip.",
531
+ },
532
+ PROJECT_NAME: {
533
+ type: "string",
534
+ title: "PROJECT_NAME",
535
+ description: "Human-readable project name.",
536
+ },
537
+ },
538
+ },
539
+ };
540
+
541
+ const parsed = parseTextInputElicitation(request as any);
542
+ assert.deepEqual(parsed, [
543
+ {
544
+ id: "TEST_PASSWORD",
545
+ title: "TEST_PASSWORD",
546
+ description: "Format: min 8 characters\nLeave empty to skip.",
547
+ required: false,
548
+ secure: true,
549
+ },
550
+ {
551
+ id: "PROJECT_NAME",
552
+ title: "PROJECT_NAME",
553
+ description: "Human-readable project name.",
554
+ required: false,
555
+ secure: false,
556
+ },
557
+ ]);
558
+ });
559
+
560
+ test("parseTextInputElicitation accepts legacy keys schema and skips unsupported fields", () => {
561
+ const request = {
562
+ serverName: "gsd-workflow",
563
+ message: "Enter secure values",
564
+ mode: "form" as const,
565
+ requestedSchema: {
566
+ type: "object" as const,
567
+ keys: {
568
+ API_TOKEN: {
569
+ type: "string",
570
+ title: "API_TOKEN",
571
+ description: "Leave empty to skip.",
572
+ },
573
+ META: {
574
+ type: "object",
575
+ title: "metadata",
576
+ },
577
+ },
578
+ },
579
+ };
580
+
581
+ const parsed = parseTextInputElicitation(request as any);
582
+ assert.deepEqual(parsed, [
583
+ {
584
+ id: "API_TOKEN",
585
+ title: "API_TOKEN",
586
+ description: "Leave empty to skip.",
587
+ required: false,
588
+ secure: true,
589
+ },
590
+ ]);
591
+ });
592
+
593
+ test("createClaudeCodeElicitationHandler collects secure_env_collect fields through input dialogs", async () => {
594
+ const secureRequest = {
595
+ serverName: "gsd-workflow",
596
+ message: "Enter values for environment variables.",
597
+ mode: "form" as const,
598
+ requestedSchema: {
599
+ type: "object" as const,
600
+ properties: {
601
+ TEST_PASSWORD: {
602
+ type: "string",
603
+ title: "TEST_PASSWORD",
604
+ description: "Format: Your secure testing password\nLeave empty to skip.",
605
+ },
606
+ },
607
+ },
608
+ };
609
+
610
+ const inputCalls: Array<{ opts?: { secure?: boolean } }> = [];
611
+ const handler = createClaudeCodeElicitationHandler({
612
+ input: async (_title: string, _placeholder?: string, opts?: { secure?: boolean }) => {
613
+ inputCalls.push({ opts });
614
+ return "super-secret";
615
+ },
616
+ } as any);
617
+ assert.ok(handler);
618
+
619
+ const result = await handler!(secureRequest as any, { signal: new AbortController().signal });
620
+ assert.deepEqual(result, {
621
+ action: "accept",
622
+ content: {
623
+ TEST_PASSWORD: "super-secret",
624
+ },
625
+ });
626
+ assert.equal(inputCalls.length, 1);
627
+ assert.equal(inputCalls[0]?.opts?.secure, true, "secure_env_collect fields should request secure input");
628
+ });
484
629
  });
485
630
 
486
631
  describe("stream-adapter — Windows Claude path lookup (#3770)", () => {
@@ -126,7 +126,7 @@ async function collectOneSecret(
126
126
  ): Promise<string | null> {
127
127
  if (!ctx.hasUI) return null;
128
128
 
129
- return ctx.ui.custom((tui: any, theme: any, _kb: any, done: (r: string | null) => void) => {
129
+ const customResult = await ctx.ui.custom((tui: any, theme: any, _kb: any, done: (r: string | null) => void) => {
130
130
  let value = "";
131
131
  let cachedLines: string[] | undefined;
132
132
 
@@ -223,6 +223,29 @@ async function collectOneSecret(
223
223
  handleInput,
224
224
  };
225
225
  });
226
+
227
+ // RPC/web surfaces may not implement ctx.ui.custom(). Fall back to a
228
+ // standard input prompt so users can still provide the secret.
229
+ if (customResult !== undefined) {
230
+ return customResult;
231
+ }
232
+
233
+ if (typeof ctx.ui?.input !== "function") {
234
+ return null;
235
+ }
236
+
237
+ const inputTitle = `Secure value for ${keyName} (${pageIndex + 1}/${totalPages})`;
238
+ const inputPlaceholder = hint || "Enter secret value";
239
+ const inputResult = await ctx.ui.input(
240
+ inputTitle,
241
+ inputPlaceholder,
242
+ { secure: true },
243
+ );
244
+ if (typeof inputResult !== "string") {
245
+ return null;
246
+ }
247
+ const trimmed = inputResult.trim();
248
+ return trimmed.length > 0 ? trimmed : null;
226
249
  }
227
250
 
228
251
  /**
@@ -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
@@ -688,7 +678,7 @@ export async function bootstrapAutoSession(
688
678
  }
689
679
 
690
680
  // ── DB lifecycle ──
691
- const gsdDbPath = join(s.basePath, ".gsd", "gsd.db");
681
+ const gsdDbPath = resolveProjectRootDbPath(s.basePath);
692
682
  const gsdDirPath = join(s.basePath, ".gsd");
693
683
  if (existsSync(gsdDirPath) && !existsSync(gsdDbPath)) {
694
684
  const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
@@ -92,7 +92,7 @@ export function clearInFlightTools(): void {
92
92
  * handler. When these errors occur, retrying the same unit will produce the same
93
93
  * failure, so the retry loop must be broken.
94
94
  */
95
- const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}' in JSON|Unexpected end of JSON|Unexpected token.*in JSON/i;
95
+ const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}'(?: after property value)?(?: in JSON)?|Unexpected end of JSON|Unexpected token.*in JSON/i;
96
96
 
97
97
  /**
98
98
  * Returns true if the error message indicates a tool invocation failure due to
@@ -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
 
@@ -117,6 +121,8 @@ export function registerHooks(pi: ExtensionAPI): void {
117
121
  return { cancel: true };
118
122
  }
119
123
  const basePath = process.cwd();
124
+ const { ensureDbOpen } = await import("./dynamic-tools.js");
125
+ await ensureDbOpen();
120
126
  const state = await deriveState(basePath);
121
127
  if (!state.activeMilestone || !state.activeSlice || !state.activeTask) return;
122
128
  if (state.phase !== "executing") return;
@@ -293,6 +293,11 @@ function buildWorktreeContextBlock(): string {
293
293
  const RESUME_INTENT_PATTERNS = /^(continue|resume|ok|go|go ahead|proceed|keep going|carry on|next|yes|yeah|yep|sure|do it|let's go|pick up where you left off)$/;
294
294
 
295
295
  async function buildGuidedExecuteContextInjection(prompt: string, basePath: string): Promise<string | null> {
296
+ const ensureStateDbOpen = async () => {
297
+ const { ensureDbOpen } = await import("./dynamic-tools.js");
298
+ await ensureDbOpen();
299
+ };
300
+
296
301
  const executeMatch = prompt.match(/Execute the next task:\s+(T\d+)\s+\("([^"]+)"\)\s+in slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
297
302
  if (executeMatch) {
298
303
  const [, taskId, taskTitle, sliceId, milestoneId] = executeMatch;
@@ -302,6 +307,7 @@ async function buildGuidedExecuteContextInjection(prompt: string, basePath: stri
302
307
  const resumeMatch = prompt.match(/Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
303
308
  if (resumeMatch) {
304
309
  const [, sliceId, milestoneId] = resumeMatch;
310
+ await ensureStateDbOpen();
305
311
  const state = await deriveState(basePath);
306
312
  if (state.activeMilestone?.id === milestoneId && state.activeSlice?.id === sliceId && state.activeTask) {
307
313
  return buildTaskExecutionContextInjection(basePath, milestoneId, sliceId, state.activeTask.id, state.activeTask.title);
@@ -317,6 +323,7 @@ async function buildGuidedExecuteContextInjection(prompt: string, basePath: stri
317
323
  // replanning, gate evaluation, or other non-execution phases.
318
324
  const trimmed = prompt.trim().toLowerCase().replace(/[.!?,]+$/g, "");
319
325
  if (RESUME_INTENT_PATTERNS.test(trimmed)) {
326
+ await ensureStateDbOpen();
320
327
  const state = await deriveState(basePath);
321
328
  if (state.phase === "executing" && state.activeTask && state.activeMilestone && state.activeSlice) {
322
329
  return buildTaskExecutionContextInjection(
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
2
2
 
3
3
  import { checkRemoteAutoSession, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
4
- import { assertSafeDirectory } from "../validate-directory.js";
4
+ import { validateDirectory } from "../validate-directory.js";
5
5
  import { resolveProjectRoot } from "../worktree.js";
6
6
  import { showNextAction } from "../../shared/tui.js";
7
7
  import { handleStatus } from "./handlers/core.js";
@@ -12,6 +12,17 @@ export interface GsdDispatchContext {
12
12
  trimmed: string;
13
13
  }
14
14
 
15
+ /**
16
+ * Typed error for when GSD is run outside a valid project directory.
17
+ * Command handlers catch this to show a friendly message instead of a raw exception.
18
+ */
19
+ export class GSDNoProjectError extends Error {
20
+ constructor(reason: string) {
21
+ super(reason);
22
+ this.name = "GSDNoProjectError";
23
+ }
24
+ }
25
+
15
26
  export function projectRoot(): string {
16
27
  let cwd: string;
17
28
  try {
@@ -21,10 +32,10 @@ export function projectRoot(): string {
21
32
  cwd = process.env.HOME ?? "/";
22
33
  }
23
34
  const root = resolveProjectRoot(cwd);
24
- if (root !== cwd) {
25
- assertSafeDirectory(cwd);
26
- } else {
27
- assertSafeDirectory(root);
35
+ const pathToCheck = root !== cwd ? cwd : root;
36
+ const result = validateDirectory(pathToCheck);
37
+ if (result.severity === "blocked") {
38
+ throw new GSDNoProjectError(result.reason ?? "GSD must be run inside a project directory.");
28
39
  }
29
40
  return root;
30
41
  }
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
2
2
 
3
+ import { GSDNoProjectError } from "./context.js";
3
4
  import { handleAutoCommand } from "./handlers/auto.js";
4
5
  import { handleCoreCommand } from "./handlers/core.js";
5
6
  import { handleOpsCommand } from "./handlers/ops.js";
@@ -21,10 +22,21 @@ export async function handleGSDCommand(
21
22
  () => handleOpsCommand(trimmed, ctx, pi),
22
23
  ];
23
24
 
24
- for (const handler of handlers) {
25
- if (await handler()) {
25
+ try {
26
+ for (const handler of handlers) {
27
+ if (await handler()) {
28
+ return;
29
+ }
30
+ }
31
+ } catch (err) {
32
+ if (err instanceof GSDNoProjectError) {
33
+ ctx.ui.notify(
34
+ `${err.message} \`cd\` into a project directory first.`,
35
+ "warning",
36
+ );
26
37
  return;
27
38
  }
39
+ throw err;
28
40
  }
29
41
 
30
42
  ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
@@ -34,6 +34,7 @@ import {
34
34
  import { injectContext } from "./context-injector.js";
35
35
  import type { WorkflowDefinition, StepDefinition } from "./definition-loader.js";
36
36
  import { parseUnitId } from "./unit-id.js";
37
+ import { withFileLock } from "./file-lock.js";
37
38
 
38
39
  /** Read and parse the frozen DEFINITION.yaml from a run directory. */
39
40
  export function readFrozenDefinition(runDir: string): WorkflowDefinition {
@@ -179,24 +180,28 @@ export class CustomWorkflowEngine implements WorkflowEngine {
179
180
  state: EngineState,
180
181
  completedStep: CompletedStep,
181
182
  ): Promise<ReconcileResult> {
182
- // Re-read the graph from disk so we do not overwrite concurrent
183
- // workflow edits with a stale in-memory snapshot from deriveState().
184
- const graph = readGraph(this.runDir);
183
+ const graphPath = join(this.runDir, "GRAPH.yaml");
185
184
 
186
- // Extract stepId from "<workflowName>/<stepId>"
187
- const { milestone, slice, task } = parseUnitId(completedStep.unitId);
188
- const stepId = task ?? slice ?? milestone;
185
+ return await withFileLock(graphPath, () => {
186
+ // Re-read the graph from disk so we do not overwrite concurrent
187
+ // workflow edits with a stale in-memory snapshot from deriveState().
188
+ const graph = readGraph(this.runDir);
189
189
 
190
- const updatedGraph = markStepComplete(graph, stepId);
191
- writeGraph(this.runDir, updatedGraph);
190
+ // Extract stepId from "<workflowName>/<stepId>"
191
+ const { milestone, slice, task } = parseUnitId(completedStep.unitId);
192
+ const stepId = task ?? slice ?? milestone;
192
193
 
193
- const allDone = updatedGraph.steps.every(
194
- (s) => s.status === "complete" || s.status === "expanded",
195
- );
194
+ const updatedGraph = markStepComplete(graph, stepId);
195
+ writeGraph(this.runDir, updatedGraph);
196
196
 
197
- return {
198
- outcome: allDone ? "milestone-complete" : "continue",
199
- };
197
+ const allDone = updatedGraph.steps.every(
198
+ (s) => s.status === "complete" || s.status === "expanded",
199
+ );
200
+
201
+ return {
202
+ outcome: allDone ? "milestone-complete" : "continue",
203
+ };
204
+ });
200
205
  }
201
206
 
202
207
  /**
@@ -107,10 +107,27 @@ export function getPriorSliceCompletionBlocker(
107
107
  // it may be a cross-milestone reference handled elsewhere.
108
108
  }
109
109
  } else {
110
+ // Positional fallback is only a heuristic for legacy slices with no
111
+ // declared dependencies. Skip any earlier slice that depends on the
112
+ // target, directly or transitively, or we can deadlock a valid zero-dep
113
+ // slice behind its own downstream dependents (#3720).
114
+ const reverseDependents = new Set<string>();
115
+ let changed = true;
116
+ while (changed) {
117
+ changed = false;
118
+ for (const slice of slices) {
119
+ if (reverseDependents.has(slice.id)) continue;
120
+ if (slice.depends.some((depId) => depId === targetSid || reverseDependents.has(depId))) {
121
+ reverseDependents.add(slice.id);
122
+ changed = true;
123
+ }
124
+ }
125
+ }
126
+
110
127
  const targetIndex = slices.findIndex((slice) => slice.id === targetSid);
111
128
  const incomplete = slices
112
129
  .slice(0, targetIndex)
113
- .find((slice) => !slice.done);
130
+ .find((slice) => !slice.done && !reverseDependents.has(slice.id));
114
131
  if (incomplete) {
115
132
  return `Cannot dispatch ${unitType} ${unitId}: earlier slice ${targetMid}/${incomplete.id} is not complete.`;
116
133
  }
@@ -47,7 +47,7 @@ const RATE_LIMIT_RE = /rate.?limit|too many requests|429/i;
47
47
  const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns/i;
48
48
  const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i;
49
49
  // ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
50
- const CONNECTION_RE = /terminated|connection.?refused|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
50
+ const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
51
51
  // Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+".
52
52
  // This eliminates the need to enumerate every error message variant individually.
53
53
  const STREAM_RE = /in JSON at position \d+|Unexpected end of JSON|SyntaxError.*JSON/i;
@@ -0,0 +1,59 @@
1
+ import { existsSync } from "node:fs";
2
+
3
+ function _require(name: string) {
4
+ try {
5
+ return require(name);
6
+ } catch {
7
+ try {
8
+ const gsdPiRequire = require("module").createRequire(
9
+ require("path").join(process.cwd(), "node_modules", "gsd-pi", "index.js")
10
+ );
11
+ return gsdPiRequire(name);
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+ }
17
+
18
+ export function withFileLockSync<T>(filePath: string, fn: () => T): T {
19
+ const lockfile = _require("proper-lockfile");
20
+ if (!lockfile) return fn();
21
+
22
+ if (!existsSync(filePath)) return fn();
23
+
24
+ try {
25
+ const release = lockfile.lockSync(filePath, { retries: 5, stale: 10000 });
26
+ try {
27
+ return fn();
28
+ } finally {
29
+ release();
30
+ }
31
+ } catch (err: any) {
32
+ if (err.code === "ELOCKED") {
33
+ // Could not get lock after retries, let's fallback to un-locked instead of crashing the whole state machine
34
+ return fn();
35
+ }
36
+ throw err;
37
+ }
38
+ }
39
+
40
+ export async function withFileLock<T>(filePath: string, fn: () => Promise<T> | T): Promise<T> {
41
+ const lockfile = _require("proper-lockfile");
42
+ if (!lockfile) return await fn();
43
+
44
+ if (!existsSync(filePath)) return await fn();
45
+
46
+ try {
47
+ const release = await lockfile.lock(filePath, { retries: 5, stale: 10000 });
48
+ try {
49
+ return await fn();
50
+ } finally {
51
+ await release();
52
+ }
53
+ } catch (err: any) {
54
+ if (err.code === "ELOCKED") {
55
+ return await fn();
56
+ }
57
+ throw err;
58
+ }
59
+ }
@@ -426,8 +426,9 @@ function resolveAvailableModel<T extends { id: string; provider: string }>(
426
426
  * Build the discuss-and-plan prompt for a new milestone.
427
427
  * Used by all three "new milestone" paths (first ever, no active, all complete).
428
428
  */
429
- 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 {
430
430
  const milestoneRel = `.gsd/milestones/${nextId}`;
431
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
431
432
  const inlinedTemplates = [
432
433
  inlineTemplate("project", "Project"),
433
434
  inlineTemplate("requirements", "Requirements"),
@@ -439,6 +440,7 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string,
439
440
  milestoneId: nextId,
440
441
  preamble,
441
442
  preparationContext: preparationContext ?? "",
443
+ structuredQuestionsAvailable,
442
444
  contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
443
445
  roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
444
446
  inlinedTemplates,
@@ -486,6 +488,7 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
486
488
  */
487
489
  async function prepareAndBuildDiscussPrompt(
488
490
  ctx: ExtensionCommandContext,
491
+ pi: ExtensionAPI,
489
492
  nextId: string,
490
493
  preamble: string,
491
494
  basePath: string,
@@ -520,7 +523,7 @@ async function prepareAndBuildDiscussPrompt(
520
523
  }
521
524
  }
522
525
 
523
- return buildDiscussPrompt(nextId, preamble, basePath, preparationContext);
526
+ return buildDiscussPrompt(nextId, preamble, basePath, pi, ctx, preparationContext);
524
527
  }
525
528
 
526
529
  /**
@@ -780,7 +783,7 @@ export async function showDiscuss(
780
783
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
781
784
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
782
785
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
783
- 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");
784
787
  }
785
788
  return;
786
789
  }
@@ -1185,7 +1188,7 @@ async function handleMilestoneActions(
1185
1188
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1186
1189
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1187
1190
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1188
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1191
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1189
1192
  `New milestone ${nextId}.`,
1190
1193
  basePath
1191
1194
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1375,7 +1378,7 @@ export async function showSmartEntry(
1375
1378
  if (isFirst) {
1376
1379
  // First ever — skip wizard, just ask directly
1377
1380
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1378
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1381
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1379
1382
  `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
1380
1383
  basePath
1381
1384
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1396,7 +1399,7 @@ export async function showSmartEntry(
1396
1399
 
1397
1400
  if (choice === "new_milestone") {
1398
1401
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1399
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1402
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1400
1403
  `New milestone ${nextId}.`,
1401
1404
  basePath
1402
1405
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1435,7 +1438,7 @@ export async function showSmartEntry(
1435
1438
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1436
1439
 
1437
1440
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1438
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1441
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1439
1442
  `New milestone ${nextId}.`,
1440
1443
  basePath
1441
1444
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1502,7 +1505,7 @@ export async function showSmartEntry(
1502
1505
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1503
1506
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1504
1507
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1505
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1508
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1506
1509
  `New milestone ${nextId}.`,
1507
1510
  basePath
1508
1511
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1599,7 +1602,7 @@ export async function showSmartEntry(
1599
1602
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1600
1603
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1601
1604
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1602
- await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1605
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
1603
1606
  `New milestone ${nextId}.`,
1604
1607
  basePath
1605
1608
  ), "gsd-run", ctx, "discuss-milestone");
@@ -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");