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