gsd-pi 2.51.0 → 2.52.0-dev.655ad8a

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 (419) hide show
  1. package/README.md +59 -36
  2. package/dist/headless-events.d.ts +18 -0
  3. package/dist/headless-events.js +36 -0
  4. package/dist/headless-query.js +1 -1
  5. package/dist/headless-types.d.ts +28 -0
  6. package/dist/headless-types.js +7 -0
  7. package/dist/headless.d.ts +8 -3
  8. package/dist/headless.js +47 -16
  9. package/dist/help-text.js +16 -5
  10. package/dist/onboarding.js +5 -4
  11. package/dist/remote-questions-config.js +1 -1
  12. package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
  13. package/dist/resources/extensions/async-jobs/job-manager.js +4 -1
  14. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
  15. package/dist/resources/extensions/get-secrets-from-user.js +7 -0
  16. package/dist/resources/extensions/gsd/auto/phases.js +34 -8
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +23 -1
  18. package/dist/resources/extensions/gsd/auto-start.js +2 -0
  19. package/dist/resources/extensions/gsd/auto-timers.js +24 -2
  20. package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
  21. package/dist/resources/extensions/gsd/auto-worktree.js +91 -14
  22. package/dist/resources/extensions/gsd/auto.js +30 -4
  23. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +99 -70
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  26. package/dist/resources/extensions/gsd/claude-import.js +60 -9
  27. package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
  28. package/dist/resources/extensions/gsd/commands-config.js +10 -5
  29. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +4 -4
  30. package/dist/resources/extensions/gsd/detection.js +6 -6
  31. package/dist/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  32. package/dist/resources/extensions/gsd/error-classifier.js +105 -0
  33. package/dist/resources/extensions/gsd/git-service.js +4 -3
  34. package/dist/resources/extensions/gsd/gitignore.js +7 -7
  35. package/dist/resources/extensions/gsd/gsd-db.js +298 -45
  36. package/dist/resources/extensions/gsd/guided-flow.js +4 -3
  37. package/dist/resources/extensions/gsd/init-wizard.js +2 -2
  38. package/dist/resources/extensions/gsd/key-manager.js +7 -16
  39. package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
  40. package/dist/resources/extensions/gsd/memory-store.js +28 -13
  41. package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
  42. package/dist/resources/extensions/gsd/parallel-orchestrator.js +18 -2
  43. package/dist/resources/extensions/gsd/preferences-models.js +1 -13
  44. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  45. package/dist/resources/extensions/gsd/preferences.js +13 -13
  46. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  47. package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
  48. package/dist/resources/extensions/gsd/rule-registry.js +1 -1
  49. package/dist/resources/extensions/gsd/service-tier.js +13 -2
  50. package/dist/resources/extensions/gsd/state.js +38 -30
  51. package/dist/resources/extensions/gsd/status-guards.js +12 -0
  52. package/dist/resources/extensions/gsd/tools/complete-milestone.js +7 -13
  53. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -20
  54. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -21
  55. package/dist/resources/extensions/gsd/tools/plan-milestone.js +28 -29
  56. package/dist/resources/extensions/gsd/tools/plan-slice.js +27 -26
  57. package/dist/resources/extensions/gsd/tools/plan-task.js +23 -23
  58. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +50 -41
  59. package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
  60. package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
  61. package/dist/resources/extensions/gsd/tools/replan-slice.js +51 -41
  62. package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
  63. package/dist/resources/extensions/gsd/validation.js +21 -0
  64. package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
  65. package/dist/resources/extensions/remote-questions/config.js +1 -1
  66. package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
  67. package/dist/resources/extensions/search-the-web/native-search.js +1 -1
  68. package/dist/resources/extensions/search-the-web/provider.js +1 -1
  69. package/dist/resources/extensions/shared/rtk.js +14 -4
  70. package/dist/rtk.js +3 -1
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  73. package/dist/web/standalone/.next/build-manifest.json +4 -4
  74. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  75. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  76. package/dist/web/standalone/.next/required-server-files.json +3 -3
  77. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  78. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  80. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  88. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  97. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  116. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  150. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  164. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  166. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  168. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  170. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/index.html +1 -1
  180. package/dist/web/standalone/.next/server/app/index.rsc +5 -5
  181. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  182. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
  183. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  184. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
  185. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  186. package/dist/web/standalone/.next/server/app/page.js +2 -2
  187. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  189. package/dist/web/standalone/.next/server/chunks/2229.js +3 -3
  190. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  191. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/middleware.js +2 -2
  194. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  196. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  197. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  198. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  199. package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +9 -0
  200. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  201. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  202. package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +1 -0
  203. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  204. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  205. package/dist/web/standalone/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-bca0e732db0dcec3.js} +1 -1
  206. package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
  207. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  208. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  209. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  210. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  211. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  212. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  213. package/dist/web/standalone/server.js +1 -1
  214. package/dist/wizard.js +4 -1
  215. package/package.json +2 -2
  216. package/packages/mcp-server/README.md +202 -0
  217. package/packages/mcp-server/package.json +36 -0
  218. package/packages/mcp-server/src/cli.ts +68 -0
  219. package/packages/mcp-server/src/index.ts +14 -0
  220. package/packages/mcp-server/src/mcp-server.test.ts +628 -0
  221. package/packages/mcp-server/src/server.ts +278 -0
  222. package/packages/mcp-server/src/session-manager.ts +328 -0
  223. package/packages/mcp-server/src/types.ts +107 -0
  224. package/packages/mcp-server/tsconfig.json +24 -0
  225. package/packages/pi-ai/dist/models.d.ts +14 -3
  226. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  227. package/packages/pi-ai/dist/models.js +53 -10
  228. package/packages/pi-ai/dist/models.js.map +1 -1
  229. package/packages/pi-ai/dist/models.test.js +102 -1
  230. package/packages/pi-ai/dist/models.test.js.map +1 -1
  231. package/packages/pi-ai/dist/types.d.ts +30 -0
  232. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  233. package/packages/pi-ai/dist/types.js.map +1 -1
  234. package/packages/pi-ai/src/models.test.ts +114 -1
  235. package/packages/pi-ai/src/models.ts +70 -13
  236. package/packages/pi-ai/src/types.ts +31 -0
  237. package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
  238. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  239. package/packages/pi-coding-agent/dist/cli/args.js +3 -0
  240. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  241. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
  242. package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
  243. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  244. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  245. package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
  246. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  247. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
  248. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
  249. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
  250. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
  251. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  252. package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
  253. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  254. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  255. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  256. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  257. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/main.js +5 -3
  259. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  260. package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
  261. package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
  262. package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
  263. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  264. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
  265. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  266. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
  267. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  268. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
  269. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  270. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
  271. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  272. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
  273. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  274. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
  275. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
  276. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
  277. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
  278. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
  279. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  280. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  281. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  282. package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
  283. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  284. package/packages/pi-coding-agent/package.json +1 -1
  285. package/packages/pi-coding-agent/src/cli/args.ts +4 -0
  286. package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
  287. package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
  288. package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
  289. package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
  290. package/packages/pi-coding-agent/src/index.ts +3 -0
  291. package/packages/pi-coding-agent/src/main.ts +5 -3
  292. package/packages/pi-coding-agent/src/modes/index.ts +8 -1
  293. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
  294. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
  295. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
  296. package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
  297. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
  298. package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
  299. package/packages/rpc-client/package.json +20 -0
  300. package/pkg/package.json +1 -1
  301. package/scripts/ensure-workspace-builds.cjs +36 -8
  302. package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
  303. package/src/resources/extensions/async-jobs/job-manager.ts +4 -1
  304. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
  305. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
  306. package/src/resources/extensions/get-secrets-from-user.ts +8 -0
  307. package/src/resources/extensions/gsd/auto/phases.ts +44 -7
  308. package/src/resources/extensions/gsd/auto-dispatch.ts +25 -1
  309. package/src/resources/extensions/gsd/auto-start.ts +2 -0
  310. package/src/resources/extensions/gsd/auto-timers.ts +25 -1
  311. package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
  312. package/src/resources/extensions/gsd/auto-worktree.ts +94 -14
  313. package/src/resources/extensions/gsd/auto.ts +31 -4
  314. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +118 -73
  315. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
  316. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  317. package/src/resources/extensions/gsd/claude-import.ts +58 -9
  318. package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
  319. package/src/resources/extensions/gsd/commands-config.ts +11 -5
  320. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -4
  321. package/src/resources/extensions/gsd/detection.ts +6 -6
  322. package/src/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  323. package/src/resources/extensions/gsd/error-classifier.ts +139 -0
  324. package/src/resources/extensions/gsd/git-service.ts +4 -3
  325. package/src/resources/extensions/gsd/gitignore.ts +7 -7
  326. package/src/resources/extensions/gsd/gsd-db.ts +355 -63
  327. package/src/resources/extensions/gsd/guided-flow.ts +4 -3
  328. package/src/resources/extensions/gsd/init-wizard.ts +2 -2
  329. package/src/resources/extensions/gsd/key-manager.ts +7 -16
  330. package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
  331. package/src/resources/extensions/gsd/memory-store.ts +29 -18
  332. package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
  333. package/src/resources/extensions/gsd/parallel-orchestrator.ts +23 -1
  334. package/src/resources/extensions/gsd/preferences-models.ts +1 -13
  335. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  336. package/src/resources/extensions/gsd/preferences.ts +12 -13
  337. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  338. package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
  339. package/src/resources/extensions/gsd/rule-registry.ts +1 -1
  340. package/src/resources/extensions/gsd/service-tier.ts +14 -2
  341. package/src/resources/extensions/gsd/state.ts +39 -30
  342. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  343. package/src/resources/extensions/gsd/tests/active-milestone-id-guard.test.ts +91 -0
  344. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +1 -1
  345. package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
  346. package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +87 -0
  347. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +80 -0
  348. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +1 -1
  349. package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
  350. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
  351. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +39 -0
  352. package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
  353. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  354. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
  355. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  356. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
  357. package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
  358. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
  359. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
  360. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
  361. package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
  362. package/src/resources/extensions/gsd/tests/git-service.test.ts +65 -31
  363. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  364. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
  365. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
  366. package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
  367. package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
  368. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  369. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  370. package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +51 -0
  371. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
  372. package/src/resources/extensions/gsd/tests/parallel-orchestrator-zombie-cleanup.test.ts +277 -0
  373. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
  374. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +103 -0
  375. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
  376. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -2
  377. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
  378. package/src/resources/extensions/gsd/tests/rate-limit-model-fallback.test.ts +90 -0
  379. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
  380. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
  381. package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +9 -8
  382. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +125 -0
  383. package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
  384. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
  385. package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
  386. package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
  387. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
  388. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -2
  389. package/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +124 -0
  390. package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
  391. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
  392. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
  393. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -17
  394. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -24
  395. package/src/resources/extensions/gsd/tools/complete-task.ts +13 -25
  396. package/src/resources/extensions/gsd/tools/plan-milestone.ts +30 -32
  397. package/src/resources/extensions/gsd/tools/plan-slice.ts +30 -30
  398. package/src/resources/extensions/gsd/tools/plan-task.ts +26 -26
  399. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +57 -46
  400. package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
  401. package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
  402. package/src/resources/extensions/gsd/tools/replan-slice.ts +55 -44
  403. package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
  404. package/src/resources/extensions/gsd/validation.ts +23 -0
  405. package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
  406. package/src/resources/extensions/remote-questions/config.ts +1 -1
  407. package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
  408. package/src/resources/extensions/search-the-web/native-search.ts +1 -1
  409. package/src/resources/extensions/search-the-web/provider.ts +1 -1
  410. package/src/resources/extensions/shared/rtk.ts +22 -4
  411. package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
  412. package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
  413. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  414. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  415. package/dist/web/standalone/.next/static/css/de141508b083f922.css +0 -1
  416. /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
  417. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_buildManifest.js +0 -0
  418. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_ssgManifest.js +0 -0
  419. /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
@@ -36,6 +36,7 @@ import {
36
36
 
37
37
  import { findMilestoneIds } from './milestone-ids.js';
38
38
  import { loadQueueOrder, sortByQueueOrder } from './queue-order.js';
39
+ import { isClosedStatus } from './status-guards.js';
39
40
  import { nativeBatchParseGsdFiles, type BatchParsedFile } from './native-parser-bridge.js';
40
41
 
41
42
  import { join, resolve } from 'path';
@@ -89,18 +90,13 @@ export function isMilestoneComplete(roadmap: Roadmap): boolean {
89
90
  }
90
91
 
91
92
  /**
92
- * Check whether a VALIDATION file's verdict is terminal (pass or needs-attention).
93
- * A non-terminal verdict (needs-remediation) means validation must re-run
94
- * after remediation slices are executed.
93
+ * Check whether a VALIDATION file's verdict is terminal.
94
+ * Any successfully extracted verdict (pass, needs-attention, needs-remediation,
95
+ * fail, etc.) means validation completed. Only return false when no verdict
96
+ * could be parsed — i.e. extractVerdict() returns undefined (#2769).
95
97
  */
96
98
  export function isValidationTerminal(validationContent: string): boolean {
97
- const v = extractVerdict(validationContent);
98
- if (!v) return false;
99
- // 'pass' and 'needs-attention' are always terminal.
100
- // 'needs-remediation' is treated as terminal to prevent infinite loops
101
- // when no remediation slices exist in the roadmap (#832). The validation
102
- // report is preserved on disk for manual review.
103
- return v === 'pass' || v === 'needs-attention' || v === 'needs-remediation';
99
+ return extractVerdict(validationContent) != null;
104
100
  }
105
101
 
106
102
  // ─── State Derivation ──────────────────────────────────────────────────────
@@ -211,7 +207,24 @@ export async function deriveState(basePath: string): Promise<GSDState> {
211
207
 
212
208
  // Dual-path: try DB-backed derivation first when hierarchy tables are populated
213
209
  if (isDbAvailable()) {
214
- const dbMilestones = getAllMilestones();
210
+ let dbMilestones = getAllMilestones();
211
+
212
+ // Disk→DB reconciliation (#2631): when the milestones table is empty
213
+ // (e.g. failed initial migration per #2529), the reconciliation code
214
+ // inside deriveStateFromDb is unreachable. Populate from disk here so
215
+ // the DB path activates correctly.
216
+ if (dbMilestones.length === 0) {
217
+ const diskIds = findMilestoneIds(basePath);
218
+ let synced = false;
219
+ for (const diskId of diskIds) {
220
+ if (!isGhostMilestone(basePath, diskId)) {
221
+ insertMilestone({ id: diskId, status: 'active' });
222
+ synced = true;
223
+ }
224
+ }
225
+ if (synced) dbMilestones = getAllMilestones();
226
+ }
227
+
215
228
  if (dbMilestones.length > 0) {
216
229
  const stopDbTimer = debugTime("derive-state-db");
217
230
  result = await deriveStateFromDb(basePath);
@@ -255,13 +268,6 @@ function extractContextTitle(content: string | null, fallback: string): string {
255
268
 
256
269
  // ─── DB-backed State Derivation ────────────────────────────────────────────
257
270
 
258
- /**
259
- * Helper: check if a DB status counts as "done" (handles K002 ambiguity).
260
- */
261
- function isStatusDone(status: string): boolean {
262
- return status === 'complete' || status === 'done';
263
- }
264
-
265
271
  /**
266
272
  * Derive GSD state from the milestones/slices/tasks DB tables.
267
273
  * Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
@@ -351,7 +357,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
351
357
  continue;
352
358
  }
353
359
 
354
- if (isStatusDone(m.status)) {
360
+ if (isClosedStatus(m.status)) {
355
361
  completeMilestoneIds.add(m.id);
356
362
  continue;
357
363
  }
@@ -365,7 +371,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
365
371
 
366
372
  // Check roadmap: all slices done means milestone is complete
367
373
  const slices = getMilestoneSlices(m.id);
368
- if (slices.length > 0 && slices.every(s => isStatusDone(s.status))) {
374
+ if (slices.length > 0 && slices.every(s => isClosedStatus(s.status))) {
369
375
  // All slices done but no summary — still counts as complete for dep resolution
370
376
  // if a summary file exists
371
377
  // Note: without summary file, the milestone is in validating/completing state, not complete
@@ -387,7 +393,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
387
393
 
388
394
  // Ghost milestone check: no slices in DB AND no substantive files on disk
389
395
  const slices = getMilestoneSlices(m.id);
390
- if (slices.length === 0 && !isStatusDone(m.status)) {
396
+ if (slices.length === 0 && !isClosedStatus(m.status)) {
391
397
  // Check disk for ghost detection
392
398
  if (isGhostMilestone(basePath, m.id)) continue;
393
399
  }
@@ -410,7 +416,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
410
416
  }
411
417
 
412
418
  // Not complete — determine if it should be active
413
- const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
419
+ const allSlicesDone = slices.length > 0 && slices.every(s => isClosedStatus(s.status));
414
420
 
415
421
  // Get title — prefer DB, fall back to context file extraction
416
422
  let title = stripMilestonePrefix(m.title) || m.id;
@@ -562,7 +568,10 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
562
568
  }
563
569
 
564
570
  // ── All slices done → validating/completing ─────────────────────────
565
- const allSlicesDone = activeMilestoneSlices.every(s => isStatusDone(s.status));
571
+ // Guard: [].every() === true (vacuous truth). Without the length check,
572
+ // an empty slice array causes a premature phase transition to
573
+ // validating-milestone. See: https://github.com/gsd-build/gsd-2/issues/2667
574
+ const allSlicesDone = activeMilestoneSlices.length > 0 && activeMilestoneSlices.every(s => isClosedStatus(s.status));
566
575
  if (allSlicesDone) {
567
576
  const validationFile = resolveMilestoneFile(basePath, activeMilestone.id, "VALIDATION");
568
577
  const validationContent = validationFile ? await loadFile(validationFile) : null;
@@ -595,19 +604,19 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
595
604
 
596
605
  // ── Find active slice (first incomplete with deps satisfied) ─────────
597
606
  const sliceProgress = {
598
- done: activeMilestoneSlices.filter(s => isStatusDone(s.status)).length,
607
+ done: activeMilestoneSlices.filter(s => isClosedStatus(s.status)).length,
599
608
  total: activeMilestoneSlices.length,
600
609
  };
601
610
 
602
611
  const doneSliceIds = new Set(
603
- activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id)
612
+ activeMilestoneSlices.filter(s => isClosedStatus(s.status)).map(s => s.id)
604
613
  );
605
614
 
606
615
  let activeSlice: ActiveRef | null = null;
607
616
  let activeSliceRow: SliceRow | null = null;
608
617
 
609
618
  for (const s of activeMilestoneSlices) {
610
- if (isStatusDone(s.status)) continue;
619
+ if (isClosedStatus(s.status)) continue;
611
620
  if (s.depends.every(dep => doneSliceIds.has(dep))) {
612
621
  activeSlice = { id: s.id, title: s.title };
613
622
  activeSliceRow = s;
@@ -650,7 +659,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
650
659
  // causing the dispatcher to re-dispatch the same completed task forever.
651
660
  let reconciled = false;
652
661
  for (const t of tasks) {
653
- if (isStatusDone(t.status)) continue;
662
+ if (isClosedStatus(t.status)) continue;
654
663
  const summaryPath = resolveTaskFile(basePath, activeMilestone.id, activeSlice.id, t.id, "SUMMARY");
655
664
  if (summaryPath && existsSync(summaryPath)) {
656
665
  try {
@@ -673,11 +682,11 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
673
682
  }
674
683
 
675
684
  const taskProgress = {
676
- done: tasks.filter(t => isStatusDone(t.status)).length,
685
+ done: tasks.filter(t => isClosedStatus(t.status)).length,
677
686
  total: tasks.length,
678
687
  };
679
688
 
680
- const activeTaskRow = tasks.find(t => !isStatusDone(t.status));
689
+ const activeTaskRow = tasks.find(t => !isClosedStatus(t.status));
681
690
 
682
691
  if (!activeTaskRow && tasks.length > 0) {
683
692
  // All tasks done but slice not marked complete → summarizing
@@ -738,7 +747,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
738
747
  }
739
748
 
740
749
  // ── Blocker detection: check completed tasks for blocker_discovered ──
741
- const completedTasks = tasks.filter(t => isStatusDone(t.status));
750
+ const completedTasks = tasks.filter(t => isClosedStatus(t.status));
742
751
  let blockerTaskId: string | null = null;
743
752
  for (const ct of completedTasks) {
744
753
  if (ct.blocker_discovered) {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Status predicates for GSD state-machine guards.
3
+ *
4
+ * The DB stores status as free-form strings. Two values indicate
5
+ * "closed": "complete" (canonical) and "done" (legacy / alias).
6
+ * Every inline `status === "complete" || status === "done"` should
7
+ * use isClosedStatus() instead.
8
+ */
9
+
10
+ /** Returns true when a milestone, slice, or task status indicates closure. */
11
+ export function isClosedStatus(status: string): boolean {
12
+ return status === "complete" || status === "done";
13
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Regression test for #2773 — activeMilestone.id guard
3
+ *
4
+ * When activeMilestone is a non-null object with `id: undefined` (corrupted
5
+ * state), the old `!state.activeMilestone` truthiness check passed through,
6
+ * causing a downstream crash when code assumed `.id` was a valid string.
7
+ *
8
+ * The fix uses optional chaining (`!state.activeMilestone?.id`) so all three
9
+ * "no usable milestone" shapes are caught:
10
+ * 1. activeMilestone === null
11
+ * 2. activeMilestone === undefined
12
+ * 3. activeMilestone === { id: undefined, title: "..." }
13
+ */
14
+
15
+ import { describe, it } from 'node:test'
16
+ import assert from 'node:assert/strict'
17
+
18
+ import type { GSDState, ActiveRef } from '../types.ts'
19
+
20
+ // ─── Guard Under Test ────────────────────────────────────────────────────────
21
+ // Extracted guard logic identical to headless-query.ts (line 74) and
22
+ // guided-flow.ts (lines 522, 1047).
23
+
24
+ function activeMilestoneIsUsable(activeMilestone: ActiveRef | null | undefined): boolean {
25
+ return !!activeMilestone?.id
26
+ }
27
+
28
+ // ─── Tests ───────────────────────────────────────────────────────────────────
29
+
30
+ describe('activeMilestone?.id guard (#2773)', () => {
31
+ it('rejects null activeMilestone', () => {
32
+ assert.equal(activeMilestoneIsUsable(null), false)
33
+ })
34
+
35
+ it('rejects undefined activeMilestone', () => {
36
+ assert.equal(activeMilestoneIsUsable(undefined), false)
37
+ })
38
+
39
+ it('rejects malformed activeMilestone with id: undefined', () => {
40
+ // This is the crash case from #2773 — object exists but id is undefined
41
+ const malformed = { id: undefined, title: 'Ghost Milestone' } as unknown as ActiveRef
42
+ assert.equal(activeMilestoneIsUsable(malformed), false)
43
+ })
44
+
45
+ it('rejects malformed activeMilestone with id: empty string', () => {
46
+ const malformed = { id: '', title: 'Empty ID Milestone' } as unknown as ActiveRef
47
+ assert.equal(activeMilestoneIsUsable(malformed), false)
48
+ })
49
+
50
+ it('accepts valid activeMilestone with a real id', () => {
51
+ const valid: ActiveRef = { id: 'M001', title: 'Real Milestone' }
52
+ assert.equal(activeMilestoneIsUsable(valid), true)
53
+ })
54
+ })
55
+
56
+ describe('headless-query stop behavior with corrupted milestone', () => {
57
+ // Simulates the decision logic from handleQuery (headless-query.ts:74-78)
58
+ function deriveNextAction(activeMilestone: ActiveRef | null | undefined, phase: string) {
59
+ if (!activeMilestone?.id) {
60
+ return {
61
+ action: 'stop' as const,
62
+ reason: phase === 'complete' ? 'All milestones complete.' : 'No active milestone.',
63
+ }
64
+ }
65
+ return { action: 'dispatch' as const, unitId: activeMilestone.id }
66
+ }
67
+
68
+ it('returns stop when activeMilestone is null', () => {
69
+ const result = deriveNextAction(null, 'pre-planning')
70
+ assert.equal(result.action, 'stop')
71
+ })
72
+
73
+ it('returns stop when activeMilestone has undefined id', () => {
74
+ const corrupted = { id: undefined, title: 'Corrupted' } as unknown as ActiveRef
75
+ const result = deriveNextAction(corrupted, 'executing')
76
+ assert.equal(result.action, 'stop')
77
+ assert.equal(result.reason, 'No active milestone.')
78
+ })
79
+
80
+ it('returns dispatch with valid milestone id', () => {
81
+ const valid: ActiveRef = { id: 'M001', title: 'Valid' }
82
+ const result = deriveNextAction(valid, 'executing')
83
+ assert.equal(result.action, 'dispatch')
84
+ })
85
+
86
+ it('returns correct stop reason when phase is complete', () => {
87
+ const result = deriveNextAction(null, 'complete')
88
+ assert.equal(result.action, 'stop')
89
+ assert.equal(result.reason, 'All milestones complete.')
90
+ })
91
+ })
@@ -102,7 +102,7 @@ test("pauseAuto calls resolveAgentEndCancelled to unblock the loop", () => {
102
102
  const fnBlock = source.slice(fnIdx, source.indexOf("\n/**\n * Build", fnIdx + 100));
103
103
 
104
104
  assert.ok(
105
- fnBlock.includes("resolveAgentEndCancelled()"),
105
+ fnBlock.includes("resolveAgentEndCancelled("),
106
106
  "pauseAuto must call resolveAgentEndCancelled to unblock the auto-loop promise",
107
107
  );
108
108
  });
@@ -0,0 +1,61 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { parseMilestoneTarget } from "../commands/handlers/auto.js";
5
+
6
+ describe("parseMilestoneTarget", () => {
7
+ it("extracts a simple milestone ID", () => {
8
+ const result = parseMilestoneTarget("auto M016");
9
+ assert.equal(result.milestoneId, "M016");
10
+ assert.equal(result.rest, "auto");
11
+ });
12
+
13
+ it("extracts a milestone ID with unique suffix", () => {
14
+ const result = parseMilestoneTarget("auto M001-a3b4c5 --verbose");
15
+ assert.equal(result.milestoneId, "M001-a3b4c5");
16
+ assert.equal(result.rest, "auto --verbose");
17
+ });
18
+
19
+ it("returns null when no milestone ID is present", () => {
20
+ const result = parseMilestoneTarget("auto --verbose");
21
+ assert.equal(result.milestoneId, null);
22
+ assert.equal(result.rest, "auto --verbose");
23
+ });
24
+
25
+ it("extracts milestone ID with flags in any order", () => {
26
+ const result = parseMilestoneTarget("auto --verbose M003 --debug");
27
+ assert.equal(result.milestoneId, "M003");
28
+ assert.equal(result.rest, "auto --verbose --debug");
29
+ });
30
+
31
+ it("returns null for plain 'auto'", () => {
32
+ const result = parseMilestoneTarget("auto");
33
+ assert.equal(result.milestoneId, null);
34
+ assert.equal(result.rest, "auto");
35
+ });
36
+
37
+ it("extracts from 'next' command", () => {
38
+ const result = parseMilestoneTarget("next M012");
39
+ assert.equal(result.milestoneId, "M012");
40
+ assert.equal(result.rest, "next");
41
+ });
42
+
43
+ it("handles milestone ID at the start of input", () => {
44
+ const result = parseMilestoneTarget("M007");
45
+ assert.equal(result.milestoneId, "M007");
46
+ assert.equal(result.rest, "");
47
+ });
48
+
49
+ it("picks the first milestone ID when multiple appear", () => {
50
+ // Edge case: user accidentally types two. First one wins.
51
+ const result = parseMilestoneTarget("auto M001 M002");
52
+ assert.equal(result.milestoneId, "M001");
53
+ // M002 remains in rest since only the first match is removed
54
+ assert.ok(result.rest.includes("M002"));
55
+ });
56
+
57
+ it("does not match bare numbers without M prefix", () => {
58
+ const result = parseMilestoneTarget("auto 016");
59
+ assert.equal(result.milestoneId, null);
60
+ });
61
+ });
@@ -0,0 +1,87 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, mkdtempSync, writeFileSync, existsSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { writeLock, readCrashLock, clearLock } from "../crash-recovery.ts";
8
+ import { checkRemoteAutoSession, stopAutoRemote } from "../auto.ts";
9
+
10
+ function makeTmpProject(): string {
11
+ const dir = mkdtempSync(join(tmpdir(), "gsd-stale-lock-test-"));
12
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
13
+ return dir;
14
+ }
15
+
16
+ // ─── checkRemoteAutoSession: own-PID filtering (#2730) ───────────────────
17
+
18
+ test("#2730: checkRemoteAutoSession returns { running: false } when lock PID matches current process", (t) => {
19
+ const dir = makeTmpProject();
20
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
21
+
22
+ // Write a lock with the current process PID — simulates a stale lock
23
+ // left behind after step-mode exit without full cleanup.
24
+ writeLock(dir, "execute-task", "M001/S01/T01");
25
+
26
+ const lock = readCrashLock(dir);
27
+ assert.ok(lock, "lock file should exist");
28
+ assert.equal(lock!.pid, process.pid, "lock should have our PID");
29
+
30
+ const result = checkRemoteAutoSession(dir);
31
+ assert.equal(result.running, false, "own PID must not be treated as a remote session");
32
+ });
33
+
34
+ test("#2730: checkRemoteAutoSession still detects a genuine remote session (different PID)", (t) => {
35
+ const dir = makeTmpProject();
36
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
37
+
38
+ // Use parent PID — guaranteed alive, guaranteed not our PID.
39
+ const remotePid = process.ppid;
40
+ const lockData = {
41
+ pid: remotePid,
42
+ startedAt: new Date().toISOString(),
43
+ unitType: "execute-task",
44
+ unitId: "M001/S01/T02",
45
+ unitStartedAt: new Date().toISOString(),
46
+ };
47
+ writeFileSync(join(dir, ".gsd", "auto.lock"), JSON.stringify(lockData, null, 2));
48
+
49
+ const result = checkRemoteAutoSession(dir);
50
+ assert.equal(result.running, true, "different live PID should be detected as running");
51
+ assert.equal(result.pid, remotePid);
52
+ });
53
+
54
+ // ─── stopAutoRemote: self-kill prevention (#2730) ────────────────────────
55
+
56
+ test("#2730: stopAutoRemote does not send SIGTERM when lock PID matches current process", (t) => {
57
+ const dir = makeTmpProject();
58
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
59
+
60
+ // Write a lock with our own PID
61
+ writeLock(dir, "execute-task", "M001/S01/T01");
62
+
63
+ const result = stopAutoRemote(dir);
64
+ assert.equal(result.found, false, "own PID must not be signalled");
65
+
66
+ // The lock should be cleared as part of the self-detection cleanup
67
+ assert.ok(!existsSync(join(dir, ".gsd", "auto.lock")), "stale self-lock should be cleared");
68
+ });
69
+
70
+ test("#2730: stopAutoRemote clears stale lock from dead remote process without error", (t) => {
71
+ const dir = makeTmpProject();
72
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
73
+
74
+ // Simulate a stale lock from a process that no longer exists
75
+ const lockData = {
76
+ pid: 9999999,
77
+ startedAt: "2026-03-01T00:00:00Z",
78
+ unitType: "plan-slice",
79
+ unitId: "M001/S02",
80
+ unitStartedAt: "2026-03-01T00:05:00Z",
81
+ };
82
+ writeFileSync(join(dir, ".gsd", "auto.lock"), JSON.stringify(lockData, null, 2));
83
+
84
+ const result = stopAutoRemote(dir);
85
+ assert.equal(result.found, false, "dead remote PID should not be reported as found");
86
+ assert.ok(!existsSync(join(dir, ".gsd", "auto.lock")), "stale lock should be cleaned up");
87
+ });
@@ -0,0 +1,80 @@
1
+ /**
2
+ * auto-worktree-auto-resolve.test.ts — Unit tests for isSafeToAutoResolve.
3
+ *
4
+ * Covers: .gsd/ state files, build artifacts (.tsbuildinfo, .pyc, __pycache__,
5
+ * .DS_Store, .map), and rejection of real source files.
6
+ */
7
+
8
+ import { describe, test } from "node:test";
9
+ import assert from "node:assert/strict";
10
+
11
+ import {
12
+ isSafeToAutoResolve,
13
+ SAFE_AUTO_RESOLVE_PATTERNS,
14
+ } from "../auto-worktree.ts";
15
+
16
+ describe("isSafeToAutoResolve", () => {
17
+ // ─── .gsd/ state files ───────────────────────────────────────────────────
18
+ test("returns true for .gsd/ prefixed paths", () => {
19
+ assert.ok(isSafeToAutoResolve(".gsd/STATE.md"));
20
+ assert.ok(isSafeToAutoResolve(".gsd/milestones/M001/CONTEXT.md"));
21
+ assert.ok(isSafeToAutoResolve(".gsd/gsd.db"));
22
+ });
23
+
24
+ // ─── Build artifact patterns ─────────────────────────────────────────────
25
+ test("returns true for .tsbuildinfo files", () => {
26
+ assert.ok(isSafeToAutoResolve("tsconfig.tsbuildinfo"));
27
+ assert.ok(isSafeToAutoResolve("dist/tsconfig.tsbuildinfo"));
28
+ });
29
+
30
+ test("returns true for .pyc files", () => {
31
+ assert.ok(isSafeToAutoResolve("module.pyc"));
32
+ assert.ok(isSafeToAutoResolve("src/utils/helpers.pyc"));
33
+ });
34
+
35
+ test("returns true for __pycache__/ paths", () => {
36
+ assert.ok(isSafeToAutoResolve("src/__pycache__/module.cpython-311.pyc"));
37
+ assert.ok(isSafeToAutoResolve("lib/__pycache__/foo.py"));
38
+ });
39
+
40
+ test("returns true for .DS_Store files", () => {
41
+ assert.ok(isSafeToAutoResolve(".DS_Store"));
42
+ assert.ok(isSafeToAutoResolve("src/.DS_Store"));
43
+ });
44
+
45
+ test("returns true for .map source map files", () => {
46
+ assert.ok(isSafeToAutoResolve("dist/index.js.map"));
47
+ assert.ok(isSafeToAutoResolve("out/bundle.css.map"));
48
+ });
49
+
50
+ // ─── Real source files (should NOT be auto-resolved) ─────────────────────
51
+ test("returns false for .ts source files", () => {
52
+ assert.ok(!isSafeToAutoResolve("src/index.ts"));
53
+ assert.ok(!isSafeToAutoResolve("lib/utils.ts"));
54
+ });
55
+
56
+ test("returns false for .js source files", () => {
57
+ assert.ok(!isSafeToAutoResolve("src/index.js"));
58
+ assert.ok(!isSafeToAutoResolve("lib/helpers.js"));
59
+ });
60
+
61
+ test("returns false for .py source files", () => {
62
+ assert.ok(!isSafeToAutoResolve("src/main.py"));
63
+ assert.ok(!isSafeToAutoResolve("scripts/deploy.py"));
64
+ });
65
+
66
+ test("returns false for config and data files", () => {
67
+ assert.ok(!isSafeToAutoResolve("package.json"));
68
+ assert.ok(!isSafeToAutoResolve("tsconfig.json"));
69
+ assert.ok(!isSafeToAutoResolve("README.md"));
70
+ });
71
+
72
+ // ─── SAFE_AUTO_RESOLVE_PATTERNS export ────────────────────────────────────
73
+ test("SAFE_AUTO_RESOLVE_PATTERNS is a non-empty array of RegExp", () => {
74
+ assert.ok(Array.isArray(SAFE_AUTO_RESOLVE_PATTERNS));
75
+ assert.ok(SAFE_AUTO_RESOLVE_PATTERNS.length > 0);
76
+ for (const pattern of SAFE_AUTO_RESOLVE_PATTERNS) {
77
+ assert.ok(pattern instanceof RegExp);
78
+ }
79
+ });
80
+ });
@@ -77,7 +77,7 @@ function addSliceToMilestone(
77
77
  run(`git branch -d ${sliceBranch}`, wtPath);
78
78
  }
79
79
 
80
- describe("auto-worktree-milestone-merge", () => {
80
+ describe("auto-worktree-milestone-merge", { timeout: 300_000 }, () => {
81
81
  const savedCwd = process.cwd();
82
82
  const tempDirs: string[] = [];
83
83