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
@@ -71,6 +71,9 @@ export function getActiveMemoriesRanked(limit = 30) {
71
71
  /**
72
72
  * Generate the next memory ID: MEM + zero-padded 3-digit from MAX(seq).
73
73
  * Returns MEM001 if no memories exist.
74
+ *
75
+ * NOTE: For race-safe creation, prefer createMemory() which inserts with a
76
+ * placeholder ID then updates to the seq-derived ID atomically.
74
77
  */
75
78
  export function nextMemoryId() {
76
79
  if (!isDbAvailable())
@@ -94,7 +97,9 @@ export function nextMemoryId() {
94
97
  }
95
98
  // ─── Mutation Functions ─────────────────────────────────────────────────────
96
99
  /**
97
- * Insert a new memory with auto-assigned ID.
100
+ * Insert a new memory with a race-safe auto-assigned ID.
101
+ * Uses AUTOINCREMENT seq to derive the ID after insert, avoiding
102
+ * the read-then-write race in concurrent scenarios (e.g. worktrees).
98
103
  * Returns the assigned ID, or null on failure.
99
104
  */
100
105
  export function createMemory(fields) {
@@ -104,11 +109,12 @@ export function createMemory(fields) {
104
109
  if (!adapter)
105
110
  return null;
106
111
  try {
107
- const id = nextMemoryId();
108
112
  const now = new Date().toISOString();
113
+ // Insert with a temporary placeholder ID — seq is auto-assigned
114
+ const placeholder = `_TMP_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
109
115
  adapter.prepare(`INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at)
110
116
  VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at)`).run({
111
- ':id': id,
117
+ ':id': placeholder,
112
118
  ':category': fields.category,
113
119
  ':content': fields.content,
114
120
  ':confidence': fields.confidence ?? 0.8,
@@ -117,7 +123,17 @@ export function createMemory(fields) {
117
123
  ':created_at': now,
118
124
  ':updated_at': now,
119
125
  });
120
- return id;
126
+ // Derive the real ID from the assigned seq
127
+ const row = adapter.prepare('SELECT seq FROM memories WHERE id = :id').get({ ':id': placeholder });
128
+ if (!row)
129
+ return placeholder; // fallback — should not happen
130
+ const seq = row['seq'];
131
+ const realId = `MEM${String(seq).padStart(3, '0')}`;
132
+ adapter.prepare('UPDATE memories SET id = :real_id WHERE id = :placeholder').run({
133
+ ':real_id': realId,
134
+ ':placeholder': placeholder,
135
+ });
136
+ return realId;
121
137
  }
122
138
  catch {
123
139
  return null;
@@ -258,15 +274,14 @@ export function enforceMemoryCap(max = 50) {
258
274
  if (count <= max)
259
275
  return;
260
276
  const excess = count - max;
261
- // Find the IDs of the lowest-ranked active memories
262
- const rows = adapter.prepare(`SELECT id FROM memories
263
- WHERE superseded_by IS NULL
264
- ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
265
- LIMIT :limit`).all({ ':limit': excess });
266
- const now = new Date().toISOString();
267
- for (const row of rows) {
268
- adapter.prepare('UPDATE memories SET superseded_by = :reason, updated_at = :now WHERE id = :id').run({ ':reason': 'CAP_EXCEEDED', ':now': now, ':id': row['id'] });
269
- }
277
+ // Batch update: supersede lowest-ranked active memories in a single statement
278
+ adapter.prepare(`UPDATE memories SET superseded_by = 'CAP_EXCEEDED', updated_at = :now
279
+ WHERE id IN (
280
+ SELECT id FROM memories
281
+ WHERE superseded_by IS NULL
282
+ ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
283
+ LIMIT :limit
284
+ )`).run({ ':now': new Date().toISOString(), ':limit': excess });
270
285
  }
271
286
  catch {
272
287
  // non-fatal
@@ -15,6 +15,7 @@ import { join } from "node:path";
15
15
  import { resolveMilestonePath, resolveMilestoneFile, buildMilestoneFileName, } from "./paths.js";
16
16
  import { invalidateAllCaches } from "./cache.js";
17
17
  import { loadQueueOrder, saveQueueOrder } from "./queue-order.js";
18
+ import { isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
18
19
  // ─── Park ──────────────────────────────────────────────────────────────────
19
20
  /**
20
21
  * Park a milestone — creates a PARKED.md marker file with reason and timestamp.
@@ -44,6 +45,15 @@ export function parkMilestone(basePath, milestoneId, reason) {
44
45
  "",
45
46
  ].join("\n");
46
47
  writeFileSync(parkedPath, content, "utf-8");
48
+ // Sync DB status so deriveStateFromDb also skips this milestone (#2694)
49
+ if (isDbAvailable()) {
50
+ try {
51
+ updateMilestoneStatus(milestoneId, "parked");
52
+ }
53
+ catch (err) {
54
+ process.stderr.write(`gsd: parkMilestone DB sync failed for ${milestoneId}: ${err.message}\n`);
55
+ }
56
+ }
47
57
  invalidateAllCaches();
48
58
  return true;
49
59
  }
@@ -60,6 +70,15 @@ export function unparkMilestone(basePath, milestoneId) {
60
70
  if (!existsSync(parkedPath))
61
71
  return false; // not parked
62
72
  unlinkSync(parkedPath);
73
+ // Sync DB status so deriveStateFromDb picks up the unparked milestone (#2694)
74
+ if (isDbAvailable()) {
75
+ try {
76
+ updateMilestoneStatus(milestoneId, "active");
77
+ }
78
+ catch (err) {
79
+ process.stderr.write(`gsd: unparkMilestone DB sync failed for ${milestoneId}: ${err.message}\n`);
80
+ }
81
+ }
63
82
  invalidateAllCaches();
64
83
  return true;
65
84
  }
@@ -98,18 +98,6 @@ export function getNextFallbackModel(currentModelId, modelConfig) {
98
98
  return modelsToTry[0];
99
99
  }
100
100
  }
101
- /**
102
- * Detect whether an error message indicates a transient network error
103
- * (worth retrying the same model) vs a permanent provider error
104
- * (auth failure, quota exceeded, etc. -- should fall back immediately).
105
- */
106
- export function isTransientNetworkError(errorMsg) {
107
- if (!errorMsg)
108
- return false;
109
- const hasNetworkSignal = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns/i.test(errorMsg);
110
- const hasPermanentSignal = /auth|unauthorized|forbidden|invalid.*key|quota|billing/i.test(errorMsg);
111
- return hasNetworkSignal && !hasPermanentSignal;
112
- }
113
101
  /**
114
102
  * Validate a model ID string.
115
103
  * Returns true if the ID looks like a valid model identifier.
@@ -273,7 +261,7 @@ export function resolveContextSelection() {
273
261
  return profile === "budget" ? "smart" : "full";
274
262
  }
275
263
  /**
276
- * Resolve the search provider preference from preferences.md.
264
+ * Resolve the search provider preference from PREFERENCES.md.
277
265
  * Returns undefined if not configured (caller falls back to existing behavior).
278
266
  */
279
267
  export function resolveSearchProviderFromPreferences() {
@@ -11,7 +11,7 @@ export const MODE_DEFAULTS = {
11
11
  git: {
12
12
  auto_push: true,
13
13
  push_branches: false,
14
- pre_merge_check: false,
14
+ pre_merge_check: "auto",
15
15
  merge_strategy: "squash",
16
16
  isolation: "none",
17
17
  },
@@ -24,27 +24,27 @@ export { validatePreferences } from "./preferences-validation.js";
24
24
  // ─── Re-exports: skills ─────────────────────────────────────────────────────
25
25
  export { resolveAllSkillReferences, resolveSkillDiscoveryMode, resolveSkillStalenessDays, } from "./preferences-skills.js";
26
26
  // ─── Re-exports: models ─────────────────────────────────────────────────────
27
- export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
27
+ export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
28
28
  // ─── Path Constants & Getters ───────────────────────────────────────────────
29
29
  function gsdHome() {
30
30
  return process.env.GSD_HOME || join(homedir(), ".gsd");
31
31
  }
32
32
  function globalPreferencesPath() {
33
- return join(gsdHome(), "preferences.md");
33
+ return join(gsdHome(), "PREFERENCES.md");
34
34
  }
35
35
  function legacyGlobalPreferencesPath() {
36
36
  return join(homedir(), ".pi", "agent", "gsd-preferences.md");
37
37
  }
38
38
  function projectPreferencesPath() {
39
- return join(gsdRoot(process.cwd()), "preferences.md");
39
+ return join(gsdRoot(process.cwd()), "PREFERENCES.md");
40
40
  }
41
- // Bootstrap in gitignore.ts historically created PREFERENCES.md (uppercase) by mistake.
42
- // Check uppercase as a fallback so those files aren't silently ignored.
43
- function globalPreferencesPathUppercase() {
44
- return join(gsdHome(), "PREFERENCES.md");
41
+ // Legacy: older versions used lowercase preferences.md.
42
+ // Check lowercase as a fallback so those files aren't silently ignored.
43
+ function globalPreferencesPathLegacy() {
44
+ return join(gsdHome(), "preferences.md");
45
45
  }
46
- function projectPreferencesPathUppercase() {
47
- return join(gsdRoot(process.cwd()), "PREFERENCES.md");
46
+ function projectPreferencesPathLegacy() {
47
+ return join(gsdRoot(process.cwd()), "preferences.md");
48
48
  }
49
49
  export function getGlobalGSDPreferencesPath() {
50
50
  return globalPreferencesPath();
@@ -58,12 +58,12 @@ export function getProjectGSDPreferencesPath() {
58
58
  // ─── Loading ────────────────────────────────────────────────────────────────
59
59
  export function loadGlobalGSDPreferences() {
60
60
  return loadPreferencesFile(globalPreferencesPath(), "global")
61
- ?? loadPreferencesFile(globalPreferencesPathUppercase(), "global")
61
+ ?? loadPreferencesFile(globalPreferencesPathLegacy(), "global")
62
62
  ?? loadPreferencesFile(legacyGlobalPreferencesPath(), "global");
63
63
  }
64
64
  export function loadProjectGSDPreferences() {
65
65
  return loadPreferencesFile(projectPreferencesPath(), "project")
66
- ?? loadPreferencesFile(projectPreferencesPathUppercase(), "project");
66
+ ?? loadPreferencesFile(projectPreferencesPathLegacy(), "project");
67
67
  }
68
68
  export function loadEffectiveGSDPreferences() {
69
69
  const globalPreferences = loadGlobalGSDPreferences();
@@ -149,7 +149,7 @@ export function parsePreferencesMarkdown(content) {
149
149
  }
150
150
  if (!_warnedUnrecognizedFormat) {
151
151
  _warnedUnrecognizedFormat = true;
152
- console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping.");
152
+ console.warn("[parsePreferencesMarkdown] PREFERENCES.md exists but uses an unrecognized format — skipping.");
153
153
  }
154
154
  return null;
155
155
  }
@@ -398,7 +398,7 @@ export function resolvePreDispatchHooks() {
398
398
  * Resolve the effective git isolation mode from preferences.
399
399
  * Returns "none" (default), "worktree", or "branch".
400
400
  *
401
- * Default is "none" so GSD works out of the box without preferences.md.
401
+ * Default is "none" so GSD works out of the box without PREFERENCES.md.
402
402
  * Worktree isolation requires explicit opt-in because it depends on git
403
403
  * branch infrastructure that must be set up before use.
404
404
  */
@@ -92,7 +92,7 @@ Titles live inside file content (headings, frontmatter), not in file or director
92
92
 
93
93
  ### Isolation Model
94
94
 
95
- Auto-mode supports three isolation modes (configured in `.gsd/preferences.md` under `taskIsolation.mode`):
95
+ Auto-mode supports three isolation modes (configured in `.gsd/PREFERENCES.md` under `taskIsolation.mode`):
96
96
 
97
97
  - **worktree** (default): Work happens in `.gsd/worktrees/<MID>/`, a full git worktree on the `milestone/<MID>` branch. Each worktree has its own working copy and `.gsd/` directory. Squash-merged back to the integration branch on milestone completion.
98
98
  - **branch**: Work happens in the project root on a `milestone/<MID>` branch. No worktree directory — files are checked out in-place.
@@ -1,47 +1,3 @@
1
- /**
2
- * Classify a provider error as transient (auto-resume) or permanent (manual resume).
3
- *
4
- * Transient: rate limits, server errors (500/502/503), overloaded, internal errors.
5
- * These are expected to self-resolve and should auto-resume after a delay.
6
- *
7
- * Permanent: auth errors, invalid API key, billing issues.
8
- * These require user intervention and should pause indefinitely.
9
- */
10
- export function classifyProviderError(errorMsg) {
11
- const isRateLimit = /rate.?limit|too many requests|429/i.test(errorMsg);
12
- const isServerError = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i.test(errorMsg);
13
- // Connection/process errors — transient, auto-resume after brief backoff (#2309).
14
- // These indicate the process was killed, the connection was reset, or a network
15
- // blip occurred. They are NOT permanent failures.
16
- const isConnectionError = /terminated|connection.?reset|connection.?refused|other side closed|fetch failed|network.?(?:is\s+)?unavailable|ECONNREFUSED|ECONNRESET|EPIPE/i.test(errorMsg);
17
- // Permanent errors — never auto-resume
18
- const isPermanent = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|billing|quota exceeded|account/i.test(errorMsg);
19
- if (isPermanent && !isRateLimit) {
20
- return { isTransient: false, isRateLimit: false, suggestedDelayMs: 0 };
21
- }
22
- if (isRateLimit) {
23
- // Try to extract retry-after from the message
24
- const resetMatch = errorMsg.match(/reset in (\d+)s/i);
25
- const delayMs = resetMatch ? Number(resetMatch[1]) * 1000 : 60_000; // default 60s for rate limits
26
- return { isTransient: true, isRateLimit: true, suggestedDelayMs: delayMs };
27
- }
28
- if (isServerError) {
29
- return { isTransient: true, isRateLimit: false, suggestedDelayMs: 30_000 }; // 30s for server errors
30
- }
31
- if (isConnectionError) {
32
- return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s for connection errors
33
- }
34
- // Stream-truncation JSON parse errors — transient (#2572).
35
- // When the API stream is cut mid-chunk, pi tries to reassemble the partial
36
- // tool-call JSON and gets a SyntaxError. This is the downstream symptom of
37
- // a connection drop — same root cause as ECONNRESET, one layer up.
38
- const isMalformedStream = /Unexpected end of JSON|Unexpected token.*JSON|Expected double-quoted property name|SyntaxError.*JSON/i.test(errorMsg);
39
- if (isMalformedStream) {
40
- return { isTransient: true, isRateLimit: false, suggestedDelayMs: 15_000 }; // 15s, same as connection errors
41
- }
42
- // Unknown error — treat as permanent (user reviews)
43
- return { isTransient: false, isRateLimit: false, suggestedDelayMs: 0 };
44
- }
45
1
  /**
46
2
  * Pause auto-mode due to a provider error.
47
3
  *
@@ -426,7 +426,7 @@ export class RuleRegistry {
426
426
  formatHookStatus() {
427
427
  const entries = this.getHookStatus();
428
428
  if (entries.length === 0) {
429
- return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/preferences.md";
429
+ return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/PREFERENCES.md";
430
430
  }
431
431
  const lines = ["Configured Hooks:", ""];
432
432
  const postHooks = entries.filter(e => e.type === "post");
@@ -13,16 +13,27 @@ import { getGlobalGSDPreferencesPath, loadEffectiveGSDPreferences, loadGlobalGSD
13
13
  import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
14
14
  const SERVICE_TIER_SCOPE_NOTE = "Only affects gpt-5.4 models, regardless of provider.";
15
15
  // ─── Gating ──────────────────────────────────────────────────────────────────
16
+ /**
17
+ * Model ID prefixes (bare, without provider) that support OpenAI service tiers.
18
+ *
19
+ * This list is the fallback for callers that only have a model ID string.
20
+ * The authoritative source of truth is `model.capabilities.supportsServiceTier`
21
+ * (set via CAPABILITY_PATCHES in packages/pi-ai/src/models.ts). When callers
22
+ * have access to the full Model object, prefer reading capabilities directly.
23
+ *
24
+ * See: https://github.com/gsd-build/gsd-2/issues/2546
25
+ */
26
+ const SERVICE_TIER_MODEL_PREFIXES = ["gpt-5.4"];
16
27
  /**
17
28
  * Returns true when the given model ID supports OpenAI service tiers.
18
- * Currently only gpt-5.4 variants qualify.
29
+ * Reads from SERVICE_TIER_MODEL_PREFIXES update that list, not this function.
19
30
  */
20
31
  export function supportsServiceTier(modelId) {
21
32
  if (!modelId)
22
33
  return false;
23
34
  // Strip provider prefix if present (e.g. "openai/gpt-5.4" → "gpt-5.4")
24
35
  const bare = modelId.includes("/") ? modelId.split("/").pop() : modelId;
25
- return bare.startsWith("gpt-5.4");
36
+ return SERVICE_TIER_MODEL_PREFIXES.some((prefix) => bare.startsWith(prefix));
26
37
  }
27
38
  // ─── Status Formatting ───────────────────────────────────────────────────────
28
39
  /**
@@ -6,6 +6,7 @@ import { parseSummary, loadFile, parseRequirementCounts, parseContextDependsOn,
6
6
  import { resolveMilestoneFile, resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTasksDir, resolveGsdRootFile, gsdRoot, } from './paths.js';
7
7
  import { findMilestoneIds } from './milestone-ids.js';
8
8
  import { loadQueueOrder, sortByQueueOrder } from './queue-order.js';
9
+ import { isClosedStatus } from './status-guards.js';
9
10
  import { nativeBatchParseGsdFiles } from './native-parser-bridge.js';
10
11
  import { join, resolve } from 'path';
11
12
  import { existsSync, readdirSync } from 'node:fs';
@@ -144,7 +145,23 @@ export async function deriveState(basePath) {
144
145
  let result;
145
146
  // Dual-path: try DB-backed derivation first when hierarchy tables are populated
146
147
  if (isDbAvailable()) {
147
- const dbMilestones = getAllMilestones();
148
+ let dbMilestones = getAllMilestones();
149
+ // Disk→DB reconciliation (#2631): when the milestones table is empty
150
+ // (e.g. failed initial migration per #2529), the reconciliation code
151
+ // inside deriveStateFromDb is unreachable. Populate from disk here so
152
+ // the DB path activates correctly.
153
+ if (dbMilestones.length === 0) {
154
+ const diskIds = findMilestoneIds(basePath);
155
+ let synced = false;
156
+ for (const diskId of diskIds) {
157
+ if (!isGhostMilestone(basePath, diskId)) {
158
+ insertMilestone({ id: diskId, status: 'active' });
159
+ synced = true;
160
+ }
161
+ }
162
+ if (synced)
163
+ dbMilestones = getAllMilestones();
164
+ }
148
165
  if (dbMilestones.length > 0) {
149
166
  const stopDbTimer = debugTime("derive-state-db");
150
167
  result = await deriveStateFromDb(basePath);
@@ -187,12 +204,6 @@ function extractContextTitle(content, fallback) {
187
204
  return stripMilestonePrefix(h1.slice(2).trim()) || fallback;
188
205
  }
189
206
  // ─── DB-backed State Derivation ────────────────────────────────────────────
190
- /**
191
- * Helper: check if a DB status counts as "done" (handles K002 ambiguity).
192
- */
193
- function isStatusDone(status) {
194
- return status === 'complete' || status === 'done';
195
- }
196
207
  /**
197
208
  * Derive GSD state from the milestones/slices/tasks DB tables.
198
209
  * Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
@@ -276,7 +287,7 @@ export async function deriveStateFromDb(basePath) {
276
287
  parkedMilestoneIds.add(m.id);
277
288
  continue;
278
289
  }
279
- if (isStatusDone(m.status)) {
290
+ if (isClosedStatus(m.status)) {
280
291
  completeMilestoneIds.add(m.id);
281
292
  continue;
282
293
  }
@@ -288,7 +299,7 @@ export async function deriveStateFromDb(basePath) {
288
299
  }
289
300
  // Check roadmap: all slices done means milestone is complete
290
301
  const slices = getMilestoneSlices(m.id);
291
- if (slices.length > 0 && slices.every(s => isStatusDone(s.status))) {
302
+ if (slices.length > 0 && slices.every(s => isClosedStatus(s.status))) {
292
303
  // All slices done but no summary — still counts as complete for dep resolution
293
304
  // if a summary file exists
294
305
  // Note: without summary file, the milestone is in validating/completing state, not complete
@@ -307,7 +318,7 @@ export async function deriveStateFromDb(basePath) {
307
318
  }
308
319
  // Ghost milestone check: no slices in DB AND no substantive files on disk
309
320
  const slices = getMilestoneSlices(m.id);
310
- if (slices.length === 0 && !isStatusDone(m.status)) {
321
+ if (slices.length === 0 && !isClosedStatus(m.status)) {
311
322
  // Check disk for ghost detection
312
323
  if (isGhostMilestone(basePath, m.id))
313
324
  continue;
@@ -328,7 +339,7 @@ export async function deriveStateFromDb(basePath) {
328
339
  continue;
329
340
  }
330
341
  // Not complete — determine if it should be active
331
- const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
342
+ const allSlicesDone = slices.length > 0 && slices.every(s => isClosedStatus(s.status));
332
343
  // Get title — prefer DB, fall back to context file extraction
333
344
  let title = stripMilestonePrefix(m.title) || m.id;
334
345
  if (title === m.id) {
@@ -465,7 +476,10 @@ export async function deriveStateFromDb(basePath) {
465
476
  };
466
477
  }
467
478
  // ── All slices done → validating/completing ─────────────────────────
468
- const allSlicesDone = activeMilestoneSlices.every(s => isStatusDone(s.status));
479
+ // Guard: [].every() === true (vacuous truth). Without the length check,
480
+ // an empty slice array causes a premature phase transition to
481
+ // validating-milestone. See: https://github.com/gsd-build/gsd-2/issues/2667
482
+ const allSlicesDone = activeMilestoneSlices.length > 0 && activeMilestoneSlices.every(s => isClosedStatus(s.status));
469
483
  if (allSlicesDone) {
470
484
  const validationFile = resolveMilestoneFile(basePath, activeMilestone.id, "VALIDATION");
471
485
  const validationContent = validationFile ? await loadFile(validationFile) : null;
@@ -495,14 +509,14 @@ export async function deriveStateFromDb(basePath) {
495
509
  }
496
510
  // ── Find active slice (first incomplete with deps satisfied) ─────────
497
511
  const sliceProgress = {
498
- done: activeMilestoneSlices.filter(s => isStatusDone(s.status)).length,
512
+ done: activeMilestoneSlices.filter(s => isClosedStatus(s.status)).length,
499
513
  total: activeMilestoneSlices.length,
500
514
  };
501
- const doneSliceIds = new Set(activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id));
515
+ const doneSliceIds = new Set(activeMilestoneSlices.filter(s => isClosedStatus(s.status)).map(s => s.id));
502
516
  let activeSlice = null;
503
517
  let activeSliceRow = null;
504
518
  for (const s of activeMilestoneSlices) {
505
- if (isStatusDone(s.status))
519
+ if (isClosedStatus(s.status))
506
520
  continue;
507
521
  if (s.depends.every(dep => doneSliceIds.has(dep))) {
508
522
  activeSlice = { id: s.id, title: s.title };
@@ -542,7 +556,7 @@ export async function deriveStateFromDb(basePath) {
542
556
  // causing the dispatcher to re-dispatch the same completed task forever.
543
557
  let reconciled = false;
544
558
  for (const t of tasks) {
545
- if (isStatusDone(t.status))
559
+ if (isClosedStatus(t.status))
546
560
  continue;
547
561
  const summaryPath = resolveTaskFile(basePath, activeMilestone.id, activeSlice.id, t.id, "SUMMARY");
548
562
  if (summaryPath && existsSync(summaryPath)) {
@@ -562,10 +576,10 @@ export async function deriveStateFromDb(basePath) {
562
576
  tasks = getSliceTasks(activeMilestone.id, activeSlice.id);
563
577
  }
564
578
  const taskProgress = {
565
- done: tasks.filter(t => isStatusDone(t.status)).length,
579
+ done: tasks.filter(t => isClosedStatus(t.status)).length,
566
580
  total: tasks.length,
567
581
  };
568
- const activeTaskRow = tasks.find(t => !isStatusDone(t.status));
582
+ const activeTaskRow = tasks.find(t => !isClosedStatus(t.status));
569
583
  if (!activeTaskRow && tasks.length > 0) {
570
584
  // All tasks done but slice not marked complete → summarizing
571
585
  return {
@@ -620,7 +634,7 @@ export async function deriveStateFromDb(basePath) {
620
634
  };
621
635
  }
622
636
  // ── Blocker detection: check completed tasks for blocker_discovered ──
623
- const completedTasks = tasks.filter(t => isStatusDone(t.status));
637
+ const completedTasks = tasks.filter(t => isClosedStatus(t.status));
624
638
  let blockerTaskId = null;
625
639
  for (const ct of completedTasks) {
626
640
  if (ct.blocker_discovered) {
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Status predicates for GSD state-machine guards.
3
+ *
4
+ * The DB stores status as free-form strings. Two values indicate
5
+ * "closed": "complete" (canonical) and "done" (legacy / alias).
6
+ * Every inline `status === "complete" || status === "done"` should
7
+ * use isClosedStatus() instead.
8
+ */
9
+ /** Returns true when a milestone, slice, or task status indicates closure. */
10
+ export function isClosedStatus(status) {
11
+ return status === "complete" || status === "done";
12
+ }
@@ -7,8 +7,9 @@
7
7
  */
8
8
  import { join } from "node:path";
9
9
  import { mkdirSync } from "node:fs";
10
- import { transaction, getMilestone, getMilestoneSlices, getSliceTasks, _getAdapter, } from "../gsd-db.js";
10
+ import { transaction, getMilestone, getMilestoneSlices, getSliceTasks, updateMilestoneStatus, } from "../gsd-db.js";
11
11
  import { resolveMilestonePath, clearPathCache } from "../paths.js";
12
+ import { isClosedStatus } from "../status-guards.js";
12
13
  import { saveFile, clearParseCache } from "../files.js";
13
14
  import { invalidateStateCache } from "../state.js";
14
15
  import { renderAllProjections } from "../workflow-projections.js";
@@ -89,7 +90,7 @@ export async function handleCompleteMilestone(params, basePath) {
89
90
  guardError = `milestone not found: ${params.milestoneId}`;
90
91
  return;
91
92
  }
92
- if (milestone.status === "complete" || milestone.status === "done") {
93
+ if (isClosedStatus(milestone.status)) {
93
94
  guardError = `milestone ${params.milestoneId} is already complete`;
94
95
  return;
95
96
  }
@@ -99,7 +100,7 @@ export async function handleCompleteMilestone(params, basePath) {
99
100
  guardError = `no slices found for milestone ${params.milestoneId}`;
100
101
  return;
101
102
  }
102
- const incompleteSlices = slices.filter(s => s.status !== "complete" && s.status !== "done");
103
+ const incompleteSlices = slices.filter(s => !isClosedStatus(s.status));
103
104
  if (incompleteSlices.length > 0) {
104
105
  const incompleteIds = incompleteSlices.map(s => `${s.id} (status: ${s.status})`).join(", ");
105
106
  guardError = `incomplete slices: ${incompleteIds}`;
@@ -108,7 +109,7 @@ export async function handleCompleteMilestone(params, basePath) {
108
109
  // Deep check: verify all tasks in all slices are complete
109
110
  for (const slice of slices) {
110
111
  const tasks = getSliceTasks(params.milestoneId, slice.id);
111
- const incompleteTasks = tasks.filter(t => t.status !== "complete" && t.status !== "done");
112
+ const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
112
113
  if (incompleteTasks.length > 0) {
113
114
  const ids = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
114
115
  guardError = `slice ${slice.id} has incomplete tasks: ${ids}`;
@@ -116,11 +117,7 @@ export async function handleCompleteMilestone(params, basePath) {
116
117
  }
117
118
  }
118
119
  // All guards passed — perform write
119
- const adapter = _getAdapter();
120
- adapter.prepare(`UPDATE milestones SET status = 'complete', completed_at = :completed_at WHERE id = :mid`).run({
121
- ":completed_at": completedAt,
122
- ":mid": params.milestoneId,
123
- });
120
+ updateMilestoneStatus(params.milestoneId, 'complete', completedAt);
124
121
  });
125
122
  if (guardError) {
126
123
  return { error: guardError };
@@ -144,10 +141,7 @@ export async function handleCompleteMilestone(params, basePath) {
144
141
  catch (renderErr) {
145
142
  // Disk render failed — roll back DB status so state stays consistent
146
143
  process.stderr.write(`gsd-db: complete_milestone — disk render failed, rolling back DB status: ${renderErr.message}\n`);
147
- const rollbackAdapter = _getAdapter();
148
- if (rollbackAdapter) {
149
- rollbackAdapter.prepare(`UPDATE milestones SET status = 'active', completed_at = NULL WHERE id = :mid`).run({ ":mid": params.milestoneId });
150
- }
144
+ updateMilestoneStatus(params.milestoneId, 'active', null);
151
145
  invalidateStateCache();
152
146
  return { error: `disk render failed: ${renderErr.message}` };
153
147
  }
@@ -8,7 +8,8 @@
8
8
  */
9
9
  import { join } from "node:path";
10
10
  import { mkdirSync } from "node:fs";
11
- import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus, _getAdapter, } from "../gsd-db.js";
11
+ import { isClosedStatus } from "../status-guards.js";
12
+ import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus, setSliceSummaryMd, } from "../gsd-db.js";
12
13
  import { resolveSlicePath, clearPathCache } from "../paths.js";
13
14
  import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
14
15
  import { saveFile, clearParseCache } from "../files.js";
@@ -179,12 +180,12 @@ export async function handleCompleteSlice(params, basePath) {
179
180
  // Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
180
181
  // Only block if they exist and are closed.
181
182
  const milestone = getMilestone(params.milestoneId);
182
- if (milestone && (milestone.status === "complete" || milestone.status === "done")) {
183
+ if (milestone && isClosedStatus(milestone.status)) {
183
184
  guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
184
185
  return;
185
186
  }
186
187
  const slice = getSlice(params.milestoneId, params.sliceId);
187
- if (slice && (slice.status === "complete" || slice.status === "done")) {
188
+ if (slice && isClosedStatus(slice.status)) {
188
189
  guardError = `slice ${params.sliceId} is already complete — use gsd_slice_reopen first if you need to redo it`;
189
190
  return;
190
191
  }
@@ -194,7 +195,7 @@ export async function handleCompleteSlice(params, basePath) {
194
195
  guardError = `no tasks found for slice ${params.sliceId} in milestone ${params.milestoneId}`;
195
196
  return;
196
197
  }
197
- const incompleteTasks = tasks.filter(t => t.status !== "complete" && t.status !== "done");
198
+ const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
198
199
  if (incompleteTasks.length > 0) {
199
200
  const incompleteIds = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
200
201
  guardError = `incomplete tasks: ${incompleteIds}`;
@@ -240,26 +241,12 @@ export async function handleCompleteSlice(params, basePath) {
240
241
  catch (renderErr) {
241
242
  // Disk render failed — roll back DB status so state stays consistent
242
243
  process.stderr.write(`gsd-db: complete_slice — disk render failed, rolling back DB status: ${renderErr.message}\n`);
243
- const rollbackAdapter = _getAdapter();
244
- if (rollbackAdapter) {
245
- rollbackAdapter.prepare(`UPDATE slices SET status = 'pending' WHERE milestone_id = :mid AND id = :sid`).run({
246
- ":mid": params.milestoneId,
247
- ":sid": params.sliceId,
248
- });
249
- }
244
+ updateSliceStatus(params.milestoneId, params.sliceId, 'pending');
250
245
  invalidateStateCache();
251
246
  return { error: `disk render failed: ${renderErr.message}` };
252
247
  }
253
248
  // Store rendered markdown in DB for D004 recovery
254
- const adapter = _getAdapter();
255
- if (adapter) {
256
- adapter.prepare(`UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid`).run({
257
- ":summary_md": summaryMd,
258
- ":uat_md": uatMd,
259
- ":mid": params.milestoneId,
260
- ":sid": params.sliceId,
261
- });
262
- }
249
+ setSliceSummaryMd(params.milestoneId, params.sliceId, summaryMd, uatMd);
263
250
  // Invalidate all caches
264
251
  invalidateStateCache();
265
252
  clearPathCache();