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,205 @@
1
+ // GSD Extension — workflow-events unit tests
2
+ // Tests appendEvent, readEvents, findForkPoint, compactMilestoneEvents.
3
+
4
+ import test from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import * as os from 'node:os';
9
+ import {
10
+ appendEvent,
11
+ readEvents,
12
+ findForkPoint,
13
+ compactMilestoneEvents,
14
+ type WorkflowEvent,
15
+ } from '../workflow-events.ts';
16
+
17
+ function tempDir(): string {
18
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-events-'));
19
+ }
20
+
21
+ function cleanupDir(dirPath: string): void {
22
+ try { fs.rmSync(dirPath, { recursive: true, force: true }); } catch { /* best effort */ }
23
+ }
24
+
25
+ function makeEvent(cmd: string, params: Record<string, unknown> = {}): Omit<WorkflowEvent, 'hash' | 'session_id'> {
26
+ return { cmd, params, ts: new Date().toISOString(), actor: 'agent' };
27
+ }
28
+
29
+ // ─── appendEvent ─────────────────────────────────────────────────────────
30
+
31
+ test('workflow-events: appendEvent creates .gsd dir and event-log.jsonl', () => {
32
+ const base = tempDir();
33
+ try {
34
+ appendEvent(base, makeEvent('complete-task', { milestoneId: 'M001', taskId: 'T01' }));
35
+ assert.ok(fs.existsSync(path.join(base, '.gsd', 'event-log.jsonl')));
36
+ } finally {
37
+ cleanupDir(base);
38
+ }
39
+ });
40
+
41
+ test('workflow-events: appendEvent writes valid JSON line', () => {
42
+ const base = tempDir();
43
+ try {
44
+ appendEvent(base, makeEvent('complete-task', { milestoneId: 'M001', taskId: 'T01' }));
45
+ const content = fs.readFileSync(path.join(base, '.gsd', 'event-log.jsonl'), 'utf-8');
46
+ const lines = content.trim().split('\n');
47
+ assert.strictEqual(lines.length, 1);
48
+ const parsed = JSON.parse(lines[0]!) as WorkflowEvent;
49
+ assert.strictEqual(parsed.cmd, 'complete-task');
50
+ assert.strictEqual(parsed.actor, 'agent');
51
+ assert.strictEqual(typeof parsed.hash, 'string');
52
+ assert.strictEqual(parsed.hash.length, 16);
53
+ } finally {
54
+ cleanupDir(base);
55
+ }
56
+ });
57
+
58
+ test('workflow-events: appendEvent appends multiple events', () => {
59
+ const base = tempDir();
60
+ try {
61
+ appendEvent(base, makeEvent('complete-task', { taskId: 'T01' }));
62
+ appendEvent(base, makeEvent('complete-slice', { sliceId: 'S01' }));
63
+ const events = readEvents(path.join(base, '.gsd', 'event-log.jsonl'));
64
+ assert.strictEqual(events.length, 2);
65
+ assert.strictEqual(events[0]!.cmd, 'complete-task');
66
+ assert.strictEqual(events[1]!.cmd, 'complete-slice');
67
+ } finally {
68
+ cleanupDir(base);
69
+ }
70
+ });
71
+
72
+ test('workflow-events: same cmd+params → same hash (deterministic)', () => {
73
+ const base = tempDir();
74
+ try {
75
+ appendEvent(base, makeEvent('plan-task', { milestoneId: 'M001', sliceId: 'S01' }));
76
+ appendEvent(base, makeEvent('plan-task', { milestoneId: 'M001', sliceId: 'S01' }));
77
+ const events = readEvents(path.join(base, '.gsd', 'event-log.jsonl'));
78
+ assert.strictEqual(events[0]!.hash, events[1]!.hash, 'identical cmd+params produce identical hash');
79
+ } finally {
80
+ cleanupDir(base);
81
+ }
82
+ });
83
+
84
+ test('workflow-events: different params → different hash', () => {
85
+ const base = tempDir();
86
+ try {
87
+ appendEvent(base, makeEvent('complete-task', { taskId: 'T01' }));
88
+ appendEvent(base, makeEvent('complete-task', { taskId: 'T02' }));
89
+ const events = readEvents(path.join(base, '.gsd', 'event-log.jsonl'));
90
+ assert.notStrictEqual(events[0]!.hash, events[1]!.hash, 'different params produce different hash');
91
+ } finally {
92
+ cleanupDir(base);
93
+ }
94
+ });
95
+
96
+ // ─── readEvents ──────────────────────────────────────────────────────────
97
+
98
+ test('workflow-events: readEvents returns [] for non-existent file', () => {
99
+ const result = readEvents('/nonexistent/path/event-log.jsonl');
100
+ assert.deepStrictEqual(result, []);
101
+ });
102
+
103
+ test('workflow-events: readEvents skips corrupted lines', () => {
104
+ const base = tempDir();
105
+ try {
106
+ fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
107
+ const logPath = path.join(base, '.gsd', 'event-log.jsonl');
108
+ // Write a valid line, a corrupted line, and another valid line
109
+ fs.writeFileSync(logPath,
110
+ '{"cmd":"complete-task","params":{},"ts":"2026-01-01T00:00:00Z","hash":"abcd1234abcd1234","actor":"agent"}\n' +
111
+ 'NOT VALID JSON {{{{\n' +
112
+ '{"cmd":"plan-task","params":{},"ts":"2026-01-01T00:00:01Z","hash":"1234abcd1234abcd","actor":"system"}\n',
113
+ );
114
+ const events = readEvents(logPath);
115
+ assert.strictEqual(events.length, 2, 'should return 2 valid events, skipping the corrupted line');
116
+ assert.strictEqual(events[0]!.cmd, 'complete-task');
117
+ assert.strictEqual(events[1]!.cmd, 'plan-task');
118
+ } finally {
119
+ cleanupDir(base);
120
+ }
121
+ });
122
+
123
+ // ─── findForkPoint ───────────────────────────────────────────────────────
124
+
125
+ test('workflow-events: findForkPoint returns -1 for two empty logs', () => {
126
+ assert.strictEqual(findForkPoint([], []), -1);
127
+ });
128
+
129
+ test('workflow-events: findForkPoint returns -1 when first events differ', () => {
130
+ const e1 = { cmd: 'a', params: {}, ts: '', hash: 'hash1', actor: 'agent' } as WorkflowEvent;
131
+ const e2 = { cmd: 'b', params: {}, ts: '', hash: 'hash2', actor: 'agent' } as WorkflowEvent;
132
+ assert.strictEqual(findForkPoint([e1], [e2]), -1);
133
+ });
134
+
135
+ test('workflow-events: findForkPoint returns 0 when only first event is common', () => {
136
+ const common = { cmd: 'a', params: {}, ts: '', hash: 'hash1', actor: 'agent' } as WorkflowEvent;
137
+ const eA = { cmd: 'b', params: {}, ts: '', hash: 'hash2', actor: 'agent' } as WorkflowEvent;
138
+ const eB = { cmd: 'c', params: {}, ts: '', hash: 'hash3', actor: 'agent' } as WorkflowEvent;
139
+ // logA: [common, eA], logB: [common, eB]
140
+ assert.strictEqual(findForkPoint([common, eA], [common, eB]), 0);
141
+ });
142
+
143
+ test('workflow-events: findForkPoint returns last common index for prefix relationship', () => {
144
+ const e1 = { cmd: 'a', params: {}, ts: '', hash: 'h1', actor: 'agent' } as WorkflowEvent;
145
+ const e2 = { cmd: 'b', params: {}, ts: '', hash: 'h2', actor: 'agent' } as WorkflowEvent;
146
+ const e3 = { cmd: 'c', params: {}, ts: '', hash: 'h3', actor: 'agent' } as WorkflowEvent;
147
+ // logA is a prefix of logB → fork point is last index of logA
148
+ assert.strictEqual(findForkPoint([e1, e2], [e1, e2, e3]), 1);
149
+ });
150
+
151
+ test('workflow-events: findForkPoint handles equal logs', () => {
152
+ const e1 = { cmd: 'a', params: {}, ts: '', hash: 'h1', actor: 'agent' } as WorkflowEvent;
153
+ const e2 = { cmd: 'b', params: {}, ts: '', hash: 'h2', actor: 'agent' } as WorkflowEvent;
154
+ assert.strictEqual(findForkPoint([e1, e2], [e1, e2]), 1);
155
+ });
156
+
157
+ // ─── compactMilestoneEvents ──────────────────────────────────────────────
158
+
159
+ test('workflow-events: compactMilestoneEvents returns { archived: 0 } when no matching events', () => {
160
+ const base = tempDir();
161
+ try {
162
+ appendEvent(base, makeEvent('complete-task', { milestoneId: 'M002', taskId: 'T01' }));
163
+ const result = compactMilestoneEvents(base, 'M001');
164
+ assert.strictEqual(result.archived, 0);
165
+ } finally {
166
+ cleanupDir(base);
167
+ }
168
+ });
169
+
170
+ test('workflow-events: compactMilestoneEvents archives milestone events', () => {
171
+ const base = tempDir();
172
+ try {
173
+ appendEvent(base, makeEvent('complete-task', { milestoneId: 'M001', taskId: 'T01' }));
174
+ appendEvent(base, makeEvent('complete-task', { milestoneId: 'M001', taskId: 'T02' }));
175
+ appendEvent(base, makeEvent('complete-task', { milestoneId: 'M002', taskId: 'T03' }));
176
+
177
+ const result = compactMilestoneEvents(base, 'M001');
178
+ assert.strictEqual(result.archived, 2, 'should archive 2 M001 events');
179
+
180
+ // Archive file should exist
181
+ const archivePath = path.join(base, '.gsd', 'event-log-M001.jsonl.archived');
182
+ assert.ok(fs.existsSync(archivePath), 'archive file should exist');
183
+ const archived = readEvents(archivePath);
184
+ assert.strictEqual(archived.length, 2, 'archive file should have 2 events');
185
+
186
+ // Active log should retain only M002 event
187
+ const active = readEvents(path.join(base, '.gsd', 'event-log.jsonl'));
188
+ assert.strictEqual(active.length, 1, 'active log should have 1 remaining event');
189
+ assert.strictEqual((active[0]!.params as { milestoneId?: string }).milestoneId, 'M002');
190
+ } finally {
191
+ cleanupDir(base);
192
+ }
193
+ });
194
+
195
+ test('workflow-events: compactMilestoneEvents empties active log when all events are from milestone', () => {
196
+ const base = tempDir();
197
+ try {
198
+ appendEvent(base, makeEvent('complete-task', { milestoneId: 'M001', taskId: 'T01' }));
199
+ compactMilestoneEvents(base, 'M001');
200
+ const active = readEvents(path.join(base, '.gsd', 'event-log.jsonl'));
201
+ assert.strictEqual(active.length, 0, 'active log should be empty after full compact');
202
+ } finally {
203
+ cleanupDir(base);
204
+ }
205
+ });
@@ -0,0 +1,275 @@
1
+ // GSD Extension — Workflow Logger Tests
2
+ // Tests for the centralized warning/error accumulator.
3
+
4
+ import { describe, test, beforeEach } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import {
7
+ logWarning,
8
+ logError,
9
+ drainLogs,
10
+ drainAndSummarize,
11
+ peekLogs,
12
+ hasErrors,
13
+ hasWarnings,
14
+ hasAnyIssues,
15
+ summarizeLogs,
16
+ formatForNotification,
17
+ _resetLogs,
18
+ } from "../workflow-logger.ts";
19
+
20
+ const ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
21
+
22
+ describe("workflow-logger", () => {
23
+ beforeEach(() => {
24
+ _resetLogs();
25
+ });
26
+
27
+ describe("accumulation", () => {
28
+ test("logWarning adds an entry with severity warn", () => {
29
+ logWarning("engine", "test warning");
30
+ const entries = peekLogs();
31
+ assert.equal(entries.length, 1);
32
+ assert.equal(entries[0].severity, "warn");
33
+ assert.equal(entries[0].component, "engine");
34
+ assert.equal(entries[0].message, "test warning");
35
+ assert.match(entries[0].ts, ISO_RE);
36
+ });
37
+
38
+ test("logError adds an entry with severity error", () => {
39
+ logError("intercept", "blocked write", { path: "/foo/STATE.md" });
40
+ const entries = peekLogs();
41
+ assert.equal(entries.length, 1);
42
+ assert.equal(entries[0].severity, "error");
43
+ assert.equal(entries[0].component, "intercept");
44
+ assert.deepEqual(entries[0].context, { path: "/foo/STATE.md" });
45
+ });
46
+
47
+ test("accumulates multiple entries in order", () => {
48
+ logWarning("projection", "render failed");
49
+ logError("intercept", "blocked write");
50
+ logWarning("manifest", "write failed");
51
+ assert.equal(peekLogs().length, 3);
52
+ assert.equal(peekLogs()[0].component, "projection");
53
+ assert.equal(peekLogs()[1].component, "intercept");
54
+ assert.equal(peekLogs()[2].component, "manifest");
55
+ });
56
+
57
+ test("omits context field when not provided", () => {
58
+ logWarning("engine", "no context");
59
+ assert.equal("context" in peekLogs()[0], false);
60
+ });
61
+
62
+ test("omits context field when undefined is passed", () => {
63
+ logWarning("engine", "no context", undefined);
64
+ assert.equal("context" in peekLogs()[0], false);
65
+ });
66
+
67
+ test("context with special characters is stored as-is", () => {
68
+ logError("tool", "failed", { path: '/foo/"quoted".md', msg: "line1\nline2" });
69
+ assert.deepEqual(peekLogs()[0].context, {
70
+ path: '/foo/"quoted".md',
71
+ msg: "line1\nline2",
72
+ });
73
+ });
74
+
75
+ test("ts field is a valid ISO 8601 timestamp", () => {
76
+ logWarning("engine", "ts check");
77
+ assert.match(peekLogs()[0].ts, ISO_RE);
78
+ });
79
+ });
80
+
81
+ describe("drain", () => {
82
+ test("returns all entries and clears buffer", () => {
83
+ logWarning("engine", "w1");
84
+ logError("engine", "e1");
85
+ const drained = drainLogs();
86
+ assert.equal(drained.length, 2);
87
+ assert.equal(peekLogs().length, 0);
88
+ });
89
+
90
+ test("returns empty array when no entries", () => {
91
+ assert.deepEqual(drainLogs(), []);
92
+ });
93
+
94
+ test("second drain returns empty array", () => {
95
+ logWarning("engine", "w1");
96
+ drainLogs();
97
+ assert.deepEqual(drainLogs(), []);
98
+ });
99
+ });
100
+
101
+ describe("drainAndSummarize", () => {
102
+ test("returns summary and clears buffer atomically", () => {
103
+ logError("intercept", "blocked");
104
+ logWarning("projection", "render failed");
105
+ const { logs, summary } = drainAndSummarize();
106
+ assert.equal(logs.length, 2);
107
+ assert.equal(peekLogs().length, 0);
108
+ assert.ok(summary?.includes("1 error(s)"));
109
+ assert.ok(summary?.includes("1 warning(s)"));
110
+ });
111
+
112
+ test("returns null summary when buffer is empty", () => {
113
+ const { logs, summary } = drainAndSummarize();
114
+ assert.deepEqual(logs, []);
115
+ assert.equal(summary, null);
116
+ });
117
+ });
118
+
119
+ describe("hasErrors / hasWarnings / hasAnyIssues", () => {
120
+ test("hasErrors returns false when only warnings", () => {
121
+ logWarning("engine", "just a warning");
122
+ assert.equal(hasErrors(), false);
123
+ assert.equal(hasWarnings(), true);
124
+ });
125
+
126
+ test("hasErrors returns true when errors present", () => {
127
+ logWarning("engine", "warning");
128
+ logError("intercept", "error");
129
+ assert.equal(hasErrors(), true);
130
+ });
131
+
132
+ test("hasWarnings returns false when buffer empty", () => {
133
+ assert.equal(hasWarnings(), false);
134
+ });
135
+
136
+ test("hasWarnings returns false when buffer contains only errors", () => {
137
+ logError("intercept", "only an error");
138
+ assert.equal(hasWarnings(), false);
139
+ assert.equal(hasErrors(), true);
140
+ });
141
+
142
+ test("hasAnyIssues returns true for warnings only", () => {
143
+ logWarning("engine", "warn");
144
+ assert.equal(hasAnyIssues(), true);
145
+ });
146
+
147
+ test("hasAnyIssues returns true for errors only", () => {
148
+ logError("engine", "err");
149
+ assert.equal(hasAnyIssues(), true);
150
+ });
151
+
152
+ test("hasAnyIssues returns false when buffer empty", () => {
153
+ assert.equal(hasAnyIssues(), false);
154
+ });
155
+ });
156
+
157
+ describe("summarizeLogs", () => {
158
+ test("returns null when empty", () => {
159
+ assert.equal(summarizeLogs(), null);
160
+ });
161
+
162
+ test("summarizes errors and warnings separately", () => {
163
+ logError("intercept", "blocked STATE.md");
164
+ logWarning("projection", "render failed");
165
+ logWarning("manifest", "write failed");
166
+ const summary = summarizeLogs()!;
167
+ assert.ok(summary.includes("1 error(s)"));
168
+ assert.ok(summary.includes("blocked STATE.md"));
169
+ assert.ok(summary.includes("2 warning(s)"));
170
+ });
171
+
172
+ test("only shows errors section when no warnings", () => {
173
+ logError("intercept", "blocked");
174
+ const summary = summarizeLogs()!;
175
+ assert.ok(summary.includes("1 error(s)"));
176
+ assert.ok(!summary.includes("warning"));
177
+ });
178
+
179
+ test("only shows warnings section when no errors", () => {
180
+ logWarning("projection", "render degraded");
181
+ logWarning("manifest", "write slow");
182
+ const summary = summarizeLogs()!;
183
+ assert.ok(summary.includes("2 warning(s)"));
184
+ assert.ok(!summary.includes("error"));
185
+ });
186
+
187
+ test("does not clear buffer", () => {
188
+ logError("intercept", "blocked");
189
+ summarizeLogs();
190
+ assert.equal(peekLogs().length, 1);
191
+ });
192
+ });
193
+
194
+ describe("formatForNotification", () => {
195
+ test("returns empty string for empty array", () => {
196
+ assert.equal(formatForNotification([]), "");
197
+ });
198
+
199
+ test("formats single entry without line breaks", () => {
200
+ logError("intercept", "blocked write");
201
+ const entries = drainLogs();
202
+ const formatted = formatForNotification(entries);
203
+ assert.equal(formatted, "[intercept] blocked write");
204
+ });
205
+
206
+ test("formats multiple entries with line breaks", () => {
207
+ logWarning("projection", "render failed");
208
+ logError("intercept", "blocked write");
209
+ const entries = drainLogs();
210
+ const formatted = formatForNotification(entries);
211
+ assert.ok(formatted.includes("[projection] render failed"));
212
+ assert.ok(formatted.includes("[intercept] blocked write"));
213
+ assert.ok(formatted.includes("\n"));
214
+ });
215
+
216
+ test("does not include context in formatted output", () => {
217
+ logError("tool", "failed", { cmd: "complete_task" });
218
+ const entries = drainLogs();
219
+ const formatted = formatForNotification(entries);
220
+ assert.equal(formatted, "[tool] failed");
221
+ assert.ok(!formatted.includes("complete_task"));
222
+ });
223
+ });
224
+
225
+ describe("buffer limit", () => {
226
+ test("caps at MAX_BUFFER entries, dropping oldest", () => {
227
+ const OVER = 110;
228
+ const MAX = 100;
229
+ for (let i = 0; i < OVER; i++) {
230
+ logWarning("engine", `msg-${i}`);
231
+ }
232
+ const entries = peekLogs();
233
+ assert.equal(entries.length, MAX);
234
+ // First MAX entries dropped; oldest surviving = msg-(OVER-MAX)
235
+ assert.equal(entries[0].message, `msg-${OVER - MAX}`);
236
+ assert.equal(entries[MAX - 1].message, `msg-${OVER - 1}`);
237
+ });
238
+ });
239
+
240
+ describe("stderr output", () => {
241
+ test("writes WARN prefix to stderr for warnings", (t) => {
242
+ const written: string[] = [];
243
+ const orig = process.stderr.write.bind(process.stderr);
244
+ // @ts-ignore — patching for test
245
+ process.stderr.write = (chunk: string) => { written.push(chunk); return true; };
246
+ t.after(() => { process.stderr.write = orig; });
247
+
248
+ logWarning("engine", "test warn");
249
+ assert.equal(written.length, 1);
250
+ assert.ok(written[0].includes("[gsd:engine] WARN: test warn"));
251
+ });
252
+
253
+ test("writes ERROR prefix to stderr for errors", (t) => {
254
+ const written: string[] = [];
255
+ const orig = process.stderr.write.bind(process.stderr);
256
+ // @ts-ignore — patching for test
257
+ process.stderr.write = (chunk: string) => { written.push(chunk); return true; };
258
+ t.after(() => { process.stderr.write = orig; });
259
+
260
+ logError("intercept", "blocked");
261
+ assert.ok(written[0].includes("[gsd:intercept] ERROR: blocked"));
262
+ });
263
+
264
+ test("includes serialized context in stderr output", (t) => {
265
+ const written: string[] = [];
266
+ const orig = process.stderr.write.bind(process.stderr);
267
+ // @ts-ignore — patching for test
268
+ process.stderr.write = (chunk: string) => { written.push(chunk); return true; };
269
+ t.after(() => { process.stderr.write = orig; });
270
+
271
+ logError("tool", "failed", { cmd: "complete_task" });
272
+ assert.ok(written[0].includes('"cmd":"complete_task"'));
273
+ });
274
+ });
275
+ });
@@ -0,0 +1,186 @@
1
+ // GSD Extension — workflow-manifest unit tests
2
+ // Tests writeManifest, readManifest, snapshotState, bootstrapFromManifest.
3
+
4
+ import test from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import * as os from 'node:os';
9
+ import {
10
+ openDatabase,
11
+ closeDatabase,
12
+ insertMilestone,
13
+ insertSlice,
14
+ insertTask,
15
+ } from '../gsd-db.ts';
16
+ import {
17
+ writeManifest,
18
+ readManifest,
19
+ snapshotState,
20
+ bootstrapFromManifest,
21
+ } from '../workflow-manifest.ts';
22
+
23
+ function tempDir(): string {
24
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-manifest-'));
25
+ }
26
+
27
+ function tempDbPath(base: string): string {
28
+ return path.join(base, 'test.db');
29
+ }
30
+
31
+ function cleanupDir(dirPath: string): void {
32
+ try { fs.rmSync(dirPath, { recursive: true, force: true }); } catch { /* best effort */ }
33
+ }
34
+
35
+ // ─── readManifest: no file ────────────────────────────────────────────────
36
+
37
+ test('workflow-manifest: readManifest returns null when file does not exist', () => {
38
+ const base = tempDir();
39
+ try {
40
+ const result = readManifest(base);
41
+ assert.strictEqual(result, null);
42
+ } finally {
43
+ cleanupDir(base);
44
+ }
45
+ });
46
+
47
+ // ─── writeManifest + readManifest round-trip ─────────────────────────────
48
+
49
+ test('workflow-manifest: writeManifest creates state-manifest.json with version 1', () => {
50
+ const base = tempDir();
51
+ openDatabase(tempDbPath(base));
52
+ try {
53
+ writeManifest(base);
54
+ const manifestPath = path.join(base, '.gsd', 'state-manifest.json');
55
+ assert.ok(fs.existsSync(manifestPath), 'state-manifest.json should exist');
56
+ const raw = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
57
+ assert.strictEqual(raw.version, 1);
58
+ } finally {
59
+ closeDatabase();
60
+ cleanupDir(base);
61
+ }
62
+ });
63
+
64
+ test('workflow-manifest: readManifest parses manifest written by writeManifest', () => {
65
+ const base = tempDir();
66
+ openDatabase(tempDbPath(base));
67
+ try {
68
+ writeManifest(base);
69
+ const manifest = readManifest(base);
70
+ assert.ok(manifest !== null);
71
+ assert.strictEqual(manifest!.version, 1);
72
+ assert.ok(typeof manifest!.exported_at === 'string');
73
+ assert.ok(Array.isArray(manifest!.milestones));
74
+ assert.ok(Array.isArray(manifest!.slices));
75
+ assert.ok(Array.isArray(manifest!.tasks));
76
+ assert.ok(Array.isArray(manifest!.decisions));
77
+ assert.ok(Array.isArray(manifest!.verification_evidence));
78
+ } finally {
79
+ closeDatabase();
80
+ cleanupDir(base);
81
+ }
82
+ });
83
+
84
+ // ─── snapshotState: captures DB rows ─────────────────────────────────────
85
+
86
+ test('workflow-manifest: snapshotState includes inserted milestone', () => {
87
+ const base = tempDir();
88
+ openDatabase(tempDbPath(base));
89
+ try {
90
+ insertMilestone({ id: 'M001', title: 'Auth Milestone' });
91
+ const snap = snapshotState();
92
+ assert.strictEqual(snap.version, 1);
93
+ const m = snap.milestones.find((r) => r.id === 'M001');
94
+ assert.ok(m !== undefined, 'M001 should appear in snapshot');
95
+ assert.strictEqual(m!.title, 'Auth Milestone');
96
+ } finally {
97
+ closeDatabase();
98
+ cleanupDir(base);
99
+ }
100
+ });
101
+
102
+ test('workflow-manifest: snapshotState captures tasks', () => {
103
+ const base = tempDir();
104
+ openDatabase(tempDbPath(base));
105
+ try {
106
+ insertMilestone({ id: 'M001' });
107
+ insertSlice({ id: 'S01', milestoneId: 'M001' });
108
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Do thing', status: 'complete' });
109
+ const snap = snapshotState();
110
+ const t = snap.tasks.find((r) => r.id === 'T01');
111
+ assert.ok(t !== undefined, 'T01 should appear in snapshot');
112
+ assert.strictEqual(t!.status, 'complete');
113
+ } finally {
114
+ closeDatabase();
115
+ cleanupDir(base);
116
+ }
117
+ });
118
+
119
+ // ─── bootstrapFromManifest ────────────────────────────────────────────────
120
+
121
+ test('workflow-manifest: bootstrapFromManifest returns false when no manifest file', () => {
122
+ const base = tempDir();
123
+ openDatabase(tempDbPath(base));
124
+ try {
125
+ const result = bootstrapFromManifest(base);
126
+ assert.strictEqual(result, false);
127
+ } finally {
128
+ closeDatabase();
129
+ cleanupDir(base);
130
+ }
131
+ });
132
+
133
+ test('workflow-manifest: bootstrapFromManifest restores DB from manifest (round-trip)', () => {
134
+ const base = tempDir();
135
+ openDatabase(tempDbPath(base));
136
+ try {
137
+ // Insert data and write manifest
138
+ insertMilestone({ id: 'M001', title: 'Restored Milestone' });
139
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Restored Slice' });
140
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Restored Task', status: 'complete' });
141
+ writeManifest(base);
142
+ closeDatabase();
143
+
144
+ // Open a fresh DB and bootstrap from manifest
145
+ const newDbPath = path.join(base, 'new.db');
146
+ openDatabase(newDbPath);
147
+ const result = bootstrapFromManifest(base);
148
+ assert.strictEqual(result, true, 'bootstrapFromManifest should return true');
149
+
150
+ // Verify restored state
151
+ const snap = snapshotState();
152
+ const m = snap.milestones.find((r) => r.id === 'M001');
153
+ assert.ok(m !== undefined, 'M001 should be restored');
154
+ assert.strictEqual(m!.title, 'Restored Milestone');
155
+
156
+ const s = snap.slices.find((r) => r.id === 'S01');
157
+ assert.ok(s !== undefined, 'S01 should be restored');
158
+
159
+ const t = snap.tasks.find((r) => r.id === 'T01');
160
+ assert.ok(t !== undefined, 'T01 should be restored');
161
+ assert.strictEqual(t!.status, 'complete');
162
+ } finally {
163
+ closeDatabase();
164
+ cleanupDir(base);
165
+ }
166
+ });
167
+
168
+ // ─── readManifest: version check ─────────────────────────────────────────
169
+
170
+ test('workflow-manifest: readManifest throws on unsupported version', () => {
171
+ const base = tempDir();
172
+ try {
173
+ fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
174
+ fs.writeFileSync(
175
+ path.join(base, '.gsd', 'state-manifest.json'),
176
+ JSON.stringify({ version: 99, exported_at: '', milestones: [], slices: [], tasks: [], decisions: [], verification_evidence: [] }),
177
+ );
178
+ assert.throws(
179
+ () => readManifest(base),
180
+ /Unsupported manifest version/,
181
+ 'should throw on version mismatch',
182
+ );
183
+ } finally {
184
+ cleanupDir(base);
185
+ }
186
+ });