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
@@ -5,8 +5,6 @@
5
5
  * Runs in a real temp git repo.
6
6
  */
7
7
 
8
- import { describe, test, afterEach } from "node:test";
9
- import assert from "node:assert/strict";
10
8
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, realpathSync } from "node:fs";
11
9
  import { join } from "node:path";
12
10
  import { tmpdir } from "node:os";
@@ -22,9 +20,10 @@ import {
22
20
  getActiveAutoWorktreeContext,
23
21
  } from "../auto-worktree.ts";
24
22
 
25
- // Note: execSync is used intentionally in tests for git operations with
26
- // controlled, hardcoded inputs (no user input). This is safe and matches
27
- // the pattern used by the original test file.
23
+ import { createTestContext } from "./test-helpers.ts";
24
+
25
+ const { assertEq, assertTrue, report } = createTestContext();
26
+
28
27
  function run(command: string, cwd: string): string {
29
28
  return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
30
29
  }
@@ -43,19 +42,11 @@ function createTempRepo(): string {
43
42
  return dir;
44
43
  }
45
44
 
46
- describe("auto-worktree lifecycle", () => {
45
+ async function main(): Promise<void> {
47
46
  const savedCwd = process.cwd();
48
47
  let tempDir = "";
49
48
 
50
- afterEach(() => {
51
- process.chdir(savedCwd);
52
- if (tempDir && existsSync(tempDir)) {
53
- rmSync(tempDir, { recursive: true, force: true });
54
- }
55
- tempDir = "";
56
- });
57
-
58
- test("create → detect → teardown", () => {
49
+ try {
59
50
  tempDir = createTempRepo();
60
51
 
61
52
  // Create .gsd/milestones/M003 with a dummy file (simulates planning artifacts)
@@ -65,26 +56,28 @@ describe("auto-worktree lifecycle", () => {
65
56
  run("git add .", tempDir);
66
57
  run("git commit -m \"add milestone\"", tempDir);
67
58
 
59
+ console.log("\n=== auto-worktree lifecycle ===");
60
+
68
61
  // ─── createAutoWorktree ──────────────────────────────────────────
69
62
  const wtPath = createAutoWorktree(tempDir, "M003");
70
63
 
71
- assert.ok(existsSync(wtPath), "worktree directory exists after create");
72
- assert.strictEqual(process.cwd(), wtPath, "process.cwd() is worktree path after create");
64
+ assertTrue(existsSync(wtPath), "worktree directory exists after create");
65
+ assertEq(process.cwd(), wtPath, "process.cwd() is worktree path after create");
73
66
 
74
67
  const branch = run("git branch --show-current", wtPath);
75
- assert.strictEqual(branch, "milestone/M003", "git branch is milestone/M003");
68
+ assertEq(branch, "milestone/M003", "git branch is milestone/M003");
76
69
 
77
- assert.ok(
70
+ assertTrue(
78
71
  existsSync(join(wtPath, ".gsd", "milestones", "M003", "CONTEXT.md")),
79
72
  "planning files inherited in worktree",
80
73
  );
81
74
 
82
75
  // ─── isInAutoWorktree ────────────────────────────────────────────
83
- assert.ok(isInAutoWorktree(tempDir), "isInAutoWorktree returns true when inside");
76
+ assertTrue(isInAutoWorktree(tempDir), "isInAutoWorktree returns true when inside");
84
77
 
85
78
  // ─── getAutoWorktreeOriginalBase ─────────────────────────────────
86
- assert.strictEqual(getAutoWorktreeOriginalBase(), tempDir, "originalBase returns temp dir");
87
- assert.deepStrictEqual(
79
+ assertEq(getAutoWorktreeOriginalBase(), tempDir, "originalBase returns temp dir");
80
+ assertEq(
88
81
  getActiveAutoWorktreeContext(),
89
82
  {
90
83
  originalBase: tempDir,
@@ -95,39 +88,33 @@ describe("auto-worktree lifecycle", () => {
95
88
  );
96
89
 
97
90
  // ─── getAutoWorktreePath ─────────────────────────────────────────
98
- assert.strictEqual(getAutoWorktreePath(tempDir, "M003"), wtPath, "getAutoWorktreePath returns correct path");
99
- assert.strictEqual(getAutoWorktreePath(tempDir, "M999"), null, "getAutoWorktreePath returns null for nonexistent");
91
+ assertEq(getAutoWorktreePath(tempDir, "M003"), wtPath, "getAutoWorktreePath returns correct path");
92
+ assertEq(getAutoWorktreePath(tempDir, "M999"), null, "getAutoWorktreePath returns null for nonexistent");
100
93
 
101
94
  // ─── teardownAutoWorktree ────────────────────────────────────────
102
95
  teardownAutoWorktree(tempDir, "M003");
103
96
 
104
- assert.strictEqual(process.cwd(), tempDir, "process.cwd() back to original after teardown");
105
- assert.ok(!existsSync(wtPath), "worktree directory removed after teardown");
106
- assert.ok(!isInAutoWorktree(tempDir), "isInAutoWorktree returns false after teardown");
107
- assert.strictEqual(getAutoWorktreeOriginalBase(), null, "originalBase is null after teardown");
108
- assert.strictEqual(getActiveAutoWorktreeContext(), null, "active auto-worktree context clears after teardown");
109
- });
97
+ assertEq(process.cwd(), tempDir, "process.cwd() back to original after teardown");
98
+ assertTrue(!existsSync(wtPath), "worktree directory removed after teardown");
99
+ assertTrue(!isInAutoWorktree(tempDir), "isInAutoWorktree returns false after teardown");
100
+ assertEq(getAutoWorktreeOriginalBase(), null, "originalBase is null after teardown");
101
+ assertEq(getActiveAutoWorktreeContext(), null, "active auto-worktree context clears after teardown");
110
102
 
111
- test("re-entry: create again, exit without teardown, re-enter", () => {
112
- tempDir = createTempRepo();
113
- const msDir = join(tempDir, ".gsd", "milestones", "M003");
114
- mkdirSync(msDir, { recursive: true });
115
- writeFileSync(join(msDir, "CONTEXT.md"), "# M003 Context\n");
116
- run("git add .", tempDir);
117
- run("git commit -m \"add milestone\"", tempDir);
103
+ // ─── Re-entry: create again, exit without teardown, re-enter ─────
104
+ console.log("\n=== re-entry ===");
118
105
 
119
106
  const wtPath2 = createAutoWorktree(tempDir, "M003");
120
- assert.ok(existsSync(wtPath2), "worktree re-created");
107
+ assertTrue(existsSync(wtPath2), "worktree re-created");
121
108
 
122
109
  // Manually chdir out (simulates pause/crash)
123
110
  process.chdir(tempDir);
124
111
 
125
112
  // enterAutoWorktree should re-enter
126
113
  const entered = enterAutoWorktree(tempDir, "M003");
127
- assert.strictEqual(process.cwd(), entered, "re-entered worktree via enterAutoWorktree");
128
- assert.strictEqual(getAutoWorktreeOriginalBase(), tempDir, "originalBase restored on re-entry");
129
- assert.ok(isInAutoWorktree(tempDir), "isInAutoWorktree true after re-entry");
130
- assert.deepStrictEqual(
114
+ assertEq(process.cwd(), entered, "re-entered worktree via enterAutoWorktree");
115
+ assertEq(getAutoWorktreeOriginalBase(), tempDir, "originalBase restored on re-entry");
116
+ assertTrue(isInAutoWorktree(tempDir), "isInAutoWorktree true after re-entry");
117
+ assertEq(
131
118
  getActiveAutoWorktreeContext(),
132
119
  {
133
120
  originalBase: tempDir,
@@ -139,151 +126,142 @@ describe("auto-worktree lifecycle", () => {
139
126
 
140
127
  // Cleanup
141
128
  teardownAutoWorktree(tempDir, "M003");
142
- });
143
129
 
144
- test("coexistence with manual worktree", async () => {
145
- tempDir = createTempRepo();
146
- const msDir = join(tempDir, ".gsd", "milestones", "M003");
147
- mkdirSync(msDir, { recursive: true });
148
- writeFileSync(join(msDir, "CONTEXT.md"), "# M003 Context\n");
149
- run("git add .", tempDir);
150
- run("git commit -m \"add milestone\"", tempDir);
130
+ // ─── Coexistence with manual worktree ─────────────────────────────
131
+ console.log("\n=== coexistence ===");
151
132
 
152
133
  // Import createWorktree directly for manual worktree
153
134
  const { createWorktree } = await import("../worktree-manager.ts");
154
135
 
155
136
  // Create manual worktree (uses worktree/<name> branch)
156
137
  const manualWt = createWorktree(tempDir, "feature-x");
157
- assert.ok(existsSync(manualWt.path), "manual worktree exists");
158
- assert.strictEqual(manualWt.branch, "worktree/feature-x", "manual worktree uses worktree/ prefix");
138
+ assertTrue(existsSync(manualWt.path), "manual worktree exists");
139
+ assertEq(manualWt.branch, "worktree/feature-x", "manual worktree uses worktree/ prefix");
159
140
 
160
141
  // Create auto-worktree alongside
161
142
  const autoWtPath = createAutoWorktree(tempDir, "M003");
162
- assert.ok(existsSync(autoWtPath), "auto-worktree coexists with manual");
163
- assert.ok(existsSync(manualWt.path), "manual worktree still exists");
143
+ assertTrue(existsSync(autoWtPath), "auto-worktree coexists with manual");
144
+ assertTrue(existsSync(manualWt.path), "manual worktree still exists");
164
145
 
165
146
  // Cleanup both
166
147
  teardownAutoWorktree(tempDir, "M003");
167
148
  const { removeWorktree } = await import("../worktree-manager.ts");
168
149
  removeWorktree(tempDir, "feature-x");
169
- });
170
-
171
- test("split-brain prevention: originalBase cleared after teardown", () => {
172
- tempDir = createTempRepo();
173
- const msDir = join(tempDir, ".gsd", "milestones", "M003");
174
- mkdirSync(msDir, { recursive: true });
175
- writeFileSync(join(msDir, "CONTEXT.md"), "# M003 Context\n");
176
- run("git add .", tempDir);
177
- run("git commit -m \"add milestone\"", tempDir);
178
-
179
- createAutoWorktree(tempDir, "M003");
180
- teardownAutoWorktree(tempDir, "M003");
181
-
182
- assert.strictEqual(getAutoWorktreeOriginalBase(), null, "no split-brain: originalBase cleared");
183
- });
184
-
185
- test("#1526: getMainBranch returns milestone/<MID> in auto-worktree", async () => {
186
- tempDir = createTempRepo();
187
- const msDir = join(tempDir, ".gsd", "milestones", "M005");
188
- mkdirSync(msDir, { recursive: true });
189
- writeFileSync(join(msDir, "CONTEXT.md"), "# M005 Context\n");
190
- run("git add .", tempDir);
191
- run("git commit -m \"add milestone\"", tempDir);
192
-
193
- const { GitServiceImpl } = await import("../git-service.ts");
194
-
195
- // Create worktree
196
- const wtPath = createAutoWorktree(tempDir, "M005");
197
- // Don't set main_branch pref so getMainBranch falls through to worktree detection
198
- const gitService = new GitServiceImpl(wtPath);
199
- gitService.setMilestoneId("M005");
200
-
201
- // Verify getMainBranch returns the milestone branch
202
- const mainBranch = gitService.getMainBranch();
203
- assert.strictEqual(mainBranch, "milestone/M005", "getMainBranch returns milestone/<MID> in auto-worktree");
204
-
205
- // Cleanup
206
- teardownAutoWorktree(tempDir, "M005");
207
- });
208
150
 
209
- test("#1713: stale worktree directory without .git file", async () => {
210
- tempDir = createTempRepo();
211
- const msDir = join(tempDir, ".gsd", "milestones", "M010");
212
- mkdirSync(msDir, { recursive: true });
213
- writeFileSync(join(msDir, "CONTEXT.md"), "# M010 Context\n");
214
- run("git add .", tempDir);
215
- run("git commit -m \"add milestone\"", tempDir);
151
+ // ─── Failure: split-brain prevention ──────────────────────────────
152
+ console.log("\n=== split-brain prevention ===");
153
+ // After teardown, originalBase should be null
154
+ assertEq(getAutoWorktreeOriginalBase(), null, "no split-brain: originalBase cleared");
216
155
 
217
- // Simulate a crash leaving a stale directory with no .git file.
218
- const { worktreePath } = await import("../worktree-manager.ts");
219
- const staleDir = worktreePath(tempDir, "M010");
220
- mkdirSync(staleDir, { recursive: true });
221
- writeFileSync(join(staleDir, "orphan.txt"), "stale leftover\n");
222
- assert.ok(existsSync(staleDir), "stale directory exists before recovery");
223
- assert.ok(!existsSync(join(staleDir, ".git")), "stale directory has no .git file");
156
+ // ─── #1526: getMainBranch returns milestone branch in auto-worktree ──
157
+ console.log("\n=== #1526: getMainBranch() returns milestone/<MID> in auto-worktree ===");
158
+ {
159
+ const { GitServiceImpl } = await import("../git-service.ts");
224
160
 
225
- // createAutoWorktree should remove the stale dir and create a real worktree
226
- const recoveredPath = createAutoWorktree(tempDir, "M010");
227
- assert.ok(existsSync(recoveredPath), "worktree created after stale dir recovery");
228
- assert.ok(existsSync(join(recoveredPath, ".git")), "recovered worktree has .git file");
229
- assert.ok(!existsSync(join(recoveredPath, "orphan.txt")), "stale file removed by recovery");
230
-
231
- teardownAutoWorktree(tempDir, "M010");
232
- });
233
-
234
- test("#778: reconcile plan checkboxes on re-attach", async () => {
235
- tempDir = createTempRepo();
236
- const msDir = join(tempDir, ".gsd", "milestones", "M003");
237
- mkdirSync(msDir, { recursive: true });
238
- writeFileSync(join(msDir, "CONTEXT.md"), "# M003 Context\n");
239
- run("git add .", tempDir);
240
- run("git commit -m \"add milestone\"", tempDir);
161
+ // Create worktree
162
+ const wtPath = createAutoWorktree(tempDir, "M005");
163
+ // Don't set main_branch pref so getMainBranch falls through to worktree detection
164
+ const gitService = new GitServiceImpl(wtPath);
165
+ gitService.setMilestoneId("M005");
241
166
 
242
- const planRelPath = join(".gsd", "milestones", "M004", "slices", "S01", "S01-PLAN.md");
243
- const planDir = join(tempDir, ".gsd", "milestones", "M004", "slices", "S01");
244
- const { mkdirSync: mkdir, writeFileSync: write, readFileSync: read } = await import("node:fs");
167
+ // Verify getMainBranch returns the milestone branch
168
+ const mainBranch = gitService.getMainBranch();
169
+ assertEq(mainBranch, "milestone/M005", "getMainBranch returns milestone/<MID> in auto-worktree");
245
170
 
246
- // Plan on integration branch (project root): T01 [x], T02 [x]
247
- mkdir(planDir, { recursive: true });
248
- write(
249
- join(tempDir, planRelPath),
250
- "# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
251
- );
171
+ // Cleanup
172
+ teardownAutoWorktree(tempDir, "M005");
173
+ }
252
174
 
253
- run(`git add .`, tempDir);
254
- run(`git commit -m "add plan with T01 and T02 checked" --allow-empty`, tempDir);
175
+ // ─── #1713: stale worktree directory recovery ─────────────────────
176
+ console.log("\n=== #1713: stale worktree directory without .git file ===");
177
+ {
178
+ // Simulate a crash leaving a stale directory with no .git file.
179
+ // createAutoWorktree should detect and remove the stale directory,
180
+ // then successfully create a fresh worktree.
181
+ const { worktreePath } = await import("../worktree-manager.ts");
182
+ const staleDir = worktreePath(tempDir, "M010");
183
+ mkdirSync(staleDir, { recursive: true });
184
+ // Write a dummy file to prove it's not an empty directory
185
+ writeFileSync(join(staleDir, "orphan.txt"), "stale leftover\n");
186
+ assertTrue(existsSync(staleDir), "stale directory exists before recovery");
187
+ assertTrue(!existsSync(join(staleDir, ".git")), "stale directory has no .git file");
188
+
189
+ // createAutoWorktree should remove the stale dir and create a real worktree
190
+ const recoveredPath = createAutoWorktree(tempDir, "M010");
191
+ assertTrue(existsSync(recoveredPath), "worktree created after stale dir recovery");
192
+ assertTrue(existsSync(join(recoveredPath, ".git")), "recovered worktree has .git file");
193
+ assertTrue(!existsSync(join(recoveredPath, "orphan.txt")), "stale file removed by recovery");
194
+
195
+ teardownAutoWorktree(tempDir, "M010");
196
+ }
255
197
 
256
- // Create milestone branch with only T01 [x] (simulating crash before T02 commit)
257
- const milestoneBranch = "milestone/M004";
258
- run(`git checkout -b ${milestoneBranch}`, tempDir);
259
- mkdir(planDir, { recursive: true });
260
- write(
261
- join(tempDir, planRelPath),
262
- "# S01 Plan\n- [x] **T01:** task one\n- [ ] **T02:** task two\n- [ ] **T03:** task three\n",
263
- );
264
- run(`git add .`, tempDir);
265
- run(`git commit -m "milestone: only T01 checked"`, tempDir);
266
- run(`git checkout main`, tempDir);
267
-
268
- // Restore project root plan (T01+T02 [x])
269
- write(
270
- join(tempDir, planRelPath),
271
- "# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
272
- );
198
+ // ─── #778: reconcile plan checkboxes on re-attach ─────────────────
199
+ console.log("\n=== #778: reconcile plan checkboxes on re-attach ===");
200
+ {
201
+ // Simulate: T01 [x] was committed to milestone branch, T02 [x] was
202
+ // written to project root by syncStateToProjectRoot() but the
203
+ // auto-commit crashed before it fired. On restart the worktree is
204
+ // re-created from the milestone branch HEAD (T02 still [ ]).
205
+ // reconcilePlanCheckboxes should forward-apply T02 [x] from the root.
206
+
207
+ const planRelPath = join(".gsd", "milestones", "M004", "slices", "S01", "S01-PLAN.md");
208
+ const planDir = join(tempDir, ".gsd", "milestones", "M004", "slices", "S01");
209
+ const { mkdirSync: mkdir, writeFileSync: write, readFileSync: read } = await import("node:fs");
210
+
211
+ // Plan on integration branch (project root): T01 [x], T02 [x]
212
+ mkdir(planDir, { recursive: true });
213
+ write(
214
+ join(tempDir, planRelPath),
215
+ "# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
216
+ );
217
+
218
+ // Write integration-branch plan to git so milestone branch starts from it
219
+ run(`git add .`, tempDir);
220
+ run(`git commit -m "add plan with T01 and T02 checked" --allow-empty`, tempDir);
221
+
222
+ // Create milestone branch with only T01 [x] (simulating crash before T02 commit)
223
+ const milestoneBranch = "milestone/M004";
224
+ run(`git checkout -b ${milestoneBranch}`, tempDir);
225
+ mkdir(planDir, { recursive: true });
226
+ write(
227
+ join(tempDir, planRelPath),
228
+ "# S01 Plan\n- [x] **T01:** task one\n- [ ] **T02:** task two\n- [ ] **T03:** task three\n",
229
+ );
230
+ run(`git add .`, tempDir);
231
+ run(`git commit -m "milestone: only T01 checked"`, tempDir);
232
+ run(`git checkout main`, tempDir);
233
+
234
+ // Restore project root plan (T01+T02 [x]) — simulates syncStateToProjectRoot
235
+ write(
236
+ join(tempDir, planRelPath),
237
+ "# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
238
+ );
239
+
240
+ // Create worktree re-attached to existing milestone branch (T02 still [ ] in branch)
241
+ const wtPath = createAutoWorktree(tempDir, "M004");
242
+
243
+ try {
244
+ const wtPlanPath = join(wtPath, planRelPath);
245
+ assertTrue(existsSync(wtPlanPath), "plan file exists in worktree after re-attach");
246
+
247
+ const wtPlan = read(wtPlanPath, "utf-8");
248
+ assertTrue(wtPlan.includes("- [x] **T02:"), "T02 should be [x] after reconciliation (was [ ] on branch)");
249
+ assertTrue(wtPlan.includes("- [x] **T01:"), "T01 stays [x]");
250
+ assertTrue(wtPlan.includes("- [ ] **T03:"), "T03 stays [ ] (not in root either)");
251
+ } finally {
252
+ teardownAutoWorktree(tempDir, "M004");
253
+ }
254
+ }
273
255
 
274
- // Create worktree re-attached to existing milestone branch (T02 still [ ] in branch)
275
- const wtPath = createAutoWorktree(tempDir, "M004");
256
+ } finally {
257
+ // Always restore cwd and clean up
258
+ process.chdir(savedCwd);
259
+ if (tempDir && existsSync(tempDir)) {
260
+ rmSync(tempDir, { recursive: true, force: true });
261
+ }
262
+ }
276
263
 
277
- try {
278
- const wtPlanPath = join(wtPath, planRelPath);
279
- assert.ok(existsSync(wtPlanPath), "plan file exists in worktree after re-attach");
264
+ report();
265
+ }
280
266
 
281
- const wtPlan = read(wtPlanPath, "utf-8");
282
- assert.ok(wtPlan.includes("- [x] **T02:"), "T02 should be [x] after reconciliation (was [ ] on branch)");
283
- assert.ok(wtPlan.includes("- [x] **T01:"), "T01 stays [x]");
284
- assert.ok(wtPlan.includes("- [ ] **T03:"), "T03 stays [ ] (not in root either)");
285
- } finally {
286
- teardownAutoWorktree(tempDir, "M004");
287
- }
288
- });
289
- });
267
+ main();
@@ -12,14 +12,15 @@
12
12
  * Pattern: derive state → write file → invalidate cache → derive again → verify update
13
13
  */
14
14
 
15
- import { describe, test, afterEach } from "node:test";
16
- import assert from "node:assert/strict";
17
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
15
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
18
16
  import { join } from 'node:path';
19
17
  import { tmpdir } from 'node:os';
20
18
 
21
19
  import { deriveState, invalidateStateCache } from '../state.ts';
22
20
  import { invalidateAllCaches } from '../cache.ts';
21
+ import { createTestContext } from './test-helpers.ts';
22
+
23
+ const { assertEq, assertTrue, report } = createTestContext();
23
24
 
24
25
  function createBase(): string {
25
26
  const base = mkdtempSync(join(tmpdir(), 'gsd-cache-stale-'));
@@ -43,9 +44,11 @@ function writeSliceFile(base: string, mid: string, sid: string, suffix: string,
43
44
  writeFileSync(join(dir, `${sid}-${suffix}.md`), content);
44
45
  }
45
46
 
46
- describe("cache-staleness-regression", () => {
47
+ async function main(): Promise<void> {
47
48
 
48
- test("#1240: roadmap written after first derive detected after invalidation", async () => {
49
+ // ─── 1. Regression #1240: New roadmap detected after cache invalidation
50
+ console.log('\n=== 1. #1240: roadmap written after first derive → detected after invalidation ===');
51
+ {
49
52
  const base = createBase();
50
53
  try {
51
54
  // Step 1: Create milestone with just context (no roadmap)
@@ -54,7 +57,7 @@ describe("cache-staleness-regression", () => {
54
57
  invalidateAllCaches();
55
58
  invalidateStateCache();
56
59
  const state1 = await deriveState(base);
57
- assert.strictEqual(state1.phase, 'pre-planning', 'initial: pre-planning (no roadmap)');
60
+ assertEq(state1.phase, 'pre-planning', 'initial: pre-planning (no roadmap)');
58
61
 
59
62
  // Step 2: Write roadmap (simulating what the LLM does during planning)
60
63
  const roadmap = [
@@ -77,14 +80,16 @@ describe("cache-staleness-regression", () => {
77
80
  invalidateAllCaches();
78
81
  invalidateStateCache();
79
82
  const state2 = await deriveState(base);
80
- assert.strictEqual(state2.phase, 'planning', '#1240: after roadmap write + invalidation → planning phase');
81
- assert.strictEqual(state2.activeSlice?.id, 'S01', '#1240: S01 is now the active slice');
83
+ assertEq(state2.phase, 'planning', '#1240: after roadmap write + invalidation → planning phase');
84
+ assertEq(state2.activeSlice?.id, 'S01', '#1240: S01 is now the active slice');
82
85
  } finally {
83
86
  cleanup(base);
84
87
  }
85
- });
88
+ }
86
89
 
87
- test("#1249: slice context written mid-loop → detected after invalidation", async () => {
90
+ // ─── 2. Regression #1249: Slice context detected after cache invalidation
91
+ console.log('\n=== 2. #1249: slice context written mid-loop → detected after invalidation ===');
92
+ {
88
93
  const base = createBase();
89
94
  try {
90
95
  // Create a milestone in needs-discussion phase (CONTEXT-DRAFT, no CONTEXT)
@@ -95,7 +100,7 @@ describe("cache-staleness-regression", () => {
95
100
  invalidateAllCaches();
96
101
  invalidateStateCache();
97
102
  const state1 = await deriveState(base);
98
- assert.strictEqual(state1.phase, 'needs-discussion', 'initial: needs-discussion');
103
+ assertEq(state1.phase, 'needs-discussion', 'initial: needs-discussion');
99
104
 
100
105
  // Simulate: discussion completes, CONTEXT.md is written
101
106
  writeMilestoneFile(base, 'M001', 'CONTEXT', '# M001: Test\n\nFull context after discussion.\n');
@@ -107,16 +112,21 @@ describe("cache-staleness-regression", () => {
107
112
  invalidateAllCaches();
108
113
  invalidateStateCache();
109
114
  const state2 = await deriveState(base);
110
- assert.ok(
115
+ // Should now be pre-planning (has context, but no roadmap yet)
116
+ // Actually needs-discussion won't trigger because now CONTEXT exists
117
+ // The state should advance past needs-discussion
118
+ assertTrue(
111
119
  state2.phase !== 'needs-discussion',
112
120
  '#1249: after context write + invalidation → not stuck in needs-discussion',
113
121
  );
114
122
  } finally {
115
123
  cleanup(base);
116
124
  }
117
- });
125
+ }
118
126
 
119
- test("state cache TTL: fresh reads after 100ms", async () => {
127
+ // ─── 3. State cache TTL expires naturally ─────────────────────────────
128
+ console.log('\n=== 3. state cache TTL: fresh reads after 100ms ===');
129
+ {
120
130
  const base = createBase();
121
131
  try {
122
132
  writeMilestoneFile(base, 'M001', 'CONTEXT', '# M001\n\nDesc.\n');
@@ -124,7 +134,7 @@ describe("cache-staleness-regression", () => {
124
134
  invalidateAllCaches();
125
135
  invalidateStateCache();
126
136
  const state1 = await deriveState(base);
127
- assert.strictEqual(state1.phase, 'pre-planning', 'initial: pre-planning');
137
+ assertEq(state1.phase, 'pre-planning', 'initial: pre-planning');
128
138
 
129
139
  // Write roadmap immediately
130
140
  writeMilestoneFile(base, 'M001', 'ROADMAP', [
@@ -147,13 +157,15 @@ describe("cache-staleness-regression", () => {
147
157
  invalidateAllCaches();
148
158
  invalidateStateCache();
149
159
  const state3 = await deriveState(base);
150
- assert.strictEqual(state3.phase, 'planning', 'after TTL expiry + invalidation → planning');
160
+ assertEq(state3.phase, 'planning', 'after TTL expiry + invalidation → planning');
151
161
  } finally {
152
162
  cleanup(base);
153
163
  }
154
- });
164
+ }
155
165
 
156
- test("task marked done in plan state advances", async () => {
166
+ // ─── 4. Task completion detection after file write ────────────────────
167
+ console.log('\n=== 4. task marked done in plan → state advances ===');
168
+ {
157
169
  const base = createBase();
158
170
  try {
159
171
  writeMilestoneFile(base, 'M001', 'CONTEXT', '# M001\n\nDesc.\n');
@@ -182,7 +194,7 @@ describe("cache-staleness-regression", () => {
182
194
  invalidateAllCaches();
183
195
  invalidateStateCache();
184
196
  const state1 = await deriveState(base);
185
- assert.strictEqual(state1.activeTask?.id, 'T01', 'initial: T01 is active task');
197
+ assertEq(state1.activeTask?.id, 'T01', 'initial: T01 is active task');
186
198
 
187
199
  // Mark T01 as done by rewriting the plan
188
200
  writeSliceFile(base, 'M001', 'S01', 'PLAN', [
@@ -198,13 +210,15 @@ describe("cache-staleness-regression", () => {
198
210
  invalidateAllCaches();
199
211
  invalidateStateCache();
200
212
  const state2 = await deriveState(base);
201
- assert.strictEqual(state2.activeTask?.id, 'T02', 'after T01 done → T02 is active task');
213
+ assertEq(state2.activeTask?.id, 'T02', 'after T01 done → T02 is active task');
202
214
  } finally {
203
215
  cleanup(base);
204
216
  }
205
- });
217
+ }
206
218
 
207
- test("all tasks done summarizing phase", async () => {
219
+ // ─── 5. Slice completion detection ────────────────────────────────────
220
+ console.log('\n=== 5. all tasks done → summarizing phase ===');
221
+ {
208
222
  const base = createBase();
209
223
  try {
210
224
  writeMilestoneFile(base, 'M001', 'CONTEXT', '# M001\n\nDesc.\n');
@@ -231,7 +245,7 @@ describe("cache-staleness-regression", () => {
231
245
  invalidateAllCaches();
232
246
  invalidateStateCache();
233
247
  const state1 = await deriveState(base);
234
- assert.strictEqual(state1.phase, 'executing', 'initial: executing');
248
+ assertEq(state1.phase, 'executing', 'initial: executing');
235
249
 
236
250
  // Mark task done
237
251
  writeSliceFile(base, 'M001', 'S01', 'PLAN', [
@@ -246,13 +260,15 @@ describe("cache-staleness-regression", () => {
246
260
  invalidateAllCaches();
247
261
  invalidateStateCache();
248
262
  const state2 = await deriveState(base);
249
- assert.strictEqual(state2.phase, 'summarizing', 'after all tasks done → summarizing');
263
+ assertEq(state2.phase, 'summarizing', 'after all tasks done → summarizing');
250
264
  } finally {
251
265
  cleanup(base);
252
266
  }
253
- });
267
+ }
254
268
 
255
- test("roadmap slice marked [x] → next slice active", async () => {
269
+ // ─── 6. Roadmap slice marked doneadvance to next slice ─────────────
270
+ console.log('\n=== 6. roadmap slice marked [x] → next slice active ===');
271
+ {
256
272
  const base = createBase();
257
273
  try {
258
274
  writeMilestoneFile(base, 'M001', 'CONTEXT', '# M001\n\nDesc.\n');
@@ -269,7 +285,7 @@ describe("cache-staleness-regression", () => {
269
285
  invalidateAllCaches();
270
286
  invalidateStateCache();
271
287
  const state1 = await deriveState(base);
272
- assert.strictEqual(state1.activeSlice?.id, 'S01', 'initial: S01 active');
288
+ assertEq(state1.activeSlice?.id, 'S01', 'initial: S01 active');
273
289
 
274
290
  // Mark S01 as done in roadmap
275
291
  writeMilestoneFile(base, 'M001', 'ROADMAP', [
@@ -286,9 +302,16 @@ describe("cache-staleness-regression", () => {
286
302
  invalidateAllCaches();
287
303
  invalidateStateCache();
288
304
  const state2 = await deriveState(base);
289
- assert.strictEqual(state2.activeSlice?.id, 'S02', 'after S01 done → S02 active');
305
+ assertEq(state2.activeSlice?.id, 'S02', 'after S01 done → S02 active');
290
306
  } finally {
291
307
  cleanup(base);
292
308
  }
293
- });
309
+ }
310
+
311
+ report();
312
+ }
313
+
314
+ main().catch((error) => {
315
+ console.error(error);
316
+ process.exit(1);
294
317
  });