gsd-pi 2.51.0 → 2.52.0-dev.585e355

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 (399) hide show
  1. package/README.md +4 -4
  2. package/dist/headless-events.d.ts +18 -0
  3. package/dist/headless-events.js +36 -0
  4. package/dist/headless-types.d.ts +28 -0
  5. package/dist/headless-types.js +7 -0
  6. package/dist/headless.d.ts +8 -3
  7. package/dist/headless.js +47 -16
  8. package/dist/help-text.js +16 -5
  9. package/dist/onboarding.js +5 -4
  10. package/dist/remote-questions-config.js +1 -1
  11. package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
  12. package/dist/resources/extensions/async-jobs/job-manager.js +4 -1
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
  14. package/dist/resources/extensions/gsd/auto/phases.js +6 -0
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +18 -0
  16. package/dist/resources/extensions/gsd/auto-start.js +2 -0
  17. package/dist/resources/extensions/gsd/auto-timers.js +24 -2
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
  19. package/dist/resources/extensions/gsd/auto-worktree.js +21 -0
  20. package/dist/resources/extensions/gsd/auto.js +8 -4
  21. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +105 -70
  22. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  24. package/dist/resources/extensions/gsd/claude-import.js +60 -9
  25. package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
  26. package/dist/resources/extensions/gsd/commands-config.js +10 -5
  27. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +4 -4
  28. package/dist/resources/extensions/gsd/detection.js +6 -6
  29. package/dist/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  30. package/dist/resources/extensions/gsd/error-classifier.js +105 -0
  31. package/dist/resources/extensions/gsd/git-service.js +4 -3
  32. package/dist/resources/extensions/gsd/gitignore.js +7 -7
  33. package/dist/resources/extensions/gsd/gsd-db.js +298 -45
  34. package/dist/resources/extensions/gsd/init-wizard.js +2 -2
  35. package/dist/resources/extensions/gsd/key-manager.js +7 -16
  36. package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
  37. package/dist/resources/extensions/gsd/memory-store.js +28 -13
  38. package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
  39. package/dist/resources/extensions/gsd/preferences-models.js +1 -13
  40. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  41. package/dist/resources/extensions/gsd/preferences.js +13 -13
  42. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  43. package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
  44. package/dist/resources/extensions/gsd/rule-registry.js +1 -1
  45. package/dist/resources/extensions/gsd/service-tier.js +13 -2
  46. package/dist/resources/extensions/gsd/state.js +33 -19
  47. package/dist/resources/extensions/gsd/status-guards.js +12 -0
  48. package/dist/resources/extensions/gsd/tools/complete-milestone.js +7 -13
  49. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -20
  50. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -21
  51. package/dist/resources/extensions/gsd/tools/plan-milestone.js +28 -29
  52. package/dist/resources/extensions/gsd/tools/plan-slice.js +27 -26
  53. package/dist/resources/extensions/gsd/tools/plan-task.js +23 -23
  54. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +50 -41
  55. package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
  56. package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
  57. package/dist/resources/extensions/gsd/tools/replan-slice.js +51 -41
  58. package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
  59. package/dist/resources/extensions/gsd/validation.js +21 -0
  60. package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
  61. package/dist/resources/extensions/remote-questions/config.js +1 -1
  62. package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
  63. package/dist/resources/extensions/search-the-web/native-search.js +1 -1
  64. package/dist/resources/extensions/search-the-web/provider.js +1 -1
  65. package/dist/resources/extensions/shared/rtk.js +5 -3
  66. package/dist/rtk.js +3 -1
  67. package/dist/web/standalone/.next/BUILD_ID +1 -1
  68. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  69. package/dist/web/standalone/.next/build-manifest.json +4 -4
  70. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  71. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  72. package/dist/web/standalone/.next/required-server-files.json +3 -3
  73. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  74. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  76. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  84. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  93. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  100. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  112. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  140. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  146. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  160. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  162. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  164. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  166. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/index.html +1 -1
  176. package/dist/web/standalone/.next/server/app/index.rsc +5 -5
  177. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  178. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
  179. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  180. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
  181. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  182. package/dist/web/standalone/.next/server/app/page.js +2 -2
  183. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  185. package/dist/web/standalone/.next/server/chunks/2229.js +3 -3
  186. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  187. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/middleware.js +2 -2
  190. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  192. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  193. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  194. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  195. package/dist/web/standalone/.next/static/chunks/4024.21054f459af5cc78.js +9 -0
  196. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  197. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  198. package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +1 -0
  199. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  200. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  201. package/dist/web/standalone/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-024d82be84800e52.js} +1 -1
  202. package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
  203. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  204. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  205. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  206. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  207. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  208. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  209. package/dist/web/standalone/server.js +1 -1
  210. package/dist/wizard.js +4 -1
  211. package/package.json +2 -2
  212. package/packages/mcp-server/README.md +202 -0
  213. package/packages/mcp-server/package.json +36 -0
  214. package/packages/mcp-server/src/cli.ts +68 -0
  215. package/packages/mcp-server/src/index.ts +14 -0
  216. package/packages/mcp-server/src/mcp-server.test.ts +628 -0
  217. package/packages/mcp-server/src/server.ts +278 -0
  218. package/packages/mcp-server/src/session-manager.ts +328 -0
  219. package/packages/mcp-server/src/types.ts +107 -0
  220. package/packages/mcp-server/tsconfig.json +24 -0
  221. package/packages/pi-ai/dist/models.d.ts +14 -3
  222. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  223. package/packages/pi-ai/dist/models.js +53 -10
  224. package/packages/pi-ai/dist/models.js.map +1 -1
  225. package/packages/pi-ai/dist/models.test.js +102 -1
  226. package/packages/pi-ai/dist/models.test.js.map +1 -1
  227. package/packages/pi-ai/dist/types.d.ts +30 -0
  228. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  229. package/packages/pi-ai/dist/types.js.map +1 -1
  230. package/packages/pi-ai/src/models.test.ts +114 -1
  231. package/packages/pi-ai/src/models.ts +70 -13
  232. package/packages/pi-ai/src/types.ts +31 -0
  233. package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
  234. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  235. package/packages/pi-coding-agent/dist/cli/args.js +3 -0
  236. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  237. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
  238. package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
  239. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  240. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  241. package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
  242. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  243. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
  244. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
  245. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
  246. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
  247. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  248. package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
  249. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  250. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  251. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  252. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  253. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  254. package/packages/pi-coding-agent/dist/main.js +5 -3
  255. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  256. package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
  257. package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
  259. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  260. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
  261. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  262. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
  263. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  264. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
  265. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  266. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
  267. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  268. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
  269. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  270. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
  271. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
  272. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
  273. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
  274. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
  275. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  276. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  277. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  278. package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
  279. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  280. package/packages/pi-coding-agent/package.json +1 -1
  281. package/packages/pi-coding-agent/src/cli/args.ts +4 -0
  282. package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
  283. package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
  284. package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
  285. package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
  286. package/packages/pi-coding-agent/src/index.ts +3 -0
  287. package/packages/pi-coding-agent/src/main.ts +5 -3
  288. package/packages/pi-coding-agent/src/modes/index.ts +8 -1
  289. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
  290. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
  291. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
  292. package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
  293. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
  294. package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
  295. package/packages/rpc-client/package.json +20 -0
  296. package/pkg/package.json +1 -1
  297. package/scripts/ensure-workspace-builds.cjs +36 -8
  298. package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
  299. package/src/resources/extensions/async-jobs/job-manager.ts +4 -1
  300. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
  301. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
  302. package/src/resources/extensions/gsd/auto/phases.ts +6 -0
  303. package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
  304. package/src/resources/extensions/gsd/auto-start.ts +2 -0
  305. package/src/resources/extensions/gsd/auto-timers.ts +25 -1
  306. package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
  307. package/src/resources/extensions/gsd/auto-worktree.ts +21 -0
  308. package/src/resources/extensions/gsd/auto.ts +10 -4
  309. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -73
  310. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
  311. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  312. package/src/resources/extensions/gsd/claude-import.ts +58 -9
  313. package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
  314. package/src/resources/extensions/gsd/commands-config.ts +11 -5
  315. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -4
  316. package/src/resources/extensions/gsd/detection.ts +6 -6
  317. package/src/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  318. package/src/resources/extensions/gsd/error-classifier.ts +139 -0
  319. package/src/resources/extensions/gsd/git-service.ts +4 -3
  320. package/src/resources/extensions/gsd/gitignore.ts +7 -7
  321. package/src/resources/extensions/gsd/gsd-db.ts +355 -63
  322. package/src/resources/extensions/gsd/init-wizard.ts +2 -2
  323. package/src/resources/extensions/gsd/key-manager.ts +7 -16
  324. package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
  325. package/src/resources/extensions/gsd/memory-store.ts +29 -18
  326. package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
  327. package/src/resources/extensions/gsd/preferences-models.ts +1 -13
  328. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  329. package/src/resources/extensions/gsd/preferences.ts +12 -13
  330. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  331. package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
  332. package/src/resources/extensions/gsd/rule-registry.ts +1 -1
  333. package/src/resources/extensions/gsd/service-tier.ts +14 -2
  334. package/src/resources/extensions/gsd/state.ts +34 -20
  335. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  336. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +1 -1
  337. package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
  338. package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
  339. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
  340. package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
  341. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  342. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
  343. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  344. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
  345. package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
  346. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
  347. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
  348. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
  349. package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
  350. package/src/resources/extensions/gsd/tests/git-service.test.ts +37 -4
  351. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  352. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
  353. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
  354. package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
  355. package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
  356. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  357. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  358. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
  359. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
  360. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
  361. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -2
  362. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
  363. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
  364. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
  365. package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
  366. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
  367. package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
  368. package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
  369. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
  370. package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
  371. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
  372. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
  373. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -17
  374. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -24
  375. package/src/resources/extensions/gsd/tools/complete-task.ts +13 -25
  376. package/src/resources/extensions/gsd/tools/plan-milestone.ts +30 -32
  377. package/src/resources/extensions/gsd/tools/plan-slice.ts +30 -30
  378. package/src/resources/extensions/gsd/tools/plan-task.ts +26 -26
  379. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +57 -46
  380. package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
  381. package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
  382. package/src/resources/extensions/gsd/tools/replan-slice.ts +55 -44
  383. package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
  384. package/src/resources/extensions/gsd/validation.ts +23 -0
  385. package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
  386. package/src/resources/extensions/remote-questions/config.ts +1 -1
  387. package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
  388. package/src/resources/extensions/search-the-web/native-search.ts +1 -1
  389. package/src/resources/extensions/search-the-web/provider.ts +1 -1
  390. package/src/resources/extensions/shared/rtk.ts +12 -3
  391. package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
  392. package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
  393. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  394. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  395. package/dist/web/standalone/.next/static/css/de141508b083f922.css +0 -1
  396. /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
  397. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → KTe1kB5nPLQFIIFz2OcmI}/_buildManifest.js +0 -0
  398. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → KTe1kB5nPLQFIIFz2OcmI}/_ssgManifest.js +0 -0
  399. /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Provider error handling tests — consolidated from:
3
- * - provider-error-classify.test.ts (classifyProviderError)
3
+ * - provider-error-classify.test.ts (classifyError)
4
4
  * - network-error-fallback.test.ts (isTransientNetworkError, getNextFallbackModel)
5
5
  * - agent-end-provider-error.test.ts (pauseAutoForProviderError)
6
6
  */
@@ -10,102 +10,111 @@ import assert from "node:assert/strict";
10
10
  import { readFileSync } from "node:fs";
11
11
  import { join, dirname } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
- import { classifyProviderError, pauseAutoForProviderError } from "../provider-error-pause.ts";
14
- import { getNextFallbackModel, isTransientNetworkError } from "../preferences.ts";
13
+ import { classifyError, isTransient, isTransientNetworkError } from "../error-classifier.ts";
14
+ import { pauseAutoForProviderError } from "../provider-error-pause.ts";
15
+ import { getNextFallbackModel } from "../preferences.ts";
15
16
 
16
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
18
 
18
- // ── classifyProviderError ────────────────────────────────────────────────────
19
+ // ── classifyError ────────────────────────────────────────────────────────────
19
20
 
20
- test("classifyProviderError detects rate limit from 429", () => {
21
- const result = classifyProviderError("HTTP 429 Too Many Requests");
22
- assert.ok(result.isTransient);
23
- assert.ok(result.isRateLimit);
24
- assert.ok(result.suggestedDelayMs > 0);
21
+ test("classifyError detects rate limit from 429", () => {
22
+ const result = classifyError("HTTP 429 Too Many Requests");
23
+ assert.ok(isTransient(result));
24
+ assert.equal(result.kind, "rate-limit");
25
+ assert.ok("retryAfterMs" in result && result.retryAfterMs > 0);
25
26
  });
26
27
 
27
- test("classifyProviderError detects rate limit from message", () => {
28
- const result = classifyProviderError("rate limit exceeded");
29
- assert.ok(result.isTransient);
30
- assert.ok(result.isRateLimit);
28
+ test("classifyError detects rate limit from message", () => {
29
+ const result = classifyError("rate limit exceeded");
30
+ assert.ok(isTransient(result));
31
+ assert.equal(result.kind, "rate-limit");
31
32
  });
32
33
 
33
- test("classifyProviderError extracts reset delay from message", () => {
34
- const result = classifyProviderError("rate limit exceeded, reset in 45s");
35
- assert.ok(result.isRateLimit);
36
- assert.equal(result.suggestedDelayMs, 45000);
34
+ test("classifyError extracts reset delay from message", () => {
35
+ const result = classifyError("rate limit exceeded, reset in 45s");
36
+ assert.equal(result.kind, "rate-limit");
37
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 45000);
37
38
  });
38
39
 
39
- test("classifyProviderError defaults to 60s for rate limit without reset", () => {
40
- const result = classifyProviderError("429 too many requests");
41
- assert.ok(result.isRateLimit);
42
- assert.equal(result.suggestedDelayMs, 60_000);
40
+ test("classifyError defaults to 60s for rate limit without reset", () => {
41
+ const result = classifyError("429 too many requests");
42
+ assert.equal(result.kind, "rate-limit");
43
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 60_000);
43
44
  });
44
45
 
45
- test("classifyProviderError detects Anthropic internal server error", () => {
46
+ test("classifyError treats stream_exhausted_without_result as transient connection failure", () => {
47
+ const result = classifyError("stream_exhausted_without_result");
48
+ assert.ok(isTransient(result));
49
+ assert.equal(result.kind, "connection");
50
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 15_000);
51
+ });
52
+
53
+ test("classifyError detects Anthropic internal server error", () => {
46
54
  const msg = '{"type":"error","error":{"details":null,"type":"api_error","message":"Internal server error"}}';
47
- const result = classifyProviderError(msg);
48
- assert.ok(result.isTransient);
49
- assert.ok(!result.isRateLimit);
50
- assert.equal(result.suggestedDelayMs, 30_000);
55
+ const result = classifyError(msg);
56
+ assert.ok(isTransient(result));
57
+ assert.equal(result.kind, "server");
58
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 30_000);
51
59
  });
52
60
 
53
- test("classifyProviderError detects Codex server_error from extracted message", () => {
61
+ test("classifyError detects Codex server_error from extracted message", () => {
54
62
  // After fix, mapCodexEvents extracts the nested error type and produces
55
63
  // "Codex server_error: <message>" instead of raw JSON.
56
64
  const msg = "Codex server_error: An error occurred while processing your request.";
57
- const result = classifyProviderError(msg);
58
- assert.ok(result.isTransient);
59
- assert.ok(!result.isRateLimit);
60
- assert.equal(result.suggestedDelayMs, 30_000);
65
+ const result = classifyError(msg);
66
+ assert.ok(isTransient(result));
67
+ assert.equal(result.kind, "server");
68
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 30_000);
61
69
  });
62
70
 
63
- test("classifyProviderError detects overloaded error", () => {
64
- const result = classifyProviderError("overloaded_error: Overloaded");
65
- assert.ok(result.isTransient);
66
- assert.equal(result.suggestedDelayMs, 30_000);
71
+ test("classifyError detects overloaded error", () => {
72
+ const result = classifyError("overloaded_error: Overloaded");
73
+ assert.ok(isTransient(result));
74
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 30_000);
67
75
  });
68
76
 
69
- test("classifyProviderError detects 503 service unavailable", () => {
70
- const result = classifyProviderError("HTTP 503 Service Unavailable");
71
- assert.ok(result.isTransient);
77
+ test("classifyError detects 503 service unavailable", () => {
78
+ const result = classifyError("HTTP 503 Service Unavailable");
79
+ assert.ok(isTransient(result));
72
80
  });
73
81
 
74
- test("classifyProviderError detects 502 bad gateway", () => {
75
- const result = classifyProviderError("HTTP 502 Bad Gateway");
76
- assert.ok(result.isTransient);
82
+ test("classifyError detects 502 bad gateway", () => {
83
+ const result = classifyError("HTTP 502 Bad Gateway");
84
+ assert.ok(isTransient(result));
77
85
  });
78
86
 
79
- test("classifyProviderError detects auth error as permanent", () => {
80
- const result = classifyProviderError("unauthorized: invalid API key");
81
- assert.ok(!result.isTransient);
82
- assert.ok(!result.isRateLimit);
87
+ test("classifyError detects auth error as permanent", () => {
88
+ const result = classifyError("unauthorized: invalid API key");
89
+ assert.ok(!isTransient(result));
90
+ assert.equal(result.kind, "permanent");
83
91
  });
84
92
 
85
- test("classifyProviderError detects billing error as permanent", () => {
86
- const result = classifyProviderError("billing issue: payment required");
87
- assert.ok(!result.isTransient);
93
+ test("classifyError detects billing error as permanent", () => {
94
+ const result = classifyError("billing issue: payment required");
95
+ assert.ok(!isTransient(result));
88
96
  });
89
97
 
90
- test("classifyProviderError detects quota exceeded as permanent", () => {
91
- const result = classifyProviderError("quota exceeded for this month");
92
- assert.ok(!result.isTransient);
98
+ test("classifyError detects quota exceeded as permanent", () => {
99
+ const result = classifyError("quota exceeded for this month");
100
+ assert.ok(!isTransient(result));
93
101
  });
94
102
 
95
- test("classifyProviderError treats unknown error as permanent", () => {
96
- const result = classifyProviderError("something went wrong");
97
- assert.ok(!result.isTransient);
103
+ test("classifyError treats unknown error as not transient", () => {
104
+ const result = classifyError("something went wrong");
105
+ assert.ok(!isTransient(result));
106
+ assert.equal(result.kind, "unknown");
98
107
  });
99
108
 
100
- test("classifyProviderError treats empty string as permanent", () => {
101
- const result = classifyProviderError("");
102
- assert.ok(!result.isTransient);
109
+ test("classifyError treats empty string as not transient", () => {
110
+ const result = classifyError("");
111
+ assert.ok(!isTransient(result));
103
112
  });
104
113
 
105
- test("classifyProviderError: rate limit takes precedence over auth keywords", () => {
106
- const result = classifyProviderError("429 unauthorized rate limit");
107
- assert.ok(result.isRateLimit);
108
- assert.ok(result.isTransient);
114
+ test("classifyError: rate limit takes precedence over auth keywords", () => {
115
+ const result = classifyError("429 unauthorized rate limit");
116
+ assert.equal(result.kind, "rate-limit");
117
+ assert.ok(isTransient(result));
109
118
  });
110
119
 
111
120
  // ── isTransientNetworkError ──────────────────────────────────────────────────
@@ -265,8 +274,8 @@ test("agent-end-recovery.ts tracks consecutive transient errors for escalating b
265
274
  const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
266
275
 
267
276
  assert.ok(
268
- src.includes("consecutiveTransientErrors"),
269
- "agent-end-recovery.ts must track consecutiveTransientErrors for escalating backoff (#1166)",
277
+ src.includes("consecutiveTransientCount"),
278
+ "agent-end-recovery.ts must track consecutiveTransientCount for escalating backoff (#1166)",
270
279
  );
271
280
  assert.ok(
272
281
  src.includes("MAX_TRANSIENT_AUTO_RESUMES"),
@@ -274,15 +283,13 @@ test("agent-end-recovery.ts tracks consecutive transient errors for escalating b
274
283
  );
275
284
  });
276
285
 
277
- test("agent-end-recovery.ts resets consecutive transient error counter on success", () => {
286
+ test("agent-end-recovery.ts resets retry state before resolveAgentEnd on success", () => {
278
287
  const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
279
288
 
280
- // After successful agent_end (before resolveAgentEnd), the counter must be reset.
281
- // Use a regex across the success block so CRLF checkouts on Windows do not
282
- // push the reset line outside a fixed substring window.
289
+ // After successful agent_end, resetRetryState must be called before resolveAgentEnd.
283
290
  assert.ok(
284
- /consecutiveTransientErrors\s*=\s*0\s*;[\s\S]{0,250}resolveAgentEnd/.test(src),
285
- "consecutive transient error counter must be reset before resolveAgentEnd on the success path (#1166)",
291
+ /resetRetryState[\s\S]{0,250}resolveAgentEnd/.test(src),
292
+ "resetRetryState must be called before resolveAgentEnd on the success path (#1166)",
286
293
  );
287
294
  });
288
295
 
@@ -291,7 +298,7 @@ test("agent-end-recovery.ts applies escalating delay for repeated transient erro
291
298
 
292
299
  // Must contain the exponential backoff formula (may span multiple lines)
293
300
  assert.ok(
294
- src.includes("2 ** Math.max(0, consecutiveTransientErrors"),
301
+ src.includes("2 ** Math.max(0, retryState.consecutiveTransientCount"),
295
302
  "agent-end-recovery.ts must escalate retryAfterMs exponentially for consecutive transient errors (#1166)",
296
303
  );
297
304
  });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Regression test for #2675: completing-milestone dispatch rule must
3
+ * block completion when VALIDATION verdict is "needs-remediation".
4
+ *
5
+ * Without this guard, needs-remediation + allSlicesDone causes a loop:
6
+ * complete-milestone dispatched → agent refuses (correct) → no SUMMARY
7
+ * → re-dispatch → repeat until stuck detection fires.
8
+ */
9
+ import { test } from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { tmpdir } from "node:os";
14
+
15
+ import { DISPATCH_RULES } from "../auto-dispatch.ts";
16
+
17
+ /** Find the completing-milestone dispatch rule */
18
+ const completingRule = DISPATCH_RULES.find(r => r.name === "completing-milestone → complete-milestone");
19
+
20
+ test("completing-milestone dispatch rule exists", () => {
21
+ assert.ok(completingRule, "rule should exist in DISPATCH_RULES");
22
+ });
23
+
24
+ test("completing-milestone blocks when VALIDATION verdict is needs-remediation (#2675)", async () => {
25
+ const base = mkdtempSync(join(tmpdir(), "gsd-remediation-"));
26
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
27
+
28
+ try {
29
+ // Write a VALIDATION file with needs-remediation verdict
30
+ writeFileSync(
31
+ join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md"),
32
+ [
33
+ "---",
34
+ "verdict: needs-remediation",
35
+ "remediation_round: 0",
36
+ "---",
37
+ "",
38
+ "# Validation Report",
39
+ "",
40
+ "3 success criteria failed. Remediation required.",
41
+ ].join("\n"),
42
+ );
43
+
44
+ const ctx = {
45
+ mid: "M001",
46
+ midTitle: "Test Milestone",
47
+ basePath: base,
48
+ state: { phase: "completing-milestone" } as any,
49
+ prefs: {} as any,
50
+ session: undefined,
51
+ };
52
+
53
+ const result = await completingRule!.match(ctx);
54
+
55
+ assert.ok(result !== null, "rule should match");
56
+ assert.equal(result!.action, "stop", "should return stop action");
57
+ if (result!.action === "stop") {
58
+ assert.equal(result!.level, "warning", "should be warning level (pausable)");
59
+ assert.ok(
60
+ result!.reason.includes("needs-remediation"),
61
+ "reason should mention needs-remediation",
62
+ );
63
+ }
64
+ } finally {
65
+ rmSync(base, { recursive: true, force: true });
66
+ }
67
+ });
68
+
69
+ test("completing-milestone proceeds normally when VALIDATION verdict is pass (#2675 guard)", async () => {
70
+ const base = mkdtempSync(join(tmpdir(), "gsd-remediation-"));
71
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
72
+
73
+ try {
74
+ // Write a VALIDATION file with pass verdict
75
+ writeFileSync(
76
+ join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md"),
77
+ [
78
+ "---",
79
+ "verdict: pass",
80
+ "---",
81
+ "",
82
+ "# Validation Report",
83
+ "",
84
+ "All criteria met.",
85
+ ].join("\n"),
86
+ );
87
+
88
+ const ctx = {
89
+ mid: "M001",
90
+ midTitle: "Test Milestone",
91
+ basePath: base,
92
+ state: { phase: "completing-milestone" } as any,
93
+ prefs: {} as any,
94
+ session: undefined,
95
+ };
96
+
97
+ const result = await completingRule!.match(ctx);
98
+
99
+ // Should NOT return a stop — should either dispatch or return stop for
100
+ // a different reason (e.g. missing SUMMARY files, no implementation)
101
+ if (result && result.action === "stop") {
102
+ assert.ok(
103
+ !result.reason.includes("needs-remediation"),
104
+ "pass verdict should NOT trigger the remediation guard",
105
+ );
106
+ }
107
+ } finally {
108
+ rmSync(base, { recursive: true, force: true });
109
+ }
110
+ });
@@ -724,3 +724,32 @@ test("resolveRemoteConfig returns null when preferences are absent (no env side-
724
724
  if (savedTelegram !== undefined) process.env.TELEGRAM_BOT_TOKEN = savedTelegram;
725
725
  }
726
726
  });
727
+
728
+ test("config source-level: hydration skips api_key entries with empty keys", () => {
729
+ const configSrc = readFileSync(
730
+ join(__dirname, "..", "..", "remote-questions", "config.ts"),
731
+ "utf-8",
732
+ );
733
+ // The find() call in hydrateRemoteTokensFromAuth must filter for non-empty keys,
734
+ // not just match on type === "api_key". This prevents stale empty-key entries
735
+ // (left by removeProviderToken) from shadowing valid tokens.
736
+ assert.ok(
737
+ configSrc.includes('c.type === "api_key" && !!c.key'),
738
+ "hydrateRemoteTokensFromAuth find() should require a non-empty key",
739
+ );
740
+ });
741
+
742
+ test("config source-level: removeProviderToken uses auth.remove not auth.set with empty key", () => {
743
+ const commandSrc = readFileSync(
744
+ join(__dirname, "..", "..", "remote-questions", "remote-command.ts"),
745
+ "utf-8",
746
+ );
747
+ // removeProviderToken should call auth.remove(provider), not auth.set(provider, { key: "" }).
748
+ // Setting an empty key pollutes the credentials array and shadows valid tokens.
749
+ const fnStart = commandSrc.indexOf("function removeProviderToken");
750
+ assert.ok(fnStart !== -1, "removeProviderToken should exist");
751
+ const fnEnd = commandSrc.indexOf("\n}", fnStart);
752
+ const fnBody = commandSrc.slice(fnStart, fnEnd);
753
+ assert.ok(fnBody.includes("auth.remove("), "removeProviderToken should call auth.remove()");
754
+ assert.ok(!fnBody.includes('key: ""'), "removeProviderToken should not set an empty key");
755
+ });
@@ -0,0 +1,30 @@
1
+ // GSD — status-guards unit tests
2
+
3
+ import test from 'node:test';
4
+ import assert from 'node:assert/strict';
5
+
6
+ import { isClosedStatus } from '../status-guards.ts';
7
+
8
+ test('isClosedStatus: "complete" returns true', () => {
9
+ assert.equal(isClosedStatus('complete'), true);
10
+ });
11
+
12
+ test('isClosedStatus: "done" returns true', () => {
13
+ assert.equal(isClosedStatus('done'), true);
14
+ });
15
+
16
+ test('isClosedStatus: "pending" returns false', () => {
17
+ assert.equal(isClosedStatus('pending'), false);
18
+ });
19
+
20
+ test('isClosedStatus: "in_progress" returns false', () => {
21
+ assert.equal(isClosedStatus('in_progress'), false);
22
+ });
23
+
24
+ test('isClosedStatus: "active" returns false', () => {
25
+ assert.equal(isClosedStatus('active'), false);
26
+ });
27
+
28
+ test('isClosedStatus: "" (empty string) returns false', () => {
29
+ assert.equal(isClosedStatus(''), false);
30
+ });
@@ -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
+ });