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
@@ -1,73 +1,84 @@
1
1
  /**
2
2
  * terminated-transient.test.ts — Regression test for #2309.
3
3
  *
4
- * classifyProviderError should treat 'terminated' errors (process killed,
4
+ * classifyError should treat 'terminated' errors (process killed,
5
5
  * connection reset) as transient with auto-resume, not permanent.
6
6
  */
7
7
 
8
8
  import test from "node:test";
9
9
  import assert from "node:assert/strict";
10
- import { classifyProviderError } from "../provider-error-pause.ts";
10
+ import { classifyError, isTransient } from "../error-classifier.ts";
11
11
 
12
12
  test("#2309: 'terminated' errors should be classified as transient", () => {
13
- const result = classifyProviderError("terminated");
14
- assert.equal(result.isTransient, true, "'terminated' should be transient");
15
- assert.equal(result.isRateLimit, false, "'terminated' is not a rate limit");
16
- assert.ok(result.suggestedDelayMs > 0, "'terminated' should have a retry delay");
13
+ const result = classifyError("terminated");
14
+ assert.equal(isTransient(result), true, "'terminated' should be transient");
15
+ assert.equal(result.kind, "connection", "'terminated' matches connection");
16
+ assert.ok("retryAfterMs" in result && result.retryAfterMs > 0, "'terminated' should have a retry delay");
17
+ assert.equal("retryAfterMs" in result && result.retryAfterMs, 15_000, "'terminated' should use 15s backoff");
17
18
  });
18
19
 
19
- test("#2309: 'connection reset' errors should be classified as transient", () => {
20
- const result = classifyProviderError("connection reset by peer");
21
- assert.equal(result.isTransient, true, "'connection reset' should be transient");
20
+ test("#2309: 'connection reset by peer' errors should be classified as transient (network)", () => {
21
+ const result = classifyError("connection reset by peer");
22
+ assert.equal(isTransient(result), true, "'connection reset by peer' should be transient");
23
+ assert.equal(result.kind, "network", "'connection reset by peer' matches NETWORK_RE (connection.*reset) before CONNECTION_RE");
24
+ assert.equal("retryAfterMs" in result && result.retryAfterMs, 3_000, "network errors use 3s backoff");
22
25
  });
23
26
 
24
27
  test("#2309: 'other side closed' errors should be classified as transient", () => {
25
- const result = classifyProviderError("other side closed the connection");
26
- assert.equal(result.isTransient, true, "'other side closed' should be transient");
28
+ const result = classifyError("other side closed the connection");
29
+ assert.equal(isTransient(result), true, "'other side closed' should be transient");
30
+ assert.equal(result.kind, "connection", "'other side closed' matches CONNECTION_RE");
27
31
  });
28
32
 
29
33
  test("#2309: 'fetch failed' errors should be classified as transient", () => {
30
- const result = classifyProviderError("fetch failed: network error");
31
- assert.equal(result.isTransient, true, "'fetch failed' should be transient");
34
+ const result = classifyError("fetch failed: network error");
35
+ assert.equal(isTransient(result), true, "'fetch failed' should be transient");
36
+ assert.equal(result.kind, "network", "'fetch failed' matches NETWORK_RE");
37
+ assert.equal("retryAfterMs" in result && result.retryAfterMs, 3_000, "network errors use 3s backoff");
32
38
  });
33
39
 
34
40
  test("#2309: 'connection refused' errors should be classified as transient", () => {
35
- const result = classifyProviderError("ECONNREFUSED: connection refused");
36
- assert.equal(result.isTransient, true, "'connection refused' should be transient");
41
+ const result = classifyError("ECONNREFUSED: connection refused");
42
+ assert.equal(isTransient(result), true, "'connection refused' should be transient");
43
+ assert.equal(result.kind, "network", "'ECONNREFUSED' matches NETWORK_RE (same-model retry)");
37
44
  });
38
45
 
39
46
  test("#2309: permanent errors are still permanent", () => {
40
- const authResult = classifyProviderError("unauthorized: invalid API key");
41
- assert.equal(authResult.isTransient, false, "auth errors should stay permanent");
42
- assert.equal(authResult.suggestedDelayMs, 0, "permanent errors have no delay");
47
+ const authResult = classifyError("unauthorized: invalid API key");
48
+ assert.equal(isTransient(authResult), false, "auth errors should stay permanent");
49
+ assert.equal(authResult.kind, "permanent", "auth errors are permanent");
50
+ assert.equal("retryAfterMs" in authResult, false, "permanent errors have no retryAfterMs");
43
51
  });
44
52
 
45
53
  test("#2309: rate limits are still transient", () => {
46
- const rlResult = classifyProviderError("rate limit exceeded (429)");
47
- assert.equal(rlResult.isTransient, true, "rate limits are still transient");
48
- assert.equal(rlResult.isRateLimit, true, "rate limits are flagged as rate limits");
54
+ const rlResult = classifyError("rate limit exceeded (429)");
55
+ assert.equal(isTransient(rlResult), true, "rate limits are still transient");
56
+ assert.equal(rlResult.kind, "rate-limit", "rate limits are flagged as rate-limit kind");
49
57
  });
50
58
 
51
59
  // --- #2572: stream-truncation JSON parse errors should be transient ---
52
60
 
53
61
  test("#2572: 'Expected double-quoted property name' (truncated stream) is transient", () => {
54
- const result = classifyProviderError("Expected double-quoted property name in JSON at position 23 (line 1 column 24)");
55
- assert.equal(result.isTransient, true, "truncated-stream JSON parse error should be transient");
56
- assert.equal(result.isRateLimit, false, "not a rate limit");
57
- assert.equal(result.suggestedDelayMs, 15_000, "should use 15s backoff like connection errors");
62
+ const result = classifyError("Expected double-quoted property name in JSON at position 23 (line 1 column 24)");
63
+ assert.equal(isTransient(result), true, "truncated-stream JSON parse error should be transient");
64
+ assert.equal(result.kind, "stream", "JSON parse errors are stream kind");
65
+ assert.equal("retryAfterMs" in result && result.retryAfterMs, 15_000, "should use 15s backoff");
58
66
  });
59
67
 
60
68
  test("#2572: 'Unexpected end of JSON input' (truncated stream) is transient", () => {
61
- const result = classifyProviderError("Unexpected end of JSON input");
62
- assert.equal(result.isTransient, true, "'Unexpected end of JSON input' should be transient");
69
+ const result = classifyError("Unexpected end of JSON input");
70
+ assert.equal(isTransient(result), true, "'Unexpected end of JSON input' should be transient");
71
+ assert.equal(result.kind, "stream", "JSON parse errors are stream kind");
63
72
  });
64
73
 
65
74
  test("#2572: 'Unexpected token' in JSON (truncated stream) is transient", () => {
66
- const result = classifyProviderError("Unexpected token < in JSON at position 0");
67
- assert.equal(result.isTransient, true, "'Unexpected token in JSON' should be transient");
75
+ const result = classifyError("Unexpected token < in JSON at position 0");
76
+ assert.equal(isTransient(result), true, "'Unexpected token in JSON' should be transient");
77
+ assert.equal(result.kind, "stream", "JSON parse errors are stream kind");
68
78
  });
69
79
 
70
80
  test("#2572: 'SyntaxError' with JSON context (truncated stream) is transient", () => {
71
- const result = classifyProviderError("SyntaxError: JSON.parse: unexpected character at line 1 column 1");
72
- assert.equal(result.isTransient, true, "'SyntaxError...JSON' should be transient");
81
+ const result = classifyError("SyntaxError: JSON.parse: unexpected character at line 1 column 1");
82
+ assert.equal(isTransient(result), true, "'SyntaxError...JSON' should be transient");
83
+ assert.equal(result.kind, "stream", "JSON parse errors are stream kind");
73
84
  });
@@ -63,13 +63,13 @@ test("show_token_cost defaults to undefined (disabled) when not set", () => {
63
63
  assert.equal(preferences.show_token_cost, undefined);
64
64
  });
65
65
 
66
- test("empty preferences.md does not enable show_token_cost", () => {
66
+ test("empty PREFERENCES.md does not enable show_token_cost", () => {
67
67
  const prefs = parsePreferencesMarkdown("---\nversion: 1\n---\n");
68
68
  assert.ok(prefs);
69
69
  assert.equal(prefs.show_token_cost, undefined);
70
70
  });
71
71
 
72
- test("preferences.md with show_token_cost: true enables the preference", () => {
72
+ test("PREFERENCES.md with show_token_cost: true enables the preference", () => {
73
73
  const prefs = parsePreferencesMarkdown("---\nshow_token_cost: true\n---\n");
74
74
  assert.ok(prefs);
75
75
  assert.equal(prefs.show_token_cost, true);
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Regression test for #2667: deriveStateFromDb must NOT treat an empty
3
+ * slice array as "all slices done" due to JavaScript's vacuous-truth
4
+ * behavior of Array.prototype.every on an empty array.
5
+ *
6
+ * [].every(predicate) === true in JavaScript. Without a length > 0 guard,
7
+ * this causes a premature phase transition to validating-milestone when
8
+ * the DB returns 0 slices (e.g. after a worktree DB wipe).
9
+ */
10
+ import { test } from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+
16
+ import { deriveStateFromDb, invalidateStateCache } from "../state.ts";
17
+ import {
18
+ openDatabase,
19
+ closeDatabase,
20
+ insertMilestone,
21
+ insertSlice,
22
+ } from "../gsd-db.ts";
23
+
24
+ test("deriveStateFromDb does NOT skip to validating when slice array is empty (#2667)", async () => {
25
+ const base = mkdtempSync(join(tmpdir(), "gsd-vacuous-truth-"));
26
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
27
+
28
+ try {
29
+ // Set up a milestone with a roadmap that references slices,
30
+ // but the DB has NO slice rows (simulating a worktree DB wipe)
31
+ writeFileSync(
32
+ join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
33
+ [
34
+ "# M001: Test Milestone",
35
+ "",
36
+ "## Slices",
37
+ "",
38
+ "### S01 — First Slice",
39
+ "Do something.",
40
+ "",
41
+ "### S02 — Second Slice",
42
+ "Do another thing.",
43
+ ].join("\n"),
44
+ );
45
+
46
+ openDatabase(":memory:");
47
+ // Milestone exists but NO slices inserted — simulates DB wipe
48
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
49
+
50
+ invalidateStateCache();
51
+ const state = await deriveStateFromDb(base);
52
+
53
+ // The phase must NOT be "validating-milestone" or "completing-milestone"
54
+ // because no slices have been executed — the empty array should not
55
+ // trigger the "all slices done" code path.
56
+ assert.notEqual(
57
+ state.phase,
58
+ "validating-milestone",
59
+ "empty slice array must not trigger validating-milestone (vacuous truth)",
60
+ );
61
+ assert.notEqual(
62
+ state.phase,
63
+ "completing-milestone",
64
+ "empty slice array must not trigger completing-milestone (vacuous truth)",
65
+ );
66
+
67
+ closeDatabase();
68
+ } finally {
69
+ closeDatabase();
70
+ rmSync(base, { recursive: true, force: true });
71
+ }
72
+ });
73
+
74
+ test("deriveStateFromDb correctly reaches validating when all slices are done (#2667 guard)", async () => {
75
+ const base = mkdtempSync(join(tmpdir(), "gsd-vacuous-truth-"));
76
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01"), { recursive: true });
77
+
78
+ try {
79
+ writeFileSync(
80
+ join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
81
+ [
82
+ "# M001: Test Milestone",
83
+ "",
84
+ "## Slices",
85
+ "",
86
+ "### S01 — First Slice",
87
+ "Do something.",
88
+ ].join("\n"),
89
+ );
90
+
91
+ // Write a slice summary so the filesystem recognizes it as complete
92
+ writeFileSync(
93
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md"),
94
+ "# S01 Summary\n\nDone.",
95
+ );
96
+
97
+ openDatabase(":memory:");
98
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
99
+ insertSlice({ id: "S01", milestoneId: "M001", title: "First Slice", status: "complete", risk: "low", depends: [] });
100
+
101
+ invalidateStateCache();
102
+ const state = await deriveStateFromDb(base);
103
+
104
+ // With one slice that IS complete, phase should advance
105
+ assert.ok(
106
+ state.phase === "validating-milestone" || state.phase === "completing-milestone",
107
+ `expected validating or completing phase, got "${state.phase}"`,
108
+ );
109
+
110
+ closeDatabase();
111
+ } finally {
112
+ closeDatabase();
113
+ rmSync(base, { recursive: true, force: true });
114
+ }
115
+ });
@@ -0,0 +1,90 @@
1
+ import { describe, it, afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, existsSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { randomUUID } from "node:crypto";
7
+
8
+ import { handleValidateMilestone } from "../tools/validate-milestone.js";
9
+ import { openDatabase, closeDatabase, _getAdapter, insertMilestone } from "../gsd-db.js";
10
+ import { clearPathCache } from "../paths.js";
11
+ import { clearParseCache } from "../files.js";
12
+
13
+ function makeTmpBase(): string {
14
+ const base = join(tmpdir(), `gsd-val-handler-${randomUUID()}`);
15
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
16
+ return base;
17
+ }
18
+
19
+ const VALID_PARAMS = {
20
+ milestoneId: "M001",
21
+ verdict: "pass" as const,
22
+ remediationRound: 0,
23
+ successCriteriaChecklist: "- [x] All pass",
24
+ sliceDeliveryAudit: "| S01 | delivered |",
25
+ crossSliceIntegration: "No issues",
26
+ requirementCoverage: "All covered",
27
+ verdictRationale: "Everything checks out",
28
+ };
29
+
30
+ describe("handleValidateMilestone write ordering (#2725)", () => {
31
+ let base: string;
32
+
33
+ afterEach(() => {
34
+ clearPathCache();
35
+ clearParseCache();
36
+ try { closeDatabase(); } catch { /* */ }
37
+ if (base) {
38
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
39
+ }
40
+ });
41
+
42
+ it("writes DB row and disk file on success", async () => {
43
+ base = makeTmpBase();
44
+ const dbPath = join(base, ".gsd", "gsd.db");
45
+ openDatabase(dbPath);
46
+ insertMilestone({ id: "M001" });
47
+
48
+ const result = await handleValidateMilestone(VALID_PARAMS, base);
49
+ assert.ok(!("error" in result), `unexpected error: ${"error" in result ? result.error : ""}`);
50
+
51
+ // DB row exists
52
+ const adapter = _getAdapter()!;
53
+ const row = adapter.prepare(
54
+ `SELECT status, scope FROM assessments WHERE milestone_id = 'M001' AND scope = 'milestone-validation'`,
55
+ ).get() as { status: string; scope: string } | undefined;
56
+ assert.ok(row, "assessment row should exist in DB");
57
+ assert.equal(row!.status, "pass");
58
+
59
+ // Disk file exists
60
+ const filePath = join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md");
61
+ assert.ok(existsSync(filePath), "VALIDATION.md should exist on disk");
62
+ });
63
+
64
+ it("rolls back DB row when disk write fails", async () => {
65
+ base = makeTmpBase();
66
+ const dbPath = join(base, ".gsd", "gsd.db");
67
+ openDatabase(dbPath);
68
+ insertMilestone({ id: "M001" });
69
+
70
+ // Force disk write failure by replacing the milestone directory with a
71
+ // regular file. saveFile() will fail because it cannot write inside a
72
+ // non-directory. This works cross-platform (chmod is ignored on Windows).
73
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
74
+ rmSync(milestoneDir, { recursive: true, force: true });
75
+ writeFileSync(milestoneDir, "not-a-directory");
76
+
77
+ const result = await handleValidateMilestone(VALID_PARAMS, base);
78
+
79
+ // Should return error
80
+ assert.ok("error" in result, "should return error when disk write fails");
81
+ assert.ok(result.error.includes("disk render failed"));
82
+
83
+ // DB row should have been rolled back (deleted)
84
+ const adapter = _getAdapter()!;
85
+ const row = adapter.prepare(
86
+ `SELECT * FROM assessments WHERE milestone_id = 'M001' AND scope = 'milestone-validation'`,
87
+ ).get();
88
+ assert.equal(row, undefined, "assessment row should be deleted after disk-write rollback");
89
+ });
90
+ });
@@ -110,6 +110,16 @@ test("isValidationTerminal returns true for verdict: passed (#1429)", () => {
110
110
  assert.equal(isValidationTerminal(content), true);
111
111
  });
112
112
 
113
+ test("isValidationTerminal returns true for verdict: fail (#2769)", () => {
114
+ const content = "---\nverdict: fail\nremediation_round: 1\n---\n\n# Validation";
115
+ assert.equal(isValidationTerminal(content), true);
116
+ });
117
+
118
+ test("isValidationTerminal returns true for any arbitrary verdict string (#2769)", () => {
119
+ const content = "---\nverdict: custom-verdict\nremediation_round: 0\n---\n\n# Validation";
120
+ assert.equal(isValidationTerminal(content), true);
121
+ });
122
+
113
123
  test("isValidationTerminal returns false for missing frontmatter", () => {
114
124
  const content = "# Validation\nNo frontmatter here.";
115
125
  assert.equal(isValidationTerminal(content), false);
@@ -327,14 +337,14 @@ test("verifyExpectedArtifact rejects VALIDATION with missing verdict field", ()
327
337
  }
328
338
  });
329
339
 
330
- test("verifyExpectedArtifact rejects VALIDATION with unrecognized verdict", () => {
340
+ test("verifyExpectedArtifact accepts VALIDATION with any extracted verdict", () => {
331
341
  const base = makeTmpBase();
332
342
  try {
333
343
  writeValidation(base, "M001", "---\nverdict: unknown-value\nremediation_round: 0\n---\n\n# Validation");
334
344
  clearPathCache();
335
345
  clearParseCache();
336
346
  const result = verifyExpectedArtifact("validate-milestone", "M001", base);
337
- assert.equal(result, false, "VALIDATION with unrecognized verdict should fail verification");
347
+ assert.equal(result, true, "VALIDATION with any extracted verdict should pass verification");
338
348
  } finally {
339
349
  cleanup(base);
340
350
  }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Unit tests for the milestone completion validation gate pattern matching.
3
+ *
4
+ * The gate in auto-dispatch accepts two evidence formats:
5
+ * 1. Structured template: content contains "Operational" AND ("MET" or "N/A")
6
+ * 2. Prose evidence: matches /[Oo]perational[\s:][^\n]*(?:pass|verified|...)/i
7
+ *
8
+ * These tests exercise the exact same expressions used in auto-dispatch.ts
9
+ * to ensure both formats are correctly recognized, and that content without
10
+ * operational evidence is properly rejected.
11
+ */
12
+
13
+ import test from "node:test";
14
+ import assert from "node:assert/strict";
15
+
16
+ // ─── Replicate the gate matching logic from auto-dispatch.ts ─────────────────
17
+
18
+ /**
19
+ * Returns true when validation content contains acceptable operational
20
+ * verification evidence (structured or prose). Mirrors the inline checks
21
+ * in the "execute → complete-milestone" dispatch rule.
22
+ */
23
+ function hasOperationalEvidence(validationContent: string): boolean {
24
+ const structuredMatch =
25
+ validationContent.includes("Operational") &&
26
+ (validationContent.includes("MET") || validationContent.includes("N/A"));
27
+ const proseMatch =
28
+ /[Oo]perational[\s:][^\n]*(?:pass|verified|confirmed|met|complete|true|yes|addressed|covered|n\/a|not\s+applicable)/i.test(
29
+ validationContent,
30
+ );
31
+ return structuredMatch || proseMatch;
32
+ }
33
+
34
+ // ─── Structured format ───────────────────────────────────────────────────────
35
+
36
+ test("structured: Operational + MET passes", () => {
37
+ const content = `| Criteria | Status |
38
+ | Operational | MET |
39
+ | Functional | MET |`;
40
+ assert.ok(hasOperationalEvidence(content));
41
+ });
42
+
43
+ test("structured: Operational + N/A passes", () => {
44
+ const content = `| Criteria | Status |
45
+ | Operational | N/A |
46
+ | Functional | MET |`;
47
+ assert.ok(hasOperationalEvidence(content));
48
+ });
49
+
50
+ test("structured: Operational present with MET on another row still passes (includes is content-wide)", () => {
51
+ // The structured check uses .includes() across the entire content,
52
+ // so "MET" on the Functional row satisfies the condition alongside
53
+ // "Operational" anywhere in the document.
54
+ const content = `| Criteria | Status |
55
+ | Operational | PENDING |
56
+ | Functional | MET |`;
57
+ assert.ok(hasOperationalEvidence(content));
58
+ });
59
+
60
+ test("structured: Operational alone without any MET or N/A anywhere fails", () => {
61
+ const content = `| Criteria | Status |
62
+ | Operational | PENDING |
63
+ | Functional | PENDING |`;
64
+ assert.ok(!hasOperationalEvidence(content));
65
+ });
66
+
67
+ // ─── Prose format ────────────────────────────────────────────────────────────
68
+
69
+ test('prose: "Operational: verified" passes', () => {
70
+ const content = `## Validation Report
71
+ Operational: verified — all endpoints responsive.
72
+ Functional: tests pass.`;
73
+ assert.ok(hasOperationalEvidence(content));
74
+ });
75
+
76
+ test('prose: "Operational checks confirmed" passes', () => {
77
+ const content = `## Validation Report
78
+ Operational checks confirmed by smoke test suite.`;
79
+ assert.ok(hasOperationalEvidence(content));
80
+ });
81
+
82
+ test('prose: "Operational — pass" passes', () => {
83
+ const content = `Operational — pass (all services healthy)`;
84
+ assert.ok(hasOperationalEvidence(content));
85
+ });
86
+
87
+ test('prose: "operational: addressed" passes (case-insensitive)', () => {
88
+ const content = `operational: addressed in CI pipeline run #42.`;
89
+ assert.ok(hasOperationalEvidence(content));
90
+ });
91
+
92
+ test('prose: "Operational: not applicable" passes', () => {
93
+ const content = `Operational: not applicable for this library-only change.`;
94
+ assert.ok(hasOperationalEvidence(content));
95
+ });
96
+
97
+ test('prose: "Operational: n/a" passes', () => {
98
+ const content = `Operational: n/a — no runtime components.`;
99
+ assert.ok(hasOperationalEvidence(content));
100
+ });
101
+
102
+ test('prose: "Operational: complete" passes', () => {
103
+ const content = `Operational: complete — all health checks green.`;
104
+ assert.ok(hasOperationalEvidence(content));
105
+ });
106
+
107
+ // ─── Rejection cases ─────────────────────────────────────────────────────────
108
+
109
+ test("no operational evidence: unrelated content fails", () => {
110
+ const content = `## Validation Report
111
+ All functional tests pass.
112
+ Code coverage at 92%.`;
113
+ assert.ok(!hasOperationalEvidence(content));
114
+ });
115
+
116
+ test("no operational evidence: word 'operational' buried without qualifying keyword fails", () => {
117
+ const content = `## Validation Report
118
+ The operational aspects were not evaluated in this round.`;
119
+ assert.ok(!hasOperationalEvidence(content));
120
+ });
121
+
122
+ test("no operational evidence: empty content fails", () => {
123
+ assert.ok(!hasOperationalEvidence(""));
124
+ });
@@ -0,0 +1,72 @@
1
+ // GSD — validation unit tests
2
+
3
+ import test from 'node:test';
4
+ import assert from 'node:assert/strict';
5
+
6
+ import { isNonEmptyString, validateStringArray } from '../validation.ts';
7
+
8
+ // ─── isNonEmptyString ────────────────────────────────────────────────────────
9
+
10
+ test('isNonEmptyString: "hello" returns true', () => {
11
+ assert.equal(isNonEmptyString('hello'), true);
12
+ });
13
+
14
+ test('isNonEmptyString: " " (whitespace only) returns false', () => {
15
+ assert.equal(isNonEmptyString(' '), false);
16
+ });
17
+
18
+ test('isNonEmptyString: "" (empty string) returns false', () => {
19
+ assert.equal(isNonEmptyString(''), false);
20
+ });
21
+
22
+ test('isNonEmptyString: null returns false', () => {
23
+ assert.equal(isNonEmptyString(null), false);
24
+ });
25
+
26
+ test('isNonEmptyString: undefined returns false', () => {
27
+ assert.equal(isNonEmptyString(undefined), false);
28
+ });
29
+
30
+ test('isNonEmptyString: 42 (number) returns false', () => {
31
+ assert.equal(isNonEmptyString(42), false);
32
+ });
33
+
34
+ // ─── validateStringArray ─────────────────────────────────────────────────────
35
+
36
+ test('validateStringArray: ["a", "b"] returns ["a", "b"]', () => {
37
+ assert.deepEqual(validateStringArray(['a', 'b'], 'items'), ['a', 'b']);
38
+ });
39
+
40
+ test('validateStringArray: [] (empty array) returns []', () => {
41
+ assert.deepEqual(validateStringArray([], 'items'), []);
42
+ });
43
+
44
+ test('validateStringArray: "not an array" throws with "must be an array"', () => {
45
+ assert.throws(
46
+ () => validateStringArray('not an array', 'items'),
47
+ (err: Error) => {
48
+ assert.ok(err.message.includes('must be an array'));
49
+ return true;
50
+ },
51
+ );
52
+ });
53
+
54
+ test('validateStringArray: ["a", 42] throws with "must contain only non-empty strings"', () => {
55
+ assert.throws(
56
+ () => validateStringArray(['a', 42], 'items'),
57
+ (err: Error) => {
58
+ assert.ok(err.message.includes('must contain only non-empty strings'));
59
+ return true;
60
+ },
61
+ );
62
+ });
63
+
64
+ test('validateStringArray: ["a", ""] throws with "must contain only non-empty strings"', () => {
65
+ assert.throws(
66
+ () => validateStringArray(['a', ''], 'items'),
67
+ (err: Error) => {
68
+ assert.ok(err.message.includes('must contain only non-empty strings'));
69
+ return true;
70
+ },
71
+ );
72
+ });