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
@@ -1,87 +1,240 @@
1
- /**
2
- * Routing History — structural tests for adaptive learning module.
3
- *
4
- * Verifies routing-history.ts exports and structure from #579.
5
- * Uses source-level checks to avoid @gsd/pi-coding-agent import chain.
6
- */
7
-
8
1
  import test from "node:test";
9
2
  import assert from "node:assert/strict";
10
- import { readFileSync } from "node:fs";
11
- import { join, dirname } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const historySrc = readFileSync(join(__dirname, "..", "routing-history.ts"), "utf-8");
16
-
17
- // ═══════════════════════════════════════════════════════════════════════════
18
- // Module Exports
19
- // ═══════════════════════════════════════════════════════════════════════════
20
-
21
- test("routing-history: exports initRoutingHistory", () => {
22
- assert.ok(historySrc.includes("export function initRoutingHistory"), "should export initRoutingHistory");
3
+ import { mkdirSync, rmSync, writeFileSync, readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import {
8
+ initRoutingHistory,
9
+ resetRoutingHistory,
10
+ recordOutcome,
11
+ recordFeedback,
12
+ getAdaptiveTierAdjustment,
13
+ clearRoutingHistory,
14
+ getRoutingHistory,
15
+ } from "../routing-history.js";
16
+
17
+ // ─── Test Setup ──────────────────────────────────────────────────────────────
18
+
19
+ function makeTmpDir(): string {
20
+ const dir = join(tmpdir(), `gsd-routing-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
21
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
22
+ return dir;
23
+ }
24
+
25
+ function cleanup(dir: string): void {
26
+ try { rmSync(dir, { recursive: true, force: true }); } catch {}
27
+ resetRoutingHistory();
28
+ }
29
+
30
+ // ─── recordOutcome ───────────────────────────────────────────────────────────
31
+
32
+ test("recordOutcome tracks success and failure counts", () => {
33
+ const dir = makeTmpDir();
34
+ try {
35
+ initRoutingHistory(dir);
36
+ recordOutcome("execute-task", "standard", true);
37
+ recordOutcome("execute-task", "standard", true);
38
+ recordOutcome("execute-task", "standard", false);
39
+
40
+ const history = getRoutingHistory();
41
+ assert.ok(history);
42
+ const pattern = history.patterns["execute-task"];
43
+ assert.ok(pattern);
44
+ assert.equal(pattern.standard.success, 2);
45
+ assert.equal(pattern.standard.fail, 1);
46
+ } finally {
47
+ cleanup(dir);
48
+ }
23
49
  });
24
50
 
25
- test("routing-history: exports recordOutcome", () => {
26
- assert.ok(historySrc.includes("export function recordOutcome"), "should export recordOutcome");
51
+ test("recordOutcome tracks tag-specific patterns", () => {
52
+ const dir = makeTmpDir();
53
+ try {
54
+ initRoutingHistory(dir);
55
+ recordOutcome("execute-task", "light", true, ["docs"]);
56
+
57
+ const history = getRoutingHistory();
58
+ assert.ok(history);
59
+ assert.ok(history.patterns["execute-task:docs"]);
60
+ assert.equal(history.patterns["execute-task:docs"].light.success, 1);
61
+ } finally {
62
+ cleanup(dir);
63
+ }
27
64
  });
28
65
 
29
- test("routing-history: exports recordFeedback", () => {
30
- assert.ok(historySrc.includes("export function recordFeedback"), "should export recordFeedback");
66
+ test("recordOutcome applies rolling window", () => {
67
+ const dir = makeTmpDir();
68
+ try {
69
+ initRoutingHistory(dir);
70
+ // Record 60 successes — should be capped to 50
71
+ for (let i = 0; i < 60; i++) {
72
+ recordOutcome("execute-task", "standard", true);
73
+ }
74
+
75
+ const history = getRoutingHistory();
76
+ assert.ok(history);
77
+ const total = history.patterns["execute-task"].standard.success +
78
+ history.patterns["execute-task"].standard.fail;
79
+ assert.ok(total <= 50, `total ${total} should be <= 50`);
80
+ } finally {
81
+ cleanup(dir);
82
+ }
31
83
  });
32
84
 
33
- test("routing-history: exports getAdaptiveTierAdjustment", () => {
34
- assert.ok(historySrc.includes("export function getAdaptiveTierAdjustment"), "should export getAdaptiveTierAdjustment");
85
+ // ─── getAdaptiveTierAdjustment ───────────────────────────────────────────────
86
+
87
+ test("no adjustment when insufficient data", () => {
88
+ const dir = makeTmpDir();
89
+ try {
90
+ initRoutingHistory(dir);
91
+ recordOutcome("execute-task", "light", false);
92
+ // Only 1 data point — not enough
93
+ const adj = getAdaptiveTierAdjustment("execute-task", "light");
94
+ assert.equal(adj, null);
95
+ } finally {
96
+ cleanup(dir);
97
+ }
35
98
  });
36
99
 
37
- test("routing-history: exports resetRoutingHistory", () => {
38
- assert.ok(historySrc.includes("export function resetRoutingHistory"), "should export resetRoutingHistory");
100
+ test("bumps tier when failure rate exceeds threshold", () => {
101
+ const dir = makeTmpDir();
102
+ try {
103
+ initRoutingHistory(dir);
104
+ // Record high failure rate at light tier
105
+ recordOutcome("execute-task", "light", false);
106
+ recordOutcome("execute-task", "light", false);
107
+ recordOutcome("execute-task", "light", true);
108
+ // 2/3 = 66% failure rate > 20% threshold
109
+
110
+ const adj = getAdaptiveTierAdjustment("execute-task", "light");
111
+ assert.equal(adj, "standard");
112
+ } finally {
113
+ cleanup(dir);
114
+ }
39
115
  });
40
116
 
41
- // ═══════════════════════════════════════════════════════════════════════════
42
- // Design Constants
43
- // ═══════════════════════════════════════════════════════════════════════════
44
-
45
- test("routing-history: uses rolling window of 50 entries", () => {
46
- assert.ok(historySrc.includes("ROLLING_WINDOW = 50"), "should use 50-entry rolling window");
117
+ test("no adjustment when success rate is high", () => {
118
+ const dir = makeTmpDir();
119
+ try {
120
+ initRoutingHistory(dir);
121
+ for (let i = 0; i < 10; i++) {
122
+ recordOutcome("execute-task", "light", true);
123
+ }
124
+ const adj = getAdaptiveTierAdjustment("execute-task", "light");
125
+ assert.equal(adj, null);
126
+ } finally {
127
+ cleanup(dir);
128
+ }
47
129
  });
48
130
 
49
- test("routing-history: failure threshold is 20%", () => {
50
- assert.ok(historySrc.includes("FAILURE_THRESHOLD = 0.20"), "should use 20% failure threshold");
131
+ test("tag-specific patterns take precedence", () => {
132
+ const dir = makeTmpDir();
133
+ try {
134
+ initRoutingHistory(dir);
135
+ // Base pattern has high success rate (tagged calls also count toward base)
136
+ for (let i = 0; i < 15; i++) {
137
+ recordOutcome("execute-task", "light", true);
138
+ }
139
+ // But docs-tagged tasks fail at light
140
+ recordOutcome("execute-task", "light", false, ["docs"]);
141
+ recordOutcome("execute-task", "light", false, ["docs"]);
142
+ recordOutcome("execute-task", "light", true, ["docs"]);
143
+
144
+ // With tags, should bump (docs pattern: 1/3 success = 66% failure)
145
+ const adj = getAdaptiveTierAdjustment("execute-task", "light", ["docs"]);
146
+ assert.equal(adj, "standard");
147
+
148
+ // Without tags, should not bump (base: 16/18 success = 11% failure)
149
+ const adjBase = getAdaptiveTierAdjustment("execute-task", "light");
150
+ assert.equal(adjBase, null);
151
+ } finally {
152
+ cleanup(dir);
153
+ }
51
154
  });
52
155
 
53
- test("routing-history: feedback weight is 2x", () => {
54
- assert.ok(historySrc.includes("FEEDBACK_WEIGHT = 2"), "feedback should count 2x");
156
+ // ─── recordFeedback ──────────────────────────────────────────────────────────
157
+
158
+ test("recordFeedback stores feedback entries", () => {
159
+ const dir = makeTmpDir();
160
+ try {
161
+ initRoutingHistory(dir);
162
+ recordFeedback("execute-task", "M001/S01/T01", "standard", "over");
163
+
164
+ const history = getRoutingHistory();
165
+ assert.ok(history);
166
+ assert.equal(history.feedback.length, 1);
167
+ assert.equal(history.feedback[0].rating, "over");
168
+ assert.equal(history.feedback[0].tier, "standard");
169
+ } finally {
170
+ cleanup(dir);
171
+ }
55
172
  });
56
173
 
57
- // ═══════════════════════════════════════════════════════════════════════════
58
- // Type Structure
59
- // ═══════════════════════════════════════════════════════════════════════════
60
-
61
- test("routing-history: imports ComplexityTier from types.ts", () => {
62
- assert.ok(
63
- historySrc.includes('from "./types.js"') && historySrc.includes("ComplexityTier"),
64
- "should import ComplexityTier from types.ts",
65
- );
66
- });
67
-
68
- test("routing-history: defines RoutingHistoryData interface", () => {
69
- assert.ok(historySrc.includes("interface RoutingHistoryData"), "should define RoutingHistoryData");
174
+ test("recordFeedback 'under' increases failure count at tier", () => {
175
+ const dir = makeTmpDir();
176
+ try {
177
+ initRoutingHistory(dir);
178
+ recordFeedback("execute-task", "M001/S01/T01", "light", "under");
179
+
180
+ const history = getRoutingHistory();
181
+ assert.ok(history);
182
+ // "under" adds 2 (FEEDBACK_WEIGHT) failures
183
+ assert.equal(history.patterns["execute-task"].light.fail, 2);
184
+ } finally {
185
+ cleanup(dir);
186
+ }
70
187
  });
71
188
 
72
- test("routing-history: defines FeedbackEntry interface", () => {
73
- assert.ok(historySrc.includes("interface FeedbackEntry"), "should define FeedbackEntry");
189
+ test("recordFeedback 'over' increases success count at lower tier", () => {
190
+ const dir = makeTmpDir();
191
+ try {
192
+ initRoutingHistory(dir);
193
+ recordFeedback("execute-task", "M001/S01/T01", "standard", "over");
194
+
195
+ const history = getRoutingHistory();
196
+ assert.ok(history);
197
+ // "over" at standard → adds 2 successes at light
198
+ assert.equal(history.patterns["execute-task"].light.success, 2);
199
+ } finally {
200
+ cleanup(dir);
201
+ }
74
202
  });
75
203
 
76
- // ═══════════════════════════════════════════════════════════════════════════
77
- // Persistence
78
- // ═══════════════════════════════════════════════════════════════════════════
79
-
80
- test("routing-history: persists to routing-history.json", () => {
81
- assert.ok(historySrc.includes("routing-history.json"), "should persist to routing-history.json");
204
+ // ─── clearRoutingHistory ─────────────────────────────────────────────────────
205
+
206
+ test("clearRoutingHistory resets all data", () => {
207
+ const dir = makeTmpDir();
208
+ try {
209
+ initRoutingHistory(dir);
210
+ recordOutcome("execute-task", "light", true);
211
+ clearRoutingHistory(dir);
212
+
213
+ const history = getRoutingHistory();
214
+ assert.ok(history);
215
+ assert.deepEqual(history.patterns, {});
216
+ assert.deepEqual(history.feedback, []);
217
+ } finally {
218
+ cleanup(dir);
219
+ }
82
220
  });
83
221
 
84
- test("routing-history: has save and load functions", () => {
85
- assert.ok(historySrc.includes("saveHistory") || historySrc.includes("function save"), "should have save");
86
- assert.ok(historySrc.includes("loadHistory") || historySrc.includes("function load"), "should have load");
222
+ // ─── Persistence ─────────────────────────────────────────────────────────────
223
+
224
+ test("routing history persists to disk and reloads", () => {
225
+ const dir = makeTmpDir();
226
+ try {
227
+ initRoutingHistory(dir);
228
+ recordOutcome("execute-task", "standard", true);
229
+ recordOutcome("execute-task", "standard", true);
230
+ resetRoutingHistory();
231
+
232
+ // Reload from disk
233
+ initRoutingHistory(dir);
234
+ const history = getRoutingHistory();
235
+ assert.ok(history);
236
+ assert.equal(history.patterns["execute-task"].standard.success, 2);
237
+ } finally {
238
+ cleanup(dir);
239
+ }
87
240
  });
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Tests for skill telemetry and skill health (#599).
3
+ * Tests the pure functions — no file I/O, no extension context.
4
+ */
5
+
6
+ import { describe, it, beforeEach } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import type { UnitMetrics } from "../metrics.js";
9
+
10
+ // ─── Test helpers ─────────────────────────────────────────────────────────────
11
+
12
+ function makeUnit(overrides: Partial<UnitMetrics> = {}): UnitMetrics {
13
+ return {
14
+ type: "execute-task",
15
+ id: "M001/S01/T01",
16
+ model: "claude-sonnet-4-20250514",
17
+ startedAt: 1000,
18
+ finishedAt: 2000,
19
+ tokens: { input: 1000, output: 500, cacheRead: 200, cacheWrite: 100, total: 1800 },
20
+ cost: 0.05,
21
+ toolCalls: 3,
22
+ assistantMessages: 5,
23
+ userMessages: 2,
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ // ─── Skill Telemetry ──────────────────────────────────────────────────────────
29
+
30
+ describe("skill-telemetry", () => {
31
+ // Note: captureAvailableSkills/getAndClearSkills depend on filesystem (getAgentDir)
32
+ // so we test the data flow via getSkillLastUsed and detectStaleSkills which are pure
33
+
34
+ it("getSkillLastUsed returns most recent timestamp per skill", async () => {
35
+ const { getSkillLastUsed } = await import("../skill-telemetry.js");
36
+
37
+ const units = [
38
+ makeUnit({ finishedAt: 1000, skills: ["rust-core", "axum-web-framework"] }),
39
+ makeUnit({ finishedAt: 2000, skills: ["rust-core"] }),
40
+ makeUnit({ finishedAt: 3000, skills: ["axum-web-framework"] }),
41
+ ];
42
+
43
+ const result = getSkillLastUsed(units);
44
+ assert.equal(result.get("rust-core"), 2000);
45
+ assert.equal(result.get("axum-web-framework"), 3000);
46
+ });
47
+
48
+ it("getSkillLastUsed returns empty map for units without skills", async () => {
49
+ const { getSkillLastUsed } = await import("../skill-telemetry.js");
50
+
51
+ const units = [makeUnit(), makeUnit()];
52
+ const result = getSkillLastUsed(units);
53
+ assert.equal(result.size, 0);
54
+ });
55
+ });
56
+
57
+ // ─── Skill Health ─────────────────────────────────────────────────────────────
58
+
59
+ describe("skill-health", () => {
60
+ it("buildHealSkillPrompt includes unit ID", async () => {
61
+ const { buildHealSkillPrompt } = await import("../skill-health.js");
62
+ const prompt = buildHealSkillPrompt("M001/S01/T01");
63
+ assert.ok(prompt.includes("M001/S01/T01"));
64
+ assert.ok(prompt.includes("Skill Heal Analysis"));
65
+ assert.ok(prompt.includes("skill-review-queue.md"));
66
+ });
67
+
68
+ it("computeStaleAvoidList excludes already-avoided skills", async () => {
69
+ // This test requires filesystem access for loadLedgerFromDisk
70
+ // so we test the filtering logic conceptually
71
+ const { computeStaleAvoidList } = await import("../skill-health.js");
72
+
73
+ // With no metrics file, should return empty
74
+ const result = computeStaleAvoidList("/nonexistent/path", ["some-skill"]);
75
+ assert.ok(Array.isArray(result));
76
+ });
77
+ });
78
+
79
+ // ─── UnitMetrics skills field ─────────────────────────────────────────────────
80
+
81
+ describe("UnitMetrics skills field", () => {
82
+ it("skills field is optional and accepts string array", () => {
83
+ const unit = makeUnit({ skills: ["rust-core", "axum-web-framework"] });
84
+ assert.deepEqual(unit.skills, ["rust-core", "axum-web-framework"]);
85
+ });
86
+
87
+ it("skills field is undefined when not provided", () => {
88
+ const unit = makeUnit();
89
+ assert.equal(unit.skills, undefined);
90
+ });
91
+ });
92
+
93
+ // ─── Preferences ──────────────────────────────────────────────────────────────
94
+
95
+ describe("skill_staleness_days preference", () => {
96
+ it("validates valid staleness days", async () => {
97
+ const { validatePreferences } = await import("../preferences.js");
98
+
99
+ const result = validatePreferences({ skill_staleness_days: 30 });
100
+ assert.equal(result.preferences.skill_staleness_days, 30);
101
+ assert.equal(result.errors.length, 0);
102
+ });
103
+
104
+ it("validates zero (disabled) staleness days", async () => {
105
+ const { validatePreferences } = await import("../preferences.js");
106
+
107
+ const result = validatePreferences({ skill_staleness_days: 0 });
108
+ assert.equal(result.preferences.skill_staleness_days, 0);
109
+ assert.equal(result.errors.length, 0);
110
+ });
111
+
112
+ it("rejects negative staleness days", async () => {
113
+ const { validatePreferences } = await import("../preferences.js");
114
+
115
+ const result = validatePreferences({ skill_staleness_days: -5 });
116
+ assert.equal(result.preferences.skill_staleness_days, undefined);
117
+ assert.ok(result.errors.some(e => e.includes("skill_staleness_days")));
118
+ });
119
+
120
+ it("floors fractional days", async () => {
121
+ const { validatePreferences } = await import("../preferences.js");
122
+
123
+ const result = validatePreferences({ skill_staleness_days: 30.7 });
124
+ assert.equal(result.preferences.skill_staleness_days, 30);
125
+ });
126
+ });
@@ -4,7 +4,7 @@ import { mkdirSync, rmSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { randomUUID } from "node:crypto";
7
- import { fork } from "node:child_process";
7
+ import { spawn, type ChildProcess } from "node:child_process";
8
8
 
9
9
  import { writeFileSync } from "node:fs";
10
10
  import {
@@ -25,6 +25,27 @@ function cleanup(base: string): void {
25
25
  try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
26
26
  }
27
27
 
28
+ function waitForChildExit(child: ChildProcess, timeoutMs = 5000): Promise<number | null> {
29
+ return new Promise((resolve) => {
30
+ if (child.exitCode !== null) {
31
+ resolve(child.exitCode);
32
+ return;
33
+ }
34
+
35
+ const timeout = setTimeout(() => {
36
+ child.off("exit", onExit);
37
+ resolve(child.exitCode);
38
+ }, timeoutMs);
39
+
40
+ const onExit = (code: number | null) => {
41
+ clearTimeout(timeout);
42
+ resolve(code);
43
+ };
44
+
45
+ child.once("exit", onExit);
46
+ });
47
+ }
48
+
28
49
  // ─── stopAutoRemote ──────────────────────────────────────────────────────
29
50
 
30
51
  test("stopAutoRemote returns found:false when no lock file exists", () => {
@@ -63,12 +84,16 @@ test("stopAutoRemote sends SIGTERM to a live process and returns found:true", as
63
84
  const base = makeTmpBase();
64
85
 
65
86
  // Spawn a child process that sleeps, acting as a fake auto-mode session
66
- const child = fork(
67
- "-e",
68
- ["process.on('SIGTERM', () => process.exit(0)); setTimeout(() => process.exit(1), 30000);"],
87
+ const child = spawn(
88
+ process.execPath,
89
+ ["-e", "process.on('SIGTERM', () => process.exit(0)); setTimeout(() => process.exit(1), 30000);"],
69
90
  { stdio: "ignore", detached: false },
70
91
  );
71
92
 
93
+ if (!child.pid) {
94
+ throw new Error("failed to spawn child process for stopAutoRemote test");
95
+ }
96
+
72
97
  try {
73
98
  // Wait for child to be ready
74
99
  await new Promise((resolve) => setTimeout(resolve, 200));
@@ -84,15 +109,13 @@ test("stopAutoRemote sends SIGTERM to a live process and returns found:true", as
84
109
  };
85
110
  writeFileSync(join(base, ".gsd", "auto.lock"), JSON.stringify(lockData, null, 2), "utf-8");
86
111
 
112
+ const exitPromise = waitForChildExit(child);
87
113
  const result = stopAutoRemote(base);
88
114
  assert.equal(result.found, true, "should find running auto-mode");
89
115
  assert.equal(result.pid, child.pid, "should return the PID");
90
116
 
91
117
  // Wait for child to exit (it should receive SIGTERM)
92
- const exitCode = await new Promise<number | null>((resolve) => {
93
- child.on("exit", (code) => resolve(code));
94
- setTimeout(() => resolve(null), 5000);
95
- });
118
+ const exitCode = await exitPromise;
96
119
  // On Windows, SIGTERM is not interceptable — the process exits with code 1
97
120
  // rather than running the handler. Accept either clean exit (0) or forced (1).
98
121
  assert.ok(exitCode !== null, "child should have exited after SIGTERM");