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,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,90 @@
1
+ /**
2
+ * rate-limit-model-fallback.test.ts — Regression test for #2770.
3
+ *
4
+ * Rate-limit errors enter the model fallback path before falling through
5
+ * to pause. This verifies the structural contract in agent-end-recovery.ts.
6
+ */
7
+
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { readFileSync } from "node:fs";
11
+ import { join, dirname } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const RECOVERY_PATH = join(__dirname, "..", "bootstrap", "agent-end-recovery.ts");
16
+
17
+ function getRecoverySource(): string {
18
+ return readFileSync(RECOVERY_PATH, "utf-8");
19
+ }
20
+
21
+ // ── Rate-limit errors attempt model fallback (#2770) ─────────────────────────
22
+
23
+ test("rate-limit errors enter the model fallback branch alongside other transient errors", () => {
24
+ const src = getRecoverySource();
25
+
26
+ // The condition that gates model fallback must include rate-limit.
27
+ // Match the if-condition that contains both "rate-limit" and fallback-related kinds.
28
+ const fallbackConditionRe = /if\s*\([^)]*cls\.kind\s*===\s*"rate-limit"[^)]*cls\.kind\s*===\s*"network"/;
29
+ const fallbackConditionReAlt = /if\s*\([^)]*cls\.kind\s*===\s*"network"[^)]*cls\.kind\s*===\s*"rate-limit"/;
30
+
31
+ assert.ok(
32
+ fallbackConditionRe.test(src) || fallbackConditionReAlt.test(src),
33
+ 'rate-limit must appear in the same if-condition as network/server for model fallback (#2770)',
34
+ );
35
+ });
36
+
37
+ test("rate-limit errors are NOT short-circuited to pause before model fallback", () => {
38
+ const src = getRecoverySource();
39
+
40
+ // The old code had a dedicated rate-limit early-return block before the fallback block.
41
+ // Verify it no longer exists.
42
+ const earlyRateLimitPause = /if\s*\(\s*cls\.kind\s*===\s*"rate-limit"\s*\)\s*\{[^}]*pauseTransientWithBackoff/;
43
+ assert.ok(
44
+ !earlyRateLimitPause.test(src),
45
+ 'rate-limit must NOT have a dedicated early pause before the model fallback path (#2770)',
46
+ );
47
+ });
48
+
49
+ test("rate-limit errors fall through to pause if no fallback model is available", () => {
50
+ const src = getRecoverySource();
51
+
52
+ // After the fallback block, the transient fallback pause must still fire for rate-limit.
53
+ // The isTransient check covers rate-limit (verified by error-classifier tests).
54
+ // Verify pauseTransientWithBackoff is called with isRateLimit derived from cls.kind.
55
+ assert.ok(
56
+ src.includes('cls.kind === "rate-limit"'),
57
+ 'agent-end-recovery.ts must reference cls.kind === "rate-limit" for fallback and pause paths (#2770)',
58
+ );
59
+
60
+ // The transient fallback pause must pass the isRateLimit flag correctly.
61
+ const pauseCallRe = /pauseTransientWithBackoff\([^)]*cls\.kind\s*===\s*"rate-limit"/;
62
+ assert.ok(
63
+ pauseCallRe.test(src),
64
+ 'pauseTransientWithBackoff must receive isRateLimit based on cls.kind === "rate-limit" (#2770)',
65
+ );
66
+ });
67
+
68
+ test("other transient errors (server, connection, stream) still attempt model fallback", () => {
69
+ const src = getRecoverySource();
70
+
71
+ // All transient kinds must appear in the fallback condition.
72
+ for (const kind of ["server", "connection", "stream"]) {
73
+ assert.ok(
74
+ src.includes(`cls.kind === "${kind}"`),
75
+ `model fallback condition must include cls.kind === "${kind}"`,
76
+ );
77
+ }
78
+ });
79
+
80
+ test("permanent errors still bypass model fallback and pause indefinitely", () => {
81
+ const src = getRecoverySource();
82
+
83
+ // The permanent/unknown error handler must exist and call pauseAutoForProviderError
84
+ // with isTransient: false.
85
+ const permanentPauseRe = /pauseAutoForProviderError[\s\S]{0,300}isTransient:\s*false/;
86
+ assert.ok(
87
+ permanentPauseRe.test(src),
88
+ 'permanent errors must pause with isTransient: false (no auto-resume)',
89
+ );
90
+ });
@@ -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
+ });
@@ -95,13 +95,13 @@ async function main(): Promise<void> {
95
95
  writeFileSync(lockFile, JSON.stringify(lockData, null, 2));
96
96
 
97
97
  // Simulate transient unavailability: move file away, spawn a child process
98
- // to restore it after 100ms. The child runs outside our event loop so it
99
- // fires even during busy-wait retries.
98
+ // to restore it shortly after. The child runs outside our event loop so it
99
+ // fires even during busy-wait retries. Give the test extra retry budget so
100
+ // it stays stable under full-suite CPU contention.
100
101
  renameSync(lockFile, tmpFile);
101
- spawn('bash', ['-c', `sleep 0.1 && mv "${tmpFile}" "${lockFile}"`], { stdio: 'ignore', detached: true }).unref();
102
+ spawn('bash', ['-c', `sleep 0.05 && mv "${tmpFile}" "${lockFile}"`], { stdio: 'ignore', detached: true }).unref();
102
103
 
103
- // With retries (3 attempts, 200ms delay), it should recover on 2nd or 3rd attempt
104
- const result = readExistingLockDataWithRetry(lockFile, { maxAttempts: 3, delayMs: 200 });
104
+ const result = readExistingLockDataWithRetry(lockFile, { maxAttempts: 8, delayMs: 400 });
105
105
  assertTrue(result !== null, 'data recovered after transient unavailability');
106
106
  if (result) {
107
107
  assertEq(result.pid, process.pid, 'correct PID after recovery');
@@ -131,11 +131,12 @@ async function main(): Promise<void> {
131
131
  writeFileSync(lockFile, JSON.stringify(lockData, null, 2));
132
132
 
133
133
  // Remove read permission to simulate NFS/CIFS latency, then spawn a child
134
- // to restore permissions after 100ms (runs outside our event loop).
134
+ // to restore permissions shortly after (runs outside our event loop).
135
+ // Use the same wider retry window as the rename case for full-suite stability.
135
136
  chmodSync(lockFile, 0o000);
136
- spawn('bash', ['-c', `sleep 0.1 && chmod 644 "${lockFile}"`], { stdio: 'ignore', detached: true }).unref();
137
+ spawn('bash', ['-c', `sleep 0.05 && chmod 644 "${lockFile}"`], { stdio: 'ignore', detached: true }).unref();
137
138
 
138
- const result = readExistingLockDataWithRetry(lockFile, { maxAttempts: 3, delayMs: 200 });
139
+ const result = readExistingLockDataWithRetry(lockFile, { maxAttempts: 8, delayMs: 400 });
139
140
  assertTrue(result !== null, 'data recovered after transient permission error');
140
141
  if (result) {
141
142
  assertEq(result.pid, process.pid, 'correct PID after permission recovery');
@@ -0,0 +1,125 @@
1
+ /**
2
+ * stash-pop-gsd-conflict.test.ts — Regression test for #2766.
3
+ *
4
+ * When a squash merge stash-pops and hits conflicts on .gsd/ state files,
5
+ * the UU entries block every subsequent merge. This test verifies that
6
+ * mergeMilestoneToMain auto-resolves .gsd/ conflicts by accepting HEAD
7
+ * and drops the stash, leaving the repo in a clean state.
8
+ */
9
+
10
+ import test from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, realpathSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+ import { execSync } from "node:child_process";
16
+
17
+ import { createAutoWorktree, mergeMilestoneToMain } from "../auto-worktree.ts";
18
+
19
+ function run(cmd: string, cwd: string): string {
20
+ return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
21
+ }
22
+
23
+ function createTempRepo(): string {
24
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "wt-stashpop-test-")));
25
+ run("git init", dir);
26
+ run("git config user.email test@test.com", dir);
27
+ run("git config user.name Test", dir);
28
+ writeFileSync(join(dir, "README.md"), "# test\n");
29
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
30
+ writeFileSync(join(dir, ".gsd", "STATE.md"), "version: 1\n");
31
+ run("git add .", dir);
32
+ run("git commit -m init", dir);
33
+ run("git branch -M main", dir);
34
+ return dir;
35
+ }
36
+
37
+ function makeRoadmap(milestoneId: string, title: string, slices: Array<{ id: string; title: string }>): string {
38
+ const sliceLines = slices.map(s => `- [x] **${s.id}: ${s.title}**`).join("\n");
39
+ return `# ${milestoneId}: ${title}\n\n## Slices\n${sliceLines}\n`;
40
+ }
41
+
42
+ test("#2766: stash pop conflict on .gsd/ files is auto-resolved", () => {
43
+ const repo = createTempRepo();
44
+ try {
45
+ const wtPath = createAutoWorktree(repo, "M300");
46
+
47
+ // Add a slice with real code on the milestone branch
48
+ const normalizedPath = wtPath.replaceAll("\\", "/");
49
+ const worktreeName = normalizedPath.split("/").pop() || "M300";
50
+ const sliceBranch = `slice/${worktreeName}/S01`;
51
+ run(`git checkout -b "${sliceBranch}"`, wtPath);
52
+ writeFileSync(join(wtPath, "feature.ts"), "export const feature = true;\n");
53
+
54
+ // Modify .gsd/STATE.md on the milestone branch (diverges from main)
55
+ writeFileSync(join(wtPath, ".gsd", "STATE.md"), "version: 2-milestone\n");
56
+ run("git add .", wtPath);
57
+ run('git commit -m "add feature and update state"', wtPath);
58
+ run("git checkout milestone/M300", wtPath);
59
+ run(`git merge --no-ff "${sliceBranch}" -m "merge S01: feature"`, wtPath);
60
+
61
+ // Dirty .gsd/STATE.md in the main repo (stash will conflict on pop)
62
+ writeFileSync(join(repo, ".gsd", "STATE.md"), "version: 2-main-dirty\n");
63
+
64
+ const roadmap = makeRoadmap("M300", "Stash pop conflict test", [
65
+ { id: "S01", title: "Feature" },
66
+ ]);
67
+
68
+ // mergeMilestoneToMain should succeed — .gsd/ conflict auto-resolved
69
+ const result = mergeMilestoneToMain(repo, "M300", roadmap);
70
+ assert.ok(
71
+ result.commitMessage.includes("GSD-Milestone: M300"),
72
+ "merge succeeds despite stash pop conflict on .gsd/ file",
73
+ );
74
+ assert.ok(existsSync(join(repo, "feature.ts")), "milestone code merged to main");
75
+
76
+ // Verify repo is clean (no UU entries blocking future merges)
77
+ const status = run("git status --porcelain", repo);
78
+ assert.ok(
79
+ !status.includes("UU "),
80
+ "no unmerged (UU) entries remain after stash pop conflict resolution",
81
+ );
82
+
83
+ // Stash should be dropped (no remaining stash entries)
84
+ let stashList = "";
85
+ try { stashList = run("git stash list", repo); } catch { /* empty stash */ }
86
+ assert.strictEqual(stashList, "", "stash is empty after .gsd/ conflict auto-resolution");
87
+ } finally {
88
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* cleanup best-effort */ }
89
+ }
90
+ });
91
+
92
+ test("#2766: stash pop conflict on non-.gsd files preserves stash for manual resolution", () => {
93
+ const repo = createTempRepo();
94
+ try {
95
+ const wtPath = createAutoWorktree(repo, "M301");
96
+
97
+ // Add a slice that modifies a file also dirty on main
98
+ const normalizedPath = wtPath.replaceAll("\\", "/");
99
+ const worktreeName = normalizedPath.split("/").pop() || "M301";
100
+ const sliceBranch = `slice/${worktreeName}/S01`;
101
+ run(`git checkout -b "${sliceBranch}"`, wtPath);
102
+ writeFileSync(join(wtPath, "README.md"), "# milestone version\n");
103
+ run("git add .", wtPath);
104
+ run('git commit -m "update readme"', wtPath);
105
+ run("git checkout milestone/M301", wtPath);
106
+ run(`git merge --no-ff "${sliceBranch}" -m "merge S01: readme"`, wtPath);
107
+
108
+ // Dirty README.md in the main repo — this will conflict on stash pop
109
+ // and is NOT a .gsd/ file, so it should be left for manual resolution
110
+ writeFileSync(join(repo, "README.md"), "# locally modified\n");
111
+
112
+ const roadmap = makeRoadmap("M301", "Non-gsd stash conflict", [
113
+ { id: "S01", title: "Readme update" },
114
+ ]);
115
+
116
+ // The merge itself should still succeed (stash pop conflict is non-fatal)
117
+ const result = mergeMilestoneToMain(repo, "M301", roadmap);
118
+ assert.ok(
119
+ result.commitMessage.includes("GSD-Milestone: M301"),
120
+ "merge succeeds even with non-.gsd stash pop conflict",
121
+ );
122
+ } finally {
123
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* cleanup best-effort */ }
124
+ }
125
+ });
@@ -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
+ });