@winspan/claude-forge 8.50.6 → 8.51.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 (361) hide show
  1. package/CLAUDE.md +7 -7
  2. package/dist/claudemd/claudemd-generator.d.ts.map +1 -1
  3. package/dist/claudemd/claudemd-generator.js +27 -237
  4. package/dist/claudemd/claudemd-generator.js.map +1 -1
  5. package/dist/claudemd/resume-manager.js +1 -1
  6. package/dist/claudemd/resume-manager.js.map +1 -1
  7. package/dist/claudemd/templates/swarm-protocol.md +222 -0
  8. package/dist/cli/commands/daemon.js +6 -6
  9. package/dist/cli/commands/daemon.js.map +1 -1
  10. package/dist/cli/commands/executions.d.ts.map +1 -1
  11. package/dist/cli/commands/executions.js +4 -3
  12. package/dist/cli/commands/executions.js.map +1 -1
  13. package/dist/cli/commands/init.js +2 -2
  14. package/dist/cli/commands/init.js.map +1 -1
  15. package/dist/cli/commands/logs.js.map +1 -1
  16. package/dist/cli/commands/mcp.d.ts.map +1 -1
  17. package/dist/cli/commands/mcp.js +3 -5
  18. package/dist/cli/commands/mcp.js.map +1 -1
  19. package/dist/cli/commands/menu.d.ts.map +1 -1
  20. package/dist/cli/commands/menu.js +4 -3
  21. package/dist/cli/commands/menu.js.map +1 -1
  22. package/dist/cli/commands/stats.d.ts.map +1 -1
  23. package/dist/cli/commands/stats.js +2 -3
  24. package/dist/cli/commands/stats.js.map +1 -1
  25. package/dist/cli/commands/status.js +2 -2
  26. package/dist/cli/commands/status.js.map +1 -1
  27. package/dist/cli/commands/trace.d.ts.map +1 -1
  28. package/dist/cli/commands/trace.js +11 -23
  29. package/dist/cli/commands/trace.js.map +1 -1
  30. package/dist/cli/init/hook-manager.d.ts.map +1 -1
  31. package/dist/cli/init/hook-manager.js +2 -2
  32. package/dist/cli/init/hook-manager.js.map +1 -1
  33. package/dist/core/ai/provider.js +2 -2
  34. package/dist/core/ai/provider.js.map +1 -1
  35. package/dist/core/constants.d.ts +12 -1
  36. package/dist/core/constants.d.ts.map +1 -1
  37. package/dist/core/constants.js +15 -1
  38. package/dist/core/constants.js.map +1 -1
  39. package/dist/core/event-fields.d.ts +16 -0
  40. package/dist/core/event-fields.d.ts.map +1 -0
  41. package/dist/core/event-fields.js +19 -0
  42. package/dist/core/event-fields.js.map +1 -0
  43. package/dist/core/queue/index.d.ts.map +1 -1
  44. package/dist/core/queue/index.js +3 -4
  45. package/dist/core/queue/index.js.map +1 -1
  46. package/dist/core/storage/base.d.ts +36 -3
  47. package/dist/core/storage/base.d.ts.map +1 -1
  48. package/dist/core/storage/base.js +101 -58
  49. package/dist/core/storage/base.js.map +1 -1
  50. package/dist/core/storage/events.d.ts +92 -3
  51. package/dist/core/storage/events.d.ts.map +1 -1
  52. package/dist/core/storage/events.js +147 -0
  53. package/dist/core/storage/events.js.map +1 -1
  54. package/dist/core/storage/routing.d.ts +54 -1
  55. package/dist/core/storage/routing.d.ts.map +1 -1
  56. package/dist/core/storage/routing.js +99 -1
  57. package/dist/core/storage/routing.js.map +1 -1
  58. package/dist/core/storage/schema.sql +12 -2
  59. package/dist/core/storage/sessions.d.ts +20 -0
  60. package/dist/core/storage/sessions.d.ts.map +1 -1
  61. package/dist/core/storage/sessions.js +59 -0
  62. package/dist/core/storage/sessions.js.map +1 -1
  63. package/dist/core/storage/skills.d.ts +23 -0
  64. package/dist/core/storage/skills.d.ts.map +1 -1
  65. package/dist/core/storage/skills.js +47 -0
  66. package/dist/core/storage/skills.js.map +1 -1
  67. package/dist/core/storage/sqlite.d.ts +35 -2
  68. package/dist/core/storage/sqlite.d.ts.map +1 -1
  69. package/dist/core/storage/sqlite.js +93 -4
  70. package/dist/core/storage/sqlite.js.map +1 -1
  71. package/dist/core/storage/tasks.d.ts +49 -0
  72. package/dist/core/storage/tasks.d.ts.map +1 -1
  73. package/dist/core/storage/tasks.js +143 -1
  74. package/dist/core/storage/tasks.js.map +1 -1
  75. package/dist/core/storage/token-usage.d.ts +1 -1
  76. package/dist/core/storage/token-usage.d.ts.map +1 -1
  77. package/dist/core/storage/token-usage.js +1 -1
  78. package/dist/core/storage/token-usage.js.map +1 -1
  79. package/dist/core/types.d.ts +24 -3
  80. package/dist/core/types.d.ts.map +1 -1
  81. package/dist/core/types.js.map +1 -1
  82. package/dist/core/utils/error-handler.d.ts.map +1 -1
  83. package/dist/core/utils/error-handler.js +3 -2
  84. package/dist/core/utils/error-handler.js.map +1 -1
  85. package/dist/core/utils/git.d.ts +10 -0
  86. package/dist/core/utils/git.d.ts.map +1 -0
  87. package/dist/core/utils/git.js +24 -0
  88. package/dist/core/utils/git.js.map +1 -0
  89. package/dist/core/utils/logger.d.ts.map +1 -1
  90. package/dist/core/utils/logger.js +15 -1
  91. package/dist/core/utils/logger.js.map +1 -1
  92. package/dist/core/utils/lru-cache.d.ts +1 -0
  93. package/dist/core/utils/lru-cache.d.ts.map +1 -1
  94. package/dist/core/utils/lru-cache.js +3 -0
  95. package/dist/core/utils/lru-cache.js.map +1 -1
  96. package/dist/core/utils/token-tracker.js +1 -1
  97. package/dist/core/utils/token-tracker.js.map +1 -1
  98. package/dist/daemon/event-parser.d.ts.map +1 -1
  99. package/dist/daemon/event-parser.js +2 -1
  100. package/dist/daemon/event-parser.js.map +1 -1
  101. package/dist/daemon/handlers/history-exporter.js.map +1 -1
  102. package/dist/daemon/handlers/post-tool-use.d.ts.map +1 -1
  103. package/dist/daemon/handlers/post-tool-use.js +7 -3
  104. package/dist/daemon/handlers/post-tool-use.js.map +1 -1
  105. package/dist/daemon/handlers/stop.d.ts +4 -0
  106. package/dist/daemon/handlers/stop.d.ts.map +1 -1
  107. package/dist/daemon/handlers/stop.js +23 -35
  108. package/dist/daemon/handlers/stop.js.map +1 -1
  109. package/dist/daemon/handlers/user-prompt.d.ts +3 -3
  110. package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
  111. package/dist/daemon/handlers/user-prompt.js +12 -22
  112. package/dist/daemon/handlers/user-prompt.js.map +1 -1
  113. package/dist/daemon/index.d.ts.map +1 -1
  114. package/dist/daemon/index.js +23 -9
  115. package/dist/daemon/index.js.map +1 -1
  116. package/dist/daemon/lifecycle.js +3 -4
  117. package/dist/daemon/lifecycle.js.map +1 -1
  118. package/dist/daemon/server.d.ts +6 -4
  119. package/dist/daemon/server.d.ts.map +1 -1
  120. package/dist/daemon/server.js +76 -85
  121. package/dist/daemon/server.js.map +1 -1
  122. package/dist/daemon/services/task-segmenter.js +1 -1
  123. package/dist/daemon/services/task-segmenter.js.map +1 -1
  124. package/dist/hooks/hook-lib.sh +37 -0
  125. package/dist/hooks/notification.sh +2 -2
  126. package/dist/hooks/post-tool-use.sh +2 -2
  127. package/dist/hooks/pre-tool-use.sh +2 -2
  128. package/dist/hooks/stop.sh +9 -6
  129. package/dist/hooks/user-prompt-submit.sh +2 -2
  130. package/dist/{daemon/services → web/analytics}/anti-pattern-detector.d.ts +3 -4
  131. package/dist/web/analytics/anti-pattern-detector.d.ts.map +1 -0
  132. package/dist/{daemon/services → web/analytics}/anti-pattern-detector.js +7 -46
  133. package/dist/{daemon/services → web/analytics}/anti-pattern-detector.js.map +1 -1
  134. package/dist/web/analytics/drift-detector.d.ts.map +1 -0
  135. package/dist/{daemon/services → web/analytics}/drift-detector.js +10 -13
  136. package/dist/web/analytics/drift-detector.js.map +1 -0
  137. package/dist/web/analytics/weekly-report.d.ts.map +1 -0
  138. package/dist/{daemon/services → web/analytics}/weekly-report.js +51 -50
  139. package/dist/web/analytics/weekly-report.js.map +1 -0
  140. package/dist/web/auth-middleware.d.ts.map +1 -1
  141. package/dist/web/auth-middleware.js +1 -2
  142. package/dist/web/auth-middleware.js.map +1 -1
  143. package/dist/web/routes/_helpers.d.ts +16 -0
  144. package/dist/web/routes/_helpers.d.ts.map +1 -0
  145. package/dist/web/routes/_helpers.js +32 -0
  146. package/dist/web/routes/_helpers.js.map +1 -0
  147. package/dist/web/routes/drift.js +1 -1
  148. package/dist/web/routes/drift.js.map +1 -1
  149. package/dist/web/routes/insights.js +1 -1
  150. package/dist/web/routes/insights.js.map +1 -1
  151. package/dist/web/routes/reports.js +1 -1
  152. package/dist/web/routes/reports.js.map +1 -1
  153. package/dist/web/routes/rules.d.ts +3 -0
  154. package/dist/web/routes/rules.d.ts.map +1 -1
  155. package/dist/web/routes/rules.js +28 -52
  156. package/dist/web/routes/rules.js.map +1 -1
  157. package/dist/web/routes/sessions.d.ts.map +1 -1
  158. package/dist/web/routes/sessions.js +16 -30
  159. package/dist/web/routes/sessions.js.map +1 -1
  160. package/dist/web/routes/skill-stats.d.ts +2 -0
  161. package/dist/web/routes/skill-stats.d.ts.map +1 -1
  162. package/dist/web/routes/skill-stats.js +28 -64
  163. package/dist/web/routes/skill-stats.js.map +1 -1
  164. package/dist/web/routes/skills.d.ts.map +1 -1
  165. package/dist/web/routes/skills.js +5 -4
  166. package/dist/web/routes/skills.js.map +1 -1
  167. package/dist/web/routes/stats.d.ts +4 -0
  168. package/dist/web/routes/stats.d.ts.map +1 -1
  169. package/dist/web/routes/stats.js +19 -21
  170. package/dist/web/routes/stats.js.map +1 -1
  171. package/dist/web/routes/tasks.d.ts.map +1 -1
  172. package/dist/web/routes/tasks.js +17 -42
  173. package/dist/web/routes/tasks.js.map +1 -1
  174. package/dist/web/routes/trace.d.ts.map +1 -1
  175. package/dist/web/routes/trace.js +7 -17
  176. package/dist/web/routes/trace.js.map +1 -1
  177. package/dist/web/routes/types.d.ts.map +1 -1
  178. package/dist/web/routes/types.js +4 -3
  179. package/dist/web/routes/types.js.map +1 -1
  180. package/dist/web/static/assets/{AIConfig-BQCAQE9D.js → AIConfig-CdDWzJyO.js} +2 -2
  181. package/dist/web/static/assets/{AIConfig-BQCAQE9D.js.map → AIConfig-CdDWzJyO.js.map} +1 -1
  182. package/dist/web/static/assets/{Dashboard-D7Bo6Kan.js → Dashboard-CoEmmIDt.js} +2 -2
  183. package/dist/web/static/assets/{Dashboard-D7Bo6Kan.js.map → Dashboard-CoEmmIDt.js.map} +1 -1
  184. package/dist/web/static/assets/{Drawer-BeHRQxUS.js → Drawer-DdRTzlLB.js} +2 -2
  185. package/dist/web/static/assets/{Drawer-BeHRQxUS.js.map → Drawer-DdRTzlLB.js.map} +1 -1
  186. package/dist/web/static/assets/{Events-K_tCY2ti.js → Events-DrIq1SUS.js} +2 -2
  187. package/dist/web/static/assets/{Events-K_tCY2ti.js.map → Events-DrIq1SUS.js.map} +1 -1
  188. package/dist/web/static/assets/{Reports-BJCmBnc_.js → Reports-DFBM3MDK.js} +2 -2
  189. package/dist/web/static/assets/{Reports-BJCmBnc_.js.map → Reports-DFBM3MDK.js.map} +1 -1
  190. package/dist/web/static/assets/{SearchInput-BX2KhMkw.js → SearchInput-qCj_jAcf.js} +2 -2
  191. package/dist/web/static/assets/{SearchInput-BX2KhMkw.js.map → SearchInput-qCj_jAcf.js.map} +1 -1
  192. package/dist/web/static/assets/{SessionDetail-Bkr-kC7V.js → SessionDetail-CCzwdoT7.js} +2 -2
  193. package/dist/web/static/assets/{SessionDetail-Bkr-kC7V.js.map → SessionDetail-CCzwdoT7.js.map} +1 -1
  194. package/dist/web/static/assets/{Sessions-Chx9OCLH.js → Sessions-FfLYkAw9.js} +2 -2
  195. package/dist/web/static/assets/{Sessions-Chx9OCLH.js.map → Sessions-FfLYkAw9.js.map} +1 -1
  196. package/dist/web/static/assets/{Skills-O0GT1i7m.js → Skills-C8Gvs3Qa.js} +2 -2
  197. package/dist/web/static/assets/{Skills-O0GT1i7m.js.map → Skills-C8Gvs3Qa.js.map} +1 -1
  198. package/dist/web/static/assets/TaskDetail-BS8pYhaR.js +2 -0
  199. package/dist/web/static/assets/TaskDetail-BS8pYhaR.js.map +1 -0
  200. package/dist/web/static/assets/Tasks-CyuhizG8.js +2 -0
  201. package/dist/web/static/assets/Tasks-CyuhizG8.js.map +1 -0
  202. package/dist/web/static/assets/index-CBX47X8l.js +3 -0
  203. package/dist/web/static/assets/{index-DxIbmNmr.js.map → index-CBX47X8l.js.map} +1 -1
  204. package/dist/web/static/assets/index-DjIoMdoR.css +1 -0
  205. package/dist/web/static/assets/{lucide-fJlPI3H7.js → lucide-Bs_edTLa.js} +44 -39
  206. package/dist/web/static/assets/lucide-Bs_edTLa.js.map +1 -0
  207. package/dist/web/static/assets/react-router-r79dBVy4.js +20 -0
  208. package/dist/web/static/assets/{react-router-I-HqunH7.js.map → react-router-r79dBVy4.js.map} +1 -1
  209. package/dist/web/static/assets/task-title-BhOcemuR.js +2 -0
  210. package/dist/web/static/assets/task-title-BhOcemuR.js.map +1 -0
  211. package/dist/web/static/index.html +4 -4
  212. package/docs/design/h1-storage-aggregation-spec-20260518-1121.md +299 -0
  213. package/docs/design/h2-getdatabase-encapsulation-spec-20260518-1450.md +191 -0
  214. package/docs/design/h3-fallback-removal-spec-20260518-1245.md +76 -0
  215. package/docs/design/h4-index-dedup-spec-20260518-1230.md +109 -0
  216. package/docs/design/h6-services-migration-spec-20260518-1355.md +82 -0
  217. package/docs/design/l1-swarm-protocol-extract-spec-20260518-1605.md +106 -0
  218. package/docs/design/m10-forge-paths-spec-20260518-1320.md +121 -0
  219. package/docs/design/m2-m3-tool-input-spec-20260518-1425.md +131 -0
  220. package/docs/design/m7-routing-event-association-spec-20260518-1545.md +103 -0
  221. package/docs/design/project-path-gitroot-spec-20260518-1715.md +134 -0
  222. package/docs/design/task-active-gc-spec-20260518-1745.md +146 -0
  223. package/docs/implementation/h1-storage-aggregation-changelog-20260518-1121.md +82 -0
  224. package/docs/implementation/h2-final-changelog-20260518-1530.md +61 -0
  225. package/docs/implementation/h2-phase1-safety-net-changelog-20260518-1450.md +70 -0
  226. package/docs/implementation/h2-phase2-operations-changelog-20260518-1450.md +120 -0
  227. package/docs/implementation/h2-phase3-callsites-changelog-20260518-1450.md +71 -0
  228. package/docs/implementation/h3-fallback-removal-changelog-20260518-1245.md +71 -0
  229. package/docs/implementation/h4-index-dedup-changelog-20260518-1230.md +60 -0
  230. package/docs/implementation/h6-services-migration-changelog-20260518-1355.md +46 -0
  231. package/docs/implementation/h7-m9-defaults-changelog-20260518-1300.md +46 -0
  232. package/docs/implementation/l1-swarm-protocol-extract-changelog-20260518-1605.md +45 -0
  233. package/docs/implementation/l3-l4-daemon-perf-changelog-20260518-1410.md +63 -0
  234. package/docs/implementation/l6-l8-final-cleanup-changelog-20260518-1640.md +38 -0
  235. package/docs/implementation/m1-m4-m5-l7-cleanup-changelog-20260518-1310.md +58 -0
  236. package/docs/implementation/m10-forge-paths-changelog-20260518-1320.md +60 -0
  237. package/docs/implementation/m2-m3-tool-input-changelog-20260518-1425.md +43 -0
  238. package/docs/implementation/m6-m8-naming-shutdown-changelog-20260518-1340.md +56 -0
  239. package/docs/implementation/m7-routing-association-changelog-20260518-1545.md +69 -0
  240. package/docs/implementation/project-path-gitroot-changelog-20260518-1715.md +63 -0
  241. package/docs/implementation/task-active-gc-changelog-20260518-1745.md +35 -0
  242. package/docs/implementation/task-title-summary-changelog-20260518-1130.md +39 -0
  243. package/docs/implementation/tasks-detail-back-loses-filters-changelog-20260518-1100.md +22 -0
  244. package/docs/implementation/tasks-page-white-screen-hotfix-changelog-20260518-1015.md +56 -0
  245. package/docs/reviews/task-title-summary.md +92 -0
  246. package/docs/reviews/tasks-detail-back-loses-filters.md +58 -0
  247. package/docs/reviews/tasks-page-white-screen-hotfix.md +126 -0
  248. package/package.json +2 -2
  249. package/src/claudemd/claudemd-generator.ts +29 -238
  250. package/src/claudemd/resume-manager.ts +1 -1
  251. package/src/claudemd/templates/swarm-protocol.md +222 -0
  252. package/src/cli/commands/daemon.ts +6 -6
  253. package/src/cli/commands/executions.ts +4 -3
  254. package/src/cli/commands/init.ts +2 -2
  255. package/src/cli/commands/logs.ts +1 -1
  256. package/src/cli/commands/mcp.ts +3 -5
  257. package/src/cli/commands/menu.ts +4 -3
  258. package/src/cli/commands/stats.ts +2 -3
  259. package/src/cli/commands/status.ts +2 -2
  260. package/src/cli/commands/trace.ts +10 -26
  261. package/src/cli/init/hook-manager.ts +2 -2
  262. package/src/core/ai/provider.ts +2 -2
  263. package/src/core/constants.ts +18 -1
  264. package/src/core/event-fields.ts +32 -0
  265. package/src/core/queue/index.ts +3 -4
  266. package/src/core/storage/base.ts +132 -56
  267. package/src/core/storage/events.ts +183 -4
  268. package/src/core/storage/routing.ts +129 -1
  269. package/src/core/storage/schema.sql +12 -2
  270. package/src/core/storage/sessions.ts +64 -0
  271. package/src/core/storage/skills.ts +69 -0
  272. package/src/core/storage/sqlite.ts +103 -4
  273. package/src/core/storage/tasks.ts +149 -1
  274. package/src/core/storage/token-usage.ts +1 -1
  275. package/src/core/types.ts +30 -3
  276. package/src/core/utils/error-handler.ts +3 -2
  277. package/src/core/utils/git.ts +23 -0
  278. package/src/core/utils/logger.ts +16 -1
  279. package/src/core/utils/lru-cache.ts +4 -0
  280. package/src/core/utils/token-tracker.ts +1 -1
  281. package/src/daemon/event-parser.ts +4 -3
  282. package/src/daemon/handlers/history-exporter.ts +1 -1
  283. package/src/daemon/handlers/post-tool-use.ts +7 -3
  284. package/src/daemon/handlers/stop.ts +32 -39
  285. package/src/daemon/handlers/user-prompt.ts +12 -22
  286. package/src/daemon/index.ts +24 -10
  287. package/src/daemon/lifecycle.ts +3 -3
  288. package/src/daemon/server.ts +76 -89
  289. package/src/daemon/services/task-segmenter.ts +1 -1
  290. package/src/hooks/hook-lib.sh +37 -0
  291. package/src/hooks/notification.sh +2 -2
  292. package/src/hooks/post-tool-use.sh +2 -2
  293. package/src/hooks/pre-tool-use.sh +2 -2
  294. package/src/hooks/stop.sh +9 -6
  295. package/src/hooks/user-prompt-submit.sh +2 -2
  296. package/src/{daemon/services → web/analytics}/anti-pattern-detector.ts +9 -54
  297. package/src/{daemon/services → web/analytics}/drift-detector.ts +10 -23
  298. package/src/{daemon/services → web/analytics}/weekly-report.ts +52 -75
  299. package/src/web/auth-middleware.ts +1 -2
  300. package/src/web/routes/_helpers.ts +34 -0
  301. package/src/web/routes/drift.ts +1 -1
  302. package/src/web/routes/insights.ts +1 -1
  303. package/src/web/routes/reports.ts +1 -1
  304. package/src/web/routes/rules.ts +31 -56
  305. package/src/web/routes/sessions.ts +18 -30
  306. package/src/web/routes/skill-stats.ts +29 -69
  307. package/src/web/routes/skills.ts +5 -4
  308. package/src/web/routes/stats.ts +19 -29
  309. package/src/web/routes/tasks.ts +17 -42
  310. package/src/web/routes/trace.ts +7 -19
  311. package/src/web/routes/types.ts +4 -3
  312. package/tests/integration/claudemd-generator.test.ts +90 -0
  313. package/tests/integration/web-analytics.integration.test.ts +133 -0
  314. package/tests/integration/web-stats.integration.test.ts +135 -0
  315. package/tests/integration/web-trace.integration.test.ts +175 -0
  316. package/tests/unit/core/forge-paths.test.ts +99 -0
  317. package/tests/unit/daemon/post-tool-use.test.ts +121 -0
  318. package/tests/unit/daemon/stop-handler-behavior-summary.test.ts +202 -0
  319. package/tests/unit/daemon/task-segmenter-recover.test.ts +84 -0
  320. package/tests/unit/event-fields.test.ts +88 -0
  321. package/tests/unit/event-parser.test.ts +55 -0
  322. package/tests/unit/hooks/resolve-project-path.test.ts +122 -0
  323. package/tests/unit/socket-server.test.ts +183 -0
  324. package/tests/unit/storage/event-operations-aggregates.test.ts +342 -0
  325. package/tests/unit/storage/migration-idempotent.test.ts +304 -0
  326. package/tests/unit/storage/routing-aggregates.test.ts +276 -0
  327. package/tests/unit/storage/routing.test.ts +117 -0
  328. package/tests/unit/storage/schema-missing.test.ts +81 -0
  329. package/tests/unit/storage/session-operations-aggregates.test.ts +120 -0
  330. package/tests/unit/storage/skill-operations-counts.test.ts +106 -0
  331. package/tests/unit/storage/skills-aggregates.test.ts +104 -0
  332. package/tests/unit/storage/sqlite-refactor-harness.test.ts +3 -3
  333. package/tests/unit/storage/task-operations-counts.test.ts +46 -0
  334. package/tests/unit/storage/tasks-getById.test.ts +343 -0
  335. package/tests/unit/storage/tasks-stale-gc.test.ts +86 -0
  336. package/tests/unit/token-usage.test.ts +6 -6
  337. package/tests/unit/web/navigation-back-contract.test.ts +134 -0
  338. package/tests/unit/web/routes-rules.test.ts +182 -0
  339. package/tests/unit/web/routes-tasks.test.ts +34 -0
  340. package/tests/unit/web/task-title-contract.test.ts +210 -0
  341. package/tests/unit/web/tasks-component-contract.test.ts +179 -0
  342. package/vitest.config.ts +1 -1
  343. package/web/src/pages/TaskDetail.tsx +9 -5
  344. package/web/src/pages/Tasks.tsx +315 -50
  345. package/web/src/utils/navigation.ts +25 -0
  346. package/web/src/utils/task-title.ts +49 -0
  347. package/dist/daemon/services/anti-pattern-detector.d.ts.map +0 -1
  348. package/dist/daemon/services/drift-detector.d.ts.map +0 -1
  349. package/dist/daemon/services/drift-detector.js.map +0 -1
  350. package/dist/daemon/services/weekly-report.d.ts.map +0 -1
  351. package/dist/daemon/services/weekly-report.js.map +0 -1
  352. package/dist/web/static/assets/TaskDetail-5SR8zGzv.js +0 -2
  353. package/dist/web/static/assets/TaskDetail-5SR8zGzv.js.map +0 -1
  354. package/dist/web/static/assets/Tasks-DCgDqvOZ.js +0 -2
  355. package/dist/web/static/assets/Tasks-DCgDqvOZ.js.map +0 -1
  356. package/dist/web/static/assets/index-D8AKj26b.css +0 -1
  357. package/dist/web/static/assets/index-DxIbmNmr.js +0 -3
  358. package/dist/web/static/assets/lucide-fJlPI3H7.js.map +0 -1
  359. package/dist/web/static/assets/react-router-I-HqunH7.js +0 -20
  360. /package/dist/{daemon/services → web/analytics}/drift-detector.d.ts +0 -0
  361. /package/dist/{daemon/services → web/analytics}/weekly-report.d.ts +0 -0
@@ -7,8 +7,7 @@
7
7
  import { Command } from 'commander';
8
8
  import { SQLiteStorage } from '../../core/storage/sqlite.js';
9
9
  import chalk from 'chalk';
10
- import path from 'node:path';
11
- import { homedir } from 'node:os';
10
+ import { FORGE_PATHS } from '../../core/constants.js';
12
11
 
13
12
  export function createStatsCommand(): Command {
14
13
  const cmd = new Command('stats');
@@ -18,7 +17,7 @@ export function createStatsCommand(): Command {
18
17
  .option('-d, --days <days>', '统计天数', '7')
19
18
  .option('-p, --project <path>', '项目路径')
20
19
  .action((options: { days: string; project?: string }) => {
21
- const dbPath = path.join(homedir(), '.claude-forge', 'data.db');
20
+ const dbPath = FORGE_PATHS.database();
22
21
  const storage = new SQLiteStorage(dbPath);
23
22
 
24
23
  const days = parseInt(options.days);
@@ -2,10 +2,10 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { homedir } from 'os';
4
4
  import type { Command } from 'commander';
5
- import { FORGE_HOME, FORGE_PATHS } from '../../core/constants.js';
5
+ import { FORGE_PATHS } from '../../core/constants.js';
6
6
  import { ConfigManager } from '../../core/config.js';
7
7
 
8
- const PID_FILE = path.join(FORGE_HOME, 'daemon.pid');
8
+ const PID_FILE = FORGE_PATHS.daemonPid();
9
9
 
10
10
  function readPid(): number | null {
11
11
  if (!fs.existsSync(PID_FILE)) return null;
@@ -11,7 +11,7 @@ import path from 'node:path';
11
11
  import { homedir } from 'node:os';
12
12
  import chalk from 'chalk';
13
13
  import { SQLiteStorage } from '../../core/storage/sqlite.js';
14
- import { FORGE_PATHS } from '../../core/constants.js';
14
+ import { FORGE_PATHS, DEFAULTS } from '../../core/constants.js';
15
15
  import { ConfigManager } from '../../core/config.js';
16
16
 
17
17
  function resolveDbPath(): string {
@@ -79,19 +79,17 @@ export function register(program: Command): void {
79
79
  // 4. Query storage for session details
80
80
  const dbPath = resolveDbPath();
81
81
  const storage = new SQLiteStorage(dbPath);
82
- const db = storage.getDatabase();
83
82
 
84
83
  console.log(chalk.bold(`\nCommit: ${commitHash.slice(0, 7)} (${commitMessage})\n`));
85
84
 
86
85
  for (const sessionId of sessionIds) {
87
- printSessionTrace(storage, db, sessionId);
86
+ printSessionTrace(storage, sessionId);
88
87
  }
89
88
  });
90
89
  }
91
90
 
92
91
  function printSessionTrace(
93
92
  storage: SQLiteStorage,
94
- db: ReturnType<SQLiteStorage['getDatabase']>,
95
93
  sessionId: string,
96
94
  ): void {
97
95
  // Find session
@@ -111,32 +109,18 @@ function printSessionTrace(
111
109
  const durationMin = Math.round((end.getTime() - start.getTime()) / 60000);
112
110
  const timeStr = `${formatTime(start)} - ${formatTime(end)} (${durationMin} min)`;
113
111
 
114
- // Event breakdown
115
- const eventBreakdown = db.prepare(`
116
- SELECT hook_type, COUNT(*) as cnt FROM events
117
- WHERE session_id = ? GROUP BY hook_type
118
- `).all(fullId) as Array<{ hook_type: string; cnt: number }>;
119
-
120
- const totalEvents = eventBreakdown.reduce((sum, r) => sum + r.cnt, 0);
121
- const breakdownStr = eventBreakdown.map(r => `${r.hook_type}: ${r.cnt}`).join(', ');
112
+ // H2 Phase 3: 改用 facade 聚合方法。
113
+ const eventBreakdown = storage.aggregateHookTypeBySession(fullId);
114
+ const totalEvents = eventBreakdown.reduce((sum, r) => sum + r.count, 0);
115
+ const breakdownStr = eventBreakdown.map(r => `${r.hook_type}: ${r.count}`).join(', ');
122
116
 
123
117
  // Agent calls
124
- const agentRows = db.prepare(`
125
- SELECT json_extract(tool_input, '$.subagent_type') as agent_type, COUNT(*) as cnt
126
- FROM events
127
- WHERE session_id = ? AND tool_name IN ('Agent', 'Task') AND tool_input IS NOT NULL
128
- GROUP BY agent_type
129
- `).all(fullId) as Array<{ agent_type: string | null; cnt: number }>;
130
-
131
- const totalAgents = agentRows.reduce((sum, r) => sum + r.cnt, 0);
118
+ const agentRows = storage.aggregateAgentTypeBySession(fullId);
119
+ const totalAgents = agentRows.reduce((sum, r) => sum + r.count, 0);
132
120
  const agentNames = agentRows.filter(r => r.agent_type).map(r => r.agent_type as string);
133
121
 
134
122
  // Skills
135
- const skillRows = db.prepare(`
136
- SELECT DISTINCT skill_id FROM skill_invocations WHERE session_id = ?
137
- `).all(fullId) as Array<{ skill_id: string }>;
138
-
139
- const skillNames = skillRows.map(r => r.skill_id);
123
+ const skillNames = storage.queryDistinctSkillIdsBySession(fullId);
140
124
 
141
125
  // Output
142
126
  console.log(chalk.cyan(` Session: ${fullId}`));
@@ -151,7 +135,7 @@ function printSessionTrace(
151
135
  console.log(` Skills: ${skillNames.join(', ')}`);
152
136
  }
153
137
 
154
- console.log(chalk.dim(` Dashboard: http://localhost:3721/sessions/${fullId}`));
138
+ console.log(chalk.dim(` Dashboard: http://localhost:${DEFAULTS.WEB_PORT}/sessions/${fullId}`));
155
139
  console.log();
156
140
  }
157
141
 
@@ -1,11 +1,11 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
- import { FORGE_HOME } from '../../core/constants.js';
4
+ import { FORGE_PATHS } from '../../core/constants.js';
5
5
 
6
6
  export const CLAUDE_HOME = path.join(os.homedir(), '.claude');
7
7
  export const SETTINGS_PATH = path.join(CLAUDE_HOME, 'settings.json');
8
- export const HOOKS_DIR = path.join(FORGE_HOME, 'hooks');
8
+ export const HOOKS_DIR = FORGE_PATHS.hooks();
9
9
 
10
10
  export const HOOK_SCRIPT_NAMES = [
11
11
  'pre-tool-use.sh',
@@ -24,8 +24,8 @@ export interface CompleteWithImageOptions extends CompleteOptions {
24
24
  images: ImageInput[];
25
25
  }
26
26
  import { logger } from '../utils/logger.js';
27
+ import { DEFAULTS } from '../constants.js';
27
28
 
28
- const DEFAULT_MODEL = 'claude-sonnet-4-20250514';
29
29
  const DEFAULT_MAX_TOKENS = 4096;
30
30
  const DEFAULT_TIMEOUT_MS = 30_000;
31
31
  const DEFAULT_MAX_RETRIES = 2;
@@ -98,7 +98,7 @@ export class ClaudeProvider {
98
98
  private totalLatencyMs = 0;
99
99
 
100
100
  constructor(apiKey: string, model?: string, baseUrl?: string) {
101
- this.model = model || DEFAULT_MODEL;
101
+ this.model = model || DEFAULTS.AI_MODEL;
102
102
  this.client = new Anthropic({
103
103
  apiKey,
104
104
  ...(baseUrl ? { baseURL: baseUrl } : {}),
@@ -11,6 +11,23 @@ export const FORGE_PATHS = {
11
11
  config: () => join(FORGE_HOME, 'config.yaml'),
12
12
  database: () => join(FORGE_HOME, 'data.db'),
13
13
  logs: () => join(FORGE_HOME, 'logs'),
14
+
15
+ // ── Daemon runtime files ────────────────────────────────
16
+ daemonSocket: () => join(FORGE_HOME, 'daemon.sock'),
17
+ daemonPid: () => join(FORGE_HOME, 'daemon.pid'),
18
+ daemonToken: () => join(FORGE_HOME, 'daemon.token'),
19
+ daemonLog: () => join(FORGE_HOME, 'daemon.log'),
20
+ daemonStdout: () => join(FORGE_HOME, 'daemon-stdout.log'),
21
+ daemonStderr: () => join(FORGE_HOME, 'daemon-stderr.log'),
22
+
23
+ // ── Subdirs ─────────────────────────────────────────────
24
+ hooks: () => join(FORGE_HOME, 'hooks'),
25
+ queue: () => join(FORGE_HOME, 'queue'),
26
+ queueDead: () => join(FORGE_HOME, 'queue', 'dead'),
27
+
28
+ // ── Patchable targets ───────────────────────────────────
29
+ routingYaml: () => join(FORGE_HOME, 'routing.yaml'),
30
+ backups: (kind: 'skills' | 'routing') => join(FORGE_HOME, 'backups', kind),
14
31
  } as const;
15
32
 
16
33
  /**
@@ -41,5 +58,5 @@ export const DEFAULTS = {
41
58
  AI_MODEL: 'claude-sonnet-4-6',
42
59
  AI_PROVIDER: 'anthropic',
43
60
  STORAGE_MAX_SIZE_MB: 500,
44
- WEB_PORT: 3456,
61
+ WEB_PORT: 3721,
45
62
  } as const;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Typed getters for ForgeEvent.tool_input fields.
3
+ *
4
+ * Centralises the defensive-access pattern that previously lived as
5
+ * `(e.tool_input as any)?.command ?? ''` across 30+ call sites. Each
6
+ * getter returns `string | undefined`; callers decide their own fallback.
7
+ *
8
+ * Adding a new tool field? Extend ToolInputFields in core/types.ts first,
9
+ * then add a getter here if it needs a normalised access entry.
10
+ */
11
+ import type { ForgeEvent } from './types.js';
12
+
13
+ export function getCommand(e: ForgeEvent): string | undefined {
14
+ const c = e.tool_input?.command;
15
+ return typeof c === 'string' ? c : undefined;
16
+ }
17
+
18
+ export function getFilePath(e: ForgeEvent): string | undefined {
19
+ const fp = e.tool_input?.file_path ?? e.tool_input?.notebook_path;
20
+ return typeof fp === 'string' && fp.length > 0 ? fp : undefined;
21
+ }
22
+
23
+ export function getUserPrompt(e: ForgeEvent): string | undefined {
24
+ if (typeof e.user_prompt === 'string') return e.user_prompt;
25
+ const fallback = e.tool_input?.user_prompt;
26
+ return typeof fallback === 'string' ? fallback : undefined;
27
+ }
28
+
29
+ export function getSubagentType(e: ForgeEvent): string | undefined {
30
+ const s = e.tool_input?.subagent_type;
31
+ return typeof s === 'string' ? s : undefined;
32
+ }
@@ -16,16 +16,15 @@
16
16
  import { mkdirSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
17
17
  import { readFile } from 'node:fs/promises';
18
18
  import { join } from 'node:path';
19
- import { homedir } from 'node:os';
20
19
  import { randomUUID } from 'node:crypto';
21
20
  import type { SQLiteStorage } from '../storage/sqlite.js';
22
21
  import type { ForgeEvent } from '../types.js';
23
22
  import { logger } from '../utils/logger.js';
23
+ import { FORGE_PATHS } from '../constants.js';
24
24
 
25
25
  // ── Constants ──────────────────────────────────────────────────────────────
26
- const FORGE_DIR = join(homedir(), '.claude-forge');
27
- export const QUEUE_DIR = join(FORGE_DIR, 'queue');
28
- export const DEAD_DIR = join(FORGE_DIR, 'queue', 'dead');
26
+ export const QUEUE_DIR = FORGE_PATHS.queue();
27
+ export const DEAD_DIR = FORGE_PATHS.queueDead();
29
28
  export const MAX_FILES = 500;
30
29
  export const TTL_DAYS = 7;
31
30
 
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 拆分自 sqlite.ts。仅负责:
5
5
  * - 打开数据库连接 + 配置 PRAGMA
6
- * - 加载 schema.sql 或 inline fallback
6
+ * - 加载 schema.sql(缺失则抛错)
7
7
  * - 增量迁移(hasColumn / addColumnIfMissing)
8
8
  * - 一次性 sessions 回填
9
9
  *
@@ -43,32 +43,43 @@ export class SQLiteBase {
43
43
  this.db.pragma('mmap_size = 30000000000'); // 30GB memory-mapped I/O
44
44
  this.db.pragma('page_size = 4096'); // Optimal page size for most systems
45
45
 
46
+ // Order matters:
47
+ // 1) runColumnMigrations() ALTERs missing columns on PRE-EXISTING tables.
48
+ // Must run BEFORE initSchema() because schema.sql contains indexes that
49
+ // reference new columns (e.g. idx_skill_invocations_workflow). On a
50
+ // legacy DB those CREATE INDEX statements would otherwise fail because
51
+ // CREATE TABLE IF NOT EXISTS skips the old table and never adds the column.
52
+ // 2) initSchema() applies schema.sql (idempotent CREATE TABLE/INDEX).
53
+ // 3) runIndexMigrations() patches indexes that may still be missing on
54
+ // legacy DBs (defensive; schema.sql already creates them on new DBs).
55
+ this.runColumnMigrations();
46
56
  this.initSchema();
47
- this.runMigrations();
57
+ this.runIndexMigrations();
58
+ this.runPostMigrations();
48
59
  }
49
60
 
50
61
  private initSchema(): void {
51
62
  const thisDir = dirname(fileURLToPath(import.meta.url));
52
63
  const schemaPath = join(thisDir, 'schema.sql');
53
64
 
54
- if (existsSync(schemaPath)) {
55
- const schema = readFileSync(schemaPath, 'utf-8');
56
- this.db.exec(schema);
57
- } else {
58
- // Inline fallback — events table only
59
- this.db.exec(`
60
- CREATE TABLE IF NOT EXISTS events (
61
- event_id TEXT PRIMARY KEY, session_id TEXT NOT NULL,
62
- project_path TEXT NOT NULL, timestamp TEXT NOT NULL,
63
- hook_type TEXT NOT NULL, tool_name TEXT, tool_input TEXT,
64
- tool_output TEXT, user_prompt TEXT, ai_response TEXT,
65
- distilled INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now'))
66
- );
67
- CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
68
- CREATE INDEX IF NOT EXISTS idx_events_project ON events(project_path);
69
- CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp DESC);
70
- CREATE INDEX IF NOT EXISTS idx_events_distilled ON events(distilled);
71
- `);
65
+ if (!existsSync(schemaPath)) {
66
+ throw new Error(
67
+ `[SQLiteStorage] schema.sql not found at ${schemaPath}. ` +
68
+ `This indicates a broken build/install. Run \`npm run build\` or reinstall the package.`,
69
+ );
70
+ }
71
+ const schema = readFileSync(schemaPath, 'utf-8');
72
+ this.db.exec(schema);
73
+ }
74
+
75
+ protected hasTable(name: string): boolean {
76
+ try {
77
+ const row = this.db
78
+ .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`)
79
+ .get(name);
80
+ return !!row;
81
+ } catch {
82
+ return false;
72
83
  }
73
84
  }
74
85
 
@@ -82,6 +93,10 @@ export class SQLiteBase {
82
93
  }
83
94
 
84
95
  protected addColumnIfMissing(table: string, column: string, definition: string): void {
96
+ // Skip silently if the table doesn't exist yet — new DBs hit this path on
97
+ // first boot (initSchema runs after column migrations); the column will be
98
+ // created by schema.sql's CREATE TABLE.
99
+ if (!this.hasTable(table)) return;
85
100
  if (!this.hasColumn(table, column)) {
86
101
  try {
87
102
  this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
@@ -92,10 +107,38 @@ export class SQLiteBase {
92
107
  }
93
108
  }
94
109
 
110
+ protected hasIndex(name: string): boolean {
111
+ try {
112
+ const row = this.db
113
+ .prepare(`SELECT name FROM sqlite_master WHERE type='index' AND name = ?`)
114
+ .get(name);
115
+ return !!row;
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Create an index only if it's missing. The CREATE INDEX IF NOT EXISTS already
123
+ * makes this safe; the hasIndex guard makes it explicit which indexes are still
124
+ * being patched by migrations (vs. provided by schema.sql).
125
+ */
126
+ private createIndexIfMissing(name: string, ddl: string): void {
127
+ if (this.hasIndex(name)) return;
128
+ try {
129
+ this.db.exec(ddl);
130
+ logger.debug(`[SQLiteStorage] migration created ${name}`);
131
+ } catch (err) {
132
+ logger.warn(`[SQLiteStorage] migration: create ${name} failed: ${err}`);
133
+ }
134
+ }
135
+
95
136
  /**
96
- * Incremental schema migrations. Kept idempotent so daemon restarts are safe.
137
+ * Column migrations run BEFORE initSchema() so legacy tables get missing
138
+ * columns (e.g. skill_invocations.workflow) before schema.sql's CREATE INDEX
139
+ * statements try to reference them.
97
140
  */
98
- private runMigrations(): void {
141
+ private runColumnMigrations(): void {
99
142
  this.addColumnIfMissing('sessions', 'first_prompt', 'TEXT');
100
143
 
101
144
  this.addColumnIfMissing('routing_events', 'skill_confidence', 'REAL');
@@ -105,45 +148,69 @@ export class SQLiteBase {
105
148
  this.addColumnIfMissing('skill_invocations', 'phase', 'TEXT');
106
149
  this.addColumnIfMissing('skill_invocations', 'feature_slug', 'TEXT');
107
150
  this.addColumnIfMissing('skill_invocations', 'artifact_path', 'TEXT');
151
+ }
108
152
 
109
- try {
110
- this.db.exec(
111
- `CREATE INDEX IF NOT EXISTS idx_skill_invocations_workflow ON skill_invocations(workflow, phase);
112
- CREATE INDEX IF NOT EXISTS idx_skill_invocations_feature ON skill_invocations(feature_slug);`,
113
- );
114
- } catch (err) {
115
- logger.warn(`[SQLiteStorage] migration: create skill_invocations workflow indexes failed: ${err}`);
116
- }
153
+ /**
154
+ * Index migrations run AFTER initSchema(). Each call is guarded by hasIndex
155
+ * so:
156
+ * - new DBs: schema.sql already created the index → skipped
157
+ * - legacy DBs: schema.sql skipped (existing index) for indexes it tries to
158
+ * create or covered above; remaining migration-only / pre-H1 indexes are
159
+ * patched here.
160
+ * CREATE INDEX IF NOT EXISTS is preserved as a second line of defense.
161
+ */
162
+ private runIndexMigrations(): void {
163
+ this.createIndexIfMissing(
164
+ 'idx_skill_invocations_workflow',
165
+ `CREATE INDEX IF NOT EXISTS idx_skill_invocations_workflow ON skill_invocations(workflow, phase)`,
166
+ );
167
+ this.createIndexIfMissing(
168
+ 'idx_skill_invocations_feature',
169
+ `CREATE INDEX IF NOT EXISTS idx_skill_invocations_feature ON skill_invocations(feature_slug)`,
170
+ );
117
171
 
118
- try {
119
- this.db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time DESC)');
120
- } catch (err) {
121
- logger.warn(`[SQLiteStorage] migration: create idx_sessions_start_time failed: ${err}`);
122
- }
172
+ this.createIndexIfMissing(
173
+ 'idx_sessions_start_time',
174
+ `CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time DESC)`,
175
+ );
123
176
 
124
- // Composite indexes for high-frequency session-scoped queries (idempotent)
125
- try {
126
- this.db.exec(`
127
- CREATE INDEX IF NOT EXISTS idx_events_session_ts ON events(session_id, timestamp DESC);
128
- CREATE INDEX IF NOT EXISTS idx_routing_events_session_ts ON routing_events(session_id, ts DESC);
129
- CREATE INDEX IF NOT EXISTS idx_skill_invocations_session_ts ON skill_invocations(session_id, timestamp DESC);
130
- `);
131
- } catch (err) {
132
- logger.warn(`[SQLiteStorage] migration: create composite indexes failed: ${err}`);
133
- }
177
+ // Composite indexes for high-frequency session-scoped queries
178
+ this.createIndexIfMissing(
179
+ 'idx_events_session_ts',
180
+ `CREATE INDEX IF NOT EXISTS idx_events_session_ts ON events(session_id, timestamp DESC)`,
181
+ );
182
+ this.createIndexIfMissing(
183
+ 'idx_routing_events_session_ts',
184
+ `CREATE INDEX IF NOT EXISTS idx_routing_events_session_ts ON routing_events(session_id, ts DESC)`,
185
+ );
186
+ this.createIndexIfMissing(
187
+ 'idx_skill_invocations_session_ts',
188
+ `CREATE INDEX IF NOT EXISTS idx_skill_invocations_session_ts ON skill_invocations(session_id, timestamp DESC)`,
189
+ );
134
190
 
135
- // Phase 1 Refactor: Additional performance indexes (idempotent)
136
- try {
137
- this.db.exec(`
138
- CREATE INDEX IF NOT EXISTS idx_routing_events_obeyed_ts ON routing_events(obeyed, ts DESC);
139
- CREATE INDEX IF NOT EXISTS idx_events_session_hook ON events(session_id, hook_type, timestamp DESC);
140
- CREATE INDEX IF NOT EXISTS idx_injections_session_handler ON injections(session_id, source_handler);
141
- `);
142
- logger.info('[SQLiteStorage] Phase 1 performance indexes created');
143
- } catch (err) {
144
- logger.warn(`[SQLiteStorage] migration: create Phase 1 performance indexes failed: ${err}`);
145
- }
191
+ // Phase 1 Refactor: Additional performance indexes
192
+ this.createIndexIfMissing(
193
+ 'idx_routing_events_obeyed_ts',
194
+ `CREATE INDEX IF NOT EXISTS idx_routing_events_obeyed_ts ON routing_events(obeyed, ts DESC)`,
195
+ );
196
+ this.createIndexIfMissing(
197
+ 'idx_events_session_hook',
198
+ `CREATE INDEX IF NOT EXISTS idx_events_session_hook ON events(session_id, hook_type, timestamp DESC)`,
199
+ );
200
+ this.createIndexIfMissing(
201
+ 'idx_injections_session_handler',
202
+ `CREATE INDEX IF NOT EXISTS idx_injections_session_handler ON injections(session_id, source_handler)`,
203
+ );
204
+ this.createIndexIfMissing(
205
+ 'idx_routing_events_type_ts',
206
+ `CREATE INDEX IF NOT EXISTS idx_routing_events_type_ts ON routing_events(routed_to_type, ts DESC)`,
207
+ );
208
+ }
146
209
 
210
+ /**
211
+ * One-shot data migrations + deprecated table cleanup.
212
+ */
213
+ private runPostMigrations(): void {
147
214
  this.backfillSessionsIfNeeded();
148
215
 
149
216
  const deprecatedTables = [
@@ -211,6 +278,15 @@ export class SQLiteBase {
211
278
  }
212
279
  }
213
280
 
281
+ /**
282
+ * @internal 仅供 *Operations 子类、migration、初始化使用。
283
+ *
284
+ * Routes / Handlers / Analytics 禁止直接调用。需要查询请走 SQLiteStorage facade
285
+ * 上的具名方法(query* / get* / list* / count* / aggregate*);若 facade 缺方法,
286
+ * 应在对应 Operations 类下沉 SQL 并补 facade 转发,而不是 db.prepare() 直写。
287
+ *
288
+ * 历史:H2 (2026-05) 已收敛 27 处越权 SQL,新增越权请走同样的下沉模式。
289
+ */
214
290
  getDatabase(): Database.Database {
215
291
  return this.db;
216
292
  }