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
@@ -39,400 +39,444 @@ function cleanup(base: string): void {
39
39
 
40
40
  // ─── resolveExpectedArtifactPath ──────────────────────────────────────────
41
41
 
42
- test("resolveExpectedArtifactPath returns correct path for research-milestone", (t) => {
42
+ test("resolveExpectedArtifactPath returns correct path for research-milestone", () => {
43
43
  const base = makeTmpBase();
44
- t.after(() => cleanup(base));
45
-
46
- const result = resolveExpectedArtifactPath("research-milestone", "M001", base);
47
- assert.ok(result);
48
- assert.ok(result!.includes("M001"));
49
- assert.ok(result!.includes("RESEARCH"));
44
+ try {
45
+ const result = resolveExpectedArtifactPath("research-milestone", "M001", base);
46
+ assert.ok(result);
47
+ assert.ok(result!.includes("M001"));
48
+ assert.ok(result!.includes("RESEARCH"));
49
+ } finally {
50
+ cleanup(base);
51
+ }
50
52
  });
51
53
 
52
- test("resolveExpectedArtifactPath returns correct path for execute-task", (t) => {
54
+ test("resolveExpectedArtifactPath returns correct path for execute-task", () => {
53
55
  const base = makeTmpBase();
54
- t.after(() => cleanup(base));
55
-
56
- const result = resolveExpectedArtifactPath("execute-task", "M001/S01/T01", base);
57
- assert.ok(result);
58
- assert.ok(result!.includes("tasks"));
59
- assert.ok(result!.includes("SUMMARY"));
56
+ try {
57
+ const result = resolveExpectedArtifactPath("execute-task", "M001/S01/T01", base);
58
+ assert.ok(result);
59
+ assert.ok(result!.includes("tasks"));
60
+ assert.ok(result!.includes("SUMMARY"));
61
+ } finally {
62
+ cleanup(base);
63
+ }
60
64
  });
61
65
 
62
- test("resolveExpectedArtifactPath returns correct path for complete-slice", (t) => {
66
+ test("resolveExpectedArtifactPath returns correct path for complete-slice", () => {
63
67
  const base = makeTmpBase();
64
- t.after(() => cleanup(base));
65
-
66
- const result = resolveExpectedArtifactPath("complete-slice", "M001/S01", base);
67
- assert.ok(result);
68
- assert.ok(result!.includes("SUMMARY"));
68
+ try {
69
+ const result = resolveExpectedArtifactPath("complete-slice", "M001/S01", base);
70
+ assert.ok(result);
71
+ assert.ok(result!.includes("SUMMARY"));
72
+ } finally {
73
+ cleanup(base);
74
+ }
69
75
  });
70
76
 
71
- test("resolveExpectedArtifactPath returns correct path for plan-slice", (t) => {
77
+ test("resolveExpectedArtifactPath returns correct path for plan-slice", () => {
72
78
  const base = makeTmpBase();
73
- t.after(() => cleanup(base));
74
-
75
- const result = resolveExpectedArtifactPath("plan-slice", "M001/S01", base);
76
- assert.ok(result);
77
- assert.ok(result!.includes("PLAN"));
79
+ try {
80
+ const result = resolveExpectedArtifactPath("plan-slice", "M001/S01", base);
81
+ assert.ok(result);
82
+ assert.ok(result!.includes("PLAN"));
83
+ } finally {
84
+ cleanup(base);
85
+ }
78
86
  });
79
87
 
80
- test("resolveExpectedArtifactPath returns null for unknown type", (t) => {
88
+ test("resolveExpectedArtifactPath returns null for unknown type", () => {
81
89
  const base = makeTmpBase();
82
- t.after(() => cleanup(base));
83
-
84
- const result = resolveExpectedArtifactPath("unknown-type", "M001", base);
85
- assert.equal(result, null);
90
+ try {
91
+ const result = resolveExpectedArtifactPath("unknown-type", "M001", base);
92
+ assert.equal(result, null);
93
+ } finally {
94
+ cleanup(base);
95
+ }
86
96
  });
87
97
 
88
- test("resolveExpectedArtifactPath returns correct path for all milestone-level types", (t) => {
98
+ test("resolveExpectedArtifactPath returns correct path for all milestone-level types", () => {
89
99
  const base = makeTmpBase();
90
- t.after(() => cleanup(base));
91
-
92
- const planResult = resolveExpectedArtifactPath("plan-milestone", "M001", base);
93
- assert.ok(planResult);
94
- assert.ok(planResult!.includes("ROADMAP"));
100
+ try {
101
+ const planResult = resolveExpectedArtifactPath("plan-milestone", "M001", base);
102
+ assert.ok(planResult);
103
+ assert.ok(planResult!.includes("ROADMAP"));
95
104
 
96
- const completeResult = resolveExpectedArtifactPath("complete-milestone", "M001", base);
97
- assert.ok(completeResult);
98
- assert.ok(completeResult!.includes("SUMMARY"));
105
+ const completeResult = resolveExpectedArtifactPath("complete-milestone", "M001", base);
106
+ assert.ok(completeResult);
107
+ assert.ok(completeResult!.includes("SUMMARY"));
108
+ } finally {
109
+ cleanup(base);
110
+ }
99
111
  });
100
112
 
101
- test("resolveExpectedArtifactPath returns correct path for all slice-level types", (t) => {
113
+ test("resolveExpectedArtifactPath returns correct path for all slice-level types", () => {
102
114
  const base = makeTmpBase();
103
- t.after(() => cleanup(base));
104
-
105
- const researchResult = resolveExpectedArtifactPath("research-slice", "M001/S01", base);
106
- assert.ok(researchResult);
107
- assert.ok(researchResult!.includes("RESEARCH"));
115
+ try {
116
+ const researchResult = resolveExpectedArtifactPath("research-slice", "M001/S01", base);
117
+ assert.ok(researchResult);
118
+ assert.ok(researchResult!.includes("RESEARCH"));
108
119
 
109
- const assessResult = resolveExpectedArtifactPath("reassess-roadmap", "M001/S01", base);
110
- assert.ok(assessResult);
111
- assert.ok(assessResult!.includes("ASSESSMENT"));
120
+ const assessResult = resolveExpectedArtifactPath("reassess-roadmap", "M001/S01", base);
121
+ assert.ok(assessResult);
122
+ assert.ok(assessResult!.includes("ASSESSMENT"));
112
123
 
113
- const uatResult = resolveExpectedArtifactPath("run-uat", "M001/S01", base);
114
- assert.ok(uatResult);
115
- assert.ok(uatResult!.includes("UAT-RESULT"));
124
+ const uatResult = resolveExpectedArtifactPath("run-uat", "M001/S01", base);
125
+ assert.ok(uatResult);
126
+ assert.ok(uatResult!.includes("UAT-RESULT"));
127
+ } finally {
128
+ cleanup(base);
129
+ }
116
130
  });
117
131
 
118
132
  // ─── diagnoseExpectedArtifact ─────────────────────────────────────────────
119
133
 
120
- test("diagnoseExpectedArtifact returns description for known types", (t) => {
134
+ test("diagnoseExpectedArtifact returns description for known types", () => {
121
135
  const base = makeTmpBase();
122
- t.after(() => cleanup(base));
123
-
124
- const research = diagnoseExpectedArtifact("research-milestone", "M001", base);
125
- assert.ok(research);
126
- assert.ok(research!.includes("research"));
136
+ try {
137
+ const research = diagnoseExpectedArtifact("research-milestone", "M001", base);
138
+ assert.ok(research);
139
+ assert.ok(research!.includes("research"));
127
140
 
128
- const plan = diagnoseExpectedArtifact("plan-slice", "M001/S01", base);
129
- assert.ok(plan);
130
- assert.ok(plan!.includes("plan"));
141
+ const plan = diagnoseExpectedArtifact("plan-slice", "M001/S01", base);
142
+ assert.ok(plan);
143
+ assert.ok(plan!.includes("plan"));
131
144
 
132
- const task = diagnoseExpectedArtifact("execute-task", "M001/S01/T01", base);
133
- assert.ok(task);
134
- assert.ok(task!.includes("T01"));
145
+ const task = diagnoseExpectedArtifact("execute-task", "M001/S01/T01", base);
146
+ assert.ok(task);
147
+ assert.ok(task!.includes("T01"));
148
+ } finally {
149
+ cleanup(base);
150
+ }
135
151
  });
136
152
 
137
- test("diagnoseExpectedArtifact returns null for unknown type", (t) => {
153
+ test("diagnoseExpectedArtifact returns null for unknown type", () => {
138
154
  const base = makeTmpBase();
139
- t.after(() => cleanup(base));
140
-
141
- assert.equal(diagnoseExpectedArtifact("unknown", "M001", base), null);
155
+ try {
156
+ assert.equal(diagnoseExpectedArtifact("unknown", "M001", base), null);
157
+ } finally {
158
+ cleanup(base);
159
+ }
142
160
  });
143
161
 
144
162
  // ─── buildLoopRemediationSteps ────────────────────────────────────────────
145
163
 
146
- test("buildLoopRemediationSteps returns steps for execute-task", (t) => {
164
+ test("buildLoopRemediationSteps returns steps for execute-task", () => {
147
165
  const base = makeTmpBase();
148
- t.after(() => cleanup(base));
149
-
150
- const steps = buildLoopRemediationSteps("execute-task", "M001/S01/T01", base);
151
- assert.ok(steps);
152
- assert.ok(steps!.includes("T01"));
153
- assert.ok(steps!.includes("gsd undo-task"));
166
+ try {
167
+ const steps = buildLoopRemediationSteps("execute-task", "M001/S01/T01", base);
168
+ assert.ok(steps);
169
+ assert.ok(steps!.includes("T01"));
170
+ assert.ok(steps!.includes("gsd undo-task"));
171
+ } finally {
172
+ cleanup(base);
173
+ }
154
174
  });
155
175
 
156
- test("buildLoopRemediationSteps returns steps for plan-slice", (t) => {
176
+ test("buildLoopRemediationSteps returns steps for plan-slice", () => {
157
177
  const base = makeTmpBase();
158
- t.after(() => cleanup(base));
159
-
160
- const steps = buildLoopRemediationSteps("plan-slice", "M001/S01", base);
161
- assert.ok(steps);
162
- assert.ok(steps!.includes("PLAN"));
163
- assert.ok(steps!.includes("gsd recover"));
178
+ try {
179
+ const steps = buildLoopRemediationSteps("plan-slice", "M001/S01", base);
180
+ assert.ok(steps);
181
+ assert.ok(steps!.includes("PLAN"));
182
+ assert.ok(steps!.includes("gsd recover"));
183
+ } finally {
184
+ cleanup(base);
185
+ }
164
186
  });
165
187
 
166
- test("buildLoopRemediationSteps returns steps for complete-slice", (t) => {
188
+ test("buildLoopRemediationSteps returns steps for complete-slice", () => {
167
189
  const base = makeTmpBase();
168
- t.after(() => cleanup(base));
169
-
170
- const steps = buildLoopRemediationSteps("complete-slice", "M001/S01", base);
171
- assert.ok(steps);
172
- assert.ok(steps!.includes("S01"));
173
- assert.ok(steps!.includes("gsd reset-slice"));
190
+ try {
191
+ const steps = buildLoopRemediationSteps("complete-slice", "M001/S01", base);
192
+ assert.ok(steps);
193
+ assert.ok(steps!.includes("S01"));
194
+ assert.ok(steps!.includes("gsd reset-slice"));
195
+ } finally {
196
+ cleanup(base);
197
+ }
174
198
  });
175
199
 
176
- test("buildLoopRemediationSteps returns null for unknown type", (t) => {
200
+ test("buildLoopRemediationSteps returns null for unknown type", () => {
177
201
  const base = makeTmpBase();
178
- t.after(() => cleanup(base));
179
-
180
- assert.equal(buildLoopRemediationSteps("unknown", "M001", base), null);
202
+ try {
203
+ assert.equal(buildLoopRemediationSteps("unknown", "M001", base), null);
204
+ } finally {
205
+ cleanup(base);
206
+ }
181
207
  });
182
208
 
183
209
  // ─── verifyExpectedArtifact: parse cache collision regression ─────────────
184
210
 
185
- test("verifyExpectedArtifact detects roadmap [x] change despite parse cache", (t) => {
211
+ test("verifyExpectedArtifact detects roadmap [x] change despite parse cache", () => {
186
212
  // Regression test: cacheKey collision when [ ] → [x] doesn't change
187
213
  // file length or first/last 100 chars. Without the fix, parseRoadmap
188
214
  // returns stale cached data with done=false even though the file has [x].
189
215
  const base = makeTmpBase();
190
- t.after(() => {
216
+ try {
217
+ // Build a roadmap long enough that the [x] change is outside the first/last 100 chars
218
+ const padding = "A".repeat(200);
219
+ const roadmapBefore = [
220
+ `# M001: Test Milestone ${padding}`,
221
+ "",
222
+ "## Slices",
223
+ "",
224
+ "- [ ] **S01: First slice** `risk:low`",
225
+ "",
226
+ `## Footer ${padding}`,
227
+ ].join("\n");
228
+ const roadmapAfter = roadmapBefore.replace("- [ ] **S01:", "- [x] **S01:");
229
+
230
+ // Verify lengths are identical (the key collision condition)
231
+ assert.equal(roadmapBefore.length, roadmapAfter.length);
232
+
233
+ // Populate parse cache with the pre-edit roadmap
234
+ const before = parseRoadmap(roadmapBefore);
235
+ const sliceBefore = before.slices.find(s => s.id === "S01");
236
+ assert.ok(sliceBefore);
237
+ assert.equal(sliceBefore!.done, false);
238
+
239
+ // Now write the post-edit roadmap to disk and create required artifacts
240
+ const roadmapPath = join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
241
+ writeFileSync(roadmapPath, roadmapAfter);
242
+ const summaryPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md");
243
+ writeFileSync(summaryPath, "# Summary\nDone.");
244
+ const uatPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-UAT.md");
245
+ writeFileSync(uatPath, "# UAT\nPassed.");
246
+
247
+ // verifyExpectedArtifact should see the [x] despite the parse cache
248
+ // having the [ ] version. The fix clears the parse cache inside verify.
249
+ const verified = verifyExpectedArtifact("complete-slice", "M001/S01", base);
250
+ assert.equal(verified, true, "verifyExpectedArtifact should return true when roadmap has [x]");
251
+ } finally {
191
252
  clearParseCache();
192
253
  cleanup(base);
193
- });
194
-
195
- // Build a roadmap long enough that the [x] change is outside the first/last 100 chars
196
- const padding = "A".repeat(200);
197
- const roadmapBefore = [
198
- `# M001: Test Milestone ${padding}`,
199
- "",
200
- "## Slices",
201
- "",
202
- "- [ ] **S01: First slice** `risk:low`",
203
- "",
204
- `## Footer ${padding}`,
205
- ].join("\n");
206
- const roadmapAfter = roadmapBefore.replace("- [ ] **S01:", "- [x] **S01:");
207
-
208
- // Verify lengths are identical (the key collision condition)
209
- assert.equal(roadmapBefore.length, roadmapAfter.length);
210
-
211
- // Populate parse cache with the pre-edit roadmap
212
- const before = parseRoadmap(roadmapBefore);
213
- const sliceBefore = before.slices.find(s => s.id === "S01");
214
- assert.ok(sliceBefore);
215
- assert.equal(sliceBefore!.done, false);
216
-
217
- // Now write the post-edit roadmap to disk and create required artifacts
218
- const roadmapPath = join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
219
- writeFileSync(roadmapPath, roadmapAfter);
220
- const summaryPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md");
221
- writeFileSync(summaryPath, "# Summary\nDone.");
222
- const uatPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-UAT.md");
223
- writeFileSync(uatPath, "# UAT\nPassed.");
224
-
225
- // verifyExpectedArtifact should see the [x] despite the parse cache
226
- // having the [ ] version. The fix clears the parse cache inside verify.
227
- const verified = verifyExpectedArtifact("complete-slice", "M001/S01", base);
228
- assert.equal(verified, true, "verifyExpectedArtifact should return true when roadmap has [x]");
254
+ }
229
255
  });
230
256
 
231
257
  // ─── verifyExpectedArtifact: plan-slice empty scaffold regression (#699) ──
232
258
 
233
- test("verifyExpectedArtifact rejects plan-slice with empty scaffold", (t) => {
259
+ test("verifyExpectedArtifact rejects plan-slice with empty scaffold", () => {
234
260
  const base = makeTmpBase();
235
- t.after(() => cleanup(base));
236
-
237
- const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
238
- mkdirSync(sliceDir, { recursive: true });
239
- writeFileSync(join(sliceDir, "S01-PLAN.md"), "# S01: Test Slice\n\n## Tasks\n\n");
240
- assert.strictEqual(
241
- verifyExpectedArtifact("plan-slice", "M001/S01", base),
242
- false,
243
- "Empty scaffold should not be treated as completed artifact",
244
- );
261
+ try {
262
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
263
+ mkdirSync(sliceDir, { recursive: true });
264
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), "# S01: Test Slice\n\n## Tasks\n\n");
265
+ assert.strictEqual(
266
+ verifyExpectedArtifact("plan-slice", "M001/S01", base),
267
+ false,
268
+ "Empty scaffold should not be treated as completed artifact",
269
+ );
270
+ } finally {
271
+ cleanup(base);
272
+ }
245
273
  });
246
274
 
247
- test("verifyExpectedArtifact accepts plan-slice with actual tasks", (t) => {
275
+ test("verifyExpectedArtifact accepts plan-slice with actual tasks", () => {
248
276
  const base = makeTmpBase();
249
- t.after(() => cleanup(base));
250
-
251
- const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
252
- const tasksDir = join(sliceDir, "tasks");
253
- mkdirSync(tasksDir, { recursive: true });
254
- writeFileSync(join(sliceDir, "S01-PLAN.md"), [
255
- "# S01: Test Slice",
256
- "",
257
- "## Tasks",
258
- "",
259
- "- [ ] **T01: Implement feature** `est:2h`",
260
- "- [ ] **T02: Write tests** `est:1h`",
261
- ].join("\n"));
262
- writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
263
- writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
264
- assert.strictEqual(
265
- verifyExpectedArtifact("plan-slice", "M001/S01", base),
266
- true,
267
- "Plan with task entries should be treated as completed artifact",
268
- );
269
- });
270
-
271
- test("verifyExpectedArtifact accepts plan-slice with completed tasks", (t) => {
277
+ try {
278
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
279
+ const tasksDir = join(sliceDir, "tasks");
280
+ mkdirSync(tasksDir, { recursive: true });
281
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
282
+ "# S01: Test Slice",
283
+ "",
284
+ "## Tasks",
285
+ "",
286
+ "- [ ] **T01: Implement feature** `est:2h`",
287
+ "- [ ] **T02: Write tests** `est:1h`",
288
+ ].join("\n"));
289
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
290
+ writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
291
+ assert.strictEqual(
292
+ verifyExpectedArtifact("plan-slice", "M001/S01", base),
293
+ true,
294
+ "Plan with task entries should be treated as completed artifact",
295
+ );
296
+ } finally {
297
+ cleanup(base);
298
+ }
299
+ });
300
+
301
+ test("verifyExpectedArtifact accepts plan-slice with completed tasks", () => {
272
302
  const base = makeTmpBase();
273
- t.after(() => cleanup(base));
274
-
275
- const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
276
- const tasksDir = join(sliceDir, "tasks");
277
- mkdirSync(tasksDir, { recursive: true });
278
- writeFileSync(join(sliceDir, "S01-PLAN.md"), [
279
- "# S01: Test Slice",
280
- "",
281
- "## Tasks",
282
- "",
283
- "- [x] **T01: Implement feature** `est:2h`",
284
- "- [ ] **T02: Write tests** `est:1h`",
285
- ].join("\n"));
286
- writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
287
- writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
288
- assert.strictEqual(
289
- verifyExpectedArtifact("plan-slice", "M001/S01", base),
290
- true,
291
- "Plan with completed task entries should be treated as completed artifact",
292
- );
303
+ try {
304
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
305
+ const tasksDir = join(sliceDir, "tasks");
306
+ mkdirSync(tasksDir, { recursive: true });
307
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
308
+ "# S01: Test Slice",
309
+ "",
310
+ "## Tasks",
311
+ "",
312
+ "- [x] **T01: Implement feature** `est:2h`",
313
+ "- [ ] **T02: Write tests** `est:1h`",
314
+ ].join("\n"));
315
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
316
+ writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
317
+ assert.strictEqual(
318
+ verifyExpectedArtifact("plan-slice", "M001/S01", base),
319
+ true,
320
+ "Plan with completed task entries should be treated as completed artifact",
321
+ );
322
+ } finally {
323
+ cleanup(base);
324
+ }
293
325
  });
294
326
 
295
327
  // ─── verifyExpectedArtifact: plan-slice task plan check (#739) ────────────
296
328
 
297
- test("verifyExpectedArtifact plan-slice passes when all task plan files exist", (t) => {
298
- const base = makeTmpBase();
299
- t.after(() => cleanup(base));
300
-
301
- const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
302
- const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
303
- const planContent = [
304
- "# S01: Test Slice",
305
- "",
306
- "## Tasks",
307
- "",
308
- "- [ ] **T01: First task** `est:1h`",
309
- "- [ ] **T02: Second task** `est:2h`",
310
- ].join("\n");
311
- writeFileSync(planPath, planContent);
312
- writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
313
- writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan\n\nDo the other thing.");
314
-
315
- const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
316
- assert.equal(result, true, "should pass when all task plan files exist");
317
- });
318
-
319
- test("verifyExpectedArtifact plan-slice fails when a task plan file is missing (#739)", (t) => {
329
+ test("verifyExpectedArtifact plan-slice passes when all task plan files exist", () => {
320
330
  const base = makeTmpBase();
321
- t.after(() => cleanup(base));
322
-
323
- const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
324
- const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
325
- const planContent = [
326
- "# S01: Test Slice",
327
- "",
328
- "## Tasks",
329
- "",
330
- "- [ ] **T01: First task** `est:1h`",
331
- "- [ ] **T02: Second task** `est:2h`",
332
- ].join("\n");
333
- writeFileSync(planPath, planContent);
334
- // Only write T01-PLAN.md T02 is missing
335
- writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
336
-
337
- const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
338
- assert.equal(result, false, "should fail when T02-PLAN.md is missing");
339
- });
340
-
341
- test("verifyExpectedArtifact plan-slice fails for plan with no tasks (#699)", (t) => {
331
+ try {
332
+ const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
333
+ const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
334
+ const planContent = [
335
+ "# S01: Test Slice",
336
+ "",
337
+ "## Tasks",
338
+ "",
339
+ "- [ ] **T01: First task** `est:1h`",
340
+ "- [ ] **T02: Second task** `est:2h`",
341
+ ].join("\n");
342
+ writeFileSync(planPath, planContent);
343
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
344
+ writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan\n\nDo the other thing.");
345
+
346
+ const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
347
+ assert.equal(result, true, "should pass when all task plan files exist");
348
+ } finally {
349
+ cleanup(base);
350
+ }
351
+ });
352
+
353
+ test("verifyExpectedArtifact plan-slice fails when a task plan file is missing (#739)", () => {
342
354
  const base = makeTmpBase();
343
- t.after(() => cleanup(base));
355
+ try {
356
+ const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
357
+ const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
358
+ const planContent = [
359
+ "# S01: Test Slice",
360
+ "",
361
+ "## Tasks",
362
+ "",
363
+ "- [ ] **T01: First task** `est:1h`",
364
+ "- [ ] **T02: Second task** `est:2h`",
365
+ ].join("\n");
366
+ writeFileSync(planPath, planContent);
367
+ // Only write T01-PLAN.md — T02 is missing
368
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
369
+
370
+ const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
371
+ assert.equal(result, false, "should fail when T02-PLAN.md is missing");
372
+ } finally {
373
+ cleanup(base);
374
+ }
375
+ });
344
376
 
345
- const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
346
- const planContent = [
347
- "# S01: Test Slice",
348
- "",
349
- "## Goal",
350
- "",
351
- "Just some documentation updates, no tasks.",
352
- ].join("\n");
353
- writeFileSync(planPath, planContent);
377
+ test("verifyExpectedArtifact plan-slice fails for plan with no tasks (#699)", () => {
378
+ const base = makeTmpBase();
379
+ try {
380
+ const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
381
+ const planContent = [
382
+ "# S01: Test Slice",
383
+ "",
384
+ "## Goal",
385
+ "",
386
+ "Just some documentation updates, no tasks.",
387
+ ].join("\n");
388
+ writeFileSync(planPath, planContent);
354
389
 
355
- const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
356
- assert.equal(result, false, "should fail when plan has no task entries (empty scaffold, #699)");
390
+ const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
391
+ assert.equal(result, false, "should fail when plan has no task entries (empty scaffold, #699)");
392
+ } finally {
393
+ cleanup(base);
394
+ }
357
395
  });
358
396
 
359
397
  // ─── verifyExpectedArtifact: heading-style plan tasks (#1691) ─────────────
360
398
 
361
- test("verifyExpectedArtifact accepts plan-slice with heading-style tasks (### T01 --)", (t) => {
399
+ test("verifyExpectedArtifact accepts plan-slice with heading-style tasks (### T01 --)", () => {
362
400
  const base = makeTmpBase();
363
- t.after(() => cleanup(base));
364
-
365
- const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
366
- const tasksDir = join(sliceDir, "tasks");
367
- mkdirSync(tasksDir, { recursive: true });
368
- writeFileSync(join(sliceDir, "S01-PLAN.md"), [
369
- "# S01: Test Slice",
370
- "",
371
- "## Tasks",
372
- "",
373
- "### T01 -- Implement feature",
374
- "",
375
- "Feature description.",
376
- "",
377
- "### T02 -- Write tests",
378
- "",
379
- "Test description.",
380
- ].join("\n"));
381
- writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
382
- writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
383
- assert.strictEqual(
384
- verifyExpectedArtifact("plan-slice", "M001/S01", base),
385
- true,
386
- "Heading-style plan with task entries should be treated as completed artifact",
387
- );
388
- });
389
-
390
- test("verifyExpectedArtifact accepts plan-slice with colon-style heading tasks (### T01:)", (t) => {
401
+ try {
402
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
403
+ const tasksDir = join(sliceDir, "tasks");
404
+ mkdirSync(tasksDir, { recursive: true });
405
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
406
+ "# S01: Test Slice",
407
+ "",
408
+ "## Tasks",
409
+ "",
410
+ "### T01 -- Implement feature",
411
+ "",
412
+ "Feature description.",
413
+ "",
414
+ "### T02 -- Write tests",
415
+ "",
416
+ "Test description.",
417
+ ].join("\n"));
418
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
419
+ writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
420
+ assert.strictEqual(
421
+ verifyExpectedArtifact("plan-slice", "M001/S01", base),
422
+ true,
423
+ "Heading-style plan with task entries should be treated as completed artifact",
424
+ );
425
+ } finally {
426
+ cleanup(base);
427
+ }
428
+ });
429
+
430
+ test("verifyExpectedArtifact accepts plan-slice with colon-style heading tasks (### T01:)", () => {
391
431
  const base = makeTmpBase();
392
- t.after(() => cleanup(base));
393
-
394
- const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
395
- const tasksDir = join(sliceDir, "tasks");
396
- mkdirSync(tasksDir, { recursive: true });
397
- writeFileSync(join(sliceDir, "S01-PLAN.md"), [
398
- "# S01: Test Slice",
399
- "",
400
- "## Tasks",
401
- "",
402
- "### T01: Implement feature",
403
- "",
404
- "Feature description.",
405
- ].join("\n"));
406
- writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
407
- assert.strictEqual(
408
- verifyExpectedArtifact("plan-slice", "M001/S01", base),
409
- true,
410
- "Colon heading-style plan should be treated as completed artifact",
411
- );
412
- });
413
-
414
- test("verifyExpectedArtifact execute-task passes for heading-style plan entry (#1691)", (t) => {
432
+ try {
433
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
434
+ const tasksDir = join(sliceDir, "tasks");
435
+ mkdirSync(tasksDir, { recursive: true });
436
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
437
+ "# S01: Test Slice",
438
+ "",
439
+ "## Tasks",
440
+ "",
441
+ "### T01: Implement feature",
442
+ "",
443
+ "Feature description.",
444
+ ].join("\n"));
445
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
446
+ assert.strictEqual(
447
+ verifyExpectedArtifact("plan-slice", "M001/S01", base),
448
+ true,
449
+ "Colon heading-style plan should be treated as completed artifact",
450
+ );
451
+ } finally {
452
+ cleanup(base);
453
+ }
454
+ });
455
+
456
+ test("verifyExpectedArtifact execute-task passes for heading-style plan entry (#1691)", () => {
415
457
  const base = makeTmpBase();
416
- t.after(() => cleanup(base));
417
-
418
- const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
419
- const tasksDir = join(sliceDir, "tasks");
420
- mkdirSync(tasksDir, { recursive: true });
421
- writeFileSync(join(sliceDir, "S01-PLAN.md"), [
422
- "# S01: Test Slice",
423
- "",
424
- "## Tasks",
425
- "",
426
- "### T01 -- Implement feature",
427
- "",
428
- "Feature description.",
429
- ].join("\n"));
430
- writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n\nDone.");
431
- assert.strictEqual(
432
- verifyExpectedArtifact("execute-task", "M001/S01/T01", base),
433
- true,
434
- "execute-task should pass for heading-style plan entry when summary exists",
435
- );
458
+ try {
459
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
460
+ const tasksDir = join(sliceDir, "tasks");
461
+ mkdirSync(tasksDir, { recursive: true });
462
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
463
+ "# S01: Test Slice",
464
+ "",
465
+ "## Tasks",
466
+ "",
467
+ "### T01 -- Implement feature",
468
+ "",
469
+ "Feature description.",
470
+ ].join("\n"));
471
+ writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n\nDone.");
472
+ assert.strictEqual(
473
+ verifyExpectedArtifact("execute-task", "M001/S01/T01", base),
474
+ true,
475
+ "execute-task should pass for heading-style plan entry when summary exists",
476
+ );
477
+ } finally {
478
+ cleanup(base);
479
+ }
436
480
  });
437
481
 
438
482
  test("verifyExpectedArtifact plan-slice passes for rendered slice/task plan artifacts from DB", async () => {
@@ -574,81 +618,83 @@ test("verifyExpectedArtifact plan-slice fails after deleting a rendered task pla
574
618
 
575
619
  // ─── selfHealRuntimeRecords — worktree base path (#769) ──────────────────
576
620
 
577
- test("selfHealRuntimeRecords clears stale dispatched records (#769)", async (t) => {
621
+ test("selfHealRuntimeRecords clears stale dispatched records (#769)", async () => {
578
622
  // selfHealRuntimeRecords now only clears stale dispatched records (>1h).
579
623
  // No completedKeySet parameter — deriveState is sole authority.
580
624
  const worktreeBase = makeTmpBase();
581
625
  const mainBase = makeTmpBase();
582
- t.after(() => {
583
- cleanup(worktreeBase);
584
- cleanup(mainBase);
585
- });
586
-
587
- const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
626
+ try {
627
+ const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
588
628
 
589
- // Write a stale runtime record in the worktree .gsd/runtime/units/
590
- writeUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
591
- phase: "dispatched",
592
- });
629
+ // Write a stale runtime record in the worktree .gsd/runtime/units/
630
+ writeUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
631
+ phase: "dispatched",
632
+ });
593
633
 
594
- // Verify the runtime record exists before heal
595
- const before = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
596
- assert.ok(before, "runtime record should exist before heal");
634
+ // Verify the runtime record exists before heal
635
+ const before = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
636
+ assert.ok(before, "runtime record should exist before heal");
597
637
 
598
- // Mock ExtensionContext with minimal notify
599
- const notifications: string[] = [];
600
- const mockCtx = {
601
- ui: { notify: (msg: string) => { notifications.push(msg); } },
602
- } as any;
638
+ // Mock ExtensionContext with minimal notify
639
+ const notifications: string[] = [];
640
+ const mockCtx = {
641
+ ui: { notify: (msg: string) => { notifications.push(msg); } },
642
+ } as any;
603
643
 
604
- // Call selfHeal with worktreeBase — should clear the stale record
605
- await selfHealRuntimeRecords(worktreeBase, mockCtx);
644
+ // Call selfHeal with worktreeBase — should clear the stale record
645
+ await selfHealRuntimeRecords(worktreeBase, mockCtx);
606
646
 
607
- // The stale record should be cleared
608
- const after = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
609
- assert.equal(after, null, "runtime record should be cleared after heal");
610
- assert.ok(notifications.some(n => n.includes("Self-heal")), "should emit self-heal notification");
647
+ // The stale record should be cleared
648
+ const after = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
649
+ assert.equal(after, null, "runtime record should be cleared after heal");
650
+ assert.ok(notifications.some(n => n.includes("Self-heal")), "should emit self-heal notification");
611
651
 
612
- // Write a stale record at mainBase
613
- writeUnitRuntimeRecord(mainBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
614
- phase: "dispatched",
615
- });
616
- await selfHealRuntimeRecords(mainBase, mockCtx);
652
+ // Write a stale record at mainBase
653
+ writeUnitRuntimeRecord(mainBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
654
+ phase: "dispatched",
655
+ });
656
+ await selfHealRuntimeRecords(mainBase, mockCtx);
617
657
 
618
- // The record at mainBase should also be cleared by the stale timeout (>1h)
619
- const afterMain = readUnitRuntimeRecord(mainBase, "run-uat", "M001/S01");
620
- assert.equal(afterMain, null, "stale record at main base should be cleared by timeout");
658
+ // The record at mainBase should also be cleared by the stale timeout (>1h)
659
+ const afterMain = readUnitRuntimeRecord(mainBase, "run-uat", "M001/S01");
660
+ assert.equal(afterMain, null, "stale record at main base should be cleared by timeout");
661
+ } finally {
662
+ cleanup(worktreeBase);
663
+ cleanup(mainBase);
664
+ }
621
665
  });
622
666
 
623
667
  // ─── #1625: selfHealRuntimeRecords on resume clears paused-session leftovers ──
624
668
 
625
- test("selfHealRuntimeRecords clears recently-paused dispatched records on resume (#1625)", async (t) => {
669
+ test("selfHealRuntimeRecords clears recently-paused dispatched records on resume (#1625)", async () => {
626
670
  // When pauseAuto closes out a unit but clearUnitRuntimeRecord silently fails
627
671
  // (e.g. permission error), selfHealRuntimeRecords on resume should still
628
672
  // clean up stale dispatched records that are >1h old.
629
673
  const base = makeTmpBase();
630
- t.after(() => cleanup(base));
631
-
632
- const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
674
+ try {
675
+ const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
633
676
 
634
- // Simulate a record left behind after a pause — aged >1h to be considered stale
635
- writeUnitRuntimeRecord(base, "execute-task", "M001/S01/T01", Date.now() - 3700_000, {
636
- phase: "dispatched",
637
- });
677
+ // Simulate a record left behind after a pause — aged >1h to be considered stale
678
+ writeUnitRuntimeRecord(base, "execute-task", "M001/S01/T01", Date.now() - 3700_000, {
679
+ phase: "dispatched",
680
+ });
638
681
 
639
- const before = readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01");
640
- assert.ok(before, "dispatched record should exist before resume heal");
641
- assert.equal(before!.phase, "dispatched");
682
+ const before = readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01");
683
+ assert.ok(before, "dispatched record should exist before resume heal");
684
+ assert.equal(before!.phase, "dispatched");
642
685
 
643
- const notifications: string[] = [];
644
- const mockCtx = {
645
- ui: { notify: (msg: string) => { notifications.push(msg); } },
646
- } as any;
686
+ const notifications: string[] = [];
687
+ const mockCtx = {
688
+ ui: { notify: (msg: string) => { notifications.push(msg); } },
689
+ } as any;
647
690
 
648
- await selfHealRuntimeRecords(base, mockCtx);
691
+ await selfHealRuntimeRecords(base, mockCtx);
649
692
 
650
- const after = readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01");
651
- assert.equal(after, null, "stale dispatched record should be cleared on resume (#1625)");
693
+ const after = readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01");
694
+ assert.equal(after, null, "stale dispatched record should be cleared on resume (#1625)");
695
+ } finally {
696
+ cleanup(base);
697
+ }
652
698
  });
653
699
 
654
700
  // ─── #793: invalidateAllCaches unblocks skip-loop ─────────────────────────
@@ -656,49 +702,51 @@ test("selfHealRuntimeRecords clears recently-paused dispatched records on resume
656
702
  // just invalidateStateCache()) to clear path/parse caches that deriveState
657
703
  // depends on. Without this, even after cache invalidation, deriveState reads
658
704
  // stale directory listings and returns the same unit, looping forever.
659
- test("#793: invalidateAllCaches clears all caches so deriveState sees fresh disk state", async (t) => {
705
+ test("#793: invalidateAllCaches clears all caches so deriveState sees fresh disk state", async () => {
660
706
  const base = makeTmpBase();
661
- t.after(() => cleanup(base));
662
-
663
- const mid = "M001";
664
- const sid = "S01";
665
- const planDir = join(base, ".gsd", "milestones", mid, "slices", sid);
666
- const tasksDir = join(planDir, "tasks");
667
- mkdirSync(tasksDir, { recursive: true });
668
- mkdirSync(join(base, ".gsd", "milestones", mid), { recursive: true });
669
-
670
- writeFileSync(
671
- join(base, ".gsd", "milestones", mid, `${mid}-ROADMAP.md`),
672
- `# M001: Test Milestone\n\n**Vision:** test.\n\n## Slices\n\n- [ ] **${sid}: Slice One** \`risk:low\` \`depends:[]\`\n > After this: done.\n`,
673
- );
674
- const planUnchecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [ ] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
675
- writeFileSync(join(planDir, `${sid}-PLAN.md`), planUnchecked);
676
- writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01: Task One\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
677
- writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02: Task Two\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
678
-
679
- // Warm all caches
680
- const state1 = await deriveState(base);
681
- assert.equal(state1.activeTask?.id, "T01", "initial: T01 is active");
682
-
683
- // Simulate task completion on disk (what the LLM does)
684
- const planChecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [x] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
685
- writeFileSync(join(planDir, `${sid}-PLAN.md`), planChecked);
686
- writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# Summary\n");
687
-
688
- // invalidateStateCache alone: _stateCache cleared but path/parse caches warm
689
- invalidateStateCache();
690
-
691
- // invalidateAllCaches: all caches cleared — deriveState must re-read disk
692
- invalidateAllCaches();
693
- const state2 = await deriveState(base);
694
-
695
- // After full invalidation, T01 should be complete and T02 should be next
696
- assert.notEqual(state2.activeTask?.id, "T01", "#793: T01 not re-dispatched after full invalidation");
697
-
698
- // Verify the caches are truly cleared by calling clearParseCache and clearPathCache
699
- // do not throw (they should be no-ops after invalidateAllCaches already cleared them)
700
- clearParseCache(); // no-op, but should not throw
701
- assert.ok(true, "clearParseCache after invalidateAllCaches is safe");
707
+ try {
708
+ const mid = "M001";
709
+ const sid = "S01";
710
+ const planDir = join(base, ".gsd", "milestones", mid, "slices", sid);
711
+ const tasksDir = join(planDir, "tasks");
712
+ mkdirSync(tasksDir, { recursive: true });
713
+ mkdirSync(join(base, ".gsd", "milestones", mid), { recursive: true });
714
+
715
+ writeFileSync(
716
+ join(base, ".gsd", "milestones", mid, `${mid}-ROADMAP.md`),
717
+ `# M001: Test Milestone\n\n**Vision:** test.\n\n## Slices\n\n- [ ] **${sid}: Slice One** \`risk:low\` \`depends:[]\`\n > After this: done.\n`,
718
+ );
719
+ const planUnchecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [ ] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
720
+ writeFileSync(join(planDir, `${sid}-PLAN.md`), planUnchecked);
721
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01: Task One\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
722
+ writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02: Task Two\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
723
+
724
+ // Warm all caches
725
+ const state1 = await deriveState(base);
726
+ assert.equal(state1.activeTask?.id, "T01", "initial: T01 is active");
727
+
728
+ // Simulate task completion on disk (what the LLM does)
729
+ const planChecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [x] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
730
+ writeFileSync(join(planDir, `${sid}-PLAN.md`), planChecked);
731
+ writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# Summary\n");
732
+
733
+ // invalidateStateCache alone: _stateCache cleared but path/parse caches warm
734
+ invalidateStateCache();
735
+
736
+ // invalidateAllCaches: all caches cleared — deriveState must re-read disk
737
+ invalidateAllCaches();
738
+ const state2 = await deriveState(base);
739
+
740
+ // After full invalidation, T01 should be complete and T02 should be next
741
+ assert.notEqual(state2.activeTask?.id, "T01", "#793: T01 not re-dispatched after full invalidation");
742
+
743
+ // Verify the caches are truly cleared by calling clearParseCache and clearPathCache
744
+ // do not throw (they should be no-ops after invalidateAllCaches already cleared them)
745
+ clearParseCache(); // no-op, but should not throw
746
+ assert.ok(true, "clearParseCache after invalidateAllCaches is safe");
747
+ } finally {
748
+ cleanup(base);
749
+ }
702
750
  });
703
751
 
704
752
  // ─── hasImplementationArtifacts (#1703) ───────────────────────────────────
@@ -718,78 +766,88 @@ function makeGitBase(): string {
718
766
  return base;
719
767
  }
720
768
 
721
- test("hasImplementationArtifacts returns false when only .gsd/ files committed (#1703)", (t) => {
769
+ test("hasImplementationArtifacts returns false when only .gsd/ files committed (#1703)", () => {
722
770
  const base = makeGitBase();
723
- t.after(() => cleanup(base));
724
-
725
- // Create a feature branch and commit only .gsd/ files
726
- execFileSync("git", ["checkout", "-b", "feat/test-milestone"], { cwd: base, stdio: "ignore" });
727
- mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
728
- writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# Roadmap");
729
- writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Summary");
730
- execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
731
- execFileSync("git", ["commit", "-m", "chore: add plan files"], { cwd: base, stdio: "ignore" });
732
-
733
- const result = hasImplementationArtifacts(base);
734
- assert.equal(result, false, "should return false when only .gsd/ files were committed");
771
+ try {
772
+ // Create a feature branch and commit only .gsd/ files
773
+ execFileSync("git", ["checkout", "-b", "feat/test-milestone"], { cwd: base, stdio: "ignore" });
774
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
775
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# Roadmap");
776
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Summary");
777
+ execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
778
+ execFileSync("git", ["commit", "-m", "chore: add plan files"], { cwd: base, stdio: "ignore" });
779
+
780
+ const result = hasImplementationArtifacts(base);
781
+ assert.equal(result, false, "should return false when only .gsd/ files were committed");
782
+ } finally {
783
+ cleanup(base);
784
+ }
735
785
  });
736
786
 
737
- test("hasImplementationArtifacts returns true when implementation files committed (#1703)", (t) => {
787
+ test("hasImplementationArtifacts returns true when implementation files committed (#1703)", () => {
738
788
  const base = makeGitBase();
739
- t.after(() => cleanup(base));
740
-
741
- // Create a feature branch with both .gsd/ and implementation files
742
- execFileSync("git", ["checkout", "-b", "feat/test-impl"], { cwd: base, stdio: "ignore" });
743
- mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
744
- writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# Roadmap");
745
- mkdirSync(join(base, "src"), { recursive: true });
746
- writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}");
747
- execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
748
- execFileSync("git", ["commit", "-m", "feat: add feature"], { cwd: base, stdio: "ignore" });
749
-
750
- const result = hasImplementationArtifacts(base);
751
- assert.equal(result, true, "should return true when implementation files are present");
789
+ try {
790
+ // Create a feature branch with both .gsd/ and implementation files
791
+ execFileSync("git", ["checkout", "-b", "feat/test-impl"], { cwd: base, stdio: "ignore" });
792
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
793
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# Roadmap");
794
+ mkdirSync(join(base, "src"), { recursive: true });
795
+ writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}");
796
+ execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
797
+ execFileSync("git", ["commit", "-m", "feat: add feature"], { cwd: base, stdio: "ignore" });
798
+
799
+ const result = hasImplementationArtifacts(base);
800
+ assert.equal(result, true, "should return true when implementation files are present");
801
+ } finally {
802
+ cleanup(base);
803
+ }
752
804
  });
753
805
 
754
- test("hasImplementationArtifacts returns true on non-git directory (fail-open)", (t) => {
806
+ test("hasImplementationArtifacts returns true on non-git directory (fail-open)", () => {
755
807
  const base = join(tmpdir(), `gsd-test-nogit-${randomUUID()}`);
756
808
  mkdirSync(base, { recursive: true });
757
- t.after(() => cleanup(base));
758
-
759
- const result = hasImplementationArtifacts(base);
760
- assert.equal(result, true, "should return true (fail-open) in non-git directory");
809
+ try {
810
+ const result = hasImplementationArtifacts(base);
811
+ assert.equal(result, true, "should return true (fail-open) in non-git directory");
812
+ } finally {
813
+ cleanup(base);
814
+ }
761
815
  });
762
816
 
763
817
  // ─── verifyExpectedArtifact: complete-milestone requires impl artifacts (#1703) ──
764
818
 
765
- test("verifyExpectedArtifact complete-milestone fails with only .gsd/ files (#1703)", (t) => {
819
+ test("verifyExpectedArtifact complete-milestone fails with only .gsd/ files (#1703)", () => {
766
820
  const base = makeGitBase();
767
- t.after(() => cleanup(base));
768
-
769
- // Create feature branch with only .gsd/ files
770
- execFileSync("git", ["checkout", "-b", "feat/ms-only-gsd"], { cwd: base, stdio: "ignore" });
771
- mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
772
- writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
773
- execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
774
- execFileSync("git", ["commit", "-m", "chore: milestone plan files"], { cwd: base, stdio: "ignore" });
775
-
776
- const result = verifyExpectedArtifact("complete-milestone", "M001", base);
777
- assert.equal(result, false, "complete-milestone should fail verification when only .gsd/ files present");
821
+ try {
822
+ // Create feature branch with only .gsd/ files
823
+ execFileSync("git", ["checkout", "-b", "feat/ms-only-gsd"], { cwd: base, stdio: "ignore" });
824
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
825
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
826
+ execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
827
+ execFileSync("git", ["commit", "-m", "chore: milestone plan files"], { cwd: base, stdio: "ignore" });
828
+
829
+ const result = verifyExpectedArtifact("complete-milestone", "M001", base);
830
+ assert.equal(result, false, "complete-milestone should fail verification when only .gsd/ files present");
831
+ } finally {
832
+ cleanup(base);
833
+ }
778
834
  });
779
835
 
780
- test("verifyExpectedArtifact complete-milestone passes with impl files (#1703)", (t) => {
836
+ test("verifyExpectedArtifact complete-milestone passes with impl files (#1703)", () => {
781
837
  const base = makeGitBase();
782
- t.after(() => cleanup(base));
783
-
784
- // Create feature branch with implementation files AND milestone summary
785
- execFileSync("git", ["checkout", "-b", "feat/ms-with-impl"], { cwd: base, stdio: "ignore" });
786
- mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
787
- writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
788
- mkdirSync(join(base, "src"), { recursive: true });
789
- writeFileSync(join(base, "src", "app.ts"), "console.log('hello');");
790
- execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
791
- execFileSync("git", ["commit", "-m", "feat: implementation"], { cwd: base, stdio: "ignore" });
792
-
793
- const result = verifyExpectedArtifact("complete-milestone", "M001", base);
794
- assert.equal(result, true, "complete-milestone should pass verification with implementation files");
838
+ try {
839
+ // Create feature branch with implementation files AND milestone summary
840
+ execFileSync("git", ["checkout", "-b", "feat/ms-with-impl"], { cwd: base, stdio: "ignore" });
841
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
842
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
843
+ mkdirSync(join(base, "src"), { recursive: true });
844
+ writeFileSync(join(base, "src", "app.ts"), "console.log('hello');");
845
+ execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
846
+ execFileSync("git", ["commit", "-m", "feat: implementation"], { cwd: base, stdio: "ignore" });
847
+
848
+ const result = verifyExpectedArtifact("complete-milestone", "M001", base);
849
+ assert.equal(result, true, "complete-milestone should pass verification with implementation files");
850
+ } finally {
851
+ cleanup(base);
852
+ }
795
853
  });