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,417 @@
1
+ /**
2
+ * GSD Skill Health — Dashboard, Staleness, and Heal-Skill Integration (#599)
3
+ *
4
+ * Aggregates skill telemetry from metrics.json to surface:
5
+ * - Per-skill pass/fail rates, token usage, and trends
6
+ * - Staleness warnings for unused skills
7
+ * - Declining performance flags
8
+ * - Heal-skill suggestions (inspired by glittercowboy's heal-skill command)
9
+ *
10
+ * The heal-skill concept: when an agent deviates from what a skill recommends
11
+ * during execution, detect the drift and propose specific fixes with user
12
+ * approval before applying. This closes the feedback loop that SkillsBench
13
+ * research identified as critical for skill quality.
14
+ */
15
+
16
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { getAgentDir } from "@gsd/pi-coding-agent";
19
+ import type { UnitMetrics, MetricsLedger } from "./metrics.js";
20
+ import { formatCost, formatTokenCount, loadLedgerFromDisk } from "./metrics.js";
21
+ import { getSkillLastUsed, detectStaleSkills } from "./skill-telemetry.js";
22
+
23
+ // ─── Types ────────────────────────────────────────────────────────────────────
24
+
25
+ export interface SkillHealthEntry {
26
+ name: string;
27
+ totalUses: number;
28
+ /** Success rate: units with this skill that completed without retry */
29
+ successRate: number;
30
+ /** Average tokens per unit when this skill is loaded */
31
+ avgTokens: number;
32
+ /** Token trend over recent uses */
33
+ tokenTrend: "stable" | "rising" | "declining";
34
+ /** Timestamp of most recent use */
35
+ lastUsed: number;
36
+ /** Days since last use */
37
+ staleDays: number;
38
+ /** Average cost per unit when this skill is loaded */
39
+ avgCost: number;
40
+ /** Whether this skill is flagged for review */
41
+ flagged: boolean;
42
+ /** Reason for flag, if any */
43
+ flagReason?: string;
44
+ }
45
+
46
+ export interface SkillHealthReport {
47
+ generatedAt: string;
48
+ totalUnitsWithSkills: number;
49
+ skills: SkillHealthEntry[];
50
+ staleSkills: string[];
51
+ decliningSkills: string[];
52
+ suggestions: SkillHealSuggestion[];
53
+ }
54
+
55
+ export interface SkillHealSuggestion {
56
+ skillName: string;
57
+ trigger: "declining_success" | "rising_tokens" | "high_retry_rate" | "stale";
58
+ message: string;
59
+ severity: "info" | "warning" | "critical";
60
+ }
61
+
62
+ // ─── Constants ────────────────────────────────────────────────────────────────
63
+
64
+ /** Default staleness threshold in days */
65
+ const DEFAULT_STALE_DAYS = 60;
66
+
67
+ /** Success rate below this triggers a flag */
68
+ const SUCCESS_RATE_THRESHOLD = 0.70;
69
+
70
+ /** Token increase percentage that triggers a "rising" flag */
71
+ const TOKEN_RISE_THRESHOLD = 0.20;
72
+
73
+ /** Minimum uses before trend analysis kicks in */
74
+ const MIN_USES_FOR_TREND = 5;
75
+
76
+ /** Window size for trend comparison (compare last N to previous N) */
77
+ const TREND_WINDOW = 5;
78
+
79
+ // ─── Public API ───────────────────────────────────────────────────────────────
80
+
81
+ /**
82
+ * Generate a full skill health report from metrics data.
83
+ */
84
+ export function generateSkillHealthReport(basePath: string, staleDays?: number): SkillHealthReport {
85
+ const ledger = loadLedgerFromDisk(basePath);
86
+ const unitsWithSkills = (ledger?.units ?? []).filter(u => u.skills && u.skills.length > 0);
87
+ const threshold = staleDays ?? DEFAULT_STALE_DAYS;
88
+
89
+ const skillMap = aggregateBySkill(unitsWithSkills);
90
+ const skills = Array.from(skillMap.values()).sort((a, b) => b.totalUses - a.totalUses);
91
+ const staleSkills = detectStaleSkills(unitsWithSkills, threshold);
92
+ const decliningSkills = skills.filter(s => s.flagged).map(s => s.name);
93
+ const suggestions = generateSuggestions(skills, staleSkills);
94
+
95
+ return {
96
+ generatedAt: new Date().toISOString(),
97
+ totalUnitsWithSkills: unitsWithSkills.length,
98
+ skills,
99
+ staleSkills,
100
+ decliningSkills,
101
+ suggestions,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Format a skill health report for terminal display.
107
+ */
108
+ export function formatSkillHealthReport(report: SkillHealthReport): string {
109
+ const lines: string[] = [];
110
+
111
+ lines.push("Skill Health Report");
112
+ lines.push("═".repeat(60));
113
+ lines.push(`Generated: ${report.generatedAt}`);
114
+ lines.push(`Units with skill data: ${report.totalUnitsWithSkills}`);
115
+ lines.push("");
116
+
117
+ if (report.skills.length === 0) {
118
+ lines.push("No skill telemetry data yet. Run auto-mode to start collecting.");
119
+ lines.push("Skill usage is recorded per-unit in metrics.json.");
120
+ return lines.join("\n");
121
+ }
122
+
123
+ // Main table
124
+ lines.push("Skill Uses Success% Avg Tokens Trend Last Used");
125
+ lines.push("─".repeat(80));
126
+
127
+ for (const s of report.skills) {
128
+ const name = s.name.padEnd(24).slice(0, 24);
129
+ const uses = String(s.totalUses).padStart(5);
130
+ const success = `${Math.round(s.successRate * 100)}%`.padStart(8);
131
+ const tokens = formatTokenCount(s.avgTokens).padStart(11);
132
+ const trend = s.tokenTrend.padEnd(10);
133
+ const lastUsed = s.staleDays === 0 ? "today" :
134
+ s.staleDays === 1 ? "1 day ago" :
135
+ `${s.staleDays} days ago`;
136
+ const flag = s.flagged ? " ⚠" : "";
137
+ lines.push(`${name}${uses}${success}${tokens} ${trend}${lastUsed}${flag}`);
138
+ }
139
+
140
+ // Stale skills
141
+ if (report.staleSkills.length > 0) {
142
+ lines.push("");
143
+ lines.push("Stale Skills (unused for 60+ days):");
144
+ for (const name of report.staleSkills) {
145
+ lines.push(` ⏸ ${name}`);
146
+ }
147
+ }
148
+
149
+ // Declining skills
150
+ if (report.decliningSkills.length > 0) {
151
+ lines.push("");
152
+ lines.push("Declining Skills (flagged for review):");
153
+ for (const name of report.decliningSkills) {
154
+ const entry = report.skills.find(s => s.name === name);
155
+ if (entry?.flagReason) {
156
+ lines.push(` ⚠ ${name}: ${entry.flagReason}`);
157
+ }
158
+ }
159
+ }
160
+
161
+ // Suggestions
162
+ if (report.suggestions.length > 0) {
163
+ lines.push("");
164
+ lines.push("Heal Suggestions:");
165
+ for (const sug of report.suggestions) {
166
+ const icon = sug.severity === "critical" ? "🔴" : sug.severity === "warning" ? "🟡" : "🔵";
167
+ lines.push(` ${icon} ${sug.skillName}: ${sug.message}`);
168
+ }
169
+ }
170
+
171
+ return lines.join("\n");
172
+ }
173
+
174
+ /**
175
+ * Format a detailed health view for a single skill.
176
+ */
177
+ export function formatSkillDetail(basePath: string, skillName: string): string {
178
+ const ledger = loadLedgerFromDisk(basePath);
179
+ const units = (ledger?.units ?? []).filter(u => u.skills?.includes(skillName));
180
+ const lines: string[] = [];
181
+
182
+ lines.push(`Skill Detail: ${skillName}`);
183
+ lines.push("═".repeat(50));
184
+
185
+ if (units.length === 0) {
186
+ lines.push("No usage data recorded for this skill.");
187
+ return lines.join("\n");
188
+ }
189
+
190
+ const totalTokens = units.reduce((s, u) => s + u.tokens.total, 0);
191
+ const totalCost = units.reduce((s, u) => s + u.cost, 0);
192
+ const avgTokens = Math.round(totalTokens / units.length);
193
+ const avgCost = totalCost / units.length;
194
+
195
+ lines.push(`Total uses: ${units.length}`);
196
+ lines.push(`Total tokens: ${formatTokenCount(totalTokens)}`);
197
+ lines.push(`Total cost: ${formatCost(totalCost)}`);
198
+ lines.push(`Avg tokens/use: ${formatTokenCount(avgTokens)}`);
199
+ lines.push(`Avg cost/use: ${formatCost(avgCost)}`);
200
+ lines.push("");
201
+
202
+ // Recent uses
203
+ lines.push("Recent uses:");
204
+ const recent = units.slice(-10).reverse();
205
+ for (const u of recent) {
206
+ const date = new Date(u.finishedAt).toISOString().slice(0, 10);
207
+ lines.push(` ${date} ${u.id.padEnd(20)} ${formatTokenCount(u.tokens.total).padStart(8)} tokens ${formatCost(u.cost)}`);
208
+ }
209
+
210
+ // Check for SKILL.md existence
211
+ const skillPath = join(getAgentDir(), "skills", skillName, "SKILL.md");
212
+ if (existsSync(skillPath)) {
213
+ const stat = require("node:fs").statSync(skillPath);
214
+ lines.push("");
215
+ lines.push(`SKILL.md: ${skillPath}`);
216
+ lines.push(`Last modified: ${stat.mtime.toISOString().slice(0, 10)}`);
217
+ }
218
+
219
+ return lines.join("\n");
220
+ }
221
+
222
+ /**
223
+ * Build the heal-skill prompt for a post-unit hook.
224
+ * This is the GSD-integrated version of glittercowboy's heal-skill concept.
225
+ *
226
+ * The prompt instructs the agent to:
227
+ * 1. Detect which skill was loaded during the completed unit
228
+ * 2. Analyze whether the agent deviated from the skill's instructions
229
+ * 3. If deviations found, propose specific fixes (not auto-apply)
230
+ * 4. Write suggestions to a review queue for human approval
231
+ */
232
+ export function buildHealSkillPrompt(unitId: string): string {
233
+ return `## Skill Heal Analysis
234
+
235
+ Analyze the just-completed unit (${unitId}) for skill drift.
236
+
237
+ ### Steps
238
+
239
+ 1. **Identify loaded skill**: Check which SKILL.md file was read during this unit.
240
+ If no skill was loaded, write "No skill loaded — skipping heal analysis" and stop.
241
+
242
+ 2. **Read the skill**: Load the SKILL.md that was used.
243
+
244
+ 3. **Compare execution to skill guidance**: Review what the agent actually did vs what
245
+ the skill recommended. Look for:
246
+ - API patterns the skill recommended that the agent did differently
247
+ - Error handling approaches the skill specified but the agent bypassed
248
+ - Conventions the skill documented that the agent ignored
249
+ - Outdated instructions in the skill that caused errors or retries
250
+
251
+ 4. **Assess drift severity**:
252
+ - **None**: Agent followed skill correctly → write "No drift detected" to the summary and stop
253
+ - **Minor**: Agent found a better approach but skill isn't wrong → note in KNOWLEDGE.md
254
+ - **Significant**: Skill has outdated or incorrect guidance → propose fix
255
+
256
+ 5. **If significant drift found**, write a heal suggestion to \`.gsd/skill-review-queue.md\`:
257
+
258
+ \`\`\`markdown
259
+ ### {skill-name} (flagged {date})
260
+ - **Unit:** ${unitId}
261
+ - **Issue:** {1-2 sentence description}
262
+ - **Root cause:** {outdated API / incorrect pattern / missing context}
263
+ - **Proposed fix:**
264
+ - File: SKILL.md
265
+ - Section: {section name}
266
+ - Current: {quote the incorrect text}
267
+ - Suggested: {the corrected text}
268
+ - **Action:** [ ] Reviewed [ ] Updated [ ] Dismissed
269
+ \`\`\`
270
+
271
+ **Important:** Do NOT modify the skill directly. Write the suggestion to the review queue.
272
+ The SkillsBench research shows that human-curated skills outperform auto-generated ones by +16.2pp.
273
+ The human review step is what makes this valuable.`;
274
+ }
275
+
276
+ /**
277
+ * Compute stale skills that should be added to avoid_skills.
278
+ * Returns only skills not already in the avoid list.
279
+ */
280
+ export function computeStaleAvoidList(
281
+ basePath: string,
282
+ currentAvoidList: string[],
283
+ staleDays?: number,
284
+ ): string[] {
285
+ const ledger = loadLedgerFromDisk(basePath);
286
+ const units = (ledger?.units ?? []).filter(u => u.skills && u.skills.length > 0);
287
+ const stale = detectStaleSkills(units, staleDays ?? DEFAULT_STALE_DAYS);
288
+ const avoidSet = new Set(currentAvoidList);
289
+
290
+ return stale.filter(s => !avoidSet.has(s));
291
+ }
292
+
293
+ // ─── Internals ────────────────────────────────────────────────────────────────
294
+
295
+ function aggregateBySkill(units: UnitMetrics[]): Map<string, SkillHealthEntry> {
296
+ const map = new Map<string, { uses: UnitMetrics[] }>();
297
+
298
+ for (const u of units) {
299
+ if (!u.skills) continue;
300
+ for (const skill of u.skills) {
301
+ let entry = map.get(skill);
302
+ if (!entry) {
303
+ entry = { uses: [] };
304
+ map.set(skill, entry);
305
+ }
306
+ entry.uses.push(u);
307
+ }
308
+ }
309
+
310
+ const result = new Map<string, SkillHealthEntry>();
311
+ const now = Date.now();
312
+
313
+ for (const [name, { uses }] of map) {
314
+ const totalTokens = uses.reduce((s, u) => s + u.tokens.total, 0);
315
+ const totalCost = uses.reduce((s, u) => s + u.cost, 0);
316
+ const avgTokens = Math.round(totalTokens / uses.length);
317
+ const avgCost = totalCost / uses.length;
318
+
319
+ // Success rate: units that didn't have excessive retries (proxy: low tool call count relative to messages)
320
+ // Without direct retry tracking, use a heuristic: success if toolCalls < assistantMessages * 20
321
+ const successCount = uses.filter(u => u.toolCalls < u.assistantMessages * 20).length;
322
+ const successRate = uses.length > 0 ? successCount / uses.length : 1;
323
+
324
+ // Token trend
325
+ const tokenTrend = computeTokenTrend(uses);
326
+
327
+ // Last used
328
+ const lastUsed = Math.max(...uses.map(u => u.finishedAt));
329
+ const staleDays = Math.floor((now - lastUsed) / (24 * 60 * 60 * 1000));
330
+
331
+ // Flag conditions
332
+ let flagged = false;
333
+ let flagReason: string | undefined;
334
+
335
+ if (uses.length >= MIN_USES_FOR_TREND) {
336
+ if (successRate < SUCCESS_RATE_THRESHOLD) {
337
+ flagged = true;
338
+ flagReason = `Success rate ${Math.round(successRate * 100)}% (below ${Math.round(SUCCESS_RATE_THRESHOLD * 100)}% threshold)`;
339
+ } else if (tokenTrend === "rising") {
340
+ flagged = true;
341
+ flagReason = `Token usage trending upward (${Math.round(TOKEN_RISE_THRESHOLD * 100)}%+ increase)`;
342
+ }
343
+ }
344
+
345
+ result.set(name, {
346
+ name,
347
+ totalUses: uses.length,
348
+ successRate,
349
+ avgTokens,
350
+ tokenTrend,
351
+ lastUsed,
352
+ staleDays,
353
+ avgCost,
354
+ flagged,
355
+ flagReason,
356
+ });
357
+ }
358
+
359
+ return result;
360
+ }
361
+
362
+ function computeTokenTrend(uses: UnitMetrics[]): "stable" | "rising" | "declining" {
363
+ if (uses.length < MIN_USES_FOR_TREND * 2) return "stable";
364
+
365
+ // Sort by start time
366
+ const sorted = [...uses].sort((a, b) => a.startedAt - b.startedAt);
367
+ const window = Math.min(TREND_WINDOW, Math.floor(sorted.length / 2));
368
+
369
+ const recent = sorted.slice(-window);
370
+ const previous = sorted.slice(-window * 2, -window);
371
+
372
+ const recentAvg = recent.reduce((s, u) => s + u.tokens.total, 0) / recent.length;
373
+ const previousAvg = previous.reduce((s, u) => s + u.tokens.total, 0) / previous.length;
374
+
375
+ if (previousAvg === 0) return "stable";
376
+
377
+ const change = (recentAvg - previousAvg) / previousAvg;
378
+
379
+ if (change > TOKEN_RISE_THRESHOLD) return "rising";
380
+ if (change < -TOKEN_RISE_THRESHOLD) return "declining";
381
+ return "stable";
382
+ }
383
+
384
+ function generateSuggestions(skills: SkillHealthEntry[], staleSkills: string[]): SkillHealSuggestion[] {
385
+ const suggestions: SkillHealSuggestion[] = [];
386
+
387
+ for (const skill of skills) {
388
+ if (skill.totalUses >= MIN_USES_FOR_TREND && skill.successRate < SUCCESS_RATE_THRESHOLD) {
389
+ suggestions.push({
390
+ skillName: skill.name,
391
+ trigger: "declining_success",
392
+ message: `Success rate dropped to ${Math.round(skill.successRate * 100)}% over ${skill.totalUses} uses. Review SKILL.md for outdated patterns.`,
393
+ severity: skill.successRate < 0.5 ? "critical" : "warning",
394
+ });
395
+ }
396
+
397
+ if (skill.tokenTrend === "rising" && skill.totalUses >= MIN_USES_FOR_TREND * 2) {
398
+ suggestions.push({
399
+ skillName: skill.name,
400
+ trigger: "rising_tokens",
401
+ message: `Token usage trending upward. Skill may be causing inefficient execution patterns.`,
402
+ severity: "info",
403
+ });
404
+ }
405
+ }
406
+
407
+ for (const name of staleSkills) {
408
+ suggestions.push({
409
+ skillName: name,
410
+ trigger: "stale",
411
+ message: `Not used in ${DEFAULT_STALE_DAYS}+ days. Consider archiving or updating.`,
412
+ severity: "info",
413
+ });
414
+ }
415
+
416
+ return suggestions;
417
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * GSD Skill Telemetry — Track which skills are loaded per unit (#599)
3
+ *
4
+ * Captures skill names at dispatch time for inclusion in UnitMetrics.
5
+ * Distinguishes between "available" skills (in system prompt) and
6
+ * "actively loaded" skills (read via tool calls during execution).
7
+ *
8
+ * Data flow:
9
+ * 1. At dispatch, captureAvailableSkills() records skills from the system prompt
10
+ * 2. During execution, recordSkillRead() tracks explicit SKILL.md reads
11
+ * 3. At unit completion, getAndClearSkills() returns the loaded list for metrics
12
+ */
13
+
14
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { getAgentDir } from "@gsd/pi-coding-agent";
17
+
18
+ // ─── In-memory state ──────────────────────────────────────────────────────────
19
+
20
+ /** Skills available in the system prompt for the current unit */
21
+ let availableSkills: string[] = [];
22
+
23
+ /** Skills explicitly read (SKILL.md loaded) during the current unit */
24
+ const activelyLoadedSkills = new Set<string>();
25
+
26
+ // ─── Public API ───────────────────────────────────────────────────────────────
27
+
28
+ /**
29
+ * Capture the list of available skill names at dispatch time.
30
+ * Called before each unit starts.
31
+ */
32
+ export function captureAvailableSkills(): void {
33
+ const skillsDir = join(getAgentDir(), "skills");
34
+ availableSkills = listSkillNames(skillsDir);
35
+ activelyLoadedSkills.clear();
36
+ }
37
+
38
+ /**
39
+ * Record that a skill was actively loaded (its SKILL.md was read).
40
+ * Call this when the agent reads a SKILL.md file.
41
+ */
42
+ export function recordSkillRead(skillName: string): void {
43
+ activelyLoadedSkills.add(skillName);
44
+ }
45
+
46
+ /**
47
+ * Get the skill names for the current unit and clear state.
48
+ * Returns actively loaded skills if any, otherwise available skills.
49
+ * This gives the most useful signal: if the agent read specific skills,
50
+ * report those; otherwise report what was available.
51
+ */
52
+ export function getAndClearSkills(): string[] {
53
+ const result = activelyLoadedSkills.size > 0
54
+ ? Array.from(activelyLoadedSkills)
55
+ : [...availableSkills];
56
+ availableSkills = [];
57
+ activelyLoadedSkills.clear();
58
+ return result;
59
+ }
60
+
61
+ /**
62
+ * Reset all telemetry state. Called when auto-mode stops.
63
+ */
64
+ export function resetSkillTelemetry(): void {
65
+ availableSkills = [];
66
+ activelyLoadedSkills.clear();
67
+ }
68
+
69
+ /**
70
+ * Get last-used timestamps for all skills from metrics data.
71
+ * Returns a Map from skill name to most recent ms timestamp.
72
+ */
73
+ export function getSkillLastUsed(units: Array<{ finishedAt: number; skills?: string[] }>): Map<string, number> {
74
+ const lastUsed = new Map<string, number>();
75
+ for (const u of units) {
76
+ if (!u.skills) continue;
77
+ for (const skill of u.skills) {
78
+ const existing = lastUsed.get(skill) ?? 0;
79
+ if (u.finishedAt > existing) {
80
+ lastUsed.set(skill, u.finishedAt);
81
+ }
82
+ }
83
+ }
84
+ return lastUsed;
85
+ }
86
+
87
+ /**
88
+ * Detect stale skills — those not used within the given threshold (in days).
89
+ * Returns skill names that should be deprioritized.
90
+ */
91
+ export function detectStaleSkills(
92
+ units: Array<{ finishedAt: number; skills?: string[] }>,
93
+ thresholdDays: number,
94
+ ): string[] {
95
+ if (thresholdDays <= 0) return [];
96
+
97
+ const lastUsed = getSkillLastUsed(units);
98
+ const cutoff = Date.now() - (thresholdDays * 24 * 60 * 60 * 1000);
99
+ const stale: string[] = [];
100
+
101
+ // Check all installed skills, not just those with usage data
102
+ const skillsDir = join(getAgentDir(), "skills");
103
+ const installed = listSkillNames(skillsDir);
104
+
105
+ for (const skill of installed) {
106
+ const lastTs = lastUsed.get(skill);
107
+ if (lastTs === undefined || lastTs < cutoff) {
108
+ stale.push(skill);
109
+ }
110
+ }
111
+
112
+ return stale;
113
+ }
114
+
115
+ // ─── Internals ────────────────────────────────────────────────────────────────
116
+
117
+ function listSkillNames(skillsDir: string): string[] {
118
+ if (!existsSync(skillsDir)) return [];
119
+ try {
120
+ return readdirSync(skillsDir, { withFileTypes: true })
121
+ .filter(d => d.isDirectory() && !d.name.startsWith("."))
122
+ .filter(d => existsSync(join(skillsDir, d.name, "SKILL.md")))
123
+ .map(d => d.name);
124
+ } catch {
125
+ return [];
126
+ }
127
+ }
@@ -32,8 +32,10 @@ import {
32
32
 
33
33
  import { milestoneIdSort, findMilestoneIds } from './guided-flow.js';
34
34
  import { nativeBatchParseGsdFiles, type BatchParsedFile } from './native-parser-bridge.js';
35
+ import { isDbAvailable, _getAdapter } from './gsd-db.js';
35
36
 
36
37
  import { join, resolve } from 'path';
38
+ import { debugCount, debugTime } from './debug-logger.js';
37
39
 
38
40
  // ─── Query Functions ───────────────────────────────────────────────────────
39
41
 
@@ -116,7 +118,10 @@ export async function deriveState(basePath: string): Promise<GSDState> {
116
118
  return _stateCache.result;
117
119
  }
118
120
 
121
+ const stopTimer = debugTime("derive-state-impl");
119
122
  const result = await _deriveStateImpl(basePath);
123
+ stopTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
124
+ debugCount("deriveStateCalls");
120
125
  _stateCache = { basePath, result, timestamp: Date.now() };
121
126
  return result;
122
127
  }
@@ -131,6 +136,30 @@ async function _deriveStateImpl(basePath: string): Promise<GSDState> {
131
136
  const fileContentCache = new Map<string, string>();
132
137
  const gsdDir = gsdRoot(basePath);
133
138
 
139
+ // ── DB-first content loading ──
140
+ // When the DB is available, load artifact content from the artifacts table
141
+ // (indexed SELECT instead of O(N) file I/O). Falls back to native Rust batch
142
+ // parser, which in turn falls back to sequential JS reads via cachedLoadFile.
143
+ let dbContentLoaded = false;
144
+ if (isDbAvailable()) {
145
+ const adapter = _getAdapter();
146
+ if (adapter) {
147
+ try {
148
+ const rows = adapter.prepare('SELECT path, full_content FROM artifacts').all();
149
+ for (const row of rows) {
150
+ const relPath = (row as Record<string, unknown>)['path'] as string;
151
+ const content = (row as Record<string, unknown>)['full_content'] as string;
152
+ const absPath = resolve(gsdDir, relPath);
153
+ fileContentCache.set(absPath, content);
154
+ }
155
+ dbContentLoaded = rows.length > 0;
156
+ } catch {
157
+ // DB query failed — fall through to native batch parse
158
+ }
159
+ }
160
+ }
161
+
162
+ if (!dbContentLoaded) {
134
163
  const batchFiles = nativeBatchParseGsdFiles(gsdDir);
135
164
  if (batchFiles) {
136
165
  for (const f of batchFiles) {
@@ -138,6 +167,7 @@ async function _deriveStateImpl(basePath: string): Promise<GSDState> {
138
167
  fileContentCache.set(absPath, f.rawContent);
139
168
  }
140
169
  }
170
+ }
141
171
 
142
172
  /**
143
173
  * Load file content from batch cache first, falling back to disk read.
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  version: 1
3
+ mode:
3
4
  always_use_skills: []
4
5
  prefer_skills: []
5
6
  avoid_skills: []