pi-mega-compact 0.11.4 → 0.11.6

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 (381) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/cache-stripes.js +8 -0
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +32 -0
  3. package/dist/extensions/dashboard-server/api-contracts/memory-map.js +1 -0
  4. package/dist/extensions/dashboard-server/api-contracts/memory.js +8 -0
  5. package/dist/extensions/dashboard-server/api-contracts/provider-cache.js +1 -0
  6. package/dist/extensions/dashboard-server/api-contracts/setup.js +10 -0
  7. package/dist/extensions/dashboard-server/index-reader.js +6 -2
  8. package/dist/extensions/dashboard-server/routes-cache.js +135 -47
  9. package/dist/extensions/dashboard-server/routes-game.js +3 -0
  10. package/dist/extensions/dashboard-server/routes-memory-map.js +50 -0
  11. package/dist/extensions/dashboard-server/routes-memory.js +51 -0
  12. package/dist/extensions/dashboard-server/routes-repo.js +2 -2
  13. package/dist/extensions/dashboard-server/routes-setup.js +267 -0
  14. package/dist/extensions/dashboard-server/routes.js +4 -0
  15. package/dist/extensions/dashboard-server/server.js +13 -1
  16. package/dist/extensions/mega-commands.js +189 -1
  17. package/dist/extensions/mega-compact.js +5 -0
  18. package/dist/extensions/mega-config.js +7 -0
  19. package/dist/extensions/mega-events/agent-handlers.js +19 -4
  20. package/dist/extensions/mega-events/compact-handlers.js +1 -0
  21. package/dist/extensions/mega-events/context-handler.js +45 -92
  22. package/dist/extensions/mega-events/context-hwm.js +51 -0
  23. package/dist/extensions/mega-events/error-classifier.js +16 -5
  24. package/dist/extensions/mega-events/mirror-append.js +107 -0
  25. package/dist/extensions/mega-events/outage-advisor.js +7 -1
  26. package/dist/extensions/mega-events/perf-handler.js +134 -2
  27. package/dist/extensions/mega-events/recall-tail.js +59 -0
  28. package/dist/extensions/mega-events/separated-prompt.js +248 -0
  29. package/dist/extensions/mega-events/session-handlers.js +15 -8
  30. package/dist/extensions/mega-runtime/env-loader.js +56 -0
  31. package/dist/extensions/mega-runtime/reset-runtime.js +6 -0
  32. package/dist/extensions/mega-runtime/runtime-snapshot.js +1 -0
  33. package/dist/extensions/mega-runtime/runtime.js +7 -0
  34. package/dist/extensions/mega-runtime/snapshot.js +1 -0
  35. package/dist/extensions/mega-runtime/widget.js +5 -0
  36. package/dist/src/cache-stripe-impl.js +288 -0
  37. package/dist/src/cache-stripe.js +11 -0
  38. package/dist/src/config/stopwords.js +24 -0
  39. package/dist/src/config.js +17 -0
  40. package/dist/src/embedder.js +41 -2
  41. package/dist/src/memoryGraph.js +299 -0
  42. package/dist/src/memoryStats.js +75 -0
  43. package/dist/src/monitoring.js +13 -0
  44. package/dist/src/prefix-break.js +43 -0
  45. package/dist/src/pricing.js +1 -1
  46. package/dist/src/queryExpansion.js +163 -0
  47. package/dist/src/queryReformulation/cache.js +123 -0
  48. package/dist/src/queryReformulation/rrf.js +29 -0
  49. package/dist/src/queryReformulation/tfidf.js +61 -0
  50. package/dist/src/queryReformulation/vagueness.js +33 -0
  51. package/dist/src/queryReformulation.js +225 -0
  52. package/dist/src/recallMetrics.js +258 -0
  53. package/dist/src/store/sqlite/cache-stripes.js +133 -0
  54. package/dist/src/store/sqlite/fts5-search.js +68 -0
  55. package/dist/src/store/sqlite/perf-samples.js +145 -16
  56. package/dist/src/store/sqlite/schema.js +60 -1
  57. package/dist/src/store/sqlite.js +1 -0
  58. package/dist/src/store/turns/memory-store.js +8 -0
  59. package/dist/src/store/turns/sqlite-store.js +14 -3
  60. package/dist/src/store/turns/types.js +18 -1
  61. package/dist/src/store/vectorIndex.js +55 -10
  62. package/dist/src/tiered-router/types.js +18 -0
  63. package/dist/src/tieredRouter.js +421 -0
  64. package/dist/src/vectorStore.js +8 -6
  65. package/extensions/dashboard-client/dist/assets/{AchievementTiles-vEo2MtE2.js → AchievementTiles-DR9cLGDL.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{AchievementTiles-vEo2MtE2.js.map → AchievementTiles-DR9cLGDL.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/AchievementsTab-svNUgJ1e.js +2 -0
  68. package/extensions/dashboard-client/dist/assets/{AchievementsTab-DjxfYrDc.js.map → AchievementsTab-svNUgJ1e.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js +2 -0
  70. package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js.map +1 -0
  71. package/extensions/dashboard-client/dist/assets/{ConfigTab-Cm6ymbmx.js → ConfigTab-CmmWheuW.js} +2 -2
  72. package/extensions/dashboard-client/dist/assets/{ConfigTab-Cm6ymbmx.js.map → ConfigTab-CmmWheuW.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{EventsTab-DXWl_YzI.js → EventsTab-CFE7jLsa.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{EventsTab-DXWl_YzI.js.map → EventsTab-CFE7jLsa.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/GameTab-CJAX_th_.js +2 -0
  76. package/extensions/dashboard-client/dist/assets/{GameTab-BLDudcO5.js.map → GameTab-CJAX_th_.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DLkKCztn.js → MaintenanceTab-P4SUsvMf.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DLkKCztn.js.map → MaintenanceTab-P4SUsvMf.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +2 -0
  80. package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +1 -0
  81. package/extensions/dashboard-client/dist/assets/{MetricsTab-Cq4u-rZF.js → MetricsTab-CHQscixy.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{MetricsTab-Cq4u-rZF.js.map → MetricsTab-CHQscixy.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/OverviewTab-DBs4DJdd.js +2 -0
  84. package/extensions/dashboard-client/dist/assets/{OverviewTab-D4kGvsiQ.js.map → OverviewTab-DBs4DJdd.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/ReposTab-DeFz76L5.js +2 -0
  86. package/extensions/dashboard-client/dist/assets/ReposTab-DeFz76L5.js.map +1 -0
  87. package/extensions/dashboard-client/dist/assets/{SessionsTab-DZC2-GCn.js → SessionsTab-BWNsj1OB.js} +2 -2
  88. package/extensions/dashboard-client/dist/assets/{SessionsTab-DZC2-GCn.js.map → SessionsTab-BWNsj1OB.js.map} +1 -1
  89. package/extensions/dashboard-client/dist/assets/SetupTab-uS6A0xmw.js +2 -0
  90. package/extensions/dashboard-client/dist/assets/SetupTab-uS6A0xmw.js.map +1 -0
  91. package/extensions/dashboard-client/dist/assets/TimeSavedCard-CiqCOQuL.js +2 -0
  92. package/extensions/dashboard-client/dist/assets/TimeSavedCard-CiqCOQuL.js.map +1 -0
  93. package/extensions/dashboard-client/dist/assets/{TopicsTab-01G71hv1.js → TopicsTab-BWWQxltv.js} +2 -2
  94. package/extensions/dashboard-client/dist/assets/{TopicsTab-01G71hv1.js.map → TopicsTab-BWWQxltv.js.map} +1 -1
  95. package/extensions/dashboard-client/dist/assets/{TurnsTab-DLRg0LZT.js → TurnsTab-CVz4f4-o.js} +2 -2
  96. package/extensions/dashboard-client/dist/assets/{TurnsTab-DLRg0LZT.js.map → TurnsTab-CVz4f4-o.js.map} +1 -1
  97. package/extensions/dashboard-client/dist/assets/format-COjDIVGj.js +2 -0
  98. package/extensions/dashboard-client/dist/assets/format-COjDIVGj.js.map +1 -0
  99. package/extensions/dashboard-client/dist/assets/index-CUNn22iV.css +1 -0
  100. package/extensions/dashboard-client/dist/assets/index-DrktNOoT.js +42 -0
  101. package/extensions/dashboard-client/dist/assets/index-DrktNOoT.js.map +1 -0
  102. package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js +2 -0
  103. package/extensions/dashboard-client/dist/assets/pricing-CvE4NllQ.js.map +1 -0
  104. package/extensions/dashboard-client/dist/assets/{useSSE-BBYt5UPB.js → useSSE-Bf5LOLSi.js} +2 -2
  105. package/extensions/dashboard-client/dist/assets/{useSSE-BBYt5UPB.js.map → useSSE-Bf5LOLSi.js.map} +1 -1
  106. package/extensions/dashboard-client/dist/index.html +2 -2
  107. package/extensions/dashboard-client/src/App.tsx +28 -8
  108. package/extensions/dashboard-client/src/api/client.ts +29 -0
  109. package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +21 -12
  110. package/extensions/dashboard-client/src/components/CacheHitRateTrendCard.tsx +100 -0
  111. package/extensions/dashboard-client/src/components/ProviderCacheCard.tsx +56 -0
  112. package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +38 -57
  113. package/extensions/dashboard-client/src/components/StripeDistributionCard.tsx +110 -0
  114. package/extensions/dashboard-client/src/components/TabBar.tsx +49 -4
  115. package/extensions/dashboard-client/src/main.tsx +1 -0
  116. package/extensions/dashboard-client/src/styles/base.css +30 -0
  117. package/extensions/dashboard-client/src/styles/cache.css +167 -0
  118. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +75 -12
  119. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +440 -0
  120. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +418 -0
  121. package/extensions/dashboard-client/tsconfig.json +3 -2
  122. package/extensions/dashboard-client/vite.config.ts +2 -0
  123. package/extensions/dashboard-server/api-contracts/cache-stripes.ts +49 -0
  124. package/extensions/dashboard-server/api-contracts/endpoints.ts +66 -3
  125. package/extensions/dashboard-server/api-contracts/index.ts +22 -0
  126. package/extensions/dashboard-server/api-contracts/memory-map.ts +50 -0
  127. package/extensions/dashboard-server/api-contracts/memory.ts +27 -0
  128. package/extensions/dashboard-server/api-contracts/multi-repo.ts +231 -231
  129. package/extensions/dashboard-server/api-contracts/provider-cache.ts +37 -0
  130. package/extensions/dashboard-server/api-contracts/setup.ts +80 -0
  131. package/extensions/dashboard-server/index-reader.ts +138 -121
  132. package/extensions/dashboard-server/routes-cache.ts +186 -75
  133. package/extensions/dashboard-server/routes-game.ts +3 -0
  134. package/extensions/dashboard-server/routes-memory-map.ts +105 -0
  135. package/extensions/dashboard-server/routes-memory.ts +63 -0
  136. package/extensions/dashboard-server/routes-repo.ts +36 -36
  137. package/extensions/dashboard-server/routes-setup.ts +293 -0
  138. package/extensions/dashboard-server/routes.ts +4 -0
  139. package/extensions/dashboard-server/server.ts +12 -0
  140. package/extensions/mega-commands.ts +211 -1
  141. package/extensions/mega-compact.ts +5 -0
  142. package/extensions/mega-config.ts +27 -0
  143. package/extensions/mega-events/agent-handlers.ts +20 -4
  144. package/extensions/mega-events/compact-handlers.ts +1 -0
  145. package/extensions/mega-events/context-handler.ts +53 -110
  146. package/extensions/mega-events/context-hwm.ts +85 -0
  147. package/extensions/mega-events/error-classifier.ts +15 -4
  148. package/extensions/mega-events/mirror-append.ts +155 -0
  149. package/extensions/mega-events/outage-advisor.ts +11 -5
  150. package/extensions/mega-events/perf-handler.ts +190 -6
  151. package/extensions/mega-events/recall-tail.ts +88 -0
  152. package/extensions/mega-events/separated-prompt.ts +329 -0
  153. package/extensions/mega-events/session-handlers.ts +14 -7
  154. package/extensions/mega-runtime/env-loader.ts +55 -0
  155. package/extensions/mega-runtime/helpers.ts +14 -0
  156. package/extensions/mega-runtime/reset-runtime.ts +6 -0
  157. package/extensions/mega-runtime/runtime-snapshot.ts +1 -0
  158. package/extensions/mega-runtime/runtime.ts +7 -0
  159. package/extensions/mega-runtime/snapshot.ts +6 -0
  160. package/extensions/mega-runtime/widget-types.ts +3 -0
  161. package/extensions/mega-runtime/widget.ts +5 -0
  162. package/package.json +5 -2
  163. package/src/cache-stripe-impl.ts +385 -0
  164. package/src/cache-stripe.ts +18 -0
  165. package/src/config/stopwords.ts +25 -0
  166. package/src/config.ts +26 -0
  167. package/src/embedder.ts +42 -2
  168. package/src/memoryGraph.ts +438 -0
  169. package/src/memoryStats.ts +107 -0
  170. package/src/monitoring.ts +35 -0
  171. package/src/prefix-break.ts +83 -0
  172. package/src/pricing.ts +2 -2
  173. package/src/queryExpansion.ts +206 -0
  174. package/src/queryReformulation/cache.ts +169 -0
  175. package/src/queryReformulation/rrf.ts +51 -0
  176. package/src/queryReformulation/tfidf.ts +79 -0
  177. package/src/queryReformulation/vagueness.ts +38 -0
  178. package/src/queryReformulation.ts +355 -0
  179. package/src/recallMetrics.ts +388 -0
  180. package/src/store/sqlite/cache-stripes.ts +196 -0
  181. package/src/store/sqlite/fts5-search.ts +90 -0
  182. package/src/store/sqlite/perf-samples.ts +218 -23
  183. package/src/store/sqlite/schema.ts +60 -1
  184. package/src/store/sqlite.ts +1 -0
  185. package/src/store/turns/memory-store.ts +10 -1
  186. package/src/store/turns/sqlite-store.ts +27 -15
  187. package/src/store/turns/types.ts +21 -0
  188. package/src/store/vectorIndex.ts +71 -11
  189. package/src/tiered-router/types.ts +64 -0
  190. package/src/tieredRouter.ts +489 -0
  191. package/src/vectorStore.ts +9 -5
  192. package/dist/extensions/conflict-scan.test.js +0 -115
  193. package/dist/extensions/dashboard-server/api-contracts.test.js +0 -884
  194. package/dist/extensions/dashboard-server/html.test.js +0 -52
  195. package/dist/extensions/dashboard-server/perf-server.test.js +0 -80
  196. package/dist/extensions/dashboard-server/routes-cache.test.js +0 -248
  197. package/dist/extensions/dashboard-server/server.test.js +0 -120
  198. package/dist/extensions/dashboard-server-s32.test.js +0 -181
  199. package/dist/extensions/dashboard-server.test.js +0 -339
  200. package/dist/extensions/mega-cache-replay.test.js +0 -400
  201. package/dist/extensions/mega-compact-s38.test.js +0 -1383
  202. package/dist/extensions/mega-compact.test.js +0 -1112
  203. package/dist/extensions/mega-events.test.js +0 -47
  204. package/dist/extensions/mega-game-cmds.test.js +0 -163
  205. package/dist/extensions/mega-runtime/snapshot.test.js +0 -127
  206. package/dist/extensions/mega-runtime/state.test.js +0 -266
  207. package/dist/extensions/mega-runtime/widget.test.js +0 -343
  208. package/dist/extensions/mega-shutdown-widget.test.js +0 -121
  209. package/dist/extensions/mega-teamrun.test.js +0 -156
  210. package/dist/extensions/mega-trim.test.js +0 -58
  211. package/dist/extensions/mega-turn-store.test.js +0 -68
  212. package/dist/src/boundary.test.js +0 -170
  213. package/dist/src/compact.test.js +0 -78
  214. package/dist/src/config/themes.test.js +0 -94
  215. package/dist/src/dedup/dedup.test.js +0 -41
  216. package/dist/src/dedup/l1.test.js +0 -50
  217. package/dist/src/dedup/raptor/buildHistory.test.js +0 -292
  218. package/dist/src/dedup/raptor/multilevel-serve.test.js +0 -229
  219. package/dist/src/dedup/raptor/multilevel.test.js +0 -238
  220. package/dist/src/dedup/raptor/promote.test.js +0 -69
  221. package/dist/src/dedup/raptor/raptor.test.js +0 -248
  222. package/dist/src/dedup/raptor/retrieval.test.js +0 -95
  223. package/dist/src/dedup/raptor/serve-gate.test.js +0 -298
  224. package/dist/src/dedup/sprint12.test.js +0 -219
  225. package/dist/src/dedup-engine.test.js +0 -472
  226. package/dist/src/driftDetection.test.js +0 -87
  227. package/dist/src/e2e.test.js +0 -698
  228. package/dist/src/engine.test.js +0 -111
  229. package/dist/src/extractive.test.js +0 -147
  230. package/dist/src/fork.test.js +0 -81
  231. package/dist/src/game/scoring.test.js +0 -98
  232. package/dist/src/httpEmbedder.test.js +0 -277
  233. package/dist/src/importance.test.js +0 -372
  234. package/dist/src/intent.test.js +0 -78
  235. package/dist/src/log.test.js +0 -42
  236. package/dist/src/mechanical-fix.test.js +0 -65
  237. package/dist/src/memory.test.js +0 -70
  238. package/dist/src/memoryConsolidate.test.js +0 -38
  239. package/dist/src/memoryOps.test.js +0 -129
  240. package/dist/src/memoryRecall.test.js +0 -171
  241. package/dist/src/memoryRoundtrip.test.js +0 -137
  242. package/dist/src/metrics/turns.test.js +0 -152
  243. package/dist/src/mirror/mirror.test.js +0 -185
  244. package/dist/src/raptor-inject-summaries.test.js +0 -162
  245. package/dist/src/ratio.bench.test.js +0 -897
  246. package/dist/src/recall.integration.test.js +0 -77
  247. package/dist/src/recall.test.js +0 -379
  248. package/dist/src/sprint14.test.js +0 -219
  249. package/dist/src/sprint4x-rag-verification.test.js +0 -93
  250. package/dist/src/store/compression.test.js +0 -139
  251. package/dist/src/store/memoryIndex.test.js +0 -51
  252. package/dist/src/store/migrate.test.js +0 -139
  253. package/dist/src/store/pgOpenGuard.test.js +0 -74
  254. package/dist/src/store/phase01.test.js +0 -81
  255. package/dist/src/store/phase04.test.js +0 -54
  256. package/dist/src/store/sprint10.test.js +0 -186
  257. package/dist/src/store/sqlite/game-achievements.test.js +0 -67
  258. package/dist/src/store/sqlite/game-scores.test.js +0 -106
  259. package/dist/src/store/sqlite/game-state.test.js +0 -76
  260. package/dist/src/store/sqlite/global-index.test.js +0 -136
  261. package/dist/src/store/sqlite/mechanical-fix.test.js +0 -146
  262. package/dist/src/store/sqlite/perf-samples.test.js +0 -146
  263. package/dist/src/store/sqlite/raptor.test.js +0 -106
  264. package/dist/src/store/sqlite/turns.test.js +0 -183
  265. package/dist/src/store/sqlite.cachehit.test.js +0 -55
  266. package/dist/src/store/sqlite.dbmaint.test.js +0 -158
  267. package/dist/src/store/sqlite.dbmirror.test.js +0 -184
  268. package/dist/src/store/turns/contract-compliance.test.js +0 -428
  269. package/dist/src/store/turns/memory-store.test.js +0 -9
  270. package/dist/src/store/turns/migrations.test.js +0 -138
  271. package/dist/src/store/turns/sqlite-store.test.js +0 -144
  272. package/dist/src/store/vectorIndex.test.js +0 -123
  273. package/dist/src/store.test.js +0 -161
  274. package/dist/src/supersede.test.js +0 -36
  275. package/dist/src/topics/cluster.test.js +0 -150
  276. package/dist/src/topics/store.test.js +0 -117
  277. package/dist/src/types.test.js +0 -9
  278. package/dist/src/vector-search-cache.test.js +0 -157
  279. package/dist/src/vectorStore.test.js +0 -479
  280. package/dist/src/wiki.test.js +0 -171
  281. package/extensions/conflict-scan.test.ts +0 -129
  282. package/extensions/dashboard-client/dist/assets/AchievementsTab-DjxfYrDc.js +0 -2
  283. package/extensions/dashboard-client/dist/assets/CacheTab-Dnpv94wq.js +0 -2
  284. package/extensions/dashboard-client/dist/assets/CacheTab-Dnpv94wq.js.map +0 -1
  285. package/extensions/dashboard-client/dist/assets/GameTab-BLDudcO5.js +0 -2
  286. package/extensions/dashboard-client/dist/assets/OverviewTab-D4kGvsiQ.js +0 -2
  287. package/extensions/dashboard-client/dist/assets/ReposTab-C0Wnrf2u.js +0 -2
  288. package/extensions/dashboard-client/dist/assets/ReposTab-C0Wnrf2u.js.map +0 -1
  289. package/extensions/dashboard-client/dist/assets/TimeSavedCard-UqljSyP-.js +0 -2
  290. package/extensions/dashboard-client/dist/assets/TimeSavedCard-UqljSyP-.js.map +0 -1
  291. package/extensions/dashboard-client/dist/assets/index-BEdKlpAB.css +0 -1
  292. package/extensions/dashboard-client/dist/assets/index-BX0bcgLu.js +0 -42
  293. package/extensions/dashboard-client/dist/assets/index-BX0bcgLu.js.map +0 -1
  294. package/extensions/dashboard-server/api-contracts.test.ts +0 -1065
  295. package/extensions/dashboard-server/html.test.ts +0 -62
  296. package/extensions/dashboard-server/perf-server.test.ts +0 -101
  297. package/extensions/dashboard-server/routes-cache.test.ts +0 -302
  298. package/extensions/dashboard-server/server.test.ts +0 -131
  299. package/extensions/dashboard-server-s32.test.ts +0 -195
  300. package/extensions/dashboard-server.test.ts +0 -382
  301. package/extensions/mega-cache-replay.test.ts +0 -526
  302. package/extensions/mega-compact-s38.test.ts +0 -1433
  303. package/extensions/mega-compact.test.ts +0 -1509
  304. package/extensions/mega-events.test.ts +0 -55
  305. package/extensions/mega-game-cmds.test.ts +0 -197
  306. package/extensions/mega-runtime/snapshot.test.ts +0 -139
  307. package/extensions/mega-runtime/state.test.ts +0 -262
  308. package/extensions/mega-runtime/widget.test.ts +0 -468
  309. package/extensions/mega-shutdown-widget.test.ts +0 -141
  310. package/extensions/mega-teamrun.test.ts +0 -176
  311. package/extensions/mega-trim.test.ts +0 -64
  312. package/extensions/mega-turn-store.test.ts +0 -108
  313. package/src/boundary.test.ts +0 -187
  314. package/src/compact.test.ts +0 -99
  315. package/src/config/themes.test.ts +0 -116
  316. package/src/dedup/dedup.test.ts +0 -46
  317. package/src/dedup/l1.test.ts +0 -57
  318. package/src/dedup/raptor/buildHistory.test.ts +0 -353
  319. package/src/dedup/raptor/multilevel-serve.test.ts +0 -273
  320. package/src/dedup/raptor/multilevel.test.ts +0 -325
  321. package/src/dedup/raptor/promote.test.ts +0 -82
  322. package/src/dedup/raptor/raptor.test.ts +0 -297
  323. package/src/dedup/raptor/retrieval.test.ts +0 -118
  324. package/src/dedup/raptor/serve-gate.test.ts +0 -348
  325. package/src/dedup/sprint12.test.ts +0 -242
  326. package/src/dedup-engine.test.ts +0 -670
  327. package/src/driftDetection.test.ts +0 -100
  328. package/src/e2e.test.ts +0 -843
  329. package/src/engine.test.ts +0 -123
  330. package/src/extractive.test.ts +0 -174
  331. package/src/fork.test.ts +0 -94
  332. package/src/game/scoring.test.ts +0 -103
  333. package/src/httpEmbedder.test.ts +0 -286
  334. package/src/importance.test.ts +0 -538
  335. package/src/intent.test.ts +0 -87
  336. package/src/log.test.ts +0 -47
  337. package/src/mechanical-fix.test.ts +0 -70
  338. package/src/memory.test.ts +0 -92
  339. package/src/memoryConsolidate.test.ts +0 -47
  340. package/src/memoryOps.test.ts +0 -148
  341. package/src/memoryRecall.test.ts +0 -194
  342. package/src/memoryRoundtrip.test.ts +0 -155
  343. package/src/metrics/turns.test.ts +0 -165
  344. package/src/mirror/mirror.test.ts +0 -240
  345. package/src/raptor-inject-summaries.test.ts +0 -228
  346. package/src/ratio.bench.test.ts +0 -1316
  347. package/src/recall.integration.test.ts +0 -96
  348. package/src/recall.test.ts +0 -467
  349. package/src/sprint14.test.ts +0 -245
  350. package/src/sprint4x-rag-verification.test.ts +0 -119
  351. package/src/store/compression.test.ts +0 -168
  352. package/src/store/memoryIndex.test.ts +0 -61
  353. package/src/store/migrate.test.ts +0 -158
  354. package/src/store/pgOpenGuard.test.ts +0 -89
  355. package/src/store/phase01.test.ts +0 -88
  356. package/src/store/phase04.test.ts +0 -58
  357. package/src/store/sprint10.test.ts +0 -214
  358. package/src/store/sqlite/game-achievements.test.ts +0 -80
  359. package/src/store/sqlite/game-scores.test.ts +0 -132
  360. package/src/store/sqlite/game-state.test.ts +0 -89
  361. package/src/store/sqlite/global-index.test.ts +0 -222
  362. package/src/store/sqlite/mechanical-fix.test.ts +0 -162
  363. package/src/store/sqlite/perf-samples.test.ts +0 -169
  364. package/src/store/sqlite/raptor.test.ts +0 -139
  365. package/src/store/sqlite/turns.test.ts +0 -218
  366. package/src/store/sqlite.cachehit.test.ts +0 -68
  367. package/src/store/sqlite.dbmaint.test.ts +0 -193
  368. package/src/store/sqlite.dbmirror.test.ts +0 -229
  369. package/src/store/turns/contract-compliance.test.ts +0 -562
  370. package/src/store/turns/memory-store.test.ts +0 -15
  371. package/src/store/turns/migrations.test.ts +0 -161
  372. package/src/store/turns/sqlite-store.test.ts +0 -161
  373. package/src/store/vectorIndex.test.ts +0 -140
  374. package/src/store.test.ts +0 -191
  375. package/src/supersede.test.ts +0 -42
  376. package/src/topics/cluster.test.ts +0 -176
  377. package/src/topics/store.test.ts +0 -132
  378. package/src/types.test.ts +0 -10
  379. package/src/vector-search-cache.test.ts +0 -190
  380. package/src/vectorStore.test.ts +0 -533
  381. package/src/wiki.test.ts +0 -217
@@ -0,0 +1,258 @@
1
+ /**
2
+ * recallMetrics.ts — S45 CRAG quality metrics for recall evaluation.
3
+ *
4
+ * Four metrics evaluate the quality of a set of recall hits:
5
+ * - relevance: average cosine similarity (SearchHit.score) across hits
6
+ * - coverage: fraction of query sub-terms found in at least one hit summary
7
+ * - diversity: average pairwise cosine distance among top-K embeddings (MMR-style)
8
+ * - specificity: inverse document-frequency proxy via average chunk length
9
+ *
10
+ * Pi-agnostic: no pi runtime types, no network calls (PREVENT-PI-004).
11
+ * All config is passed explicitly; defaults are documented but NOT hardcoded
12
+ * inside the metric functions (the four constants from S45A.5 are also received
13
+ * via config, not baked in).
14
+ *
15
+ * Best-effort: all functions handle degenerate inputs (empty hits, missing
16
+ * embeddings, zero query terms) and return sensible defaults instead of NaN.
17
+ */
18
+ import { cosineSimilarity } from "./embedder.js";
19
+ import { STOP_WORDS } from "./config/stopwords.js";
20
+ /** Default config — all values marked "uncalibrated" unless CRAG_CALIBRATED=true. */
21
+ export const DEFAULT_RECALL_QUALITY_CONFIG = {
22
+ minDiversity: 0.3,
23
+ minCoverage: 0.4,
24
+ minRelevance: 0.5,
25
+ minOverallScore: 0.4,
26
+ weights: { relevance: 0.35, coverage: 0.25, diversity: 0.25, specificity: 0.15 },
27
+ specificityDivisor: 300,
28
+ specificityOptimalMin: 100,
29
+ specificityOptimalMax: 500,
30
+ idfBroadenRatio: 0.5,
31
+ stopwords: STOP_WORDS,
32
+ calibrated: false,
33
+ };
34
+ // ---------------------------------------------------------------------------
35
+ // Metric helpers (exported individually for unit testing)
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * S45A.2: Relevance — average of SearchHit.score across all hits.
39
+ * Scores are already cosine similarities in [0, 1].
40
+ * Empty hits → 0.
41
+ */
42
+ export function computeRelevance(hits) {
43
+ if (hits.length === 0)
44
+ return 0;
45
+ let sum = 0;
46
+ for (const h of hits)
47
+ sum += h.score;
48
+ return sum / hits.length;
49
+ }
50
+ /**
51
+ * S45A.3: Coverage — fraction of query subterms present in at least one
52
+ * hit's `checkpoint.summary`. Tokenization removes stopwords and short terms.
53
+ * Zero query terms after filtering → coverage = 1.0 (trivially satisfied).
54
+ */
55
+ export function computeCoverage(query, hits, stopwords = STOP_WORDS) {
56
+ const queryTerms = tokenizeTerms(query, stopwords);
57
+ if (queryTerms.length === 0)
58
+ return 1.0;
59
+ if (hits.length === 0)
60
+ return 0;
61
+ let foundCount = 0;
62
+ for (const qt of queryTerms) {
63
+ const found = hits.some((h) => {
64
+ const summary = h.checkpoint.summary ?? "";
65
+ return summary.toLowerCase().includes(qt);
66
+ });
67
+ if (found)
68
+ foundCount++;
69
+ }
70
+ return foundCount / queryTerms.length;
71
+ }
72
+ /**
73
+ * S45A.4: Diversity — average pairwise cosine distance (1 - similarity)
74
+ * among hit embeddings. Filtered to hits with non-empty embedding arrays.
75
+ *
76
+ * Edge cases:
77
+ * - < 2 valid hits → 1.0 (nothing to compare)
78
+ * - empty-embedding checkpoints are skipped (no NaN path)
79
+ */
80
+ export function computeDiversity(hits) {
81
+ const valid = hits.filter((h) => Array.isArray(h.checkpoint.embedding) && h.checkpoint.embedding.length > 0);
82
+ if (valid.length < 2)
83
+ return 1.0;
84
+ let totalDist = 0;
85
+ let pairCount = 0;
86
+ for (let i = 0; i < valid.length; i++) {
87
+ for (let j = i + 1; j < valid.length; j++) {
88
+ const sim = cosineSimilarity(valid[i].checkpoint.embedding, valid[j].checkpoint.embedding);
89
+ // cosineSimilarity returns 0 for empty vectors, but we've filtered those out.
90
+ // NaN/Infinity could theoretically arise from degenerate floats; guard defensively.
91
+ const dist = Number.isFinite(sim) ? 1 - sim : 1.0;
92
+ totalDist += dist;
93
+ pairCount++;
94
+ }
95
+ }
96
+ return pairCount > 0 ? totalDist / pairCount : 1.0;
97
+ }
98
+ /**
99
+ * S45A.5: Specificity — average chunk length (tokenEstimate) as a proxy
100
+ * for information density.
101
+ *
102
+ * Formula (all constants from config, NOT hardcoded):
103
+ * avg = mean(tokenEstimate across hits; hits missing tokenEstimate → 0)
104
+ * If avg < optimalMin / 2 → 0 (too short to be specific)
105
+ * base = avg / divisor
106
+ * If avg > optimalMax:
107
+ * ramp = 1 - (avg - optimalMax) / optimalMax // linear to 0 at 2*optimalMax
108
+ * capped = max(0, ramp)
109
+ * specificity = min(base, capped)
110
+ * else:
111
+ * specificity = min(base, 1)
112
+ *
113
+ * Edge cases: empty hits → 1.0 (nothing to penalize).
114
+ */
115
+ export function computeSpecificity(hits, config) {
116
+ if (hits.length === 0)
117
+ return 1.0;
118
+ const { specificityDivisor: divisor, specificityOptimalMin: optMin, specificityOptimalMax: optMax } = config;
119
+ let sum = 0;
120
+ for (const h of hits) {
121
+ sum += h.checkpoint.tokenEstimate ?? 0;
122
+ }
123
+ const avg = sum / hits.length;
124
+ // Below optMin/2 → too short to be specific
125
+ if (avg < optMin / 2)
126
+ return 0;
127
+ const base = divisor > 0 ? avg / divisor : 0;
128
+ const clamped = base > 1 ? 1 : base;
129
+ if (avg > optMax) {
130
+ // Linear ramp-down from optMax to 0 at 2*optMax
131
+ const ramp = optMax > 0 ? 1 - (avg - optMax) / optMax : 0;
132
+ const capped = ramp > 0 ? ramp : 0;
133
+ return clamped < capped ? clamped : capped;
134
+ }
135
+ return clamped;
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // Composite evaluation
139
+ // ---------------------------------------------------------------------------
140
+ /**
141
+ * Normalize a four-weight vector so its components sum to 1.0.
142
+ * If all weights are 0, return equal weights (0.25 each).
143
+ * Used defensively since users may set non-sum-1 weights via env.
144
+ */
145
+ export function normalizeWeights(w) {
146
+ const sum = w.relevance + w.coverage + w.diversity + w.specificity;
147
+ if (sum === 0)
148
+ return { relevance: 0.25, coverage: 0.25, diversity: 0.25, specificity: 0.25 };
149
+ return {
150
+ relevance: w.relevance / sum,
151
+ coverage: w.coverage / sum,
152
+ diversity: w.diversity / sum,
153
+ specificity: w.specificity / sum,
154
+ };
155
+ }
156
+ /**
157
+ * S45A.6: Evaluate recall quality — compute all four metrics and return a
158
+ * composite result with pass/fail + recommendation.
159
+ */
160
+ export function evaluateRecall(query, hits, config) {
161
+ const weights = normalizeWeights(config.weights);
162
+ const breakdown = {
163
+ relevance: computeRelevance(hits),
164
+ coverage: computeCoverage(query, hits, config.stopwords),
165
+ diversity: computeDiversity(hits),
166
+ specificity: computeSpecificity(hits, config),
167
+ };
168
+ const score = weights.relevance * breakdown.relevance +
169
+ weights.coverage * breakdown.coverage +
170
+ weights.diversity * breakdown.diversity +
171
+ weights.specificity * breakdown.specificity;
172
+ const pass = score >= config.minOverallScore &&
173
+ breakdown.relevance >= config.minRelevance &&
174
+ breakdown.coverage >= config.minCoverage &&
175
+ breakdown.diversity >= config.minDiversity;
176
+ const recommendation = buildRecommendation(breakdown, score, config);
177
+ return {
178
+ pass,
179
+ score,
180
+ breakdown,
181
+ recommendation,
182
+ uncalibrated: !config.calibrated,
183
+ weights,
184
+ };
185
+ }
186
+ // ---------------------------------------------------------------------------
187
+ // Recommendation builder
188
+ // ---------------------------------------------------------------------------
189
+ /**
190
+ * Build a human-readable recommendation based on which metric is lowest.
191
+ * Uses env-overridable thresholds from config (not hardcoded).
192
+ */
193
+ function buildRecommendation(breakdown, score, config) {
194
+ // Check each metric against its threshold (lowest first for specificity).
195
+ const issues = [];
196
+ if (breakdown.diversity < config.minDiversity) {
197
+ issues.push("chunks are too similar — consider expanding K or using MMR");
198
+ }
199
+ if (breakdown.coverage < config.minCoverage) {
200
+ issues.push("query terms not well covered — consider query expansion");
201
+ }
202
+ if (breakdown.relevance < config.minRelevance) {
203
+ issues.push("chunks may be irrelevant — consider stricter threshold");
204
+ }
205
+ if (score < config.minOverallScore) {
206
+ issues.push("quality is low — recommend re-retrieval with expanded query");
207
+ }
208
+ return issues.length > 0 ? issues.join("; ") : null;
209
+ }
210
+ /**
211
+ * Compute recall quality metrics for a set of search hits.
212
+ *
213
+ * Pi-agnostic: takes query text and SearchHit results directly, no pi types.
214
+ * The `config` parameter is optional — when omitted, DEFAULT_RECALL_QUALITY_CONFIG
215
+ * is used (labelled uncalibrated).
216
+ *
217
+ * This is the primary export for external callers (e.g., recall.ts integration).
218
+ */
219
+ export function computeRecallMetrics(query, results, config) {
220
+ const effectiveConfig = {
221
+ ...DEFAULT_RECALL_QUALITY_CONFIG,
222
+ ...config,
223
+ // Merge weights shallowly if both sides exist
224
+ weights: {
225
+ ...DEFAULT_RECALL_QUALITY_CONFIG.weights,
226
+ ...(config?.weights ?? {}),
227
+ },
228
+ };
229
+ const result = evaluateRecall(query, results, effectiveConfig);
230
+ return {
231
+ breakdown: result.breakdown,
232
+ score: result.score,
233
+ pass: result.pass,
234
+ recommendation: result.recommendation,
235
+ uncalibrated: result.uncalibrated,
236
+ };
237
+ }
238
+ // ---------------------------------------------------------------------------
239
+ // Internal helpers
240
+ // ---------------------------------------------------------------------------
241
+ /**
242
+ * Tokenize text into lowercased alphanumeric terms, filtering stop words
243
+ * and terms shorter than 3 characters.
244
+ */
245
+ function tokenizeTerms(text, stopwords) {
246
+ return text
247
+ .toLowerCase()
248
+ .split(/[^a-z0-9]+/)
249
+ .filter((t) => t.length >= 3 && !stopwords.has(t));
250
+ }
251
+ /**
252
+ * Estimate block tokens for a piece of text (rough approximation).
253
+ * Used by the specificity metric fallback when tokenEstimate is missing.
254
+ */
255
+ export function estimateBlockTokens(text) {
256
+ // ~4 chars per token for English text
257
+ return Math.ceil(text.length / 4);
258
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * src/store/sqlite/cache-stripes.ts — Cache stripe distribution reader (A3, PLAN_V2 Phase 4).
3
+ *
4
+ * Reads from the cache_stripes table (schema.ts) to produce stripe distribution
5
+ * stats, stability aggregates, and a composite health score. Pi-agnostic.
6
+ *
7
+ * PREVENT-002: all queries use parameterized SQL.
8
+ */
9
+ import { getStateDir } from "../../store.js";
10
+ import { openStore } from "./utils.js";
11
+ /** Human-readable labels for each stripe tier (0=permanent .. 4=volatile). */
12
+ const STRIPE_LABELS = {
13
+ 0: "Permanent",
14
+ 1: "Epoch",
15
+ 2: "Topic",
16
+ 3: "Thread",
17
+ 4: "Volatile",
18
+ };
19
+ /** Weight per stripe for health scoring (higher = more stable = better). */
20
+ const STRIPE_HEALTH_WEIGHTS = {
21
+ 0: 1.0,
22
+ 1: 0.8,
23
+ 2: 0.5,
24
+ 3: 0.25,
25
+ 4: 0.0,
26
+ };
27
+ /**
28
+ * Read cache stripe distribution from the database.
29
+ *
30
+ * Returns aggregated per-stripe buckets with stability stats and a composite
31
+ * health score. Returns empty buckets (all zeros) when the table is empty,
32
+ * so the dashboard always has a valid response.
33
+ */
34
+ export function readCacheStripes(stateDir = getStateDir()) {
35
+ const db = openStore(stateDir);
36
+ // ── Fetch per-stripe aggregates ──
37
+ const rows = db
38
+ .prepare(`SELECT
39
+ stripe,
40
+ COUNT(*) AS count,
41
+ ROUND(AVG(stability), 4) AS avgStability,
42
+ ROUND(MIN(stability), 4) AS minStability,
43
+ ROUND(MAX(stability), 4) AS maxStability
44
+ FROM cache_stripes
45
+ GROUP BY stripe
46
+ ORDER BY stripe ASC`)
47
+ .all();
48
+ // ── Latest epoch ──
49
+ const epochRow = db
50
+ .prepare(`SELECT epoch_id, MAX(assigned_at) AS last_refresh
51
+ FROM cache_stripes
52
+ WHERE epoch_id IS NOT NULL`)
53
+ .get();
54
+ // ── Total chunk count ──
55
+ const totalRow = db
56
+ .prepare("SELECT COUNT(*) AS cnt FROM cache_stripes")
57
+ .get();
58
+ const totalChunks = totalRow?.cnt ?? 0;
59
+ // ── Build bucket array (always 5 entries, stripe 0–4) ──
60
+ const rowMap = new Map();
61
+ for (const r of rows)
62
+ rowMap.set(r.stripe, r);
63
+ const buckets = [];
64
+ for (let s = 0; s <= 4; s++) {
65
+ const r = rowMap.get(s);
66
+ buckets.push({
67
+ stripe: s,
68
+ label: STRIPE_LABELS[s] ?? `Stripe ${s}`,
69
+ count: r?.count ?? 0,
70
+ avgStability: r?.avgStability ?? 0,
71
+ minStability: r?.minStability ?? 0,
72
+ maxStability: r?.maxStability ?? 0,
73
+ });
74
+ }
75
+ // ── Compute health score ──
76
+ const health = computeCacheHealth(buckets, totalChunks, stateDir);
77
+ return {
78
+ buckets,
79
+ health,
80
+ epochId: epochRow?.epoch_id ?? null,
81
+ lastRefreshAt: epochRow?.last_refresh ?? 0,
82
+ totalChunks,
83
+ };
84
+ }
85
+ /**
86
+ * Compute cache health from bucket distribution.
87
+ *
88
+ * Score = weighted sum of stripe fractions (normalised 0..1).
89
+ * Dominant tier = fraction of chunks in the top 2 stripes (0+1).
90
+ * Label: >= 0.8 = good, >= 0.6 = fair, >= 0.4 = degraded, else poor.
91
+ */
92
+ function computeCacheHealth(buckets, total, stateDir) {
93
+ if (total === 0) {
94
+ return { score: 1, label: "good", dominantTier: 1, churnRate: 0 };
95
+ }
96
+ let weightedSum = 0;
97
+ let dominantCount = 0;
98
+ for (const b of buckets) {
99
+ const w = STRIPE_HEALTH_WEIGHTS[b.stripe] ?? 0;
100
+ weightedSum += (b.count / total) * w;
101
+ if (b.stripe <= 1)
102
+ dominantCount += b.count;
103
+ }
104
+ const dominantTier = dominantCount / total;
105
+ // ── Churn rate: open a new DB connection for the churn query ──
106
+ let churnRate = 0;
107
+ try {
108
+ const churnDb = openStore(stateDir);
109
+ const churnRow = churnDb
110
+ .prepare(`WITH
111
+ ranked AS (
112
+ SELECT chunk_id, epoch_id,
113
+ ROW_NUMBER() OVER (PARTITION BY chunk_id ORDER BY assigned_at DESC) AS rn
114
+ FROM cache_stripes
115
+ ),
116
+ cur AS (SELECT chunk_id, epoch_id FROM ranked WHERE rn = 1),
117
+ prev AS (SELECT chunk_id, epoch_id FROM ranked WHERE rn = 2)
118
+ SELECT
119
+ COUNT(*) AS total_changed
120
+ FROM cur c
121
+ JOIN prev p ON c.chunk_id = p.chunk_id AND c.epoch_id != p.epoch_id`)
122
+ .get();
123
+ if (churnRow && total > 0) {
124
+ churnRate = Math.min(1, (churnRow.total_changed ?? 0) / total);
125
+ }
126
+ }
127
+ catch {
128
+ // churn unavailable — keep 0
129
+ }
130
+ const score = Math.round(weightedSum * 100) / 100;
131
+ const label = score >= 0.8 ? "good" : score >= 0.6 ? "fair" : score >= 0.4 ? "degraded" : "poor";
132
+ return { score, label, dominantTier, churnRate };
133
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * fts5-search.ts — FTS5 trigram search helper (L1 tier in the S44 tiered router).
3
+ *
4
+ * Runs BM25-ranked trigram search against the existing `context_chunks_trgm`
5
+ * virtual table (pg_trgm-equivalent, created in schema.ts). All queries are
6
+ * parameterized (PREVENT-002). Pi-agnostic — pure SQL over a node:sqlite
7
+ * DatabaseSync handle.
8
+ *
9
+ * The virtual table stores (id UNINDEXED, normalized_text) with tokenize='trigram'.
10
+ * FTS5 trigram queries tokenize the user query into overlapping 3-grams at query
11
+ * time automatically — no manual n-gram splitting needed. The BM25 rank from
12
+ * fts5 ranks results by trigram-overlap density.
13
+ */
14
+ /**
15
+ * Search the context_chunks_trgm FTS5 table for checkpoints whose
16
+ * `normalized_text` matches `query` via trigram similarity.
17
+ *
18
+ * @param query Raw search text (FTS5 trigram tokenizer handles n-gram splitting
19
+ * automatically; input is NOT SQL-concatenated — bound as ? param).
20
+ * @param reader A sync-reader handle against the SQLite store.
21
+ * @param limit Max results to return (default 10).
22
+ * @returns BM25-ranked hits, highest score first.
23
+ */
24
+ export function fts5Search(query, reader, limit = 10) {
25
+ const sql = `
26
+ SELECT id, bm25(context_chunks_trgm, 0.0, 1.0) AS score
27
+ FROM context_chunks_trgm
28
+ WHERE context_chunks_trgm MATCH ?
29
+ ORDER BY score
30
+ LIMIT ?
31
+ `;
32
+ const rows = reader
33
+ .prepare(sql)
34
+ .all(query, limit);
35
+ return rows.map((r) => ({ id: r.id, score: r.score }));
36
+ }
37
+ /**
38
+ * Search with an optional session_id filter for scoped FTS5 recall.
39
+ *
40
+ * JOINs the FTS5 virtual table against the real `context_chunks` table on `id`
41
+ * so we can filter by `session_id` while still using FTS5 MATCH. The vtab
42
+ * stores `id UNINDEXED` (it's a row-key passthrough, not searchable text), so
43
+ * the join is safe and efficient via the `idx_chunks_pk` index.
44
+ *
45
+ * @param query Raw search text.
46
+ * @param reader Sync-reader handle against the SQLite store.
47
+ * @param sessionId Scope to a specific session (omit for cross-session search).
48
+ * @param limit Max results (default 10).
49
+ */
50
+ export function fts5SearchScoped(query, reader, sessionId, limit = 10) {
51
+ if (sessionId) {
52
+ const sql = `
53
+ SELECT t.id, bm25(context_chunks_trgm, 0.0, 1.0) AS score
54
+ FROM context_chunks_trgm AS t
55
+ INNER JOIN context_chunks AS c ON c.id = t.id
56
+ WHERE t.context_chunks_trgm MATCH ?
57
+ AND c.session_id = ?
58
+ ORDER BY score
59
+ LIMIT ?
60
+ `;
61
+ const rows = reader
62
+ .prepare(sql)
63
+ .all(query, sessionId, limit);
64
+ return rows.map((r) => ({ id: r.id, score: r.score }));
65
+ }
66
+ // No session filter — plain FTS5 search across all sessions.
67
+ return fts5Search(query, reader, limit);
68
+ }
@@ -21,12 +21,14 @@ export const PERF_KINDS = [
21
21
  "provider_latency_ms",
22
22
  "tps",
23
23
  "cache_hit_pct",
24
+ "prefix_break",
24
25
  "rss_mb",
25
26
  "heap_mb",
26
27
  "cpu_user_ms",
27
28
  "cpu_sys_ms",
28
29
  "db_recompute_ms",
29
30
  "disk_write_ms",
31
+ "cache_health",
30
32
  ];
31
33
  function isPerfKind(k) {
32
34
  return PERF_KINDS.includes(k);
@@ -80,20 +82,39 @@ export function readPerfSamples(stateDir = getStateDir(), sinceTs = 0, kind) {
80
82
  return out;
81
83
  }
82
84
  /**
83
- * Read lifetime provider prompt cache aggregates from `perf_samples`.
84
- *
85
- * Aggregates `cache_creation_input_tokens` / `cache_read_input_tokens` from
86
- * the `meta` JSON column of `cache_hit_pct` samples (`json_extract`,
87
- * available in node:sqlite ≥22.13 / SQLite ≥3.38). Returns a zeroed
88
- * `ProviderCacheLifetime` when no samples exist (never undefined).
85
+ * Extract a model label from parsed meta (modelName or modelId), or null.
89
86
  */
90
- export function readProviderCacheLifetime(stateDir = getStateDir()) {
91
- const db = openStore(stateDir);
92
- const rows = db
93
- .prepare(`SELECT ts, meta FROM perf_samples
94
- WHERE kind = ?
95
- ORDER BY ts ASC`)
96
- .all("cache_hit_pct");
87
+ function extractModelLabel(meta) {
88
+ if (typeof meta.modelName === "string" && meta.modelName.length > 0)
89
+ return meta.modelName;
90
+ if (typeof meta.modelId === "string" && meta.modelId.length > 0)
91
+ return meta.modelId;
92
+ return null;
93
+ }
94
+ /**
95
+ * Build sorted byModel array from the model aggregation map.
96
+ */
97
+ function byModelFromMap(map) {
98
+ const out = [];
99
+ for (const [model, m] of map) {
100
+ out.push({
101
+ model,
102
+ hitPct: m.hitCount > 0 ? m.sumHitPct / m.hitCount : 0,
103
+ totalCacheRead: m.sumCr,
104
+ totalCacheWrite: m.sumCw,
105
+ sampleCount: m.samples,
106
+ });
107
+ }
108
+ out.sort((a, b) => b.sampleCount - a.sampleCount);
109
+ return out;
110
+ }
111
+ /**
112
+ * Aggregate `cache_hit_pct` rows (ts + parsed meta) into a
113
+ * `ProviderCacheLifetime`. Shared by the lifetime and window readers so both
114
+ * use the identical aggregation formula. `meta` is parsed defensively
115
+ * (PREVENT-001 null-safe). Rows must be ascending by ts.
116
+ */
117
+ function aggregateCacheRows(rows) {
97
118
  if (rows.length === 0) {
98
119
  return {
99
120
  sampleCount: 0,
@@ -103,6 +124,7 @@ export function readProviderCacheLifetime(stateDir = getStateDir()) {
103
124
  totalInput: 0,
104
125
  firstSampleAt: null,
105
126
  latestSampleAt: null,
127
+ byModel: [],
106
128
  };
107
129
  }
108
130
  let totalCacheRead = 0;
@@ -110,6 +132,7 @@ export function readProviderCacheLifetime(stateDir = getStateDir()) {
110
132
  let totalInput = 0;
111
133
  let sumHitPct = 0;
112
134
  let hitPctCount = 0;
135
+ const modelMap = new Map();
113
136
  for (const r of rows) {
114
137
  if (r.meta != null) {
115
138
  let meta = null;
@@ -140,6 +163,31 @@ export function readProviderCacheLifetime(stateDir = getStateDir()) {
140
163
  sumHitPct += (cr / denom) * 100;
141
164
  hitPctCount++;
142
165
  }
166
+ // Per-model grouping (F4)
167
+ const modelLabel = extractModelLabel(meta);
168
+ if (modelLabel != null) {
169
+ let m = modelMap.get(modelLabel);
170
+ if (!m) {
171
+ m = {
172
+ sumCr: 0,
173
+ sumCw: 0,
174
+ sumInp: 0,
175
+ sumHitPct: 0,
176
+ hitCount: 0,
177
+ samples: 0,
178
+ };
179
+ modelMap.set(modelLabel, m);
180
+ }
181
+ m.sumCr += cr;
182
+ m.sumCw += cw;
183
+ m.sumInp += inp;
184
+ m.samples++;
185
+ if (denom > 0) {
186
+ m.sumHitPct += (cr / denom) * 100;
187
+ m.hitCount++;
188
+ }
189
+ }
190
+ // Untagged samples are omitted from byModel
143
191
  }
144
192
  }
145
193
  }
@@ -151,8 +199,43 @@ export function readProviderCacheLifetime(stateDir = getStateDir()) {
151
199
  totalInput,
152
200
  firstSampleAt: new Date(rows[0].ts).toISOString(),
153
201
  latestSampleAt: new Date(rows[rows.length - 1].ts).toISOString(),
202
+ byModel: byModelFromMap(modelMap),
154
203
  };
155
204
  }
205
+ /**
206
+ * Read lifetime provider prompt cache aggregates from `perf_samples`.
207
+ *
208
+ * Aggregates `cache_creation_input_tokens` / `cache_read_input_tokens` from
209
+ * the `meta` JSON column of `cache_hit_pct` samples (`json_extract`,
210
+ * available in node:sqlite >=22.13 / SQLite >=3.38). Returns a zeroed
211
+ * `ProviderCacheLifetime` when no samples exist (never undefined).
212
+ */
213
+ export function readProviderCacheLifetime(stateDir = getStateDir()) {
214
+ const db = openStore(stateDir);
215
+ const rows = db
216
+ .prepare(`SELECT ts, meta FROM perf_samples
217
+ WHERE kind = ?
218
+ ORDER BY ts ASC`)
219
+ .all("cache_hit_pct");
220
+ return aggregateCacheRows(rows);
221
+ }
222
+ /**
223
+ * Read provider prompt cache aggregates over a trailing window of `minutes`
224
+ * (samples with ts >= now - minutes*60_000). Same aggregate shape as
225
+ * `readProviderCacheLifetime`; the existing lifetime reader is untouched.
226
+ * Returns a zeroed `ProviderCacheLifetime` when no samples fall in the window.
227
+ */
228
+ export function readProviderCacheWindow(stateDir = getStateDir(), minutes) {
229
+ const db = openStore(stateDir);
230
+ const sinceTs = Date.now() - minutes * 60_000;
231
+ const rows = db
232
+ .prepare(`SELECT ts, meta FROM perf_samples
233
+ WHERE kind = ? AND ts >= ?
234
+ ORDER BY ts ASC`)
235
+ .all("cache_hit_pct", sinceTs);
236
+ return aggregateCacheRows(rows);
237
+ }
238
+ // ... keep every function below unchanged ...
156
239
  /**
157
240
  * Read the most recent `cache_hit_pct` value from `perf_samples`.
158
241
  *
@@ -164,9 +247,9 @@ export function readLatestCacheHitPct(stateDir = getStateDir()) {
164
247
  const db = openStore(stateDir);
165
248
  const row = db
166
249
  .prepare(`SELECT value FROM perf_samples
167
- WHERE kind = ?
168
- ORDER BY ts DESC, id DESC
169
- LIMIT 1`)
250
+ WHERE kind = ?
251
+ ORDER BY ts DESC, id DESC
252
+ LIMIT 1`)
170
253
  .get("cache_hit_pct");
171
254
  if (!row || typeof row.value !== "number" || !Number.isFinite(row.value))
172
255
  return 0;
@@ -183,3 +266,49 @@ export function readLatestCacheHitPct(stateDir = getStateDir()) {
183
266
  export function readProviderCacheForRepo(stateDir) {
184
267
  return readProviderCacheLifetime(stateDir);
185
268
  }
269
+ /**
270
+ * Read `prefix_break` samples in a time window.
271
+ *
272
+ * @param stateDir State directory for this repo.
273
+ * @param sinceTs Lower bound (epoch ms). Pass 0 for no lower bound.
274
+ * @param untilTs Upper bound (epoch ms). Pass 0 for no upper bound.
275
+ * Rows with ts > untilTs are excluded.
276
+ * @returns Sorted ascending array of PrefixBreak rows.
277
+ *
278
+ * SQL is fully parameterized (PREVENT-002). `meta` is parsed defensively
279
+ * (PREVENT-001 null-safe). When no rows exist the return is an empty array.
280
+ */
281
+ export function readPrefixBreaks(stateDir = getStateDir(), sinceTs = 0, untilTs = 0) {
282
+ const db = openStore(stateDir);
283
+ let rows;
284
+ if (untilTs > 0) {
285
+ const sql = `SELECT id, ts, value, meta FROM perf_samples
286
+ WHERE kind = ? AND ts >= ? AND ts <= ? ORDER BY ts ASC`;
287
+ rows = db.prepare(sql).all("prefix_break", sinceTs, untilTs);
288
+ }
289
+ else {
290
+ const sql = `SELECT id, ts, value, meta FROM perf_samples
291
+ WHERE kind = ? AND ts >= ? ORDER BY ts ASC`;
292
+ rows = db.prepare(sql).all("prefix_break", sinceTs);
293
+ }
294
+ const out = [];
295
+ for (const r of rows) {
296
+ let meta = null;
297
+ if (r.meta != null) {
298
+ try {
299
+ meta = JSON.parse(r.meta);
300
+ }
301
+ catch {
302
+ meta = null;
303
+ }
304
+ }
305
+ out.push({
306
+ id: r.id,
307
+ ts: r.ts,
308
+ kind: "prefix_break",
309
+ value: r.value,
310
+ meta: meta,
311
+ });
312
+ }
313
+ return out;
314
+ }