gsd-pi 2.51.0 → 2.52.0-dev.655ad8a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/README.md +59 -36
  2. package/dist/headless-events.d.ts +18 -0
  3. package/dist/headless-events.js +36 -0
  4. package/dist/headless-query.js +1 -1
  5. package/dist/headless-types.d.ts +28 -0
  6. package/dist/headless-types.js +7 -0
  7. package/dist/headless.d.ts +8 -3
  8. package/dist/headless.js +47 -16
  9. package/dist/help-text.js +16 -5
  10. package/dist/onboarding.js +5 -4
  11. package/dist/remote-questions-config.js +1 -1
  12. package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
  13. package/dist/resources/extensions/async-jobs/job-manager.js +4 -1
  14. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
  15. package/dist/resources/extensions/get-secrets-from-user.js +7 -0
  16. package/dist/resources/extensions/gsd/auto/phases.js +34 -8
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +23 -1
  18. package/dist/resources/extensions/gsd/auto-start.js +2 -0
  19. package/dist/resources/extensions/gsd/auto-timers.js +24 -2
  20. package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
  21. package/dist/resources/extensions/gsd/auto-worktree.js +91 -14
  22. package/dist/resources/extensions/gsd/auto.js +30 -4
  23. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +99 -70
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  26. package/dist/resources/extensions/gsd/claude-import.js +60 -9
  27. package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
  28. package/dist/resources/extensions/gsd/commands-config.js +10 -5
  29. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +4 -4
  30. package/dist/resources/extensions/gsd/detection.js +6 -6
  31. package/dist/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  32. package/dist/resources/extensions/gsd/error-classifier.js +105 -0
  33. package/dist/resources/extensions/gsd/git-service.js +4 -3
  34. package/dist/resources/extensions/gsd/gitignore.js +7 -7
  35. package/dist/resources/extensions/gsd/gsd-db.js +298 -45
  36. package/dist/resources/extensions/gsd/guided-flow.js +4 -3
  37. package/dist/resources/extensions/gsd/init-wizard.js +2 -2
  38. package/dist/resources/extensions/gsd/key-manager.js +7 -16
  39. package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
  40. package/dist/resources/extensions/gsd/memory-store.js +28 -13
  41. package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
  42. package/dist/resources/extensions/gsd/parallel-orchestrator.js +18 -2
  43. package/dist/resources/extensions/gsd/preferences-models.js +1 -13
  44. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  45. package/dist/resources/extensions/gsd/preferences.js +13 -13
  46. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  47. package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
  48. package/dist/resources/extensions/gsd/rule-registry.js +1 -1
  49. package/dist/resources/extensions/gsd/service-tier.js +13 -2
  50. package/dist/resources/extensions/gsd/state.js +38 -30
  51. package/dist/resources/extensions/gsd/status-guards.js +12 -0
  52. package/dist/resources/extensions/gsd/tools/complete-milestone.js +7 -13
  53. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -20
  54. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -21
  55. package/dist/resources/extensions/gsd/tools/plan-milestone.js +28 -29
  56. package/dist/resources/extensions/gsd/tools/plan-slice.js +27 -26
  57. package/dist/resources/extensions/gsd/tools/plan-task.js +23 -23
  58. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +50 -41
  59. package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
  60. package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
  61. package/dist/resources/extensions/gsd/tools/replan-slice.js +51 -41
  62. package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
  63. package/dist/resources/extensions/gsd/validation.js +21 -0
  64. package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
  65. package/dist/resources/extensions/remote-questions/config.js +1 -1
  66. package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
  67. package/dist/resources/extensions/search-the-web/native-search.js +1 -1
  68. package/dist/resources/extensions/search-the-web/provider.js +1 -1
  69. package/dist/resources/extensions/shared/rtk.js +14 -4
  70. package/dist/rtk.js +3 -1
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  73. package/dist/web/standalone/.next/build-manifest.json +4 -4
  74. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  75. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  76. package/dist/web/standalone/.next/required-server-files.json +3 -3
  77. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  78. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  80. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  88. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  97. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  116. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  150. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  164. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  166. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  168. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  170. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/index.html +1 -1
  180. package/dist/web/standalone/.next/server/app/index.rsc +5 -5
  181. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  182. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
  183. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  184. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
  185. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  186. package/dist/web/standalone/.next/server/app/page.js +2 -2
  187. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  189. package/dist/web/standalone/.next/server/chunks/2229.js +3 -3
  190. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  191. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/middleware.js +2 -2
  194. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  196. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  197. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  198. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  199. package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +9 -0
  200. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  201. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  202. package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +1 -0
  203. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  204. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  205. package/dist/web/standalone/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-bca0e732db0dcec3.js} +1 -1
  206. package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
  207. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  208. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  209. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  210. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  211. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  212. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  213. package/dist/web/standalone/server.js +1 -1
  214. package/dist/wizard.js +4 -1
  215. package/package.json +2 -2
  216. package/packages/mcp-server/README.md +202 -0
  217. package/packages/mcp-server/package.json +36 -0
  218. package/packages/mcp-server/src/cli.ts +68 -0
  219. package/packages/mcp-server/src/index.ts +14 -0
  220. package/packages/mcp-server/src/mcp-server.test.ts +628 -0
  221. package/packages/mcp-server/src/server.ts +278 -0
  222. package/packages/mcp-server/src/session-manager.ts +328 -0
  223. package/packages/mcp-server/src/types.ts +107 -0
  224. package/packages/mcp-server/tsconfig.json +24 -0
  225. package/packages/pi-ai/dist/models.d.ts +14 -3
  226. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  227. package/packages/pi-ai/dist/models.js +53 -10
  228. package/packages/pi-ai/dist/models.js.map +1 -1
  229. package/packages/pi-ai/dist/models.test.js +102 -1
  230. package/packages/pi-ai/dist/models.test.js.map +1 -1
  231. package/packages/pi-ai/dist/types.d.ts +30 -0
  232. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  233. package/packages/pi-ai/dist/types.js.map +1 -1
  234. package/packages/pi-ai/src/models.test.ts +114 -1
  235. package/packages/pi-ai/src/models.ts +70 -13
  236. package/packages/pi-ai/src/types.ts +31 -0
  237. package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
  238. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  239. package/packages/pi-coding-agent/dist/cli/args.js +3 -0
  240. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  241. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
  242. package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
  243. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  244. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  245. package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
  246. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  247. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
  248. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
  249. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
  250. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
  251. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  252. package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
  253. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  254. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  255. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  256. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  257. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/main.js +5 -3
  259. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  260. package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
  261. package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
  262. package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
  263. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  264. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
  265. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  266. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
  267. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  268. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
  269. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  270. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
  271. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  272. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
  273. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  274. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
  275. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
  276. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
  277. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
  278. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
  279. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  280. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  281. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  282. package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
  283. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  284. package/packages/pi-coding-agent/package.json +1 -1
  285. package/packages/pi-coding-agent/src/cli/args.ts +4 -0
  286. package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
  287. package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
  288. package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
  289. package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
  290. package/packages/pi-coding-agent/src/index.ts +3 -0
  291. package/packages/pi-coding-agent/src/main.ts +5 -3
  292. package/packages/pi-coding-agent/src/modes/index.ts +8 -1
  293. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
  294. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
  295. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
  296. package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
  297. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
  298. package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
  299. package/packages/rpc-client/package.json +20 -0
  300. package/pkg/package.json +1 -1
  301. package/scripts/ensure-workspace-builds.cjs +36 -8
  302. package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
  303. package/src/resources/extensions/async-jobs/job-manager.ts +4 -1
  304. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
  305. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
  306. package/src/resources/extensions/get-secrets-from-user.ts +8 -0
  307. package/src/resources/extensions/gsd/auto/phases.ts +44 -7
  308. package/src/resources/extensions/gsd/auto-dispatch.ts +25 -1
  309. package/src/resources/extensions/gsd/auto-start.ts +2 -0
  310. package/src/resources/extensions/gsd/auto-timers.ts +25 -1
  311. package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
  312. package/src/resources/extensions/gsd/auto-worktree.ts +94 -14
  313. package/src/resources/extensions/gsd/auto.ts +31 -4
  314. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +118 -73
  315. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
  316. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  317. package/src/resources/extensions/gsd/claude-import.ts +58 -9
  318. package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
  319. package/src/resources/extensions/gsd/commands-config.ts +11 -5
  320. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -4
  321. package/src/resources/extensions/gsd/detection.ts +6 -6
  322. package/src/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  323. package/src/resources/extensions/gsd/error-classifier.ts +139 -0
  324. package/src/resources/extensions/gsd/git-service.ts +4 -3
  325. package/src/resources/extensions/gsd/gitignore.ts +7 -7
  326. package/src/resources/extensions/gsd/gsd-db.ts +355 -63
  327. package/src/resources/extensions/gsd/guided-flow.ts +4 -3
  328. package/src/resources/extensions/gsd/init-wizard.ts +2 -2
  329. package/src/resources/extensions/gsd/key-manager.ts +7 -16
  330. package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
  331. package/src/resources/extensions/gsd/memory-store.ts +29 -18
  332. package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
  333. package/src/resources/extensions/gsd/parallel-orchestrator.ts +23 -1
  334. package/src/resources/extensions/gsd/preferences-models.ts +1 -13
  335. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  336. package/src/resources/extensions/gsd/preferences.ts +12 -13
  337. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  338. package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
  339. package/src/resources/extensions/gsd/rule-registry.ts +1 -1
  340. package/src/resources/extensions/gsd/service-tier.ts +14 -2
  341. package/src/resources/extensions/gsd/state.ts +39 -30
  342. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  343. package/src/resources/extensions/gsd/tests/active-milestone-id-guard.test.ts +91 -0
  344. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +1 -1
  345. package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
  346. package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +87 -0
  347. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +80 -0
  348. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +1 -1
  349. package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
  350. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
  351. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +39 -0
  352. package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
  353. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  354. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
  355. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  356. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
  357. package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
  358. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
  359. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
  360. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
  361. package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
  362. package/src/resources/extensions/gsd/tests/git-service.test.ts +65 -31
  363. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  364. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
  365. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
  366. package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
  367. package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
  368. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  369. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  370. package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +51 -0
  371. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
  372. package/src/resources/extensions/gsd/tests/parallel-orchestrator-zombie-cleanup.test.ts +277 -0
  373. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
  374. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +103 -0
  375. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
  376. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -2
  377. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
  378. package/src/resources/extensions/gsd/tests/rate-limit-model-fallback.test.ts +90 -0
  379. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
  380. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
  381. package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +9 -8
  382. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +125 -0
  383. package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
  384. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
  385. package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
  386. package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
  387. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
  388. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -2
  389. package/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +124 -0
  390. package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
  391. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
  392. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
  393. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -17
  394. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -24
  395. package/src/resources/extensions/gsd/tools/complete-task.ts +13 -25
  396. package/src/resources/extensions/gsd/tools/plan-milestone.ts +30 -32
  397. package/src/resources/extensions/gsd/tools/plan-slice.ts +30 -30
  398. package/src/resources/extensions/gsd/tools/plan-task.ts +26 -26
  399. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +57 -46
  400. package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
  401. package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
  402. package/src/resources/extensions/gsd/tools/replan-slice.ts +55 -44
  403. package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
  404. package/src/resources/extensions/gsd/validation.ts +23 -0
  405. package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
  406. package/src/resources/extensions/remote-questions/config.ts +1 -1
  407. package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
  408. package/src/resources/extensions/search-the-web/native-search.ts +1 -1
  409. package/src/resources/extensions/search-the-web/provider.ts +1 -1
  410. package/src/resources/extensions/shared/rtk.ts +22 -4
  411. package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
  412. package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
  413. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  414. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  415. package/dist/web/standalone/.next/static/css/de141508b083f922.css +0 -1
  416. /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
  417. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_buildManifest.js +0 -0
  418. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_ssgManifest.js +0 -0
  419. /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
@@ -1,8 +1,11 @@
1
1
  // GSD Extension — Workflow Logger Tests
2
2
  // Tests for the centralized warning/error accumulator.
3
3
 
4
- import { describe, test, beforeEach } from "node:test";
4
+ import { describe, test, beforeEach, afterEach } from "node:test";
5
5
  import assert from "node:assert/strict";
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { makeTempDir, cleanup } from "./test-utils.ts";
6
9
  import {
7
10
  logWarning,
8
11
  logError,
@@ -14,6 +17,7 @@ import {
14
17
  hasAnyIssues,
15
18
  summarizeLogs,
16
19
  formatForNotification,
20
+ setLogBasePath,
17
21
  _resetLogs,
18
22
  } from "../workflow-logger.ts";
19
23
 
@@ -222,6 +226,44 @@ describe("workflow-logger", () => {
222
226
  });
223
227
  });
224
228
 
229
+ describe("audit log persistence", () => {
230
+ let dir: string;
231
+
232
+ beforeEach(() => {
233
+ dir = makeTempDir("wl-audit-");
234
+ });
235
+
236
+ afterEach(() => {
237
+ setLogBasePath("");
238
+ cleanup(dir);
239
+ });
240
+
241
+ test("writes entry to .gsd/audit-log.jsonl after setLogBasePath", () => {
242
+ setLogBasePath(dir);
243
+ logWarning("engine", "audit test entry");
244
+
245
+ const auditPath = join(dir, ".gsd", "audit-log.jsonl");
246
+ assert.ok(existsSync(auditPath), "audit-log.jsonl should exist");
247
+ const content = readFileSync(auditPath, "utf-8");
248
+ const entry = JSON.parse(content.trim());
249
+ assert.equal(entry.severity, "warn");
250
+ assert.equal(entry.component, "engine");
251
+ assert.equal(entry.message, "audit test entry");
252
+ });
253
+
254
+ test("_resetLogs does not clear the audit base path", () => {
255
+ setLogBasePath(dir);
256
+ _resetLogs();
257
+ logWarning("engine", "post-reset entry");
258
+
259
+ const auditPath = join(dir, ".gsd", "audit-log.jsonl");
260
+ assert.ok(existsSync(auditPath), "audit-log.jsonl should exist after _resetLogs");
261
+ const content = readFileSync(auditPath, "utf-8");
262
+ const entry = JSON.parse(content.trim());
263
+ assert.equal(entry.message, "post-reset entry");
264
+ });
265
+ });
266
+
225
267
  describe("buffer limit", () => {
226
268
  test("caps at MAX_BUFFER entries, dropping oldest", () => {
227
269
  const OVER = 110;
@@ -237,6 +279,44 @@ describe("workflow-logger", () => {
237
279
  });
238
280
  });
239
281
 
282
+ describe("audit log persistence", () => {
283
+ let dir: string;
284
+
285
+ beforeEach(() => {
286
+ dir = makeTempDir("wl-audit-");
287
+ });
288
+
289
+ afterEach(() => {
290
+ setLogBasePath("");
291
+ cleanup(dir);
292
+ });
293
+
294
+ test("writes entry to .gsd/audit-log.jsonl after setLogBasePath", () => {
295
+ setLogBasePath(dir);
296
+ logWarning("engine", "audit test entry");
297
+
298
+ const auditPath = join(dir, ".gsd", "audit-log.jsonl");
299
+ assert.ok(existsSync(auditPath), "audit-log.jsonl should exist");
300
+ const content = readFileSync(auditPath, "utf-8");
301
+ const entry = JSON.parse(content.trim());
302
+ assert.equal(entry.severity, "warn");
303
+ assert.equal(entry.component, "engine");
304
+ assert.equal(entry.message, "audit test entry");
305
+ });
306
+
307
+ test("_resetLogs does not clear the audit base path", () => {
308
+ setLogBasePath(dir);
309
+ _resetLogs();
310
+ logWarning("engine", "post-reset entry");
311
+
312
+ const auditPath = join(dir, ".gsd", "audit-log.jsonl");
313
+ assert.ok(existsSync(auditPath), "audit-log.jsonl should exist after _resetLogs");
314
+ const content = readFileSync(auditPath, "utf-8");
315
+ const entry = JSON.parse(content.trim());
316
+ assert.equal(entry.message, "post-reset entry");
317
+ });
318
+ });
319
+
240
320
  describe("stderr output", () => {
241
321
  test("writes WARN prefix to stderr for warnings", (t) => {
242
322
  const written: string[] = [];
@@ -0,0 +1,130 @@
1
+ /**
2
+ * worktree-preferences-sync.test.ts — Regression test for #2684.
3
+ *
4
+ * Verifies that preferences.md is seeded into auto-mode worktrees:
5
+ *
6
+ * 1. copyPlanningArtifacts() copies preferences.md on initial worktree creation
7
+ * 2. syncGsdStateToWorktree() forward-syncs preferences.md (additive only)
8
+ * 3. syncWorktreeStateBack() does NOT overwrite project root preferences.md
9
+ */
10
+
11
+ import test from "node:test";
12
+ import assert from "node:assert/strict";
13
+ import {
14
+ existsSync,
15
+ mkdirSync,
16
+ mkdtempSync,
17
+ readFileSync,
18
+ rmSync,
19
+ writeFileSync,
20
+ } from "node:fs";
21
+ import { join } from "node:path";
22
+ import { tmpdir } from "node:os";
23
+
24
+ import {
25
+ syncGsdStateToWorktree,
26
+ syncWorktreeStateBack,
27
+ } from "../auto-worktree.ts";
28
+
29
+ // ─── Helpers ─────────────────────────────────────────────────────────
30
+
31
+ function makeTempDir(prefix: string): string {
32
+ return mkdtempSync(join(tmpdir(), `gsd-prefs-test-${prefix}-`));
33
+ }
34
+
35
+ function cleanup(...dirs: string[]): void {
36
+ for (const dir of dirs) {
37
+ rmSync(dir, { recursive: true, force: true });
38
+ }
39
+ }
40
+
41
+ function writeFile(dir: string, relativePath: string, content: string): void {
42
+ const fullPath = join(dir, relativePath);
43
+ mkdirSync(join(fullPath, ".."), { recursive: true });
44
+ writeFileSync(fullPath, content, "utf-8");
45
+ }
46
+
47
+ // ─── Tests ───────────────────────────────────────────────────────────
48
+
49
+ const PREFS_CONTENT = [
50
+ "# Preferences",
51
+ "",
52
+ "post_unit_hooks:",
53
+ " - npm run lint",
54
+ "",
55
+ "skill_rules:",
56
+ ' - use: "frontend-design"',
57
+ ].join("\n");
58
+
59
+ test("#2684: syncGsdStateToWorktree forward-syncs preferences.md when missing from worktree", (t) => {
60
+ const mainBase = makeTempDir("main");
61
+ const wtBase = makeTempDir("wt");
62
+ t.after(() => cleanup(mainBase, wtBase));
63
+
64
+ // Project root has preferences.md
65
+ writeFile(mainBase, ".gsd/preferences.md", PREFS_CONTENT);
66
+
67
+ // Worktree has .gsd/ but no preferences.md
68
+ mkdirSync(join(wtBase, ".gsd"), { recursive: true });
69
+
70
+ const result = syncGsdStateToWorktree(mainBase, wtBase);
71
+
72
+ assert.ok(
73
+ existsSync(join(wtBase, ".gsd", "preferences.md")),
74
+ "preferences.md should be copied to worktree",
75
+ );
76
+ assert.equal(
77
+ readFileSync(join(wtBase, ".gsd", "preferences.md"), "utf-8"),
78
+ PREFS_CONTENT,
79
+ "preferences.md content should match source",
80
+ );
81
+ assert.ok(
82
+ result.synced.includes("preferences.md"),
83
+ "preferences.md should appear in synced list",
84
+ );
85
+ });
86
+
87
+ test("#2684: syncGsdStateToWorktree does NOT overwrite existing worktree preferences.md", (t) => {
88
+ const mainBase = makeTempDir("main");
89
+ const wtBase = makeTempDir("wt");
90
+ t.after(() => cleanup(mainBase, wtBase));
91
+
92
+ const rootPrefs = "# Root preferences\nold: true";
93
+ const wtPrefs = "# Worktree preferences\nmodified: true";
94
+
95
+ writeFile(mainBase, ".gsd/preferences.md", rootPrefs);
96
+ writeFile(wtBase, ".gsd/preferences.md", wtPrefs);
97
+
98
+ syncGsdStateToWorktree(mainBase, wtBase);
99
+
100
+ assert.equal(
101
+ readFileSync(join(wtBase, ".gsd", "preferences.md"), "utf-8"),
102
+ wtPrefs,
103
+ "existing worktree preferences.md must not be overwritten",
104
+ );
105
+ });
106
+
107
+ test("#2684: syncWorktreeStateBack does NOT overwrite project root preferences.md", (t) => {
108
+ const mainBase = makeTempDir("main");
109
+ const wtBase = makeTempDir("wt");
110
+ const mid = "M001";
111
+ t.after(() => cleanup(mainBase, wtBase));
112
+
113
+ const rootPrefs = "# Root preferences\nauthoritative: true";
114
+ const wtPrefs = "# Worktree preferences\nstale-copy: true";
115
+
116
+ writeFile(mainBase, ".gsd/preferences.md", rootPrefs);
117
+ writeFile(wtBase, ".gsd/preferences.md", wtPrefs);
118
+
119
+ // Worktree needs at least a milestone dir for the function to proceed
120
+ mkdirSync(join(wtBase, ".gsd", "milestones", mid), { recursive: true });
121
+ mkdirSync(join(mainBase, ".gsd", "milestones"), { recursive: true });
122
+
123
+ syncWorktreeStateBack(mainBase, wtBase, mid);
124
+
125
+ assert.equal(
126
+ readFileSync(join(mainBase, ".gsd", "preferences.md"), "utf-8"),
127
+ rootPrefs,
128
+ "project root preferences.md must NOT be overwritten by worktree copy",
129
+ );
130
+ });
@@ -14,9 +14,10 @@ import {
14
14
  getMilestone,
15
15
  getMilestoneSlices,
16
16
  getSliceTasks,
17
- _getAdapter,
17
+ updateMilestoneStatus,
18
18
  } from "../gsd-db.js";
19
19
  import { resolveMilestonePath, clearPathCache } from "../paths.js";
20
+ import { isClosedStatus } from "../status-guards.js";
20
21
  import { saveFile, clearParseCache } from "../files.js";
21
22
  import { invalidateStateCache } from "../state.js";
22
23
  import { renderAllProjections } from "../workflow-projections.js";
@@ -134,7 +135,7 @@ export async function handleCompleteMilestone(
134
135
  guardError = `milestone not found: ${params.milestoneId}`;
135
136
  return;
136
137
  }
137
- if (milestone.status === "complete" || milestone.status === "done") {
138
+ if (isClosedStatus(milestone.status)) {
138
139
  guardError = `milestone ${params.milestoneId} is already complete`;
139
140
  return;
140
141
  }
@@ -146,7 +147,7 @@ export async function handleCompleteMilestone(
146
147
  return;
147
148
  }
148
149
 
149
- const incompleteSlices = slices.filter(s => s.status !== "complete" && s.status !== "done");
150
+ const incompleteSlices = slices.filter(s => !isClosedStatus(s.status));
150
151
  if (incompleteSlices.length > 0) {
151
152
  const incompleteIds = incompleteSlices.map(s => `${s.id} (status: ${s.status})`).join(", ");
152
153
  guardError = `incomplete slices: ${incompleteIds}`;
@@ -156,7 +157,7 @@ export async function handleCompleteMilestone(
156
157
  // Deep check: verify all tasks in all slices are complete
157
158
  for (const slice of slices) {
158
159
  const tasks = getSliceTasks(params.milestoneId, slice.id);
159
- const incompleteTasks = tasks.filter(t => t.status !== "complete" && t.status !== "done");
160
+ const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
160
161
  if (incompleteTasks.length > 0) {
161
162
  const ids = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
162
163
  guardError = `slice ${slice.id} has incomplete tasks: ${ids}`;
@@ -165,13 +166,7 @@ export async function handleCompleteMilestone(
165
166
  }
166
167
 
167
168
  // All guards passed — perform write
168
- const adapter = _getAdapter()!;
169
- adapter.prepare(
170
- `UPDATE milestones SET status = 'complete', completed_at = :completed_at WHERE id = :mid`,
171
- ).run({
172
- ":completed_at": completedAt,
173
- ":mid": params.milestoneId,
174
- });
169
+ updateMilestoneStatus(params.milestoneId, 'complete', completedAt);
175
170
  });
176
171
 
177
172
  if (guardError) {
@@ -199,12 +194,7 @@ export async function handleCompleteMilestone(
199
194
  process.stderr.write(
200
195
  `gsd-db: complete_milestone — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
201
196
  );
202
- const rollbackAdapter = _getAdapter();
203
- if (rollbackAdapter) {
204
- rollbackAdapter.prepare(
205
- `UPDATE milestones SET status = 'active', completed_at = NULL WHERE id = :mid`,
206
- ).run({ ":mid": params.milestoneId });
207
- }
197
+ updateMilestoneStatus(params.milestoneId, 'active', null);
208
198
  invalidateStateCache();
209
199
  return { error: `disk render failed: ${(renderErr as Error).message}` };
210
200
  }
@@ -11,6 +11,7 @@ import { join } from "node:path";
11
11
  import { mkdirSync } from "node:fs";
12
12
 
13
13
  import type { CompleteSliceParams } from "../types.js";
14
+ import { isClosedStatus } from "../status-guards.js";
14
15
  import {
15
16
  transaction,
16
17
  insertMilestone,
@@ -19,7 +20,7 @@ import {
19
20
  getSliceTasks,
20
21
  getMilestone,
21
22
  updateSliceStatus,
22
- _getAdapter,
23
+ setSliceSummaryMd,
23
24
  } from "../gsd-db.js";
24
25
  import { resolveSliceFile, resolveSlicePath, clearPathCache } from "../paths.js";
25
26
  import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
@@ -225,13 +226,13 @@ export async function handleCompleteSlice(
225
226
  // Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
226
227
  // Only block if they exist and are closed.
227
228
  const milestone = getMilestone(params.milestoneId);
228
- if (milestone && (milestone.status === "complete" || milestone.status === "done")) {
229
+ if (milestone && isClosedStatus(milestone.status)) {
229
230
  guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
230
231
  return;
231
232
  }
232
233
 
233
234
  const slice = getSlice(params.milestoneId, params.sliceId);
234
- if (slice && (slice.status === "complete" || slice.status === "done")) {
235
+ if (slice && isClosedStatus(slice.status)) {
235
236
  guardError = `slice ${params.sliceId} is already complete — use gsd_slice_reopen first if you need to redo it`;
236
237
  return;
237
238
  }
@@ -243,7 +244,7 @@ export async function handleCompleteSlice(
243
244
  return;
244
245
  }
245
246
 
246
- const incompleteTasks = tasks.filter(t => t.status !== "complete" && t.status !== "done");
247
+ const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
247
248
  if (incompleteTasks.length > 0) {
248
249
  const incompleteIds = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
249
250
  guardError = `incomplete tasks: ${incompleteIds}`;
@@ -299,31 +300,13 @@ export async function handleCompleteSlice(
299
300
  process.stderr.write(
300
301
  `gsd-db: complete_slice — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
301
302
  );
302
- const rollbackAdapter = _getAdapter();
303
- if (rollbackAdapter) {
304
- rollbackAdapter.prepare(
305
- `UPDATE slices SET status = 'pending' WHERE milestone_id = :mid AND id = :sid`,
306
- ).run({
307
- ":mid": params.milestoneId,
308
- ":sid": params.sliceId,
309
- });
310
- }
303
+ updateSliceStatus(params.milestoneId, params.sliceId, 'pending');
311
304
  invalidateStateCache();
312
305
  return { error: `disk render failed: ${(renderErr as Error).message}` };
313
306
  }
314
307
 
315
308
  // Store rendered markdown in DB for D004 recovery
316
- const adapter = _getAdapter();
317
- if (adapter) {
318
- adapter.prepare(
319
- `UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid`,
320
- ).run({
321
- ":summary_md": summaryMd,
322
- ":uat_md": uatMd,
323
- ":mid": params.milestoneId,
324
- ":sid": params.sliceId,
325
- });
326
- }
309
+ setSliceSummaryMd(params.milestoneId, params.sliceId, summaryMd, uatMd);
327
310
 
328
311
  // Invalidate all caches
329
312
  invalidateStateCache();
@@ -11,6 +11,7 @@ import { join } from "node:path";
11
11
  import { mkdirSync, existsSync } from "node:fs";
12
12
 
13
13
  import type { CompleteTaskParams } from "../types.js";
14
+ import { isClosedStatus } from "../status-guards.js";
14
15
  import {
15
16
  transaction,
16
17
  insertMilestone,
@@ -20,7 +21,9 @@ import {
20
21
  getMilestone,
21
22
  getSlice,
22
23
  getTask,
23
- _getAdapter,
24
+ updateTaskStatus,
25
+ setTaskSummaryMd,
26
+ deleteVerificationEvidence,
24
27
  } from "../gsd-db.js";
25
28
  import { resolveSliceFile, resolveTasksDir, clearPathCache } from "../paths.js";
26
29
  import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
@@ -157,19 +160,19 @@ export async function handleCompleteTask(
157
160
  // Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
158
161
  // Only block if they exist and are closed.
159
162
  const milestone = getMilestone(params.milestoneId);
160
- if (milestone && (milestone.status === "complete" || milestone.status === "done")) {
163
+ if (milestone && isClosedStatus(milestone.status)) {
161
164
  guardError = `cannot complete task in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
162
165
  return;
163
166
  }
164
167
 
165
168
  const slice = getSlice(params.milestoneId, params.sliceId);
166
- if (slice && (slice.status === "complete" || slice.status === "done")) {
169
+ if (slice && isClosedStatus(slice.status)) {
167
170
  guardError = `cannot complete task in a closed slice: ${params.sliceId} (status: ${slice.status})`;
168
171
  return;
169
172
  }
170
173
 
171
174
  const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
172
- if (existingTask && (existingTask.status === "complete" || existingTask.status === "done")) {
175
+ if (existingTask && isClosedStatus(existingTask.status)) {
173
176
  guardError = `task ${params.taskId} is already complete — use gsd_task_reopen first if you need to redo it`;
174
177
  return;
175
178
  }
@@ -248,32 +251,17 @@ export async function handleCompleteTask(
248
251
  process.stderr.write(
249
252
  `gsd-db: complete_task — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
250
253
  );
251
- const rollbackAdapter = _getAdapter();
252
- if (rollbackAdapter) {
253
- rollbackAdapter.prepare(
254
- `UPDATE tasks SET status = 'pending' WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`,
255
- ).run({
256
- ":mid": params.milestoneId,
257
- ":sid": params.sliceId,
258
- ":tid": params.taskId,
259
- });
260
- }
254
+ // Delete orphaned verification_evidence rows first (FK constraint
255
+ // references tasks, so evidence must go before status change).
256
+ // Without this, retries accumulate duplicate evidence rows (#2724).
257
+ deleteVerificationEvidence(params.milestoneId, params.sliceId, params.taskId);
258
+ updateTaskStatus(params.milestoneId, params.sliceId, params.taskId, 'pending');
261
259
  invalidateStateCache();
262
260
  return { error: `disk render failed: ${(renderErr as Error).message}` };
263
261
  }
264
262
 
265
263
  // Store rendered markdown in DB for D004 recovery
266
- const adapter = _getAdapter();
267
- if (adapter) {
268
- adapter.prepare(
269
- `UPDATE tasks SET full_summary_md = :md WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`,
270
- ).run({
271
- ":md": summaryMd,
272
- ":mid": params.milestoneId,
273
- ":sid": params.sliceId,
274
- ":tid": params.taskId,
275
- });
276
- }
264
+ setTaskSummaryMd(params.milestoneId, params.sliceId, params.taskId, summaryMd);
277
265
 
278
266
  // Invalidate all caches
279
267
  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 {
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();