gsd-pi 2.18.0 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
  8. package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
  9. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  10. package/dist/resources/extensions/gsd/auto.ts +690 -39
  11. package/dist/resources/extensions/gsd/captures.ts +384 -0
  12. package/dist/resources/extensions/gsd/commands.ts +654 -36
  13. package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
  14. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  15. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  16. package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  17. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  18. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  19. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  20. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  21. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  22. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  23. package/dist/resources/extensions/gsd/export.ts +81 -2
  24. package/dist/resources/extensions/gsd/files.ts +39 -9
  25. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  26. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  27. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  28. package/dist/resources/extensions/gsd/history.ts +0 -1
  29. package/dist/resources/extensions/gsd/index.ts +277 -1
  30. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  31. package/dist/resources/extensions/gsd/metrics.ts +84 -0
  32. package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
  33. package/dist/resources/extensions/gsd/model-router.ts +256 -0
  34. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  35. package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  36. package/dist/resources/extensions/gsd/preferences.ts +198 -150
  37. package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  39. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  40. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  41. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  42. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  43. package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  44. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  45. package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  46. package/dist/resources/extensions/gsd/quick.ts +156 -0
  47. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  48. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  49. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  50. package/dist/resources/extensions/gsd/state.ts +30 -0
  51. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  52. package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
  53. package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  54. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  55. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  56. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  57. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  58. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  59. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  60. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  62. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  63. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  64. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  65. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  66. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  67. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  68. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  69. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  70. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  71. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  72. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  73. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  74. package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  75. package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  76. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  77. package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  78. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  79. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  80. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  81. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  82. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  83. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  84. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  85. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  86. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  87. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  88. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  89. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  90. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  91. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  92. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  93. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  94. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  95. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  96. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  97. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  98. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  99. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  100. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  101. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  102. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  103. package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
  104. package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
  105. package/dist/resources/extensions/gsd/types.ts +29 -0
  106. package/dist/resources/extensions/gsd/undo.ts +0 -1
  107. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  108. package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
  109. package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  110. package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
  111. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  112. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  113. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  114. package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  115. package/dist/resources/extensions/remote-questions/format.ts +166 -14
  116. package/dist/resources/extensions/remote-questions/manager.ts +14 -4
  117. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  118. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  119. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  120. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  121. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  122. package/dist/resources/extensions/voice/index.ts +4 -3
  123. package/package.json +1 -1
  124. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  126. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  129. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  131. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  133. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  137. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  139. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  140. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  142. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  144. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  146. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  148. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  150. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  153. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  156. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  158. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  161. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  162. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  163. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  164. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  165. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  166. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  167. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  168. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  169. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  170. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  171. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  172. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  173. package/src/resources/extensions/google-search/index.ts +164 -47
  174. package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
  175. package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
  176. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  177. package/src/resources/extensions/gsd/auto.ts +690 -39
  178. package/src/resources/extensions/gsd/captures.ts +384 -0
  179. package/src/resources/extensions/gsd/commands.ts +654 -36
  180. package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
  181. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  182. package/src/resources/extensions/gsd/context-store.ts +195 -0
  183. package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  184. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  185. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  186. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  187. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  188. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  189. package/src/resources/extensions/gsd/doctor.ts +283 -2
  190. package/src/resources/extensions/gsd/export.ts +81 -2
  191. package/src/resources/extensions/gsd/files.ts +39 -9
  192. package/src/resources/extensions/gsd/git-service.ts +6 -0
  193. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  194. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  195. package/src/resources/extensions/gsd/history.ts +0 -1
  196. package/src/resources/extensions/gsd/index.ts +277 -1
  197. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  198. package/src/resources/extensions/gsd/metrics.ts +84 -0
  199. package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
  200. package/src/resources/extensions/gsd/model-router.ts +256 -0
  201. package/src/resources/extensions/gsd/notifications.ts +0 -1
  202. package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  203. package/src/resources/extensions/gsd/preferences.ts +198 -150
  204. package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
  205. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  206. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  207. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  208. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  209. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  210. package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  211. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  212. package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  213. package/src/resources/extensions/gsd/quick.ts +156 -0
  214. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  215. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  216. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  217. package/src/resources/extensions/gsd/state.ts +30 -0
  218. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  219. package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
  220. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  221. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  222. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  223. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  224. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  225. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  226. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  227. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  228. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  229. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  230. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  231. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  232. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  233. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  234. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  235. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  236. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  237. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  238. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  239. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  240. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  241. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  242. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  243. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  244. package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  245. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  246. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  247. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  248. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  249. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  250. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  251. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  252. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  253. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  254. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  255. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  256. package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  257. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  258. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  259. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  260. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  261. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  262. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  263. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  264. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  265. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  266. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  267. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  268. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  269. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  270. package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
  271. package/src/resources/extensions/gsd/triage-ui.ts +175 -0
  272. package/src/resources/extensions/gsd/types.ts +29 -0
  273. package/src/resources/extensions/gsd/undo.ts +0 -1
  274. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  275. package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
  276. package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  277. package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
  278. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  279. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  280. package/src/resources/extensions/remote-questions/config.ts +4 -2
  281. package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  282. package/src/resources/extensions/remote-questions/format.ts +166 -14
  283. package/src/resources/extensions/remote-questions/manager.ts +14 -4
  284. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  285. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  286. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  287. package/src/resources/extensions/remote-questions/types.ts +2 -1
  288. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  289. package/src/resources/extensions/voice/index.ts +4 -3
@@ -0,0 +1,144 @@
1
+ /**
2
+ * milestone-transition-worktree.test.ts — Tests for #616 fix.
3
+ *
4
+ * Verifies that when auto-mode transitions between milestones, the
5
+ * worktree lifecycle is handled: old worktree merged, new worktree created.
6
+ *
7
+ * Uses source-level checks since the full auto-mode dispatch loop
8
+ * requires the @gsd/pi-coding-agent runtime.
9
+ */
10
+
11
+ import test from "node:test";
12
+ import assert from "node:assert/strict";
13
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, realpathSync, readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { tmpdir } from "node:os";
16
+ import { execSync } from "node:child_process";
17
+
18
+ import { dirname } from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ import {
22
+ createAutoWorktree,
23
+ teardownAutoWorktree,
24
+ isInAutoWorktree,
25
+ getAutoWorktreeOriginalBase,
26
+ mergeMilestoneToMain,
27
+ } from "../auto-worktree.ts";
28
+
29
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
+
31
+ function run(command: string, cwd: string): string {
32
+ return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
33
+ }
34
+
35
+ function createTempRepo(): string {
36
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-mt-wt-test-")));
37
+ run("git init", dir);
38
+ run("git config user.email test@test.com", dir);
39
+ run("git config user.name Test", dir);
40
+ writeFileSync(join(dir, "README.md"), "# test\n");
41
+ run("git add .", dir);
42
+ run("git commit -m init", dir);
43
+ run("git branch -M main", dir);
44
+ return dir;
45
+ }
46
+
47
+ function createMilestoneArtifacts(dir: string, mid: string): void {
48
+ const msDir = join(dir, ".gsd", "milestones", mid);
49
+ mkdirSync(msDir, { recursive: true });
50
+ writeFileSync(join(msDir, "CONTEXT.md"), `# ${mid} Context\n`);
51
+ const roadmap = [
52
+ `# ${mid}: Test Milestone`,
53
+ "**Vision**: testing",
54
+ "## Success Criteria",
55
+ "- It works",
56
+ "## Slices",
57
+ "- [x] S01 — First slice",
58
+ ].join("\n");
59
+ writeFileSync(join(msDir, `${mid}-ROADMAP.md`), roadmap);
60
+ }
61
+
62
+ // ─── Milestone transition: worktree swap ─────────────────────────────────────
63
+
64
+ test("worktree swap on milestone transition: merge old, create new", () => {
65
+ const savedCwd = process.cwd();
66
+ let tempDir = "";
67
+
68
+ try {
69
+ tempDir = createTempRepo();
70
+
71
+ // Set up M001 and M002 milestone artifacts
72
+ createMilestoneArtifacts(tempDir, "M001");
73
+ createMilestoneArtifacts(tempDir, "M002");
74
+ run("git add .", tempDir);
75
+ run("git commit -m \"add milestones\"", tempDir);
76
+
77
+ // Phase 1: Create worktree for M001 (simulates auto-mode start)
78
+ const wt1 = createAutoWorktree(tempDir, "M001");
79
+ assert.equal(process.cwd(), wt1, "cwd should be in M001 worktree");
80
+ assert.ok(isInAutoWorktree(tempDir), "should be in auto-worktree");
81
+ assert.equal(getAutoWorktreeOriginalBase(), tempDir, "original base preserved");
82
+
83
+ // Add a commit in M001 worktree to simulate work
84
+ writeFileSync(join(wt1, "feature-m001.txt"), "M001 work\n");
85
+ run("git add .", wt1);
86
+ run("git commit -m \"feat(M001): add feature\"", wt1);
87
+
88
+ // Phase 2: Simulate milestone transition — merge M001, exit worktree
89
+ const roadmapPath = join(tempDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
90
+ const roadmapContent = readFileSync(roadmapPath, "utf-8");
91
+ mergeMilestoneToMain(tempDir, "M001", roadmapContent);
92
+
93
+ // After merge: cwd should be back at project root
94
+ assert.equal(process.cwd(), tempDir, "cwd restored to project root after merge");
95
+ assert.ok(!isInAutoWorktree(tempDir), "no longer in auto-worktree after merge");
96
+
97
+ // Verify M001 work was merged to main
98
+ const mainLog = run("git log --oneline -3", tempDir);
99
+ assert.ok(mainLog.includes("M001"), "M001 squash commit should be on main");
100
+
101
+ // Phase 3: Create new worktree for M002 (simulates new milestone)
102
+ const wt2 = createAutoWorktree(tempDir, "M002");
103
+ assert.equal(process.cwd(), wt2, "cwd should be in M002 worktree");
104
+ assert.ok(isInAutoWorktree(tempDir), "should be in M002 auto-worktree");
105
+
106
+ // The new worktree should have the M001 feature file (merged to main)
107
+ assert.ok(existsSync(join(wt2, "feature-m001.txt")), "M002 worktree inherits M001 merged work");
108
+
109
+ // Verify branch is correct
110
+ const branch = run("git branch --show-current", wt2);
111
+ assert.equal(branch, "milestone/M002", "M002 worktree on correct branch");
112
+
113
+ // Cleanup
114
+ teardownAutoWorktree(tempDir, "M002");
115
+ } finally {
116
+ process.chdir(savedCwd);
117
+ if (tempDir && existsSync(tempDir)) {
118
+ rmSync(tempDir, { recursive: true, force: true });
119
+ }
120
+ }
121
+ });
122
+
123
+ // ─── Verify the transition code path exists in auto.ts ──────────────────────
124
+
125
+ test("auto.ts milestone transition block contains worktree lifecycle", () => {
126
+ const autoSrc = readFileSync(
127
+ join(__dirname, "..", "auto.ts"),
128
+ "utf-8",
129
+ );
130
+
131
+ // The fix adds worktree merge + create inside the milestone transition block
132
+ assert.ok(
133
+ autoSrc.includes("Worktree lifecycle on milestone transition"),
134
+ "auto.ts should contain the worktree lifecycle comment marker",
135
+ );
136
+ assert.ok(
137
+ autoSrc.includes("mergeMilestoneToMain") && autoSrc.includes("mid !== currentMilestoneId"),
138
+ "auto.ts should call mergeMilestoneToMain during milestone transition",
139
+ );
140
+ assert.ok(
141
+ autoSrc.includes("createAutoWorktree") && autoSrc.includes("Created auto-worktree for"),
142
+ "auto.ts should create new worktree for incoming milestone",
143
+ );
144
+ });
@@ -0,0 +1,69 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { lookupModelCost, compareModelCost, BUNDLED_COST_TABLE } from "../model-cost-table.js";
5
+
6
+ // ─── lookupModelCost ─────────────────────────────────────────────────────────
7
+
8
+ test("lookupModelCost finds exact match", () => {
9
+ const entry = lookupModelCost("claude-opus-4-6");
10
+ assert.ok(entry);
11
+ assert.equal(entry.id, "claude-opus-4-6");
12
+ assert.ok(entry.inputPer1k > 0);
13
+ assert.ok(entry.outputPer1k > 0);
14
+ });
15
+
16
+ test("lookupModelCost strips provider prefix", () => {
17
+ const entry = lookupModelCost("anthropic/claude-opus-4-6");
18
+ assert.ok(entry);
19
+ assert.equal(entry.id, "claude-opus-4-6");
20
+ });
21
+
22
+ test("lookupModelCost returns undefined for unknown model", () => {
23
+ const entry = lookupModelCost("totally-unknown-model");
24
+ assert.equal(entry, undefined);
25
+ });
26
+
27
+ test("lookupModelCost finds haiku", () => {
28
+ const entry = lookupModelCost("claude-haiku-4-5");
29
+ assert.ok(entry);
30
+ assert.ok(entry.inputPer1k < 0.001, "haiku should be cheap");
31
+ });
32
+
33
+ // ─── compareModelCost ────────────────────────────────────────────────────────
34
+
35
+ test("haiku is cheaper than opus", () => {
36
+ assert.ok(compareModelCost("claude-haiku-4-5", "claude-opus-4-6") < 0);
37
+ });
38
+
39
+ test("opus is more expensive than sonnet", () => {
40
+ assert.ok(compareModelCost("claude-opus-4-6", "claude-sonnet-4-6") > 0);
41
+ });
42
+
43
+ test("same model has equal cost", () => {
44
+ assert.equal(compareModelCost("claude-opus-4-6", "claude-opus-4-6"), 0);
45
+ });
46
+
47
+ // ─── BUNDLED_COST_TABLE ──────────────────────────────────────────────────────
48
+
49
+ test("cost table has entries for all major providers", () => {
50
+ const ids = BUNDLED_COST_TABLE.map(e => e.id);
51
+ // Anthropic
52
+ assert.ok(ids.includes("claude-opus-4-6"));
53
+ assert.ok(ids.includes("claude-sonnet-4-6"));
54
+ assert.ok(ids.includes("claude-haiku-4-5"));
55
+ // OpenAI
56
+ assert.ok(ids.includes("gpt-4o"));
57
+ assert.ok(ids.includes("gpt-4o-mini"));
58
+ // Google
59
+ assert.ok(ids.includes("gemini-2.0-flash"));
60
+ });
61
+
62
+ test("all cost table entries have valid data", () => {
63
+ for (const entry of BUNDLED_COST_TABLE) {
64
+ assert.ok(entry.id, `entry missing id`);
65
+ assert.ok(entry.inputPer1k >= 0, `${entry.id} inputPer1k should be >= 0`);
66
+ assert.ok(entry.outputPer1k >= 0, `${entry.id} outputPer1k should be >= 0`);
67
+ assert.ok(entry.updatedAt, `${entry.id} missing updatedAt`);
68
+ }
69
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Tests for model config isolation between concurrent instances (#650).
3
+ */
4
+
5
+ import { describe, it, beforeEach, afterEach } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { tmpdir } from "node:os";
10
+
11
+ // ─── Test helpers ─────────────────────────────────────────────────────────────
12
+
13
+ function makeTmpDir(suffix: string): string {
14
+ const dir = join(tmpdir(), `gsd-test-650-${suffix}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
15
+ mkdirSync(dir, { recursive: true });
16
+ return dir;
17
+ }
18
+
19
+ // ─── Settings Manager Model Scoping ───────────────────────────────────────────
20
+
21
+ describe("model config isolation (#650)", () => {
22
+ let tmpGlobal: string;
23
+ let tmpProjectA: string;
24
+ let tmpProjectB: string;
25
+
26
+ beforeEach(() => {
27
+ tmpGlobal = makeTmpDir("global");
28
+ tmpProjectA = makeTmpDir("project-a");
29
+ tmpProjectB = makeTmpDir("project-b");
30
+ // Create .pi directories for project settings
31
+ mkdirSync(join(tmpProjectA, ".pi"), { recursive: true });
32
+ mkdirSync(join(tmpProjectB, ".pi"), { recursive: true });
33
+ });
34
+
35
+ afterEach(() => {
36
+ try { rmSync(tmpGlobal, { recursive: true, force: true }); } catch {}
37
+ try { rmSync(tmpProjectA, { recursive: true, force: true }); } catch {}
38
+ try { rmSync(tmpProjectB, { recursive: true, force: true }); } catch {}
39
+ });
40
+
41
+ it("project settings file isolates model from global", async () => {
42
+ // Write project settings for project A
43
+ const projectSettingsPath = join(tmpProjectA, ".pi", "settings.json");
44
+ writeFileSync(projectSettingsPath, JSON.stringify({
45
+ defaultProvider: "anthropic",
46
+ defaultModel: "claude-opus-4-6",
47
+ }));
48
+
49
+ // Write global settings with a different model
50
+ const globalSettingsPath = join(tmpGlobal, "settings.json");
51
+ writeFileSync(globalSettingsPath, JSON.stringify({
52
+ defaultProvider: "openai",
53
+ defaultModel: "gpt-5.4",
54
+ }));
55
+
56
+ // Verify project settings exist and have independent data
57
+ const projectData = JSON.parse(readFileSync(projectSettingsPath, "utf-8"));
58
+ const globalData = JSON.parse(readFileSync(globalSettingsPath, "utf-8"));
59
+
60
+ assert.equal(projectData.defaultModel, "claude-opus-4-6");
61
+ assert.equal(globalData.defaultModel, "gpt-5.4");
62
+ assert.notEqual(projectData.defaultModel, globalData.defaultModel,
63
+ "Project and global should have different models");
64
+ });
65
+
66
+ it("two projects have independent model configs", () => {
67
+ const settingsA = join(tmpProjectA, ".pi", "settings.json");
68
+ const settingsB = join(tmpProjectB, ".pi", "settings.json");
69
+
70
+ writeFileSync(settingsA, JSON.stringify({
71
+ defaultProvider: "anthropic",
72
+ defaultModel: "claude-opus-4-6",
73
+ }));
74
+ writeFileSync(settingsB, JSON.stringify({
75
+ defaultProvider: "openai-codex",
76
+ defaultModel: "gpt-5.4",
77
+ }));
78
+
79
+ const dataA = JSON.parse(readFileSync(settingsA, "utf-8"));
80
+ const dataB = JSON.parse(readFileSync(settingsB, "utf-8"));
81
+
82
+ assert.equal(dataA.defaultModel, "claude-opus-4-6");
83
+ assert.equal(dataB.defaultModel, "gpt-5.4");
84
+ assert.notEqual(dataA.defaultProvider, dataB.defaultProvider);
85
+ });
86
+
87
+ it("autoModeStartModel concept prevents model drift", () => {
88
+ // Simulate the auto-mode start model capture pattern
89
+ const autoModeStartModel = { provider: "anthropic", id: "claude-opus-4-6" };
90
+
91
+ // Simulate another instance writing to global settings
92
+ const globalSettings = { defaultProvider: "openai-codex", defaultModel: "gpt-5.4" };
93
+
94
+ // The captured model should be used, not the global settings
95
+ assert.notEqual(autoModeStartModel.id, globalSettings.defaultModel);
96
+ assert.equal(autoModeStartModel.id, "claude-opus-4-6",
97
+ "Captured model should be preserved regardless of global settings changes");
98
+ });
99
+ });
@@ -0,0 +1,167 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import {
5
+ resolveModelForComplexity,
6
+ escalateTier,
7
+ defaultRoutingConfig,
8
+ } from "../model-router.js";
9
+ import type { DynamicRoutingConfig, RoutingDecision } from "../model-router.js";
10
+ import type { ClassificationResult } from "../complexity-classifier.js";
11
+
12
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
13
+
14
+ function makeClassification(tier: "light" | "standard" | "heavy", reason = "test"): ClassificationResult {
15
+ return { tier, reason, downgraded: false };
16
+ }
17
+
18
+ const AVAILABLE_MODELS = [
19
+ "claude-opus-4-6",
20
+ "claude-sonnet-4-6",
21
+ "claude-haiku-4-5",
22
+ "gpt-4o-mini",
23
+ ];
24
+
25
+ // ─── Passthrough when disabled ───────────────────────────────────────────────
26
+
27
+ test("returns configured model when routing is disabled", () => {
28
+ const config = { ...defaultRoutingConfig(), enabled: false };
29
+ const result = resolveModelForComplexity(
30
+ makeClassification("light"),
31
+ { primary: "claude-opus-4-6", fallbacks: [] },
32
+ config,
33
+ AVAILABLE_MODELS,
34
+ );
35
+ assert.equal(result.modelId, "claude-opus-4-6");
36
+ assert.equal(result.wasDowngraded, false);
37
+ });
38
+
39
+ test("returns configured model when no phase config", () => {
40
+ const config = { ...defaultRoutingConfig(), enabled: true };
41
+ const result = resolveModelForComplexity(
42
+ makeClassification("light"),
43
+ undefined,
44
+ config,
45
+ AVAILABLE_MODELS,
46
+ );
47
+ assert.equal(result.modelId, "");
48
+ assert.equal(result.wasDowngraded, false);
49
+ });
50
+
51
+ // ─── Downgrade-only semantics ────────────────────────────────────────────────
52
+
53
+ test("does not downgrade when tier matches configured model tier", () => {
54
+ const config = { ...defaultRoutingConfig(), enabled: true };
55
+ const result = resolveModelForComplexity(
56
+ makeClassification("heavy"),
57
+ { primary: "claude-opus-4-6", fallbacks: [] },
58
+ config,
59
+ AVAILABLE_MODELS,
60
+ );
61
+ assert.equal(result.modelId, "claude-opus-4-6");
62
+ assert.equal(result.wasDowngraded, false);
63
+ });
64
+
65
+ test("does not upgrade beyond configured model", () => {
66
+ const config = { ...defaultRoutingConfig(), enabled: true };
67
+ // Configured model is sonnet (standard), classification says heavy
68
+ const result = resolveModelForComplexity(
69
+ makeClassification("heavy"),
70
+ { primary: "claude-sonnet-4-6", fallbacks: [] },
71
+ config,
72
+ AVAILABLE_MODELS,
73
+ );
74
+ assert.equal(result.modelId, "claude-sonnet-4-6");
75
+ assert.equal(result.wasDowngraded, false);
76
+ });
77
+
78
+ test("downgrades from opus to haiku for light tier", () => {
79
+ const config = { ...defaultRoutingConfig(), enabled: true };
80
+ const result = resolveModelForComplexity(
81
+ makeClassification("light"),
82
+ { primary: "claude-opus-4-6", fallbacks: [] },
83
+ config,
84
+ AVAILABLE_MODELS,
85
+ );
86
+ // Should pick haiku or gpt-4o-mini (cheapest light tier)
87
+ assert.ok(
88
+ result.modelId === "claude-haiku-4-5" || result.modelId === "gpt-4o-mini",
89
+ `Expected light-tier model, got ${result.modelId}`,
90
+ );
91
+ assert.equal(result.wasDowngraded, true);
92
+ });
93
+
94
+ test("downgrades from opus to sonnet for standard tier", () => {
95
+ const config = { ...defaultRoutingConfig(), enabled: true };
96
+ const result = resolveModelForComplexity(
97
+ makeClassification("standard"),
98
+ { primary: "claude-opus-4-6", fallbacks: [] },
99
+ config,
100
+ AVAILABLE_MODELS,
101
+ );
102
+ assert.equal(result.modelId, "claude-sonnet-4-6");
103
+ assert.equal(result.wasDowngraded, true);
104
+ });
105
+
106
+ // ─── Explicit tier_models ────────────────────────────────────────────────────
107
+
108
+ test("uses explicit tier_models when configured", () => {
109
+ const config: DynamicRoutingConfig = {
110
+ ...defaultRoutingConfig(),
111
+ enabled: true,
112
+ tier_models: { light: "gpt-4o-mini", standard: "claude-sonnet-4-6" },
113
+ };
114
+ const result = resolveModelForComplexity(
115
+ makeClassification("light"),
116
+ { primary: "claude-opus-4-6", fallbacks: [] },
117
+ config,
118
+ AVAILABLE_MODELS,
119
+ );
120
+ assert.equal(result.modelId, "gpt-4o-mini");
121
+ assert.equal(result.wasDowngraded, true);
122
+ });
123
+
124
+ // ─── Fallback chain construction ─────────────────────────────────────────────
125
+
126
+ test("fallback chain includes configured primary as last resort", () => {
127
+ const config = { ...defaultRoutingConfig(), enabled: true };
128
+ const result = resolveModelForComplexity(
129
+ makeClassification("light"),
130
+ { primary: "claude-opus-4-6", fallbacks: ["claude-sonnet-4-6"] },
131
+ config,
132
+ AVAILABLE_MODELS,
133
+ );
134
+ assert.ok(result.wasDowngraded);
135
+ // Fallbacks should include the configured fallbacks and primary
136
+ assert.ok(result.fallbacks.includes("claude-opus-4-6"), "primary should be in fallbacks");
137
+ assert.ok(result.fallbacks.includes("claude-sonnet-4-6"), "configured fallback should be in fallbacks");
138
+ });
139
+
140
+ // ─── Escalation ──────────────────────────────────────────────────────────────
141
+
142
+ test("escalateTier moves light → standard", () => {
143
+ assert.equal(escalateTier("light"), "standard");
144
+ });
145
+
146
+ test("escalateTier moves standard → heavy", () => {
147
+ assert.equal(escalateTier("standard"), "heavy");
148
+ });
149
+
150
+ test("escalateTier returns null for heavy (max)", () => {
151
+ assert.equal(escalateTier("heavy"), null);
152
+ });
153
+
154
+ // ─── No suitable model available ─────────────────────────────────────────────
155
+
156
+ test("falls back to configured model when no light-tier model available", () => {
157
+ const config = { ...defaultRoutingConfig(), enabled: true };
158
+ // Only heavy-tier models available
159
+ const result = resolveModelForComplexity(
160
+ makeClassification("light"),
161
+ { primary: "claude-opus-4-6", fallbacks: [] },
162
+ config,
163
+ ["claude-opus-4-6"],
164
+ );
165
+ assert.equal(result.modelId, "claude-opus-4-6");
166
+ assert.equal(result.wasDowngraded, false);
167
+ });
@@ -1661,4 +1661,44 @@ console.log('\n=== LLM round-trip: extra blank lines ===');
1661
1661
  assertTrue(consecutiveBlanks === null, 'blank-lines: formatted output has no 4+ consecutive newlines');
1662
1662
  }
1663
1663
 
1664
+ // ═══════════════════════════════════════════════════════════════════════════
1665
+ // parseRoadmap: boundary map with embedded code fences (#468)
1666
+ // ═══════════════════════════════════════════════════════════════════════════
1667
+
1668
+ console.log('\n=== parseRoadmap: boundary map with code fences (#468) ===');
1669
+ {
1670
+ const content = `# M001: Test
1671
+
1672
+ **Vision:** Test
1673
+
1674
+ ## Slices
1675
+
1676
+ - [ ] **S01: Core** \`risk:low\` \`depends:[]\`
1677
+ - [ ] **S02: API** \`risk:low\` \`depends:[S01]\`
1678
+
1679
+ ## Boundary Map
1680
+
1681
+ ### S01 → S02
1682
+
1683
+ Produces:
1684
+ types.ts — all types
1685
+ \`\`\`
1686
+ const x = 1;
1687
+ \`\`\`
1688
+
1689
+ Consumes: nothing
1690
+ `;
1691
+
1692
+ // This test ensures the boundary map parser does not hang or
1693
+ // catastrophically backtrack when content contains code fences.
1694
+ const start = Date.now();
1695
+ const r = parseRoadmap(content);
1696
+ const elapsed = Date.now() - start;
1697
+
1698
+ assertTrue(elapsed < 1000, `boundary map with code fences parsed in ${elapsed}ms (should be < 1s)`);
1699
+ assertEq(r.slices.length, 2, 'code-fence roadmap: slice count');
1700
+ // Boundary map should still parse (may not capture perfectly with code fences, but must not hang)
1701
+ assertTrue(r.boundaryMap.length >= 0, 'code-fence roadmap: boundary map parsed without hanging');
1702
+ }
1703
+
1664
1704
  report();
@@ -1,5 +1,4 @@
1
1
  // GSD Extension — Hook Engine Tests (Post-Unit, Pre-Dispatch, State Persistence)
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
2
 
4
3
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from "node:fs";
5
4
  import { join } from "node:path";
@@ -18,6 +17,7 @@ import {
18
17
  clearPersistedHookState,
19
18
  getHookStatus,
20
19
  formatHookStatus,
20
+ triggerHookManually,
21
21
  } from "../post-unit-hooks.ts";
22
22
 
23
23
  const { assertEq, assertTrue, assertMatch, report } = createTestContext();
@@ -294,4 +294,44 @@ console.log("\n=== Hook status: no hooks ===");
294
294
  assertMatch(formatted, /No hooks configured/, "status message says no hooks");
295
295
  }
296
296
 
297
+ // ═══════════════════════════════════════════════════════════════════════════
298
+ // Phase 4: Manual Hook Trigger Tests
299
+ // ═══════════════════════════════════════════════════════════════════════════
300
+
301
+ console.log("\n=== triggerHookManually: hook not found ===");
302
+
303
+ {
304
+ resetHookState();
305
+ const base = createFixtureBase();
306
+ try {
307
+ const result = triggerHookManually("nonexistent-hook", "execute-task", "M001/S01/T01", base);
308
+ assertEq(result, null, "returns null when hook not found");
309
+ } finally {
310
+ rmSync(base, { recursive: true, force: true });
311
+ }
312
+ }
313
+
314
+ console.log("\n=== triggerHookManually: with configured hook ===");
315
+
316
+ {
317
+ resetHookState();
318
+ const base = createFixtureBase();
319
+ try {
320
+ // This test will work when preferences are configured
321
+ // For now, just verify the function exists and handles missing hooks
322
+ const result = triggerHookManually("code-review", "execute-task", "M001/S01/T01", base);
323
+ // Result depends on whether code-review hook is configured in preferences
324
+ // The function should either return null or a valid HookDispatchResult
325
+ assertTrue(result === null || typeof result === "object", "returns null or object");
326
+ if (result) {
327
+ assertEq(result.hookName, "code-review", "hook name in result");
328
+ assertEq(result.unitType, "hook/code-review", "unit type is hook-prefixed");
329
+ assertEq(result.unitId, "M001/S01/T01", "unit ID preserved");
330
+ assertTrue(typeof result.prompt === "string", "prompt is a string");
331
+ }
332
+ } finally {
333
+ rmSync(base, { recursive: true, force: true });
334
+ }
335
+ }
336
+
297
337
  report();
@@ -1,5 +1,4 @@
1
1
  // GSD Git Preferences Tests — validates git.isolation and git.merge_to_main handling
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
2
 
4
3
  import { createTestContext } from "./test-helpers.ts";
5
4
  import { validatePreferences } from "../preferences.ts";
@@ -1,5 +1,4 @@
1
1
  // GSD Extension — Hook Preferences Parsing Tests (Post-Unit + Pre-Dispatch)
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
2
 
4
3
  import { createTestContext } from "./test-helpers.ts";
5
4
  import type { PreDispatchHookConfig } from "../types.ts";