gsd-pi 2.44.0-dev.848dd4c → 2.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/README.md +12 -30
  2. package/dist/resources/extensions/gsd/auto-start.js +0 -10
  3. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +0 -5
  4. package/dist/web/standalone/.next/BUILD_ID +1 -1
  5. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  6. package/dist/web/standalone/.next/build-manifest.json +3 -3
  7. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  8. package/dist/web/standalone/.next/required-server-files.json +3 -3
  9. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  10. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  12. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  20. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  30. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  31. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  32. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  33. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  34. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  35. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  36. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  37. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  38. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  39. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  40. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  74. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  80. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  94. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  96. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  98. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  100. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/index.html +1 -1
  110. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  111. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  112. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  113. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  115. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/page.js +2 -2
  117. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  119. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  120. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  121. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/middleware.js +2 -2
  123. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  125. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  126. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  127. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  128. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
  129. package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
  130. package/dist/web/standalone/.next/static/chunks/app/page-7e9530a7122506c5.js +1 -0
  131. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
  132. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
  133. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  134. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  135. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  136. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  137. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  138. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  139. package/dist/web/standalone/server.js +1 -1
  140. package/package.json +1 -1
  141. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +8 -6
  142. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +26 -24
  144. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +48 -29
  146. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +44 -34
  148. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/session-manager.test.js +34 -30
  150. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +12 -10
  152. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  153. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +47 -43
  154. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
  155. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
  156. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
  157. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +43 -31
  158. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +45 -40
  159. package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
  160. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
  161. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
  162. package/src/resources/extensions/gsd/auto-start.ts +0 -14
  163. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +0 -8
  164. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
  165. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +16 -14
  166. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +57 -43
  167. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +13 -11
  168. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +523 -465
  169. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +75 -73
  170. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +56 -34
  171. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +656 -533
  172. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +143 -165
  173. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +52 -29
  174. package/src/resources/extensions/gsd/tests/captures.test.ts +176 -148
  175. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +33 -32
  176. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +143 -141
  177. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
  178. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
  179. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +59 -38
  180. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +263 -228
  181. package/src/resources/extensions/gsd/tests/complete-task.test.ts +302 -250
  182. package/src/resources/extensions/gsd/tests/context-store.test.ts +367 -354
  183. package/src/resources/extensions/gsd/tests/continue-here.test.ts +72 -68
  184. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +106 -92
  185. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +35 -27
  186. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +237 -220
  187. package/src/resources/extensions/gsd/tests/db-writer.test.ts +420 -390
  188. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +92 -76
  189. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +83 -68
  190. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +183 -152
  191. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +101 -78
  192. package/src/resources/extensions/gsd/tests/derive-state.test.ts +227 -192
  193. package/src/resources/extensions/gsd/tests/detection.test.ts +278 -232
  194. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +34 -30
  195. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +180 -164
  196. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +49 -43
  197. package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +32 -28
  198. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +29 -27
  199. package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +38 -34
  200. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +75 -54
  201. package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +32 -21
  202. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +97 -72
  203. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +44 -38
  204. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +145 -104
  205. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +106 -84
  206. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +60 -54
  207. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +93 -72
  208. package/src/resources/extensions/gsd/tests/doctor.test.ts +134 -104
  209. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +131 -123
  210. package/src/resources/extensions/gsd/tests/exit-command.test.ts +24 -20
  211. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +57 -48
  212. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +7 -5
  213. package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +42 -30
  214. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +206 -198
  215. package/src/resources/extensions/gsd/tests/git-locale.test.ts +27 -13
  216. package/src/resources/extensions/gsd/tests/git-service.test.ts +388 -285
  217. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +39 -31
  218. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +69 -63
  219. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +264 -255
  220. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +119 -108
  221. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +103 -81
  222. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +262 -229
  223. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
  224. package/src/resources/extensions/gsd/tests/health-widget.test.ts +37 -29
  225. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +102 -81
  226. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +18 -16
  227. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +46 -41
  228. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +53 -42
  229. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +91 -75
  230. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
  231. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +194 -150
  232. package/src/resources/extensions/gsd/tests/md-importer.test.ts +125 -101
  233. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +54 -45
  234. package/src/resources/extensions/gsd/tests/memory-store.test.ts +93 -80
  235. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +66 -57
  236. package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +93 -83
  237. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +170 -161
  238. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +141 -125
  239. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +131 -107
  240. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +96 -87
  241. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +164 -125
  242. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +94 -81
  243. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +36 -35
  244. package/src/resources/extensions/gsd/tests/overrides.test.ts +106 -99
  245. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +47 -40
  246. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +28 -25
  247. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +83 -66
  248. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +77 -54
  249. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +115 -68
  250. package/src/resources/extensions/gsd/tests/parsers.test.ts +611 -546
  251. package/src/resources/extensions/gsd/tests/paths.test.ts +87 -72
  252. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +117 -77
  253. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
  254. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +119 -93
  255. package/src/resources/extensions/gsd/tests/queue-order.test.ts +82 -70
  256. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +55 -42
  257. package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +73 -45
  258. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +38 -28
  259. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +80 -73
  260. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +74 -71
  261. package/src/resources/extensions/gsd/tests/requirements.test.ts +75 -70
  262. package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +66 -44
  263. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +181 -114
  264. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +65 -63
  265. package/src/resources/extensions/gsd/tests/run-uat.test.ts +128 -66
  266. package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +25 -18
  267. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +44 -37
  268. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +26 -19
  269. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +8 -6
  270. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +28 -22
  271. package/src/resources/extensions/gsd/tests/token-savings.test.ts +56 -54
  272. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +25 -23
  273. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +11 -9
  274. package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +82 -66
  275. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +47 -46
  276. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +22 -20
  277. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +86 -84
  278. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +43 -41
  279. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +96 -94
  280. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +13 -11
  281. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +29 -27
  282. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +52 -50
  283. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +13 -10
  284. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +18 -14
  285. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +39 -38
  286. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +21 -17
  287. package/src/resources/extensions/gsd/tests/worktree-health.test.ts +30 -25
  288. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +37 -30
  289. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +22 -15
  290. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +66 -59
  291. package/src/resources/extensions/gsd/tests/worktree.test.ts +50 -44
  292. package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +0 -1
  293. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
  294. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
  295. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +0 -100
  296. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +0 -63
  297. /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → mgkxN0mGP6gSUmGPEzbk_}/_buildManifest.js +0 -0
  298. /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → mgkxN0mGP6gSUmGPEzbk_}/_ssgManifest.js +0 -0
@@ -1,5 +1,3 @@
1
- import { describe, test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
1
  import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
2
  import { join, dirname } from 'node:path';
5
3
  import { tmpdir } from 'node:os';
@@ -24,6 +22,7 @@ function loadPromptFromWorktree(name: string, vars: Record<string, string> = {})
24
22
  return content.trim();
25
23
  }
26
24
 
25
+ const { assertEq, assertTrue, report } = createTestContext();
27
26
  // ─── Fixture Helpers ───────────────────────────────────────────────────────
28
27
 
29
28
  function createFixtureBase(): string {
@@ -162,7 +161,7 @@ Found a blocker.
162
161
  `;
163
162
 
164
163
  const s = parseSummary(content);
165
- assert.deepStrictEqual(s.frontmatter.blocker_discovered, true, 'blocker_discovered: true (string) extracts as true');
164
+ assertEq(s.frontmatter.blocker_discovered, true, 'blocker_discovered: true (string) extracts as true');
166
165
  }
167
166
 
168
167
  console.log('\n=== parseSummary: blocker_discovered false (string) ===');
@@ -185,7 +184,7 @@ No blocker.
185
184
  `;
186
185
 
187
186
  const s = parseSummary(content);
188
- assert.deepStrictEqual(s.frontmatter.blocker_discovered, false, 'blocker_discovered: false extracts as false');
187
+ assertEq(s.frontmatter.blocker_discovered, false, 'blocker_discovered: false extracts as false');
189
188
  }
190
189
 
191
190
  console.log('\n=== parseSummary: blocker_discovered missing (defaults to false) ===');
@@ -207,7 +206,7 @@ No blocker field at all.
207
206
  `;
208
207
 
209
208
  const s = parseSummary(content);
210
- assert.deepStrictEqual(s.frontmatter.blocker_discovered, false, 'blocker_discovered missing defaults to false');
209
+ assertEq(s.frontmatter.blocker_discovered, false, 'blocker_discovered missing defaults to false');
211
210
  }
212
211
 
213
212
  console.log('\n=== parseSummary: blocker_discovered true (boolean from YAML) ===');
@@ -233,7 +232,7 @@ Blocker as boolean.
233
232
  `;
234
233
 
235
234
  const s = parseSummary(content);
236
- assert.deepStrictEqual(s.frontmatter.blocker_discovered, true, 'blocker_discovered: true (YAML boolean) extracts as true');
235
+ assertEq(s.frontmatter.blocker_discovered, true, 'blocker_discovered: true (YAML boolean) extracts as true');
237
236
  }
238
237
 
239
238
  console.log('\n=== parseSummary: blocker_discovered with full frontmatter ===');
@@ -276,10 +275,10 @@ Major deviation from plan.
276
275
  `;
277
276
 
278
277
  const s = parseSummary(content);
279
- assert.deepStrictEqual(s.frontmatter.blocker_discovered, true, 'blocker_discovered true with full frontmatter');
280
- assert.deepStrictEqual(s.frontmatter.id, 'T05', 'other fields still parse correctly alongside blocker_discovered');
281
- assert.deepStrictEqual(s.frontmatter.duration, '15min', 'duration still parsed');
282
- assert.deepStrictEqual(s.frontmatter.provides[0], 'something', 'provides still parsed');
278
+ assertEq(s.frontmatter.blocker_discovered, true, 'blocker_discovered true with full frontmatter');
279
+ assertEq(s.frontmatter.id, 'T05', 'other fields still parse correctly alongside blocker_discovered');
280
+ assertEq(s.frontmatter.duration, '15min', 'duration still parsed');
281
+ assertEq(s.frontmatter.provides[0], 'something', 'provides still parsed');
283
282
  }
284
283
 
285
284
  // ═══════════════════════════════════════════════════════════════════════════
@@ -295,11 +294,11 @@ console.log('\n=== deriveState: blocker found, no REPLAN → replanning-slice ==
295
294
  writeTaskSummary(base, 'M001', 'S01', 'T01', makeTaskSummary('T01', true));
296
295
 
297
296
  const state = await deriveState(base);
298
- assert.deepStrictEqual(state.phase, 'replanning-slice', 'phase is replanning-slice when blocker found and no REPLAN.md');
299
- assert.ok(state.nextAction.includes('T01'), 'nextAction mentions blocker task T01');
300
- assert.ok(state.nextAction.includes('blocker_discovered'), 'nextAction mentions blocker_discovered');
301
- assert.deepStrictEqual(state.activeTask?.id, 'T02', 'activeTask is still T02 (the next incomplete task)');
302
- assert.ok(state.blockers.length > 0, 'blockers array is non-empty');
297
+ assertEq(state.phase, 'replanning-slice', 'phase is replanning-slice when blocker found and no REPLAN.md');
298
+ assertTrue(state.nextAction.includes('T01'), 'nextAction mentions blocker task T01');
299
+ assertTrue(state.nextAction.includes('blocker_discovered'), 'nextAction mentions blocker_discovered');
300
+ assertEq(state.activeTask?.id, 'T02', 'activeTask is still T02 (the next incomplete task)');
301
+ assertTrue(state.blockers.length > 0, 'blockers array is non-empty');
303
302
  rmSync(base, { recursive: true, force: true });
304
303
  }
305
304
 
@@ -313,8 +312,8 @@ console.log('\n=== deriveState: blocker found + REPLAN exists → executing (loo
313
312
  writeReplanFile(base, 'M001', 'S01', '# Replan\n\nAlready replanned.');
314
313
 
315
314
  const state = await deriveState(base);
316
- assert.deepStrictEqual(state.phase, 'executing', 'phase is executing when REPLAN.md exists (loop protection)');
317
- assert.deepStrictEqual(state.activeTask?.id, 'T02', 'activeTask is T02');
315
+ assertEq(state.phase, 'executing', 'phase is executing when REPLAN.md exists (loop protection)');
316
+ assertEq(state.activeTask?.id, 'T02', 'activeTask is T02');
318
317
  rmSync(base, { recursive: true, force: true });
319
318
  }
320
319
 
@@ -327,8 +326,8 @@ console.log('\n=== deriveState: no blocker in completed tasks → executing ==='
327
326
  writeTaskSummary(base, 'M001', 'S01', 'T01', makeTaskSummary('T01', false));
328
327
 
329
328
  const state = await deriveState(base);
330
- assert.deepStrictEqual(state.phase, 'executing', 'phase is executing when no blocker found');
331
- assert.deepStrictEqual(state.activeTask?.id, 'T02', 'activeTask is T02');
329
+ assertEq(state.phase, 'executing', 'phase is executing when no blocker found');
330
+ assertEq(state.activeTask?.id, 'T02', 'activeTask is T02');
332
331
  rmSync(base, { recursive: true, force: true });
333
332
  }
334
333
 
@@ -342,9 +341,9 @@ console.log('\n=== deriveState: multiple completed tasks, one blocker → replan
342
341
  writeTaskSummary(base, 'M001', 'S01', 'T02', makeTaskSummary('T02', true));
343
342
 
344
343
  const state = await deriveState(base);
345
- assert.deepStrictEqual(state.phase, 'replanning-slice', 'phase is replanning-slice when T02 has blocker');
346
- assert.ok(state.nextAction.includes('T02'), 'nextAction mentions blocker task T02');
347
- assert.deepStrictEqual(state.activeTask?.id, 'T03', 'activeTask is T03 (next incomplete)');
344
+ assertEq(state.phase, 'replanning-slice', 'phase is replanning-slice when T02 has blocker');
345
+ assertTrue(state.nextAction.includes('T02'), 'nextAction mentions blocker task T02');
346
+ assertEq(state.activeTask?.id, 'T03', 'activeTask is T03 (next incomplete)');
348
347
  rmSync(base, { recursive: true, force: true });
349
348
  }
350
349
 
@@ -357,7 +356,7 @@ console.log('\n=== deriveState: completed task with no summary file → executin
357
356
  // No summary file written for T01
358
357
 
359
358
  const state = await deriveState(base);
360
- assert.deepStrictEqual(state.phase, 'executing', 'phase is executing when completed task has no summary');
359
+ assertEq(state.phase, 'executing', 'phase is executing when completed task has no summary');
361
360
  rmSync(base, { recursive: true, force: true });
362
361
  }
363
362
 
@@ -377,11 +376,11 @@ console.log('\n=== prompt: replan-slice template loads and substitutes variables
377
376
  inlinedContext: '## Inlined Context\n\nTest context here.',
378
377
  });
379
378
 
380
- assert.ok(prompt.includes('M001'), 'prompt contains milestoneId');
381
- assert.ok(prompt.includes('S01'), 'prompt contains sliceId');
382
- assert.ok(prompt.includes('Test Slice'), 'prompt contains sliceTitle');
383
- assert.ok(prompt.includes('.gsd/milestones/M001/slices/S01/S01-PLAN.md'), 'prompt contains planPath');
384
- assert.ok(prompt.includes('Test context here'), 'prompt contains inlined context');
379
+ assertTrue(prompt.includes('M001'), 'prompt contains milestoneId');
380
+ assertTrue(prompt.includes('S01'), 'prompt contains sliceId');
381
+ assertTrue(prompt.includes('Test Slice'), 'prompt contains sliceTitle');
382
+ assertTrue(prompt.includes('.gsd/milestones/M001/slices/S01/S01-PLAN.md'), 'prompt contains planPath');
383
+ assertTrue(prompt.includes('Test context here'), 'prompt contains inlined context');
385
384
  }
386
385
 
387
386
  console.log('\n=== prompt: replan-slice contains preserve-completed-tasks instruction ===');
@@ -398,10 +397,10 @@ console.log('\n=== prompt: replan-slice contains preserve-completed-tasks instru
398
397
  inlinedContext: '',
399
398
  });
400
399
 
401
- assert.ok(prompt.includes('Do NOT renumber or remove completed tasks'), 'prompt contains preserve-completed-tasks instruction');
402
- assert.ok(prompt.includes('[x]'), 'prompt mentions [x] checkmarks');
403
- assert.ok(prompt.includes('REPLAN'), 'prompt references replan output path');
404
- assert.ok(prompt.includes('blocker_discovered'), 'prompt mentions blocker_discovered');
400
+ assertTrue(prompt.includes('Do NOT renumber or remove completed tasks'), 'prompt contains preserve-completed-tasks instruction');
401
+ assertTrue(prompt.includes('[x]'), 'prompt mentions [x] checkmarks');
402
+ assertTrue(prompt.includes('REPLAN'), 'prompt references replan output path');
403
+ assertTrue(prompt.includes('blocker_discovered'), 'prompt mentions blocker_discovered');
405
404
  }
406
405
 
407
406
  // ═══════════════════════════════════════════════════════════════════════════
@@ -422,8 +421,8 @@ console.log('\n=== dispatch: diagnoseExpectedArtifact returns REPLAN.md path ===
422
421
  writeTaskSummary(base, 'M001', 'S01', 'T01', makeTaskSummary('T01', true));
423
422
 
424
423
  const state = await deriveState(base);
425
- assert.deepStrictEqual(state.phase, 'replanning-slice', 'dispatch: state routes to replanning-slice when blocker found');
426
- assert.ok(state.activeSlice?.id === 'S01', 'dispatch: activeSlice is S01');
424
+ assertEq(state.phase, 'replanning-slice', 'dispatch: state routes to replanning-slice when blocker found');
425
+ assertTrue(state.activeSlice?.id === 'S01', 'dispatch: activeSlice is S01');
427
426
  rmSync(base, { recursive: true, force: true });
428
427
  }
429
428
 
@@ -444,8 +443,8 @@ console.log('\n=== display: replan-slice prompt template has correct unit header
444
443
  inlinedContext: '',
445
444
  });
446
445
 
447
- assert.ok(prompt.includes('UNIT: Replan Slice'), 'prompt has Replan Slice unit header');
448
- assert.ok(prompt.includes('Slice S01 replanned'), 'prompt has completion message');
446
+ assertTrue(prompt.includes('UNIT: Replan Slice'), 'prompt has Replan Slice unit header');
447
+ assertTrue(prompt.includes('Slice S01 replanned'), 'prompt has completion message');
449
448
  }
450
449
 
451
450
  // ═══════════════════════════════════════════════════════════════════════════
@@ -453,6 +452,8 @@ console.log('\n=== display: replan-slice prompt template has correct unit header
453
452
  // ═══════════════════════════════════════════════════════════════════════════
454
453
 
455
454
  import { runGSDDoctor } from '../doctor.ts';
455
+ import { createTestContext } from './test-helpers.ts';
456
+
456
457
  // (a) blocker + no REPLAN.md → issue emitted
457
458
  console.log('\n=== doctor: blocker + no REPLAN.md → blocker_discovered_no_replan issue ===');
458
459
  {
@@ -463,10 +464,10 @@ console.log('\n=== doctor: blocker + no REPLAN.md → blocker_discovered_no_repl
463
464
 
464
465
  const report = await runGSDDoctor(base, { fix: false, scope: 'M001/S01' });
465
466
  const blockerIssues = report.issues.filter(i => i.code === 'blocker_discovered_no_replan');
466
- assert.ok(blockerIssues.length > 0, 'doctor emits blocker_discovered_no_replan when blocker + no REPLAN');
467
- assert.ok(blockerIssues[0]?.message.includes('T01'), 'issue message mentions the blocker task T01');
468
- assert.deepStrictEqual(blockerIssues[0]?.severity, 'warning', 'blocker_discovered_no_replan is warning severity');
469
- assert.deepStrictEqual(blockerIssues[0]?.scope, 'slice', 'blocker_discovered_no_replan has slice scope');
467
+ assertTrue(blockerIssues.length > 0, 'doctor emits blocker_discovered_no_replan when blocker + no REPLAN');
468
+ assertTrue(blockerIssues[0]?.message.includes('T01'), 'issue message mentions the blocker task T01');
469
+ assertEq(blockerIssues[0]?.severity, 'warning', 'blocker_discovered_no_replan is warning severity');
470
+ assertEq(blockerIssues[0]?.scope, 'slice', 'blocker_discovered_no_replan has slice scope');
470
471
  rmSync(base, { recursive: true, force: true });
471
472
  }
472
473
 
@@ -481,7 +482,7 @@ console.log('\n=== doctor: blocker + REPLAN.md exists → no blocker_discovered_
481
482
 
482
483
  const report = await runGSDDoctor(base, { fix: false, scope: 'M001/S01' });
483
484
  const blockerIssues = report.issues.filter(i => i.code === 'blocker_discovered_no_replan');
484
- assert.deepStrictEqual(blockerIssues.length, 0, 'no blocker_discovered_no_replan when REPLAN.md exists');
485
+ assertEq(blockerIssues.length, 0, 'no blocker_discovered_no_replan when REPLAN.md exists');
485
486
  rmSync(base, { recursive: true, force: true });
486
487
  }
487
488
 
@@ -495,7 +496,7 @@ console.log('\n=== doctor: no blocker → no blocker_discovered_no_replan issue
495
496
 
496
497
  const report = await runGSDDoctor(base, { fix: false, scope: 'M001/S01' });
497
498
  const blockerIssues = report.issues.filter(i => i.code === 'blocker_discovered_no_replan');
498
- assert.deepStrictEqual(blockerIssues.length, 0, 'no blocker_discovered_no_replan when no blocker');
499
+ assertEq(blockerIssues.length, 0, 'no blocker_discovered_no_replan when no blocker');
499
500
  rmSync(base, { recursive: true, force: true });
500
501
  }
501
502
 
@@ -505,45 +506,48 @@ console.log('\n=== doctor: no blocker → no blocker_discovered_no_replan issue
505
506
 
506
507
  import { resolveExpectedArtifactPath, verifyExpectedArtifact } from '../auto-recovery.ts';
507
508
 
508
-
509
- describe('replan-slice', () => {
510
- test('artifact: resolveExpectedArtifactPath returns REPLAN.md path for replan-slice', () => {
509
+ console.log('\n=== artifact: resolveExpectedArtifactPath returns REPLAN.md path for replan-slice ===');
510
+ {
511
511
  const base = createFixtureBase();
512
512
  writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
513
513
  writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
514
514
 
515
515
  const path = resolveExpectedArtifactPath('replan-slice', 'M001/S01', base);
516
- assert.ok(path !== null, 'resolveExpectedArtifactPath returns non-null for replan-slice');
517
- assert.ok(path!.endsWith('S01-REPLAN.md'), 'path ends with S01-REPLAN.md');
516
+ assertTrue(path !== null, 'resolveExpectedArtifactPath returns non-null for replan-slice');
517
+ assertTrue(path!.endsWith('S01-REPLAN.md'), 'path ends with S01-REPLAN.md');
518
518
  rmSync(base, { recursive: true, force: true });
519
- });
519
+ }
520
520
 
521
- test('artifact: verifyExpectedArtifact fails when REPLAN.md missing (#858)', () => {
521
+ console.log('\n=== artifact: verifyExpectedArtifact fails when REPLAN.md missing (#858) ===');
522
+ {
522
523
  const base = createFixtureBase();
523
524
  writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
524
525
  writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
525
526
 
526
527
  const result = verifyExpectedArtifact('replan-slice', 'M001/S01', base);
527
- assert.deepStrictEqual(result, false, 'verifyExpectedArtifact returns false when REPLAN.md is missing');
528
+ assertEq(result, false, 'verifyExpectedArtifact returns false when REPLAN.md is missing');
528
529
  rmSync(base, { recursive: true, force: true });
529
- });
530
+ }
530
531
 
531
- test('artifact: verifyExpectedArtifact passes when REPLAN.md exists (#858)', () => {
532
+ console.log('\n=== artifact: verifyExpectedArtifact passes when REPLAN.md exists (#858) ===');
533
+ {
532
534
  const base = createFixtureBase();
533
535
  writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
534
536
  writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
535
537
  writeReplanFile(base, 'M001', 'S01', '# Replan\n\nBlocker addressed.');
536
538
 
537
539
  const result = verifyExpectedArtifact('replan-slice', 'M001/S01', base);
538
- assert.deepStrictEqual(result, true, 'verifyExpectedArtifact returns true when REPLAN.md exists');
540
+ assertEq(result, true, 'verifyExpectedArtifact returns true when REPLAN.md exists');
539
541
  rmSync(base, { recursive: true, force: true });
540
- });
542
+ }
541
543
 
542
544
  // ═══════════════════════════════════════════════════════════════════════════
543
545
  // REPLAN-TRIGGER.md detection (triage-initiated replan, #1701)
544
546
  // ═══════════════════════════════════════════════════════════════════════════
547
+
545
548
  // (a) REPLAN-TRIGGER.md exists + no REPLAN.md → replanning-slice
546
- test('deriveState: REPLAN-TRIGGER.md exists, no REPLAN → replanning-slice (#1701)', async () => {
549
+ console.log('\n=== deriveState: REPLAN-TRIGGER.md exists, no REPLAN → replanning-slice (#1701) ===');
550
+ {
547
551
  const base = createFixtureBase();
548
552
  writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
549
553
  writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
@@ -552,16 +556,17 @@ test('deriveState: REPLAN-TRIGGER.md exists, no REPLAN → replanning-slice (#17
552
556
  writeReplanTrigger(base, 'M001', 'S01', '# Replan Trigger\n\n**Source:** Capture C001\n');
553
557
 
554
558
  const state = await deriveState(base);
555
- assert.deepStrictEqual(state.phase, 'replanning-slice', 'phase is replanning-slice when REPLAN-TRIGGER.md exists');
556
- assert.ok(state.blockers.length > 0, 'blockers array is non-empty for triage replan trigger');
557
- assert.ok(state.nextAction.includes('Triage replan'), 'nextAction mentions triage replan');
558
- assert.deepStrictEqual(state.activeSlice?.id, 'S01', 'activeSlice is S01');
559
- assert.deepStrictEqual(state.activeTask?.id, 'T02', 'activeTask is T02 (next incomplete task)');
559
+ assertEq(state.phase, 'replanning-slice', 'phase is replanning-slice when REPLAN-TRIGGER.md exists');
560
+ assertTrue(state.blockers.length > 0, 'blockers array is non-empty for triage replan trigger');
561
+ assertTrue(state.nextAction.includes('Triage replan'), 'nextAction mentions triage replan');
562
+ assertEq(state.activeSlice?.id, 'S01', 'activeSlice is S01');
563
+ assertEq(state.activeTask?.id, 'T02', 'activeTask is T02 (next incomplete task)');
560
564
  rmSync(base, { recursive: true, force: true });
561
- });
565
+ }
562
566
 
563
567
  // (b) REPLAN-TRIGGER.md + REPLAN.md both exist → executing (loop protection)
564
- test('deriveState: REPLAN-TRIGGER.md + REPLAN.md → executing (loop protection, #1701)', async () => {
568
+ console.log('\n=== deriveState: REPLAN-TRIGGER.md + REPLAN.md → executing (loop protection, #1701) ===');
569
+ {
565
570
  const base = createFixtureBase();
566
571
  writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
567
572
  writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
@@ -570,25 +575,27 @@ test('deriveState: REPLAN-TRIGGER.md + REPLAN.md → executing (loop protection,
570
575
  writeReplanFile(base, 'M001', 'S01', '# Replan\n\nAlready replanned.');
571
576
 
572
577
  const state = await deriveState(base);
573
- assert.deepStrictEqual(state.phase, 'executing', 'phase is executing when REPLAN.md exists (loop protection)');
574
- assert.deepStrictEqual(state.activeTask?.id, 'T02', 'activeTask is T02');
578
+ assertEq(state.phase, 'executing', 'phase is executing when REPLAN.md exists (loop protection)');
579
+ assertEq(state.activeTask?.id, 'T02', 'activeTask is T02');
575
580
  rmSync(base, { recursive: true, force: true });
576
- });
581
+ }
577
582
 
578
583
  // (c) No REPLAN-TRIGGER.md, no blocker → executing (no false positive)
579
- test('deriveState: no REPLAN-TRIGGER.md, no blocker → executing (#1701)', async () => {
584
+ console.log('\n=== deriveState: no REPLAN-TRIGGER.md, no blocker → executing (#1701) ===');
585
+ {
580
586
  const base = createFixtureBase();
581
587
  writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
582
588
  writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
583
589
  writeTaskSummary(base, 'M001', 'S01', 'T01', makeTaskSummary('T01', false));
584
590
 
585
591
  const state = await deriveState(base);
586
- assert.deepStrictEqual(state.phase, 'executing', 'phase is executing when no trigger and no blocker');
592
+ assertEq(state.phase, 'executing', 'phase is executing when no trigger and no blocker');
587
593
  rmSync(base, { recursive: true, force: true });
588
- });
594
+ }
589
595
 
590
596
  // (d) blocker_discovered takes priority over REPLAN-TRIGGER.md
591
- test('deriveState: blocker_discovered takes priority over REPLAN-TRIGGER.md (#1701)', async () => {
597
+ console.log('\n=== deriveState: blocker_discovered takes priority over REPLAN-TRIGGER.md (#1701) ===');
598
+ {
592
599
  const base = createFixtureBase();
593
600
  writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
594
601
  writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
@@ -596,10 +603,10 @@ test('deriveState: blocker_discovered takes priority over REPLAN-TRIGGER.md (#17
596
603
  writeReplanTrigger(base, 'M001', 'S01', '# Replan Trigger\n\n**Source:** Capture C001\n');
597
604
 
598
605
  const state = await deriveState(base);
599
- assert.deepStrictEqual(state.phase, 'replanning-slice', 'phase is replanning-slice');
606
+ assertEq(state.phase, 'replanning-slice', 'phase is replanning-slice');
600
607
  // blocker_discovered path should fire first (blockerTaskId is set, so REPLAN-TRIGGER check is skipped)
601
- assert.ok(state.nextAction.includes('T01'), 'nextAction mentions blocker task T01 (blocker path, not trigger path)');
608
+ assertTrue(state.nextAction.includes('T01'), 'nextAction mentions blocker task T01 (blocker path, not trigger path)');
602
609
  rmSync(base, { recursive: true, force: true });
603
- });
610
+ }
604
611
 
605
- });
612
+ report();
@@ -1,11 +1,13 @@
1
- import { describe, test, before, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
1
  import { mkdtempSync, rmSync, writeFileSync, existsSync, lstatSync, realpathSync, mkdirSync, symlinkSync, renameSync } from "node:fs";
4
2
  import { join } from "node:path";
5
3
  import { tmpdir } from "node:os";
6
4
  import { execSync } from "node:child_process";
7
5
 
8
6
  import { repoIdentity, externalGsdRoot, ensureGsdSymlink, validateProjectId, readRepoMeta, isInheritedRepo } from "../repo-identity.ts";
7
+ import { createTestContext } from "./test-helpers.ts";
8
+
9
+ const { assertEq, assertTrue, report } = createTestContext();
10
+
9
11
  /**
10
12
  * Normalize a path for reliable comparison on Windows CI runners.
11
13
  * `os.tmpdir()` may return the 8.3 short-path form (e.g. `C:\Users\RUNNER~1`)
@@ -21,15 +23,11 @@ function run(command: string, cwd: string): string {
21
23
  return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
22
24
  }
23
25
 
24
- describe('repo-identity-worktree', () => {
25
- let base: string;
26
- let stateDir: string;
27
- let worktreePath: string;
28
- let expectedExternalState: string;
26
+ async function main(): Promise<void> {
27
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-repo-identity-")));
28
+ const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-")));
29
29
 
30
- before(() => {
31
- base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-repo-identity-")));
32
- stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-")));
30
+ try {
33
31
  process.env.GSD_STATE_DIR = stateDir;
34
32
 
35
33
  run("git init -b main", base);
@@ -40,69 +38,57 @@ describe('repo-identity-worktree', () => {
40
38
  run("git add README.md", base);
41
39
  run('git commit -m "chore: init"', base);
42
40
 
43
- worktreePath = join(base, ".gsd", "worktrees", "M001");
41
+ const worktreePath = join(base, ".gsd", "worktrees", "M001");
44
42
  run(`git worktree add -b milestone/M001 ${worktreePath}`, base);
45
43
 
46
- expectedExternalState = externalGsdRoot(base);
47
- });
48
-
49
- after(() => {
50
- delete process.env.GSD_PROJECT_ID;
51
- delete process.env.GSD_STATE_DIR;
52
- rmSync(base, { recursive: true, force: true });
53
- rmSync(stateDir, { recursive: true, force: true });
54
- });
55
-
56
- test('ensureGsdSymlink points worktree at main repo external state dir', () => {
44
+ console.log("\n=== ensureGsdSymlink points worktree at main repo external state dir ===");
45
+ const expectedExternalState = externalGsdRoot(base);
57
46
  const mainState = ensureGsdSymlink(base);
58
- assert.deepStrictEqual(mainState, realpathSync(join(base, ".gsd")), "ensureGsdSymlink(base) returns the current main repo .gsd target");
47
+ assertEq(mainState, realpathSync(join(base, ".gsd")), "ensureGsdSymlink(base) returns the current main repo .gsd target");
59
48
  const worktreeState = ensureGsdSymlink(worktreePath);
60
- assert.deepStrictEqual(worktreeState, expectedExternalState, "worktree symlink target matches main repo external state dir");
61
- assert.ok(existsSync(join(worktreePath, ".gsd")), "worktree .gsd exists");
62
- assert.ok(lstatSync(join(worktreePath, ".gsd")).isSymbolicLink(), "worktree .gsd is a symlink");
63
- assert.deepStrictEqual(realpathSync(join(worktreePath, ".gsd")), realpathSync(expectedExternalState), "worktree .gsd symlink resolves to main repo external state dir");
64
- });
49
+ assertEq(worktreeState, expectedExternalState, "worktree symlink target matches main repo external state dir");
50
+ assertTrue(existsSync(join(worktreePath, ".gsd")), "worktree .gsd exists");
51
+ assertTrue(lstatSync(join(worktreePath, ".gsd")).isSymbolicLink(), "worktree .gsd is a symlink");
52
+ assertEq(realpathSync(join(worktreePath, ".gsd")), realpathSync(expectedExternalState), "worktree .gsd symlink resolves to main repo external state dir");
65
53
 
66
- test('ensureGsdSymlink heals stale worktree symlinks', () => {
54
+ console.log("\n=== ensureGsdSymlink heals stale worktree symlinks ===");
67
55
  const staleState = join(stateDir, "projects", "stale-worktree-state");
68
56
  mkdirSync(staleState, { recursive: true });
69
57
  rmSync(join(worktreePath, ".gsd"), { recursive: true, force: true });
70
58
  symlinkSync(staleState, join(worktreePath, ".gsd"), "junction");
71
59
  const healedState = ensureGsdSymlink(worktreePath);
72
- assert.deepStrictEqual(healedState, expectedExternalState, "stale worktree symlink is repaired to canonical external state dir");
73
- assert.deepStrictEqual(realpathSync(join(worktreePath, ".gsd")), realpathSync(expectedExternalState), "healed worktree symlink resolves to canonical external state dir");
74
- });
60
+ assertEq(healedState, expectedExternalState, "stale worktree symlink is repaired to canonical external state dir");
61
+ assertEq(realpathSync(join(worktreePath, ".gsd")), realpathSync(expectedExternalState), "healed worktree symlink resolves to canonical external state dir");
75
62
 
76
- test('ensureGsdSymlink preserves worktree .gsd directories', () => {
63
+ console.log("\n=== ensureGsdSymlink preserves worktree .gsd directories ===");
77
64
  rmSync(join(worktreePath, ".gsd"), { recursive: true, force: true });
78
65
  mkdirSync(join(worktreePath, ".gsd", "milestones"), { recursive: true });
79
66
  writeFileSync(join(worktreePath, ".gsd", "milestones", "stale.txt"), "stale\n", "utf-8");
80
67
  const preservedDirState = ensureGsdSymlink(worktreePath);
81
- assert.deepStrictEqual(preservedDirState, join(worktreePath, ".gsd"), "worktree .gsd directory is left in place for sync-based refresh");
82
- assert.ok(lstatSync(join(worktreePath, ".gsd")).isDirectory(), "worktree .gsd directory remains a directory");
83
- assert.ok(existsSync(join(worktreePath, ".gsd", "milestones", "stale.txt")), "existing worktree .gsd directory contents remain available for sync logic");
84
- });
68
+ assertEq(preservedDirState, join(worktreePath, ".gsd"), "worktree .gsd directory is left in place for sync-based refresh");
69
+ assertTrue(lstatSync(join(worktreePath, ".gsd")).isDirectory(), "worktree .gsd directory remains a directory");
70
+ assertTrue(existsSync(join(worktreePath, ".gsd", "milestones", "stale.txt")), "existing worktree .gsd directory contents remain available for sync logic");
85
71
 
86
- test('GSD_PROJECT_ID overrides computed repo hash', () => {
72
+ console.log("\n=== GSD_PROJECT_ID overrides computed repo hash ===");
87
73
  process.env.GSD_PROJECT_ID = "my-project";
88
- assert.deepStrictEqual(repoIdentity(base), "my-project", "repoIdentity returns GSD_PROJECT_ID when set");
89
- assert.deepStrictEqual(externalGsdRoot(base), join(stateDir, "projects", "my-project"), "externalGsdRoot uses GSD_PROJECT_ID");
74
+ assertEq(repoIdentity(base), "my-project", "repoIdentity returns GSD_PROJECT_ID when set");
75
+ assertEq(externalGsdRoot(base), join(stateDir, "projects", "my-project"), "externalGsdRoot uses GSD_PROJECT_ID");
90
76
  delete process.env.GSD_PROJECT_ID;
91
- });
92
77
 
93
- test('GSD_PROJECT_ID falls back to hash when unset', () => {
78
+ console.log("\n=== GSD_PROJECT_ID falls back to hash when unset ===");
94
79
  const hashIdentity = repoIdentity(base);
95
- assert.ok(/^[0-9a-f]{12}$/.test(hashIdentity), "repoIdentity returns 12-char hex hash when GSD_PROJECT_ID is unset");
96
- });
80
+ assertTrue(/^[0-9a-f]{12}$/.test(hashIdentity), "repoIdentity returns 12-char hex hash when GSD_PROJECT_ID is unset");
97
81
 
98
- test('readRepoMeta returns null for malformed metadata', () => {
82
+ console.log("\n=== readRepoMeta returns null for malformed metadata ===");
83
+ {
99
84
  const malformedPath = join(stateDir, "projects", "malformed");
100
85
  mkdirSync(malformedPath, { recursive: true });
101
86
  writeFileSync(join(malformedPath, "repo-meta.json"), JSON.stringify({ version: 1 }) + "\n", "utf-8");
102
- assert.deepStrictEqual(readRepoMeta(malformedPath), null, "malformed repo-meta.json is treated as unknown metadata");
103
- });
87
+ assertEq(readRepoMeta(malformedPath), null, "malformed repo-meta.json is treated as unknown metadata");
88
+ }
104
89
 
105
- test('ensureGsdSymlink refreshes repo-meta gitRoot after repo move with fixed project id', () => {
90
+ console.log("\n=== ensureGsdSymlink refreshes repo-meta gitRoot after repo move with fixed project id ===");
91
+ {
106
92
  const moveRepo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-repo-identity-move-")));
107
93
  run("git init -b main", moveRepo);
108
94
  run('git config user.name "Pi Test"', moveRepo);
@@ -114,25 +100,26 @@ test('ensureGsdSymlink refreshes repo-meta gitRoot after repo move with fixed pr
114
100
  process.env.GSD_PROJECT_ID = "fixed-project";
115
101
  const fixedExternal = ensureGsdSymlink(moveRepo);
116
102
  const before = readRepoMeta(fixedExternal);
117
- assert.ok(before !== null, "repo metadata exists before repo move");
118
- assert.deepStrictEqual(normalizePath(before!.gitRoot), normalizePath(moveRepo), "repo metadata tracks current git root before move");
103
+ assertTrue(before !== null, "repo metadata exists before repo move");
104
+ assertEq(normalizePath(before!.gitRoot), normalizePath(moveRepo), "repo metadata tracks current git root before move");
119
105
 
120
106
  const movedBaseRaw = join(tmpdir(), `gsd-repo-identity-moved-${Date.now()}-${Math.random().toString(36).slice(2)}`);
121
107
  renameSync(moveRepo, movedBaseRaw);
122
108
  const movedBase = realpathSync(movedBaseRaw);
123
109
  const movedExternal = ensureGsdSymlink(movedBase);
124
- assert.deepStrictEqual(realpathSync(movedExternal), realpathSync(fixedExternal), "fixed project id keeps the same external state dir");
110
+ assertEq(realpathSync(movedExternal), realpathSync(fixedExternal), "fixed project id keeps the same external state dir");
125
111
 
126
112
  const after = readRepoMeta(movedExternal);
127
- assert.ok(after !== null, "repo metadata exists after repo move");
128
- assert.deepStrictEqual(normalizePath(after!.gitRoot), normalizePath(movedBase), "repo metadata gitRoot is refreshed to moved repo path");
129
- assert.deepStrictEqual(after!.createdAt, before!.createdAt, "repo metadata preserves createdAt on refresh");
113
+ assertTrue(after !== null, "repo metadata exists after repo move");
114
+ assertEq(normalizePath(after!.gitRoot), normalizePath(movedBase), "repo metadata gitRoot is refreshed to moved repo path");
115
+ assertEq(after!.createdAt, before!.createdAt, "repo metadata preserves createdAt on refresh");
130
116
 
131
117
  rmSync(movedBase, { recursive: true, force: true });
132
118
  delete process.env.GSD_PROJECT_ID;
133
- });
119
+ }
134
120
 
135
- test('isInheritedRepo detects subdirectory of parent repo without .gsd (#1639)', () => {
121
+ console.log("\n=== isInheritedRepo detects subdirectory of parent repo without .gsd (#1639) ===");
122
+ {
136
123
  const parentRepo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-inherited-parent-")));
137
124
  run("git init -b main", parentRepo);
138
125
  run('git config user.name "Pi Test"', parentRepo);
@@ -141,26 +128,31 @@ test('isInheritedRepo detects subdirectory of parent repo without .gsd (#1639)',
141
128
  run("git add README.md", parentRepo);
142
129
  run('git commit -m "init"', parentRepo);
143
130
 
131
+ // Create a subdirectory — no .gsd at parent
144
132
  const subdir = join(parentRepo, "newproject");
145
133
  mkdirSync(subdir, { recursive: true });
146
- assert.ok(isInheritedRepo(subdir), "subdirectory of parent repo without .gsd is inherited");
134
+ assertTrue(isInheritedRepo(subdir), "subdirectory of parent repo without .gsd is inherited");
147
135
 
136
+ // After adding .gsd at parent, subdirectory is a legitimate child
148
137
  mkdirSync(join(parentRepo, ".gsd"), { recursive: true });
149
- assert.ok(!isInheritedRepo(subdir), "subdirectory of parent repo WITH .gsd is NOT inherited");
138
+ assertTrue(!isInheritedRepo(subdir), "subdirectory of parent repo WITH .gsd is NOT inherited");
150
139
 
151
- assert.ok(!isInheritedRepo(parentRepo), "git root is not inherited");
140
+ // The git root itself is never inherited
141
+ assertTrue(!isInheritedRepo(parentRepo), "git root is not inherited");
152
142
 
143
+ // A standalone repo (not a subdir) is not inherited
153
144
  const standaloneRepo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-inherited-standalone-")));
154
145
  run("git init -b main", standaloneRepo);
155
146
  run('git config user.name "Pi Test"', standaloneRepo);
156
147
  run('git config user.email "pi@example.com"', standaloneRepo);
157
- assert.ok(!isInheritedRepo(standaloneRepo), "standalone repo is not inherited");
148
+ assertTrue(!isInheritedRepo(standaloneRepo), "standalone repo is not inherited");
158
149
 
159
150
  rmSync(parentRepo, { recursive: true, force: true });
160
151
  rmSync(standaloneRepo, { recursive: true, force: true });
161
- });
152
+ }
162
153
 
163
- test('subdirectory of parent repo gets unique identity after git init (#1639)', () => {
154
+ console.log("\n=== subdirectory of parent repo gets unique identity after git init (#1639) ===");
155
+ {
164
156
  const parentRepo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-identity-parent-")));
165
157
  run("git init -b main", parentRepo);
166
158
  run('git config user.name "Pi Test"', parentRepo);
@@ -173,27 +165,38 @@ test('subdirectory of parent repo gets unique identity after git init (#1639)',
173
165
  const subdir = join(parentRepo, "childproject");
174
166
  mkdirSync(subdir, { recursive: true });
175
167
 
168
+ // Before git init, subdirectory shares parent's identity
176
169
  const parentIdentity = repoIdentity(parentRepo);
177
170
  const subdirIdentityBefore = repoIdentity(subdir);
178
- assert.deepStrictEqual(subdirIdentityBefore, parentIdentity, "subdirectory shares parent identity before its own git init");
171
+ assertEq(subdirIdentityBefore, parentIdentity, "subdirectory shares parent identity before its own git init");
179
172
 
173
+ // After git init, subdirectory gets its own identity
180
174
  run("git init -b main", subdir);
181
175
  const subdirIdentityAfter = repoIdentity(subdir);
182
- assert.ok(subdirIdentityAfter !== parentIdentity, "subdirectory gets unique identity after git init");
176
+ assertTrue(subdirIdentityAfter !== parentIdentity, "subdirectory gets unique identity after git init");
183
177
 
184
178
  rmSync(parentRepo, { recursive: true, force: true });
185
- });
179
+ }
186
180
 
187
- test('validateProjectId rejects invalid values', () => {
181
+ console.log("\n=== validateProjectId rejects invalid values ===");
188
182
  for (const invalid of ["has spaces", "path/traversal", "dot..dot", "back\\slash"]) {
189
- assert.ok(!validateProjectId(invalid), `validateProjectId rejects invalid value: "${invalid}"`);
183
+ assertTrue(!validateProjectId(invalid), `validateProjectId rejects invalid value: "${invalid}"`);
190
184
  }
191
- });
192
185
 
193
- test('validateProjectId accepts valid values', () => {
186
+ console.log("\n=== validateProjectId accepts valid values ===");
194
187
  for (const valid of ["my-project", "foo_bar", "abc123", "A-Z_0-9"]) {
195
- assert.ok(validateProjectId(valid), `validateProjectId accepts valid value: "${valid}"`);
188
+ assertTrue(validateProjectId(valid), `validateProjectId accepts valid value: "${valid}"`);
196
189
  }
197
- });
190
+ } finally {
191
+ delete process.env.GSD_PROJECT_ID;
192
+ delete process.env.GSD_STATE_DIR;
193
+ rmSync(base, { recursive: true, force: true });
194
+ rmSync(stateDir, { recursive: true, force: true });
195
+ report();
196
+ }
197
+ }
198
198
 
199
+ main().catch((error) => {
200
+ console.error(error);
201
+ process.exit(1);
199
202
  });