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
package/README.md CHANGED
@@ -27,36 +27,43 @@ One command. Walk away. Come back to a built project with clean git history.
27
27
 
28
28
  ---
29
29
 
30
- ## What's New in v2.68
30
+ ## What's New in v2.71
31
31
 
32
- ### MCP Workflow Tools
32
+ ### MCP Secure Env Collect
33
33
 
34
- - **Full workflow over MCP** — slice replanning, milestone management, slice completion, task completion, and core planning tools are now exposed over MCP for external integrations.
35
- - **Transport-gated MCP** — workflow tool availability adapts to provider transport capabilities automatically.
36
- - **Write gate enforcement** — workflow MCP respects write gates, preventing unauthorized state mutations from external clients.
34
+ - **Secure credential collection over MCP** — the new `secure_env_collect` tool uses MCP form elicitation to collect secrets (API keys, tokens) from external clients without exposing values in tool output. Masks input in interactive mode.
35
+ - **Hardened elicitation schema** — MCP elicitation schema handling is stricter, with proper validation and fallback for providers that don't support forms.
37
36
 
38
- ### Reliability & Recovery
37
+ ### MCP Reliability
39
38
 
40
- - **False degraded-mode fix** — eliminates spurious degraded-mode warnings when the DB hasn't been initialized yet.
41
- - **Stale session resume suppression** — prevents stale interrupted-session resume prompts from hijacking fresh sessions.
42
- - **Merge conflict recovery** — `autoCommitDirtyState` guarded with cwd restore on `MergeConflictError`.
43
- - **Auto-resume hardening** — `autoStartTime` restored on resume, managed resources resynced on auto resume.
39
+ - **Stream ordering preserved** — MCP tool output now renders in the correct order, fixing interleaved output in Claude Code and other MCP clients.
40
+ - **isError flag propagation** — workflow tool execution failures now correctly return `isError: true`, so MCP clients can distinguish success from failure.
41
+ - **Multi-round discuss questions** — new-project discuss phase supports multi-round questioning with structured question gates.
44
42
 
45
- ### TUI & Developer Experience
43
+ ### TUI Fixes
46
44
 
47
- - **Contextual tips system** — TUI and web terminal now surface contextual tips based on workflow state.
48
- - **Claude Code MCP streaming** — real-time streaming and tool output rendering for Claude Code MCP connections.
45
+ - **Pinned output restored** — pinned output bar displays above the editor during tool execution again.
46
+ - **Turn completion cleanup** — pinned latest output is cleared on turn completion, preventing stale output from persisting.
47
+ - **Secure input masking** — extension input values are masked in interactive mode when collecting secrets.
49
48
 
50
- ### Infrastructure
49
+ ### Reliability & Internals
51
50
 
52
- - **Weekly model registry refresh** — CI workflow auto-regenerates the model registry on a weekly schedule.
53
- - **Codebase cache auto-refresh** — stale codebase cache is refreshed automatically without manual intervention.
51
+ - **TOCTOU file locking** — race conditions in event log and custom workflow graph file locking are fixed with proper atomic lock acquisition.
52
+ - **State derive refactor** — `deriveStateFromDb` god function extracted into composable, testable helpers.
53
+ - **Windows portability** — hardened cross-platform portability across runtime, tooling, and CI.
54
+ - **Model routing transparency** — dynamic routing is skipped for interactive dispatches; model changes are always shown in the banner.
55
+ - **Capability-aware routing (ADR-004)** — full implementation of capability scoring, `before_model_select` hook, and task metadata extraction.
56
+ - **Multi-model provider strategy (ADR-005)** — infrastructure for multi-provider model selection wired into live paths.
54
57
 
55
58
  See the full [Changelog](./CHANGELOG.md) for details on every release.
56
59
 
57
60
  <details>
58
- <summary>Previous highlights (v2.67 and earlier)</summary>
61
+ <summary>Previous highlights (v2.70 and earlier)</summary>
59
62
 
63
+ - **Full workflow over MCP (v2.68)** — slice replanning, milestone management, slice completion, task completion, and core planning tools exposed over MCP
64
+ - **Transport-gated MCP (v2.68)** — workflow tool availability adapts to provider transport capabilities automatically
65
+ - **Contextual tips system (v2.68)** — TUI and web terminal surface contextual tips based on workflow state
66
+ - **Ask user questions over MCP (v2.70)** — interactive questions exposed via elicitation for external integrations
60
67
  - **Tiered Context Injection (M005)** — relevance-scoped context with 65%+ token reduction
61
68
  - **Resilient transient error recovery** — defers to Core RetryHandler and fixes cmdCtx race conditions
62
69
  - **Anthropic subscription routing** — auto-routed through Claude Code CLI provider with proper display names
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { ensureManagedTools } from './tool-bootstrap.js';
7
7
  import { loadStoredEnvKeys } from './wizard.js';
8
8
  import { migratePiCredentials } from './pi-migration.js';
9
9
  import { validateConfiguredModel } from './startup-model-validation.js';
10
+ import { shouldMigrateAnthropicToClaudeCode } from './provider-migrations.js';
10
11
  import { shouldRunOnboarding, runOnboarding } from './onboarding.js';
11
12
  import chalk from 'chalk';
12
13
  import { checkForUpdates } from './update-check.js';
@@ -285,7 +286,7 @@ const { resolveModelsJsonPath } = await import('./models-resolver.js');
285
286
  const modelsJsonPath = resolveModelsJsonPath();
286
287
  const modelRegistry = new ModelRegistry(authStorage, modelsJsonPath);
287
288
  markStartup('ModelRegistry');
288
- const settingsManager = SettingsManager.create(agentDir);
289
+ const settingsManager = SettingsManager.create(process.cwd(), agentDir);
289
290
  applySecurityOverrides(settingsManager);
290
291
  markStartup('SettingsManager.create');
291
292
  // Run onboarding wizard on first launch (no LLM provider configured)
@@ -401,7 +402,11 @@ if (isPrintMode) {
401
402
  // Migrate anthropic OAuth users to claude-code provider when CLI is available (#3772).
402
403
  // Anthropic blocks third-party apps from using subscription quotas — routing through
403
404
  // the local claude CLI binary is TOS-compliant.
404
- if (modelRegistry.isProviderRequestReady('claude-code') && settingsManager.getDefaultProvider() === 'anthropic') {
405
+ if (shouldMigrateAnthropicToClaudeCode({
406
+ authStorage,
407
+ isClaudeCodeReady: modelRegistry.isProviderRequestReady('claude-code'),
408
+ defaultProvider: settingsManager.getDefaultProvider(),
409
+ })) {
405
410
  const currentModelId = settingsManager.getDefaultModel();
406
411
  if (currentModelId) {
407
412
  const ccModel = modelRegistry.find('claude-code', currentModelId);
@@ -576,7 +581,11 @@ markStartup('createAgentSession');
576
581
  // Migrate anthropic OAuth users to claude-code provider when CLI is available (#3772).
577
582
  // Anthropic blocks third-party apps from using subscription quotas — routing through
578
583
  // the local claude CLI binary is TOS-compliant.
579
- if (modelRegistry.isProviderRequestReady('claude-code') && settingsManager.getDefaultProvider() === 'anthropic') {
584
+ if (shouldMigrateAnthropicToClaudeCode({
585
+ authStorage,
586
+ isClaudeCodeReady: modelRegistry.isProviderRequestReady('claude-code'),
587
+ defaultProvider: settingsManager.getDefaultProvider(),
588
+ })) {
580
589
  const currentModelId = settingsManager.getDefaultModel();
581
590
  if (currentModelId) {
582
591
  const ccModel = modelRegistry.find('claude-code', currentModelId);
@@ -1,10 +1,10 @@
1
1
  // MCP SDK subpath imports use wildcard exports (./*) that NodeNext resolves
2
2
  // at runtime but TypeScript cannot statically type-check. We construct the
3
3
  // specifiers dynamically so tsc treats them as `any`.
4
- // Use createRequire to resolve wildcard subpaths — CJS resolver auto-appends
5
- // .js, which the ESM wildcard export map does not (#3603).
6
- import { createRequire } from 'node:module';
7
- const _require = createRequire(import.meta.url);
4
+ //
5
+ // Use explicit .js subpaths for modules that are loaded dynamically at runtime.
6
+ // Recent Node / SDK combinations do not reliably resolve the extensionless
7
+ // wildcard targets for `server/stdio` and `types` (#3914).
8
8
  const MCP_PKG = '@modelcontextprotocol/sdk';
9
9
  /**
10
10
  * Starts a native MCP (Model Context Protocol) server over stdin/stdout.
@@ -23,8 +23,8 @@ const MCP_PKG = '@modelcontextprotocol/sdk';
23
23
  export async function startMcpServer(options) {
24
24
  const { tools, version = '0.0.0' } = options;
25
25
  const serverMod = await import(`${MCP_PKG}/server`);
26
- const stdioMod = await import(_require.resolve(`${MCP_PKG}/server/stdio`));
27
- const typesMod = await import(_require.resolve(`${MCP_PKG}/types`));
26
+ const stdioMod = await import(`${MCP_PKG}/server/stdio.js`);
27
+ const typesMod = await import(`${MCP_PKG}/types.js`);
28
28
  const Server = serverMod.Server;
29
29
  const StdioServerTransport = stdioMod.StdioServerTransport;
30
30
  const { ListToolsRequestSchema, CallToolRequestSchema } = typesMod;
@@ -0,0 +1,10 @@
1
+ import type { AuthStorage } from "@gsd/pi-coding-agent";
2
+ type AnthropicMigrationDeps = {
3
+ authStorage: Pick<AuthStorage, "getCredentialsForProvider">;
4
+ isClaudeCodeReady: boolean;
5
+ defaultProvider: string | undefined;
6
+ env?: NodeJS.ProcessEnv;
7
+ };
8
+ export declare function hasDirectAnthropicApiKey(authStorage: Pick<AuthStorage, "getCredentialsForProvider">, env?: NodeJS.ProcessEnv): boolean;
9
+ export declare function shouldMigrateAnthropicToClaudeCode({ authStorage, isClaudeCodeReady, defaultProvider, env, }: AnthropicMigrationDeps): boolean;
10
+ export {};
@@ -0,0 +1,12 @@
1
+ export function hasDirectAnthropicApiKey(authStorage, env = process.env) {
2
+ if ((env.ANTHROPIC_API_KEY ?? "").trim()) {
3
+ return true;
4
+ }
5
+ return authStorage.getCredentialsForProvider("anthropic").some((credential) => credential?.type === "api_key" && typeof credential?.key === "string" && credential.key.trim().length > 0);
6
+ }
7
+ export function shouldMigrateAnthropicToClaudeCode({ authStorage, isClaudeCodeReady, defaultProvider, env = process.env, }) {
8
+ if (!isClaudeCodeReady || defaultProvider !== "anthropic") {
9
+ return false;
10
+ }
11
+ return !hasDirectAnthropicApiKey(authStorage, env);
12
+ }
@@ -2,7 +2,7 @@ import { DefaultResourceLoader, sortExtensionPaths } from '@gsd/pi-coding-agent'
2
2
  import { createHash } from 'node:crypto';
3
3
  import { homedir } from 'node:os';
4
4
  import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, openSync, closeSync, readFileSync, readlinkSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
5
- import { dirname, join, relative, resolve } from 'node:path';
5
+ import { basename, dirname, join, relative, resolve } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { compareSemver } from './update-check.js';
8
8
  import { discoverExtensionEntryPaths } from './extension-discovery.js';
@@ -254,34 +254,157 @@ function copyDirRecursive(src, dest) {
254
254
  * ~/.gsd/agent/extensions/ have no ancestor node_modules, so imports of
255
255
  * @gsd/* packages fail. The symlink makes Node's standard resolution find
256
256
  * them without requiring every call site to use jiti.
257
+ *
258
+ * Layout differences by install method:
259
+ * - Source/monorepo: packageRoot/node_modules has everything → simple symlink
260
+ * - npm/bun global: deps hoisted to dirname(packageRoot), including @gsd/* → simple symlink
261
+ * - pnpm global: external deps hoisted, but @gsd/* stays in packageRoot/node_modules
262
+ * → merged directory with symlinks from both roots (#3529, #3564)
257
263
  */
258
264
  function ensureNodeModulesSymlink(agentDir) {
259
265
  const agentNodeModules = join(agentDir, 'node_modules');
260
- const gsdNodeModules = join(packageRoot, 'node_modules');
266
+ const internalNodeModules = join(packageRoot, 'node_modules');
267
+ const hoistedNodeModules = dirname(packageRoot);
268
+ const isGlobalInstall = basename(hoistedNodeModules) === 'node_modules';
269
+ if (!isGlobalInstall) {
270
+ // Source/monorepo: internal node_modules has everything
271
+ reconcileSymlink(agentNodeModules, internalNodeModules);
272
+ return;
273
+ }
274
+ // Global install: check if workspace scopes (@gsd/*) are hoisted.
275
+ // npm/bun hoist everything; pnpm keeps workspace packages internal.
276
+ if (!hasMissingWorkspaceScopes(hoistedNodeModules, internalNodeModules)) {
277
+ // Everything is hoisted — simple symlink to parent node_modules
278
+ reconcileSymlink(agentNodeModules, hoistedNodeModules);
279
+ return;
280
+ }
281
+ // pnpm-style layout: create a real directory merging both roots
282
+ reconcileMergedNodeModules(agentNodeModules, hoistedNodeModules, internalNodeModules);
283
+ }
284
+ /** Check if any @gsd* scopes exist in internal but not in hoisted node_modules */
285
+ function hasMissingWorkspaceScopes(hoisted, internal) {
286
+ if (!existsSync(internal))
287
+ return false;
261
288
  try {
262
- const stat = lstatSync(agentNodeModules);
289
+ for (const entry of readdirSync(internal, { withFileTypes: true })) {
290
+ if (entry.isDirectory() && entry.name.startsWith('@gsd') &&
291
+ !existsSync(join(hoisted, entry.name))) {
292
+ return true;
293
+ }
294
+ }
295
+ }
296
+ catch { /* non-fatal */ }
297
+ return false;
298
+ }
299
+ /** Ensure a symlink at `link` points to `target`, fixing stale/wrong entries */
300
+ function reconcileSymlink(link, target) {
301
+ try {
302
+ const stat = lstatSync(link);
263
303
  if (stat.isSymbolicLink()) {
264
- const existing = readlinkSync(agentNodeModules);
265
- // Symlink exists verify it points to the correct, existing target
266
- if (existing === gsdNodeModules && existsSync(agentNodeModules))
304
+ const existing = readlinkSync(link);
305
+ if (existing === target && existsSync(link))
267
306
  return; // correct and target exists
268
- // Stale or wrong target — remove and recreate
307
+ unlinkSync(link);
308
+ }
309
+ else {
310
+ // Real directory (or merged dir from previous pnpm fix) — remove it
311
+ rmSync(link, { recursive: true, force: true });
312
+ }
313
+ }
314
+ catch {
315
+ // lstatSync throws if path doesn't exist — fine, we'll create below
316
+ }
317
+ try {
318
+ symlinkSync(target, link, 'junction');
319
+ }
320
+ catch (err) {
321
+ console.error(`[gsd] WARN: Failed to symlink ${link} → ${target}: ${err instanceof Error ? err.message : err}`);
322
+ }
323
+ }
324
+ /**
325
+ * Create a real node_modules directory containing symlinks from both the
326
+ * hoisted root (external deps) and internal root (@gsd/* workspace packages).
327
+ * Used for pnpm global installs where @gsd/* isn't hoisted.
328
+ */
329
+ function reconcileMergedNodeModules(agentNodeModules, hoisted, internal) {
330
+ // Fast path: if already merged for this packageRoot + same directory contents, skip.
331
+ // The fingerprint includes entry names from both roots so `pnpm add/remove` triggers rebuild.
332
+ const marker = join(agentNodeModules, '.gsd-merged');
333
+ const fingerprint = mergedFingerprint(hoisted, internal);
334
+ try {
335
+ if (existsSync(marker) && readFileSync(marker, 'utf-8').trim() === fingerprint)
336
+ return;
337
+ }
338
+ catch { /* rebuild */ }
339
+ // Remove any existing symlink or stale merged directory
340
+ try {
341
+ const stat = lstatSync(agentNodeModules);
342
+ if (stat.isSymbolicLink()) {
269
343
  unlinkSync(agentNodeModules);
270
344
  }
271
345
  else {
272
- // Real directory (not a symlink) is blocking — remove it
273
346
  rmSync(agentNodeModules, { recursive: true, force: true });
274
347
  }
275
348
  }
276
- catch {
277
- // lstatSync throws if path doesn't exist — that's fine, we'll create below
349
+ catch { /* doesn't exist */ }
350
+ mkdirSync(agentNodeModules, { recursive: true });
351
+ let linkedCount = 0;
352
+ // Symlink entries from the hoisted node_modules (external deps)
353
+ try {
354
+ for (const entry of readdirSync(hoisted, { withFileTypes: true })) {
355
+ // Skip the gsd-pi package itself and dotfiles
356
+ if (entry.name === basename(packageRoot))
357
+ continue;
358
+ if (entry.name.startsWith('.'))
359
+ continue;
360
+ try {
361
+ symlinkSync(join(hoisted, entry.name), join(agentNodeModules, entry.name));
362
+ linkedCount++;
363
+ }
364
+ catch { /* skip individual */ }
365
+ }
278
366
  }
367
+ catch (err) {
368
+ console.error(`[gsd] WARN: Failed to read hoisted node_modules at ${hoisted}: ${err instanceof Error ? err.message : err}`);
369
+ }
370
+ // Overlay @gsd* workspace scopes from internal node_modules
279
371
  try {
280
- symlinkSync(gsdNodeModules, agentNodeModules, 'junction');
372
+ for (const entry of readdirSync(internal, { withFileTypes: true })) {
373
+ if (!entry.name.startsWith('@gsd'))
374
+ continue;
375
+ const link = join(agentNodeModules, entry.name);
376
+ try {
377
+ lstatSync(link);
378
+ unlinkSync(link);
379
+ }
380
+ catch { /* didn't exist */ }
381
+ try {
382
+ symlinkSync(join(internal, entry.name), link);
383
+ linkedCount++;
384
+ }
385
+ catch { /* skip individual */ }
386
+ }
281
387
  }
282
388
  catch (err) {
283
- // This failure makes GSD non-functional extensions can't resolve @gsd/* packages
284
- console.error(`[gsd] WARN: Failed to symlink ${agentNodeModules} → ${gsdNodeModules}: ${err instanceof Error ? err.message : err}`);
389
+ console.error(`[gsd] WARN: Failed to read internal node_modules at ${internal}: ${err instanceof Error ? err.message : err}`);
390
+ }
391
+ // Only stamp marker if we actually linked something — avoids caching a broken state
392
+ if (linkedCount > 0) {
393
+ try {
394
+ writeFileSync(marker, fingerprint);
395
+ }
396
+ catch { /* non-fatal */ }
397
+ }
398
+ }
399
+ /** Build a cache fingerprint from packageRoot + sorted entry names of both directories */
400
+ function mergedFingerprint(hoisted, internal) {
401
+ try {
402
+ const h = readdirSync(hoisted).sort().join(',');
403
+ const i = readdirSync(internal).sort().join(',');
404
+ return `${packageRoot}\n${h}\n${i}`;
405
+ }
406
+ catch {
407
+ return packageRoot; // fallback: at least invalidate on version change
285
408
  }
286
409
  }
287
410
  /**
@@ -275,7 +275,7 @@ Work flows through these phases. Each phase produces a file.
275
275
  **How to do it manually:**
276
276
  1. Read the roadmap to understand the scope.
277
277
  2. Identify 3-5 gray areas — implementation decisions the user cares about.
278
- 3. Use `ask_user_questions` to discuss each area.
278
+ 3. Use `ask_user_questions` to discuss each area, one round at a time. Never fabricate user input; wait for the user's actual response before the next round.
279
279
  4. Write decisions to the appropriate context file (`M###-CONTEXT.md` or `S##-CONTEXT.md`).
280
280
  5. Do NOT discuss how to implement — only what the user wants.
281
281
 
@@ -12,6 +12,7 @@ import { PartialMessageBuilder, ZERO_USAGE, mapUsage } from "./partial-builder.j
12
12
  import { buildWorkflowMcpServers } from "../gsd/workflow-mcp.js";
13
13
  import { showInterviewRound } from "../shared/tui.js";
14
14
  const OTHER_OPTION_LABEL = "None of the above";
15
+ const SENSITIVE_FIELD_PATTERN = /(password|passphrase|secret|token|api[_\s-]*key|private[_\s-]*key|credential)/i;
15
16
  // ---------------------------------------------------------------------------
16
17
  // Stream factory
17
18
  // ---------------------------------------------------------------------------
@@ -182,6 +183,56 @@ export function parseAskUserQuestionsElicitation(request) {
182
183
  }
183
184
  return questions.length > 0 ? questions : null;
184
185
  }
186
+ function isSecureElicitationField(requestMessage, fieldId, field) {
187
+ if (field.format === "password")
188
+ return true;
189
+ if (field.writeOnly === true)
190
+ return true;
191
+ const rawField = field;
192
+ if (rawField.sensitive === true || rawField["x-sensitive"] === true)
193
+ return true;
194
+ const haystack = [
195
+ requestMessage,
196
+ fieldId.replace(/[_-]+/g, " "),
197
+ typeof field.title === "string" ? field.title : "",
198
+ typeof field.description === "string" ? field.description : "",
199
+ ]
200
+ .join(" ")
201
+ .toLowerCase();
202
+ return SENSITIVE_FIELD_PATTERN.test(haystack);
203
+ }
204
+ export function parseTextInputElicitation(request) {
205
+ if (request.mode && request.mode !== "form")
206
+ return null;
207
+ const schema = request.requestedSchema;
208
+ const fieldsSource = schema?.properties && typeof schema.properties === "object"
209
+ ? schema.properties
210
+ : schema?.keys && typeof schema.keys === "object"
211
+ ? schema.keys
212
+ : undefined;
213
+ if (!fieldsSource)
214
+ return null;
215
+ const requiredSet = new Set(Array.isArray(request.requestedSchema?.required)
216
+ ? request.requestedSchema.required.filter((value) => typeof value === "string")
217
+ : []);
218
+ const fields = [];
219
+ for (const [fieldId, field] of Object.entries(fieldsSource)) {
220
+ if (!field || typeof field !== "object")
221
+ continue;
222
+ if (field.type !== "string")
223
+ continue;
224
+ if (Array.isArray(field.oneOf) && field.oneOf.length > 0)
225
+ continue;
226
+ fields.push({
227
+ id: fieldId,
228
+ title: typeof field.title === "string" && field.title.length > 0 ? field.title : fieldId,
229
+ description: typeof field.description === "string" ? field.description : "",
230
+ required: requiredSet.has(fieldId),
231
+ secure: isSecureElicitationField(request.message, fieldId, field),
232
+ });
233
+ }
234
+ return fields.length > 0 ? fields : null;
235
+ }
185
236
  export function roundResultToElicitationContent(questions, result) {
186
237
  const content = {};
187
238
  for (const question of questions) {
@@ -246,6 +297,38 @@ async function promptElicitationWithDialogs(request, questions, ui, signal) {
246
297
  }
247
298
  return { action: "accept", content };
248
299
  }
300
+ function buildTextInputPromptTitle(request, field) {
301
+ const parts = [
302
+ request.serverName ? `[${request.serverName}]` : "",
303
+ field.title,
304
+ field.description,
305
+ ].filter((part) => typeof part === "string" && part.trim().length > 0);
306
+ return parts.join("\n\n");
307
+ }
308
+ function buildTextInputPlaceholder(field) {
309
+ const desc = field.description.trim();
310
+ if (!desc)
311
+ return field.required ? "Required" : "Leave empty to skip";
312
+ const formatLine = desc
313
+ .split(/\r?\n/)
314
+ .map((line) => line.trim())
315
+ .find((line) => /^format:/i.test(line));
316
+ if (!formatLine)
317
+ return field.required ? "Required" : "Leave empty to skip";
318
+ const hint = formatLine.replace(/^format:\s*/i, "").trim();
319
+ return hint.length > 0 ? hint : field.required ? "Required" : "Leave empty to skip";
320
+ }
321
+ async function promptTextInputElicitation(request, fields, ui, signal) {
322
+ const content = {};
323
+ for (const field of fields) {
324
+ const value = await ui.input(buildTextInputPromptTitle(request, field), buildTextInputPlaceholder(field), { signal, ...(field.secure ? { secure: true } : {}) });
325
+ if (value === undefined) {
326
+ return { action: "cancel" };
327
+ }
328
+ content[field.id] = value;
329
+ }
330
+ return { action: "accept", content };
331
+ }
249
332
  export function createClaudeCodeElicitationHandler(ui) {
250
333
  if (!ui)
251
334
  return undefined;
@@ -254,17 +337,21 @@ export function createClaudeCodeElicitationHandler(ui) {
254
337
  return { action: "decline" };
255
338
  }
256
339
  const questions = parseAskUserQuestionsElicitation(request);
257
- if (!questions) {
258
- return { action: "decline" };
340
+ if (questions) {
341
+ const interviewResult = await showInterviewRound(questions, { signal }, { ui }).catch(() => undefined);
342
+ if (interviewResult && Object.keys(interviewResult.answers).length > 0) {
343
+ return {
344
+ action: "accept",
345
+ content: roundResultToElicitationContent(questions, interviewResult),
346
+ };
347
+ }
348
+ return promptElicitationWithDialogs(request, questions, ui, signal);
259
349
  }
260
- const interviewResult = await showInterviewRound(questions, { signal }, { ui }).catch(() => undefined);
261
- if (interviewResult && Object.keys(interviewResult.answers).length > 0) {
262
- return {
263
- action: "accept",
264
- content: roundResultToElicitationContent(questions, interviewResult),
265
- };
350
+ const textFields = parseTextInputElicitation(request);
351
+ if (textFields) {
352
+ return promptTextInputElicitation(request, textFields, ui, signal);
266
353
  }
267
- return promptElicitationWithDialogs(request, questions, ui, signal);
354
+ return { action: "decline" };
268
355
  };
269
356
  }
270
357
  // ---------------------------------------------------------------------------
@@ -278,6 +365,7 @@ export function createClaudeCodeElicitationHandler(ui) {
278
365
  */
279
366
  export function buildSdkOptions(modelId, prompt, extraOptions = {}) {
280
367
  const mcpServers = buildWorkflowMcpServers();
368
+ const disallowedTools = ["AskUserQuestion"];
281
369
  return {
282
370
  pathToClaudeCodeExecutable: getClaudePath(),
283
371
  model: modelId,
@@ -288,6 +376,7 @@ export function buildSdkOptions(modelId, prompt, extraOptions = {}) {
288
376
  allowDangerouslySkipPermissions: true,
289
377
  settingSources: ["project"],
290
378
  systemPrompt: { type: "preset", preset: "claude_code" },
379
+ disallowedTools,
291
380
  ...(mcpServers ? { mcpServers } : {}),
292
381
  betas: modelId.includes("sonnet") ? ["context-1m-2025-08-07"] : [],
293
382
  ...extraOptions,
@@ -371,9 +460,9 @@ export function extractToolResultsFromSdkUserMessage(message) {
371
460
  }
372
461
  return extracted;
373
462
  }
374
- function attachExternalResultsToToolCalls(toolCalls, toolResultsById) {
375
- for (const block of toolCalls) {
376
- if (block.type !== "toolCall")
463
+ function attachExternalResultsToToolBlocks(toolBlocks, toolResultsById) {
464
+ for (const block of toolBlocks) {
465
+ if (block.type !== "toolCall" && block.type !== "serverToolUse")
377
466
  continue;
378
467
  const externalResult = toolResultsById.get(block.id);
379
468
  if (!externalResult)
@@ -402,8 +491,8 @@ async function pumpSdkMessages(model, context, options, stream) {
402
491
  /** Track the last text content seen across all assistant turns for the final message. */
403
492
  let lastTextContent = "";
404
493
  let lastThinkingContent = "";
405
- /** Collect tool calls from intermediate SDK turns for tool_execution events. */
406
- const intermediateToolCalls = [];
494
+ /** Collect tool blocks from intermediate SDK turns for tool execution rendering. */
495
+ const intermediateToolBlocks = [];
407
496
  /** Preserve real external tool results from Claude Code's synthetic user messages. */
408
497
  const toolResultsById = new Map();
409
498
  try {
@@ -491,9 +580,9 @@ async function pumpSdkMessages(model, context, options, stream) {
491
580
  else if (block.type === "thinking" && block.thinking) {
492
581
  lastThinkingContent = block.thinking;
493
582
  }
494
- else if (block.type === "toolCall") {
495
- // Collect tool calls for externalToolExecution rendering
496
- intermediateToolCalls.push(block);
583
+ else if (block.type === "toolCall" || block.type === "serverToolUse") {
584
+ // Collect tool blocks for externalToolExecution rendering
585
+ intermediateToolBlocks.push(block);
497
586
  }
498
587
  }
499
588
  }
@@ -502,25 +591,35 @@ async function pumpSdkMessages(model, context, options, stream) {
502
591
  for (const { toolUseId, result } of extractToolResultsFromSdkUserMessage(msg)) {
503
592
  toolResultsById.set(toolUseId, result);
504
593
  }
505
- attachExternalResultsToToolCalls(intermediateToolCalls, toolResultsById);
594
+ attachExternalResultsToToolBlocks(intermediateToolBlocks, toolResultsById);
506
595
  // Push a synthetic toolcall_end for each tool call from this turn
507
596
  // so the TUI can render tool results in real-time during the SDK
508
597
  // session instead of waiting until the entire session completes.
509
598
  if (builder) {
510
599
  for (const block of builder.message.content) {
511
- if (block.type !== "toolCall")
512
- continue;
513
600
  const extResult = block.externalResult;
514
601
  if (!extResult)
515
602
  continue;
516
- // Push a toolcall_end with result attached so the chat-controller
517
- // can call updateResult on the pending ToolExecutionComponent.
518
- stream.push({
519
- type: "toolcall_end",
520
- contentIndex: builder.message.content.indexOf(block),
521
- toolCall: block,
522
- partial: builder.message,
523
- });
603
+ const contentIndex = builder.message.content.indexOf(block);
604
+ if (contentIndex < 0)
605
+ continue;
606
+ // Push synthetic completion events with result attached so the
607
+ // chat-controller can update pending ToolExecutionComponents.
608
+ if (block.type === "toolCall") {
609
+ stream.push({
610
+ type: "toolcall_end",
611
+ contentIndex,
612
+ toolCall: block,
613
+ partial: builder.message,
614
+ });
615
+ }
616
+ else if (block.type === "serverToolUse") {
617
+ stream.push({
618
+ type: "server_tool_use",
619
+ contentIndex,
620
+ partial: builder.message,
621
+ });
622
+ }
524
623
  }
525
624
  }
526
625
  builder = null;
@@ -534,8 +633,8 @@ async function pumpSdkMessages(model, context, options, stream) {
534
633
  // events for proper TUI rendering, followed by the text response.
535
634
  const finalContent = [];
536
635
  // Add tool calls from intermediate turns first (renders above text)
537
- attachExternalResultsToToolCalls(intermediateToolCalls, toolResultsById);
538
- finalContent.push(...intermediateToolCalls);
636
+ attachExternalResultsToToolBlocks(intermediateToolBlocks, toolResultsById);
637
+ finalContent.push(...intermediateToolBlocks);
539
638
  // Add text/thinking from the last turn
540
639
  if (builder && builder.message.content.length > 0) {
541
640
  for (const block of builder.message.content) {
@@ -93,7 +93,7 @@ export function detectDestination(basePath) {
93
93
  async function collectOneSecret(ctx, pageIndex, totalPages, keyName, hint, guidance) {
94
94
  if (!ctx.hasUI)
95
95
  return null;
96
- return ctx.ui.custom((tui, theme, _kb, done) => {
96
+ const customResult = await ctx.ui.custom((tui, theme, _kb, done) => {
97
97
  let value = "";
98
98
  let cachedLines;
99
99
  const editorTheme = {
@@ -178,6 +178,22 @@ async function collectOneSecret(ctx, pageIndex, totalPages, keyName, hint, guida
178
178
  handleInput,
179
179
  };
180
180
  });
181
+ // RPC/web surfaces may not implement ctx.ui.custom(). Fall back to a
182
+ // standard input prompt so users can still provide the secret.
183
+ if (customResult !== undefined) {
184
+ return customResult;
185
+ }
186
+ if (typeof ctx.ui?.input !== "function") {
187
+ return null;
188
+ }
189
+ const inputTitle = `Secure value for ${keyName} (${pageIndex + 1}/${totalPages})`;
190
+ const inputPlaceholder = hint || "Enter secret value";
191
+ const inputResult = await ctx.ui.input(inputTitle, inputPlaceholder, { secure: true });
192
+ if (typeof inputResult !== "string") {
193
+ return null;
194
+ }
195
+ const trimmed = inputResult.trim();
196
+ return trimmed.length > 0 ? trimmed : null;
181
197
  }
182
198
  /**
183
199
  * Exported wrapper around collectOneSecret for testing.