gsd-pi 2.44.0-dev.848dd4c → 2.44.0

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 (298) hide show
  1. package/README.md +12 -30
  2. package/dist/resources/extensions/gsd/auto-start.js +0 -10
  3. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +0 -5
  4. package/dist/web/standalone/.next/BUILD_ID +1 -1
  5. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  6. package/dist/web/standalone/.next/build-manifest.json +3 -3
  7. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  8. package/dist/web/standalone/.next/required-server-files.json +3 -3
  9. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  10. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  12. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  20. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  30. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  31. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  32. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  33. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  34. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  35. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  36. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  37. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  38. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  39. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  40. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  74. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  80. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  94. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  96. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  98. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  100. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/index.html +1 -1
  110. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  111. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  112. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  113. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  115. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/page.js +2 -2
  117. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  119. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  120. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  121. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/middleware.js +2 -2
  123. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  125. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  126. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  127. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  128. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
  129. package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
  130. package/dist/web/standalone/.next/static/chunks/app/page-7e9530a7122506c5.js +1 -0
  131. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
  132. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
  133. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  134. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  135. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  136. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  137. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  138. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  139. package/dist/web/standalone/server.js +1 -1
  140. package/package.json +1 -1
  141. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +8 -6
  142. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +26 -24
  144. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +48 -29
  146. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +44 -34
  148. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/session-manager.test.js +34 -30
  150. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +12 -10
  152. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  153. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +47 -43
  154. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
  155. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
  156. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
  157. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +43 -31
  158. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +45 -40
  159. package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
  160. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
  161. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
  162. package/src/resources/extensions/gsd/auto-start.ts +0 -14
  163. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +0 -8
  164. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
  165. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +16 -14
  166. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +57 -43
  167. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +13 -11
  168. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +523 -465
  169. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +75 -73
  170. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +56 -34
  171. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +656 -533
  172. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +143 -165
  173. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +52 -29
  174. package/src/resources/extensions/gsd/tests/captures.test.ts +176 -148
  175. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +33 -32
  176. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +143 -141
  177. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
  178. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
  179. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +59 -38
  180. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +263 -228
  181. package/src/resources/extensions/gsd/tests/complete-task.test.ts +302 -250
  182. package/src/resources/extensions/gsd/tests/context-store.test.ts +367 -354
  183. package/src/resources/extensions/gsd/tests/continue-here.test.ts +72 -68
  184. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +106 -92
  185. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +35 -27
  186. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +237 -220
  187. package/src/resources/extensions/gsd/tests/db-writer.test.ts +420 -390
  188. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +92 -76
  189. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +83 -68
  190. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +183 -152
  191. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +101 -78
  192. package/src/resources/extensions/gsd/tests/derive-state.test.ts +227 -192
  193. package/src/resources/extensions/gsd/tests/detection.test.ts +278 -232
  194. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +34 -30
  195. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +180 -164
  196. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +49 -43
  197. package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +32 -28
  198. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +29 -27
  199. package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +38 -34
  200. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +75 -54
  201. package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +32 -21
  202. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +97 -72
  203. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +44 -38
  204. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +145 -104
  205. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +106 -84
  206. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +60 -54
  207. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +93 -72
  208. package/src/resources/extensions/gsd/tests/doctor.test.ts +134 -104
  209. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +131 -123
  210. package/src/resources/extensions/gsd/tests/exit-command.test.ts +24 -20
  211. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +57 -48
  212. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +7 -5
  213. package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +42 -30
  214. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +206 -198
  215. package/src/resources/extensions/gsd/tests/git-locale.test.ts +27 -13
  216. package/src/resources/extensions/gsd/tests/git-service.test.ts +388 -285
  217. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +39 -31
  218. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +69 -63
  219. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +264 -255
  220. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +119 -108
  221. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +103 -81
  222. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +262 -229
  223. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
  224. package/src/resources/extensions/gsd/tests/health-widget.test.ts +37 -29
  225. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +102 -81
  226. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +18 -16
  227. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +46 -41
  228. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +53 -42
  229. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +91 -75
  230. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
  231. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +194 -150
  232. package/src/resources/extensions/gsd/tests/md-importer.test.ts +125 -101
  233. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +54 -45
  234. package/src/resources/extensions/gsd/tests/memory-store.test.ts +93 -80
  235. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +66 -57
  236. package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +93 -83
  237. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +170 -161
  238. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +141 -125
  239. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +131 -107
  240. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +96 -87
  241. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +164 -125
  242. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +94 -81
  243. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +36 -35
  244. package/src/resources/extensions/gsd/tests/overrides.test.ts +106 -99
  245. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +47 -40
  246. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +28 -25
  247. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +83 -66
  248. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +77 -54
  249. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +115 -68
  250. package/src/resources/extensions/gsd/tests/parsers.test.ts +611 -546
  251. package/src/resources/extensions/gsd/tests/paths.test.ts +87 -72
  252. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +117 -77
  253. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
  254. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +119 -93
  255. package/src/resources/extensions/gsd/tests/queue-order.test.ts +82 -70
  256. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +55 -42
  257. package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +73 -45
  258. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +38 -28
  259. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +80 -73
  260. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +74 -71
  261. package/src/resources/extensions/gsd/tests/requirements.test.ts +75 -70
  262. package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +66 -44
  263. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +181 -114
  264. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +65 -63
  265. package/src/resources/extensions/gsd/tests/run-uat.test.ts +128 -66
  266. package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +25 -18
  267. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +44 -37
  268. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +26 -19
  269. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +8 -6
  270. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +28 -22
  271. package/src/resources/extensions/gsd/tests/token-savings.test.ts +56 -54
  272. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +25 -23
  273. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +11 -9
  274. package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +82 -66
  275. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +47 -46
  276. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +22 -20
  277. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +86 -84
  278. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +43 -41
  279. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +96 -94
  280. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +13 -11
  281. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +29 -27
  282. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +52 -50
  283. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +13 -10
  284. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +18 -14
  285. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +39 -38
  286. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +21 -17
  287. package/src/resources/extensions/gsd/tests/worktree-health.test.ts +30 -25
  288. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +37 -30
  289. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +22 -15
  290. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +66 -59
  291. package/src/resources/extensions/gsd/tests/worktree.test.ts +50 -44
  292. package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +0 -1
  293. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
  294. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
  295. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +0 -100
  296. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +0 -63
  297. /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → mgkxN0mGP6gSUmGPEzbk_}/_buildManifest.js +0 -0
  298. /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → mgkxN0mGP6gSUmGPEzbk_}/_ssgManifest.js +0 -0
@@ -1,5 +1,4 @@
1
- import { describe, test } from 'node:test';
2
- import assert from 'node:assert/strict';
1
+ import { createTestContext } from './test-helpers.ts';
3
2
  import * as path from 'node:path';
4
3
  import * as os from 'node:os';
5
4
  import * as fs from 'node:fs';
@@ -27,6 +26,8 @@ import {
27
26
  } from '../db-writer.ts';
28
27
  import type { Decision, Requirement } from '../types.ts';
29
28
 
29
+ const { assertEq, assertTrue, assertMatch, report } = createTestContext();
30
+
30
31
  // ═══════════════════════════════════════════════════════════════════════════
31
32
  // Helpers
32
33
  // ═══════════════════════════════════════════════════════════════════════════
@@ -150,433 +151,462 @@ const SAMPLE_REQUIREMENTS: Requirement[] = [
150
151
  // Round-Trip Tests: Decisions
151
152
  // ═══════════════════════════════════════════════════════════════════════════
152
153
 
153
- describe('db-writer', () => {
154
- test('generateDecisionsMd round-trip', () => {
155
- const md = generateDecisionsMd(SAMPLE_DECISIONS);
156
- const parsed = parseDecisionsTable(md);
157
-
158
- assert.deepStrictEqual(parsed.length, SAMPLE_DECISIONS.length, 'decisions count matches');
159
-
160
- for (let i = 0; i < SAMPLE_DECISIONS.length; i++) {
161
- const orig = SAMPLE_DECISIONS[i];
162
- const rt = parsed[i];
163
- assert.deepStrictEqual(rt.id, orig.id, `decision ${orig.id} id round-trips`);
164
- assert.deepStrictEqual(rt.when_context, orig.when_context, `decision ${orig.id} when_context round-trips`);
165
- assert.deepStrictEqual(rt.scope, orig.scope, `decision ${orig.id} scope round-trips`);
166
- assert.deepStrictEqual(rt.decision, orig.decision, `decision ${orig.id} decision round-trips`);
167
- assert.deepStrictEqual(rt.choice, orig.choice, `decision ${orig.id} choice round-trips`);
168
- assert.deepStrictEqual(rt.rationale, orig.rationale, `decision ${orig.id} rationale round-trips`);
169
- assert.deepStrictEqual(rt.revisable, orig.revisable, `decision ${orig.id} revisable round-trips`);
170
- assert.deepStrictEqual(rt.made_by, orig.made_by, `decision ${orig.id} made_by round-trips`);
171
- }
172
- });
154
+ console.log('\n── generateDecisionsMd round-trip ──');
155
+
156
+ {
157
+ const md = generateDecisionsMd(SAMPLE_DECISIONS);
158
+ const parsed = parseDecisionsTable(md);
159
+
160
+ assertEq(parsed.length, SAMPLE_DECISIONS.length, 'decisions count matches');
161
+
162
+ for (let i = 0; i < SAMPLE_DECISIONS.length; i++) {
163
+ const orig = SAMPLE_DECISIONS[i];
164
+ const rt = parsed[i];
165
+ assertEq(rt.id, orig.id, `decision ${orig.id} id round-trips`);
166
+ assertEq(rt.when_context, orig.when_context, `decision ${orig.id} when_context round-trips`);
167
+ assertEq(rt.scope, orig.scope, `decision ${orig.id} scope round-trips`);
168
+ assertEq(rt.decision, orig.decision, `decision ${orig.id} decision round-trips`);
169
+ assertEq(rt.choice, orig.choice, `decision ${orig.id} choice round-trips`);
170
+ assertEq(rt.rationale, orig.rationale, `decision ${orig.id} rationale round-trips`);
171
+ assertEq(rt.revisable, orig.revisable, `decision ${orig.id} revisable round-trips`);
172
+ assertEq(rt.made_by, orig.made_by, `decision ${orig.id} made_by round-trips`);
173
+ }
174
+ }
173
175
 
174
- test('generateDecisionsMd format', () => {
175
- const md = generateDecisionsMd(SAMPLE_DECISIONS);
176
- assert.ok(md.startsWith('# Decisions Register\n'), 'starts with H1 header');
177
- assert.ok(md.includes('<!-- Append-only'), 'contains HTML comment block');
178
- assert.ok(md.includes('| # | When | Scope'), 'contains table header');
179
- assert.ok(md.includes('|---|------|-------'), 'contains separator row');
180
- assert.ok(md.includes('| Made By |'), 'contains Made By column header');
181
- });
176
+ console.log('\n── generateDecisionsMd format ──');
182
177
 
183
- test('generateDecisionsMd empty input', () => {
184
- const md = generateDecisionsMd([]);
185
- const parsed = parseDecisionsTable(md);
186
- assert.deepStrictEqual(parsed.length, 0, 'empty decisions produces empty parse');
187
- assert.ok(md.includes('| # | When | Scope'), 'still has table header even when empty');
188
- });
178
+ {
179
+ const md = generateDecisionsMd(SAMPLE_DECISIONS);
180
+ assertTrue(md.startsWith('# Decisions Register\n'), 'starts with H1 header');
181
+ assertTrue(md.includes('<!-- Append-only'), 'contains HTML comment block');
182
+ assertTrue(md.includes('| # | When | Scope'), 'contains table header');
183
+ assertTrue(md.includes('|---|------|-------'), 'contains separator row');
184
+ assertTrue(md.includes('| Made By |'), 'contains Made By column header');
185
+ }
189
186
 
190
- test('generateDecisionsMd pipe escaping', () => {
191
- const withPipe: Decision = {
192
- seq: 1,
193
- id: 'D001',
194
- when_context: 'M001',
195
- scope: 'arch',
196
- decision: 'Choice A | Choice B comparison',
197
- choice: 'A',
198
- rationale: 'Better',
199
- revisable: 'No',
200
- made_by: 'agent',
201
- superseded_by: null,
202
- };
203
- const md = generateDecisionsMd([withPipe]);
204
- // Should not break the table — pipe in decision text should be escaped
205
- const parsed = parseDecisionsTable(md);
206
- assert.ok(parsed.length >= 1, 'pipe-containing decision parses without breaking table');
207
- });
187
+ console.log('\n── generateDecisionsMd empty input ──');
188
+
189
+ {
190
+ const md = generateDecisionsMd([]);
191
+ const parsed = parseDecisionsTable(md);
192
+ assertEq(parsed.length, 0, 'empty decisions produces empty parse');
193
+ assertTrue(md.includes('| # | When | Scope'), 'still has table header even when empty');
194
+ }
195
+
196
+ console.log('\n── generateDecisionsMd pipe escaping ──');
208
197
 
209
- // ═══════════════════════════════════════════════════════════════════════════
210
- // Round-Trip Tests: Requirements
211
- // ═══════════════════════════════════════════════════════════════════════════
212
-
213
- test('generateRequirementsMd round-trip', () => {
214
- const md = generateRequirementsMd(SAMPLE_REQUIREMENTS);
215
- const parsed = parseRequirementsSections(md);
216
-
217
- assert.deepStrictEqual(parsed.length, SAMPLE_REQUIREMENTS.length, 'requirements count matches');
218
-
219
- for (const orig of SAMPLE_REQUIREMENTS) {
220
- const rt = parsed.find(r => r.id === orig.id);
221
- assert.ok(!!rt, `requirement ${orig.id} found in parsed output`);
222
- if (rt) {
223
- assert.deepStrictEqual(rt.class, orig.class, `requirement ${orig.id} class round-trips`);
224
- assert.deepStrictEqual(rt.description, orig.description, `requirement ${orig.id} description round-trips`);
225
- assert.deepStrictEqual(rt.why, orig.why, `requirement ${orig.id} why round-trips`);
226
- assert.deepStrictEqual(rt.source, orig.source, `requirement ${orig.id} source round-trips`);
227
- assert.deepStrictEqual(rt.primary_owner, orig.primary_owner, `requirement ${orig.id} primary_owner round-trips`);
228
- assert.deepStrictEqual(rt.supporting_slices, orig.supporting_slices, `requirement ${orig.id} supporting_slices round-trips`);
229
- if (orig.notes) {
230
- assert.deepStrictEqual(rt.notes, orig.notes, `requirement ${orig.id} notes round-trips`);
231
- }
198
+ {
199
+ const withPipe: Decision = {
200
+ seq: 1,
201
+ id: 'D001',
202
+ when_context: 'M001',
203
+ scope: 'arch',
204
+ decision: 'Choice A | Choice B comparison',
205
+ choice: 'A',
206
+ rationale: 'Better',
207
+ revisable: 'No',
208
+ made_by: 'agent',
209
+ superseded_by: null,
210
+ };
211
+ const md = generateDecisionsMd([withPipe]);
212
+ // Should not break the table — pipe in decision text should be escaped
213
+ const parsed = parseDecisionsTable(md);
214
+ assertTrue(parsed.length >= 1, 'pipe-containing decision parses without breaking table');
215
+ }
216
+
217
+ // ═══════════════════════════════════════════════════════════════════════════
218
+ // Round-Trip Tests: Requirements
219
+ // ═══════════════════════════════════════════════════════════════════════════
220
+
221
+ console.log('\n── generateRequirementsMd round-trip ──');
222
+
223
+ {
224
+ const md = generateRequirementsMd(SAMPLE_REQUIREMENTS);
225
+ const parsed = parseRequirementsSections(md);
226
+
227
+ assertEq(parsed.length, SAMPLE_REQUIREMENTS.length, 'requirements count matches');
228
+
229
+ for (const orig of SAMPLE_REQUIREMENTS) {
230
+ const rt = parsed.find(r => r.id === orig.id);
231
+ assertTrue(!!rt, `requirement ${orig.id} found in parsed output`);
232
+ if (rt) {
233
+ assertEq(rt.class, orig.class, `requirement ${orig.id} class round-trips`);
234
+ assertEq(rt.description, orig.description, `requirement ${orig.id} description round-trips`);
235
+ assertEq(rt.why, orig.why, `requirement ${orig.id} why round-trips`);
236
+ assertEq(rt.source, orig.source, `requirement ${orig.id} source round-trips`);
237
+ assertEq(rt.primary_owner, orig.primary_owner, `requirement ${orig.id} primary_owner round-trips`);
238
+ assertEq(rt.supporting_slices, orig.supporting_slices, `requirement ${orig.id} supporting_slices round-trips`);
239
+ if (orig.notes) {
240
+ assertEq(rt.notes, orig.notes, `requirement ${orig.id} notes round-trips`);
232
241
  }
233
242
  }
234
- });
243
+ }
244
+ }
235
245
 
236
- test('generateRequirementsMd sections', () => {
237
- const md = generateRequirementsMd(SAMPLE_REQUIREMENTS);
238
- assert.ok(md.includes('## Active'), 'has Active section');
239
- assert.ok(md.includes('## Validated'), 'has Validated section');
240
- assert.ok(md.includes('## Deferred'), 'has Deferred section');
241
- assert.ok(md.includes('## Out of Scope'), 'has Out of Scope section');
242
- assert.ok(md.includes('## Traceability'), 'has Traceability section');
243
- assert.ok(md.includes('## Coverage Summary'), 'has Coverage Summary section');
244
- });
246
+ console.log('\n── generateRequirementsMd sections ──');
245
247
 
246
- test('generateRequirementsMd only populated sections', () => {
247
- // Only active requirements — should only have Active section
248
- const activeOnly = SAMPLE_REQUIREMENTS.filter(r => r.status === 'active');
249
- const md = generateRequirementsMd(activeOnly);
250
- assert.ok(md.includes('## Active'), 'has Active section');
251
- assert.ok(!md.includes('## Validated'), 'no Validated section when no validated reqs');
252
- assert.ok(!md.includes('## Deferred'), 'no Deferred section when no deferred reqs');
253
- assert.ok(!md.includes('## Out of Scope'), 'no Out of Scope section when no out-of-scope reqs');
254
- });
248
+ {
249
+ const md = generateRequirementsMd(SAMPLE_REQUIREMENTS);
250
+ assertTrue(md.includes('## Active'), 'has Active section');
251
+ assertTrue(md.includes('## Validated'), 'has Validated section');
252
+ assertTrue(md.includes('## Deferred'), 'has Deferred section');
253
+ assertTrue(md.includes('## Out of Scope'), 'has Out of Scope section');
254
+ assertTrue(md.includes('## Traceability'), 'has Traceability section');
255
+ assertTrue(md.includes('## Coverage Summary'), 'has Coverage Summary section');
256
+ }
255
257
 
256
- test('generateRequirementsMd empty input', () => {
257
- const md = generateRequirementsMd([]);
258
- const parsed = parseRequirementsSections(md);
259
- assert.deepStrictEqual(parsed.length, 0, 'empty requirements produces empty parse');
260
- });
258
+ console.log('\n── generateRequirementsMd only populated sections ──');
261
259
 
262
- // ═══════════════════════════════════════════════════════════════════════════
263
- // nextDecisionId Tests
264
- // ═══════════════════════════════════════════════════════════════════════════
260
+ {
261
+ // Only active requirements — should only have Active section
262
+ const activeOnly = SAMPLE_REQUIREMENTS.filter(r => r.status === 'active');
263
+ const md = generateRequirementsMd(activeOnly);
264
+ assertTrue(md.includes('## Active'), 'has Active section');
265
+ assertTrue(!md.includes('## Validated'), 'no Validated section when no validated reqs');
266
+ assertTrue(!md.includes('## Deferred'), 'no Deferred section when no deferred reqs');
267
+ assertTrue(!md.includes('## Out of Scope'), 'no Out of Scope section when no out-of-scope reqs');
268
+ }
265
269
 
266
- test('nextDecisionId', async () => {
267
- // Open in-memory DB
268
- openDatabase(':memory:');
270
+ console.log('\n── generateRequirementsMd empty input ──');
269
271
 
270
- const id1 = await nextDecisionId();
271
- assert.deepStrictEqual(id1, 'D001', 'first ID when no decisions exist');
272
+ {
273
+ const md = generateRequirementsMd([]);
274
+ const parsed = parseRequirementsSections(md);
275
+ assertEq(parsed.length, 0, 'empty requirements produces empty parse');
276
+ }
272
277
 
273
- // Insert some decisions
274
- upsertDecision({
275
- id: 'D001',
276
- when_context: 'M001',
277
- scope: 'test',
278
- decision: 'test decision',
279
- choice: 'test choice',
280
- rationale: 'test',
281
- revisable: 'No',
282
- made_by: 'agent',
283
- superseded_by: null,
284
- });
285
- upsertDecision({
286
- id: 'D005',
287
- when_context: 'M001',
288
- scope: 'test',
289
- decision: 'test decision 5',
290
- choice: 'test choice',
291
- rationale: 'test',
292
- revisable: 'No',
293
- made_by: 'agent',
294
- superseded_by: null,
295
- });
278
+ // ═══════════════════════════════════════════════════════════════════════════
279
+ // nextDecisionId Tests
280
+ // ═══════════════════════════════════════════════════════════════════════════
296
281
 
297
- const id2 = await nextDecisionId();
298
- assert.deepStrictEqual(id2, 'D006', 'next ID after D005 is D006');
282
+ console.log('\n── nextDecisionId ──');
299
283
 
300
- closeDatabase();
284
+ {
285
+ // Open in-memory DB
286
+ openDatabase(':memory:');
287
+
288
+ const id1 = await nextDecisionId();
289
+ assertEq(id1, 'D001', 'first ID when no decisions exist');
290
+
291
+ // Insert some decisions
292
+ upsertDecision({
293
+ id: 'D001',
294
+ when_context: 'M001',
295
+ scope: 'test',
296
+ decision: 'test decision',
297
+ choice: 'test choice',
298
+ rationale: 'test',
299
+ revisable: 'No',
300
+ made_by: 'agent',
301
+ superseded_by: null,
302
+ });
303
+ upsertDecision({
304
+ id: 'D005',
305
+ when_context: 'M001',
306
+ scope: 'test',
307
+ decision: 'test decision 5',
308
+ choice: 'test choice',
309
+ rationale: 'test',
310
+ revisable: 'No',
311
+ made_by: 'agent',
312
+ superseded_by: null,
301
313
  });
302
314
 
303
- // ═══════════════════════════════════════════════════════════════════════════
304
- // saveDecisionToDb Tests
305
- // ═══════════════════════════════════════════════════════════════════════════
315
+ const id2 = await nextDecisionId();
316
+ assertEq(id2, 'D006', 'next ID after D005 is D006');
306
317
 
307
- test('saveDecisionToDb', async () => {
308
- const tmpDir = makeTmpDir();
309
- const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
310
- openDatabase(dbPath);
318
+ closeDatabase();
319
+ }
311
320
 
312
- try {
313
- const result = await saveDecisionToDb({
314
- scope: 'arch',
315
- decision: 'Test decision',
316
- choice: 'Option A',
317
- rationale: 'Best option',
318
- when_context: 'M001',
319
- }, tmpDir);
320
-
321
- assert.deepStrictEqual(result.id, 'D001', 'saveDecisionToDb returns D001 as first ID');
322
-
323
- // Verify DB state
324
- const dbDecision = getDecisionById('D001');
325
- assert.ok(!!dbDecision, 'decision exists in DB after save');
326
- assert.deepStrictEqual(dbDecision?.scope, 'arch', 'DB decision has correct scope');
327
- assert.deepStrictEqual(dbDecision?.choice, 'Option A', 'DB decision has correct choice');
328
-
329
- // Verify markdown file was written
330
- const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
331
- assert.ok(fs.existsSync(mdPath), 'DECISIONS.md file created');
332
-
333
- const mdContent = fs.readFileSync(mdPath, 'utf-8');
334
- assert.ok(mdContent.includes('D001'), 'DECISIONS.md contains new decision ID');
335
- assert.ok(mdContent.includes('Test decision'), 'DECISIONS.md contains decision text');
336
-
337
- // Verify round-trip of the written file
338
- const parsed = parseDecisionsTable(mdContent);
339
- assert.deepStrictEqual(parsed.length, 1, 'written DECISIONS.md parses to 1 decision');
340
- assert.deepStrictEqual(parsed[0].id, 'D001', 'parsed decision has correct ID');
341
-
342
- // Add second decision
343
- const result2 = await saveDecisionToDb({
344
- scope: 'impl',
345
- decision: 'Second decision',
346
- choice: 'Option B',
347
- rationale: 'Also good',
348
- }, tmpDir);
349
-
350
- assert.deepStrictEqual(result2.id, 'D002', 'second decision gets D002');
351
-
352
- const mdContent2 = fs.readFileSync(mdPath, 'utf-8');
353
- const parsed2 = parseDecisionsTable(mdContent2);
354
- assert.deepStrictEqual(parsed2.length, 2, 'DECISIONS.md now has 2 decisions');
355
- } finally {
356
- closeDatabase();
357
- cleanupDir(tmpDir);
358
- }
359
- });
321
+ // ═══════════════════════════════════════════════════════════════════════════
322
+ // saveDecisionToDb Tests
323
+ // ═══════════════════════════════════════════════════════════════════════════
360
324
 
361
- // ═══════════════════════════════════════════════════════════════════════════
362
- // updateRequirementInDb Tests
363
- // ═══════════════════════════════════════════════════════════════════════════
325
+ console.log('\n── saveDecisionToDb ──');
364
326
 
365
- test('updateRequirementInDb', async () => {
366
- const tmpDir = makeTmpDir();
367
- const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
368
- openDatabase(dbPath);
327
+ {
328
+ const tmpDir = makeTmpDir();
329
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
330
+ openDatabase(dbPath);
369
331
 
370
- try {
371
- // Seed a requirement
372
- upsertRequirement({
373
- id: 'R001',
374
- class: 'core-capability',
375
- status: 'active',
376
- description: 'Test requirement',
377
- why: 'Testing',
378
- source: 'test',
379
- primary_owner: 'M001/S01',
380
- supporting_slices: 'none',
381
- validation: 'unmapped',
382
- notes: '',
383
- full_content: '',
384
- superseded_by: null,
385
- });
386
-
387
- // Update it
388
- await updateRequirementInDb('R001', {
389
- status: 'validated',
390
- validation: 'S01 — all tests pass',
391
- notes: 'Validated in S01',
392
- }, tmpDir);
393
-
394
- // Verify DB state
395
- const updated = getRequirementById('R001');
396
- assert.ok(!!updated, 'requirement still exists after update');
397
- assert.deepStrictEqual(updated?.status, 'validated', 'status updated in DB');
398
- assert.deepStrictEqual(updated?.validation, 'S01 — all tests pass', 'validation updated in DB');
399
- assert.deepStrictEqual(updated?.description, 'Test requirement', 'description preserved after update');
400
-
401
- // Verify markdown file was written
402
- const mdPath = path.join(tmpDir, '.gsd', 'REQUIREMENTS.md');
403
- assert.ok(fs.existsSync(mdPath), 'REQUIREMENTS.md file created');
404
-
405
- const mdContent = fs.readFileSync(mdPath, 'utf-8');
406
- assert.ok(mdContent.includes('R001'), 'REQUIREMENTS.md contains requirement ID');
407
- assert.ok(mdContent.includes('validated'), 'REQUIREMENTS.md shows updated status');
408
-
409
- // Verify round-trip
410
- const parsed = parseRequirementsSections(mdContent);
411
- assert.deepStrictEqual(parsed.length, 1, 'parsed 1 requirement from written file');
412
- assert.deepStrictEqual(parsed[0].status, 'validated', 'parsed status matches update');
413
- } finally {
414
- closeDatabase();
415
- cleanupDir(tmpDir);
416
- }
417
- });
332
+ try {
333
+ const result = await saveDecisionToDb({
334
+ scope: 'arch',
335
+ decision: 'Test decision',
336
+ choice: 'Option A',
337
+ rationale: 'Best option',
338
+ when_context: 'M001',
339
+ }, tmpDir);
340
+
341
+ assertEq(result.id, 'D001', 'saveDecisionToDb returns D001 as first ID');
342
+
343
+ // Verify DB state
344
+ const dbDecision = getDecisionById('D001');
345
+ assertTrue(!!dbDecision, 'decision exists in DB after save');
346
+ assertEq(dbDecision?.scope, 'arch', 'DB decision has correct scope');
347
+ assertEq(dbDecision?.choice, 'Option A', 'DB decision has correct choice');
348
+
349
+ // Verify markdown file was written
350
+ const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
351
+ assertTrue(fs.existsSync(mdPath), 'DECISIONS.md file created');
352
+
353
+ const mdContent = fs.readFileSync(mdPath, 'utf-8');
354
+ assertTrue(mdContent.includes('D001'), 'DECISIONS.md contains new decision ID');
355
+ assertTrue(mdContent.includes('Test decision'), 'DECISIONS.md contains decision text');
356
+
357
+ // Verify round-trip of the written file
358
+ const parsed = parseDecisionsTable(mdContent);
359
+ assertEq(parsed.length, 1, 'written DECISIONS.md parses to 1 decision');
360
+ assertEq(parsed[0].id, 'D001', 'parsed decision has correct ID');
361
+
362
+ // Add second decision
363
+ const result2 = await saveDecisionToDb({
364
+ scope: 'impl',
365
+ decision: 'Second decision',
366
+ choice: 'Option B',
367
+ rationale: 'Also good',
368
+ }, tmpDir);
369
+
370
+ assertEq(result2.id, 'D002', 'second decision gets D002');
371
+
372
+ const mdContent2 = fs.readFileSync(mdPath, 'utf-8');
373
+ const parsed2 = parseDecisionsTable(mdContent2);
374
+ assertEq(parsed2.length, 2, 'DECISIONS.md now has 2 decisions');
375
+ } finally {
376
+ closeDatabase();
377
+ cleanupDir(tmpDir);
378
+ }
379
+ }
418
380
 
419
- test('updateRequirementInDb — not found', async () => {
420
- const tmpDir = makeTmpDir();
421
- const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
422
- openDatabase(dbPath);
381
+ // ═══════════════════════════════════════════════════════════════════════════
382
+ // updateRequirementInDb Tests
383
+ // ═══════════════════════════════════════════════════════════════════════════
423
384
 
424
- try {
425
- let threw = false;
426
- try {
427
- await updateRequirementInDb('R999', { status: 'validated' }, tmpDir);
428
- } catch (err) {
429
- threw = true;
430
- assert.ok(
431
- (err as Error).message.includes('R999'),
432
- 'error message mentions the missing ID',
433
- );
434
- }
435
- assert.ok(threw, 'throws when requirement not found');
436
- } finally {
437
- closeDatabase();
438
- cleanupDir(tmpDir);
439
- }
440
- });
385
+ console.log('\n── updateRequirementInDb ──');
441
386
 
442
- // ═══════════════════════════════════════════════════════════════════════════
443
- // saveArtifactToDb Tests
444
- // ═══════════════════════════════════════════════════════════════════════════
387
+ {
388
+ const tmpDir = makeTmpDir();
389
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
390
+ openDatabase(dbPath);
391
+
392
+ try {
393
+ // Seed a requirement
394
+ upsertRequirement({
395
+ id: 'R001',
396
+ class: 'core-capability',
397
+ status: 'active',
398
+ description: 'Test requirement',
399
+ why: 'Testing',
400
+ source: 'test',
401
+ primary_owner: 'M001/S01',
402
+ supporting_slices: 'none',
403
+ validation: 'unmapped',
404
+ notes: '',
405
+ full_content: '',
406
+ superseded_by: null,
407
+ });
408
+
409
+ // Update it
410
+ await updateRequirementInDb('R001', {
411
+ status: 'validated',
412
+ validation: 'S01 — all tests pass',
413
+ notes: 'Validated in S01',
414
+ }, tmpDir);
415
+
416
+ // Verify DB state
417
+ const updated = getRequirementById('R001');
418
+ assertTrue(!!updated, 'requirement still exists after update');
419
+ assertEq(updated?.status, 'validated', 'status updated in DB');
420
+ assertEq(updated?.validation, 'S01 — all tests pass', 'validation updated in DB');
421
+ assertEq(updated?.description, 'Test requirement', 'description preserved after update');
422
+
423
+ // Verify markdown file was written
424
+ const mdPath = path.join(tmpDir, '.gsd', 'REQUIREMENTS.md');
425
+ assertTrue(fs.existsSync(mdPath), 'REQUIREMENTS.md file created');
426
+
427
+ const mdContent = fs.readFileSync(mdPath, 'utf-8');
428
+ assertTrue(mdContent.includes('R001'), 'REQUIREMENTS.md contains requirement ID');
429
+ assertTrue(mdContent.includes('validated'), 'REQUIREMENTS.md shows updated status');
430
+
431
+ // Verify round-trip
432
+ const parsed = parseRequirementsSections(mdContent);
433
+ assertEq(parsed.length, 1, 'parsed 1 requirement from written file');
434
+ assertEq(parsed[0].status, 'validated', 'parsed status matches update');
435
+ } finally {
436
+ closeDatabase();
437
+ cleanupDir(tmpDir);
438
+ }
439
+ }
445
440
 
446
- test('saveArtifactToDb', async () => {
447
- const tmpDir = makeTmpDir();
448
- const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
449
- openDatabase(dbPath);
441
+ console.log('\n── updateRequirementInDb not found ──');
450
442
 
443
+ {
444
+ const tmpDir = makeTmpDir();
445
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
446
+ openDatabase(dbPath);
447
+
448
+ try {
449
+ let threw = false;
451
450
  try {
452
- const content = '# Task Summary\n\nTest content\n';
453
- await saveArtifactToDb({
454
- path: 'milestones/M001/slices/S06/tasks/T01-SUMMARY.md',
455
- artifact_type: 'SUMMARY',
456
- content,
457
- milestone_id: 'M001',
458
- slice_id: 'S06',
459
- task_id: 'T01',
460
- }, tmpDir);
461
-
462
- // Verify DB state
463
- const adapter = _getAdapter();
464
- assert.ok(!!adapter, 'adapter available');
465
- const row = adapter!
466
- .prepare('SELECT * FROM artifacts WHERE path = ?')
467
- .get('milestones/M001/slices/S06/tasks/T01-SUMMARY.md');
468
- assert.ok(!!row, 'artifact exists in DB');
469
- assert.deepStrictEqual(row!['artifact_type'], 'SUMMARY', 'artifact type correct in DB');
470
- assert.deepStrictEqual(row!['milestone_id'], 'M001', 'milestone_id correct in DB');
471
- assert.deepStrictEqual(row!['slice_id'], 'S06', 'slice_id correct in DB');
472
- assert.deepStrictEqual(row!['task_id'], 'T01', 'task_id correct in DB');
473
-
474
- // Verify file on disk
475
- const filePath = path.join(
476
- tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S06', 'tasks', 'T01-SUMMARY.md',
451
+ await updateRequirementInDb('R999', { status: 'validated' }, tmpDir);
452
+ } catch (err) {
453
+ threw = true;
454
+ assertTrue(
455
+ (err as Error).message.includes('R999'),
456
+ 'error message mentions the missing ID',
477
457
  );
478
- assert.ok(fs.existsSync(filePath), 'artifact file written to disk');
479
- assert.deepStrictEqual(fs.readFileSync(filePath, 'utf-8'), content, 'file content matches');
480
- } finally {
481
- closeDatabase();
482
- cleanupDir(tmpDir);
483
458
  }
484
- });
459
+ assertTrue(threw, 'throws when requirement not found');
460
+ } finally {
461
+ closeDatabase();
462
+ cleanupDir(tmpDir);
463
+ }
464
+ }
485
465
 
486
- // ═══════════════════════════════════════════════════════════════════════════
487
- // Full Round-Trip: DB → Markdown → Parse → Compare
488
- // ═══════════════════════════════════════════════════════════════════════════
489
-
490
- test('Full DB round-trip: decisions', () => {
491
- openDatabase(':memory:');
492
-
493
- // Insert via DB
494
- for (const d of SAMPLE_DECISIONS) {
495
- upsertDecision({
496
- id: d.id,
497
- when_context: d.when_context,
498
- scope: d.scope,
499
- decision: d.decision,
500
- choice: d.choice,
501
- rationale: d.rationale,
502
- revisable: d.revisable,
503
- made_by: d.made_by,
504
- superseded_by: d.superseded_by,
505
- });
506
- }
466
+ // ═══════════════════════════════════════════════════════════════════════════
467
+ // saveArtifactToDb Tests
468
+ // ═══════════════════════════════════════════════════════════════════════════
507
469
 
508
- // Generate markdown from DB state
509
- const adapter = _getAdapter()!;
510
- const rows = adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
511
- const dbDecisions: Decision[] = rows.map(row => ({
512
- seq: row['seq'] as number,
513
- id: row['id'] as string,
514
- when_context: row['when_context'] as string,
515
- scope: row['scope'] as string,
516
- decision: row['decision'] as string,
517
- choice: row['choice'] as string,
518
- rationale: row['rationale'] as string,
519
- revisable: row['revisable'] as string,
520
- made_by: (row['made_by'] as string as import('../types.js').DecisionMadeBy) ?? 'agent',
521
- superseded_by: (row['superseded_by'] as string) ?? null,
522
- }));
523
-
524
- const md = generateDecisionsMd(dbDecisions);
525
- const parsed = parseDecisionsTable(md);
526
-
527
- assert.deepStrictEqual(parsed.length, SAMPLE_DECISIONS.length, 'DB round-trip decision count');
528
- for (const orig of SAMPLE_DECISIONS) {
529
- const rt = parsed.find(p => p.id === orig.id);
530
- assert.ok(!!rt, `DB round-trip: ${orig.id} found`);
531
- if (rt) {
532
- assert.deepStrictEqual(rt.scope, orig.scope, `DB round-trip: ${orig.id} scope`);
533
- assert.deepStrictEqual(rt.choice, orig.choice, `DB round-trip: ${orig.id} choice`);
534
- }
535
- }
470
+ console.log('\n── saveArtifactToDb ──');
471
+
472
+ {
473
+ const tmpDir = makeTmpDir();
474
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
475
+ openDatabase(dbPath);
536
476
 
477
+ try {
478
+ const content = '# Task Summary\n\nTest content\n';
479
+ await saveArtifactToDb({
480
+ path: 'milestones/M001/slices/S06/tasks/T01-SUMMARY.md',
481
+ artifact_type: 'SUMMARY',
482
+ content,
483
+ milestone_id: 'M001',
484
+ slice_id: 'S06',
485
+ task_id: 'T01',
486
+ }, tmpDir);
487
+
488
+ // Verify DB state
489
+ const adapter = _getAdapter();
490
+ assertTrue(!!adapter, 'adapter available');
491
+ const row = adapter!
492
+ .prepare('SELECT * FROM artifacts WHERE path = ?')
493
+ .get('milestones/M001/slices/S06/tasks/T01-SUMMARY.md');
494
+ assertTrue(!!row, 'artifact exists in DB');
495
+ assertEq(row!['artifact_type'], 'SUMMARY', 'artifact type correct in DB');
496
+ assertEq(row!['milestone_id'], 'M001', 'milestone_id correct in DB');
497
+ assertEq(row!['slice_id'], 'S06', 'slice_id correct in DB');
498
+ assertEq(row!['task_id'], 'T01', 'task_id correct in DB');
499
+
500
+ // Verify file on disk
501
+ const filePath = path.join(
502
+ tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S06', 'tasks', 'T01-SUMMARY.md',
503
+ );
504
+ assertTrue(fs.existsSync(filePath), 'artifact file written to disk');
505
+ assertEq(fs.readFileSync(filePath, 'utf-8'), content, 'file content matches');
506
+ } finally {
537
507
  closeDatabase();
538
- });
508
+ cleanupDir(tmpDir);
509
+ }
510
+ }
539
511
 
540
- test('Full DB round-trip: requirements', () => {
541
- openDatabase(':memory:');
512
+ // ═══════════════════════════════════════════════════════════════════════════
513
+ // Full Round-Trip: DB → Markdown → Parse → Compare
514
+ // ═══════════════════════════════════════════════════════════════════════════
515
+
516
+ console.log('\n── Full DB round-trip: decisions ──');
517
+
518
+ {
519
+ openDatabase(':memory:');
542
520
 
543
- for (const r of SAMPLE_REQUIREMENTS) {
544
- upsertRequirement(r);
521
+ // Insert via DB
522
+ for (const d of SAMPLE_DECISIONS) {
523
+ upsertDecision({
524
+ id: d.id,
525
+ when_context: d.when_context,
526
+ scope: d.scope,
527
+ decision: d.decision,
528
+ choice: d.choice,
529
+ rationale: d.rationale,
530
+ revisable: d.revisable,
531
+ made_by: d.made_by,
532
+ superseded_by: d.superseded_by,
533
+ });
534
+ }
535
+
536
+ // Generate markdown from DB state
537
+ const adapter = _getAdapter()!;
538
+ const rows = adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
539
+ const dbDecisions: Decision[] = rows.map(row => ({
540
+ seq: row['seq'] as number,
541
+ id: row['id'] as string,
542
+ when_context: row['when_context'] as string,
543
+ scope: row['scope'] as string,
544
+ decision: row['decision'] as string,
545
+ choice: row['choice'] as string,
546
+ rationale: row['rationale'] as string,
547
+ revisable: row['revisable'] as string,
548
+ made_by: (row['made_by'] as string as import('../types.js').DecisionMadeBy) ?? 'agent',
549
+ superseded_by: (row['superseded_by'] as string) ?? null,
550
+ }));
551
+
552
+ const md = generateDecisionsMd(dbDecisions);
553
+ const parsed = parseDecisionsTable(md);
554
+
555
+ assertEq(parsed.length, SAMPLE_DECISIONS.length, 'DB round-trip decision count');
556
+ for (const orig of SAMPLE_DECISIONS) {
557
+ const rt = parsed.find(p => p.id === orig.id);
558
+ assertTrue(!!rt, `DB round-trip: ${orig.id} found`);
559
+ if (rt) {
560
+ assertEq(rt.scope, orig.scope, `DB round-trip: ${orig.id} scope`);
561
+ assertEq(rt.choice, orig.choice, `DB round-trip: ${orig.id} choice`);
545
562
  }
563
+ }
546
564
 
547
- const adapter = _getAdapter()!;
548
- const rows = adapter.prepare('SELECT * FROM requirements ORDER BY id').all();
549
- const dbReqs: Requirement[] = rows.map(row => ({
550
- id: row['id'] as string,
551
- class: row['class'] as string,
552
- status: row['status'] as string,
553
- description: row['description'] as string,
554
- why: row['why'] as string,
555
- source: row['source'] as string,
556
- primary_owner: row['primary_owner'] as string,
557
- supporting_slices: row['supporting_slices'] as string,
558
- validation: row['validation'] as string,
559
- notes: row['notes'] as string,
560
- full_content: row['full_content'] as string,
561
- superseded_by: (row['superseded_by'] as string) ?? null,
562
- }));
563
-
564
- const md = generateRequirementsMd(dbReqs);
565
- const parsed = parseRequirementsSections(md);
566
-
567
- assert.deepStrictEqual(parsed.length, SAMPLE_REQUIREMENTS.length, 'DB round-trip requirement count');
568
- for (const orig of SAMPLE_REQUIREMENTS) {
569
- const rt = parsed.find(p => p.id === orig.id);
570
- assert.ok(!!rt, `DB round-trip: ${orig.id} found`);
571
- if (rt) {
572
- assert.deepStrictEqual(rt.class, orig.class, `DB round-trip: ${orig.id} class`);
573
- assert.deepStrictEqual(rt.description, orig.description, `DB round-trip: ${orig.id} description`);
574
- }
565
+ closeDatabase();
566
+ }
567
+
568
+ console.log('\n── Full DB round-trip: requirements ──');
569
+
570
+ {
571
+ openDatabase(':memory:');
572
+
573
+ for (const r of SAMPLE_REQUIREMENTS) {
574
+ upsertRequirement(r);
575
+ }
576
+
577
+ const adapter = _getAdapter()!;
578
+ const rows = adapter.prepare('SELECT * FROM requirements ORDER BY id').all();
579
+ const dbReqs: Requirement[] = rows.map(row => ({
580
+ id: row['id'] as string,
581
+ class: row['class'] as string,
582
+ status: row['status'] as string,
583
+ description: row['description'] as string,
584
+ why: row['why'] as string,
585
+ source: row['source'] as string,
586
+ primary_owner: row['primary_owner'] as string,
587
+ supporting_slices: row['supporting_slices'] as string,
588
+ validation: row['validation'] as string,
589
+ notes: row['notes'] as string,
590
+ full_content: row['full_content'] as string,
591
+ superseded_by: (row['superseded_by'] as string) ?? null,
592
+ }));
593
+
594
+ const md = generateRequirementsMd(dbReqs);
595
+ const parsed = parseRequirementsSections(md);
596
+
597
+ assertEq(parsed.length, SAMPLE_REQUIREMENTS.length, 'DB round-trip requirement count');
598
+ for (const orig of SAMPLE_REQUIREMENTS) {
599
+ const rt = parsed.find(p => p.id === orig.id);
600
+ assertTrue(!!rt, `DB round-trip: ${orig.id} found`);
601
+ if (rt) {
602
+ assertEq(rt.class, orig.class, `DB round-trip: ${orig.id} class`);
603
+ assertEq(rt.description, orig.description, `DB round-trip: ${orig.id} description`);
575
604
  }
605
+ }
576
606
 
577
- closeDatabase();
578
- });
607
+ closeDatabase();
608
+ }
579
609
 
580
- // ═══════════════════════════════════════════════════════════════════════════
610
+ // ═══════════════════════════════════════════════════════════════════════════
581
611
 
582
- });
612
+ report();