gsd-pi 2.70.1 → 2.71.0-dev.4c35d99

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 (533) hide show
  1. package/README.md +57 -17
  2. package/dist/cli.js +29 -3
  3. package/dist/headless-events.d.ts +2 -0
  4. package/dist/headless-events.js +7 -0
  5. package/dist/headless.js +16 -3
  6. package/dist/mcp-server.js +40 -17
  7. package/dist/provider-migrations.d.ts +10 -0
  8. package/dist/provider-migrations.js +12 -0
  9. package/dist/resource-loader.js +139 -13
  10. package/dist/resources/GSD-WORKFLOW.md +1 -1
  11. package/dist/resources/agents/debugger.md +58 -0
  12. package/dist/resources/agents/doc-writer.md +43 -0
  13. package/dist/resources/agents/git-ops.md +56 -0
  14. package/dist/resources/agents/javascript-pro.md +46 -271
  15. package/dist/resources/agents/planner.md +55 -0
  16. package/dist/resources/agents/refactorer.md +47 -0
  17. package/dist/resources/agents/reviewer.md +48 -0
  18. package/dist/resources/agents/security.md +59 -0
  19. package/dist/resources/agents/tester.md +50 -0
  20. package/dist/resources/agents/typescript-pro.md +41 -235
  21. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +242 -40
  22. package/dist/resources/extensions/get-secrets-from-user.js +17 -1
  23. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  24. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  25. package/dist/resources/extensions/gsd/auto/phases.js +5 -1
  26. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  27. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  28. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  29. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  30. package/dist/resources/extensions/gsd/auto-start.js +37 -18
  31. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  32. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  33. package/dist/resources/extensions/gsd/auto.js +56 -0
  34. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  35. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +6 -0
  36. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +63 -51
  37. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
  38. package/dist/resources/extensions/gsd/commands/context.js +15 -6
  39. package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
  40. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  41. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  42. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  43. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  44. package/dist/resources/extensions/gsd/custom-workflow-engine.js +16 -12
  45. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  46. package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
  47. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  48. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  49. package/dist/resources/extensions/gsd/file-lock.js +60 -0
  50. package/dist/resources/extensions/gsd/forensics.js +19 -6
  51. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  52. package/dist/resources/extensions/gsd/gsd-db.js +41 -0
  53. package/dist/resources/extensions/gsd/guided-flow.js +17 -20
  54. package/dist/resources/extensions/gsd/init-wizard.js +3 -11
  55. package/dist/resources/extensions/gsd/metrics.js +1 -0
  56. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  57. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  58. package/dist/resources/extensions/gsd/notification-overlay.js +42 -13
  59. package/dist/resources/extensions/gsd/notification-store.js +56 -5
  60. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  61. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  62. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
  63. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  64. package/dist/resources/extensions/gsd/prompts/complete-slice.md +5 -3
  65. package/dist/resources/extensions/gsd/prompts/discuss.md +33 -13
  66. package/dist/resources/extensions/gsd/prompts/execute-task.md +22 -19
  67. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  68. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  69. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
  71. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  72. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -1
  73. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  74. package/dist/resources/extensions/gsd/shortcut-defs.js +40 -0
  75. package/dist/resources/extensions/gsd/state.js +241 -332
  76. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  77. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  78. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +38 -1
  79. package/dist/resources/extensions/gsd/workflow-events.js +25 -13
  80. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +56 -0
  81. package/dist/resources/extensions/gsd/workflow-mcp.js +1 -1
  82. package/dist/resources/extensions/ollama/index.js +13 -5
  83. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  84. package/dist/resources/extensions/subagent/agents.js +8 -0
  85. package/dist/resources/extensions/subagent/index.js +17 -0
  86. package/dist/resources/skills/create-skill/SKILL.md +2 -0
  87. package/dist/startup-model-validation.d.ts +0 -1
  88. package/dist/startup-model-validation.js +6 -2
  89. package/dist/web/standalone/.next/BUILD_ID +1 -1
  90. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  91. package/dist/web/standalone/.next/build-manifest.json +4 -4
  92. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  93. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  94. package/dist/web/standalone/.next/required-server-files.json +3 -3
  95. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  96. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  106. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  108. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  109. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  110. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  112. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  134. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  154. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  164. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  170. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  186. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  190. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  199. package/dist/web/standalone/.next/server/app/index.html +1 -1
  200. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  201. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  202. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  203. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  204. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  205. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  206. package/dist/web/standalone/.next/server/app/page.js +2 -2
  207. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  209. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  210. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  211. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  212. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  213. package/dist/web/standalone/.next/server/middleware.js +2 -2
  214. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  215. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  216. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  217. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  218. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  219. package/dist/web/standalone/.next/static/chunks/2826.dd3dc8bbd3025fa5.js +9 -0
  220. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  221. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  222. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  223. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  224. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  225. package/dist/web/standalone/.next/static/chunks/{webpack-6e4d7e9a4f57bed4.js → webpack-b868033a5834586d.js} +1 -1
  226. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  227. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  228. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  229. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  230. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  231. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  232. package/dist/web/standalone/server.js +1 -1
  233. package/package.json +1 -1
  234. package/packages/mcp-server/dist/env-writer.d.ts +39 -0
  235. package/packages/mcp-server/dist/env-writer.d.ts.map +1 -0
  236. package/packages/mcp-server/dist/env-writer.js +158 -0
  237. package/packages/mcp-server/dist/env-writer.js.map +1 -0
  238. package/packages/mcp-server/dist/server.d.ts +23 -3
  239. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  240. package/packages/mcp-server/dist/server.js +192 -44
  241. package/packages/mcp-server/dist/server.js.map +1 -1
  242. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  243. package/packages/mcp-server/dist/workflow-tools.js +22 -12
  244. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  245. package/packages/mcp-server/src/env-writer.test.ts +280 -0
  246. package/packages/mcp-server/src/env-writer.ts +183 -0
  247. package/packages/mcp-server/src/secure-env-collect.test.ts +265 -0
  248. package/packages/mcp-server/src/server.ts +247 -41
  249. package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
  250. package/packages/mcp-server/src/workflow-tools.ts +32 -12
  251. package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  252. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  253. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  254. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  255. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  256. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  257. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
  258. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  259. package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
  260. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  261. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
  262. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
  263. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  264. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  265. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  266. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  267. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  268. package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
  269. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  270. package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
  271. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  272. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
  273. package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
  274. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  275. package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
  276. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  277. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  278. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  279. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  280. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  281. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  282. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  284. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  286. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  287. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  288. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts +2 -0
  290. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts.map +1 -0
  291. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +388 -0
  292. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -0
  293. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  294. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  295. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  296. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  297. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  298. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  299. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  300. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  301. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  302. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  303. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  304. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  305. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  306. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  307. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  308. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  309. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  310. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  311. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  312. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  313. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  314. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  315. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  316. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  317. package/packages/pi-coding-agent/dist/index.js +1 -1
  318. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  319. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  320. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  321. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  322. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  323. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +19 -2
  324. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  325. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +50 -1
  326. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  327. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts +1 -0
  328. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  329. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js +1 -0
  330. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js.map +1 -1
  331. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  332. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  333. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  334. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  335. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  336. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  337. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  338. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  339. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  340. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  341. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  342. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  343. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +175 -25
  344. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  345. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  346. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  347. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  348. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  349. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  350. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  351. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +6 -0
  352. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  353. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +62 -5
  354. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  355. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  356. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  357. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
  358. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  359. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +1 -0
  360. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  361. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  362. package/packages/pi-coding-agent/package.json +1 -1
  363. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  364. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  365. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  366. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  367. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +468 -0
  368. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  369. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  370. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  371. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  372. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  373. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  374. package/packages/pi-coding-agent/src/index.ts +1 -0
  375. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  376. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +58 -2
  377. package/packages/pi-coding-agent/src/modes/interactive/components/extension-input.ts +2 -0
  378. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  379. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  380. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  381. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +205 -31
  382. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  383. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
  384. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +70 -5
  385. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  386. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +1 -1
  387. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +1 -0
  388. package/packages/pi-tui/dist/components/__tests__/input.test.js +9 -0
  389. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  390. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts +2 -0
  391. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts.map +1 -0
  392. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +66 -0
  393. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -0
  394. package/packages/pi-tui/dist/components/input.d.ts +2 -0
  395. package/packages/pi-tui/dist/components/input.d.ts.map +1 -1
  396. package/packages/pi-tui/dist/components/input.js +7 -4
  397. package/packages/pi-tui/dist/components/input.js.map +1 -1
  398. package/packages/pi-tui/dist/components/markdown.d.ts +3 -0
  399. package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
  400. package/packages/pi-tui/dist/components/markdown.js +17 -1
  401. package/packages/pi-tui/dist/components/markdown.js.map +1 -1
  402. package/packages/pi-tui/src/components/__tests__/input.test.ts +11 -0
  403. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +75 -0
  404. package/packages/pi-tui/src/components/input.ts +7 -4
  405. package/packages/pi-tui/src/components/markdown.ts +22 -1
  406. package/pkg/package.json +1 -1
  407. package/src/resources/GSD-WORKFLOW.md +1 -1
  408. package/src/resources/agents/debugger.md +58 -0
  409. package/src/resources/agents/doc-writer.md +43 -0
  410. package/src/resources/agents/git-ops.md +56 -0
  411. package/src/resources/agents/javascript-pro.md +46 -271
  412. package/src/resources/agents/planner.md +55 -0
  413. package/src/resources/agents/refactorer.md +47 -0
  414. package/src/resources/agents/reviewer.md +48 -0
  415. package/src/resources/agents/security.md +59 -0
  416. package/src/resources/agents/tester.md +50 -0
  417. package/src/resources/agents/typescript-pro.md +41 -235
  418. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +288 -39
  419. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +330 -2
  420. package/src/resources/extensions/get-secrets-from-user.ts +24 -1
  421. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  422. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  423. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  424. package/src/resources/extensions/gsd/auto/phases.ts +6 -0
  425. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  426. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  427. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  428. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  429. package/src/resources/extensions/gsd/auto-start.ts +44 -20
  430. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  431. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  432. package/src/resources/extensions/gsd/auto.ts +72 -0
  433. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  434. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
  435. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +79 -60
  436. package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
  437. package/src/resources/extensions/gsd/commands/context.ts +16 -5
  438. package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
  439. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  440. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  441. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  442. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  443. package/src/resources/extensions/gsd/custom-workflow-engine.ts +19 -14
  444. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  445. package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
  446. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  447. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  448. package/src/resources/extensions/gsd/file-lock.ts +59 -0
  449. package/src/resources/extensions/gsd/forensics.ts +23 -7
  450. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  451. package/src/resources/extensions/gsd/gsd-db.ts +51 -0
  452. package/src/resources/extensions/gsd/guided-flow.ts +17 -19
  453. package/src/resources/extensions/gsd/init-wizard.ts +3 -13
  454. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  455. package/src/resources/extensions/gsd/metrics.ts +12 -1
  456. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  457. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  458. package/src/resources/extensions/gsd/notification-overlay.ts +47 -14
  459. package/src/resources/extensions/gsd/notification-store.ts +54 -5
  460. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  461. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  462. package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
  463. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  464. package/src/resources/extensions/gsd/prompts/complete-slice.md +5 -3
  465. package/src/resources/extensions/gsd/prompts/discuss.md +33 -13
  466. package/src/resources/extensions/gsd/prompts/execute-task.md +22 -19
  467. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  468. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  469. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  470. package/src/resources/extensions/gsd/prompts/queue.md +3 -2
  471. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  472. package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -1
  473. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  474. package/src/resources/extensions/gsd/shortcut-defs.ts +56 -0
  475. package/src/resources/extensions/gsd/state.ts +285 -344
  476. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +25 -9
  477. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
  478. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
  479. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  480. package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
  481. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +436 -0
  482. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +9 -0
  483. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  484. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  485. package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
  486. package/src/resources/extensions/gsd/tests/file-lock.test.ts +103 -0
  487. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  488. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +31 -0
  489. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  490. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  491. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
  492. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  493. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  494. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  495. package/src/resources/extensions/gsd/tests/notification-store.test.ts +35 -0
  496. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +26 -0
  497. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  498. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  499. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  500. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
  501. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
  502. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  503. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  504. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +63 -5
  505. package/src/resources/extensions/gsd/tests/secure-env-collect.test.ts +45 -0
  506. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  507. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  508. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
  509. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
  510. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +76 -0
  511. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +155 -1
  512. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +22 -0
  513. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  514. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  515. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +64 -26
  516. package/src/resources/extensions/gsd/types.ts +26 -0
  517. package/src/resources/extensions/gsd/workflow-events.ts +34 -25
  518. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +76 -0
  519. package/src/resources/extensions/gsd/workflow-mcp.ts +1 -1
  520. package/src/resources/extensions/ollama/index.ts +13 -3
  521. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  522. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  523. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  524. package/src/resources/extensions/subagent/agents.ts +10 -0
  525. package/src/resources/extensions/subagent/index.ts +18 -0
  526. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  527. package/src/resources/skills/create-skill/SKILL.md +2 -0
  528. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +0 -9
  529. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  530. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  531. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  532. /package/dist/web/standalone/.next/static/{9pw9EXtXjdM7EFrCXUEPf → OI4n_CKC-lM8IQbvGJ_tK}/_buildManifest.js +0 -0
  533. /package/dist/web/standalone/.next/static/{9pw9EXtXjdM7EFrCXUEPf → OI4n_CKC-lM8IQbvGJ_tK}/_ssgManifest.js +0 -0
@@ -0,0 +1,265 @@
1
+ // @gsd-build/mcp-server — Tests for secure_env_collect MCP tool
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+ //
4
+ // Tests the secure_env_collect tool registered in createMcpServer.
5
+ // Uses a mock MCP server to intercept tool registration and elicitInput calls.
6
+
7
+ import { describe, it, beforeEach } from 'node:test';
8
+ import assert from 'node:assert/strict';
9
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
10
+ import { tmpdir } from 'node:os';
11
+ import { join } from 'node:path';
12
+
13
+ import { createMcpServer } from './server.js';
14
+ import { SessionManager } from './session-manager.js';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Mock infrastructure
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /**
21
+ * We intercept McpServer construction by monkey-patching the dynamic import.
22
+ * Instead, we'll test the tool handler indirectly through the exported
23
+ * createMcpServer function — capturing the registered tool handlers.
24
+ *
25
+ * Since createMcpServer dynamically imports McpServer, we need to test at
26
+ * a level that exercises the tool handler logic. We do this by extracting
27
+ * the tool handler through the server.tool() calls.
28
+ */
29
+
30
+ interface RegisteredTool {
31
+ name: string;
32
+ description: string;
33
+ params: Record<string, unknown>;
34
+ handler: (args: Record<string, unknown>) => Promise<unknown>;
35
+ }
36
+
37
+ interface ToolResult {
38
+ content?: Array<{ type: string; text: string }>;
39
+ isError?: boolean;
40
+ }
41
+
42
+ /**
43
+ * Mock McpServer that captures tool registrations and provides
44
+ * a controllable elicitInput response.
45
+ */
46
+ class MockMcpServer {
47
+ registeredTools: RegisteredTool[] = [];
48
+ elicitResponse: { action: string; content?: Record<string, unknown> } = { action: 'accept', content: {} };
49
+
50
+ server = {
51
+ elicitInput: async (_params: unknown) => {
52
+ return this.elicitResponse;
53
+ },
54
+ };
55
+
56
+ tool(name: string, description: string, params: Record<string, unknown>, handler: (args: Record<string, unknown>) => Promise<unknown>) {
57
+ this.registeredTools.push({ name, description, params, handler });
58
+ }
59
+
60
+ async connect(_transport: unknown) { /* no-op */ }
61
+ async close() { /* no-op */ }
62
+
63
+ getToolHandler(name: string): ((args: Record<string, unknown>) => Promise<unknown>) | undefined {
64
+ return this.registeredTools.find((t) => t.name === name)?.handler;
65
+ }
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Helper to create a mock MCP server with secure_env_collect registered
70
+ // ---------------------------------------------------------------------------
71
+
72
+ /**
73
+ * Since createMcpServer uses dynamic import for McpServer, we can't easily
74
+ * mock it. Instead, we test the env-writer utilities directly (in env-writer.test.ts)
75
+ * and test the tool integration by verifying:
76
+ * 1. The tool exists in the registered tools list
77
+ * 2. The handler produces correct results with mock data
78
+ *
79
+ * For handler-level testing, we create a standalone test that replicates
80
+ * the tool handler logic with a controllable mock.
81
+ */
82
+
83
+ function makeTempDir(prefix: string): string {
84
+ return mkdtempSync(join(tmpdir(), `${prefix}-`));
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Integration test — verify tool is registered
89
+ // ---------------------------------------------------------------------------
90
+
91
+ describe('secure_env_collect tool registration', () => {
92
+ it('createMcpServer registers secure_env_collect tool', async () => {
93
+ // This test verifies the tool exists — createMcpServer internally calls
94
+ // server.tool('secure_env_collect', ...) which we can't intercept without
95
+ // module mocking, but we can verify the server creates successfully
96
+ const sm = new SessionManager();
97
+ try {
98
+ const { server } = await createMcpServer(sm);
99
+ assert.ok(server, 'server should be created');
100
+ // The McpServer internally tracks registered tools — we verify no error
101
+ } finally {
102
+ await sm.cleanup();
103
+ }
104
+ });
105
+ });
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Handler logic tests — using env-writer directly to test the flow
109
+ // ---------------------------------------------------------------------------
110
+
111
+ describe('secure_env_collect handler logic', () => {
112
+ it('skips keys that already exist in .env', async () => {
113
+ const tmp = makeTempDir('sec-collect');
114
+ try {
115
+ const envPath = join(tmp, '.env');
116
+ writeFileSync(envPath, 'ALREADY_SET=existing-value\n');
117
+
118
+ // Import the utility directly to test the pre-check logic
119
+ const { checkExistingEnvKeys } = await import('./env-writer.js');
120
+ const existing = await checkExistingEnvKeys(['ALREADY_SET', 'NEW_KEY'], envPath);
121
+ assert.deepStrictEqual(existing, ['ALREADY_SET']);
122
+ } finally {
123
+ rmSync(tmp, { recursive: true, force: true });
124
+ }
125
+ });
126
+
127
+ it('writes collected values to .env without returning secret values', async () => {
128
+ const tmp = makeTempDir('sec-collect');
129
+ try {
130
+ const envPath = join(tmp, '.env');
131
+ const savedKey = process.env.SEC_COLLECT_TEST_KEY;
132
+
133
+ const { applySecrets } = await import('./env-writer.js');
134
+ const { applied, errors } = await applySecrets(
135
+ [{ key: 'SEC_COLLECT_TEST_KEY', value: 'super-secret-value' }],
136
+ 'dotenv',
137
+ { envFilePath: envPath },
138
+ );
139
+
140
+ assert.deepStrictEqual(applied, ['SEC_COLLECT_TEST_KEY']);
141
+ assert.deepStrictEqual(errors, []);
142
+
143
+ // Verify the value was written
144
+ const content = readFileSync(envPath, 'utf8');
145
+ assert.ok(content.includes('SEC_COLLECT_TEST_KEY=super-secret-value'));
146
+
147
+ // Verify process.env was hydrated
148
+ assert.equal(process.env.SEC_COLLECT_TEST_KEY, 'super-secret-value');
149
+
150
+ // Cleanup
151
+ if (savedKey === undefined) delete process.env.SEC_COLLECT_TEST_KEY;
152
+ else process.env.SEC_COLLECT_TEST_KEY = savedKey;
153
+ } finally {
154
+ rmSync(tmp, { recursive: true, force: true });
155
+ }
156
+ });
157
+
158
+ it('auto-detects vercel destination from vercel.json', async () => {
159
+ const tmp = makeTempDir('sec-collect');
160
+ try {
161
+ writeFileSync(join(tmp, 'vercel.json'), '{}');
162
+ const { detectDestination } = await import('./env-writer.js');
163
+ assert.equal(detectDestination(tmp), 'vercel');
164
+ } finally {
165
+ rmSync(tmp, { recursive: true, force: true });
166
+ }
167
+ });
168
+
169
+ it('handles empty form values as skipped', async () => {
170
+ // Simulate what happens when user leaves a field empty in the form
171
+ const formContent: Record<string, string> = {
172
+ 'API_KEY': 'provided-value',
173
+ 'OPTIONAL_KEY': '', // empty = skip
174
+ };
175
+
176
+ const provided: Array<{ key: string; value: string }> = [];
177
+ const skipped: string[] = [];
178
+
179
+ for (const [key, raw] of Object.entries(formContent)) {
180
+ const value = typeof raw === 'string' ? raw.trim() : '';
181
+ if (value.length > 0) {
182
+ provided.push({ key, value });
183
+ } else {
184
+ skipped.push(key);
185
+ }
186
+ }
187
+
188
+ assert.deepStrictEqual(provided, [{ key: 'API_KEY', value: 'provided-value' }]);
189
+ assert.deepStrictEqual(skipped, ['OPTIONAL_KEY']);
190
+ });
191
+
192
+ it('result text never contains secret values', async () => {
193
+ const tmp = makeTempDir('sec-collect');
194
+ try {
195
+ const envPath = join(tmp, '.env');
196
+ const savedKey = process.env.RESULT_TEXT_TEST;
197
+
198
+ const { applySecrets } = await import('./env-writer.js');
199
+ const { applied } = await applySecrets(
200
+ [{ key: 'RESULT_TEXT_TEST', value: 'sk-super-secret-abc123' }],
201
+ 'dotenv',
202
+ { envFilePath: envPath },
203
+ );
204
+
205
+ // Simulate building result text (same logic as the tool handler)
206
+ const lines: string[] = [
207
+ 'destination: dotenv (auto-detected)',
208
+ ...applied.map((k) => `✓ ${k}: applied`),
209
+ ];
210
+ const resultText = lines.join('\n');
211
+
212
+ // The result MUST NOT contain the secret value
213
+ assert.ok(!resultText.includes('sk-super-secret-abc123'), 'result text must not contain secret value');
214
+ assert.ok(resultText.includes('RESULT_TEXT_TEST'), 'result text should contain key name');
215
+
216
+ // Cleanup
217
+ if (savedKey === undefined) delete process.env.RESULT_TEXT_TEST;
218
+ else process.env.RESULT_TEXT_TEST = savedKey;
219
+ } finally {
220
+ rmSync(tmp, { recursive: true, force: true });
221
+ }
222
+ });
223
+
224
+ it('handles multiple keys with mixed existing/new/skipped', async () => {
225
+ const tmp = makeTempDir('sec-collect');
226
+ try {
227
+ const envPath = join(tmp, '.env');
228
+ writeFileSync(envPath, 'EXISTING_A=already-here\n');
229
+ const savedB = process.env.NEW_B;
230
+ const savedC = process.env.SKIP_C;
231
+
232
+ const { checkExistingEnvKeys, applySecrets } = await import('./env-writer.js');
233
+
234
+ const allKeys = ['EXISTING_A', 'NEW_B', 'SKIP_C'];
235
+ const existing = await checkExistingEnvKeys(allKeys, envPath);
236
+ assert.deepStrictEqual(existing, ['EXISTING_A']);
237
+
238
+ // Simulate form response: NEW_B has value, SKIP_C is empty
239
+ const formContent = { NEW_B: 'new-value', SKIP_C: '' };
240
+ const provided: Array<{ key: string; value: string }> = [];
241
+ const skipped: string[] = [];
242
+
243
+ for (const key of allKeys.filter((k) => !existing.includes(k))) {
244
+ const raw = formContent[key as keyof typeof formContent] ?? '';
245
+ if (raw.trim().length > 0) provided.push({ key, value: raw.trim() });
246
+ else skipped.push(key);
247
+ }
248
+
249
+ const { applied, errors } = await applySecrets(provided, 'dotenv', { envFilePath: envPath });
250
+
251
+ assert.deepStrictEqual(applied, ['NEW_B']);
252
+ assert.deepStrictEqual(skipped, ['SKIP_C']);
253
+ assert.deepStrictEqual(errors, []);
254
+ assert.deepStrictEqual(existing, ['EXISTING_A']);
255
+
256
+ // Cleanup
257
+ if (savedB === undefined) delete process.env.NEW_B;
258
+ else process.env.NEW_B = savedB;
259
+ if (savedC === undefined) delete process.env.SKIP_C;
260
+ else process.env.SKIP_C = savedC;
261
+ } finally {
262
+ rmSync(tmp, { recursive: true, force: true });
263
+ }
264
+ });
265
+ });
@@ -2,7 +2,7 @@
2
2
  * MCP Server — registers GSD orchestration, project-state, and workflow tools.
3
3
  *
4
4
  * Session tools (6): gsd_execute, gsd_status, gsd_result, gsd_cancel, gsd_query, gsd_resolve_blocker
5
- * Interactive tools (1): ask_user_questions via MCP form elicitation
5
+ * Interactive tools (2): ask_user_questions, secure_env_collect via MCP form elicitation
6
6
  * Read-only tools (6): gsd_progress, gsd_roadmap, gsd_history, gsd_doctor, gsd_captures, gsd_knowledge
7
7
  * Workflow tools (29): headless-safe planning, metadata persistence, replanning, completion, validation, reassessment, gate result, status, and journal tools
8
8
  *
@@ -22,6 +22,7 @@ import { readCaptures } from './readers/captures.js';
22
22
  import { readKnowledge } from './readers/knowledge.js';
23
23
  import { runDoctorLite } from './readers/doctor-lite.js';
24
24
  import { registerWorkflowTools } from './workflow-tools.js';
25
+ import { applySecrets, checkExistingEnvKeys, detectDestination } from './env-writer.js';
25
26
 
26
27
  // ---------------------------------------------------------------------------
27
28
  // Constants
@@ -54,46 +55,80 @@ function textContent(text: string): { content: Array<{ type: 'text'; text: strin
54
55
  // gsd_query filesystem reader
55
56
  // ---------------------------------------------------------------------------
56
57
 
57
- async function readProjectState(projectDir: string, _query: string): Promise<Record<string, unknown>> {
58
+ /**
59
+ * Normalized query categories for {@link readProjectState}.
60
+ *
61
+ * Maps user-supplied query strings (or empty) to the set of fields we return.
62
+ * Accepts common synonyms so the MCP client can pass intuitive values.
63
+ */
64
+ const QUERY_FIELDS = {
65
+ all: ['state', 'project', 'requirements', 'milestones'] as const,
66
+ state: ['state'] as const,
67
+ status: ['state'] as const,
68
+ project: ['project'] as const,
69
+ requirements: ['requirements'] as const,
70
+ milestones: ['milestones'] as const,
71
+ } as const;
72
+
73
+ type QueryCategory = keyof typeof QUERY_FIELDS;
74
+ type ProjectStateField = (typeof QUERY_FIELDS)[QueryCategory][number];
75
+
76
+ function normalizeQuery(query: string | undefined): QueryCategory {
77
+ const key = (query ?? 'all').trim().toLowerCase();
78
+ if (key in QUERY_FIELDS) return key as QueryCategory;
79
+ return 'all';
80
+ }
81
+
82
+ async function readProjectState(projectDir: string, query: string | undefined): Promise<Record<string, unknown>> {
58
83
  const gsdDir = join(resolve(projectDir), '.gsd');
59
- const result: Record<string, unknown> = { projectDir: resolve(projectDir) };
84
+ const category = normalizeQuery(query);
85
+ const wanted = new Set<ProjectStateField>(QUERY_FIELDS[category]);
60
86
 
61
- // STATE.md current execution state
62
- try {
63
- result.state = await readFile(join(gsdDir, 'STATE.md'), 'utf-8');
64
- } catch {
65
- result.state = null;
87
+ const result: Record<string, unknown> = {
88
+ projectDir: resolve(projectDir),
89
+ query: category,
90
+ };
91
+
92
+ if (wanted.has('state')) {
93
+ try {
94
+ result.state = await readFile(join(gsdDir, 'STATE.md'), 'utf-8');
95
+ } catch {
96
+ result.state = null;
97
+ }
66
98
  }
67
99
 
68
- // PROJECT.md — project description
69
- try {
70
- result.project = await readFile(join(gsdDir, 'PROJECT.md'), 'utf-8');
71
- } catch {
72
- result.project = null;
100
+ if (wanted.has('project')) {
101
+ try {
102
+ result.project = await readFile(join(gsdDir, 'PROJECT.md'), 'utf-8');
103
+ } catch {
104
+ result.project = null;
105
+ }
73
106
  }
74
107
 
75
- // REQUIREMENTS.md — requirement contract
76
- try {
77
- result.requirements = await readFile(join(gsdDir, 'REQUIREMENTS.md'), 'utf-8');
78
- } catch {
79
- result.requirements = null;
108
+ if (wanted.has('requirements')) {
109
+ try {
110
+ result.requirements = await readFile(join(gsdDir, 'REQUIREMENTS.md'), 'utf-8');
111
+ } catch {
112
+ result.requirements = null;
113
+ }
80
114
  }
81
115
 
82
- // List milestones with basic metadata
83
- const milestonesDir = join(gsdDir, 'milestones');
84
- try {
85
- const entries = await readdir(milestonesDir, { withFileTypes: true });
86
- const milestones: Array<{ id: string; hasRoadmap: boolean; hasSummary: boolean }> = [];
87
- for (const entry of entries) {
88
- if (!entry.isDirectory()) continue;
89
- const mDir = join(milestonesDir, entry.name);
90
- const hasRoadmap = await fileExists(join(mDir, `${entry.name}-ROADMAP.md`));
91
- const hasSummary = await fileExists(join(mDir, `${entry.name}-SUMMARY.md`));
92
- milestones.push({ id: entry.name, hasRoadmap, hasSummary });
116
+ if (wanted.has('milestones')) {
117
+ const milestonesDir = join(gsdDir, 'milestones');
118
+ try {
119
+ const entries = await readdir(milestonesDir, { withFileTypes: true });
120
+ const milestones: Array<{ id: string; hasRoadmap: boolean; hasSummary: boolean }> = [];
121
+ for (const entry of entries) {
122
+ if (!entry.isDirectory()) continue;
123
+ const mDir = join(milestonesDir, entry.name);
124
+ const hasRoadmap = await fileExists(join(mDir, `${entry.name}-ROADMAP.md`));
125
+ const hasSummary = await fileExists(join(mDir, `${entry.name}-SUMMARY.md`));
126
+ milestones.push({ id: entry.name, hasRoadmap, hasSummary });
127
+ }
128
+ result.milestones = milestones;
129
+ } catch {
130
+ result.milestones = [];
93
131
  }
94
- result.milestones = milestones;
95
- } catch {
96
- result.milestones = [];
97
132
  }
98
133
 
99
134
  return result;
@@ -112,11 +147,43 @@ async function fileExists(path: string): Promise<boolean> {
112
147
  // MCP Server type — minimal interface for the dynamically-imported McpServer
113
148
  // ---------------------------------------------------------------------------
114
149
 
150
+ interface ElicitResult {
151
+ action: 'accept' | 'decline' | 'cancel';
152
+ content?: Record<string, string | number | boolean | string[]>;
153
+ }
154
+
155
+ interface ElicitRequestFormParams {
156
+ mode?: 'form';
157
+ message: string;
158
+ requestedSchema: {
159
+ type: 'object';
160
+ properties: Record<string, Record<string, unknown>>;
161
+ required?: string[];
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Handler extra — the second argument passed by McpServer.tool handlers.
167
+ * Contains an AbortSignal scoped to the JSON-RPC request (cancelled when
168
+ * the client cancels the `tools/call`) plus other per-request metadata.
169
+ * Tools that can actually be stopped mid-flight should honour `signal`.
170
+ */
171
+ export interface McpToolExtra {
172
+ signal?: AbortSignal;
173
+ requestId?: string | number;
174
+ sendNotification?: (notification: unknown) => void | Promise<void>;
175
+ }
176
+
115
177
  interface McpServerInstance {
116
- tool(name: string, description: string, params: Record<string, unknown>, handler: (args: Record<string, unknown>) => Promise<unknown>): unknown;
178
+ tool(
179
+ name: string,
180
+ description: string,
181
+ params: Record<string, unknown>,
182
+ handler: (args: Record<string, unknown>, extra?: McpToolExtra) => Promise<unknown>,
183
+ ): unknown;
117
184
  server: {
118
185
  elicitInput(
119
- params: AskUserQuestionsElicitRequest,
186
+ params: AskUserQuestionsElicitRequest | ElicitRequestFormParams,
120
187
  options?: unknown,
121
188
  ): Promise<AskUserQuestionsElicitResult>;
122
189
  };
@@ -282,11 +349,16 @@ export async function createMcpServer(sessionManager: SessionManager): Promise<{
282
349
 
283
350
  const server: McpServerInstance = new McpServer(
284
351
  { name: SERVER_NAME, version: SERVER_VERSION },
285
- { capabilities: { tools: {} } },
352
+ { capabilities: { tools: {}, elicitation: {} } },
286
353
  );
287
354
 
288
355
  // -----------------------------------------------------------------------
289
- // gsd_execute — start a new GSD auto-mode session
356
+ // gsd_execute — start a new GSD auto-mode session.
357
+ //
358
+ // If the JSON-RPC request is aborted while the session is starting (or
359
+ // immediately after), we cancel the session so we don't leak a background
360
+ // RpcClient process. Once the session is running the caller should use
361
+ // `gsd_cancel` to stop it via sessionId.
290
362
  // -----------------------------------------------------------------------
291
363
  server.tool(
292
364
  'gsd_execute',
@@ -297,12 +369,20 @@ export async function createMcpServer(sessionManager: SessionManager): Promise<{
297
369
  model: z.string().optional().describe('Model ID override'),
298
370
  bare: z.boolean().optional().describe('Run in bare mode (skip user config)'),
299
371
  },
300
- async (args: Record<string, unknown>) => {
372
+ async (args: Record<string, unknown>, extra?: McpToolExtra) => {
301
373
  const { projectDir, command, model, bare } = args as {
302
374
  projectDir: string; command?: string; model?: string; bare?: boolean;
303
375
  };
304
376
  try {
305
377
  const sessionId = await sessionManager.startSession(projectDir, { command, model, bare });
378
+
379
+ // If the client aborted while startSession was running, cancel the
380
+ // newly-created session rather than leaving an orphaned process.
381
+ if (extra?.signal?.aborted) {
382
+ await sessionManager.cancelSession(sessionId).catch(() => { /* swallow */ });
383
+ return errorContent('gsd_execute aborted by client before returning');
384
+ }
385
+
306
386
  return jsonContent({ sessionId, status: 'started' });
307
387
  } catch (err) {
308
388
  return errorContent(err instanceof Error ? err.message : String(err));
@@ -395,17 +475,25 @@ export async function createMcpServer(sessionManager: SessionManager): Promise<{
395
475
  );
396
476
 
397
477
  // -----------------------------------------------------------------------
398
- // gsd_query — read project state from filesystem (no session needed)
478
+ // gsd_query — read project state from filesystem (no session needed).
479
+ //
480
+ // `query` is optional: when omitted the tool returns all fields (STATE.md,
481
+ // PROJECT.md, requirements, milestone listing). Accepted narrow values:
482
+ // "state" / "status", "project", "requirements", "milestones", "all".
483
+ // Unknown values fall back to "all" for forward-compatibility.
399
484
  // -----------------------------------------------------------------------
400
485
  server.tool(
401
486
  'gsd_query',
402
- 'Query GSD project state from the filesystem. Returns STATE.md, PROJECT.md, requirements, and milestone listing. Does not require an active session.',
487
+ 'Query GSD project state from the filesystem. By default returns STATE.md, PROJECT.md, requirements, and milestone listing. Pass `query` to narrow the response (accepted: "state"/"status", "project", "requirements", "milestones", "all"). Does not require an active session.',
403
488
  {
404
489
  projectDir: z.string().describe('Absolute path to the project directory'),
405
- query: z.string().describe('What to query (e.g. "status", "milestones", "requirements")'),
490
+ query: z
491
+ .enum(['all', 'state', 'status', 'project', 'requirements', 'milestones'])
492
+ .optional()
493
+ .describe('Narrow the response to a single field (default: "all")'),
406
494
  },
407
495
  async (args: Record<string, unknown>) => {
408
- const { projectDir, query } = args as { projectDir: string; query: string };
496
+ const { projectDir, query } = args as { projectDir: string; query?: string };
409
497
  try {
410
498
  const state = await readProjectState(projectDir, query);
411
499
  return jsonContent(state);
@@ -472,6 +560,124 @@ export async function createMcpServer(sessionManager: SessionManager): Promise<{
472
560
  },
473
561
  );
474
562
 
563
+ // -----------------------------------------------------------------------
564
+ // secure_env_collect — collect secrets via MCP form elicitation
565
+ // -----------------------------------------------------------------------
566
+ server.tool(
567
+ 'secure_env_collect',
568
+ 'Collect environment variables securely via form input. Values are written directly to .env (or Vercel/Convex) and NEVER appear in tool output — only key names and applied/skipped status are returned. Use this instead of asking users to manually edit .env files or paste secrets into chat.',
569
+ {
570
+ projectDir: z.string().describe('Absolute path to the project directory'),
571
+ keys: z.array(z.object({
572
+ key: z.string().describe('Env var name, e.g. OPENAI_API_KEY'),
573
+ hint: z.string().optional().describe('Format hint shown to user, e.g. "starts with sk-"'),
574
+ guidance: z.array(z.string()).optional().describe('Step-by-step instructions for obtaining this key'),
575
+ })).min(1).describe('Environment variables to collect'),
576
+ destination: z.enum(['dotenv', 'vercel', 'convex']).optional().describe('Where to write secrets. Auto-detected from project files if omitted.'),
577
+ envFilePath: z.string().optional().describe('Path to .env file (dotenv only). Defaults to .env in projectDir.'),
578
+ environment: z.enum(['development', 'preview', 'production']).optional().describe('Target environment (vercel/convex only)'),
579
+ },
580
+ async (args: Record<string, unknown>) => {
581
+ const { projectDir, keys, destination, envFilePath, environment } = args as {
582
+ projectDir: string;
583
+ keys: Array<{ key: string; hint?: string; guidance?: string[] }>;
584
+ destination?: 'dotenv' | 'vercel' | 'convex';
585
+ envFilePath?: string;
586
+ environment?: 'development' | 'preview' | 'production';
587
+ };
588
+
589
+ try {
590
+ const resolvedProjectDir = resolve(projectDir);
591
+ const resolvedEnvPath = resolve(resolvedProjectDir, envFilePath ?? '.env');
592
+
593
+ // (1) Check which keys already exist
594
+ const allKeyNames = keys.map((k) => k.key);
595
+ const existingKeys = await checkExistingEnvKeys(allKeyNames, resolvedEnvPath);
596
+ const existingSet = new Set(existingKeys);
597
+ const pendingKeys = keys.filter((k) => !existingSet.has(k.key));
598
+
599
+ // If all keys already exist, return immediately
600
+ if (pendingKeys.length === 0) {
601
+ const lines = existingKeys.map((k) => `• ${k}: already set`);
602
+ return textContent(`All ${existingKeys.length} key(s) already set.\n${lines.join('\n')}`);
603
+ }
604
+
605
+ // (2) Build elicitation form — one string field per pending key
606
+ const properties: Record<string, Record<string, unknown>> = {};
607
+ const required: string[] = [];
608
+
609
+ for (const item of pendingKeys) {
610
+ const descParts: string[] = [];
611
+ if (item.hint) descParts.push(`Format: ${item.hint}`);
612
+ if (item.guidance && item.guidance.length > 0) {
613
+ descParts.push('How to get this:');
614
+ item.guidance.forEach((step, i) => descParts.push(`${i + 1}. ${step}`));
615
+ }
616
+ descParts.push('Leave empty to skip.');
617
+
618
+ properties[item.key] = {
619
+ type: 'string',
620
+ title: item.key,
621
+ description: descParts.join('\n'),
622
+ };
623
+ // Don't mark as required — empty string = skip
624
+ }
625
+
626
+ // (3) Elicit input from the MCP client
627
+ const elicitation = await server.server.elicitInput({
628
+ message: `Enter values for ${pendingKeys.length} environment variable(s). Values are written directly to the project and never shown to the AI.`,
629
+ requestedSchema: {
630
+ type: 'object',
631
+ properties,
632
+ required,
633
+ },
634
+ });
635
+
636
+ if (elicitation.action !== 'accept' || !elicitation.content) {
637
+ return textContent('secure_env_collect was cancelled by user.');
638
+ }
639
+
640
+ // (4) Separate provided vs skipped from form response
641
+ const provided: Array<{ key: string; value: string }> = [];
642
+ const skipped: string[] = [];
643
+
644
+ for (const item of pendingKeys) {
645
+ const raw = elicitation.content[item.key];
646
+ const value = typeof raw === 'string' ? raw.trim() : '';
647
+ if (value.length > 0) {
648
+ provided.push({ key: item.key, value });
649
+ } else {
650
+ skipped.push(item.key);
651
+ }
652
+ }
653
+
654
+ // (5) Auto-detect destination if not specified
655
+ const resolvedDestination = destination ?? detectDestination(resolvedProjectDir);
656
+
657
+ // (6) Write secrets to destination
658
+ const { applied, errors } = await applySecrets(provided, resolvedDestination, {
659
+ envFilePath: resolvedEnvPath,
660
+ environment,
661
+ });
662
+
663
+ // (7) Build result — NEVER include secret values
664
+ const lines: string[] = [
665
+ `destination: ${resolvedDestination}${!destination ? ' (auto-detected)' : ''}${environment ? ` (${environment})` : ''}`,
666
+ ];
667
+ for (const k of applied) lines.push(`✓ ${k}: applied`);
668
+ for (const k of skipped) lines.push(`• ${k}: skipped`);
669
+ for (const k of existingKeys) lines.push(`• ${k}: already set`);
670
+ for (const e of errors) lines.push(`✗ ${e}`);
671
+
672
+ return errors.length > 0 && applied.length === 0
673
+ ? errorContent(lines.join('\n'))
674
+ : textContent(lines.join('\n'));
675
+ } catch (err) {
676
+ return errorContent(err instanceof Error ? err.message : String(err));
677
+ }
678
+ },
679
+ );
680
+
475
681
  // =======================================================================
476
682
  // READ-ONLY TOOLS — no session required, pure filesystem reads
477
683
  // =======================================================================