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
@@ -0,0 +1,90 @@
1
+ import { describe, it, afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, existsSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { randomUUID } from "node:crypto";
7
+
8
+ import { handleValidateMilestone } from "../tools/validate-milestone.js";
9
+ import { openDatabase, closeDatabase, _getAdapter, insertMilestone } from "../gsd-db.js";
10
+ import { clearPathCache } from "../paths.js";
11
+ import { clearParseCache } from "../files.js";
12
+
13
+ function makeTmpBase(): string {
14
+ const base = join(tmpdir(), `gsd-val-handler-${randomUUID()}`);
15
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
16
+ return base;
17
+ }
18
+
19
+ const VALID_PARAMS = {
20
+ milestoneId: "M001",
21
+ verdict: "pass" as const,
22
+ remediationRound: 0,
23
+ successCriteriaChecklist: "- [x] All pass",
24
+ sliceDeliveryAudit: "| S01 | delivered |",
25
+ crossSliceIntegration: "No issues",
26
+ requirementCoverage: "All covered",
27
+ verdictRationale: "Everything checks out",
28
+ };
29
+
30
+ describe("handleValidateMilestone write ordering (#2725)", () => {
31
+ let base: string;
32
+
33
+ afterEach(() => {
34
+ clearPathCache();
35
+ clearParseCache();
36
+ try { closeDatabase(); } catch { /* */ }
37
+ if (base) {
38
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
39
+ }
40
+ });
41
+
42
+ it("writes DB row and disk file on success", async () => {
43
+ base = makeTmpBase();
44
+ const dbPath = join(base, ".gsd", "gsd.db");
45
+ openDatabase(dbPath);
46
+ insertMilestone({ id: "M001" });
47
+
48
+ const result = await handleValidateMilestone(VALID_PARAMS, base);
49
+ assert.ok(!("error" in result), `unexpected error: ${"error" in result ? result.error : ""}`);
50
+
51
+ // DB row exists
52
+ const adapter = _getAdapter()!;
53
+ const row = adapter.prepare(
54
+ `SELECT status, scope FROM assessments WHERE milestone_id = 'M001' AND scope = 'milestone-validation'`,
55
+ ).get() as { status: string; scope: string } | undefined;
56
+ assert.ok(row, "assessment row should exist in DB");
57
+ assert.equal(row!.status, "pass");
58
+
59
+ // Disk file exists
60
+ const filePath = join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md");
61
+ assert.ok(existsSync(filePath), "VALIDATION.md should exist on disk");
62
+ });
63
+
64
+ it("rolls back DB row when disk write fails", async () => {
65
+ base = makeTmpBase();
66
+ const dbPath = join(base, ".gsd", "gsd.db");
67
+ openDatabase(dbPath);
68
+ insertMilestone({ id: "M001" });
69
+
70
+ // Force disk write failure by replacing the milestone directory with a
71
+ // regular file. saveFile() will fail because it cannot write inside a
72
+ // non-directory. This works cross-platform (chmod is ignored on Windows).
73
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
74
+ rmSync(milestoneDir, { recursive: true, force: true });
75
+ writeFileSync(milestoneDir, "not-a-directory");
76
+
77
+ const result = await handleValidateMilestone(VALID_PARAMS, base);
78
+
79
+ // Should return error
80
+ assert.ok("error" in result, "should return error when disk write fails");
81
+ assert.ok(result.error.includes("disk render failed"));
82
+
83
+ // DB row should have been rolled back (deleted)
84
+ const adapter = _getAdapter()!;
85
+ const row = adapter.prepare(
86
+ `SELECT * FROM assessments WHERE milestone_id = 'M001' AND scope = 'milestone-validation'`,
87
+ ).get();
88
+ assert.equal(row, undefined, "assessment row should be deleted after disk-write rollback");
89
+ });
90
+ });
@@ -0,0 +1,72 @@
1
+ // GSD — validation unit tests
2
+
3
+ import test from 'node:test';
4
+ import assert from 'node:assert/strict';
5
+
6
+ import { isNonEmptyString, validateStringArray } from '../validation.ts';
7
+
8
+ // ─── isNonEmptyString ────────────────────────────────────────────────────────
9
+
10
+ test('isNonEmptyString: "hello" returns true', () => {
11
+ assert.equal(isNonEmptyString('hello'), true);
12
+ });
13
+
14
+ test('isNonEmptyString: " " (whitespace only) returns false', () => {
15
+ assert.equal(isNonEmptyString(' '), false);
16
+ });
17
+
18
+ test('isNonEmptyString: "" (empty string) returns false', () => {
19
+ assert.equal(isNonEmptyString(''), false);
20
+ });
21
+
22
+ test('isNonEmptyString: null returns false', () => {
23
+ assert.equal(isNonEmptyString(null), false);
24
+ });
25
+
26
+ test('isNonEmptyString: undefined returns false', () => {
27
+ assert.equal(isNonEmptyString(undefined), false);
28
+ });
29
+
30
+ test('isNonEmptyString: 42 (number) returns false', () => {
31
+ assert.equal(isNonEmptyString(42), false);
32
+ });
33
+
34
+ // ─── validateStringArray ─────────────────────────────────────────────────────
35
+
36
+ test('validateStringArray: ["a", "b"] returns ["a", "b"]', () => {
37
+ assert.deepEqual(validateStringArray(['a', 'b'], 'items'), ['a', 'b']);
38
+ });
39
+
40
+ test('validateStringArray: [] (empty array) returns []', () => {
41
+ assert.deepEqual(validateStringArray([], 'items'), []);
42
+ });
43
+
44
+ test('validateStringArray: "not an array" throws with "must be an array"', () => {
45
+ assert.throws(
46
+ () => validateStringArray('not an array', 'items'),
47
+ (err: Error) => {
48
+ assert.ok(err.message.includes('must be an array'));
49
+ return true;
50
+ },
51
+ );
52
+ });
53
+
54
+ test('validateStringArray: ["a", 42] throws with "must contain only non-empty strings"', () => {
55
+ assert.throws(
56
+ () => validateStringArray(['a', 42], 'items'),
57
+ (err: Error) => {
58
+ assert.ok(err.message.includes('must contain only non-empty strings'));
59
+ return true;
60
+ },
61
+ );
62
+ });
63
+
64
+ test('validateStringArray: ["a", ""] throws with "must contain only non-empty strings"', () => {
65
+ assert.throws(
66
+ () => validateStringArray(['a', ''], 'items'),
67
+ (err: Error) => {
68
+ assert.ok(err.message.includes('must contain only non-empty strings'));
69
+ return true;
70
+ },
71
+ );
72
+ });
@@ -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();