gsd-pi 2.45.0 → 2.46.0-dev.cc9d310

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 (347) hide show
  1. package/dist/help-text.js +1 -1
  2. package/dist/loader.js +34 -0
  3. package/dist/resources/extensions/gsd/auto/phases.js +27 -42
  4. package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
  5. package/dist/resources/extensions/gsd/auto/session.js +0 -11
  6. package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
  7. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
  8. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  9. package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
  10. package/dist/resources/extensions/gsd/auto.js +12 -57
  11. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +15 -12
  12. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
  13. package/dist/resources/extensions/gsd/commands/context.js +0 -4
  14. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
  15. package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
  16. package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
  17. package/dist/resources/extensions/gsd/db-writer.js +9 -9
  18. package/dist/resources/extensions/gsd/doctor-checks.js +167 -2
  19. package/dist/resources/extensions/gsd/doctor.js +5 -3
  20. package/dist/resources/extensions/gsd/gsd-db.js +16 -3
  21. package/dist/resources/extensions/gsd/guided-flow.js +1 -2
  22. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  23. package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
  24. package/dist/resources/extensions/gsd/preferences-types.js +2 -2
  25. package/dist/resources/extensions/gsd/preferences.js +8 -4
  26. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
  27. package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
  28. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
  29. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
  30. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  31. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  34. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  36. package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
  37. package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
  38. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
  40. package/dist/resources/extensions/gsd/prompts/rethink.md +7 -2
  41. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  42. package/dist/resources/extensions/gsd/session-lock.js +1 -3
  43. package/dist/resources/extensions/gsd/state.js +7 -0
  44. package/dist/resources/extensions/gsd/sync-lock.js +89 -0
  45. package/dist/resources/extensions/gsd/tools/complete-milestone.js +61 -11
  46. package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
  47. package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
  48. package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
  49. package/dist/resources/extensions/gsd/tools/plan-slice.js +30 -1
  50. package/dist/resources/extensions/gsd/tools/plan-task.js +27 -1
  51. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
  52. package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
  53. package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
  54. package/dist/resources/extensions/gsd/tools/replan-slice.js +32 -2
  55. package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
  56. package/dist/resources/extensions/gsd/workflow-events.js +102 -0
  57. package/dist/resources/extensions/gsd/workflow-logger.js +193 -0
  58. package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
  59. package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
  60. package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
  61. package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
  62. package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
  63. package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
  64. package/dist/resources/extensions/gsd/write-intercept.js +84 -0
  65. package/dist/resources/extensions/voice/index.js +11 -16
  66. package/dist/resources/extensions/voice/linux-ready.js +67 -0
  67. package/dist/web/standalone/.next/BUILD_ID +1 -1
  68. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  69. package/dist/web/standalone/.next/build-manifest.json +3 -3
  70. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  71. package/dist/web/standalone/.next/required-server-files.json +3 -3
  72. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  73. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  83. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  137. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  143. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  157. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  159. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  161. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  163. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/index.html +1 -1
  173. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  174. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  175. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  176. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  177. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  178. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  179. package/dist/web/standalone/.next/server/app/page.js +2 -2
  180. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  182. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  183. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  184. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/middleware.js +2 -2
  186. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  188. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  189. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  190. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  191. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  192. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  193. package/dist/web/standalone/.next/static/chunks/app/page-6654a8cca61a3d1c.js +1 -0
  194. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  195. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  196. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  197. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  198. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  199. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  200. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  201. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  202. package/dist/web/standalone/server.js +1 -1
  203. package/package.json +2 -1
  204. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
  206. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  208. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +4 -0
  211. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
  212. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
  213. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
  214. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
  215. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
  216. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
  217. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
  218. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
  219. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
  221. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  222. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
  223. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  224. package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
  225. package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
  226. package/packages/pi-coding-agent/package.json +1 -1
  227. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
  228. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
  229. package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
  230. package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
  231. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
  232. package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
  233. package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
  234. package/pkg/package.json +1 -1
  235. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
  236. package/src/resources/extensions/gsd/auto/phases.ts +24 -44
  237. package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
  238. package/src/resources/extensions/gsd/auto/session.ts +0 -18
  239. package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
  240. package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
  241. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
  242. package/src/resources/extensions/gsd/auto-start.ts +1 -3
  243. package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
  244. package/src/resources/extensions/gsd/auto.ts +7 -83
  245. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -12
  246. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  247. package/src/resources/extensions/gsd/commands/context.ts +0 -5
  248. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
  249. package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
  250. package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
  251. package/src/resources/extensions/gsd/db-writer.ts +9 -17
  252. package/src/resources/extensions/gsd/doctor-checks.ts +180 -2
  253. package/src/resources/extensions/gsd/doctor-types.ts +7 -1
  254. package/src/resources/extensions/gsd/doctor.ts +6 -3
  255. package/src/resources/extensions/gsd/gsd-db.ts +16 -3
  256. package/src/resources/extensions/gsd/guided-flow.ts +1 -2
  257. package/src/resources/extensions/gsd/journal.ts +6 -1
  258. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  259. package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
  260. package/src/resources/extensions/gsd/preferences-types.ts +2 -2
  261. package/src/resources/extensions/gsd/preferences.ts +7 -3
  262. package/src/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
  263. package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
  264. package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
  265. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
  266. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  267. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  268. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  269. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  270. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  271. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  272. package/src/resources/extensions/gsd/prompts/queue.md +2 -2
  273. package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
  274. package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
  275. package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
  276. package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
  277. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  278. package/src/resources/extensions/gsd/session-lock.ts +0 -4
  279. package/src/resources/extensions/gsd/state.ts +8 -0
  280. package/src/resources/extensions/gsd/sync-lock.ts +94 -0
  281. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
  282. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
  283. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +96 -0
  284. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
  285. package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
  286. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
  287. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
  288. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  289. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
  291. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
  292. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  293. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  294. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
  295. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
  296. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
  297. package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
  298. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
  299. package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
  300. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
  301. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
  302. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
  303. package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
  304. package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
  305. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
  306. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
  307. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
  308. package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
  309. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
  310. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
  311. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
  312. package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
  313. package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
  314. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
  315. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
  316. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
  317. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
  318. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
  319. package/src/resources/extensions/gsd/tools/complete-milestone.ts +74 -11
  320. package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
  321. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
  322. package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
  323. package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
  324. package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
  325. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
  326. package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
  327. package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
  328. package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
  329. package/src/resources/extensions/gsd/types.ts +8 -0
  330. package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
  331. package/src/resources/extensions/gsd/workflow-events.ts +154 -0
  332. package/src/resources/extensions/gsd/workflow-logger.ts +243 -0
  333. package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
  334. package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
  335. package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
  336. package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
  337. package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
  338. package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
  339. package/src/resources/extensions/gsd/write-intercept.ts +90 -0
  340. package/src/resources/extensions/voice/index.ts +11 -21
  341. package/src/resources/extensions/voice/linux-ready.ts +87 -0
  342. package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
  343. package/dist/web/standalone/.next/static/chunks/app/page-12dd5ece0df4badc.js +0 -1
  344. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  345. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  346. /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → ZIDqryyYDroh_8AnaAOSG}/_buildManifest.js +0 -0
  347. /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → ZIDqryyYDroh_8AnaAOSG}/_ssgManifest.js +0 -0
@@ -0,0 +1,171 @@
1
+ // GSD Extension — workflow-projections unit tests
2
+ // Tests the pure rendering functions (no DB required).
3
+
4
+ import test from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { renderPlanContent } from '../workflow-projections.ts';
7
+ import type { SliceRow, TaskRow } from '../gsd-db.ts';
8
+
9
+ // ─── Test fixtures ────────────────────────────────────────────────────────
10
+
11
+ function makeSlice(overrides: Partial<SliceRow> = {}): SliceRow {
12
+ return {
13
+ id: 'S01',
14
+ milestone_id: 'M001',
15
+ title: 'Auth Layer',
16
+ status: 'active',
17
+ risk: 'high',
18
+ depends: [],
19
+ demo: 'Login flow works end-to-end',
20
+ goal: 'Implement JWT authentication',
21
+ full_summary_md: '',
22
+ full_uat_md: '',
23
+ success_criteria: '',
24
+ proof_level: '',
25
+ integration_closure: '',
26
+ observability_impact: '',
27
+ created_at: '2026-01-01T00:00:00Z',
28
+ completed_at: null,
29
+ sequence: 1,
30
+ replan_triggered_at: null,
31
+ ...overrides,
32
+ };
33
+ }
34
+
35
+ function makeTask(overrides: Partial<TaskRow> = {}): TaskRow {
36
+ return {
37
+ id: 'T01',
38
+ slice_id: 'S01',
39
+ milestone_id: 'M001',
40
+ title: 'Create JWT middleware',
41
+ status: 'pending',
42
+ description: 'Implement JWT validation middleware',
43
+ estimate: '2h',
44
+ files: ['src/middleware/auth.ts'],
45
+ verify: 'npm test src/middleware/auth.test.ts',
46
+ one_liner: '',
47
+ narrative: '',
48
+ verification_result: '',
49
+ duration: '',
50
+ completed_at: null,
51
+ blocker_discovered: false,
52
+ deviations: '',
53
+ known_issues: '',
54
+ key_files: [],
55
+ key_decisions: [],
56
+ full_summary_md: '',
57
+ full_plan_md: '',
58
+ inputs: [],
59
+ expected_output: [],
60
+ observability_impact: '',
61
+ sequence: 1,
62
+ ...overrides,
63
+ };
64
+ }
65
+
66
+ // ─── renderPlanContent: structure ────────────────────────────────────────
67
+
68
+ test('workflow-projections: renderPlanContent starts with H1 containing slice id and title', () => {
69
+ const content = renderPlanContent(makeSlice(), []);
70
+ assert.ok(content.startsWith('# S01: Auth Layer'), `expected H1, got: ${content.slice(0, 60)}`);
71
+ });
72
+
73
+ test('workflow-projections: renderPlanContent includes Goal line', () => {
74
+ const content = renderPlanContent(makeSlice(), []);
75
+ assert.ok(content.includes('**Goal:** Implement JWT authentication'));
76
+ });
77
+
78
+ test('workflow-projections: renderPlanContent includes Demo line', () => {
79
+ const content = renderPlanContent(makeSlice(), []);
80
+ assert.ok(content.includes('**Demo:** After this: Login flow works end-to-end'));
81
+ });
82
+
83
+ test('workflow-projections: renderPlanContent falls back to TBD when goal and full_summary_md are empty', () => {
84
+ const slice = makeSlice({ goal: '', full_summary_md: '' });
85
+ const content = renderPlanContent(slice, []);
86
+ assert.ok(content.includes('**Goal:** TBD'));
87
+ });
88
+
89
+ test('workflow-projections: renderPlanContent falls back to full_summary_md when goal is empty', () => {
90
+ const slice = makeSlice({ goal: '', full_summary_md: 'Fallback goal text' });
91
+ const content = renderPlanContent(slice, []);
92
+ assert.ok(content.includes('**Goal:** Fallback goal text'));
93
+ });
94
+
95
+ test('workflow-projections: renderPlanContent includes ## Tasks section', () => {
96
+ const content = renderPlanContent(makeSlice(), []);
97
+ assert.ok(content.includes('## Tasks'));
98
+ });
99
+
100
+ // ─── renderPlanContent: task checkboxes ──────────────────────────────────
101
+
102
+ test('workflow-projections: pending task renders with [ ] checkbox', () => {
103
+ const task = makeTask({ status: 'pending' });
104
+ const content = renderPlanContent(makeSlice(), [task]);
105
+ assert.ok(content.includes('- [ ] **T01:'), `expected unchecked, got: ${content}`);
106
+ });
107
+
108
+ test('workflow-projections: done task renders with [x] checkbox', () => {
109
+ const task = makeTask({ status: 'done' });
110
+ const content = renderPlanContent(makeSlice(), [task]);
111
+ assert.ok(content.includes('- [x] **T01:'), `expected checked, got: ${content}`);
112
+ });
113
+
114
+ test('workflow-projections: complete status renders with [x] checkbox', () => {
115
+ const task = makeTask({ status: 'complete' }); // 'complete' and 'done' both → checked
116
+ const content = renderPlanContent(makeSlice(), [task]);
117
+ assert.ok(content.includes('- [x] **T01:'));
118
+ });
119
+
120
+ // ─── renderPlanContent: task sublines ────────────────────────────────────
121
+
122
+ test('workflow-projections: task with estimate renders Estimate subline', () => {
123
+ const task = makeTask({ estimate: '2h' });
124
+ const content = renderPlanContent(makeSlice(), [task]);
125
+ assert.ok(content.includes(' - Estimate: 2h'));
126
+ });
127
+
128
+ test('workflow-projections: task with empty estimate omits Estimate subline', () => {
129
+ const task = makeTask({ estimate: '' });
130
+ const content = renderPlanContent(makeSlice(), [task]);
131
+ assert.ok(!content.includes(' - Estimate:'));
132
+ });
133
+
134
+ test('workflow-projections: task with files renders Files subline', () => {
135
+ const task = makeTask({ files: ['src/auth.ts', 'src/auth.test.ts'] });
136
+ const content = renderPlanContent(makeSlice(), [task]);
137
+ assert.ok(content.includes(' - Files: src/auth.ts, src/auth.test.ts'));
138
+ });
139
+
140
+ test('workflow-projections: task with empty files array omits Files subline', () => {
141
+ const task = makeTask({ files: [] });
142
+ const content = renderPlanContent(makeSlice(), [task]);
143
+ assert.ok(!content.includes(' - Files:'));
144
+ });
145
+
146
+ test('workflow-projections: task with verify renders Verify subline', () => {
147
+ const task = makeTask({ verify: 'npm test' });
148
+ const content = renderPlanContent(makeSlice(), [task]);
149
+ assert.ok(content.includes(' - Verify: npm test'));
150
+ });
151
+
152
+ test('workflow-projections: task with no verify omits Verify subline', () => {
153
+ const task = makeTask({ verify: '' });
154
+ const content = renderPlanContent(makeSlice(), [task]);
155
+ assert.ok(!content.includes(' - Verify:'));
156
+ });
157
+
158
+ test('workflow-projections: task with duration renders Duration subline', () => {
159
+ const task = makeTask({ duration: '45m' });
160
+ const content = renderPlanContent(makeSlice(), [task]);
161
+ assert.ok(content.includes(' - Duration: 45m'));
162
+ });
163
+
164
+ test('workflow-projections: multiple tasks rendered in order', () => {
165
+ const t1 = makeTask({ id: 'T01', title: 'First task', sequence: 1 });
166
+ const t2 = makeTask({ id: 'T02', title: 'Second task', sequence: 2 });
167
+ const content = renderPlanContent(makeSlice(), [t1, t2]);
168
+ const idxT1 = content.indexOf('**T01:');
169
+ const idxT2 = content.indexOf('**T02:');
170
+ assert.ok(idxT1 < idxT2, 'T01 should appear before T02');
171
+ });
@@ -0,0 +1,220 @@
1
+ import { describe, test, beforeEach, afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, rmSync, readFileSync, readdirSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import {
7
+ WorktreeResolver,
8
+ type WorktreeResolverDeps,
9
+ type NotifyCtx,
10
+ } from "../worktree-resolver.js";
11
+ import { AutoSession } from "../auto/session.js";
12
+ import type { JournalEntry } from "../journal.js";
13
+
14
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
15
+
16
+ function makeSession(
17
+ overrides?: Partial<{ basePath: string; originalBasePath: string }>,
18
+ ): AutoSession {
19
+ const s = new AutoSession();
20
+ s.basePath = overrides?.basePath ?? "/project";
21
+ s.originalBasePath = overrides?.originalBasePath ?? "/project";
22
+ return s;
23
+ }
24
+
25
+ function makeDeps(
26
+ overrides?: Partial<WorktreeResolverDeps>,
27
+ ): WorktreeResolverDeps {
28
+ const deps: WorktreeResolverDeps = {
29
+ isInAutoWorktree: () => false,
30
+ shouldUseWorktreeIsolation: () => true,
31
+ getIsolationMode: () => "worktree",
32
+ mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
33
+ syncWorktreeStateBack: () => ({ synced: [] }),
34
+ teardownAutoWorktree: () => {},
35
+ createAutoWorktree: (_basePath: string, milestoneId: string) =>
36
+ `/project/.gsd/worktrees/${milestoneId}`,
37
+ enterAutoWorktree: (_basePath: string, milestoneId: string) =>
38
+ `/project/.gsd/worktrees/${milestoneId}`,
39
+ getAutoWorktreePath: () => null,
40
+ autoCommitCurrentBranch: () => {},
41
+ getCurrentBranch: () => "main",
42
+ autoWorktreeBranch: (milestoneId: string) => `milestone/${milestoneId}`,
43
+ resolveMilestoneFile: (_basePath: string, milestoneId: string) =>
44
+ `/project/.gsd/milestones/${milestoneId}/${milestoneId}-ROADMAP.md`,
45
+ readFileSync: () => "# Roadmap\n- [x] S01: Slice one\n",
46
+ GitServiceImpl: class {
47
+ constructor() {}
48
+ } as unknown as WorktreeResolverDeps["GitServiceImpl"],
49
+ loadEffectiveGSDPreferences: () => ({ preferences: { git: {} } }),
50
+ invalidateAllCaches: () => {},
51
+ captureIntegrationBranch: () => {},
52
+ ...overrides,
53
+ };
54
+ return deps;
55
+ }
56
+
57
+ function makeNotifyCtx(): NotifyCtx {
58
+ return {
59
+ notify: () => {},
60
+ };
61
+ }
62
+
63
+ /** Read all journal entries from a temp .gsd/journal directory. */
64
+ function readJournalEntries(basePath: string): JournalEntry[] {
65
+ const journalDir = join(basePath, ".gsd", "journal");
66
+ try {
67
+ const files = readdirSync(journalDir).filter(f => f.endsWith(".jsonl")).sort();
68
+ const entries: JournalEntry[] = [];
69
+ for (const file of files) {
70
+ const raw = readFileSync(join(journalDir, file), "utf-8");
71
+ for (const line of raw.split("\n")) {
72
+ if (!line.trim()) continue;
73
+ entries.push(JSON.parse(line) as JournalEntry);
74
+ }
75
+ }
76
+ return entries;
77
+ } catch {
78
+ return [];
79
+ }
80
+ }
81
+
82
+ // ─── Tests ───────────────────────────────────────────────────────────────────
83
+
84
+ describe("worktree journal events", () => {
85
+ let tmp: string;
86
+ const originalCwd = process.cwd();
87
+
88
+ beforeEach(() => {
89
+ tmp = mkdtempSync(join(tmpdir(), "wt-journal-"));
90
+ });
91
+ afterEach(() => {
92
+ // Restore cwd before cleanup — on Windows, rmSync fails with EPERM
93
+ // if the process cwd is inside the directory being deleted.
94
+ try { process.chdir(originalCwd); } catch { /* best-effort */ }
95
+ rmSync(tmp, { recursive: true, force: true });
96
+ });
97
+
98
+ test("enterMilestone emits worktree-enter on success (new worktree)", () => {
99
+ const s = makeSession({ basePath: tmp, originalBasePath: tmp });
100
+ const deps = makeDeps({ getAutoWorktreePath: () => null });
101
+ const resolver = new WorktreeResolver(s, deps);
102
+
103
+ resolver.enterMilestone("M001", makeNotifyCtx());
104
+
105
+ const entries = readJournalEntries(tmp);
106
+ const enter = entries.find(e => e.eventType === "worktree-enter");
107
+ assert.ok(enter, "worktree-enter event should be emitted");
108
+ assert.equal(enter!.data?.milestoneId, "M001");
109
+ assert.equal(enter!.data?.created, true);
110
+ assert.ok(enter!.data?.wtPath);
111
+ });
112
+
113
+ test("enterMilestone emits worktree-enter with created=false for existing worktree", () => {
114
+ const s = makeSession({ basePath: tmp, originalBasePath: tmp });
115
+ const deps = makeDeps({
116
+ getAutoWorktreePath: () => "/project/.gsd/worktrees/M001",
117
+ });
118
+ const resolver = new WorktreeResolver(s, deps);
119
+
120
+ resolver.enterMilestone("M001", makeNotifyCtx());
121
+
122
+ const entries = readJournalEntries(tmp);
123
+ const enter = entries.find(e => e.eventType === "worktree-enter");
124
+ assert.ok(enter, "worktree-enter event should be emitted");
125
+ assert.equal(enter!.data?.created, false);
126
+ });
127
+
128
+ test("enterMilestone emits worktree-skip when isolation disabled", () => {
129
+ const s = makeSession({ basePath: tmp, originalBasePath: tmp });
130
+ const deps = makeDeps({ shouldUseWorktreeIsolation: () => false });
131
+ const resolver = new WorktreeResolver(s, deps);
132
+
133
+ resolver.enterMilestone("M001", makeNotifyCtx());
134
+
135
+ const entries = readJournalEntries(tmp);
136
+ const skip = entries.find(e => e.eventType === "worktree-skip");
137
+ assert.ok(skip, "worktree-skip event should be emitted");
138
+ assert.equal(skip!.data?.milestoneId, "M001");
139
+ assert.equal(skip!.data?.reason, "isolation-disabled");
140
+ });
141
+
142
+ test("enterMilestone emits worktree-create-failed on error", () => {
143
+ const s = makeSession({ basePath: tmp, originalBasePath: tmp });
144
+ const deps = makeDeps({
145
+ getAutoWorktreePath: () => null,
146
+ createAutoWorktree: () => { throw new Error("disk full"); },
147
+ });
148
+ const resolver = new WorktreeResolver(s, deps);
149
+
150
+ resolver.enterMilestone("M001", makeNotifyCtx());
151
+
152
+ const entries = readJournalEntries(tmp);
153
+ const failed = entries.find(e => e.eventType === "worktree-create-failed");
154
+ assert.ok(failed, "worktree-create-failed event should be emitted");
155
+ assert.equal(failed!.data?.milestoneId, "M001");
156
+ assert.equal(failed!.data?.error, "disk full");
157
+ assert.equal(failed!.data?.fallback, "project-root");
158
+ });
159
+
160
+ test("mergeAndExit emits worktree-merge-start", () => {
161
+ const s = makeSession({
162
+ basePath: join(tmp, "worktree"),
163
+ originalBasePath: tmp,
164
+ });
165
+ const deps = makeDeps({
166
+ isInAutoWorktree: () => true,
167
+ getIsolationMode: () => "worktree",
168
+ });
169
+ const resolver = new WorktreeResolver(s, deps);
170
+
171
+ resolver.mergeAndExit("M001", makeNotifyCtx());
172
+
173
+ const entries = readJournalEntries(tmp);
174
+ const start = entries.find(e => e.eventType === "worktree-merge-start");
175
+ assert.ok(start, "worktree-merge-start event should be emitted");
176
+ assert.equal(start!.data?.milestoneId, "M001");
177
+ assert.equal(start!.data?.mode, "worktree");
178
+ });
179
+
180
+ test("mergeAndExit emits worktree-merge-failed on error", () => {
181
+ const s = makeSession({
182
+ basePath: join(tmp, "worktree"),
183
+ originalBasePath: tmp,
184
+ });
185
+ const deps = makeDeps({
186
+ isInAutoWorktree: () => true,
187
+ getIsolationMode: () => "worktree",
188
+ mergeMilestoneToMain: () => { throw new Error("conflict in main"); },
189
+ });
190
+ const resolver = new WorktreeResolver(s, deps);
191
+
192
+ resolver.mergeAndExit("M001", makeNotifyCtx());
193
+
194
+ const entries = readJournalEntries(tmp);
195
+ const failed = entries.find(e => e.eventType === "worktree-merge-failed");
196
+ assert.ok(failed, "worktree-merge-failed event should be emitted");
197
+ assert.equal(failed!.data?.milestoneId, "M001");
198
+ assert.equal(failed!.data?.error, "conflict in main");
199
+ });
200
+
201
+ test("journal entries have valid flowId, seq, and ts fields", () => {
202
+ const s = makeSession({ basePath: tmp, originalBasePath: tmp });
203
+ const deps = makeDeps({ shouldUseWorktreeIsolation: () => false });
204
+ const resolver = new WorktreeResolver(s, deps);
205
+
206
+ resolver.enterMilestone("M001", makeNotifyCtx());
207
+
208
+ const entries = readJournalEntries(tmp);
209
+ assert.ok(entries.length > 0, "at least one entry should exist");
210
+ const entry = entries[0];
211
+ assert.ok(entry.flowId, "flowId should be set");
212
+ assert.ok(
213
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(entry.flowId),
214
+ "flowId should be a valid UUID",
215
+ );
216
+ assert.equal(entry.seq, 0);
217
+ assert.ok(entry.ts, "ts should be set");
218
+ assert.ok(!isNaN(Date.parse(entry.ts)), "ts should be a valid ISO date");
219
+ });
220
+ });
@@ -0,0 +1,76 @@
1
+ // GSD Extension — write-intercept unit tests
2
+ // Tests isBlockedStateFile() and BLOCKED_WRITE_ERROR constant.
3
+
4
+ import test from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { isBlockedStateFile, BLOCKED_WRITE_ERROR } from '../write-intercept.ts';
7
+
8
+ // ─── isBlockedStateFile: blocked paths ───────────────────────────────────
9
+
10
+ test('write-intercept: blocks unix .gsd/STATE.md path', () => {
11
+ assert.strictEqual(isBlockedStateFile('/project/.gsd/STATE.md'), true);
12
+ });
13
+
14
+ test('write-intercept: blocks relative path with dir prefix before .gsd/STATE.md', () => {
15
+ assert.strictEqual(isBlockedStateFile('project/.gsd/STATE.md'), true);
16
+ });
17
+
18
+ test('write-intercept: blocks bare relative .gsd/STATE.md (no leading separator)', () => {
19
+ // (^|[/\\]) matches paths that start with .gsd/ — covers the case where write
20
+ // tools receive a bare relative path before the file exists (realpathSync fails).
21
+ assert.strictEqual(isBlockedStateFile('.gsd/STATE.md'), true);
22
+ });
23
+
24
+ test('write-intercept: blocks nested project .gsd/STATE.md path', () => {
25
+ assert.strictEqual(isBlockedStateFile('/Users/dev/my-project/.gsd/STATE.md'), true);
26
+ });
27
+
28
+ test('write-intercept: blocks .gsd/projects/<name>/STATE.md (symlinked projects path)', () => {
29
+ assert.strictEqual(isBlockedStateFile('/home/user/.gsd/projects/my-project/STATE.md'), true);
30
+ });
31
+
32
+ // ─── isBlockedStateFile: allowed paths ───────────────────────────────────
33
+
34
+ test('write-intercept: allows .gsd/ROADMAP.md', () => {
35
+ assert.strictEqual(isBlockedStateFile('/project/.gsd/ROADMAP.md'), false);
36
+ });
37
+
38
+ test('write-intercept: allows .gsd/PLAN.md', () => {
39
+ assert.strictEqual(isBlockedStateFile('/project/.gsd/PLAN.md'), false);
40
+ });
41
+
42
+ test('write-intercept: allows .gsd/REQUIREMENTS.md', () => {
43
+ assert.strictEqual(isBlockedStateFile('/project/.gsd/REQUIREMENTS.md'), false);
44
+ });
45
+
46
+ test('write-intercept: allows .gsd/SUMMARY.md', () => {
47
+ assert.strictEqual(isBlockedStateFile('/project/.gsd/SUMMARY.md'), false);
48
+ });
49
+
50
+ test('write-intercept: allows .gsd/PROJECT.md', () => {
51
+ assert.strictEqual(isBlockedStateFile('/project/.gsd/PROJECT.md'), false);
52
+ });
53
+
54
+ test('write-intercept: allows regular source files', () => {
55
+ assert.strictEqual(isBlockedStateFile('/project/src/index.ts'), false);
56
+ });
57
+
58
+ test('write-intercept: allows slice plan files', () => {
59
+ assert.strictEqual(isBlockedStateFile('/project/.gsd/milestones/M001/slices/S01/S01-PLAN.md'), false);
60
+ });
61
+
62
+ test('write-intercept: does not block files named STATE.md outside .gsd/', () => {
63
+ assert.strictEqual(isBlockedStateFile('/project/docs/STATE.md'), false);
64
+ });
65
+
66
+ // ─── BLOCKED_WRITE_ERROR: content ────────────────────────────────────────
67
+
68
+ test('write-intercept: BLOCKED_WRITE_ERROR is a non-empty string', () => {
69
+ assert.strictEqual(typeof BLOCKED_WRITE_ERROR, 'string');
70
+ assert.ok(BLOCKED_WRITE_ERROR.length > 0);
71
+ });
72
+
73
+ test('write-intercept: BLOCKED_WRITE_ERROR mentions engine tool calls', () => {
74
+ assert.ok(BLOCKED_WRITE_ERROR.includes('gsd_complete_task'), 'should mention gsd_complete_task');
75
+ assert.ok(BLOCKED_WRITE_ERROR.includes('engine tool calls'), 'should mention engine tool calls');
76
+ });
@@ -11,12 +11,17 @@ import { mkdirSync } from "node:fs";
11
11
 
12
12
  import {
13
13
  transaction,
14
+ getMilestone,
14
15
  getMilestoneSlices,
16
+ getSliceTasks,
15
17
  _getAdapter,
16
18
  } from "../gsd-db.js";
17
19
  import { resolveMilestonePath, clearPathCache } from "../paths.js";
18
20
  import { saveFile, clearParseCache } from "../files.js";
19
21
  import { invalidateStateCache } from "../state.js";
22
+ import { renderAllProjections } from "../workflow-projections.js";
23
+ import { writeManifest } from "../workflow-manifest.js";
24
+ import { appendEvent } from "../workflow-events.js";
20
25
 
21
26
  export interface CompleteMilestoneParams {
22
27
  milestoneId: string;
@@ -31,6 +36,11 @@ export interface CompleteMilestoneParams {
31
36
  lessonsLearned: string[];
32
37
  followUps: string;
33
38
  deviations: string;
39
+ verificationPassed: boolean;
40
+ /** Optional caller-provided identity for audit trail */
41
+ actorName?: string;
42
+ /** Optional caller-provided reason this action was triggered */
43
+ triggerReason?: string;
34
44
  }
35
45
 
36
46
  export interface CompleteMilestoneResult {
@@ -108,22 +118,53 @@ export async function handleCompleteMilestone(
108
118
  return { error: "title is required and must be a non-empty string" };
109
119
  }
110
120
 
111
- // ── Verify all slices are complete ───────────────────────────────────────
112
- const slices = getMilestoneSlices(params.milestoneId);
113
- if (slices.length === 0) {
114
- return { error: `no slices found for milestone ${params.milestoneId}` };
121
+ // ── Verify that verification passed ─────────────────────────────────────
122
+ if (params.verificationPassed !== true) {
123
+ return { error: "verification did not pass — milestone completion blocked. verificationPassed must be explicitly set to true after all verification steps succeed" };
115
124
  }
116
125
 
117
- const incompleteSlices = slices.filter(s => s.status !== "complete" && s.status !== "done");
118
- if (incompleteSlices.length > 0) {
119
- const incompleteIds = incompleteSlices.map(s => `${s.id} (status: ${s.status})`).join(", ");
120
- return { error: `incomplete slices: ${incompleteIds}` };
121
- }
122
-
123
- // ── DB writes inside a transaction ──────────────────────────────────────
126
+ // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
124
127
  const completedAt = new Date().toISOString();
128
+ let guardError: string | null = null;
125
129
 
126
130
  transaction(() => {
131
+ // State machine preconditions (inside txn for atomicity)
132
+ const milestone = getMilestone(params.milestoneId);
133
+ if (!milestone) {
134
+ guardError = `milestone not found: ${params.milestoneId}`;
135
+ return;
136
+ }
137
+ if (milestone.status === "complete" || milestone.status === "done") {
138
+ guardError = `milestone ${params.milestoneId} is already complete`;
139
+ return;
140
+ }
141
+
142
+ // Verify all slices are complete
143
+ const slices = getMilestoneSlices(params.milestoneId);
144
+ if (slices.length === 0) {
145
+ guardError = `no slices found for milestone ${params.milestoneId}`;
146
+ return;
147
+ }
148
+
149
+ const incompleteSlices = slices.filter(s => s.status !== "complete" && s.status !== "done");
150
+ if (incompleteSlices.length > 0) {
151
+ const incompleteIds = incompleteSlices.map(s => `${s.id} (status: ${s.status})`).join(", ");
152
+ guardError = `incomplete slices: ${incompleteIds}`;
153
+ return;
154
+ }
155
+
156
+ // Deep check: verify all tasks in all slices are complete
157
+ for (const slice of slices) {
158
+ const tasks = getSliceTasks(params.milestoneId, slice.id);
159
+ const incompleteTasks = tasks.filter(t => t.status !== "complete" && t.status !== "done");
160
+ if (incompleteTasks.length > 0) {
161
+ const ids = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
162
+ guardError = `slice ${slice.id} has incomplete tasks: ${ids}`;
163
+ return;
164
+ }
165
+ }
166
+
167
+ // All guards passed — perform write
127
168
  const adapter = _getAdapter()!;
128
169
  adapter.prepare(
129
170
  `UPDATE milestones SET status = 'complete', completed_at = :completed_at WHERE id = :mid`,
@@ -133,6 +174,10 @@ export async function handleCompleteMilestone(
133
174
  });
134
175
  });
135
176
 
177
+ if (guardError) {
178
+ return { error: guardError };
179
+ }
180
+
136
181
  // ── Filesystem operations (outside transaction) ─────────────────────────
137
182
  const summaryMd = renderMilestoneSummaryMarkdown(params);
138
183
 
@@ -169,6 +214,24 @@ export async function handleCompleteMilestone(
169
214
  clearPathCache();
170
215
  clearParseCache();
171
216
 
217
+ // ── Post-mutation hook: projections, manifest, event log ───────────────
218
+ try {
219
+ await renderAllProjections(basePath, params.milestoneId);
220
+ writeManifest(basePath);
221
+ appendEvent(basePath, {
222
+ cmd: "complete-milestone",
223
+ params: { milestoneId: params.milestoneId },
224
+ ts: new Date().toISOString(),
225
+ actor: "agent",
226
+ actor_name: params.actorName,
227
+ trigger_reason: params.triggerReason,
228
+ });
229
+ } catch (hookErr) {
230
+ process.stderr.write(
231
+ `gsd: complete-milestone post-mutation hook warning: ${(hookErr as Error).message}\n`,
232
+ );
233
+ }
234
+
172
235
  return {
173
236
  milestoneId: params.milestoneId,
174
237
  summaryPath,