gsd-pi 2.51.0 → 2.52.0-dev.585e355

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (399) hide show
  1. package/README.md +4 -4
  2. package/dist/headless-events.d.ts +18 -0
  3. package/dist/headless-events.js +36 -0
  4. package/dist/headless-types.d.ts +28 -0
  5. package/dist/headless-types.js +7 -0
  6. package/dist/headless.d.ts +8 -3
  7. package/dist/headless.js +47 -16
  8. package/dist/help-text.js +16 -5
  9. package/dist/onboarding.js +5 -4
  10. package/dist/remote-questions-config.js +1 -1
  11. package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
  12. package/dist/resources/extensions/async-jobs/job-manager.js +4 -1
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
  14. package/dist/resources/extensions/gsd/auto/phases.js +6 -0
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +18 -0
  16. package/dist/resources/extensions/gsd/auto-start.js +2 -0
  17. package/dist/resources/extensions/gsd/auto-timers.js +24 -2
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
  19. package/dist/resources/extensions/gsd/auto-worktree.js +21 -0
  20. package/dist/resources/extensions/gsd/auto.js +8 -4
  21. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +105 -70
  22. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  24. package/dist/resources/extensions/gsd/claude-import.js +60 -9
  25. package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
  26. package/dist/resources/extensions/gsd/commands-config.js +10 -5
  27. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +4 -4
  28. package/dist/resources/extensions/gsd/detection.js +6 -6
  29. package/dist/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  30. package/dist/resources/extensions/gsd/error-classifier.js +105 -0
  31. package/dist/resources/extensions/gsd/git-service.js +4 -3
  32. package/dist/resources/extensions/gsd/gitignore.js +7 -7
  33. package/dist/resources/extensions/gsd/gsd-db.js +298 -45
  34. package/dist/resources/extensions/gsd/init-wizard.js +2 -2
  35. package/dist/resources/extensions/gsd/key-manager.js +7 -16
  36. package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
  37. package/dist/resources/extensions/gsd/memory-store.js +28 -13
  38. package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
  39. package/dist/resources/extensions/gsd/preferences-models.js +1 -13
  40. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  41. package/dist/resources/extensions/gsd/preferences.js +13 -13
  42. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  43. package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
  44. package/dist/resources/extensions/gsd/rule-registry.js +1 -1
  45. package/dist/resources/extensions/gsd/service-tier.js +13 -2
  46. package/dist/resources/extensions/gsd/state.js +33 -19
  47. package/dist/resources/extensions/gsd/status-guards.js +12 -0
  48. package/dist/resources/extensions/gsd/tools/complete-milestone.js +7 -13
  49. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -20
  50. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -21
  51. package/dist/resources/extensions/gsd/tools/plan-milestone.js +28 -29
  52. package/dist/resources/extensions/gsd/tools/plan-slice.js +27 -26
  53. package/dist/resources/extensions/gsd/tools/plan-task.js +23 -23
  54. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +50 -41
  55. package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
  56. package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
  57. package/dist/resources/extensions/gsd/tools/replan-slice.js +51 -41
  58. package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
  59. package/dist/resources/extensions/gsd/validation.js +21 -0
  60. package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
  61. package/dist/resources/extensions/remote-questions/config.js +1 -1
  62. package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
  63. package/dist/resources/extensions/search-the-web/native-search.js +1 -1
  64. package/dist/resources/extensions/search-the-web/provider.js +1 -1
  65. package/dist/resources/extensions/shared/rtk.js +5 -3
  66. package/dist/rtk.js +3 -1
  67. package/dist/web/standalone/.next/BUILD_ID +1 -1
  68. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  69. package/dist/web/standalone/.next/build-manifest.json +4 -4
  70. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  71. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  72. package/dist/web/standalone/.next/required-server-files.json +3 -3
  73. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  74. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  76. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  84. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  93. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  100. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  112. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  140. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  146. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  160. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  162. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  164. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  166. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/index.html +1 -1
  176. package/dist/web/standalone/.next/server/app/index.rsc +5 -5
  177. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  178. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
  179. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  180. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
  181. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  182. package/dist/web/standalone/.next/server/app/page.js +2 -2
  183. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  185. package/dist/web/standalone/.next/server/chunks/2229.js +3 -3
  186. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  187. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/middleware.js +2 -2
  190. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  192. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  193. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  194. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  195. package/dist/web/standalone/.next/static/chunks/4024.21054f459af5cc78.js +9 -0
  196. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  197. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  198. package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +1 -0
  199. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  200. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  201. package/dist/web/standalone/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-024d82be84800e52.js} +1 -1
  202. package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
  203. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  204. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  205. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  206. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  207. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  208. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  209. package/dist/web/standalone/server.js +1 -1
  210. package/dist/wizard.js +4 -1
  211. package/package.json +2 -2
  212. package/packages/mcp-server/README.md +202 -0
  213. package/packages/mcp-server/package.json +36 -0
  214. package/packages/mcp-server/src/cli.ts +68 -0
  215. package/packages/mcp-server/src/index.ts +14 -0
  216. package/packages/mcp-server/src/mcp-server.test.ts +628 -0
  217. package/packages/mcp-server/src/server.ts +278 -0
  218. package/packages/mcp-server/src/session-manager.ts +328 -0
  219. package/packages/mcp-server/src/types.ts +107 -0
  220. package/packages/mcp-server/tsconfig.json +24 -0
  221. package/packages/pi-ai/dist/models.d.ts +14 -3
  222. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  223. package/packages/pi-ai/dist/models.js +53 -10
  224. package/packages/pi-ai/dist/models.js.map +1 -1
  225. package/packages/pi-ai/dist/models.test.js +102 -1
  226. package/packages/pi-ai/dist/models.test.js.map +1 -1
  227. package/packages/pi-ai/dist/types.d.ts +30 -0
  228. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  229. package/packages/pi-ai/dist/types.js.map +1 -1
  230. package/packages/pi-ai/src/models.test.ts +114 -1
  231. package/packages/pi-ai/src/models.ts +70 -13
  232. package/packages/pi-ai/src/types.ts +31 -0
  233. package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
  234. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  235. package/packages/pi-coding-agent/dist/cli/args.js +3 -0
  236. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  237. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
  238. package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
  239. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  240. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  241. package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
  242. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  243. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
  244. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
  245. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
  246. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
  247. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  248. package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
  249. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  250. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  251. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  252. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  253. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  254. package/packages/pi-coding-agent/dist/main.js +5 -3
  255. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  256. package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
  257. package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
  259. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  260. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
  261. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  262. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
  263. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  264. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
  265. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  266. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
  267. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  268. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
  269. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  270. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
  271. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
  272. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
  273. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
  274. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
  275. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  276. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  277. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  278. package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
  279. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  280. package/packages/pi-coding-agent/package.json +1 -1
  281. package/packages/pi-coding-agent/src/cli/args.ts +4 -0
  282. package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
  283. package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
  284. package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
  285. package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
  286. package/packages/pi-coding-agent/src/index.ts +3 -0
  287. package/packages/pi-coding-agent/src/main.ts +5 -3
  288. package/packages/pi-coding-agent/src/modes/index.ts +8 -1
  289. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
  290. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
  291. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
  292. package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
  293. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
  294. package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
  295. package/packages/rpc-client/package.json +20 -0
  296. package/pkg/package.json +1 -1
  297. package/scripts/ensure-workspace-builds.cjs +36 -8
  298. package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
  299. package/src/resources/extensions/async-jobs/job-manager.ts +4 -1
  300. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
  301. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
  302. package/src/resources/extensions/gsd/auto/phases.ts +6 -0
  303. package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
  304. package/src/resources/extensions/gsd/auto-start.ts +2 -0
  305. package/src/resources/extensions/gsd/auto-timers.ts +25 -1
  306. package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
  307. package/src/resources/extensions/gsd/auto-worktree.ts +21 -0
  308. package/src/resources/extensions/gsd/auto.ts +10 -4
  309. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -73
  310. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
  311. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  312. package/src/resources/extensions/gsd/claude-import.ts +58 -9
  313. package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
  314. package/src/resources/extensions/gsd/commands-config.ts +11 -5
  315. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -4
  316. package/src/resources/extensions/gsd/detection.ts +6 -6
  317. package/src/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  318. package/src/resources/extensions/gsd/error-classifier.ts +139 -0
  319. package/src/resources/extensions/gsd/git-service.ts +4 -3
  320. package/src/resources/extensions/gsd/gitignore.ts +7 -7
  321. package/src/resources/extensions/gsd/gsd-db.ts +355 -63
  322. package/src/resources/extensions/gsd/init-wizard.ts +2 -2
  323. package/src/resources/extensions/gsd/key-manager.ts +7 -16
  324. package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
  325. package/src/resources/extensions/gsd/memory-store.ts +29 -18
  326. package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
  327. package/src/resources/extensions/gsd/preferences-models.ts +1 -13
  328. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  329. package/src/resources/extensions/gsd/preferences.ts +12 -13
  330. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  331. package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
  332. package/src/resources/extensions/gsd/rule-registry.ts +1 -1
  333. package/src/resources/extensions/gsd/service-tier.ts +14 -2
  334. package/src/resources/extensions/gsd/state.ts +34 -20
  335. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  336. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +1 -1
  337. package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
  338. package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
  339. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
  340. package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
  341. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  342. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
  343. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  344. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
  345. package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
  346. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
  347. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
  348. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
  349. package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
  350. package/src/resources/extensions/gsd/tests/git-service.test.ts +37 -4
  351. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  352. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
  353. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
  354. package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
  355. package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
  356. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  357. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  358. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
  359. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
  360. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
  361. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -2
  362. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
  363. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
  364. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
  365. package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
  366. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
  367. package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
  368. package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
  369. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
  370. package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
  371. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
  372. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
  373. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -17
  374. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -24
  375. package/src/resources/extensions/gsd/tools/complete-task.ts +13 -25
  376. package/src/resources/extensions/gsd/tools/plan-milestone.ts +30 -32
  377. package/src/resources/extensions/gsd/tools/plan-slice.ts +30 -30
  378. package/src/resources/extensions/gsd/tools/plan-task.ts +26 -26
  379. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +57 -46
  380. package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
  381. package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
  382. package/src/resources/extensions/gsd/tools/replan-slice.ts +55 -44
  383. package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
  384. package/src/resources/extensions/gsd/validation.ts +23 -0
  385. package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
  386. package/src/resources/extensions/remote-questions/config.ts +1 -1
  387. package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
  388. package/src/resources/extensions/search-the-web/native-search.ts +1 -1
  389. package/src/resources/extensions/search-the-web/provider.ts +1 -1
  390. package/src/resources/extensions/shared/rtk.ts +12 -3
  391. package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
  392. package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
  393. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  394. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  395. package/dist/web/standalone/.next/static/css/de141508b083f922.css +0 -1
  396. /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
  397. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → KTe1kB5nPLQFIIFz2OcmI}/_buildManifest.js +0 -0
  398. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → KTe1kB5nPLQFIIFz2OcmI}/_ssgManifest.js +0 -0
  399. /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
@@ -1,4 +1,6 @@
1
1
  import { clearParseCache } from "../files.js";
2
+ import { isClosedStatus } from "../status-guards.js";
3
+ import { isNonEmptyString, validateStringArray } from "../validation.js";
2
4
  import {
3
5
  transaction,
4
6
  getMilestone,
@@ -6,7 +8,6 @@ import {
6
8
  insertSlice,
7
9
  upsertMilestonePlanning,
8
10
  upsertSlicePlanning,
9
- _getAdapter,
10
11
  } from "../gsd-db.js";
11
12
  import { invalidateStateCache } from "../state.js";
12
13
  import { renderRoadmapFromDb } from "../markdown-renderer.js";
@@ -55,20 +56,6 @@ export interface PlanMilestoneResult {
55
56
  roadmapPath: string;
56
57
  }
57
58
 
58
- function isNonEmptyString(value: unknown): value is string {
59
- return typeof value === "string" && value.trim().length > 0;
60
- }
61
-
62
- function validateStringArray(value: unknown, field: string): string[] {
63
- if (!Array.isArray(value)) {
64
- throw new Error(`${field} must be an array`);
65
- }
66
- if (value.some((item) => !isNonEmptyString(item))) {
67
- throw new Error(`${field} must contain only non-empty strings`);
68
- }
69
- return value;
70
- }
71
-
72
59
  function validateRiskEntries(value: unknown): Array<{ risk: string; whyItMatters: string }> {
73
60
  if (!Array.isArray(value)) {
74
61
  throw new Error("keyRisks must be an array");
@@ -189,27 +176,34 @@ export async function handlePlanMilestone(
189
176
  return { error: `validation failed: ${(err as Error).message}` };
190
177
  }
191
178
 
192
- // ── State machine preconditions ─────────────────────────────────────────
193
- const existingMilestone = getMilestone(params.milestoneId);
194
- if (existingMilestone && (existingMilestone.status === "complete" || existingMilestone.status === "done")) {
195
- return { error: `cannot re-plan milestone ${params.milestoneId}: it is already complete` };
196
- }
179
+ // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
180
+ // Guards must be inside the transaction so the state they check cannot
181
+ // change between the read and the write (#2723).
182
+ let guardError: string | null = null;
197
183
 
198
- // Validate depends_on: all dependencies must exist and be complete
199
- if (params.dependsOn && params.dependsOn.length > 0) {
200
- for (const depId of params.dependsOn) {
201
- const dep = getMilestone(depId);
202
- if (!dep) {
203
- return { error: `depends_on references unknown milestone: ${depId}` };
184
+ try {
185
+ transaction(() => {
186
+ const existingMilestone = getMilestone(params.milestoneId);
187
+ if (existingMilestone && isClosedStatus(existingMilestone.status)) {
188
+ guardError = `cannot re-plan milestone ${params.milestoneId}: it is already complete`;
189
+ return;
204
190
  }
205
- if (dep.status !== "complete" && dep.status !== "done") {
206
- return { error: `depends_on milestone ${depId} is not yet complete (status: ${dep.status})` };
191
+
192
+ // Validate depends_on: all dependencies must exist and be complete
193
+ if (params.dependsOn && params.dependsOn.length > 0) {
194
+ for (const depId of params.dependsOn) {
195
+ const dep = getMilestone(depId);
196
+ if (!dep) {
197
+ guardError = `depends_on references unknown milestone: ${depId}`;
198
+ return;
199
+ }
200
+ if (!isClosedStatus(dep.status)) {
201
+ guardError = `depends_on milestone ${depId} is not yet complete (status: ${dep.status})`;
202
+ return;
203
+ }
204
+ }
207
205
  }
208
- }
209
- }
210
206
 
211
- try {
212
- transaction(() => {
213
207
  insertMilestone({
214
208
  id: params.milestoneId,
215
209
  title: params.title,
@@ -254,6 +248,10 @@ export async function handlePlanMilestone(
254
248
  return { error: `db write failed: ${(err as Error).message}` };
255
249
  }
256
250
 
251
+ if (guardError) {
252
+ return { error: guardError };
253
+ }
254
+
257
255
  let roadmapPath: string;
258
256
  try {
259
257
  const renderResult = await renderRoadmapFromDb(basePath, params.milestoneId);
@@ -1,4 +1,6 @@
1
1
  import { clearParseCache } from "../files.js";
2
+ import { isClosedStatus } from "../status-guards.js";
3
+ import { isNonEmptyString, validateStringArray } from "../validation.js";
2
4
  import {
3
5
  transaction,
4
6
  getMilestone,
@@ -7,7 +9,6 @@ import {
7
9
  upsertSlicePlanning,
8
10
  upsertTaskPlanning,
9
11
  insertGateRow,
10
- _getAdapter,
11
12
  } from "../gsd-db.js";
12
13
  import type { GateId } from "../types.js";
13
14
  import { invalidateStateCache } from "../state.js";
@@ -51,20 +52,6 @@ export interface PlanSliceResult {
51
52
  taskPlanPaths: string[];
52
53
  }
53
54
 
54
- function isNonEmptyString(value: unknown): value is string {
55
- return typeof value === "string" && value.trim().length > 0;
56
- }
57
-
58
- function validateStringArray(value: unknown, field: string): string[] {
59
- if (!Array.isArray(value)) {
60
- throw new Error(`${field} must be an array`);
61
- }
62
- if (value.some((item) => !isNonEmptyString(item))) {
63
- throw new Error(`${field} must contain only non-empty strings`);
64
- }
65
- return value;
66
- }
67
-
68
55
  function validateTasks(value: unknown): PlanSliceTaskInput[] {
69
56
  if (!Array.isArray(value) || value.length === 0) {
70
57
  throw new Error("tasks must be a non-empty array");
@@ -146,24 +133,33 @@ export async function handlePlanSlice(
146
133
  return { error: `validation failed: ${(err as Error).message}` };
147
134
  }
148
135
 
149
- const parentMilestone = getMilestone(params.milestoneId);
150
- if (!parentMilestone) {
151
- return { error: `milestone not found: ${params.milestoneId}` };
152
- }
153
- if (parentMilestone.status === "complete" || parentMilestone.status === "done") {
154
- return { error: `cannot plan slice in a closed milestone: ${params.milestoneId} (status: ${parentMilestone.status})` };
155
- }
156
-
157
- const parentSlice = getSlice(params.milestoneId, params.sliceId);
158
- if (!parentSlice) {
159
- return { error: `missing parent slice: ${params.milestoneId}/${params.sliceId}` };
160
- }
161
- if (parentSlice.status === "complete" || parentSlice.status === "done") {
162
- return { error: `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first` };
163
- }
136
+ // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
137
+ // Guards must be inside the transaction so the state they check cannot
138
+ // change between the read and the write (#2723).
139
+ let guardError: string | null = null;
164
140
 
165
141
  try {
166
142
  transaction(() => {
143
+ const parentMilestone = getMilestone(params.milestoneId);
144
+ if (!parentMilestone) {
145
+ guardError = `milestone not found: ${params.milestoneId}`;
146
+ return;
147
+ }
148
+ if (isClosedStatus(parentMilestone.status)) {
149
+ guardError = `cannot plan slice in a closed milestone: ${params.milestoneId} (status: ${parentMilestone.status})`;
150
+ return;
151
+ }
152
+
153
+ const parentSlice = getSlice(params.milestoneId, params.sliceId);
154
+ if (!parentSlice) {
155
+ guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
156
+ return;
157
+ }
158
+ if (isClosedStatus(parentSlice.status)) {
159
+ guardError = `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first`;
160
+ return;
161
+ }
162
+
167
163
  upsertSlicePlanning(params.milestoneId, params.sliceId, {
168
164
  goal: params.goal,
169
165
  successCriteria: params.successCriteria,
@@ -211,6 +207,10 @@ export async function handlePlanSlice(
211
207
  return { error: `db write failed: ${(err as Error).message}` };
212
208
  }
213
209
 
210
+ if (guardError) {
211
+ return { error: guardError };
212
+ }
213
+
214
214
  try {
215
215
  const renderResult = await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
216
216
  invalidateStateCache();
@@ -1,4 +1,6 @@
1
1
  import { clearParseCache } from "../files.js";
2
+ import { isClosedStatus } from "../status-guards.js";
3
+ import { isNonEmptyString, validateStringArray } from "../validation.js";
2
4
  import { transaction, getSlice, getTask, insertTask, upsertTaskPlanning } from "../gsd-db.js";
3
5
  import { invalidateStateCache } from "../state.js";
4
6
  import { renderTaskPlanFromDb } from "../markdown-renderer.js";
@@ -32,20 +34,6 @@ export interface PlanTaskResult {
32
34
  taskPlanPath: string;
33
35
  }
34
36
 
35
- function isNonEmptyString(value: unknown): value is string {
36
- return typeof value === "string" && value.trim().length > 0;
37
- }
38
-
39
- function validateStringArray(value: unknown, field: string): string[] {
40
- if (!Array.isArray(value)) {
41
- throw new Error(`${field} must be an array`);
42
- }
43
- if (value.some((item) => !isNonEmptyString(item))) {
44
- throw new Error(`${field} must contain only non-empty strings`);
45
- }
46
- return value;
47
- }
48
-
49
37
  function validateParams(params: PlanTaskParams): PlanTaskParams {
50
38
  if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required");
51
39
  if (!isNonEmptyString(params?.sliceId)) throw new Error("sliceId is required");
@@ -77,21 +65,29 @@ export async function handlePlanTask(
77
65
  return { error: `validation failed: ${(err as Error).message}` };
78
66
  }
79
67
 
80
- const parentSlice = getSlice(params.milestoneId, params.sliceId);
81
- if (!parentSlice) {
82
- return { error: `missing parent slice: ${params.milestoneId}/${params.sliceId}` };
83
- }
84
- if (parentSlice.status === "complete" || parentSlice.status === "done") {
85
- return { error: `cannot plan task in a closed slice: ${params.sliceId} (status: ${parentSlice.status})` };
86
- }
87
-
88
- const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
89
- if (existingTask && (existingTask.status === "complete" || existingTask.status === "done")) {
90
- return { error: `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first` };
91
- }
68
+ // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
69
+ // Guards must be inside the transaction so the state they check cannot
70
+ // change between the read and the write (#2723).
71
+ let guardError: string | null = null;
92
72
 
93
73
  try {
94
74
  transaction(() => {
75
+ const parentSlice = getSlice(params.milestoneId, params.sliceId);
76
+ if (!parentSlice) {
77
+ guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
78
+ return;
79
+ }
80
+ if (isClosedStatus(parentSlice.status)) {
81
+ guardError = `cannot plan task in a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
82
+ return;
83
+ }
84
+
85
+ const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
86
+ if (existingTask && isClosedStatus(existingTask.status)) {
87
+ guardError = `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first`;
88
+ return;
89
+ }
90
+
95
91
  if (!existingTask) {
96
92
  insertTask({
97
93
  id: params.taskId,
@@ -117,6 +113,10 @@ export async function handlePlanTask(
117
113
  return { error: `db write failed: ${(err as Error).message}` };
118
114
  }
119
115
 
116
+ if (guardError) {
117
+ return { error: guardError };
118
+ }
119
+
120
120
  try {
121
121
  const renderResult = await renderTaskPlanFromDb(basePath, params.milestoneId, params.sliceId, params.taskId);
122
122
  invalidateStateCache();
@@ -1,4 +1,7 @@
1
+ import { join } from "node:path";
1
2
  import { clearParseCache } from "../files.js";
3
+ import { isClosedStatus } from "../status-guards.js";
4
+ import { isNonEmptyString } from "../validation.js";
2
5
  import {
3
6
  transaction,
4
7
  getMilestone,
@@ -14,7 +17,6 @@ import { renderRoadmapFromDb, renderAssessmentFromDb } from "../markdown-rendere
14
17
  import { renderAllProjections } from "../workflow-projections.js";
15
18
  import { writeManifest } from "../workflow-manifest.js";
16
19
  import { appendEvent } from "../workflow-events.js";
17
- import { join } from "node:path";
18
20
 
19
21
  export interface SliceChangeInput {
20
22
  sliceId: string;
@@ -47,9 +49,6 @@ export interface ReassessRoadmapResult {
47
49
  roadmapPath: string;
48
50
  }
49
51
 
50
- function isNonEmptyString(value: unknown): value is string {
51
- return typeof value === "string" && value.trim().length > 0;
52
- }
53
52
 
54
53
  function validateParams(params: ReassessRoadmapParams): ReassessRoadmapParams {
55
54
  if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required");
@@ -104,47 +103,6 @@ export async function handleReassessRoadmap(
104
103
  return { error: `validation failed: ${(err as Error).message}` };
105
104
  }
106
105
 
107
- // ── Verify milestone exists and is active ────────────────────────
108
- const milestone = getMilestone(params.milestoneId);
109
- if (!milestone) {
110
- return { error: `milestone not found: ${params.milestoneId}` };
111
- }
112
- if (milestone.status === "complete" || milestone.status === "done") {
113
- return { error: `cannot reassess a closed milestone: ${params.milestoneId} (status: ${milestone.status})` };
114
- }
115
-
116
- // ── Verify completedSliceId is actually complete ──────────────────
117
- const completedSlice = getSlice(params.milestoneId, params.completedSliceId);
118
- if (!completedSlice) {
119
- return { error: `completedSliceId not found: ${params.milestoneId}/${params.completedSliceId}` };
120
- }
121
- if (completedSlice.status !== "complete" && completedSlice.status !== "done") {
122
- return { error: `completedSliceId ${params.completedSliceId} is not complete (status: ${completedSlice.status}) — reassess can only be called after a slice finishes` };
123
- }
124
-
125
- // ── Structural enforcement ────────────────────────────────────────
126
- const existingSlices = getMilestoneSlices(params.milestoneId);
127
- const completedSliceIds = new Set<string>();
128
- for (const slice of existingSlices) {
129
- if (slice.status === "complete" || slice.status === "done") {
130
- completedSliceIds.add(slice.id);
131
- }
132
- }
133
-
134
- // Reject modifications to completed slices
135
- for (const modifiedSlice of params.sliceChanges.modified) {
136
- if (completedSliceIds.has(modifiedSlice.sliceId)) {
137
- return { error: `cannot modify completed slice ${modifiedSlice.sliceId}` };
138
- }
139
- }
140
-
141
- // Reject removal of completed slices
142
- for (const removedId of params.sliceChanges.removed) {
143
- if (completedSliceIds.has(removedId)) {
144
- return { error: `cannot remove completed slice ${removedId}` };
145
- }
146
- }
147
-
148
106
  // ── Compute assessment artifact path ──────────────────────────────
149
107
  // Assessment lives in the completed slice's directory
150
108
  const assessmentRelPath = join(
@@ -153,9 +111,58 @@ export async function handleReassessRoadmap(
153
111
  `${params.completedSliceId}-ASSESSMENT.md`,
154
112
  );
155
113
 
156
- // ── Transaction: DB mutations ─────────────────────────────────────
114
+ // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
115
+ // Guards must be inside the transaction so the state they check cannot
116
+ // change between the read and the write (#2723).
117
+ let guardError: string | null = null;
118
+
157
119
  try {
158
120
  transaction(() => {
121
+ // Verify milestone exists and is active
122
+ const milestone = getMilestone(params.milestoneId);
123
+ if (!milestone) {
124
+ guardError = `milestone not found: ${params.milestoneId}`;
125
+ return;
126
+ }
127
+ if (isClosedStatus(milestone.status)) {
128
+ guardError = `cannot reassess a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
129
+ return;
130
+ }
131
+
132
+ // Verify completedSliceId is actually complete
133
+ const completedSlice = getSlice(params.milestoneId, params.completedSliceId);
134
+ if (!completedSlice) {
135
+ guardError = `completedSliceId not found: ${params.milestoneId}/${params.completedSliceId}`;
136
+ return;
137
+ }
138
+ if (!isClosedStatus(completedSlice.status)) {
139
+ guardError = `completedSliceId ${params.completedSliceId} is not complete (status: ${completedSlice.status}) — reassess can only be called after a slice finishes`;
140
+ return;
141
+ }
142
+
143
+ // Structural enforcement — reject modifications/removal of completed slices
144
+ const existingSlices = getMilestoneSlices(params.milestoneId);
145
+ const completedSliceIds = new Set<string>();
146
+ for (const slice of existingSlices) {
147
+ if (isClosedStatus(slice.status)) {
148
+ completedSliceIds.add(slice.id);
149
+ }
150
+ }
151
+
152
+ for (const modifiedSlice of params.sliceChanges.modified) {
153
+ if (completedSliceIds.has(modifiedSlice.sliceId)) {
154
+ guardError = `cannot modify completed slice ${modifiedSlice.sliceId}`;
155
+ return;
156
+ }
157
+ }
158
+
159
+ for (const removedId of params.sliceChanges.removed) {
160
+ if (completedSliceIds.has(removedId)) {
161
+ guardError = `cannot remove completed slice ${removedId}`;
162
+ return;
163
+ }
164
+ }
165
+
159
166
  // Record assessment
160
167
  insertAssessment({
161
168
  path: assessmentRelPath,
@@ -198,6 +205,10 @@ export async function handleReassessRoadmap(
198
205
  return { error: `db write failed: ${(err as Error).message}` };
199
206
  }
200
207
 
208
+ if (guardError) {
209
+ return { error: guardError };
210
+ }
211
+
201
212
  // ── Render artifacts ──────────────────────────────────────────────
202
213
  try {
203
214
  const roadmapResult = await renderRoadmapFromDb(basePath, params.milestoneId);
@@ -20,6 +20,7 @@ import {
20
20
  transaction,
21
21
  } from "../gsd-db.js";
22
22
  import { invalidateStateCache } from "../state.js";
23
+ import { isClosedStatus } from "../status-guards.js";
23
24
  import { renderAllProjections } from "../workflow-projections.js";
24
25
  import { writeManifest } from "../workflow-manifest.js";
25
26
  import { appendEvent } from "../workflow-events.js";
@@ -62,8 +63,8 @@ export async function handleReopenSlice(
62
63
  guardError = `milestone not found: ${params.milestoneId}`;
63
64
  return;
64
65
  }
65
- if (milestone.status === "complete" || milestone.status === "done") {
66
- guardError = `cannot reopen slice inside a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
66
+ if (isClosedStatus(milestone.status)) {
67
+ guardError = `cannot reopen slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
67
68
  return;
68
69
  }
69
70
 
@@ -72,7 +73,7 @@ export async function handleReopenSlice(
72
73
  guardError = `slice not found: ${params.milestoneId}/${params.sliceId}`;
73
74
  return;
74
75
  }
75
- if (slice.status !== "complete" && slice.status !== "done") {
76
+ if (!isClosedStatus(slice.status)) {
76
77
  guardError = `slice ${params.sliceId} is not complete (status: ${slice.status}) — nothing to reopen`;
77
78
  return;
78
79
  }
@@ -18,6 +18,7 @@ import {
18
18
  transaction,
19
19
  } from "../gsd-db.js";
20
20
  import { invalidateStateCache } from "../state.js";
21
+ import { isClosedStatus } from "../status-guards.js";
21
22
  import { renderAllProjections } from "../workflow-projections.js";
22
23
  import { writeManifest } from "../workflow-manifest.js";
23
24
  import { appendEvent } from "../workflow-events.js";
@@ -63,7 +64,7 @@ export async function handleReopenTask(
63
64
  guardError = `milestone not found: ${params.milestoneId}`;
64
65
  return;
65
66
  }
66
- if (milestone.status === "complete" || milestone.status === "done") {
67
+ if (isClosedStatus(milestone.status)) {
67
68
  guardError = `cannot reopen task in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
68
69
  return;
69
70
  }
@@ -73,8 +74,8 @@ export async function handleReopenTask(
73
74
  guardError = `slice not found: ${params.milestoneId}/${params.sliceId}`;
74
75
  return;
75
76
  }
76
- if (slice.status === "complete" || slice.status === "done") {
77
- guardError = `cannot reopen task inside a closed slice: ${params.sliceId} (status: ${slice.status}) — use gsd_slice_reopen first`;
77
+ if (isClosedStatus(slice.status)) {
78
+ guardError = `cannot reopen task in a closed slice: ${params.sliceId} (status: ${slice.status}) — use gsd_slice_reopen first`;
78
79
  return;
79
80
  }
80
81
 
@@ -83,7 +84,7 @@ export async function handleReopenTask(
83
84
  guardError = `task not found: ${params.milestoneId}/${params.sliceId}/${params.taskId}`;
84
85
  return;
85
86
  }
86
- if (task.status !== "complete" && task.status !== "done") {
87
+ if (!isClosedStatus(task.status)) {
87
88
  guardError = `task ${params.taskId} is not complete (status: ${task.status}) — nothing to reopen`;
88
89
  return;
89
90
  }
@@ -10,6 +10,8 @@ import {
10
10
  deleteTask,
11
11
  } from "../gsd-db.js";
12
12
  import { invalidateStateCache } from "../state.js";
13
+ import { isClosedStatus } from "../status-guards.js";
14
+ import { isNonEmptyString } from "../validation.js";
13
15
  import { renderPlanFromDb, renderReplanFromDb } from "../markdown-renderer.js";
14
16
  import { renderAllProjections } from "../workflow-projections.js";
15
17
  import { writeManifest } from "../workflow-manifest.js";
@@ -48,10 +50,6 @@ export interface ReplanSliceResult {
48
50
  planPath: string;
49
51
  }
50
52
 
51
- function isNonEmptyString(value: unknown): value is string {
52
- return typeof value === "string" && value.trim().length > 0;
53
- }
54
-
55
53
  function validateParams(params: ReplanSliceParams): ReplanSliceParams {
56
54
  if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required");
57
55
  if (!isNonEmptyString(params?.sliceId)) throw new Error("sliceId is required");
@@ -90,52 +88,61 @@ export async function handleReplanSlice(
90
88
  return { error: `validation failed: ${(err as Error).message}` };
91
89
  }
92
90
 
93
- // ── Verify parent slice exists and is not closed ─────────────────
94
- const parentSlice = getSlice(params.milestoneId, params.sliceId);
95
- if (!parentSlice) {
96
- return { error: `missing parent slice: ${params.milestoneId}/${params.sliceId}` };
97
- }
98
- if (parentSlice.status === "complete" || parentSlice.status === "done") {
99
- return { error: `cannot replan a closed slice: ${params.sliceId} (status: ${parentSlice.status})` };
100
- }
91
+ // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
92
+ // Guards must be inside the transaction so the state they check cannot
93
+ // change between the read and the write (#2723).
94
+ let guardError: string | null = null;
95
+ let existingTaskIds: Set<string> = new Set();
101
96
 
102
- // ── Verify blocker task exists and is complete ────────────────────
103
- const blockerTask = getTask(params.milestoneId, params.sliceId, params.blockerTaskId);
104
- if (!blockerTask) {
105
- return { error: `blockerTaskId not found: ${params.milestoneId}/${params.sliceId}/${params.blockerTaskId}` };
106
- }
107
- if (blockerTask.status !== "complete" && blockerTask.status !== "done") {
108
- return { error: `blockerTaskId ${params.blockerTaskId} is not complete (status: ${blockerTask.status}) — the blocker task must be finished before a replan is triggered` };
109
- }
97
+ try {
98
+ transaction(() => {
99
+ // Verify parent slice exists and is not closed
100
+ const parentSlice = getSlice(params.milestoneId, params.sliceId);
101
+ if (!parentSlice) {
102
+ guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
103
+ return;
104
+ }
105
+ if (isClosedStatus(parentSlice.status)) {
106
+ guardError = `cannot replan a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
107
+ return;
108
+ }
110
109
 
111
- // ── Structural enforcement ────────────────────────────────────────
112
- const existingTasks = getSliceTasks(params.milestoneId, params.sliceId);
113
- const completedTaskIds = new Set<string>();
114
- for (const task of existingTasks) {
115
- if (task.status === "complete" || task.status === "done") {
116
- completedTaskIds.add(task.id);
117
- }
118
- }
110
+ // Verify blocker task exists and is complete
111
+ const blockerTask = getTask(params.milestoneId, params.sliceId, params.blockerTaskId);
112
+ if (!blockerTask) {
113
+ guardError = `blockerTaskId not found: ${params.milestoneId}/${params.sliceId}/${params.blockerTaskId}`;
114
+ return;
115
+ }
116
+ if (!isClosedStatus(blockerTask.status)) {
117
+ guardError = `blockerTaskId ${params.blockerTaskId} is not complete (status: ${blockerTask.status}) — the blocker task must be finished before a replan is triggered`;
118
+ return;
119
+ }
119
120
 
120
- // Reject updates to completed tasks
121
- for (const updatedTask of params.updatedTasks) {
122
- if (completedTaskIds.has(updatedTask.taskId)) {
123
- return { error: `cannot modify completed task ${updatedTask.taskId}` };
124
- }
125
- }
121
+ // Structural enforcement reject modifications/removal of completed tasks
122
+ const existingTasks = getSliceTasks(params.milestoneId, params.sliceId);
123
+ const completedTaskIds = new Set<string>();
124
+ for (const task of existingTasks) {
125
+ if (isClosedStatus(task.status)) {
126
+ completedTaskIds.add(task.id);
127
+ }
128
+ }
126
129
 
127
- // Reject removal of completed tasks
128
- for (const removedId of params.removedTaskIds) {
129
- if (completedTaskIds.has(removedId)) {
130
- return { error: `cannot remove completed task ${removedId}` };
131
- }
132
- }
130
+ for (const updatedTask of params.updatedTasks) {
131
+ if (completedTaskIds.has(updatedTask.taskId)) {
132
+ guardError = `cannot modify completed task ${updatedTask.taskId}`;
133
+ return;
134
+ }
135
+ }
133
136
 
134
- // ── Transaction: DB mutations ─────────────────────────────────────
135
- const existingTaskIds = new Set(existingTasks.map((t) => t.id));
137
+ for (const removedId of params.removedTaskIds) {
138
+ if (completedTaskIds.has(removedId)) {
139
+ guardError = `cannot remove completed task ${removedId}`;
140
+ return;
141
+ }
142
+ }
143
+
144
+ existingTaskIds = new Set(existingTasks.map((t) => t.id));
136
145
 
137
- try {
138
- transaction(() => {
139
146
  // Record replan history
140
147
  insertReplanHistory({
141
148
  milestoneId: params.milestoneId,
@@ -189,6 +196,10 @@ export async function handleReplanSlice(
189
196
  return { error: `db write failed: ${(err as Error).message}` };
190
197
  }
191
198
 
199
+ if (guardError) {
200
+ return { error: guardError };
201
+ }
202
+
192
203
  // ── Render artifacts ──────────────────────────────────────────────
193
204
  try {
194
205
  const renderResult = await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);