gsd-pi 2.43.0-next.8 → 2.44.0-dev.0b97ffd

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 (399) hide show
  1. package/README.md +30 -12
  2. package/dist/cli.js +13 -1
  3. package/dist/help-text.js +24 -0
  4. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +21 -8
  5. package/dist/resources/extensions/gsd/auto-prompts.js +130 -51
  6. package/dist/resources/extensions/gsd/auto-start.js +10 -0
  7. package/dist/resources/extensions/gsd/auto-worktree.js +16 -2
  8. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
  9. package/dist/resources/extensions/gsd/dispatch-guard.js +34 -10
  10. package/dist/resources/extensions/gsd/markdown-renderer.js +7 -5
  11. package/dist/resources/extensions/gsd/reactive-graph.js +13 -2
  12. package/dist/resources/extensions/gsd/skill-health.js +3 -1
  13. package/dist/resources/extensions/gsd/tools/plan-milestone.js +2 -11
  14. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -10
  15. package/dist/resources/extensions/gsd/visualizer-data.js +45 -13
  16. package/dist/resources/extensions/gsd/workspace-index.js +46 -15
  17. package/dist/web/standalone/.next/BUILD_ID +1 -1
  18. package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
  19. package/dist/web/standalone/.next/build-manifest.json +3 -3
  20. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  21. package/dist/web/standalone/.next/required-server-files.json +4 -4
  22. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  23. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  25. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  33. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  49. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  87. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  93. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  109. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  111. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  113. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/index.html +1 -1
  123. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  124. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  125. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  126. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  128. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/page.js +2 -2
  130. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
  132. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  133. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  134. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/middleware.js +2 -2
  136. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  138. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  139. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  140. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  141. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-f2a7482d42a5614b.js +1 -0
  142. package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
  143. package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
  144. package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
  145. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  146. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  147. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  148. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  149. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  150. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  151. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  152. package/dist/web/standalone/server.js +1 -1
  153. package/package.json +4 -4
  154. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -3
  155. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/agent-session.js +11 -34
  157. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
  159. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts +2 -2
  161. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  163. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +2 -2
  164. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +4 -4
  167. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  168. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
  169. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  170. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  171. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
  173. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
  175. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +37 -0
  177. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts.map +1 -1
  180. package/packages/pi-coding-agent/dist/core/fallback-resolver.js +2 -3
  181. package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +12 -2
  183. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
  185. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +38 -0
  187. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -0
  188. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +192 -0
  189. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -0
  190. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts +2 -0
  191. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts.map +1 -0
  192. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +255 -0
  193. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -0
  194. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +15 -0
  195. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/model-registry.js +40 -3
  197. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/package-commands.d.ts +25 -0
  199. package/packages/pi-coding-agent/dist/core/package-commands.d.ts.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/package-commands.js +253 -0
  201. package/packages/pi-coding-agent/dist/core/package-commands.js.map +1 -0
  202. package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts +2 -0
  203. package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts.map +1 -0
  204. package/packages/pi-coding-agent/dist/core/package-commands.test.js +225 -0
  205. package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -0
  206. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
  207. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/sdk.js +4 -0
  210. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  211. package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
  212. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
  214. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/index.d.ts +3 -1
  216. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  217. package/packages/pi-coding-agent/dist/index.js +1 -0
  218. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  219. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  220. package/packages/pi-coding-agent/dist/main.js +11 -199
  221. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  222. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
  223. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
  224. package/packages/pi-coding-agent/package.json +1 -1
  225. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -37
  226. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
  227. package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +2 -2
  228. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +3 -3
  229. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +4 -4
  230. package/packages/pi-coding-agent/src/core/extensions/index.ts +5 -0
  231. package/packages/pi-coding-agent/src/core/extensions/loader.ts +23 -0
  232. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
  233. package/packages/pi-coding-agent/src/core/extensions/types.ts +44 -0
  234. package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +15 -2
  235. package/packages/pi-coding-agent/src/core/fallback-resolver.ts +2 -3
  236. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
  237. package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +274 -0
  238. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +288 -0
  239. package/packages/pi-coding-agent/src/core/model-registry.ts +39 -3
  240. package/packages/pi-coding-agent/src/core/package-commands.test.ts +240 -0
  241. package/packages/pi-coding-agent/src/core/package-commands.ts +310 -0
  242. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
  243. package/packages/pi-coding-agent/src/core/sdk.ts +4 -0
  244. package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
  245. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
  246. package/packages/pi-coding-agent/src/index.ts +7 -0
  247. package/packages/pi-coding-agent/src/main.ts +11 -232
  248. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
  249. package/pkg/package.json +1 -1
  250. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +22 -7
  251. package/src/resources/extensions/gsd/auto-prompts.ts +109 -42
  252. package/src/resources/extensions/gsd/auto-start.ts +14 -0
  253. package/src/resources/extensions/gsd/auto-worktree.ts +16 -3
  254. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
  255. package/src/resources/extensions/gsd/dispatch-guard.ts +28 -10
  256. package/src/resources/extensions/gsd/markdown-renderer.ts +7 -5
  257. package/src/resources/extensions/gsd/reactive-graph.ts +12 -2
  258. package/src/resources/extensions/gsd/skill-health.ts +2 -1
  259. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
  260. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
  261. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
  262. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
  263. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
  264. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
  265. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
  266. package/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +3 -3
  267. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
  268. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
  269. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
  270. package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
  271. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
  272. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
  273. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
  274. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
  275. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
  276. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
  277. package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
  278. package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
  279. package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
  280. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
  281. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
  282. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
  283. package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
  284. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
  285. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
  286. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +152 -183
  287. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
  288. package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
  289. package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
  290. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
  291. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
  292. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
  293. package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
  294. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
  295. package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
  296. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
  297. package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
  298. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
  299. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
  300. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
  301. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
  302. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
  303. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
  304. package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
  305. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
  306. package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
  307. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
  308. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
  309. package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
  310. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
  311. package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
  312. package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
  313. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
  314. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
  315. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
  316. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
  317. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
  318. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
  319. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
  320. package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
  321. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
  322. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
  323. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
  324. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
  325. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
  326. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
  327. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
  328. package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
  329. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
  330. package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
  331. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
  332. package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
  333. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
  334. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
  335. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
  336. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
  337. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
  338. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
  339. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
  340. package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
  341. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
  342. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
  343. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
  344. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
  345. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
  346. package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
  347. package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
  348. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
  349. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
  350. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
  351. package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
  352. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
  353. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
  354. package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
  355. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
  356. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
  357. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
  358. package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
  359. package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
  360. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
  361. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
  362. package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
  363. package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
  364. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
  365. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
  366. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
  367. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
  368. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
  369. package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
  370. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
  371. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
  372. package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
  373. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
  374. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
  375. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
  376. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
  377. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
  378. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
  379. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
  380. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
  381. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
  382. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
  383. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
  384. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
  385. package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
  386. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
  387. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
  388. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
  389. package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
  390. package/src/resources/extensions/gsd/tools/plan-milestone.ts +1 -18
  391. package/src/resources/extensions/gsd/tools/plan-slice.ts +1 -15
  392. package/src/resources/extensions/gsd/visualizer-data.ts +46 -14
  393. package/src/resources/extensions/gsd/workspace-index.ts +49 -18
  394. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
  395. package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
  396. package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
  397. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
  398. /package/dist/web/standalone/.next/static/{drUWS0zys9uepCfCwecJv → alS4hoANx0TK4UVZY27da}/_buildManifest.js +0 -0
  399. /package/dist/web/standalone/.next/static/{drUWS0zys9uepCfCwecJv → alS4hoANx0TK4UVZY27da}/_ssgManifest.js +0 -0
@@ -1,3 +1,5 @@
1
+ import { describe, test } from 'node:test';
2
+ import assert from 'node:assert/strict';
1
3
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, symlinkSync } from "node:fs";
2
4
  import { join, dirname } from "node:path";
3
5
  import { tmpdir } from "node:os";
@@ -20,174 +22,170 @@ import {
20
22
  type TaskCommitContext,
21
23
  } from "../git-service.ts";
22
24
  import { nativeAddAllWithExclusions } from "../native-git-bridge.ts";
23
- import { createTestContext } from './test-helpers.ts';
24
-
25
- const { assertEq, assertTrue, report } = createTestContext();
26
25
  function run(command: string, cwd: string): string {
27
26
  return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
28
27
  }
29
28
 
30
- async function main(): Promise<void> {
29
+ describe('git-service', async () => {
31
30
  // ─── inferCommitType ───────────────────────────────────────────────────
32
31
 
33
- console.log("\n=== inferCommitType ===");
34
32
 
35
- assertEq(
33
+ assert.deepStrictEqual(
36
34
  inferCommitType("Implement user authentication"),
37
35
  "feat",
38
36
  "generic feature title → feat"
39
37
  );
40
38
 
41
- assertEq(
39
+ assert.deepStrictEqual(
42
40
  inferCommitType("Add dashboard page"),
43
41
  "feat",
44
42
  "add-style title → feat"
45
43
  );
46
44
 
47
- assertEq(
45
+ assert.deepStrictEqual(
48
46
  inferCommitType("Fix login redirect bug"),
49
47
  "fix",
50
48
  "title with 'fix' → fix"
51
49
  );
52
50
 
53
- assertEq(
51
+ assert.deepStrictEqual(
54
52
  inferCommitType("Bug in session handling"),
55
53
  "fix",
56
54
  "title with 'bug' → fix"
57
55
  );
58
56
 
59
- assertEq(
57
+ assert.deepStrictEqual(
60
58
  inferCommitType("Hotfix for production crash"),
61
59
  "fix",
62
60
  "title with 'hotfix' → fix"
63
61
  );
64
62
 
65
- assertEq(
63
+ assert.deepStrictEqual(
66
64
  inferCommitType("Patch memory leak"),
67
65
  "fix",
68
66
  "title with 'patch' → fix"
69
67
  );
70
68
 
71
- assertEq(
69
+ assert.deepStrictEqual(
72
70
  inferCommitType("Refactor state management"),
73
71
  "refactor",
74
72
  "title with 'refactor' → refactor"
75
73
  );
76
74
 
77
- assertEq(
75
+ assert.deepStrictEqual(
78
76
  inferCommitType("Restructure project layout"),
79
77
  "refactor",
80
78
  "title with 'restructure' → refactor"
81
79
  );
82
80
 
83
- assertEq(
81
+ assert.deepStrictEqual(
84
82
  inferCommitType("Reorganize module imports"),
85
83
  "refactor",
86
84
  "title with 'reorganize' → refactor"
87
85
  );
88
86
 
89
- assertEq(
87
+ assert.deepStrictEqual(
90
88
  inferCommitType("Update API documentation"),
91
89
  "docs",
92
90
  "title with 'documentation' → docs"
93
91
  );
94
92
 
95
- assertEq(
93
+ assert.deepStrictEqual(
96
94
  inferCommitType("Add doc for setup guide"),
97
95
  "docs",
98
96
  "title with 'doc' → docs"
99
97
  );
100
98
 
101
- assertEq(
99
+ assert.deepStrictEqual(
102
100
  inferCommitType("Add unit tests for auth"),
103
101
  "test",
104
102
  "title with 'tests' → test"
105
103
  );
106
104
 
107
- assertEq(
105
+ assert.deepStrictEqual(
108
106
  inferCommitType("Testing infrastructure setup"),
109
107
  "test",
110
108
  "title with 'testing' → test"
111
109
  );
112
110
 
113
- assertEq(
111
+ assert.deepStrictEqual(
114
112
  inferCommitType("Chore: update dependencies"),
115
113
  "chore",
116
114
  "title with 'chore' → chore"
117
115
  );
118
116
 
119
- assertEq(
117
+ assert.deepStrictEqual(
120
118
  inferCommitType("Cleanup unused imports"),
121
119
  "chore",
122
120
  "title with 'cleanup' → chore"
123
121
  );
124
122
 
125
- assertEq(
123
+ assert.deepStrictEqual(
126
124
  inferCommitType("Clean up stale branches"),
127
125
  "chore",
128
126
  "title with 'clean up' → chore"
129
127
  );
130
128
 
131
- assertEq(
129
+ assert.deepStrictEqual(
132
130
  inferCommitType("Archive old milestones"),
133
131
  "chore",
134
132
  "title with 'archive' → chore"
135
133
  );
136
134
 
137
- assertEq(
135
+ assert.deepStrictEqual(
138
136
  inferCommitType("Remove deprecated endpoints"),
139
137
  "chore",
140
138
  "title with 'remove' → chore"
141
139
  );
142
140
 
143
- assertEq(
141
+ assert.deepStrictEqual(
144
142
  inferCommitType("Delete temp files"),
145
143
  "chore",
146
144
  "title with 'delete' → chore"
147
145
  );
148
146
 
149
147
  // Mixed keywords — first match wins
150
- assertEq(
148
+ assert.deepStrictEqual(
151
149
  inferCommitType("Fix and refactor the login module"),
152
150
  "fix",
153
151
  "mixed keywords → first match wins (fix before refactor)"
154
152
  );
155
153
 
156
- assertEq(
154
+ assert.deepStrictEqual(
157
155
  inferCommitType("Refactor test utilities"),
158
156
  "refactor",
159
157
  "mixed keywords → first match wins (refactor before test)"
160
158
  );
161
159
 
162
160
  // Unknown / unrecognized title → feat
163
- assertEq(
161
+ assert.deepStrictEqual(
164
162
  inferCommitType("Build the new pipeline"),
165
163
  "feat",
166
164
  "unrecognized title → feat"
167
165
  );
168
166
 
169
- assertEq(
167
+ assert.deepStrictEqual(
170
168
  inferCommitType(""),
171
169
  "feat",
172
170
  "empty title → feat"
173
171
  );
174
172
 
175
173
  // Word boundary: "testify" should NOT match "test"
176
- assertEq(
174
+ assert.deepStrictEqual(
177
175
  inferCommitType("Testify integration"),
178
176
  "feat",
179
177
  "'testify' does not match 'test' — word boundary prevents partial match"
180
178
  );
181
179
 
182
180
  // "documentary" should NOT match "doc" (word boundary)
183
- assertEq(
181
+ assert.deepStrictEqual(
184
182
  inferCommitType("Documentary style UI"),
185
183
  "feat",
186
184
  "'documentary' does not match 'doc' — word boundary prevents partial match"
187
185
  );
188
186
 
189
187
  // "prefix" should NOT match "fix" (word boundary)
190
- assertEq(
188
+ assert.deepStrictEqual(
191
189
  inferCommitType("Add prefix to all IDs"),
192
190
  "feat",
193
191
  "'prefix' does not match 'fix' — word boundary prevents partial match"
@@ -195,15 +193,14 @@ async function main(): Promise<void> {
195
193
 
196
194
  // ─── inferCommitType with oneLiner ──────────────────────────────────────
197
195
 
198
- console.log("\n=== inferCommitType with oneLiner ===");
199
196
 
200
- assertEq(
197
+ assert.deepStrictEqual(
201
198
  inferCommitType("implement dashboard", "Fixed rendering bug in sidebar"),
202
199
  "fix",
203
200
  "one-liner with 'fixed' overrides generic title → fix"
204
201
  );
205
202
 
206
- assertEq(
203
+ assert.deepStrictEqual(
207
204
  inferCommitType("add search", "Optimized query performance with caching"),
208
205
  "perf",
209
206
  "one-liner with 'performance' and 'caching' → perf"
@@ -211,29 +208,27 @@ async function main(): Promise<void> {
211
208
 
212
209
  // ─── buildTaskCommitMessage ─────────────────────────────────────────────
213
210
 
214
- console.log("\n=== buildTaskCommitMessage ===");
215
-
216
- {
211
+ test('buildTaskCommitMessage', () => {
217
212
  const msg = buildTaskCommitMessage({
218
213
  taskId: "S01/T02",
219
214
  taskTitle: "implement user authentication",
220
215
  oneLiner: "Added JWT-based auth with refresh token rotation",
221
216
  keyFiles: ["src/auth.ts", "src/middleware/jwt.ts"],
222
217
  });
223
- assertTrue(msg.startsWith("feat(S01/T02):"), "message starts with type(scope)");
224
- assertTrue(msg.includes("JWT-based auth"), "message includes one-liner content");
225
- assertTrue(msg.includes("- src/auth.ts"), "message body includes key files");
226
- assertTrue(msg.includes("- src/middleware/jwt.ts"), "message body includes second key file");
227
- }
218
+ assert.ok(msg.startsWith("feat(S01/T02):"), "message starts with type(scope)");
219
+ assert.ok(msg.includes("JWT-based auth"), "message includes one-liner content");
220
+ assert.ok(msg.includes("- src/auth.ts"), "message body includes key files");
221
+ assert.ok(msg.includes("- src/middleware/jwt.ts"), "message body includes second key file");
222
+ });
228
223
 
229
224
  {
230
225
  const msg = buildTaskCommitMessage({
231
226
  taskId: "S02/T01",
232
227
  taskTitle: "fix login redirect bug",
233
228
  });
234
- assertTrue(msg.startsWith("fix(S02/T01):"), "infers fix type from title");
235
- assertTrue(msg.includes("fix login redirect bug"), "uses task title when no one-liner");
236
- assertTrue(!msg.includes("\n"), "no body when no key files");
229
+ assert.ok(msg.startsWith("fix(S02/T01):"), "infers fix type from title");
230
+ assert.ok(msg.includes("fix login redirect bug"), "uses task title when no one-liner");
231
+ assert.ok(!msg.includes("\n"), "no body when no key files");
237
232
  }
238
233
 
239
234
  {
@@ -242,14 +237,13 @@ async function main(): Promise<void> {
242
237
  taskTitle: "add tests",
243
238
  oneLiner: "Unit tests for auth module with coverage",
244
239
  });
245
- assertTrue(msg.startsWith("test(S01/T03):"), "infers test type");
240
+ assert.ok(msg.startsWith("test(S01/T03):"), "infers test type");
246
241
  }
247
242
 
248
243
  // ─── RUNTIME_EXCLUSION_PATHS ───────────────────────────────────────────
249
244
 
250
- console.log("\n=== RUNTIME_EXCLUSION_PATHS ===");
251
245
 
252
- assertEq(
246
+ assert.deepStrictEqual(
253
247
  RUNTIME_EXCLUSION_PATHS.length,
254
248
  13,
255
249
  "exactly 13 runtime exclusion paths"
@@ -271,24 +265,23 @@ async function main(): Promise<void> {
271
265
  ".gsd/DISCUSSION-MANIFEST.json",
272
266
  ];
273
267
 
274
- assertEq(
268
+ assert.deepStrictEqual(
275
269
  [...RUNTIME_EXCLUSION_PATHS],
276
270
  expectedPaths,
277
271
  "paths match expected set in order"
278
272
  );
279
273
 
280
- assertTrue(
274
+ assert.ok(
281
275
  RUNTIME_EXCLUSION_PATHS.includes(".gsd/activity/"),
282
276
  "includes .gsd/activity/"
283
277
  );
284
- assertTrue(
278
+ assert.ok(
285
279
  RUNTIME_EXCLUSION_PATHS.includes(".gsd/STATE.md"),
286
280
  "includes .gsd/STATE.md"
287
281
  );
288
282
 
289
283
  // ─── runGit ────────────────────────────────────────────────────────────
290
284
 
291
- console.log("\n=== runGit ===");
292
285
 
293
286
  const tempDir = mkdtempSync(join(tmpdir(), "gsd-git-service-test-"));
294
287
  run("git init -b main", tempDir);
@@ -297,11 +290,11 @@ async function main(): Promise<void> {
297
290
 
298
291
  // runGit should work on a valid repo
299
292
  const branch = runGit(tempDir, ["branch", "--show-current"]);
300
- assertEq(branch, "main", "runGit returns current branch");
293
+ assert.deepStrictEqual(branch, "main", "runGit returns current branch");
301
294
 
302
295
  // runGit allowFailure returns empty string on failure
303
296
  const result = runGit(tempDir, ["log", "--oneline"], { allowFailure: true });
304
- assertEq(result, "", "runGit allowFailure returns empty on error (no commits yet)");
297
+ assert.deepStrictEqual(result, "", "runGit allowFailure returns empty on error (no commits yet)");
305
298
 
306
299
  // runGit throws on failure without allowFailure
307
300
  let threw = false;
@@ -309,22 +302,21 @@ async function main(): Promise<void> {
309
302
  runGit(tempDir, ["log", "--oneline"]);
310
303
  } catch (e) {
311
304
  threw = true;
312
- assertTrue(
305
+ assert.ok(
313
306
  (e as Error).message.includes("git log --oneline failed"),
314
307
  "error message includes command and path"
315
308
  );
316
309
  }
317
- assertTrue(threw, "runGit throws without allowFailure on error");
310
+ assert.ok(threw, "runGit throws without allowFailure on error");
318
311
 
319
312
  // ─── Type exports compile check ────────────────────────────────────────
320
313
 
321
- console.log("\n=== Type exports ===");
322
314
 
323
315
  // These are compile-time checks — if we got here, the types import fine
324
316
  const _prefs: GitPreferences = { auto_push: true, remote: "origin" };
325
317
  const _opts: CommitOptions = { message: "test" };
326
- assertTrue(true, "GitPreferences type exported and usable");
327
- assertTrue(true, "CommitOptions type exported and usable");
318
+ assert.ok(true, "GitPreferences type exported and usable");
319
+ assert.ok(true, "CommitOptions type exported and usable");
328
320
 
329
321
  // Cleanup T01 temp dir
330
322
  rmSync(tempDir, { recursive: true, force: true });
@@ -351,9 +343,7 @@ async function main(): Promise<void> {
351
343
 
352
344
  // ─── GitServiceImpl: smart staging ─────────────────────────────────────
353
345
 
354
- console.log("\n=== GitServiceImpl: smart staging ===");
355
-
356
- {
346
+ test('GitServiceImpl: smart staging', () => {
357
347
  const repo = initTempRepo();
358
348
  const svc = new GitServiceImpl(repo);
359
349
 
@@ -370,34 +360,32 @@ async function main(): Promise<void> {
370
360
 
371
361
  const result = svc.commit({ message: "test: smart staging" });
372
362
 
373
- assertEq(result, "test: smart staging", "commit returns the commit message");
363
+ assert.deepStrictEqual(result, "test: smart staging", "commit returns the commit message");
374
364
 
375
365
  // Verify only src/code.ts is in the commit
376
366
  const showStat = run("git show --stat --format= HEAD", repo);
377
- assertTrue(showStat.includes("src/code.ts"), "src/code.ts is in the commit");
378
- assertTrue(!showStat.includes(".gsd/activity"), ".gsd/activity/ excluded from commit");
379
- assertTrue(!showStat.includes(".gsd/runtime"), ".gsd/runtime/ excluded from commit");
380
- assertTrue(!showStat.includes("STATE.md"), ".gsd/STATE.md excluded from commit");
381
- assertTrue(!showStat.includes("auto.lock"), ".gsd/auto.lock excluded from commit");
382
- assertTrue(!showStat.includes("metrics.json"), ".gsd/metrics.json excluded from commit");
383
- assertTrue(!showStat.includes(".gsd/worktrees"), ".gsd/worktrees/ excluded from commit");
367
+ assert.ok(showStat.includes("src/code.ts"), "src/code.ts is in the commit");
368
+ assert.ok(!showStat.includes(".gsd/activity"), ".gsd/activity/ excluded from commit");
369
+ assert.ok(!showStat.includes(".gsd/runtime"), ".gsd/runtime/ excluded from commit");
370
+ assert.ok(!showStat.includes("STATE.md"), ".gsd/STATE.md excluded from commit");
371
+ assert.ok(!showStat.includes("auto.lock"), ".gsd/auto.lock excluded from commit");
372
+ assert.ok(!showStat.includes("metrics.json"), ".gsd/metrics.json excluded from commit");
373
+ assert.ok(!showStat.includes(".gsd/worktrees"), ".gsd/worktrees/ excluded from commit");
384
374
 
385
375
  // Verify runtime files are still untracked
386
376
  // git status --short may collapse to "?? .gsd/" or show individual files
387
377
  // Use --untracked-files=all to force individual listing
388
378
  const statusOut = run("git status --short --untracked-files=all", repo);
389
- assertTrue(statusOut.includes(".gsd/activity/"), "activity still untracked after commit");
390
- assertTrue(statusOut.includes(".gsd/runtime/"), "runtime still untracked after commit");
391
- assertTrue(statusOut.includes(".gsd/STATE.md"), "STATE.md still untracked after commit");
379
+ assert.ok(statusOut.includes(".gsd/activity/"), "activity still untracked after commit");
380
+ assert.ok(statusOut.includes(".gsd/runtime/"), "runtime still untracked after commit");
381
+ assert.ok(statusOut.includes(".gsd/STATE.md"), "STATE.md still untracked after commit");
392
382
 
393
383
  rmSync(repo, { recursive: true, force: true });
394
- }
384
+ });
395
385
 
396
386
  // ─── GitServiceImpl: smart staging excludes tracked runtime files ──────
397
387
 
398
- console.log("\n=== GitServiceImpl: smart staging excludes tracked runtime files ===");
399
-
400
- {
388
+ test('GitServiceImpl: smart staging excludes tracked runtime files', () => {
401
389
  // Reproduces the real bug: .gsd/ runtime files that are already tracked
402
390
  // (in the git index) must be excluded from staging even when .gsd/ is
403
391
  // in .gitignore. The old pathspec-exclude approach failed silently in
@@ -427,9 +415,9 @@ async function main(): Promise<void> {
427
415
 
428
416
  // Verify runtime files are tracked (precondition)
429
417
  const tracked = run("git ls-files .gsd/", repo);
430
- assertTrue(tracked.includes("metrics.json"), "precondition: metrics.json tracked");
431
- assertTrue(tracked.includes("completed-units.json"), "precondition: completed-units.json tracked");
432
- assertTrue(tracked.includes("activity/log.jsonl"), "precondition: activity log tracked");
418
+ assert.ok(tracked.includes("metrics.json"), "precondition: metrics.json tracked");
419
+ assert.ok(tracked.includes("completed-units.json"), "precondition: completed-units.json tracked");
420
+ assert.ok(tracked.includes("activity/log.jsonl"), "precondition: activity log tracked");
433
421
 
434
422
  // Now modify both runtime and real files
435
423
  createFile(repo, ".gsd/metrics.json", '{"version":2}');
@@ -440,15 +428,15 @@ async function main(): Promise<void> {
440
428
  // autoCommit should commit real.ts. The first call also runs auto-cleanup
441
429
  // which removes runtime files from the index via a dedicated commit.
442
430
  const msg = svc.autoCommit("execute-task", "M001/S01/T01");
443
- assertTrue(msg !== null, "autoCommit produces a commit");
431
+ assert.ok(msg !== null, "autoCommit produces a commit");
444
432
 
445
433
  const show = run("git show --stat HEAD", repo);
446
- assertTrue(show.includes("src/real.ts"), "real files are committed");
434
+ assert.ok(show.includes("src/real.ts"), "real files are committed");
447
435
 
448
436
  // After the commit, runtime files must no longer be in the git index.
449
437
  // They remain on disk but are untracked (protected by .gitignore).
450
438
  const trackedAfter = run("git ls-files .gsd/", repo);
451
- assertEq(trackedAfter, "", "no .gsd/ runtime files remain in the index");
439
+ assert.deepStrictEqual(trackedAfter, "", "no .gsd/ runtime files remain in the index");
452
440
 
453
441
  // Verify a second autoCommit with changed runtime files does NOT stage them
454
442
  createFile(repo, ".gsd/metrics.json", '{"version":3}');
@@ -456,37 +444,33 @@ async function main(): Promise<void> {
456
444
  createFile(repo, "src/real.ts", "third version");
457
445
 
458
446
  const msg2 = svc.autoCommit("execute-task", "M001/S01/T02");
459
- assertTrue(msg2 !== null, "second autoCommit produces a commit");
447
+ assert.ok(msg2 !== null, "second autoCommit produces a commit");
460
448
 
461
449
  const show2 = run("git show --stat HEAD", repo);
462
- assertTrue(show2.includes("src/real.ts"), "real files committed in second commit");
463
- assertTrue(!show2.includes("metrics"), "metrics.json not in second commit");
464
- assertTrue(!show2.includes("completed-units"), "completed-units.json not in second commit");
465
- assertTrue(!show2.includes("activity"), "activity not in second commit");
450
+ assert.ok(show2.includes("src/real.ts"), "real files committed in second commit");
451
+ assert.ok(!show2.includes("metrics"), "metrics.json not in second commit");
452
+ assert.ok(!show2.includes("completed-units"), "completed-units.json not in second commit");
453
+ assert.ok(!show2.includes("activity"), "activity not in second commit");
466
454
 
467
455
  rmSync(repo, { recursive: true, force: true });
468
- }
456
+ });
469
457
 
470
458
  // ─── GitServiceImpl: autoCommit on clean repo ──────────────────────────
471
459
 
472
- console.log("\n=== GitServiceImpl: autoCommit ===");
473
-
474
- {
460
+ test('GitServiceImpl: autoCommit', () => {
475
461
  const repo = initTempRepo();
476
462
  const svc = new GitServiceImpl(repo);
477
463
 
478
464
  // Clean repo — autoCommit should return null
479
465
  const cleanResult = svc.autoCommit("task", "T01");
480
- assertEq(cleanResult, null, "autoCommit on clean repo returns null");
466
+ assert.deepStrictEqual(cleanResult, null, "autoCommit on clean repo returns null");
481
467
 
482
468
  rmSync(repo, { recursive: true, force: true });
483
- }
469
+ });
484
470
 
485
471
  // ─── GitServiceImpl: autoCommit on dirty repo ──────────────────────────
486
472
 
487
- console.log("\n=== GitServiceImpl: autoCommit on dirty repo ===");
488
-
489
- {
473
+ test('GitServiceImpl: autoCommit on dirty repo', () => {
490
474
  const repo = initTempRepo();
491
475
  const svc = new GitServiceImpl(repo);
492
476
 
@@ -494,10 +478,10 @@ async function main(): Promise<void> {
494
478
 
495
479
  // Without task context, autoCommit uses generic chore message
496
480
  const msg = svc.autoCommit("task", "T01");
497
- assertEq(msg, "chore(T01): auto-commit after task", "autoCommit returns generic format without task context");
481
+ assert.deepStrictEqual(msg, "chore(T01): auto-commit after task", "autoCommit returns generic format without task context");
498
482
 
499
483
  const log = run("git log --oneline -1", repo);
500
- assertTrue(log.includes("chore(T01): auto-commit after task"), "generic commit message is in git log");
484
+ assert.ok(log.includes("chore(T01): auto-commit after task"), "generic commit message is in git log");
501
485
 
502
486
  // With task context, autoCommit uses meaningful message
503
487
  createFile(repo, "src/auth.ts", "export function login() {}");
@@ -507,18 +491,16 @@ async function main(): Promise<void> {
507
491
  oneLiner: "Added JWT-based auth with refresh token rotation",
508
492
  keyFiles: ["src/auth.ts"],
509
493
  });
510
- assertTrue(msg2 !== null, "autoCommit with task context returns a message");
511
- assertTrue(msg2!.startsWith("feat(S01/T02):"), "meaningful commit uses feat type and scope");
512
- assertTrue(msg2!.includes("JWT-based auth"), "meaningful commit includes one-liner content");
494
+ assert.ok(msg2 !== null, "autoCommit with task context returns a message");
495
+ assert.ok(msg2!.startsWith("feat(S01/T02):"), "meaningful commit uses feat type and scope");
496
+ assert.ok(msg2!.includes("JWT-based auth"), "meaningful commit includes one-liner content");
513
497
 
514
498
  rmSync(repo, { recursive: true, force: true });
515
- }
499
+ });
516
500
 
517
501
  // ─── GitServiceImpl: empty-after-staging guard ─────────────────────────
518
502
 
519
- console.log("\n=== GitServiceImpl: empty-after-staging guard ===");
520
-
521
- {
503
+ test('GitServiceImpl: empty-after-staging guard', () => {
522
504
  const repo = initTempRepo();
523
505
  const svc = new GitServiceImpl(repo);
524
506
 
@@ -526,20 +508,18 @@ async function main(): Promise<void> {
526
508
  createFile(repo, ".gsd/activity/x.jsonl", "data");
527
509
 
528
510
  const result = svc.autoCommit("task", "T02");
529
- assertEq(result, null, "autoCommit returns null when only runtime files are dirty");
511
+ assert.deepStrictEqual(result, null, "autoCommit returns null when only runtime files are dirty");
530
512
 
531
513
  // Verify no new commit was created (should still be at init commit)
532
514
  const logCount = run("git rev-list --count HEAD", repo);
533
- assertEq(logCount, "1", "no new commit created when only runtime files changed");
515
+ assert.deepStrictEqual(logCount, "1", "no new commit created when only runtime files changed");
534
516
 
535
517
  rmSync(repo, { recursive: true, force: true });
536
- }
518
+ });
537
519
 
538
520
  // ─── GitServiceImpl: autoCommit with extraExclusions ───────────────────
539
521
 
540
- console.log("\n=== GitServiceImpl: autoCommit with extraExclusions ===");
541
-
542
- {
522
+ test('GitServiceImpl: autoCommit with extraExclusions', () => {
543
523
  const repo = initTempRepo();
544
524
  const svc = new GitServiceImpl(repo);
545
525
 
@@ -549,21 +529,19 @@ async function main(): Promise<void> {
549
529
 
550
530
  // Auto-commit with .gsd/ excluded (simulates pre-switch)
551
531
  const msg = svc.autoCommit("pre-switch", "main", [".gsd/"]);
552
- assertEq(msg, "chore(main): auto-commit after pre-switch", "pre-switch autoCommit with .gsd/ exclusion commits");
532
+ assert.deepStrictEqual(msg, "chore(main): auto-commit after pre-switch", "pre-switch autoCommit with .gsd/ exclusion commits");
553
533
 
554
534
  // Verify .gsd/ file was NOT committed
555
535
  const show = run("git show --stat HEAD", repo);
556
- assertTrue(!show.includes("ROADMAP"), ".gsd/ files excluded from pre-switch auto-commit");
557
- assertTrue(show.includes("feature.ts"), "non-.gsd/ files included in pre-switch auto-commit");
536
+ assert.ok(!show.includes("ROADMAP"), ".gsd/ files excluded from pre-switch auto-commit");
537
+ assert.ok(show.includes("feature.ts"), "non-.gsd/ files included in pre-switch auto-commit");
558
538
 
559
539
  rmSync(repo, { recursive: true, force: true });
560
- }
540
+ });
561
541
 
562
542
  // ─── GitServiceImpl: autoCommit extraExclusions — only .gsd/ dirty ────
563
543
 
564
- console.log("\n=== GitServiceImpl: autoCommit extraExclusions — only .gsd/ dirty ===");
565
-
566
- {
544
+ test('GitServiceImpl: autoCommit extraExclusions — only .gsd/ dirty', () => {
567
545
  const repo = initTempRepo();
568
546
  const svc = new GitServiceImpl(repo);
569
547
 
@@ -573,25 +551,23 @@ async function main(): Promise<void> {
573
551
 
574
552
  // Auto-commit with .gsd/ excluded — nothing else to commit
575
553
  const result = svc.autoCommit("pre-switch", "main", [".gsd/"]);
576
- assertEq(result, null, "autoCommit returns null when only .gsd/ files are dirty and excluded");
554
+ assert.deepStrictEqual(result, null, "autoCommit returns null when only .gsd/ files are dirty and excluded");
577
555
 
578
556
  rmSync(repo, { recursive: true, force: true });
579
- }
557
+ });
580
558
 
581
559
  // ─── GitServiceImpl: commit returns null when nothing staged ───────────
582
560
 
583
- console.log("\n=== GitServiceImpl: commit empty ===");
584
-
585
- {
561
+ test('GitServiceImpl: commit empty', () => {
586
562
  const repo = initTempRepo();
587
563
  const svc = new GitServiceImpl(repo);
588
564
 
589
565
  // Nothing dirty, commit should return null
590
566
  const result = svc.commit({ message: "should not commit" });
591
- assertEq(result, null, "commit returns null when nothing to stage");
567
+ assert.deepStrictEqual(result, null, "commit returns null when nothing to stage");
592
568
 
593
569
  rmSync(repo, { recursive: true, force: true });
594
- }
570
+ });
595
571
 
596
572
  // ─── Helper: create repo for branch tests ────────────────────────────
597
573
 
@@ -608,36 +584,32 @@ async function main(): Promise<void> {
608
584
 
609
585
  // ─── getCurrentBranch ────────────────────────────────────────────────
610
586
 
611
- console.log("\n=== Branch queries ===");
612
-
613
- {
587
+ test('Branch queries', () => {
614
588
  const repo = initBranchTestRepo();
615
589
  const svc = new GitServiceImpl(repo);
616
590
 
617
- assertEq(svc.getCurrentBranch(), "main", "getCurrentBranch returns main on main branch");
591
+ assert.deepStrictEqual(svc.getCurrentBranch(), "main", "getCurrentBranch returns main on main branch");
618
592
 
619
593
  run("git checkout -b gsd/M001/S01", repo);
620
- assertEq(svc.getCurrentBranch(), "gsd/M001/S01", "getCurrentBranch returns slice branch name");
594
+ assert.deepStrictEqual(svc.getCurrentBranch(), "gsd/M001/S01", "getCurrentBranch returns slice branch name");
621
595
 
622
596
  run("git checkout -b feature/foo", repo);
623
- assertEq(svc.getCurrentBranch(), "feature/foo", "getCurrentBranch returns feature branch name");
597
+ assert.deepStrictEqual(svc.getCurrentBranch(), "feature/foo", "getCurrentBranch returns feature branch name");
624
598
 
625
599
  rmSync(repo, { recursive: true, force: true });
626
- }
600
+ });
627
601
 
628
602
  // ─── getMainBranch ────────────────────────────────────────────────────
629
603
 
630
- console.log("\n=== getMainBranch ===");
631
-
632
- {
604
+ test('getMainBranch', () => {
633
605
  const repo = initBranchTestRepo();
634
606
  const svc = new GitServiceImpl(repo);
635
607
 
636
608
  // Basic case: repo has "main" branch
637
- assertEq(svc.getMainBranch(), "main", "getMainBranch returns main when main exists");
609
+ assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch returns main when main exists");
638
610
 
639
611
  rmSync(repo, { recursive: true, force: true });
640
- }
612
+ });
641
613
 
642
614
  {
643
615
  // master-only repo
@@ -650,7 +622,7 @@ async function main(): Promise<void> {
650
622
  run('git commit -m "init"', repo);
651
623
 
652
624
  const svc = new GitServiceImpl(repo);
653
- assertEq(svc.getMainBranch(), "master", "getMainBranch returns master when only master exists");
625
+ assert.deepStrictEqual(svc.getMainBranch(), "master", "getMainBranch returns master when only master exists");
654
626
 
655
627
  rmSync(repo, { recursive: true, force: true });
656
628
  }
@@ -661,9 +633,7 @@ async function main(): Promise<void> {
661
633
 
662
634
  // ─── createSnapshot: prefs enabled ─────────────────────────────────────
663
635
 
664
- console.log("\n=== createSnapshot: enabled ===");
665
-
666
- {
636
+ test('createSnapshot: enabled', () => {
667
637
  const repo = initBranchTestRepo();
668
638
  const svc = new GitServiceImpl(repo, { snapshots: true });
669
639
 
@@ -677,16 +647,14 @@ async function main(): Promise<void> {
677
647
 
678
648
  // Verify ref exists under refs/gsd/snapshots/
679
649
  const refs = run("git for-each-ref refs/gsd/snapshots/", repo);
680
- assertTrue(refs.includes("refs/gsd/snapshots/gsd/M001/S01/"), "snapshot ref created under refs/gsd/snapshots/");
650
+ assert.ok(refs.includes("refs/gsd/snapshots/gsd/M001/S01/"), "snapshot ref created under refs/gsd/snapshots/");
681
651
 
682
652
  rmSync(repo, { recursive: true, force: true });
683
- }
653
+ });
684
654
 
685
655
  // ─── createSnapshot: prefs disabled ────────────────────────────────────
686
656
 
687
- console.log("\n=== createSnapshot: disabled ===");
688
-
689
- {
657
+ test('createSnapshot: disabled', () => {
690
658
  const repo = initBranchTestRepo();
691
659
  const svc = new GitServiceImpl(repo, { snapshots: false });
692
660
 
@@ -698,16 +666,14 @@ async function main(): Promise<void> {
698
666
  svc.createSnapshot("gsd/M001/S01");
699
667
 
700
668
  const refs = run("git for-each-ref refs/gsd/snapshots/", repo);
701
- assertEq(refs, "", "no snapshot ref created when prefs.snapshots is false");
669
+ assert.deepStrictEqual(refs, "", "no snapshot ref created when prefs.snapshots is false");
702
670
 
703
671
  rmSync(repo, { recursive: true, force: true });
704
- }
672
+ });
705
673
 
706
674
  // ─── runPreMergeCheck: pass ────────────────────────────────────────────
707
675
 
708
- console.log("\n=== runPreMergeCheck: pass ===");
709
-
710
- {
676
+ test('runPreMergeCheck: pass', () => {
711
677
  const repo = initBranchTestRepo();
712
678
  // Create package.json with passing test script
713
679
  createFile(repo, "package.json", JSON.stringify({
@@ -720,17 +686,15 @@ async function main(): Promise<void> {
720
686
  const svc = new GitServiceImpl(repo, { pre_merge_check: true });
721
687
  const result: PreMergeCheckResult = svc.runPreMergeCheck();
722
688
 
723
- assertEq(result.passed, true, "runPreMergeCheck returns passed:true when tests pass");
724
- assertTrue(!result.skipped, "runPreMergeCheck is not skipped when enabled");
689
+ assert.deepStrictEqual(result.passed, true, "runPreMergeCheck returns passed:true when tests pass");
690
+ assert.ok(!result.skipped, "runPreMergeCheck is not skipped when enabled");
725
691
 
726
692
  rmSync(repo, { recursive: true, force: true });
727
- }
693
+ });
728
694
 
729
695
  // ─── runPreMergeCheck: fail ────────────────────────────────────────────
730
696
 
731
- console.log("\n=== runPreMergeCheck: fail ===");
732
-
733
- {
697
+ test('runPreMergeCheck: fail', () => {
734
698
  const repo = initBranchTestRepo();
735
699
  // Create package.json with failing test script
736
700
  createFile(repo, "package.json", JSON.stringify({
@@ -743,17 +707,15 @@ async function main(): Promise<void> {
743
707
  const svc = new GitServiceImpl(repo, { pre_merge_check: true });
744
708
  const result: PreMergeCheckResult = svc.runPreMergeCheck();
745
709
 
746
- assertEq(result.passed, false, "runPreMergeCheck returns passed:false when tests fail");
747
- assertTrue(!result.skipped, "runPreMergeCheck is not skipped when enabled");
710
+ assert.deepStrictEqual(result.passed, false, "runPreMergeCheck returns passed:false when tests fail");
711
+ assert.ok(!result.skipped, "runPreMergeCheck is not skipped when enabled");
748
712
 
749
713
  rmSync(repo, { recursive: true, force: true });
750
- }
714
+ });
751
715
 
752
716
  // ─── runPreMergeCheck: disabled ────────────────────────────────────────
753
717
 
754
- console.log("\n=== runPreMergeCheck: disabled ===");
755
-
756
- {
718
+ test('runPreMergeCheck: disabled', () => {
757
719
  const repo = initBranchTestRepo();
758
720
  createFile(repo, "package.json", JSON.stringify({
759
721
  name: "test-disabled",
@@ -765,98 +727,86 @@ async function main(): Promise<void> {
765
727
  const svc = new GitServiceImpl(repo, { pre_merge_check: false });
766
728
  const result: PreMergeCheckResult = svc.runPreMergeCheck();
767
729
 
768
- assertEq(result.skipped, true, "runPreMergeCheck skipped when pre_merge_check is false");
769
- assertEq(result.passed, true, "runPreMergeCheck returns passed:true when skipped (no block)");
730
+ assert.deepStrictEqual(result.skipped, true, "runPreMergeCheck skipped when pre_merge_check is false");
731
+ assert.deepStrictEqual(result.passed, true, "runPreMergeCheck returns passed:true when skipped (no block)");
770
732
 
771
733
  rmSync(repo, { recursive: true, force: true });
772
- }
734
+ });
773
735
 
774
736
  // ─── runPreMergeCheck: custom command ──────────────────────────────────
775
737
 
776
- console.log("\n=== runPreMergeCheck: custom command ===");
777
-
778
- {
738
+ test('runPreMergeCheck: custom command', () => {
779
739
  const repo = initBranchTestRepo();
780
740
  // Custom command string overrides auto-detection
781
741
  const svc = new GitServiceImpl(repo, { pre_merge_check: 'node -e "process.exit(0)"' });
782
742
  const result: PreMergeCheckResult = svc.runPreMergeCheck();
783
743
 
784
- assertEq(result.passed, true, "runPreMergeCheck passes with custom command that exits 0");
785
- assertTrue(!result.skipped, "custom command is not skipped");
744
+ assert.deepStrictEqual(result.passed, true, "runPreMergeCheck passes with custom command that exits 0");
745
+ assert.ok(!result.skipped, "custom command is not skipped");
786
746
 
787
747
  rmSync(repo, { recursive: true, force: true });
788
- }
748
+ });
789
749
 
790
750
  // ─── VALID_BRANCH_NAME regex ──────────────────────────────────────────
791
751
 
792
- console.log("\n=== VALID_BRANCH_NAME regex ===");
793
-
794
- {
752
+ test('VALID_BRANCH_NAME regex', () => {
795
753
  // Valid branch names
796
- assertTrue(VALID_BRANCH_NAME.test("main"), "VALID_BRANCH_NAME accepts 'main'");
797
- assertTrue(VALID_BRANCH_NAME.test("master"), "VALID_BRANCH_NAME accepts 'master'");
798
- assertTrue(VALID_BRANCH_NAME.test("develop"), "VALID_BRANCH_NAME accepts 'develop'");
799
- assertTrue(VALID_BRANCH_NAME.test("feature/foo"), "VALID_BRANCH_NAME accepts 'feature/foo'");
800
- assertTrue(VALID_BRANCH_NAME.test("release-1.0"), "VALID_BRANCH_NAME accepts 'release-1.0'");
801
- assertTrue(VALID_BRANCH_NAME.test("my_branch"), "VALID_BRANCH_NAME accepts 'my_branch'");
802
- assertTrue(VALID_BRANCH_NAME.test("v2.0.1"), "VALID_BRANCH_NAME accepts 'v2.0.1'");
754
+ assert.ok(VALID_BRANCH_NAME.test("main"), "VALID_BRANCH_NAME accepts 'main'");
755
+ assert.ok(VALID_BRANCH_NAME.test("master"), "VALID_BRANCH_NAME accepts 'master'");
756
+ assert.ok(VALID_BRANCH_NAME.test("develop"), "VALID_BRANCH_NAME accepts 'develop'");
757
+ assert.ok(VALID_BRANCH_NAME.test("feature/foo"), "VALID_BRANCH_NAME accepts 'feature/foo'");
758
+ assert.ok(VALID_BRANCH_NAME.test("release-1.0"), "VALID_BRANCH_NAME accepts 'release-1.0'");
759
+ assert.ok(VALID_BRANCH_NAME.test("my_branch"), "VALID_BRANCH_NAME accepts 'my_branch'");
760
+ assert.ok(VALID_BRANCH_NAME.test("v2.0.1"), "VALID_BRANCH_NAME accepts 'v2.0.1'");
803
761
 
804
762
  // Invalid / injection attempts
805
- assertTrue(!VALID_BRANCH_NAME.test("main; rm -rf /"), "VALID_BRANCH_NAME rejects shell injection");
806
- assertTrue(!VALID_BRANCH_NAME.test("main && echo pwned"), "VALID_BRANCH_NAME rejects && injection");
807
- assertTrue(!VALID_BRANCH_NAME.test(""), "VALID_BRANCH_NAME rejects empty string");
808
- assertTrue(!VALID_BRANCH_NAME.test("branch name"), "VALID_BRANCH_NAME rejects spaces");
809
- assertTrue(!VALID_BRANCH_NAME.test("branch`cmd`"), "VALID_BRANCH_NAME rejects backticks");
810
- assertTrue(!VALID_BRANCH_NAME.test("branch$(cmd)"), "VALID_BRANCH_NAME rejects $() subshell");
811
- }
763
+ assert.ok(!VALID_BRANCH_NAME.test("main; rm -rf /"), "VALID_BRANCH_NAME rejects shell injection");
764
+ assert.ok(!VALID_BRANCH_NAME.test("main && echo pwned"), "VALID_BRANCH_NAME rejects && injection");
765
+ assert.ok(!VALID_BRANCH_NAME.test(""), "VALID_BRANCH_NAME rejects empty string");
766
+ assert.ok(!VALID_BRANCH_NAME.test("branch name"), "VALID_BRANCH_NAME rejects spaces");
767
+ assert.ok(!VALID_BRANCH_NAME.test("branch`cmd`"), "VALID_BRANCH_NAME rejects backticks");
768
+ assert.ok(!VALID_BRANCH_NAME.test("branch$(cmd)"), "VALID_BRANCH_NAME rejects $() subshell");
769
+ });
812
770
 
813
771
  // ─── getMainBranch: configured main_branch preference ──────────────────
814
772
 
815
- console.log("\n=== getMainBranch: configured main_branch ===");
816
-
817
- {
773
+ test('getMainBranch: configured main_branch', () => {
818
774
  const repo = initBranchTestRepo();
819
775
  const svc = new GitServiceImpl(repo, { main_branch: "trunk" });
820
776
 
821
- assertEq(svc.getMainBranch(), "trunk", "getMainBranch returns configured main_branch preference");
777
+ assert.deepStrictEqual(svc.getMainBranch(), "trunk", "getMainBranch returns configured main_branch preference");
822
778
 
823
779
  rmSync(repo, { recursive: true, force: true });
824
- }
780
+ });
825
781
 
826
782
  // ─── getMainBranch: falls back to auto-detection when not set ──────────
827
783
 
828
- console.log("\n=== getMainBranch: fallback to auto-detection ===");
829
-
830
- {
784
+ test('getMainBranch: fallback to auto-detection', () => {
831
785
  const repo = initBranchTestRepo();
832
786
  const svc = new GitServiceImpl(repo, {});
833
787
 
834
- assertEq(svc.getMainBranch(), "main", "getMainBranch falls back to auto-detection when main_branch not set");
788
+ assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch falls back to auto-detection when main_branch not set");
835
789
 
836
790
  rmSync(repo, { recursive: true, force: true });
837
- }
791
+ });
838
792
 
839
793
  // ─── getMainBranch: ignores invalid branch names ───────────────────────
840
794
 
841
- console.log("\n=== getMainBranch: ignores invalid branch name ===");
842
-
843
- {
795
+ test('getMainBranch: ignores invalid branch name', () => {
844
796
  const repo = initBranchTestRepo();
845
797
  const svc = new GitServiceImpl(repo, { main_branch: "main; rm -rf /" });
846
798
 
847
- assertEq(svc.getMainBranch(), "main", "getMainBranch ignores invalid branch name and falls back to auto-detection");
799
+ assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch ignores invalid branch name and falls back to auto-detection");
848
800
 
849
801
  rmSync(repo, { recursive: true, force: true });
850
- }
802
+ });
851
803
 
852
804
  // ─── PreMergeCheckResult type export compile check ─────────────────────
853
805
 
854
- console.log("\n=== PreMergeCheckResult type export ===");
855
-
856
- {
806
+ test('PreMergeCheckResult type export', () => {
857
807
  const _checkResult: PreMergeCheckResult = { passed: true, skipped: false };
858
- assertTrue(true, "PreMergeCheckResult type exported and usable");
859
- }
808
+ assert.ok(true, "PreMergeCheckResult type exported and usable");
809
+ });
860
810
 
861
811
  // ═══════════════════════════════════════════════════════════════════════
862
812
  // Integration branch — feature-branch workflow support
@@ -864,82 +814,70 @@ async function main(): Promise<void> {
864
814
 
865
815
  // ─── writeIntegrationBranch / readIntegrationBranch: round-trip ────────
866
816
 
867
- console.log("\n=== Integration branch: write and read ===");
868
-
869
- {
817
+ test('Integration branch: write and read', () => {
870
818
  const repo = initBranchTestRepo();
871
819
 
872
820
  // Initially no integration branch
873
- assertEq(readIntegrationBranch(repo, "M001"), null, "readIntegrationBranch returns null when no metadata");
821
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "readIntegrationBranch returns null when no metadata");
874
822
 
875
823
  // Write integration branch
876
824
  writeIntegrationBranch(repo, "M001", "f-123-new-thing");
877
- assertEq(readIntegrationBranch(repo, "M001"), "f-123-new-thing", "readIntegrationBranch returns written branch");
825
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-123-new-thing", "readIntegrationBranch returns written branch");
878
826
 
879
827
  rmSync(repo, { recursive: true, force: true });
880
- }
828
+ });
881
829
 
882
830
  // ─── writeIntegrationBranch: updates when branch changes (#300) ──────
883
831
 
884
- console.log("\n=== Integration branch: updates on branch change ===");
885
-
886
- {
832
+ test('Integration branch: updates on branch change', () => {
887
833
  const repo = initBranchTestRepo();
888
834
 
889
835
  writeIntegrationBranch(repo, "M001", "f-123-first");
890
836
  writeIntegrationBranch(repo, "M001", "f-456-second"); // updates to new branch (#300)
891
837
 
892
- assertEq(readIntegrationBranch(repo, "M001"), "f-456-second", "second write updates integration branch to new value");
838
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-456-second", "second write updates integration branch to new value");
893
839
 
894
840
  rmSync(repo, { recursive: true, force: true });
895
- }
841
+ });
896
842
 
897
843
  // ─── writeIntegrationBranch: same branch is idempotent ─────────────────
898
844
 
899
- console.log("\n=== Integration branch: same branch is idempotent ===");
900
-
901
- {
845
+ test('Integration branch: same branch is idempotent', () => {
902
846
  const repo = initBranchTestRepo();
903
847
 
904
848
  writeIntegrationBranch(repo, "M001", "f-123-first");
905
849
  writeIntegrationBranch(repo, "M001", "f-123-first"); // same branch — no-op
906
850
 
907
- assertEq(readIntegrationBranch(repo, "M001"), "f-123-first", "same branch write is idempotent");
851
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-123-first", "same branch write is idempotent");
908
852
 
909
853
  rmSync(repo, { recursive: true, force: true });
910
- }
854
+ });
911
855
 
912
856
  // ─── writeIntegrationBranch: rejects slice branches ───────────────────
913
857
 
914
- console.log("\n=== Integration branch: rejects slice branches ===");
915
-
916
- {
858
+ test('Integration branch: rejects slice branches', () => {
917
859
  const repo = initBranchTestRepo();
918
860
 
919
861
  writeIntegrationBranch(repo, "M001", "gsd/M001/S01");
920
- assertEq(readIntegrationBranch(repo, "M001"), null, "slice branches are not recorded as integration branch");
862
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "slice branches are not recorded as integration branch");
921
863
 
922
864
  rmSync(repo, { recursive: true, force: true });
923
- }
865
+ });
924
866
 
925
867
  // ─── writeIntegrationBranch: rejects invalid branch names ─────────────
926
868
 
927
- console.log("\n=== Integration branch: rejects invalid names ===");
928
-
929
- {
869
+ test('Integration branch: rejects invalid names', () => {
930
870
  const repo = initBranchTestRepo();
931
871
 
932
872
  writeIntegrationBranch(repo, "M001", "bad; rm -rf /");
933
- assertEq(readIntegrationBranch(repo, "M001"), null, "invalid branch name is not recorded");
873
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "invalid branch name is not recorded");
934
874
 
935
875
  rmSync(repo, { recursive: true, force: true });
936
- }
876
+ });
937
877
 
938
878
  // ─── getMainBranch: uses integration branch when milestone set ────────
939
879
 
940
- console.log("\n=== getMainBranch: integration branch from milestone metadata ===");
941
-
942
- {
880
+ test('getMainBranch: integration branch from milestone metadata', () => {
943
881
  const repo = initBranchTestRepo();
944
882
 
945
883
  // Create a feature branch
@@ -951,20 +889,18 @@ async function main(): Promise<void> {
951
889
 
952
890
  // Without milestone set, getMainBranch returns "main"
953
891
  const svc = new GitServiceImpl(repo);
954
- assertEq(svc.getMainBranch(), "main", "getMainBranch returns main when no milestone set");
892
+ assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch returns main when no milestone set");
955
893
 
956
894
  // With milestone set, getMainBranch returns the integration branch
957
895
  svc.setMilestoneId("M001");
958
- assertEq(svc.getMainBranch(), "f-123-feature", "getMainBranch returns integration branch when milestone set");
896
+ assert.deepStrictEqual(svc.getMainBranch(), "f-123-feature", "getMainBranch returns integration branch when milestone set");
959
897
 
960
898
  rmSync(repo, { recursive: true, force: true });
961
- }
899
+ });
962
900
 
963
901
  // ─── getMainBranch: main_branch pref still takes priority ─────────────
964
902
 
965
- console.log("\n=== getMainBranch: main_branch pref overrides integration branch ===");
966
-
967
- {
903
+ test('getMainBranch: main_branch pref overrides integration branch', () => {
968
904
  const repo = initBranchTestRepo();
969
905
 
970
906
  run("git checkout -b f-123-feature", repo);
@@ -976,16 +912,14 @@ async function main(): Promise<void> {
976
912
  // Explicit preference still wins
977
913
  const svc = new GitServiceImpl(repo, { main_branch: "trunk" });
978
914
  svc.setMilestoneId("M001");
979
- assertEq(svc.getMainBranch(), "trunk", "main_branch preference overrides integration branch");
915
+ assert.deepStrictEqual(svc.getMainBranch(), "trunk", "main_branch preference overrides integration branch");
980
916
 
981
917
  rmSync(repo, { recursive: true, force: true });
982
- }
918
+ });
983
919
 
984
920
  // ─── getMainBranch: falls back when integration branch deleted ────────
985
921
 
986
- console.log("\n=== getMainBranch: fallback when integration branch deleted ===");
987
-
988
- {
922
+ test('getMainBranch: fallback when integration branch deleted', () => {
989
923
  const repo = initBranchTestRepo();
990
924
 
991
925
  // Write metadata pointing to a branch that doesn't exist
@@ -993,75 +927,67 @@ async function main(): Promise<void> {
993
927
 
994
928
  const svc = new GitServiceImpl(repo);
995
929
  svc.setMilestoneId("M001");
996
- assertEq(svc.getMainBranch(), "main", "getMainBranch falls back to main when integration branch no longer exists");
930
+ assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch falls back to main when integration branch no longer exists");
997
931
 
998
932
  rmSync(repo, { recursive: true, force: true });
999
- }
933
+ });
1000
934
 
1001
935
  // ─── resolveMilestoneIntegrationBranch: recorded branch wins when it exists ───
1002
936
 
1003
- console.log("\n=== Integration branch: resolver prefers recorded branch ===");
1004
-
1005
- {
937
+ test('Integration branch: resolver prefers recorded branch', () => {
1006
938
  const repo = initBranchTestRepo();
1007
939
  run("git checkout -b feature/live", repo);
1008
940
  run("git checkout main", repo);
1009
941
  writeIntegrationBranch(repo, "M001", "feature/live");
1010
942
 
1011
943
  const resolved = resolveMilestoneIntegrationBranch(repo, "M001");
1012
- assertEq(resolved.status, "recorded", "resolver reports recorded branch when metadata branch exists");
1013
- assertEq(resolved.recordedBranch, "feature/live", "resolver includes recorded branch");
1014
- assertEq(resolved.effectiveBranch, "feature/live", "resolver uses recorded branch as effective branch");
944
+ assert.deepStrictEqual(resolved.status, "recorded", "resolver reports recorded branch when metadata branch exists");
945
+ assert.deepStrictEqual(resolved.recordedBranch, "feature/live", "resolver includes recorded branch");
946
+ assert.deepStrictEqual(resolved.effectiveBranch, "feature/live", "resolver uses recorded branch as effective branch");
1015
947
 
1016
948
  rmSync(repo, { recursive: true, force: true });
1017
- }
949
+ });
1018
950
 
1019
951
  // ─── resolveMilestoneIntegrationBranch: falls back to detected default ────────
1020
952
 
1021
- console.log("\n=== Integration branch: resolver falls back to detected default ===");
1022
-
1023
- {
953
+ test('Integration branch: resolver falls back to detected default', () => {
1024
954
  const repo = initBranchTestRepo();
1025
955
  writeIntegrationBranch(repo, "M001", "deleted-branch");
1026
956
 
1027
957
  const resolved = resolveMilestoneIntegrationBranch(repo, "M001");
1028
- assertEq(resolved.status, "fallback", "resolver reports fallback when recorded branch is stale");
1029
- assertEq(resolved.recordedBranch, "deleted-branch", "resolver preserves stale recorded branch for diagnostics");
1030
- assertEq(resolved.effectiveBranch, "main", "resolver falls back to detected default branch");
1031
- assertTrue(
958
+ assert.deepStrictEqual(resolved.status, "fallback", "resolver reports fallback when recorded branch is stale");
959
+ assert.deepStrictEqual(resolved.recordedBranch, "deleted-branch", "resolver preserves stale recorded branch for diagnostics");
960
+ assert.deepStrictEqual(resolved.effectiveBranch, "main", "resolver falls back to detected default branch");
961
+ assert.ok(
1032
962
  resolved.reason.includes("deleted-branch") && resolved.reason.includes("main"),
1033
963
  "resolver reason mentions stale recorded branch and fallback branch",
1034
964
  );
1035
965
 
1036
966
  rmSync(repo, { recursive: true, force: true });
1037
- }
967
+ });
1038
968
 
1039
969
  // ─── resolveMilestoneIntegrationBranch: configured main_branch is fallback ─────
1040
970
 
1041
- console.log("\n=== Integration branch: resolver uses configured fallback branch ===");
1042
-
1043
- {
971
+ test('Integration branch: resolver uses configured fallback branch', () => {
1044
972
  const repo = initBranchTestRepo();
1045
973
  run("git checkout -b trunk", repo);
1046
974
  run("git checkout main", repo);
1047
975
  writeIntegrationBranch(repo, "M001", "deleted-branch");
1048
976
 
1049
977
  const resolved = resolveMilestoneIntegrationBranch(repo, "M001", { main_branch: "trunk" });
1050
- assertEq(resolved.status, "fallback", "resolver reports fallback when using configured main_branch");
1051
- assertEq(resolved.effectiveBranch, "trunk", "resolver prefers configured main_branch as fallback");
1052
- assertTrue(
978
+ assert.deepStrictEqual(resolved.status, "fallback", "resolver reports fallback when using configured main_branch");
979
+ assert.deepStrictEqual(resolved.effectiveBranch, "trunk", "resolver prefers configured main_branch as fallback");
980
+ assert.ok(
1053
981
  resolved.reason.includes("deleted-branch") && resolved.reason.includes("trunk"),
1054
982
  "configured fallback reason mentions stale branch and configured branch",
1055
983
  );
1056
984
 
1057
985
  rmSync(repo, { recursive: true, force: true });
1058
- }
986
+ });
1059
987
 
1060
988
  // ─── Per-milestone isolation: different milestones, different targets ──
1061
989
 
1062
- console.log("\n=== Integration branch: per-milestone isolation ===");
1063
-
1064
- {
990
+ test('Integration branch: per-milestone isolation', () => {
1065
991
  const repo = initBranchTestRepo();
1066
992
 
1067
993
  run("git checkout -b feature-a", repo);
@@ -1074,37 +1000,33 @@ async function main(): Promise<void> {
1074
1000
  const svc = new GitServiceImpl(repo);
1075
1001
 
1076
1002
  svc.setMilestoneId("M001");
1077
- assertEq(svc.getMainBranch(), "feature-a", "M001 integration branch is feature-a");
1003
+ assert.deepStrictEqual(svc.getMainBranch(), "feature-a", "M001 integration branch is feature-a");
1078
1004
 
1079
1005
  svc.setMilestoneId("M002");
1080
- assertEq(svc.getMainBranch(), "feature-b", "M002 integration branch is feature-b");
1006
+ assert.deepStrictEqual(svc.getMainBranch(), "feature-b", "M002 integration branch is feature-b");
1081
1007
 
1082
1008
  svc.setMilestoneId(null);
1083
- assertEq(svc.getMainBranch(), "main", "no milestone set → falls back to main");
1009
+ assert.deepStrictEqual(svc.getMainBranch(), "main", "no milestone set → falls back to main");
1084
1010
 
1085
1011
  rmSync(repo, { recursive: true, force: true });
1086
- }
1012
+ });
1087
1013
 
1088
1014
  // ─── Backward compatibility: no metadata → existing behavior ──────────
1089
1015
 
1090
- console.log("\n=== Integration branch: backward compat ===");
1091
-
1092
- {
1016
+ test('Integration branch: backward compat', () => {
1093
1017
  const repo = initBranchTestRepo();
1094
1018
  const svc = new GitServiceImpl(repo);
1095
1019
 
1096
1020
  // Set milestone but no metadata file exists
1097
1021
  svc.setMilestoneId("M001");
1098
- assertEq(svc.getMainBranch(), "main", "backward compat: no metadata file → falls back to main");
1022
+ assert.deepStrictEqual(svc.getMainBranch(), "main", "backward compat: no metadata file → falls back to main");
1099
1023
 
1100
1024
  rmSync(repo, { recursive: true, force: true });
1101
- }
1025
+ });
1102
1026
 
1103
1027
  // ─── untrackRuntimeFiles: removes tracked runtime files from index ───
1104
1028
 
1105
- console.log("\n=== untrackRuntimeFiles ===");
1106
-
1107
- {
1029
+ test('untrackRuntimeFiles', async () => {
1108
1030
  const { untrackRuntimeFiles } = await import("../gitignore.ts");
1109
1031
  const repo = mkdtempSync(join(tmpdir(), "gsd-untrack-"));
1110
1032
  run("git init -b main", repo);
@@ -1125,38 +1047,36 @@ async function main(): Promise<void> {
1125
1047
 
1126
1048
  // Precondition: runtime files are tracked
1127
1049
  const trackedBefore = run("git ls-files .gsd/", repo);
1128
- assertTrue(trackedBefore.includes("completed-units.json"), "untrack: precondition — completed-units tracked");
1129
- assertTrue(trackedBefore.includes("metrics.json"), "untrack: precondition — metrics tracked");
1050
+ assert.ok(trackedBefore.includes("completed-units.json"), "untrack: precondition — completed-units tracked");
1051
+ assert.ok(trackedBefore.includes("metrics.json"), "untrack: precondition — metrics tracked");
1130
1052
 
1131
1053
  // Run untrackRuntimeFiles
1132
1054
  untrackRuntimeFiles(repo);
1133
1055
 
1134
1056
  // Runtime files should be removed from the index
1135
1057
  const trackedAfter = run("git ls-files .gsd/", repo);
1136
- assertEq(trackedAfter, "", "untrack: all runtime files removed from index");
1058
+ assert.deepStrictEqual(trackedAfter, "", "untrack: all runtime files removed from index");
1137
1059
 
1138
1060
  // Non-runtime files remain tracked
1139
1061
  const srcTracked = run("git ls-files src.ts", repo);
1140
- assertTrue(srcTracked.includes("src.ts"), "untrack: non-runtime files remain tracked");
1062
+ assert.ok(srcTracked.includes("src.ts"), "untrack: non-runtime files remain tracked");
1141
1063
 
1142
1064
  // Files still exist on disk
1143
- assertTrue(existsSync(join(repo, ".gsd", "completed-units.json")),
1065
+ assert.ok(existsSync(join(repo, ".gsd", "completed-units.json")),
1144
1066
  "untrack: completed-units.json still on disk");
1145
- assertTrue(existsSync(join(repo, ".gsd", "metrics.json")),
1067
+ assert.ok(existsSync(join(repo, ".gsd", "metrics.json")),
1146
1068
  "untrack: metrics.json still on disk");
1147
1069
 
1148
1070
  // Idempotent — running again doesn't error
1149
1071
  untrackRuntimeFiles(repo);
1150
- assertTrue(true, "untrack: second call is idempotent (no error)");
1072
+ assert.ok(true, "untrack: second call is idempotent (no error)");
1151
1073
 
1152
1074
  rmSync(repo, { recursive: true, force: true });
1153
- }
1075
+ });
1154
1076
 
1155
1077
  // ─── smartStage excludes runtime files but allows milestone artifacts ──
1156
1078
 
1157
- console.log("\n=== smartStage excludes runtime files, allows milestone artifacts ===");
1158
-
1159
- {
1079
+ test('smartStage excludes runtime files, allows milestone artifacts', () => {
1160
1080
  const repo = mkdtempSync(join(tmpdir(), "gsd-smart-stage-excludes-"));
1161
1081
  run("git init -b main", repo);
1162
1082
  run("git config user.email test@test.com", repo);
@@ -1178,71 +1098,65 @@ async function main(): Promise<void> {
1178
1098
  // smartStage excludes only runtime paths, not all of .gsd/ (#1326)
1179
1099
  const svc = new GitServiceImpl(repo);
1180
1100
  const msg = svc.commit({ message: "test commit" });
1181
- assertTrue(msg !== null, "smartStage: commit succeeds");
1101
+ assert.ok(msg !== null, "smartStage: commit succeeds");
1182
1102
 
1183
1103
  const committed = run("git show --name-only HEAD", repo);
1184
- assertTrue(committed.includes("src.ts"), "smartStage: source files ARE in commit");
1104
+ assert.ok(committed.includes("src.ts"), "smartStage: source files ARE in commit");
1185
1105
  // Runtime files should NOT be committed
1186
- assertTrue(!committed.includes(".gsd/STATE.md"), "smartStage: STATE.md excluded (runtime)");
1187
- assertTrue(!committed.includes(".gsd/runtime/"), "smartStage: runtime/ excluded");
1188
- assertTrue(!committed.includes(".gsd/activity/"), "smartStage: activity/ excluded");
1106
+ assert.ok(!committed.includes(".gsd/STATE.md"), "smartStage: STATE.md excluded (runtime)");
1107
+ assert.ok(!committed.includes(".gsd/runtime/"), "smartStage: runtime/ excluded");
1108
+ assert.ok(!committed.includes(".gsd/activity/"), "smartStage: activity/ excluded");
1189
1109
  // Milestone artifacts SHOULD be committed when not gitignored (#1326)
1190
- assertTrue(committed.includes(".gsd/milestones/"), "smartStage: milestone artifacts ARE committed");
1110
+ assert.ok(committed.includes(".gsd/milestones/"), "smartStage: milestone artifacts ARE committed");
1191
1111
 
1192
1112
  rmSync(repo, { recursive: true, force: true });
1193
- }
1113
+ });
1194
1114
 
1195
1115
  // ─── writeIntegrationBranch: no commit (metadata in external storage) ──
1196
1116
 
1197
- console.log("\n=== writeIntegrationBranch: no commit ===");
1198
-
1199
- {
1117
+ test('writeIntegrationBranch: no commit', () => {
1200
1118
  const repo = initBranchTestRepo();
1201
1119
  const commitsBefore = run("git rev-list --count HEAD", repo);
1202
1120
 
1203
1121
  writeIntegrationBranch(repo, "M001", "f-123-new-thing");
1204
1122
 
1205
1123
  // File should still be written to disk
1206
- assertEq(readIntegrationBranch(repo, "M001"), "f-123-new-thing",
1124
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-123-new-thing",
1207
1125
  "writeIntegrationBranch: metadata file exists on disk");
1208
1126
 
1209
1127
  // No commit — .gsd/ is managed externally
1210
1128
  const commitsAfter = run("git rev-list --count HEAD", repo);
1211
- assertEq(commitsBefore, commitsAfter,
1129
+ assert.deepStrictEqual(commitsBefore, commitsAfter,
1212
1130
  "writeIntegrationBranch: no git commit created for integration branch");
1213
1131
 
1214
1132
  rmSync(repo, { recursive: true, force: true });
1215
- }
1133
+ });
1216
1134
 
1217
1135
  // ─── ensureGitignore: always adds .gsd to gitignore ──────────────────
1218
1136
 
1219
- console.log("\n=== ensureGitignore: adds .gsd entry ===");
1220
-
1221
- {
1137
+ test('ensureGitignore: adds .gsd entry', async () => {
1222
1138
  const { ensureGitignore } = await import("../gitignore.ts");
1223
1139
  const repo = mkdtempSync(join(tmpdir(), "gsd-gitignore-external-state-"));
1224
1140
 
1225
1141
  // Should add .gsd to gitignore (external state dir is a symlink)
1226
1142
  const modified = ensureGitignore(repo);
1227
- assertTrue(modified, "ensureGitignore: gitignore was modified");
1143
+ assert.ok(modified, "ensureGitignore: gitignore was modified");
1228
1144
 
1229
1145
  const { readFileSync } = await import("node:fs");
1230
1146
  const content = readFileSync(join(repo, ".gitignore"), "utf-8");
1231
1147
  const lines = content.split("\n").map(l => l.trim()).filter(l => l && !l.startsWith("#"));
1232
- assertTrue(lines.includes(".gsd"), "ensureGitignore: .gitignore contains .gsd");
1148
+ assert.ok(lines.includes(".gsd"), "ensureGitignore: .gitignore contains .gsd");
1233
1149
 
1234
1150
  // Idempotent — calling again doesn't add duplicates
1235
1151
  const modified2 = ensureGitignore(repo);
1236
- assertTrue(!modified2, "ensureGitignore: second call is idempotent");
1152
+ assert.ok(!modified2, "ensureGitignore: second call is idempotent");
1237
1153
 
1238
1154
  rmSync(repo, { recursive: true, force: true });
1239
- }
1155
+ });
1240
1156
 
1241
1157
  // ─── nativeAddAllWithExclusions: symlinked .gsd fallback ───────────────
1242
1158
 
1243
- console.log("\n=== nativeAddAllWithExclusions: symlinked .gsd fallback ===");
1244
-
1245
- {
1159
+ test('nativeAddAllWithExclusions: symlinked .gsd fallback', () => {
1246
1160
  // When .gsd is a symlink, git rejects `:!.gsd/...` pathspecs with
1247
1161
  // "fatal: pathspec '...' is beyond a symbolic link". The fix falls
1248
1162
  // back to plain `git add -A`, which respects .gitignore.
@@ -1271,22 +1185,20 @@ async function main(): Promise<void> {
1271
1185
  threw = true;
1272
1186
  console.error(" unexpected error:", e);
1273
1187
  }
1274
- assertTrue(!threw, "nativeAddAllWithExclusions does not throw with symlinked .gsd");
1188
+ assert.ok(!threw, "nativeAddAllWithExclusions does not throw with symlinked .gsd");
1275
1189
 
1276
1190
  // Verify the real file was staged
1277
1191
  const staged = run("git diff --cached --name-only", repo);
1278
- assertTrue(staged.includes("src/app.ts"), "real file staged despite symlinked .gsd");
1279
- assertTrue(!staged.includes(".gsd"), ".gsd content not staged");
1192
+ assert.ok(staged.includes("src/app.ts"), "real file staged despite symlinked .gsd");
1193
+ assert.ok(!staged.includes(".gsd"), ".gsd content not staged");
1280
1194
 
1281
1195
  rmSync(repo, { recursive: true, force: true });
1282
1196
  rmSync(externalGsd, { recursive: true, force: true });
1283
- }
1197
+ });
1284
1198
 
1285
1199
  // ─── nativeAddAllWithExclusions: non-symlinked .gsd still works ───────
1286
1200
 
1287
- console.log("\n=== nativeAddAllWithExclusions: non-symlinked .gsd still works ===");
1288
-
1289
- {
1201
+ test('nativeAddAllWithExclusions: non-symlinked .gsd still works', () => {
1290
1202
  // Verify the normal (non-symlink) case still works with pathspec exclusions
1291
1203
  const repo = initTempRepo();
1292
1204
 
@@ -1300,96 +1212,91 @@ async function main(): Promise<void> {
1300
1212
  } catch {
1301
1213
  threw = true;
1302
1214
  }
1303
- assertTrue(!threw, "nativeAddAllWithExclusions works with normal .gsd directory");
1215
+ assert.ok(!threw, "nativeAddAllWithExclusions works with normal .gsd directory");
1304
1216
 
1305
1217
  const staged = run("git diff --cached --name-only", repo);
1306
- assertTrue(staged.includes("src/code.ts"), "real file staged with normal .gsd");
1218
+ assert.ok(staged.includes("src/code.ts"), "real file staged with normal .gsd");
1307
1219
 
1308
1220
  rmSync(repo, { recursive: true, force: true });
1309
- }
1221
+ });
1310
1222
 
1311
1223
  // ─── MergeConflictError: constructor fields ───────────────────────────────
1312
1224
 
1313
- console.log("\n=== MergeConflictError: constructor fields ===");
1314
- {
1225
+ test('MergeConflictError: constructor fields', () => {
1315
1226
  const err = new MergeConflictError(
1316
1227
  ["src/foo.ts", "src/bar.ts"],
1317
1228
  "squash",
1318
1229
  "gsd/M001/S01",
1319
1230
  "main",
1320
1231
  );
1321
- assertEq(err.conflictedFiles, ["src/foo.ts", "src/bar.ts"], "MergeConflictError.conflictedFiles populated");
1322
- assertEq(err.strategy, "squash", "MergeConflictError.strategy set");
1323
- assertEq(err.branch, "gsd/M001/S01", "MergeConflictError.branch set");
1324
- assertEq(err.mainBranch, "main", "MergeConflictError.mainBranch set");
1325
- assertEq(err.name, "MergeConflictError", "MergeConflictError.name is MergeConflictError");
1326
- assertTrue(err.message.includes("src/foo.ts"), "MergeConflictError message lists conflicted files");
1327
- assertTrue(err.message.toLowerCase().includes("squash"), "MergeConflictError message mentions strategy");
1328
- assertTrue(err instanceof MergeConflictError, "MergeConflictError is an instanceof MergeConflictError");
1329
- assertTrue(err instanceof Error, "MergeConflictError is an Error instance");
1330
- }
1232
+ assert.deepStrictEqual(err.conflictedFiles, ["src/foo.ts", "src/bar.ts"], "MergeConflictError.conflictedFiles populated");
1233
+ assert.deepStrictEqual(err.strategy, "squash", "MergeConflictError.strategy set");
1234
+ assert.deepStrictEqual(err.branch, "gsd/M001/S01", "MergeConflictError.branch set");
1235
+ assert.deepStrictEqual(err.mainBranch, "main", "MergeConflictError.mainBranch set");
1236
+ assert.deepStrictEqual(err.name, "MergeConflictError", "MergeConflictError.name is MergeConflictError");
1237
+ assert.ok(err.message.includes("src/foo.ts"), "MergeConflictError message lists conflicted files");
1238
+ assert.ok(err.message.toLowerCase().includes("squash"), "MergeConflictError message mentions strategy");
1239
+ assert.ok(err instanceof MergeConflictError, "MergeConflictError is an instanceof MergeConflictError");
1240
+ assert.ok(err instanceof Error, "MergeConflictError is an Error instance");
1241
+ });
1331
1242
 
1332
1243
  // ─── Integration branch: rejects gsd/quick/* branches ────────────────────
1333
1244
 
1334
- console.log("\n=== Integration branch: rejects gsd/quick/* branches ===");
1335
- {
1245
+ test('Integration branch: rejects gsd/quick/* branches', () => {
1336
1246
  const repo = initBranchTestRepo();
1337
1247
 
1338
1248
  writeIntegrationBranch(repo, "M001", "gsd/quick/1234-some-task");
1339
- assertEq(readIntegrationBranch(repo, "M001"), null, "gsd/quick/* branches are not recorded as integration branch");
1249
+ assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "gsd/quick/* branches are not recorded as integration branch");
1340
1250
 
1341
1251
  rmSync(repo, { recursive: true, force: true });
1342
- }
1252
+ });
1343
1253
 
1344
1254
  // ─── Integration branch: resolver returns missing when no metadata ────────
1345
1255
 
1346
- console.log("\n=== Integration branch: resolver returns missing when no metadata ===");
1347
- {
1256
+ test('Integration branch: resolver returns missing when no metadata', () => {
1348
1257
  const repo = initBranchTestRepo();
1349
1258
 
1350
1259
  // No writeIntegrationBranch call — no metadata file exists
1351
1260
  const resolved = resolveMilestoneIntegrationBranch(repo, "M999");
1352
- assertEq(resolved.status, "missing", "resolver reports missing when no metadata file");
1353
- assertEq(resolved.recordedBranch, null, "resolver recordedBranch is null when no metadata");
1354
- assertEq(resolved.effectiveBranch, null, "resolver effectiveBranch is null when no metadata");
1355
- assertTrue(resolved.reason.includes("M999"), "resolver reason mentions the milestone ID");
1261
+ assert.deepStrictEqual(resolved.status, "missing", "resolver reports missing when no metadata file");
1262
+ assert.deepStrictEqual(resolved.recordedBranch, null, "resolver recordedBranch is null when no metadata");
1263
+ assert.deepStrictEqual(resolved.effectiveBranch, null, "resolver effectiveBranch is null when no metadata");
1264
+ assert.ok(resolved.reason.includes("M999"), "resolver reason mentions the milestone ID");
1356
1265
 
1357
1266
  rmSync(repo, { recursive: true, force: true });
1358
- }
1267
+ });
1359
1268
 
1360
1269
  // ─── Integration branch: resolver missing when both recorded and configured branches gone ───
1361
1270
 
1362
- console.log("\n=== Integration branch: resolver missing when both recorded and configured branches gone ===");
1363
- {
1271
+ test('Integration branch: resolver missing when both recorded and configured branches gone', () => {
1364
1272
  const repo = initBranchTestRepo();
1365
1273
 
1366
1274
  // Record a branch that doesn't exist
1367
1275
  writeIntegrationBranch(repo, "M001", "deleted-feature");
1368
1276
  // configured main_branch also doesn't exist
1369
1277
  const resolved = resolveMilestoneIntegrationBranch(repo, "M001", { main_branch: "nonexistent-branch" });
1370
- assertEq(resolved.status, "missing", "resolver reports missing when recorded branch and configured main_branch both absent");
1371
- assertEq(resolved.recordedBranch, "deleted-feature", "resolver preserves stale recorded branch");
1372
- assertEq(resolved.effectiveBranch, null, "resolver effectiveBranch is null when no safe fallback");
1373
- assertTrue(
1278
+ assert.deepStrictEqual(resolved.status, "missing", "resolver reports missing when recorded branch and configured main_branch both absent");
1279
+ assert.deepStrictEqual(resolved.recordedBranch, "deleted-feature", "resolver preserves stale recorded branch");
1280
+ assert.deepStrictEqual(resolved.effectiveBranch, null, "resolver effectiveBranch is null when no safe fallback");
1281
+ assert.ok(
1374
1282
  resolved.reason.includes("deleted-feature") && resolved.reason.includes("nonexistent-branch"),
1375
1283
  "reason mentions both stale branch and unavailable configured branch",
1376
1284
  );
1377
1285
 
1378
1286
  rmSync(repo, { recursive: true, force: true });
1379
- }
1287
+ });
1380
1288
 
1381
1289
  // ─── buildTaskCommitMessage: issueNumber appends Resolves trailer ─────────
1382
1290
 
1383
- console.log("\n=== buildTaskCommitMessage: issueNumber appends Resolves trailer ===");
1384
- {
1291
+ test('buildTaskCommitMessage: issueNumber appends Resolves trailer', () => {
1385
1292
  const msg = buildTaskCommitMessage({
1386
1293
  taskId: "S01/T03",
1387
1294
  taskTitle: "fix login redirect",
1388
1295
  issueNumber: 42,
1389
1296
  });
1390
- assertTrue(msg.includes("Resolves #42"), "buildTaskCommitMessage includes Resolves #N trailer when issueNumber is set");
1391
- assertTrue(msg.startsWith("fix(S01/T03):"), "buildTaskCommitMessage infers fix type");
1392
- }
1297
+ assert.ok(msg.includes("Resolves #42"), "buildTaskCommitMessage includes Resolves #N trailer when issueNumber is set");
1298
+ assert.ok(msg.startsWith("fix(S01/T03):"), "buildTaskCommitMessage infers fix type");
1299
+ });
1393
1300
 
1394
1301
  {
1395
1302
  // No issueNumber — no Resolves trailer
@@ -1397,29 +1304,26 @@ async function main(): Promise<void> {
1397
1304
  taskId: "S01/T04",
1398
1305
  taskTitle: "add dashboard widget",
1399
1306
  });
1400
- assertTrue(!msg.includes("Resolves"), "buildTaskCommitMessage omits Resolves trailer when issueNumber is absent");
1307
+ assert.ok(!msg.includes("Resolves"), "buildTaskCommitMessage omits Resolves trailer when issueNumber is absent");
1401
1308
  }
1402
1309
 
1403
1310
  // ─── runPreMergeCheck: skips when no package.json ────────────────────────
1404
1311
 
1405
- console.log("\n=== runPreMergeCheck: skips when no package.json ===");
1406
- {
1312
+ test('runPreMergeCheck: skips when no package.json', () => {
1407
1313
  const repo = initBranchTestRepo();
1408
1314
  // No package.json created — auto-detect should skip gracefully
1409
1315
  const svc = new GitServiceImpl(repo, { pre_merge_check: true });
1410
1316
  const result: PreMergeCheckResult = svc.runPreMergeCheck();
1411
1317
 
1412
- assertEq(result.passed, true, "runPreMergeCheck passes when no package.json (skip)");
1413
- assertEq(result.skipped, true, "runPreMergeCheck skips when no package.json found");
1318
+ assert.deepStrictEqual(result.passed, true, "runPreMergeCheck passes when no package.json (skip)");
1319
+ assert.deepStrictEqual(result.skipped, true, "runPreMergeCheck skips when no package.json found");
1414
1320
 
1415
1321
  rmSync(repo, { recursive: true, force: true });
1416
- }
1322
+ });
1417
1323
 
1418
1324
  // ─── autoCommit: symlinked .gsd does NOT stage milestone artifacts (#2247) ──
1419
1325
 
1420
- console.log("\n=== autoCommit: symlinked .gsd does NOT stage milestone artifacts (#2247) ===");
1421
-
1422
- {
1326
+ test('autoCommit: symlinked .gsd does NOT stage milestone artifacts (#2247)', () => {
1423
1327
  // When .gsd is a symlink (external state project), .gsd/ files live outside
1424
1328
  // the repo by design. smartStage() must NOT force-stage them into git — the
1425
1329
  // .gitignore exclusion is correct and intentional.
@@ -1448,21 +1352,14 @@ async function main(): Promise<void> {
1448
1352
 
1449
1353
  const svc = new GitServiceImpl(repo);
1450
1354
  const msg = svc.autoCommit("complete-milestone", "M009");
1451
- assertTrue(msg !== null, "symlink autoCommit: commit succeeds");
1355
+ assert.ok(msg !== null, "symlink autoCommit: commit succeeds");
1452
1356
 
1453
1357
  const committed = run("git show --name-only HEAD", repo);
1454
- assertTrue(committed.includes("src/feature.ts"), "symlink autoCommit: source file committed");
1455
- assertTrue(!committed.includes(".gsd/milestones/"),
1358
+ assert.ok(committed.includes("src/feature.ts"), "symlink autoCommit: source file committed");
1359
+ assert.ok(!committed.includes(".gsd/milestones/"),
1456
1360
  "symlink autoCommit: .gsd/milestones/ files are NOT staged (external state stays external)");
1457
1361
 
1458
1362
  try { rmSync(repo, { recursive: true, force: true }); } catch {}
1459
1363
  try { rmSync(externalGsd, { recursive: true, force: true }); } catch {}
1460
- }
1461
-
1462
- report();
1463
- }
1464
-
1465
- main().catch((error) => {
1466
- console.error(error);
1467
- process.exit(1);
1364
+ });
1468
1365
  });