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,185 @@
1
+ // Debug Logger Tests
2
+
3
+ import { test } from 'node:test';
4
+ import assert from 'node:assert';
5
+ import { mkdtempSync, mkdirSync, readFileSync, existsSync, writeFileSync, readdirSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { tmpdir } from 'node:os';
8
+
9
+ import {
10
+ enableDebug,
11
+ disableDebug,
12
+ isDebugEnabled,
13
+ getDebugLogPath,
14
+ debugLog,
15
+ debugTime,
16
+ debugCount,
17
+ debugPeak,
18
+ writeDebugSummary,
19
+ } from '../debug-logger.ts';
20
+
21
+ function createTempGsdDir(): string {
22
+ const tmp = mkdtempSync(join(tmpdir(), 'gsd-debug-test-'));
23
+ mkdirSync(join(tmp, '.gsd'), { recursive: true });
24
+ return tmp;
25
+ }
26
+
27
+ function readLogLines(logPath: string): Record<string, unknown>[] {
28
+ const content = readFileSync(logPath, 'utf-8').trim();
29
+ if (!content) return [];
30
+ return content.split('\n').map(line => JSON.parse(line));
31
+ }
32
+
33
+ test('enableDebug creates log file and sets enabled', () => {
34
+ const tmp = createTempGsdDir();
35
+ enableDebug(tmp);
36
+
37
+ assert.strictEqual(isDebugEnabled(), true);
38
+ const logPath = getDebugLogPath();
39
+ assert.ok(logPath, 'log path should be set');
40
+ // Normalize path separators for Windows compatibility
41
+ const normalized = logPath!.replace(/\\/g, '/');
42
+ assert.ok(normalized.includes('.gsd/debug/debug-'), 'log path should be in .gsd/debug/');
43
+ assert.ok(logPath!.endsWith('.log'), 'log path should end with .log');
44
+
45
+ disableDebug();
46
+ assert.strictEqual(isDebugEnabled(), false);
47
+ });
48
+
49
+ test('debugLog writes JSONL events', () => {
50
+ const tmp = createTempGsdDir();
51
+ enableDebug(tmp);
52
+
53
+ debugLog('test-event', { foo: 'bar', num: 42 });
54
+ debugLog('another-event');
55
+
56
+ const logPath = getDebugLogPath()!;
57
+ const lines = readLogLines(logPath);
58
+
59
+ assert.strictEqual(lines.length, 2);
60
+ assert.strictEqual(lines[0].event, 'test-event');
61
+ assert.strictEqual((lines[0] as any).foo, 'bar');
62
+ assert.strictEqual((lines[0] as any).num, 42);
63
+ assert.ok(lines[0].ts, 'should have timestamp');
64
+ assert.strictEqual(lines[1].event, 'another-event');
65
+
66
+ disableDebug();
67
+ });
68
+
69
+ test('debugLog is no-op when disabled', () => {
70
+ assert.strictEqual(isDebugEnabled(), false);
71
+ // Should not throw
72
+ debugLog('should-not-appear', { data: 'test' });
73
+ });
74
+
75
+ test('debugTime measures elapsed time', async () => {
76
+ const tmp = createTempGsdDir();
77
+ enableDebug(tmp);
78
+
79
+ const stop = debugTime('timed-op');
80
+ // Small delay to ensure measurable time
81
+ await new Promise(r => setTimeout(r, 10));
82
+ stop({ extra: 'data' });
83
+
84
+ const logPath = getDebugLogPath()!;
85
+ const lines = readLogLines(logPath);
86
+
87
+ assert.strictEqual(lines.length, 1);
88
+ assert.strictEqual(lines[0].event, 'timed-op');
89
+ assert.ok((lines[0] as any).elapsed_ms >= 0, 'elapsed_ms should be non-negative');
90
+ assert.strictEqual((lines[0] as any).extra, 'data');
91
+
92
+ disableDebug();
93
+ });
94
+
95
+ test('debugTime returns no-op when disabled', () => {
96
+ assert.strictEqual(isDebugEnabled(), false);
97
+ const stop = debugTime('should-not-appear');
98
+ stop({ data: 'test' }); // Should not throw
99
+ });
100
+
101
+ test('debugCount increments counters', () => {
102
+ const tmp = createTempGsdDir();
103
+ enableDebug(tmp);
104
+
105
+ debugCount('dispatches');
106
+ debugCount('dispatches');
107
+ debugCount('dispatches', 3);
108
+
109
+ // Counters are tested via writeDebugSummary
110
+ const logPath = writeDebugSummary()!;
111
+ const lines = readLogLines(logPath);
112
+
113
+ const summary = lines.find(l => l.event === 'debug-summary') as any;
114
+ assert.ok(summary, 'should have debug-summary event');
115
+ assert.strictEqual(summary.dispatches, 5);
116
+ });
117
+
118
+ test('debugPeak tracks max values', () => {
119
+ const tmp = createTempGsdDir();
120
+ enableDebug(tmp);
121
+
122
+ debugPeak('ttsrPeakBuffer', 100);
123
+ debugPeak('ttsrPeakBuffer', 500);
124
+ debugPeak('ttsrPeakBuffer', 200); // Should not overwrite 500
125
+
126
+ const logPath = writeDebugSummary()!;
127
+ const lines = readLogLines(logPath);
128
+
129
+ const summary = lines.find(l => l.event === 'debug-summary') as any;
130
+ assert.strictEqual(summary.ttsrPeakBuffer, 500);
131
+ });
132
+
133
+ test('writeDebugSummary includes all counters and disables debug', () => {
134
+ const tmp = createTempGsdDir();
135
+ enableDebug(tmp);
136
+
137
+ debugCount('deriveStateCalls', 10);
138
+ debugCount('deriveStateTotalMs', 80);
139
+ debugCount('ttsrChecks', 50);
140
+ debugCount('parseRoadmapCalls', 3);
141
+ debugCount('dispatches', 2);
142
+
143
+ const logPath = writeDebugSummary()!;
144
+ assert.ok(logPath, 'should return log path');
145
+ assert.strictEqual(isDebugEnabled(), false, 'should be disabled after summary');
146
+
147
+ const lines = readLogLines(logPath);
148
+ const summary = lines.find(l => l.event === 'debug-summary') as any;
149
+ assert.ok(summary);
150
+ assert.strictEqual(summary.deriveStateCalls, 10);
151
+ assert.strictEqual(summary.avgDeriveState_ms, 8);
152
+ assert.strictEqual(summary.ttsrChecks, 50);
153
+ assert.strictEqual(summary.dispatches, 2);
154
+ assert.ok(summary.totalElapsed_ms >= 0);
155
+ });
156
+
157
+ test('auto-prunes old debug logs', () => {
158
+ const tmp = createTempGsdDir();
159
+ const debugDir = join(tmp, '.gsd', 'debug');
160
+ mkdirSync(debugDir, { recursive: true });
161
+
162
+ // Create 6 old log files
163
+ for (let i = 0; i < 6; i++) {
164
+ writeFileSync(join(debugDir, `debug-2026-01-0${i + 1}.log`), 'old');
165
+ }
166
+
167
+ enableDebug(tmp);
168
+
169
+ const files = readdirSync(debugDir).filter(f => f.startsWith('debug-') && f.endsWith('.log'));
170
+ // Should have at most MAX_DEBUG_LOGS (5) = 5 old + 1 new, but pruned to 5 total
171
+ // Actually: prunes to < 5 old, then creates 1 new = at most 5
172
+ assert.ok(files.length <= 6, `should have pruned old logs, got ${files.length}`);
173
+
174
+ disableDebug();
175
+ });
176
+
177
+ test('disableDebug returns log path', () => {
178
+ const tmp = createTempGsdDir();
179
+ enableDebug(tmp);
180
+
181
+ const logPath = getDebugLogPath();
182
+ const returned = disableDebug();
183
+ assert.strictEqual(returned, logPath);
184
+ assert.strictEqual(getDebugLogPath(), null);
185
+ });
@@ -0,0 +1,406 @@
1
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+
5
+ import { deriveState, invalidateStateCache } from '../state.ts';
6
+ import { openDatabase, closeDatabase, insertArtifact, isDbAvailable } from '../gsd-db.ts';
7
+ import { createTestContext } from './test-helpers.ts';
8
+
9
+ const { assertEq, assertTrue, report } = createTestContext();
10
+
11
+ // ─── Fixture Helpers ───────────────────────────────────────────────────────
12
+
13
+ function createFixtureBase(): string {
14
+ const base = mkdtempSync(join(tmpdir(), 'gsd-derive-db-'));
15
+ mkdirSync(join(base, '.gsd', 'milestones'), { recursive: true });
16
+ return base;
17
+ }
18
+
19
+ function writeFile(base: string, relativePath: string, content: string): void {
20
+ const full = join(base, '.gsd', relativePath);
21
+ mkdirSync(join(full, '..'), { recursive: true });
22
+ writeFileSync(full, content);
23
+ }
24
+
25
+ function insertArtifactRow(relativePath: string, content: string, opts?: {
26
+ artifact_type?: string;
27
+ milestone_id?: string | null;
28
+ slice_id?: string | null;
29
+ task_id?: string | null;
30
+ }): void {
31
+ insertArtifact({
32
+ path: relativePath,
33
+ artifact_type: opts?.artifact_type ?? 'planning',
34
+ milestone_id: opts?.milestone_id ?? null,
35
+ slice_id: opts?.slice_id ?? null,
36
+ task_id: opts?.task_id ?? null,
37
+ full_content: content,
38
+ });
39
+ }
40
+
41
+ function cleanup(base: string): void {
42
+ rmSync(base, { recursive: true, force: true });
43
+ }
44
+
45
+ // ═══════════════════════════════════════════════════════════════════════════
46
+ // Test Groups
47
+ // ═══════════════════════════════════════════════════════════════════════════
48
+
49
+ const ROADMAP_CONTENT = `# M001: Test Milestone
50
+
51
+ **Vision:** Test DB-backed derive state.
52
+
53
+ ## Slices
54
+
55
+ - [ ] **S01: First Slice** \`risk:low\` \`depends:[]\`
56
+ > After this: Slice done.
57
+
58
+ - [ ] **S02: Second Slice** \`risk:low\` \`depends:[S01]\`
59
+ > After this: All done.
60
+ `;
61
+
62
+ const PLAN_CONTENT = `# S01: First Slice
63
+
64
+ **Goal:** Test executing.
65
+ **Demo:** Tests pass.
66
+
67
+ ## Tasks
68
+
69
+ - [ ] **T01: First Task** \`est:10m\`
70
+ First task description.
71
+
72
+ - [x] **T02: Done Task** \`est:10m\`
73
+ Already done.
74
+ `;
75
+
76
+ const REQUIREMENTS_CONTENT = `# Requirements
77
+
78
+ ## Active
79
+
80
+ ### R001 — First Requirement
81
+ - Status: active
82
+ - Description: Something active.
83
+
84
+ ### R002 — Second Requirement
85
+ - Status: active
86
+ - Description: Another active.
87
+
88
+ ## Validated
89
+
90
+ ### R003 — Validated
91
+ - Status: validated
92
+ - Description: Already validated.
93
+ `;
94
+
95
+ async function main(): Promise<void> {
96
+
97
+ // ─── Test 1: DB-backed deriveState produces identical GSDState ─────────
98
+ console.log('\n=== derive-state-db: DB path matches file path ===');
99
+ {
100
+ const base = createFixtureBase();
101
+ try {
102
+ // Write files to disk (for file-only path)
103
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
104
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
105
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
106
+ writeFile(base, 'REQUIREMENTS.md', REQUIREMENTS_CONTENT);
107
+
108
+ // Derive state from files only (no DB)
109
+ invalidateStateCache();
110
+ const fileState = await deriveState(base);
111
+
112
+ // Now open DB, insert matching artifacts
113
+ openDatabase(':memory:');
114
+ assertTrue(isDbAvailable(), 'db-match: DB is available after open');
115
+
116
+ insertArtifactRow('milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT, {
117
+ artifact_type: 'roadmap',
118
+ milestone_id: 'M001',
119
+ });
120
+ insertArtifactRow('milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT, {
121
+ artifact_type: 'plan',
122
+ milestone_id: 'M001',
123
+ slice_id: 'S01',
124
+ });
125
+ insertArtifactRow('REQUIREMENTS.md', REQUIREMENTS_CONTENT, {
126
+ artifact_type: 'requirements',
127
+ });
128
+
129
+ // Derive state from DB
130
+ invalidateStateCache();
131
+ const dbState = await deriveState(base);
132
+
133
+ // Field-by-field equality
134
+ assertEq(dbState.phase, fileState.phase, 'db-match: phase matches');
135
+ assertEq(dbState.activeMilestone?.id, fileState.activeMilestone?.id, 'db-match: activeMilestone.id matches');
136
+ assertEq(dbState.activeMilestone?.title, fileState.activeMilestone?.title, 'db-match: activeMilestone.title matches');
137
+ assertEq(dbState.activeSlice?.id, fileState.activeSlice?.id, 'db-match: activeSlice.id matches');
138
+ assertEq(dbState.activeSlice?.title, fileState.activeSlice?.title, 'db-match: activeSlice.title matches');
139
+ assertEq(dbState.activeTask?.id, fileState.activeTask?.id, 'db-match: activeTask.id matches');
140
+ assertEq(dbState.activeTask?.title, fileState.activeTask?.title, 'db-match: activeTask.title matches');
141
+ assertEq(dbState.blockers, fileState.blockers, 'db-match: blockers match');
142
+ assertEq(dbState.registry.length, fileState.registry.length, 'db-match: registry length matches');
143
+ assertEq(dbState.registry[0]?.status, fileState.registry[0]?.status, 'db-match: registry[0] status matches');
144
+ assertEq(dbState.requirements?.active, fileState.requirements?.active, 'db-match: requirements.active matches');
145
+ assertEq(dbState.requirements?.validated, fileState.requirements?.validated, 'db-match: requirements.validated matches');
146
+ assertEq(dbState.requirements?.total, fileState.requirements?.total, 'db-match: requirements.total matches');
147
+ assertEq(dbState.progress?.milestones?.done, fileState.progress?.milestones?.done, 'db-match: milestones.done matches');
148
+ assertEq(dbState.progress?.milestones?.total, fileState.progress?.milestones?.total, 'db-match: milestones.total matches');
149
+ assertEq(dbState.progress?.slices?.done, fileState.progress?.slices?.done, 'db-match: slices.done matches');
150
+ assertEq(dbState.progress?.slices?.total, fileState.progress?.slices?.total, 'db-match: slices.total matches');
151
+ assertEq(dbState.progress?.tasks?.done, fileState.progress?.tasks?.done, 'db-match: tasks.done matches');
152
+ assertEq(dbState.progress?.tasks?.total, fileState.progress?.tasks?.total, 'db-match: tasks.total matches');
153
+
154
+ closeDatabase();
155
+ } finally {
156
+ closeDatabase();
157
+ cleanup(base);
158
+ }
159
+ }
160
+
161
+ // ─── Test 2: Fallback when DB unavailable ─────────────────────────────
162
+ console.log('\n=== derive-state-db: fallback when DB unavailable ===');
163
+ {
164
+ const base = createFixtureBase();
165
+ try {
166
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
167
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
168
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
169
+
170
+ // No DB open — isDbAvailable() is false
171
+ assertTrue(!isDbAvailable(), 'fallback: DB is not available');
172
+ invalidateStateCache();
173
+ const state = await deriveState(base);
174
+
175
+ assertEq(state.phase, 'executing', 'fallback: phase is executing');
176
+ assertEq(state.activeMilestone?.id, 'M001', 'fallback: activeMilestone is M001');
177
+ assertEq(state.activeSlice?.id, 'S01', 'fallback: activeSlice is S01');
178
+ assertEq(state.activeTask?.id, 'T01', 'fallback: activeTask is T01');
179
+ } finally {
180
+ cleanup(base);
181
+ }
182
+ }
183
+
184
+ // ─── Test 3: Empty DB falls back to file reads ────────────────────────
185
+ console.log('\n=== derive-state-db: empty DB falls back to files ===');
186
+ {
187
+ const base = createFixtureBase();
188
+ try {
189
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
190
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
191
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
192
+
193
+ // Open DB but insert nothing — empty artifacts table
194
+ openDatabase(':memory:');
195
+ assertTrue(isDbAvailable(), 'empty-db: DB is available');
196
+
197
+ invalidateStateCache();
198
+ const state = await deriveState(base);
199
+
200
+ // Should still work via cachedLoadFile → loadFile disk fallback
201
+ assertEq(state.phase, 'executing', 'empty-db: phase is executing');
202
+ assertEq(state.activeMilestone?.id, 'M001', 'empty-db: activeMilestone is M001');
203
+ assertEq(state.activeSlice?.id, 'S01', 'empty-db: activeSlice is S01');
204
+ assertEq(state.activeTask?.id, 'T01', 'empty-db: activeTask is T01');
205
+
206
+ closeDatabase();
207
+ } finally {
208
+ closeDatabase();
209
+ cleanup(base);
210
+ }
211
+ }
212
+
213
+ // ─── Test 4: Partial DB content fills gaps from disk ──────────────────
214
+ console.log('\n=== derive-state-db: partial DB fills gaps from disk ===');
215
+ {
216
+ const base = createFixtureBase();
217
+ try {
218
+ // Write all files to disk
219
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
220
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
221
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
222
+ writeFile(base, 'REQUIREMENTS.md', REQUIREMENTS_CONTENT);
223
+
224
+ // Open DB but only insert the roadmap — plan and requirements missing from DB
225
+ openDatabase(':memory:');
226
+ insertArtifactRow('milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT, {
227
+ artifact_type: 'roadmap',
228
+ milestone_id: 'M001',
229
+ });
230
+
231
+ invalidateStateCache();
232
+ const state = await deriveState(base);
233
+
234
+ // Should work: roadmap from DB, plan from disk fallback
235
+ assertEq(state.phase, 'executing', 'partial-db: phase is executing');
236
+ assertEq(state.activeMilestone?.id, 'M001', 'partial-db: activeMilestone is M001');
237
+ assertEq(state.activeSlice?.id, 'S01', 'partial-db: activeSlice is S01');
238
+ assertEq(state.activeTask?.id, 'T01', 'partial-db: activeTask is T01');
239
+ // Requirements loaded from disk fallback
240
+ assertEq(state.requirements?.active, 2, 'partial-db: requirements.active from disk');
241
+ assertEq(state.requirements?.validated, 1, 'partial-db: requirements.validated from disk');
242
+ assertEq(state.requirements?.total, 3, 'partial-db: requirements.total from disk');
243
+
244
+ closeDatabase();
245
+ } finally {
246
+ closeDatabase();
247
+ cleanup(base);
248
+ }
249
+ }
250
+
251
+ // ─── Test 5: Requirements counting from DB content ────────────────────
252
+ console.log('\n=== derive-state-db: requirements from DB content ===');
253
+ {
254
+ const base = createFixtureBase();
255
+ try {
256
+ // Write minimal milestone dir (needed for milestone discovery)
257
+ mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
258
+ // Do NOT write REQUIREMENTS.md to disk — only in DB
259
+
260
+ openDatabase(':memory:');
261
+ insertArtifactRow('REQUIREMENTS.md', REQUIREMENTS_CONTENT, {
262
+ artifact_type: 'requirements',
263
+ });
264
+
265
+ invalidateStateCache();
266
+ const state = await deriveState(base);
267
+
268
+ // Requirements should come from DB
269
+ assertEq(state.requirements?.active, 2, 'req-from-db: requirements.active = 2');
270
+ assertEq(state.requirements?.validated, 1, 'req-from-db: requirements.validated = 1');
271
+ assertEq(state.requirements?.total, 3, 'req-from-db: requirements.total = 3');
272
+
273
+ closeDatabase();
274
+ } finally {
275
+ closeDatabase();
276
+ cleanup(base);
277
+ }
278
+ }
279
+
280
+ // ─── Test 6: DB content with multi-milestone registry ─────────────────
281
+ console.log('\n=== derive-state-db: multi-milestone from DB ===');
282
+ {
283
+ const base = createFixtureBase();
284
+
285
+ const completedRoadmap = `# M001: First Milestone
286
+
287
+ **Vision:** Already done.
288
+
289
+ ## Slices
290
+
291
+ - [x] **S01: Done** \`risk:low\` \`depends:[]\`
292
+ > After this: Done.
293
+ `;
294
+ const summaryContent = `# M001 Summary\n\nFirst milestone complete.`;
295
+
296
+ const activeRoadmap = `# M002: Second Milestone
297
+
298
+ **Vision:** Currently active.
299
+
300
+ ## Slices
301
+
302
+ - [ ] **S01: In Progress** \`risk:low\` \`depends:[]\`
303
+ > After this: Done.
304
+ `;
305
+
306
+ try {
307
+ // Create milestone dirs on disk (needed for directory scanning)
308
+ // Also write roadmap files to disk — resolveMilestoneFile checks file existence
309
+ // The DB only provides content, not file discovery
310
+ mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
311
+ mkdirSync(join(base, '.gsd', 'milestones', 'M002'), { recursive: true });
312
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', completedRoadmap);
313
+ writeFile(base, 'milestones/M001/M001-SUMMARY.md', summaryContent);
314
+ writeFile(base, 'milestones/M002/M002-ROADMAP.md', activeRoadmap);
315
+
316
+ // Put roadmap content in DB only
317
+ openDatabase(':memory:');
318
+ insertArtifactRow('milestones/M001/M001-ROADMAP.md', completedRoadmap, {
319
+ artifact_type: 'roadmap',
320
+ milestone_id: 'M001',
321
+ });
322
+ insertArtifactRow('milestones/M001/M001-SUMMARY.md', summaryContent, {
323
+ artifact_type: 'summary',
324
+ milestone_id: 'M001',
325
+ });
326
+ insertArtifactRow('milestones/M002/M002-ROADMAP.md', activeRoadmap, {
327
+ artifact_type: 'roadmap',
328
+ milestone_id: 'M002',
329
+ });
330
+
331
+ invalidateStateCache();
332
+ const state = await deriveState(base);
333
+
334
+ assertEq(state.registry.length, 2, 'multi-ms-db: registry has 2 entries');
335
+ assertEq(state.registry[0]?.id, 'M001', 'multi-ms-db: registry[0] is M001');
336
+ assertEq(state.registry[0]?.status, 'complete', 'multi-ms-db: M001 is complete');
337
+ assertEq(state.registry[1]?.id, 'M002', 'multi-ms-db: registry[1] is M002');
338
+ assertEq(state.registry[1]?.status, 'active', 'multi-ms-db: M002 is active');
339
+ assertEq(state.activeMilestone?.id, 'M002', 'multi-ms-db: activeMilestone is M002');
340
+ assertEq(state.phase, 'planning', 'multi-ms-db: phase is planning (no plan for S01)');
341
+
342
+ closeDatabase();
343
+ } finally {
344
+ closeDatabase();
345
+ cleanup(base);
346
+ }
347
+ }
348
+
349
+ // ─── Test 7: Cache invalidation works for DB path ─────────────────────
350
+ console.log('\n=== derive-state-db: cache invalidation ===');
351
+ {
352
+ const base = createFixtureBase();
353
+ try {
354
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
355
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
356
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
357
+
358
+ openDatabase(':memory:');
359
+ insertArtifactRow('milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT, {
360
+ artifact_type: 'roadmap',
361
+ milestone_id: 'M001',
362
+ });
363
+ insertArtifactRow('milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT, {
364
+ artifact_type: 'plan',
365
+ milestone_id: 'M001',
366
+ slice_id: 'S01',
367
+ });
368
+
369
+ invalidateStateCache();
370
+ const state1 = await deriveState(base);
371
+ assertEq(state1.activeTask?.id, 'T01', 'cache-inv: first call gets T01');
372
+
373
+ // Simulate task completion by updating the plan in DB
374
+ const updatedPlan = PLAN_CONTENT.replace('- [ ] **T01:', '- [x] **T01:');
375
+ insertArtifactRow('milestones/M001/slices/S01/S01-PLAN.md', updatedPlan, {
376
+ artifact_type: 'plan',
377
+ milestone_id: 'M001',
378
+ slice_id: 'S01',
379
+ });
380
+ // Also update file on disk (cachedLoadFile may read from disk for some paths)
381
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', updatedPlan);
382
+
383
+ // Without invalidation, should return cached result (T01 still active)
384
+ const state2 = await deriveState(base);
385
+ assertEq(state2.activeTask?.id, 'T01', 'cache-inv: cached result still has T01');
386
+
387
+ // After invalidation, should pick up updated content
388
+ invalidateStateCache();
389
+ const state3 = await deriveState(base);
390
+ assertEq(state3.phase, 'summarizing', 'cache-inv: after invalidation, phase is summarizing (all tasks done)');
391
+ assertEq(state3.activeTask, null, 'cache-inv: activeTask is null after all done');
392
+
393
+ closeDatabase();
394
+ } finally {
395
+ closeDatabase();
396
+ cleanup(base);
397
+ }
398
+ }
399
+
400
+ report();
401
+ }
402
+
403
+ main().catch((error) => {
404
+ console.error(error);
405
+ process.exit(1);
406
+ });
@@ -1,5 +1,4 @@
1
1
  // GSD Dispatch Guard Tests
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
2
 
4
3
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5
4
  import { join } from "node:path";
@@ -0,0 +1,22 @@
1
+ const ROOT = new URL("../../../../../", import.meta.url);
2
+
3
+ export function resolve(specifier, context, nextResolve) {
4
+ // 1. Direct redirects to dist/ for specific packages
5
+ if (specifier === "../../packages/pi-coding-agent/src/index.js") {
6
+ specifier = new URL("packages/pi-coding-agent/dist/index.js", ROOT).href;
7
+ } else if (specifier === "@gsd/pi-ai/oauth") {
8
+ specifier = new URL("packages/pi-ai/dist/utils/oauth/index.js", ROOT).href;
9
+ } else if (specifier === "@gsd/pi-ai") {
10
+ specifier = new URL("packages/pi-ai/dist/index.js", ROOT).href;
11
+ } else if (specifier === "@gsd/pi-agent-core") {
12
+ specifier = new URL("packages/pi-agent-core/dist/index.js", ROOT).href;
13
+ }
14
+ // 2. Mapping .js to .ts for local imports when running tests from src/
15
+ else if (specifier.endsWith('.js') && (specifier.startsWith('./') || specifier.startsWith('../'))) {
16
+ if (context.parentURL && context.parentURL.includes('/src/')) {
17
+ specifier = specifier.replace(/\.js$/, '.ts');
18
+ }
19
+ }
20
+
21
+ return nextResolve(specifier, context);
22
+ }