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,366 @@
1
+ // Token Savings Validation Test
2
+ //
3
+ // Proves ≥30% character savings when using DB-scoped content vs full-markdown
4
+ // for planning/research prompt types. Uses realistic fixture data:
5
+ // 24 decisions across 3 milestones, 21 requirements across 5 slices in 2 milestones.
6
+ //
7
+ // Retires R016 (≥30% savings target) and provides evidence for R019 (no quality regression).
8
+
9
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { tmpdir } from 'node:os';
12
+
13
+ import { openDatabase, closeDatabase } from '../gsd-db.ts';
14
+ import { migrateFromMarkdown } from '../md-importer.ts';
15
+ import {
16
+ queryDecisions,
17
+ queryRequirements,
18
+ formatDecisionsForPrompt,
19
+ formatRequirementsForPrompt,
20
+ } from '../context-store.ts';
21
+ import { createTestContext } from './test-helpers.ts';
22
+
23
+ const { assertEq, assertTrue, assertMatch, assertNoMatch, report } = createTestContext();
24
+
25
+ // ─── Fixture Generators ────────────────────────────────────────────────────
26
+
27
+ /**
28
+ * Generate a realistic DECISIONS.md with `count` decisions spread across milestones.
29
+ * Each decision has realistic-length text in each column to produce meaningful size.
30
+ */
31
+ function generateDecisionsMarkdown(count: number, milestones: string[]): string {
32
+ const lines: string[] = [
33
+ '# Decisions Register',
34
+ '',
35
+ '<!-- Append-only. Never edit or remove existing rows. -->',
36
+ '',
37
+ '| # | When | Scope | Decision | Choice | Rationale | Revisable? |',
38
+ '|---|------|-------|----------|--------|-----------|------------|',
39
+ ];
40
+
41
+ for (let i = 1; i <= count; i++) {
42
+ const id = `D${String(i).padStart(3, '0')}`;
43
+ const milestone = milestones[(i - 1) % milestones.length];
44
+ const sliceNum = ((i - 1) % 5) + 1;
45
+ const when = `${milestone}/S${String(sliceNum).padStart(2, '0')}`;
46
+ const scope = ['architecture', 'testing', 'observability', 'security', 'performance'][(i - 1) % 5];
47
+ const decision = `${scope} decision ${i}: implement ${scope}-level ${['caching', 'validation', 'retry logic', 'circuit breaker', 'rate limiting'][(i - 1) % 5]} for the ${['API layer', 'data pipeline', 'auth subsystem', 'notification service', 'background workers'][(i - 1) % 5]}`;
48
+ const choice = `Use ${['SQLite', 'Redis', 'in-memory cache', 'exponential backoff', 'token bucket'][(i - 1) % 5]} with ${['WAL mode', 'cluster mode', 'LRU eviction', 'jitter', 'sliding window'][(i - 1) % 5]} configuration for optimal ${scope} characteristics`;
49
+ const rationale = `${['Built-in Node.js support eliminates external dependency', 'Sub-millisecond latency meets P99 requirement', 'Memory-efficient with bounded growth prevents OOM', 'Prevents thundering herd during recovery', 'Protects downstream services from burst traffic'][(i - 1) % 5]}. This aligns with our ${scope} principles established in the architecture review and satisfies the non-functional requirements for the ${milestone} milestone.`;
50
+ const revisable = i % 3 === 0 ? 'no' : 'yes';
51
+
52
+ lines.push(`| ${id} | ${when} | ${scope} | ${decision} | ${choice} | ${rationale} | ${revisable} |`);
53
+ }
54
+
55
+ return lines.join('\n');
56
+ }
57
+
58
+ /**
59
+ * Generate a realistic REQUIREMENTS.md with `count` requirements spread across slices.
60
+ * Each requirement has multiple detailed fields producing meaningful character content.
61
+ */
62
+ function generateRequirementsMarkdown(count: number, sliceAssignments: { milestone: string; slice: string }[]): string {
63
+ const lines: string[] = [
64
+ '# Requirements',
65
+ '',
66
+ '## Active',
67
+ '',
68
+ ];
69
+
70
+ for (let i = 1; i <= count; i++) {
71
+ const id = `R${String(i).padStart(3, '0')}`;
72
+ const assignment = sliceAssignments[(i - 1) % sliceAssignments.length];
73
+ const reqClass = ['functional', 'non-functional', 'constraint', 'functional', 'non-functional'][(i - 1) % 5];
74
+ const description = `${['Response latency', 'Data consistency', 'Error recovery', 'Access control', 'Audit logging', 'Cache invalidation', 'Schema migration'][(i - 1) % 7]} requirement for ${assignment.milestone}/${assignment.slice}`;
75
+ const why = `Critical for ${['user experience', 'data integrity', 'system reliability', 'security compliance', 'regulatory requirements', 'operational visibility', 'deployment safety'][(i - 1) % 7]}. Without this, the system would ${['degrade under load', 'lose data during failures', 'fail to recover from crashes', 'expose unauthorized data', 'violate compliance mandates', 'have stale data issues', 'break during schema changes'][(i - 1) % 7]}, which is unacceptable for production readiness.`;
76
+ const source = `Architecture review ${milestone_shorthand((i - 1) % 3)}, stakeholder feedback round ${((i - 1) % 4) + 1}`;
77
+ const primaryOwner = assignment.slice;
78
+ const supportingSlices = sliceAssignments
79
+ .filter(a => a.slice !== assignment.slice && a.milestone === assignment.milestone)
80
+ .map(a => a.slice)
81
+ .slice(0, 2)
82
+ .join(', ');
83
+ const validation = `${['Automated test suite covers all edge cases', 'Load test confirms P99 < 200ms under 1000 RPS', 'Chaos test proves recovery within 30s', 'Penetration test shows no unauthorized access paths', 'Audit log review confirms complete event capture', 'Integration test validates cache consistency', 'Migration test verifies zero-downtime upgrade'][(i - 1) % 7]}. Additionally, manual review by ${['architecture team', 'security team', 'SRE team', 'product owner', 'tech lead'][(i - 1) % 5]} confirms adherence to standards.`;
84
+ const notes = `Tracked in ${['JIRA-123', 'JIRA-456', 'JIRA-789', 'JIRA-012', 'JIRA-345'][(i - 1) % 5]}. See also ${['ADR-001', 'ADR-002', 'ADR-003', 'ADR-004', 'ADR-005'][(i - 1) % 5]} for background context on this requirement domain.`;
85
+
86
+ lines.push(`### ${id} — ${description}`);
87
+ lines.push('');
88
+ lines.push(`- Class: ${reqClass}`);
89
+ lines.push(`- Status: active`);
90
+ lines.push(`- Why it matters: ${why}`);
91
+ lines.push(`- Source: ${source}`);
92
+ lines.push(`- Primary owning slice: ${primaryOwner}`);
93
+ if (supportingSlices) {
94
+ lines.push(`- Supporting slices: ${supportingSlices}`);
95
+ }
96
+ lines.push(`- Validation: ${validation}`);
97
+ lines.push(`- Notes: ${notes}`);
98
+ lines.push('');
99
+ }
100
+
101
+ return lines.join('\n');
102
+ }
103
+
104
+ function milestone_shorthand(index: number): string {
105
+ return ['alpha', 'beta', 'GA'][index] ?? 'alpha';
106
+ }
107
+
108
+ // ─── Fixture Setup ─────────────────────────────────────────────────────────
109
+
110
+ const MILESTONES = ['M001', 'M002', 'M003'];
111
+
112
+ // Slice assignments: 5 slices spread across M001 and M002
113
+ const SLICE_ASSIGNMENTS = [
114
+ { milestone: 'M001', slice: 'S01' },
115
+ { milestone: 'M001', slice: 'S02' },
116
+ { milestone: 'M001', slice: 'S03' },
117
+ { milestone: 'M002', slice: 'S04' },
118
+ { milestone: 'M002', slice: 'S05' },
119
+ ];
120
+
121
+ const DECISIONS_COUNT = 24;
122
+ const REQUIREMENTS_COUNT = 21;
123
+
124
+ const decisionsMarkdown = generateDecisionsMarkdown(DECISIONS_COUNT, MILESTONES);
125
+ const requirementsMarkdown = generateRequirementsMarkdown(REQUIREMENTS_COUNT, SLICE_ASSIGNMENTS);
126
+
127
+ const PROJECT_CONTENT = `# Test Project
128
+
129
+ A test project for validating token savings with DB-scoped content.
130
+
131
+ ## Goals
132
+ - Validate ≥30% character savings on planning prompts
133
+ - Ensure quality of scoped content (correct items, no cross-contamination)
134
+
135
+ ## Architecture
136
+ - SQLite-backed artifact storage with markdown import
137
+ - Milestone/slice-scoped queries for prompt injection
138
+ - Fallback to full markdown when DB unavailable
139
+ `;
140
+
141
+ // ═══════════════════════════════════════════════════════════════════════════
142
+ // Test: Plan-slice savings (≥30%)
143
+ // ═══════════════════════════════════════════════════════════════════════════
144
+
145
+ console.log('\n=== token-savings: plan-slice prompt ≥30% character savings ===');
146
+ {
147
+ const base = mkdtempSync(join(tmpdir(), 'gsd-token-savings-'));
148
+ mkdirSync(join(base, '.gsd'), { recursive: true });
149
+ writeFileSync(join(base, '.gsd', 'DECISIONS.md'), decisionsMarkdown);
150
+ writeFileSync(join(base, '.gsd', 'REQUIREMENTS.md'), requirementsMarkdown);
151
+ writeFileSync(join(base, '.gsd', 'PROJECT.md'), PROJECT_CONTENT);
152
+
153
+ // Open :memory: DB and import
154
+ openDatabase(':memory:');
155
+ const result = migrateFromMarkdown(base);
156
+
157
+ assertTrue(result.decisions === DECISIONS_COUNT, `imported ${result.decisions} decisions, expected ${DECISIONS_COUNT}`);
158
+ assertTrue(result.requirements === REQUIREMENTS_COUNT, `imported ${result.requirements} requirements, expected ${REQUIREMENTS_COUNT}`);
159
+
160
+ // ── DB-scoped content for plan-slice (M001 decisions + S01 requirements) ──
161
+ const scopedDecisions = queryDecisions({ milestoneId: 'M001' });
162
+ const scopedRequirements = queryRequirements({ sliceId: 'S01' });
163
+ const dbDecisionsContent = formatDecisionsForPrompt(scopedDecisions);
164
+ const dbRequirementsContent = formatRequirementsForPrompt(scopedRequirements);
165
+
166
+ // ── Full-markdown equivalents (what inlineGsdRootFile would return) ──
167
+ const fullDecisionsContent = readFileSync(join(base, '.gsd', 'DECISIONS.md'), 'utf-8');
168
+ const fullRequirementsContent = readFileSync(join(base, '.gsd', 'REQUIREMENTS.md'), 'utf-8');
169
+
170
+ // DB-scoped total vs full-markdown total
171
+ const dbTotal = dbDecisionsContent.length + dbRequirementsContent.length;
172
+ const fullTotal = fullDecisionsContent.length + fullRequirementsContent.length;
173
+
174
+ const savingsPercent = ((fullTotal - dbTotal) / fullTotal) * 100;
175
+ console.log(` Plan-slice savings: ${savingsPercent.toFixed(1)}% (DB: ${dbTotal} chars, full: ${fullTotal} chars)`);
176
+
177
+ assertTrue(dbTotal > 0, 'DB-scoped content is non-empty');
178
+ assertTrue(dbDecisionsContent.length > 0, 'DB-scoped decisions content is non-empty');
179
+ assertTrue(dbRequirementsContent.length > 0, 'DB-scoped requirements content is non-empty');
180
+ assertTrue(savingsPercent >= 30, `plan-slice savings ≥30% (actual: ${savingsPercent.toFixed(1)}%)`);
181
+ assertTrue(dbTotal < fullTotal * 0.70, `DB total (${dbTotal}) < 70% of full total (${fullTotal})`);
182
+
183
+ // ── Verify correct scoping: decisions ──
184
+ // M001 decisions: those with when_context containing 'M001' — indices 1,4,7,10,13,16,19,22
185
+ // (24 decisions round-robin across M001/M002/M003 → 8 for M001)
186
+ assertTrue(scopedDecisions.length === 8, `M001 decisions: expected 8, got ${scopedDecisions.length}`);
187
+ for (const d of scopedDecisions) {
188
+ assertTrue(d.when_context.includes('M001'), `decision ${d.id} should have M001 in when_context, got "${d.when_context}"`);
189
+ }
190
+
191
+ // Verify NO decisions from other milestones leak in
192
+ for (const d of scopedDecisions) {
193
+ assertNoMatch(d.when_context, /M002|M003/, `decision ${d.id} should not contain M002 or M003`);
194
+ }
195
+
196
+ // ── Verify correct scoping: requirements ──
197
+ // S01 requirements: those assigned to S01 as primary_owner
198
+ // S01 appears in positions 1,6,11,16,21 (5 assignments cycling, 21 reqs → indices 0,5,10,15,20)
199
+ assertTrue(scopedRequirements.length > 0, 'S01 requirements non-empty');
200
+ for (const r of scopedRequirements) {
201
+ assertTrue(
202
+ r.primary_owner.includes('S01') || r.supporting_slices.includes('S01'),
203
+ `requirement ${r.id} should be owned by or support S01`,
204
+ );
205
+ }
206
+
207
+ // Verify specific expected IDs are present
208
+ const scopedDecisionIds = scopedDecisions.map(d => d.id);
209
+ assertTrue(scopedDecisionIds.includes('D001'), 'M001 scoped decisions includes D001');
210
+ assertTrue(scopedDecisionIds.includes('D004'), 'M001 scoped decisions includes D004');
211
+ assertTrue(!scopedDecisionIds.includes('D002'), 'M001 scoped decisions excludes D002 (M002)');
212
+ assertTrue(!scopedDecisionIds.includes('D003'), 'M001 scoped decisions excludes D003 (M003)');
213
+
214
+ const scopedReqIds = scopedRequirements.map(r => r.id);
215
+ assertTrue(scopedReqIds.includes('R001'), 'S01 scoped requirements includes R001');
216
+
217
+ closeDatabase();
218
+ rmSync(base, { recursive: true, force: true });
219
+ }
220
+
221
+ // ═══════════════════════════════════════════════════════════════════════════
222
+ // Test: Research-milestone savings
223
+ // ═══════════════════════════════════════════════════════════════════════════
224
+
225
+ console.log('\n=== token-savings: research-milestone prompt shows meaningful savings ===');
226
+ {
227
+ const base = mkdtempSync(join(tmpdir(), 'gsd-token-savings-'));
228
+ mkdirSync(join(base, '.gsd'), { recursive: true });
229
+ writeFileSync(join(base, '.gsd', 'DECISIONS.md'), decisionsMarkdown);
230
+ writeFileSync(join(base, '.gsd', 'REQUIREMENTS.md'), requirementsMarkdown);
231
+ writeFileSync(join(base, '.gsd', 'PROJECT.md'), PROJECT_CONTENT);
232
+
233
+ openDatabase(':memory:');
234
+ migrateFromMarkdown(base);
235
+
236
+ // ── Research-milestone: M001 decisions + ALL requirements ──
237
+ const scopedDecisions = queryDecisions({ milestoneId: 'M001' });
238
+ const allRequirements = queryRequirements(); // no filter — all requirements
239
+ const dbDecisionsContent = formatDecisionsForPrompt(scopedDecisions);
240
+ const dbRequirementsContent = formatRequirementsForPrompt(allRequirements);
241
+
242
+ const fullDecisionsContent = readFileSync(join(base, '.gsd', 'DECISIONS.md'), 'utf-8');
243
+ const fullRequirementsContent = readFileSync(join(base, '.gsd', 'REQUIREMENTS.md'), 'utf-8');
244
+
245
+ // Decisions should still show savings (8 of 24 scoped to M001)
246
+ const decisionsSavings = ((fullDecisionsContent.length - dbDecisionsContent.length) / fullDecisionsContent.length) * 100;
247
+ console.log(` Decisions savings (M001): ${decisionsSavings.toFixed(1)}% (DB: ${dbDecisionsContent.length}, full: ${fullDecisionsContent.length})`);
248
+
249
+ assertTrue(decisionsSavings > 0, `decisions savings > 0% (actual: ${decisionsSavings.toFixed(1)}%)`);
250
+ assertTrue(scopedDecisions.length === 8, `M001 decisions: 8 of 24 total`);
251
+ assertTrue(allRequirements.length === REQUIREMENTS_COUNT, `all requirements returned: ${allRequirements.length}`);
252
+
253
+ // Requirements: DB-formatted vs raw markdown — formatted output may differ in size
254
+ // but decisions savings alone should make the composite meaningful
255
+ const dbTotal = dbDecisionsContent.length + dbRequirementsContent.length;
256
+ const fullTotal = fullDecisionsContent.length + fullRequirementsContent.length;
257
+ const compositeSavings = ((fullTotal - dbTotal) / fullTotal) * 100;
258
+ console.log(` Research-milestone composite savings: ${compositeSavings.toFixed(1)}% (DB: ${dbTotal}, full: ${fullTotal})`);
259
+
260
+ // With 8/24 decisions = 66% reduction in decisions, even if requirements are equal,
261
+ // the composite should show meaningful savings
262
+ assertTrue(compositeSavings > 10, `research-milestone shows >10% composite savings (actual: ${compositeSavings.toFixed(1)}%)`);
263
+ assertTrue(decisionsSavings >= 30, `decisions-only savings ≥30% for M001 scope (actual: ${decisionsSavings.toFixed(1)}%)`);
264
+
265
+ closeDatabase();
266
+ rmSync(base, { recursive: true, force: true });
267
+ }
268
+
269
+ // ═══════════════════════════════════════════════════════════════════════════
270
+ // Test: Quality — correct content, no cross-contamination
271
+ // ═══════════════════════════════════════════════════════════════════════════
272
+
273
+ console.log('\n=== token-savings: quality — correct scoping, no cross-contamination ===');
274
+ {
275
+ const base = mkdtempSync(join(tmpdir(), 'gsd-token-savings-'));
276
+ mkdirSync(join(base, '.gsd'), { recursive: true });
277
+ writeFileSync(join(base, '.gsd', 'DECISIONS.md'), decisionsMarkdown);
278
+ writeFileSync(join(base, '.gsd', 'REQUIREMENTS.md'), requirementsMarkdown);
279
+ writeFileSync(join(base, '.gsd', 'PROJECT.md'), PROJECT_CONTENT);
280
+
281
+ openDatabase(':memory:');
282
+ migrateFromMarkdown(base);
283
+
284
+ // ── M002-scoped decisions should not contain M001/M003 items ──
285
+ const m002Decisions = queryDecisions({ milestoneId: 'M002' });
286
+ assertTrue(m002Decisions.length === 8, `M002 decisions: expected 8, got ${m002Decisions.length}`);
287
+ for (const d of m002Decisions) {
288
+ assertTrue(d.when_context.includes('M002'), `M002 decision ${d.id} has M002 in when_context`);
289
+ assertNoMatch(d.when_context, /M001|M003/, `M002 decision ${d.id} should not contain M001/M003`);
290
+ }
291
+
292
+ // ── S04-scoped requirements should only include S04-related items ──
293
+ const s04Requirements = queryRequirements({ sliceId: 'S04' });
294
+ assertTrue(s04Requirements.length > 0, 'S04 requirements non-empty');
295
+ for (const r of s04Requirements) {
296
+ assertTrue(
297
+ r.primary_owner.includes('S04') || r.supporting_slices.includes('S04'),
298
+ `S04 requirement ${r.id} should be owned by or support S04`,
299
+ );
300
+ }
301
+
302
+ // ── Verify formatted output is well-formed and non-empty ──
303
+ const formattedDecisions = formatDecisionsForPrompt(m002Decisions);
304
+ assertTrue(formattedDecisions.length > 0, 'formatted M002 decisions is non-empty');
305
+ assertMatch(formattedDecisions, /\| D/, 'formatted decisions contains decision rows');
306
+ assertMatch(formattedDecisions, /\| # \|/, 'formatted decisions has table header');
307
+
308
+ const formattedReqs = formatRequirementsForPrompt(s04Requirements);
309
+ assertTrue(formattedReqs.length > 0, 'formatted S04 requirements is non-empty');
310
+ assertMatch(formattedReqs, /### R\d+/, 'formatted requirements has requirement headings');
311
+
312
+ // ── Verify all milestones have decisions and counts add up ──
313
+ const m001Count = queryDecisions({ milestoneId: 'M001' }).length;
314
+ const m002Count = queryDecisions({ milestoneId: 'M002' }).length;
315
+ const m003Count = queryDecisions({ milestoneId: 'M003' }).length;
316
+ const allCount = queryDecisions().length;
317
+
318
+ assertTrue(m001Count === 8, `M001: 8 decisions (got ${m001Count})`);
319
+ assertTrue(m002Count === 8, `M002: 8 decisions (got ${m002Count})`);
320
+ assertTrue(m003Count === 8, `M003: 8 decisions (got ${m003Count})`);
321
+ assertTrue(allCount === DECISIONS_COUNT, `all: ${DECISIONS_COUNT} decisions (got ${allCount})`);
322
+ assertTrue(m001Count + m002Count + m003Count === allCount, 'milestone decision counts sum to total');
323
+
324
+ // ── Verify all slices have requirements ──
325
+ const s01Reqs = queryRequirements({ sliceId: 'S01' });
326
+ const s02Reqs = queryRequirements({ sliceId: 'S02' });
327
+ const s03Reqs = queryRequirements({ sliceId: 'S03' });
328
+ const s04Reqs = queryRequirements({ sliceId: 'S04' });
329
+ const s05Reqs = queryRequirements({ sliceId: 'S05' });
330
+
331
+ assertTrue(s01Reqs.length > 0, 'S01 has requirements');
332
+ assertTrue(s02Reqs.length > 0, 'S02 has requirements');
333
+ assertTrue(s03Reqs.length > 0, 'S03 has requirements');
334
+ assertTrue(s04Reqs.length > 0, 'S04 has requirements');
335
+ assertTrue(s05Reqs.length > 0, 'S05 has requirements');
336
+
337
+ closeDatabase();
338
+ rmSync(base, { recursive: true, force: true });
339
+ }
340
+
341
+ // ═══════════════════════════════════════════════════════════════════════════
342
+ // Test: Fixture data realism — sufficient volume and distribution
343
+ // ═══════════════════════════════════════════════════════════════════════════
344
+
345
+ console.log('\n=== token-savings: fixture data realism ===');
346
+ {
347
+ // Verify fixture generators produce sufficient volume
348
+ assertTrue(DECISIONS_COUNT >= 20, `decisions count ≥ 20 (actual: ${DECISIONS_COUNT})`);
349
+ assertTrue(REQUIREMENTS_COUNT >= 20, `requirements count ≥ 20 (actual: ${REQUIREMENTS_COUNT})`);
350
+ assertTrue(MILESTONES.length >= 3, `milestones ≥ 3 (actual: ${MILESTONES.length})`);
351
+ assertTrue(SLICE_ASSIGNMENTS.length >= 5, `slice assignments ≥ 5 (actual: ${SLICE_ASSIGNMENTS.length})`);
352
+
353
+ // Verify markdown content is substantial
354
+ assertTrue(decisionsMarkdown.length > 1000, `decisions markdown > 1000 chars (actual: ${decisionsMarkdown.length})`);
355
+ assertTrue(requirementsMarkdown.length > 1000, `requirements markdown > 1000 chars (actual: ${requirementsMarkdown.length})`);
356
+
357
+ // Verify content structure
358
+ assertMatch(decisionsMarkdown, /\| D001 \|/, 'decisions markdown has D001');
359
+ assertMatch(decisionsMarkdown, /\| D024 \|/, 'decisions markdown has D024');
360
+ assertMatch(requirementsMarkdown, /### R001/, 'requirements markdown has R001');
361
+ assertMatch(requirementsMarkdown, /### R021/, 'requirements markdown has R021');
362
+ }
363
+
364
+ // ─── Report ────────────────────────────────────────────────────────────────
365
+
366
+ report();
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Triage dispatch ordering contract tests.
3
+ *
4
+ * These tests verify structural invariants of the triage integration
5
+ * by inspecting the actual source code of auto.ts and post-unit-hooks.ts.
6
+ * Full behavioral testing requires the @gsd/pi-coding-agent runtime.
7
+ */
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { readFileSync } from "node:fs";
12
+ import { join, dirname } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const autoPath = join(__dirname, "..", "auto.ts");
17
+ const hooksPath = join(__dirname, "..", "post-unit-hooks.ts");
18
+ const autoPromptsPath = join(__dirname, "..", "auto-prompts.ts");
19
+
20
+ const autoSrc = readFileSync(autoPath, "utf-8");
21
+ const hooksSrc = readFileSync(hooksPath, "utf-8");
22
+ const autoPromptsSrc = (() => { try { return readFileSync(autoPromptsPath, "utf-8"); } catch { return autoSrc; } })();
23
+
24
+ // ─── Hook exclusion ──────────────────────────────────────────────────────────
25
+
26
+ test("dispatch: triage-captures excluded from post-unit hook triggering", () => {
27
+ // post-unit-hooks.ts must return null for triage-captures unit type
28
+ assert.ok(
29
+ hooksSrc.includes('"triage-captures"'),
30
+ "post-unit-hooks.ts should reference triage-captures",
31
+ );
32
+ assert.ok(
33
+ hooksSrc.includes('completedUnitType === "triage-captures"'),
34
+ "should check for triage-captures in the hook exclusion guard",
35
+ );
36
+ });
37
+
38
+ // ─── Triage check placement ──────────────────────────────────────────────────
39
+
40
+ test("dispatch: triage check appears after hook section and before stepMode check", () => {
41
+ const hookRetryIndex = autoSrc.indexOf("isRetryPending()");
42
+ // Find the triage check in handleAgentEnd (not in getAutoDashboardData)
43
+ const triageCheckIndex = autoSrc.indexOf("Triage check: dispatch triage unit");
44
+ const stepModeIndex = autoSrc.indexOf("In step mode, pause and show a wizard");
45
+
46
+ assert.ok(hookRetryIndex > 0, "hook retry check should exist");
47
+ assert.ok(triageCheckIndex > 0, "triage check block should exist");
48
+ assert.ok(stepModeIndex > 0, "step mode check should exist");
49
+
50
+ assert.ok(
51
+ triageCheckIndex > hookRetryIndex,
52
+ "triage check should come after hook retry check",
53
+ );
54
+ assert.ok(
55
+ triageCheckIndex < stepModeIndex,
56
+ "triage check should come before stepMode check",
57
+ );
58
+ });
59
+
60
+ // ─── Guard conditions ────────────────────────────────────────────────────────
61
+
62
+ test("dispatch: triage check guards against step mode", () => {
63
+ // The triage block should check !stepMode
64
+ const triageBlock = autoSrc.slice(
65
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
66
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
67
+ );
68
+ assert.ok(
69
+ triageBlock.includes("!stepMode"),
70
+ "triage block should guard against step mode",
71
+ );
72
+ });
73
+
74
+ test("dispatch: triage check guards against hook unit types", () => {
75
+ const triageBlock = autoSrc.slice(
76
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
77
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
78
+ );
79
+ assert.ok(
80
+ triageBlock.includes('!currentUnit.type.startsWith("hook/")'),
81
+ "triage block should not fire for hook units",
82
+ );
83
+ });
84
+
85
+ test("dispatch: triage check guards against triage-on-triage", () => {
86
+ const triageBlock = autoSrc.slice(
87
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
88
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
89
+ );
90
+ assert.ok(
91
+ triageBlock.includes('currentUnit.type !== "triage-captures"'),
92
+ "triage block should not fire for triage units",
93
+ );
94
+ });
95
+
96
+ test("dispatch: triage check guards against quick-task triggering triage", () => {
97
+ const triageBlock = autoSrc.slice(
98
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
99
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
100
+ );
101
+ assert.ok(
102
+ triageBlock.includes('currentUnit.type !== "quick-task"'),
103
+ "triage block should not fire for quick-task units",
104
+ );
105
+ });
106
+
107
+ test("dispatch: triage dispatch uses early-return pattern", () => {
108
+ const triageBlock = autoSrc.slice(
109
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
110
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
111
+ );
112
+ assert.ok(
113
+ triageBlock.includes("return; // handleAgentEnd will fire again"),
114
+ "triage dispatch should return after sending message",
115
+ );
116
+ });
117
+
118
+ test("dispatch: triage imports hasPendingCaptures and loadPendingCaptures", () => {
119
+ assert.ok(
120
+ autoSrc.includes('hasPendingCaptures, loadPendingCaptures, countPendingCaptures') &&
121
+ autoSrc.includes('from "./captures.js"'),
122
+ "auto.ts should import capture functions including countPendingCaptures",
123
+ );
124
+ });
125
+
126
+ // ─── Prompt integration ──────────────────────────────────────────────────────
127
+
128
+ test("dispatch: replan prompt builder loads capture context", () => {
129
+ const src = autoPromptsSrc;
130
+ assert.ok(
131
+ src.includes("loadReplanCaptures"),
132
+ "buildReplanSlicePrompt should load replan captures",
133
+ );
134
+ assert.ok(
135
+ src.includes("captureContext"),
136
+ "buildReplanSlicePrompt should pass captureContext to template",
137
+ );
138
+ });
139
+
140
+ test("dispatch: reassess prompt builder loads deferred captures", () => {
141
+ const src = autoPromptsSrc;
142
+ assert.ok(
143
+ src.includes("loadDeferredCaptures"),
144
+ "buildReassessRoadmapPrompt should load deferred captures",
145
+ );
146
+ assert.ok(
147
+ src.includes("deferredCaptures"),
148
+ "buildReassessRoadmapPrompt should pass deferredCaptures to template",
149
+ );
150
+ });
151
+
152
+ // ─── Prompt templates ────────────────────────────────────────────────────────
153
+
154
+ test("dispatch: replan prompt template includes captureContext variable", () => {
155
+ const promptPath = join(__dirname, "..", "prompts", "replan-slice.md");
156
+ const prompt = readFileSync(promptPath, "utf-8");
157
+ assert.ok(
158
+ prompt.includes("{{captureContext}}"),
159
+ "replan-slice.md should include {{captureContext}}",
160
+ );
161
+ });
162
+
163
+ test("dispatch: reassess prompt template includes deferredCaptures variable", () => {
164
+ const promptPath = join(__dirname, "..", "prompts", "reassess-roadmap.md");
165
+ const prompt = readFileSync(promptPath, "utf-8");
166
+ assert.ok(
167
+ prompt.includes("{{deferredCaptures}}"),
168
+ "reassess-roadmap.md should include {{deferredCaptures}}",
169
+ );
170
+ });
171
+
172
+ test("dispatch: triage prompt template exists and has classification criteria", () => {
173
+ const promptPath = join(__dirname, "..", "prompts", "triage-captures.md");
174
+ const prompt = readFileSync(promptPath, "utf-8");
175
+ assert.ok(prompt.includes("quick-task"), "should have quick-task classification");
176
+ assert.ok(prompt.includes("inject"), "should have inject classification");
177
+ assert.ok(prompt.includes("defer"), "should have defer classification");
178
+ assert.ok(prompt.includes("replan"), "should have replan classification");
179
+ assert.ok(prompt.includes("note"), "should have note classification");
180
+ assert.ok(prompt.includes("{{pendingCaptures}}"), "should have pending captures variable");
181
+ });
182
+
183
+ // ─── Dashboard integration ───────────────────────────────────────────────────
184
+
185
+ test("dashboard: AutoDashboardData includes pendingCaptureCount field", () => {
186
+ assert.ok(
187
+ autoSrc.includes("pendingCaptureCount"),
188
+ "auto.ts should have pendingCaptureCount in AutoDashboardData",
189
+ );
190
+ });
191
+
192
+ test("dashboard: getAutoDashboardData computes pendingCaptureCount", () => {
193
+ assert.ok(
194
+ autoSrc.includes("pendingCaptureCount = countPendingCaptures") ||
195
+ autoSrc.includes("pendingCaptureCount = countPendingCaptures(basePath)"),
196
+ "getAutoDashboardData should compute pendingCaptureCount from countPendingCaptures (single-read)",
197
+ );
198
+ });
199
+
200
+ test("dashboard: overlay renders pending captures badge", () => {
201
+ const overlayPath = join(__dirname, "..", "dashboard-overlay.ts");
202
+ const overlaySrc = readFileSync(overlayPath, "utf-8");
203
+ assert.ok(
204
+ overlaySrc.includes("pendingCaptureCount"),
205
+ "dashboard-overlay.ts should reference pendingCaptureCount",
206
+ );
207
+ assert.ok(
208
+ overlaySrc.includes("pending capture"),
209
+ "dashboard-overlay.ts should show pending captures text",
210
+ );
211
+ });
212
+
213
+ test("dashboard: overlay labels triage-captures and quick-task unit types", () => {
214
+ const overlayPath = join(__dirname, "..", "dashboard-overlay.ts");
215
+ const overlaySrc = readFileSync(overlayPath, "utf-8");
216
+ assert.ok(
217
+ overlaySrc.includes('"triage-captures"'),
218
+ "unitLabel should handle triage-captures",
219
+ );
220
+ assert.ok(
221
+ overlaySrc.includes('"quick-task"'),
222
+ "unitLabel should handle quick-task",
223
+ );
224
+ });