claude-memory-layer 1.0.27 → 1.0.28

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 (329) hide show
  1. package/.env.example +7 -0
  2. package/AGENTS.md +11 -0
  3. package/README.md +184 -41
  4. package/benchmarks/replay/anonymized-real-sessions.json +48 -0
  5. package/dist/cli/index.js +10097 -6003
  6. package/dist/cli/index.js.map +4 -4
  7. package/dist/core/index.js +9745 -5587
  8. package/dist/core/index.js.map +4 -4
  9. package/dist/hooks/post-tool-use.js +6545 -5270
  10. package/dist/hooks/post-tool-use.js.map +4 -4
  11. package/dist/hooks/semantic-daemon.js +6646 -5354
  12. package/dist/hooks/semantic-daemon.js.map +4 -4
  13. package/dist/hooks/session-end.js +6618 -5347
  14. package/dist/hooks/session-end.js.map +4 -4
  15. package/dist/hooks/session-start.js +6619 -5354
  16. package/dist/hooks/session-start.js.map +4 -4
  17. package/dist/hooks/stop.js +6614 -5325
  18. package/dist/hooks/stop.js.map +4 -4
  19. package/dist/hooks/user-prompt-submit.js +6702 -5356
  20. package/dist/hooks/user-prompt-submit.js.map +4 -4
  21. package/dist/index.js +13537 -0
  22. package/dist/index.js.map +7 -0
  23. package/dist/mcp/index.js +20770 -0
  24. package/dist/mcp/index.js.map +7 -0
  25. package/dist/server/api/index.js +6632 -5319
  26. package/dist/server/api/index.js.map +4 -4
  27. package/dist/server/index.js +6667 -5340
  28. package/dist/server/index.js.map +4 -4
  29. package/dist/services/memory-service.js +6568 -5350
  30. package/dist/services/memory-service.js.map +4 -4
  31. package/dist/ui/assets/js/bootstrap.js +244 -0
  32. package/dist/ui/assets/js/chat.js +373 -0
  33. package/dist/ui/assets/js/disclosure.js +232 -0
  34. package/dist/ui/assets/js/modals.js +298 -0
  35. package/dist/ui/assets/js/overview.js +655 -0
  36. package/dist/ui/assets/js/state.js +72 -0
  37. package/dist/ui/assets/js/views.js +468 -0
  38. package/dist/ui/index.html +43 -1
  39. package/dist/ui/index.ts +3 -0
  40. package/dist/ui/style.css +222 -0
  41. package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +627 -0
  42. package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +440 -0
  43. package/docs/MEMORY_USEFULNESS_AUDIT.md +371 -0
  44. package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +80 -0
  45. package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +333 -0
  46. package/docs/PRODUCT_VALIDATION_MATRIX.md +82 -0
  47. package/docs/PROJECT_STRUCTURE_ANALYSIS.md +421 -0
  48. package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +501 -0
  49. package/docs/REFACTORING_PLAN_THIN_CORE.md +414 -0
  50. package/docs/REFERENCE_PROJECT_ANALYSES.md +25 -0
  51. package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +452 -0
  52. package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +446 -0
  53. package/docs/architecture/comparison-index.md +47 -0
  54. package/docs/reports/codex-real-data-validation-20260505T040447Z.md +46 -0
  55. package/package.json +9 -5
  56. package/scripts/build.ts +25 -8
  57. package/scripts/generate-session-qrels.ts +126 -0
  58. package/scripts/replay-retrieval-benchmark.ts +69 -0
  59. package/specs/thin-core-refactor/context.md +275 -0
  60. package/specs/thin-core-refactor/plan.md +536 -0
  61. package/specs/thin-core-refactor/spec.md +465 -0
  62. package/src/adapters/claude/capture/index.ts +3 -0
  63. package/src/adapters/claude/context/index.ts +3 -0
  64. package/src/adapters/claude/hooks/index.ts +21 -0
  65. package/src/adapters/claude/hooks/post-tool-use.ts +239 -0
  66. package/src/adapters/claude/hooks/prompt-injection-policy.ts +104 -0
  67. package/src/adapters/claude/hooks/semantic-daemon-client.ts +209 -0
  68. package/src/adapters/claude/hooks/semantic-daemon.ts +283 -0
  69. package/src/adapters/claude/hooks/session-end.ts +59 -0
  70. package/src/adapters/claude/hooks/session-start.ts +73 -0
  71. package/src/adapters/claude/hooks/stop.ts +128 -0
  72. package/src/adapters/claude/hooks/user-prompt-submit.ts +361 -0
  73. package/src/adapters/claude/index.ts +4 -0
  74. package/src/adapters/claude/transcript/index.ts +4 -0
  75. package/src/adapters/claude/transcript/transcript-reader.ts +57 -0
  76. package/src/adapters/claude/transcript/turn-reconstructor.ts +65 -0
  77. package/src/apps/cli/claude-settings-hooks.ts +138 -0
  78. package/src/apps/cli/codex-import-runner.ts +125 -0
  79. package/src/apps/cli/codex-validation-output.ts +95 -0
  80. package/src/apps/cli/hermes-import-runner.ts +130 -0
  81. package/src/apps/cli/hermes-validation-output.ts +91 -0
  82. package/src/apps/cli/index.ts +1731 -0
  83. package/src/apps/cli/mcp-install.ts +106 -0
  84. package/src/apps/cli/retrieval-disclosure-output.ts +196 -0
  85. package/src/apps/dashboard/assets/js/bootstrap.js +244 -0
  86. package/src/apps/dashboard/assets/js/chat.js +373 -0
  87. package/src/apps/dashboard/assets/js/disclosure.js +232 -0
  88. package/src/apps/dashboard/assets/js/modals.js +298 -0
  89. package/src/apps/dashboard/assets/js/overview.js +655 -0
  90. package/src/apps/dashboard/assets/js/state.js +72 -0
  91. package/src/apps/dashboard/assets/js/views.js +468 -0
  92. package/src/{ui → apps/dashboard}/index.html +43 -1
  93. package/src/apps/dashboard/index.ts +3 -0
  94. package/src/{ui → apps/dashboard}/style.css +222 -0
  95. package/src/apps/index.ts +5 -0
  96. package/src/apps/server/api/chat.ts +244 -0
  97. package/src/apps/server/api/citations.ts +105 -0
  98. package/src/apps/server/api/events.ts +137 -0
  99. package/src/apps/server/api/health.ts +53 -0
  100. package/src/apps/server/api/index.ts +26 -0
  101. package/src/apps/server/api/projects.ts +74 -0
  102. package/src/apps/server/api/search.ts +184 -0
  103. package/src/apps/server/api/sessions.ts +115 -0
  104. package/src/apps/server/api/stats.ts +723 -0
  105. package/src/apps/server/api/turns.ts +143 -0
  106. package/src/apps/server/api/utils.ts +65 -0
  107. package/src/apps/server/index.ts +111 -0
  108. package/src/cli/index.ts +2 -1311
  109. package/src/cli/retrieval-disclosure-output.ts +2 -0
  110. package/src/compat/index.ts +5 -0
  111. package/src/core/derive/fact-deriver.ts +170 -0
  112. package/src/core/derive/index.ts +2 -0
  113. package/src/core/derive/summary-deriver.ts +76 -0
  114. package/src/core/embedder.ts +4 -152
  115. package/src/core/engine/embedding-maintenance-service.ts +187 -0
  116. package/src/core/engine/endless-memory-services.ts +4 -0
  117. package/src/core/engine/index.ts +19 -0
  118. package/src/core/engine/memory-engine-services.ts +170 -0
  119. package/src/core/engine/memory-ingest-service.ts +317 -0
  120. package/src/core/engine/memory-query-service.ts +173 -0
  121. package/src/core/engine/memory-runtime-service.ts +162 -0
  122. package/src/core/engine/memory-service-composition.ts +231 -0
  123. package/src/core/engine/retrieval-analytics-service.ts +181 -0
  124. package/src/core/engine/retrieval-disclosure-service.ts +420 -0
  125. package/src/core/engine/retrieval-orchestrator.ts +377 -0
  126. package/src/core/engine/retrieval-services.ts +176 -0
  127. package/src/core/engine/shared-memory-services.ts +4 -0
  128. package/src/core/entity-repo.ts +1 -3
  129. package/src/core/event-store.ts +3 -3
  130. package/src/core/evidence-aligner.ts +2 -2
  131. package/src/core/external-market-context.ts +582 -0
  132. package/src/core/graduation.ts +2 -3
  133. package/src/core/index.ts +21 -0
  134. package/src/core/matcher.ts +2 -4
  135. package/src/core/model/memory-fact.ts +30 -0
  136. package/src/core/model/memory-rule.ts +14 -0
  137. package/src/core/model/memory-summary.ts +21 -0
  138. package/src/core/model/raw-event.ts +28 -0
  139. package/src/core/model/retrieval-result.ts +35 -0
  140. package/src/core/privacy/filter.ts +21 -10
  141. package/src/core/product-validation-matrix.ts +314 -0
  142. package/src/core/progressive-retriever.ts +1 -2
  143. package/src/core/registry/project-path.ts +54 -0
  144. package/src/core/registry/session-registry.ts +69 -0
  145. package/src/core/replay-evaluator.ts +625 -0
  146. package/src/core/retrieval-benchmark.ts +117 -0
  147. package/src/core/retrieval-quality.ts +109 -0
  148. package/src/core/retriever.ts +53 -15
  149. package/src/core/session-qrels.ts +360 -0
  150. package/src/core/shared-event-store.ts +1 -1
  151. package/src/core/sqlite-event-store.ts +35 -11
  152. package/src/core/task/blocker-resolver.ts +2 -2
  153. package/src/core/task/task-resolver.ts +0 -1
  154. package/src/core/vector-outbox.ts +1 -10
  155. package/src/core/vector-worker.ts +1 -1
  156. package/src/extensions/endless-memory/endless-memory-services.ts +350 -0
  157. package/src/extensions/endless-memory/index.ts +1 -0
  158. package/src/extensions/index.ts +5 -0
  159. package/src/extensions/mcp/handlers.ts +960 -0
  160. package/src/extensions/mcp/index.ts +48 -0
  161. package/src/extensions/mcp/tools.ts +252 -0
  162. package/src/extensions/shared-memory/index.ts +1 -0
  163. package/src/extensions/shared-memory/shared-memory-services.ts +211 -0
  164. package/src/extensions/vector/embedder.ts +197 -0
  165. package/src/extensions/vector/index.ts +1 -0
  166. package/src/hooks/post-tool-use.ts +3 -236
  167. package/src/hooks/semantic-daemon-client.ts +1 -208
  168. package/src/hooks/semantic-daemon.ts +6 -271
  169. package/src/hooks/session-end.ts +4 -79
  170. package/src/hooks/session-start.ts +4 -73
  171. package/src/hooks/stop.ts +3 -173
  172. package/src/hooks/user-prompt-submit.ts +3 -338
  173. package/src/index.ts +13 -0
  174. package/src/mcp/handlers.ts +2 -212
  175. package/src/mcp/index.ts +3 -46
  176. package/src/mcp/tools.ts +2 -78
  177. package/src/server/api/chat.ts +2 -244
  178. package/src/server/api/citations.ts +2 -105
  179. package/src/server/api/events.ts +2 -137
  180. package/src/server/api/health.ts +2 -53
  181. package/src/server/api/index.ts +2 -26
  182. package/src/server/api/projects.ts +2 -74
  183. package/src/server/api/search.ts +2 -102
  184. package/src/server/api/sessions.ts +2 -115
  185. package/src/server/api/stats.ts +2 -724
  186. package/src/server/api/turns.ts +2 -143
  187. package/src/server/api/utils.ts +2 -46
  188. package/src/server/index.ts +2 -100
  189. package/src/services/bootstrap-organizer.ts +46 -26
  190. package/src/services/codex-session-history-importer.ts +521 -29
  191. package/src/services/hermes-session-history-importer.ts +733 -0
  192. package/src/services/memory-service-config.ts +36 -0
  193. package/src/services/memory-service-registry.ts +150 -0
  194. package/src/services/memory-service.ts +211 -1325
  195. package/src/services/session-history-importer.ts +58 -14
  196. package/tests/README.md +23 -0
  197. package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +54 -0
  198. package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +98 -0
  199. package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +99 -0
  200. package/tests/apps/app-layer-boundary.test.ts +48 -0
  201. package/tests/apps/claude-settings-hooks.test.ts +107 -0
  202. package/tests/apps/cli-disclosure-output.test.ts +212 -0
  203. package/tests/apps/codex-import-runner.test.ts +99 -0
  204. package/tests/apps/codex-validation-output.test.ts +100 -0
  205. package/tests/apps/hermes-import-runner.test.ts +99 -0
  206. package/tests/apps/mcp-install-command.test.ts +59 -0
  207. package/tests/apps/package-build-entrypoints.test.ts +30 -0
  208. package/tests/apps/search-api-disclosure.test.ts +162 -0
  209. package/tests/apps/stats-api-lightweight.test.ts +67 -0
  210. package/tests/apps/ui-disclosure-output.test.ts +140 -0
  211. package/tests/{bootstrap-organizer.test.ts → core/bootstrap-organizer.test.ts} +1 -1
  212. package/tests/{canonical-key.test.ts → core/canonical-key.test.ts} +1 -1
  213. package/tests/core/codex-session-history-importer-validation.test.ts +185 -0
  214. package/tests/{consolidation-worker.test.ts → core/consolidation-worker.test.ts} +2 -2
  215. package/tests/core/embedding-maintenance-service.test.ts +282 -0
  216. package/tests/{evidence-aligner.test.ts → core/evidence-aligner.test.ts} +1 -1
  217. package/tests/core/external-market-context.test.ts +209 -0
  218. package/tests/core/fact-deriver.test.ts +79 -0
  219. package/tests/core/hermes-session-history-importer-validation.test.ts +609 -0
  220. package/tests/{ingest-interceptor.test.ts → core/ingest-interceptor.test.ts} +1 -1
  221. package/tests/{markdown-mirror.test.ts → core/markdown-mirror.test.ts} +2 -2
  222. package/tests/{matcher.test.ts → core/matcher.test.ts} +1 -1
  223. package/tests/{md-mirror.test.ts → core/md-mirror.test.ts} +2 -2
  224. package/tests/core/memory-engine-services.test.ts +240 -0
  225. package/tests/core/memory-ingest-service.test.ts +296 -0
  226. package/tests/core/memory-query-service.test.ts +129 -0
  227. package/tests/core/memory-runtime-service.test.ts +201 -0
  228. package/tests/core/memory-service-composition.test.ts +192 -0
  229. package/tests/core/memory-service-config.test.ts +41 -0
  230. package/tests/core/memory-service-facade.test.ts +30 -0
  231. package/tests/core/memory-service-registry.test.ts +206 -0
  232. package/tests/core/product-validation-matrix.test.ts +61 -0
  233. package/tests/core/project-registry.test.ts +78 -0
  234. package/tests/core/replay-evaluator.test.ts +181 -0
  235. package/tests/core/retrieval-analytics-service.test.ts +210 -0
  236. package/tests/core/retrieval-benchmark.test.ts +93 -0
  237. package/tests/core/retrieval-disclosure-service.test.ts +264 -0
  238. package/tests/core/retrieval-orchestrator.test.ts +403 -0
  239. package/tests/core/retrieval-quality.test.ts +31 -0
  240. package/tests/core/retrieval-services.test.ts +185 -0
  241. package/tests/{retriever-fallback-chain.test.ts → core/retriever-fallback-chain.test.ts} +3 -3
  242. package/tests/{retriever-strategy-scope.test.ts → core/retriever-strategy-scope.test.ts} +70 -3
  243. package/tests/{retriever.memu-adoption.test.ts → core/retriever.memu-adoption.test.ts} +3 -3
  244. package/tests/core/session-history-importer-filter.test.ts +78 -0
  245. package/tests/core/session-qrels.test.ts +250 -0
  246. package/tests/{sqlite-event-store-replication.test.ts → core/sqlite-event-store-replication.test.ts} +36 -1
  247. package/tests/core/summary-deriver.test.ts +66 -0
  248. package/tests/extensions/embedder-warning-suppression.test.ts +53 -0
  249. package/tests/extensions/endless-memory-extension-boundary.test.ts +17 -0
  250. package/tests/extensions/endless-memory-services.test.ts +325 -0
  251. package/tests/extensions/mcp-context-tools.test.ts +905 -0
  252. package/tests/extensions/mcp-extension-boundary.test.ts +21 -0
  253. package/tests/extensions/mcp-package-build.test.ts +22 -0
  254. package/tests/extensions/mcp-project-aware-tools.test.ts +102 -0
  255. package/tests/extensions/shared-memory-extension-boundary.test.ts +24 -0
  256. package/tests/extensions/shared-memory-services.test.ts +309 -0
  257. package/tests/extensions/vector-extension-boundary.test.ts +21 -0
  258. package/.claude/settings.local.json +0 -25
  259. package/.npm-cache/_cacache/content-v2/sha512/04/76/c098f88dfe584a2b80870bff7421b05d17d3d9ee1027f77772332a22d3f93a9a57101a2855107f6ad82077a818bba912b2bc317f2361b5ddb09ad284d9ce +0 -0
  260. package/.npm-cache/_cacache/content-v2/sha512/60/25/d2ecd39cfc7cab58351162814be77f935c6d6491c10c3745d456da7ddb2117ffd90c10e53fe3c0f1ed16b403307841543634504398b16ee4e6b6dd8e0c45 +0 -0
  261. package/.npm-cache/_cacache/index-v5/2b/9a/7f8f40206ed8a2e0a84efaa953ccaed1f5d001e14b931083f2e7a0738007 +0 -2
  262. package/.npm-cache/_cacache/index-v5/2e/d9/fcfa5c6a6abdc2a3644ab84a95936047298c465a2f47ee03db8f7fe1e946 +0 -3
  263. package/.npm-cache/_cacache/index-v5/a9/42/e519633356d12d3d2f19da66a8301016d496c8f5c3e0554124aaa62dc043 +0 -2
  264. package/.npm-cache/_logs/2026-02-26T12_04_52_729Z-debug-0.log +0 -256
  265. package/.npm-cache/_logs/2026-02-26T12_05_36_835Z-debug-0.log +0 -18
  266. package/.npm-cache/_logs/2026-02-26T12_05_45_982Z-debug-0.log +0 -32
  267. package/.npm-cache/_logs/2026-02-26T12_05_48_515Z-debug-0.log +0 -260
  268. package/.npm-cache/_logs/2026-02-26T12_05_53_567Z-debug-0.log +0 -69
  269. package/.npm-cache/_update-notifier-last-checked +0 -0
  270. package/bootstrap-kb/decisions/decisions.md +0 -244
  271. package/bootstrap-kb/glossary/glossary.md +0 -46
  272. package/bootstrap-kb/modules/.claude-plugin.md +0 -22
  273. package/bootstrap-kb/modules/agents.md.md +0 -15
  274. package/bootstrap-kb/modules/claude.md.md +0 -15
  275. package/bootstrap-kb/modules/context.md.md +0 -15
  276. package/bootstrap-kb/modules/docs.md +0 -18
  277. package/bootstrap-kb/modules/handoff.md.md +0 -15
  278. package/bootstrap-kb/modules/package-lock.json.md +0 -15
  279. package/bootstrap-kb/modules/package.json.md +0 -15
  280. package/bootstrap-kb/modules/plan.md.md +0 -15
  281. package/bootstrap-kb/modules/readme.md.md +0 -15
  282. package/bootstrap-kb/modules/scripts.md +0 -26
  283. package/bootstrap-kb/modules/spec.md.md +0 -15
  284. package/bootstrap-kb/modules/specs.md +0 -20
  285. package/bootstrap-kb/modules/src.md +0 -51
  286. package/bootstrap-kb/modules/tests.md +0 -42
  287. package/bootstrap-kb/modules/tsconfig.json.md +0 -15
  288. package/bootstrap-kb/modules/vitest.config.ts.md +0 -15
  289. package/bootstrap-kb/overview/overview.md +0 -40
  290. package/bootstrap-kb/sources/manifest.json +0 -950
  291. package/bootstrap-kb/sources/manifest.md +0 -227
  292. package/bootstrap-kb/timeline/timeline.md +0 -57
  293. package/claude-memory-layer-1.0.14.tgz +0 -0
  294. package/d.sh +0 -3
  295. package/deploy.sh +0 -3
  296. package/dist/ui/app.js +0 -2101
  297. package/memory/.claude-plugin/commands/2026-02-25.md +0 -263
  298. package/memory/_index.md +0 -419
  299. package/memory/agent_response/uncategorized/2026-02-26.md +0 -176
  300. package/memory/agent_response/uncategorized/2026-03-03.md +0 -14
  301. package/memory/agent_response/uncategorized/2026-03-04.md +0 -1421
  302. package/memory/agent_response/uncategorized/2026-03-05.md +0 -157
  303. package/memory/default/uncategorized/2026-02-25.md +0 -4839
  304. package/memory/session_summary/uncategorized/2026-02-26.md +0 -13
  305. package/memory/session_summary/uncategorized/2026-03-03.md +0 -5
  306. package/memory/session_summary/uncategorized/2026-03-04.md +0 -50
  307. package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +0 -142
  308. package/memory/specs/citations-system/2026-02-25.md +0 -1121
  309. package/memory/specs/endless-mode/2026-02-25.md +0 -1392
  310. package/memory/specs/entity-edge-model/2026-02-25.md +0 -1263
  311. package/memory/specs/evidence-aligner-v2/2026-02-25.md +0 -1028
  312. package/memory/specs/mcp-desktop-integration/2026-02-25.md +0 -1334
  313. package/memory/specs/post-tool-use-hook/2026-02-25.md +0 -1164
  314. package/memory/specs/private-tags/2026-02-25.md +0 -1057
  315. package/memory/specs/progressive-disclosure/2026-02-25.md +0 -1436
  316. package/memory/specs/task-entity-system/2026-02-25.md +0 -924
  317. package/memory/specs/vector-outbox-v2/2026-02-25.md +0 -1510
  318. package/memory/specs/web-viewer-ui/2026-02-25.md +0 -1709
  319. package/memory/tool_observation/uncategorized/2026-02-26.md +0 -209
  320. package/memory/tool_observation/uncategorized/2026-03-03.md +0 -21
  321. package/memory/tool_observation/uncategorized/2026-03-04.md +0 -1033
  322. package/memory/tool_observation/uncategorized/2026-03-05.md +0 -33
  323. package/memory/user_prompt/uncategorized/2026-02-26.md +0 -25
  324. package/memory/user_prompt/uncategorized/2026-03-04.md +0 -634
  325. package/memory/user_prompt/uncategorized/2026-03-05.md +0 -6
  326. package/specs/optional-duckdb/context.md +0 -77
  327. package/specs/optional-duckdb/plan.md +0 -142
  328. package/specs/optional-duckdb/spec.md +0 -35
  329. package/src/ui/app.js +0 -2101
@@ -0,0 +1,625 @@
1
+ import {
2
+ computePrecisionRecallAtK,
3
+ summarizeReplayMetrics,
4
+ type ReplayMetricsSummary,
5
+ type ReplayQueryMetrics
6
+ } from './retrieval-benchmark.js';
7
+ import { createRetrievalServices, type RetrieveMemoriesOptions } from './engine/retrieval-services.js';
8
+ import { Matcher } from './matcher.js';
9
+ import type { Embedder } from './embedder.js';
10
+ import type { MatchConfidence, MemoryEvent } from './types.js';
11
+ import type { SearchResult, VectorStore } from './vector-store.js';
12
+
13
+ export type ReplayExpectation = 'match' | 'no_match';
14
+
15
+ export interface ReplayEvaluationQuery {
16
+ queryId: string;
17
+ query: string;
18
+ expectedIds: string[];
19
+ expectedRelevance?: Record<string, number>;
20
+ expectation?: ReplayExpectation;
21
+ forbiddenIds?: string[];
22
+ knownAnswer?: string;
23
+ }
24
+
25
+ export interface ReplayEvaluationMemory {
26
+ id: string;
27
+ content: string;
28
+ sourceSessionId?: string;
29
+ sourceTurnIndex?: number;
30
+ timestamp?: string;
31
+ eventType?: MemoryEvent['eventType'];
32
+ canonicalKey?: string;
33
+ metadata?: Record<string, unknown>;
34
+ }
35
+
36
+ export interface ReplayEvaluationFixtureMetadata {
37
+ sourceFileCount?: number;
38
+ rawContentIncluded?: boolean;
39
+ generatedAt?: string;
40
+ }
41
+
42
+ export interface ReplayEvaluationFixture {
43
+ name: string;
44
+ description?: string;
45
+ ks: number[];
46
+ queries: ReplayEvaluationQuery[];
47
+ memories: ReplayEvaluationMemory[];
48
+ metadata?: ReplayEvaluationFixtureMetadata;
49
+ }
50
+
51
+ export interface ReplayRetrievalRunInput {
52
+ fixture: ReplayEvaluationFixture;
53
+ query: ReplayEvaluationQuery;
54
+ topK: number;
55
+ retrievalOptions: Partial<RetrieveMemoriesOptions>;
56
+ }
57
+
58
+ export interface ReplayRetrievalRunResult {
59
+ retrievedIds: string[];
60
+ candidateIds?: string[];
61
+ confidence?: MatchConfidence;
62
+ fallbackTrace?: string[];
63
+ }
64
+
65
+ export type ReplayRetrievalRunner = (
66
+ query: string,
67
+ input: ReplayRetrievalRunInput
68
+ ) => Promise<ReplayRetrievalRunResult>;
69
+
70
+ export interface ReplayEvaluationOptions {
71
+ generatedAt?: string;
72
+ includePerQuery?: boolean;
73
+ evaluator?: string;
74
+ topK?: number;
75
+ retrievalOptions?: Partial<RetrieveMemoriesOptions>;
76
+ retrievalRunner?: ReplayRetrievalRunner;
77
+ }
78
+
79
+ export interface ReplayFixtureStats {
80
+ queryCount: number;
81
+ memoryCount: number;
82
+ ks: number[];
83
+ sourceFileCount?: number;
84
+ rawContentIncluded?: boolean;
85
+ }
86
+
87
+ export interface ReplayFailedQuery {
88
+ queryId: string;
89
+ expectedIds: string[];
90
+ retrievedIds: string[];
91
+ expectation?: ReplayExpectation;
92
+ reason?: 'missing_expected' | 'unexpected_match';
93
+ }
94
+
95
+ export interface ReplayEvaluationSummary extends ReplayMetricsSummary {
96
+ positiveQueryCount: number;
97
+ noMatchQueryCount: number;
98
+ noMatchCorrect: number;
99
+ noMatchAccuracy: number;
100
+ forbiddenHitCount: number;
101
+ hitAtK: Record<number, number>;
102
+ mrr: number;
103
+ failedQueryCount: number;
104
+ failedQueries: ReplayFailedQuery[];
105
+ }
106
+
107
+ export interface ReplayEvaluationQueryMetrics extends ReplayQueryMetrics {
108
+ retrievedIds: string[];
109
+ candidateIds: string[];
110
+ confidence: MatchConfidence;
111
+ fallbackTrace: string[];
112
+ reciprocalRank: number;
113
+ expectation?: ReplayExpectation;
114
+ forbiddenHitIds?: string[];
115
+ noMatchSatisfied?: boolean;
116
+ }
117
+
118
+ export interface ReplayEvaluationReport {
119
+ name: string;
120
+ description?: string;
121
+ evaluator: string;
122
+ generatedAt: string;
123
+ fixtureStats: ReplayFixtureStats;
124
+ summary: ReplayEvaluationSummary;
125
+ perQuery: ReplayEvaluationQueryMetrics[];
126
+ }
127
+
128
+ export interface ReplayEvaluationMarkdownOptions {
129
+ qrelsPath?: string;
130
+ }
131
+
132
+ export async function evaluateReplayFixture(
133
+ fixture: ReplayEvaluationFixture,
134
+ options: ReplayEvaluationOptions = {}
135
+ ): Promise<ReplayEvaluationReport> {
136
+ const topK = determineTopK(fixture, options.topK);
137
+ const retrievalOptions: Partial<RetrieveMemoriesOptions> = {
138
+ strategy: 'auto',
139
+ minScore: 0.1,
140
+ includeShared: false,
141
+ adaptiveRerank: false,
142
+ ...options.retrievalOptions,
143
+ topK
144
+ };
145
+ const runner = options.retrievalRunner ?? createReplayRetrievalRunner(fixture);
146
+
147
+ const runs = await Promise.all(
148
+ fixture.queries.map(async (query) => {
149
+ const run = await runner(query.query, {
150
+ fixture,
151
+ query,
152
+ topK,
153
+ retrievalOptions
154
+ });
155
+ return {
156
+ query,
157
+ retrievedIds: uniqueIds(run.retrievedIds).slice(0, topK),
158
+ candidateIds: uniqueIds(run.candidateIds ?? run.retrievedIds),
159
+ confidence: run.confidence ?? 'none',
160
+ fallbackTrace: run.fallbackTrace ?? []
161
+ };
162
+ })
163
+ );
164
+
165
+ const baseMetrics = computePrecisionRecallAtK(
166
+ runs.map((run) => ({
167
+ queryId: run.query.queryId,
168
+ expectedIds: run.query.expectedIds,
169
+ expectedRelevance: run.query.expectedRelevance,
170
+ retrievedIds: run.retrievedIds
171
+ })),
172
+ fixture.ks
173
+ );
174
+
175
+ const perQuery: ReplayEvaluationQueryMetrics[] = baseMetrics.map((metric, index) => {
176
+ const run = runs[index];
177
+ const expectation = getReplayExpectation(run.query);
178
+ const base: ReplayEvaluationQueryMetrics = {
179
+ ...metric,
180
+ retrievedIds: run.retrievedIds,
181
+ candidateIds: run.candidateIds,
182
+ confidence: run.confidence,
183
+ fallbackTrace: run.fallbackTrace,
184
+ reciprocalRank: expectation === 'match' ? reciprocalRank(run.retrievedIds, run.query.expectedIds) : 0
185
+ };
186
+
187
+ if (expectation === 'no_match') {
188
+ const forbiddenHitIds = findForbiddenHitIds(run.retrievedIds, run.query.forbiddenIds ?? []);
189
+ base.expectation = 'no_match';
190
+ base.forbiddenHitIds = forbiddenHitIds;
191
+ base.noMatchSatisfied = forbiddenHitIds.length === 0 && run.confidence === 'none';
192
+ }
193
+
194
+ return base;
195
+ });
196
+
197
+ const fixtureStats: ReplayFixtureStats = {
198
+ queryCount: fixture.queries.length,
199
+ memoryCount: fixture.memories.length,
200
+ ks: fixture.ks
201
+ };
202
+ if (fixture.metadata?.sourceFileCount !== undefined) {
203
+ fixtureStats.sourceFileCount = fixture.metadata.sourceFileCount;
204
+ }
205
+ if (fixture.metadata?.rawContentIncluded !== undefined) {
206
+ fixtureStats.rawContentIncluded = fixture.metadata.rawContentIncluded;
207
+ }
208
+
209
+ const report: ReplayEvaluationReport = {
210
+ name: fixture.name,
211
+ evaluator: options.evaluator ?? 'retriever-pipeline-v1',
212
+ generatedAt: options.generatedAt ?? new Date().toISOString(),
213
+ fixtureStats,
214
+ summary: summarizeEvaluationMetrics(perQuery, fixture.queries, fixture.ks),
215
+ perQuery: options.includePerQuery === false ? [] : perQuery
216
+ };
217
+
218
+ if (fixture.description !== undefined) {
219
+ report.description = fixture.description;
220
+ }
221
+
222
+ return report;
223
+ }
224
+
225
+ export function createReplayRetrievalRunner(
226
+ fixture: ReplayEvaluationFixture
227
+ ): ReplayRetrievalRunner {
228
+ const eventStore = new ReplayEventStore(fixture.memories);
229
+ const vectorStore = new ReplayVectorStore(eventStore.events);
230
+ const embedder = new ReplayEmbedder();
231
+ const services = createRetrievalServices({
232
+ initialize: async () => undefined,
233
+ eventStore: eventStore as unknown as Parameters<typeof createRetrievalServices>[0]['eventStore'],
234
+ vectorStore: vectorStore as unknown as VectorStore,
235
+ embedder: embedder as unknown as Embedder,
236
+ matcher: new Matcher(),
237
+ getProjectHash: () => null,
238
+ hasSharedStore: () => false
239
+ });
240
+
241
+ return async (query, input) => {
242
+ const result = await services.retrievalOrchestrator.retrieveMemories(query, {
243
+ ...input.retrievalOptions,
244
+ topK: input.topK,
245
+ includeShared: false
246
+ });
247
+
248
+ return {
249
+ retrievedIds: result.memories.map((memory) => memory.event.id),
250
+ candidateIds: (result.candidateDebug ?? result.selectedDebug ?? [])
251
+ .map((detail) => detail.eventId),
252
+ confidence: result.matchResult.confidence,
253
+ fallbackTrace: result.fallbackTrace ?? []
254
+ };
255
+ };
256
+ }
257
+
258
+ export function formatReplayEvaluationMarkdown(
259
+ report: ReplayEvaluationReport,
260
+ options: ReplayEvaluationMarkdownOptions = {}
261
+ ): string {
262
+ const lines: string[] = [];
263
+ lines.push('# Retrieval Replay Benchmark Report');
264
+ lines.push('');
265
+ lines.push(`- Fixture: ${escapeMarkdownCell(report.name)}`);
266
+ if (report.description) lines.push(`- Description: ${escapeMarkdownCell(report.description)}`);
267
+ if (options.qrelsPath) lines.push(`- Qrels: \`${options.qrelsPath}\``);
268
+ lines.push(`- Evaluator: \`${report.evaluator}\``);
269
+ lines.push(`- Generated at: ${report.generatedAt}`);
270
+ lines.push(`- Queries: ${report.fixtureStats.queryCount}`);
271
+ lines.push(`- Memories: ${report.fixtureStats.memoryCount}`);
272
+ if (report.fixtureStats.sourceFileCount !== undefined) {
273
+ lines.push(`- Source files: ${report.fixtureStats.sourceFileCount}`);
274
+ }
275
+ if (report.fixtureStats.rawContentIncluded !== undefined) {
276
+ lines.push(`- Raw content in evaluated fixture: ${report.fixtureStats.rawContentIncluded ? 'yes' : 'no'}`);
277
+ }
278
+ lines.push('');
279
+ lines.push('## Summary');
280
+ lines.push('');
281
+ lines.push('| k | Precision@k | Recall@k | nDCG@k | Hit@k |');
282
+ lines.push('|---:|---:|---:|---:|---:|');
283
+
284
+ for (const k of sortedKValues(report.summary)) {
285
+ lines.push(
286
+ `| ${k} | ${formatMetric(report.summary.precisionAtK[k] ?? 0)} | ${formatMetric(report.summary.recallAtK[k] ?? 0)} | ${formatMetric(report.summary.ndcgAtK[k] ?? 0)} | ${formatMetric(report.summary.hitAtK[k] ?? 0)} |`
287
+ );
288
+ }
289
+
290
+ lines.push('');
291
+ lines.push('## Key metrics');
292
+ lines.push('');
293
+ lines.push('| Metric | Value |');
294
+ lines.push('|---|---:|');
295
+ lines.push(`| Positive queries | ${report.summary.positiveQueryCount} |`);
296
+ lines.push(`| No-match queries | ${report.summary.noMatchQueryCount} |`);
297
+ lines.push(`| No-match accuracy | ${formatMetric(report.summary.noMatchAccuracy)} |`);
298
+ lines.push(`| Forbidden hits | ${report.summary.forbiddenHitCount} |`);
299
+ lines.push(`| MRR | ${formatMetric(report.summary.mrr)} |`);
300
+ lines.push(`| Failed queries | ${report.summary.failedQueryCount} |`);
301
+ for (const k of sortedKValues(report.summary)) {
302
+ lines.push(`| Precision@${k} | ${formatMetric(report.summary.precisionAtK[k] ?? 0)} |`);
303
+ lines.push(`| Recall@${k} | ${formatMetric(report.summary.recallAtK[k] ?? 0)} |`);
304
+ lines.push(`| nDCG@${k} | ${formatMetric(report.summary.ndcgAtK[k] ?? 0)} |`);
305
+ lines.push(`| Hit@${k} | ${formatMetric(report.summary.hitAtK[k] ?? 0)} |`);
306
+ }
307
+
308
+ if (report.summary.failedQueries.length > 0) {
309
+ lines.push('');
310
+ lines.push('## Failed queries');
311
+ lines.push('');
312
+ lines.push('| queryId | expectedIds | retrievedIds |');
313
+ lines.push('|---|---|---|');
314
+ for (const failed of report.summary.failedQueries) {
315
+ lines.push(
316
+ `| ${escapeMarkdownCell(failed.queryId)} | ${escapeMarkdownCell(failed.expectedIds.join(', '))} | ${escapeMarkdownCell(failed.retrievedIds.join(', '))} |`
317
+ );
318
+ }
319
+ }
320
+
321
+ if (report.perQuery.length > 0) {
322
+ lines.push('');
323
+ lines.push('## Per-query metrics');
324
+ lines.push('');
325
+ lines.push('| queryId | k | hits | Precision@k | Recall@k | nDCG@k | RR | confidence |');
326
+ lines.push('|---|---:|---:|---:|---:|---:|---:|---|');
327
+
328
+ for (const query of report.perQuery) {
329
+ const ks = Object.keys(query.at).map(Number).sort((a, b) => a - b);
330
+ for (const k of ks) {
331
+ const metric = query.at[k];
332
+ if (!metric) continue;
333
+ lines.push(
334
+ `| ${escapeMarkdownCell(query.queryId)} | ${k} | ${metric.hits} | ${formatMetric(metric.precision)} | ${formatMetric(metric.recall)} | ${formatMetric(metric.ndcg)} | ${formatMetric(query.reciprocalRank)} | ${query.confidence} |`
335
+ );
336
+ }
337
+ }
338
+ }
339
+
340
+ lines.push('');
341
+ lines.push('> Report intentionally omits raw query and memory text.');
342
+ lines.push('');
343
+ return lines.join('\n');
344
+ }
345
+
346
+ function summarizeEvaluationMetrics(
347
+ perQuery: ReplayEvaluationQueryMetrics[],
348
+ queries: ReplayEvaluationQuery[],
349
+ ks: number[]
350
+ ): ReplayEvaluationSummary {
351
+ const pairs = perQuery.map((metric, index) => ({
352
+ metric,
353
+ query: queries[index],
354
+ expectation: getReplayExpectation(queries[index])
355
+ }));
356
+ const positivePairs = pairs.filter((pair) => pair.expectation === 'match');
357
+ const noMatchPairs = pairs.filter((pair) => pair.expectation === 'no_match');
358
+ const positiveMetrics = positivePairs.map((pair) => pair.metric);
359
+ const base = summarizeReplayMetrics(positiveMetrics, ks);
360
+ const normalizedKs = normalizeKs(ks);
361
+ const hitAtK: Record<number, number> = {};
362
+ for (const k of normalizedKs) {
363
+ hitAtK[k] = average(positiveMetrics.map((metric) => (metric.at[k]?.hits ?? 0) > 0 ? 1 : 0));
364
+ }
365
+
366
+ const positiveFailures: ReplayFailedQuery[] = positivePairs
367
+ .filter(({ metric, query }) => query.expectedIds.length > 0 && metric.reciprocalRank === 0)
368
+ .map(({ metric, query }) => ({
369
+ queryId: query.queryId,
370
+ expectedIds: [...query.expectedIds],
371
+ retrievedIds: [...metric.retrievedIds],
372
+ expectation: 'match',
373
+ reason: 'missing_expected'
374
+ }));
375
+
376
+ const noMatchFailures: ReplayFailedQuery[] = noMatchPairs
377
+ .filter(({ metric }) => metric.noMatchSatisfied !== true)
378
+ .map(({ metric, query }) => ({
379
+ queryId: query.queryId,
380
+ expectedIds: [],
381
+ retrievedIds: [...metric.retrievedIds],
382
+ expectation: 'no_match',
383
+ reason: 'unexpected_match'
384
+ }));
385
+
386
+ const noMatchCorrect = noMatchPairs.filter(({ metric }) => metric.noMatchSatisfied === true).length;
387
+ const forbiddenHitCount = noMatchPairs.reduce(
388
+ (sum, { metric }) => sum + (metric.forbiddenHitIds?.length ?? 0),
389
+ 0
390
+ );
391
+ const failedQueries = [...positiveFailures, ...noMatchFailures];
392
+
393
+ return {
394
+ ...base,
395
+ queryCount: perQuery.length,
396
+ positiveQueryCount: positivePairs.length,
397
+ noMatchQueryCount: noMatchPairs.length,
398
+ noMatchCorrect,
399
+ noMatchAccuracy: noMatchPairs.length === 0 ? 0 : noMatchCorrect / noMatchPairs.length,
400
+ forbiddenHitCount,
401
+ hitAtK,
402
+ mrr: average(positiveMetrics.map((metric) => metric.reciprocalRank)),
403
+ failedQueryCount: failedQueries.length,
404
+ failedQueries
405
+ };
406
+ }
407
+
408
+ function determineTopK(fixture: ReplayEvaluationFixture, optionTopK?: number): number {
409
+ return Math.max(1, optionTopK ?? 0, ...fixture.ks.map((k) => Math.floor(k)).filter((k) => k > 0));
410
+ }
411
+
412
+ function sortedKValues(summary: ReplayMetricsSummary): number[] {
413
+ return Object.keys(summary.precisionAtK).map(Number).sort((a, b) => a - b);
414
+ }
415
+
416
+ function normalizeKs(ks: number[]): number[] {
417
+ const seen = new Set<number>();
418
+ const normalized: number[] = [];
419
+ for (const rawK of ks) {
420
+ const k = Math.max(0, Math.floor(rawK));
421
+ if (seen.has(k)) continue;
422
+ seen.add(k);
423
+ normalized.push(k);
424
+ }
425
+ return normalized.sort((a, b) => a - b);
426
+ }
427
+
428
+ function uniqueIds(ids: string[]): string[] {
429
+ const seen = new Set<string>();
430
+ const unique: string[] = [];
431
+ for (const id of ids) {
432
+ if (seen.has(id)) continue;
433
+ seen.add(id);
434
+ unique.push(id);
435
+ }
436
+ return unique;
437
+ }
438
+
439
+ function reciprocalRank(retrievedIds: string[], expectedIds: string[]): number {
440
+ const expected = new Set(expectedIds);
441
+ if (expected.size === 0) return 0;
442
+ const index = retrievedIds.findIndex((id) => expected.has(id));
443
+ return index === -1 ? 0 : 1 / (index + 1);
444
+ }
445
+
446
+ function getReplayExpectation(query: ReplayEvaluationQuery | undefined): ReplayExpectation {
447
+ if (!query) return 'match';
448
+ return query.expectation ?? (query.expectedIds.length === 0 ? 'no_match' : 'match');
449
+ }
450
+
451
+ function findForbiddenHitIds(retrievedIds: string[], forbiddenIds: string[]): string[] {
452
+ if (forbiddenIds.length === 0) return [];
453
+ const forbidden = new Set(forbiddenIds);
454
+ return uniqueIds(retrievedIds.filter((id) => forbidden.has(id)));
455
+ }
456
+
457
+ function tokenize(text: string): string[] {
458
+ return text
459
+ .toLowerCase()
460
+ .replace(/[^\p{L}\p{N}\s_.:-]/gu, ' ')
461
+ .split(/\s+/)
462
+ .flatMap((token) => token.split(/(?=[._:-])|(?<=[._:-])/g))
463
+ .map((token) => token.replace(/^[._:-]+|[._:-]+$/g, ''))
464
+ .filter((token) => token.length >= 2)
465
+ .slice(0, 128);
466
+ }
467
+
468
+ function keywordScore(queryTokens: string[], content: string): number {
469
+ if (queryTokens.length === 0) return 0;
470
+ const contentTokens = new Set(tokenize(content));
471
+ const hits = queryTokens.filter((token) => contentTokens.has(token)).length;
472
+ return hits / queryTokens.length;
473
+ }
474
+
475
+ function vectorize(text: string, dimensions = 64): number[] {
476
+ const vector = new Array<number>(dimensions).fill(0);
477
+ for (const token of tokenize(text)) {
478
+ let hash = 2166136261;
479
+ for (let i = 0; i < token.length; i += 1) {
480
+ hash ^= token.charCodeAt(i);
481
+ hash = Math.imul(hash, 16777619);
482
+ }
483
+ vector[Math.abs(hash) % dimensions] += 1;
484
+ }
485
+ const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0)) || 1;
486
+ return vector.map((value) => value / norm);
487
+ }
488
+
489
+ function cosine(a: number[], b: number[]): number {
490
+ const length = Math.min(a.length, b.length);
491
+ let dot = 0;
492
+ for (let i = 0; i < length; i += 1) dot += a[i] * b[i];
493
+ return Math.max(0, Math.min(1, dot));
494
+ }
495
+
496
+ function formatMetric(value: number): string {
497
+ return value.toFixed(4).replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1');
498
+ }
499
+
500
+ function escapeMarkdownCell(value: string): string {
501
+ return value.replace(/\|/g, '\\|').replace(/\n/g, ' ');
502
+ }
503
+
504
+ function average(values: number[]): number {
505
+ if (values.length === 0) return 0;
506
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
507
+ }
508
+
509
+ class ReplayEventStore {
510
+ readonly events: MemoryEvent[];
511
+ private readonly byId: Map<string, MemoryEvent>;
512
+
513
+ constructor(memories: ReplayEvaluationMemory[]) {
514
+ this.events = memories.map((memory, index) => replayMemoryToEvent(memory, index));
515
+ this.byId = new Map(this.events.map((event) => [event.id, event]));
516
+ }
517
+
518
+ async keywordSearch(query: string, limit = 10): Promise<Array<{ event: MemoryEvent; rank: number }>> {
519
+ const queryTokens = tokenize(query);
520
+ return this.events
521
+ .map((event) => ({ event, score: keywordScore(queryTokens, event.content) }))
522
+ .filter((row) => row.score > 0)
523
+ .sort((a, b) => b.score - a.score || a.event.id.localeCompare(b.event.id))
524
+ .slice(0, limit)
525
+ .map((row, index) => ({ event: row.event, rank: -row.score - index / 1000 }));
526
+ }
527
+
528
+ async getRecentEvents(limit = 100): Promise<MemoryEvent[]> {
529
+ return [...this.events]
530
+ .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime() || a.id.localeCompare(b.id))
531
+ .slice(0, limit);
532
+ }
533
+
534
+ async getEvent(id: string): Promise<MemoryEvent | null> {
535
+ return this.byId.get(id) ?? null;
536
+ }
537
+
538
+ async getSessionEvents(sessionId: string): Promise<MemoryEvent[]> {
539
+ return this.events
540
+ .filter((event) => event.sessionId === sessionId)
541
+ .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime() || a.id.localeCompare(b.id));
542
+ }
543
+
544
+ async getHelpfulnessStats(): Promise<{ avgScore: number; totalEvaluated: number; totalRetrievals: number; helpful: number; neutral: number; unhelpful: number }> {
545
+ return { avgScore: 0, totalEvaluated: 0, totalRetrievals: 0, helpful: 0, neutral: 0, unhelpful: 0 };
546
+ }
547
+
548
+ async recordRetrievalTrace(): Promise<void> {
549
+ return undefined;
550
+ }
551
+
552
+ async incrementAccessCount(): Promise<void> {
553
+ return undefined;
554
+ }
555
+
556
+ async recordRetrieval(): Promise<void> {
557
+ return undefined;
558
+ }
559
+ }
560
+
561
+ class ReplayVectorStore {
562
+ private readonly rows: Array<SearchResult & { vector: number[] }>;
563
+
564
+ constructor(events: MemoryEvent[]) {
565
+ this.rows = events.map((event) => ({
566
+ id: `replay-vector-${event.id}`,
567
+ eventId: event.id,
568
+ content: event.content,
569
+ score: 0,
570
+ sessionId: event.sessionId,
571
+ eventType: event.eventType,
572
+ timestamp: event.timestamp.toISOString(),
573
+ vector: vectorize(event.content)
574
+ }));
575
+ }
576
+
577
+ async search(queryVector: number[], options: { limit?: number; minScore?: number; sessionId?: string } = {}): Promise<SearchResult[]> {
578
+ const limit = options.limit ?? 5;
579
+ const minScore = options.minScore ?? 0;
580
+ return this.rows
581
+ .filter((row) => !options.sessionId || row.sessionId === options.sessionId)
582
+ .map((row) => ({ ...row, score: cosine(queryVector, row.vector) }))
583
+ .filter((row) => row.score >= minScore)
584
+ .sort((a, b) => b.score - a.score || a.eventId.localeCompare(b.eventId))
585
+ .slice(0, limit)
586
+ .map((row) => ({
587
+ id: row.id,
588
+ eventId: row.eventId,
589
+ content: row.content,
590
+ score: row.score,
591
+ sessionId: row.sessionId,
592
+ eventType: row.eventType,
593
+ timestamp: row.timestamp
594
+ }));
595
+ }
596
+
597
+ async count(): Promise<number> {
598
+ return this.rows.length;
599
+ }
600
+ }
601
+
602
+ class ReplayEmbedder {
603
+ async embed(text: string): Promise<{ vector: number[]; model: string; dimensions: number }> {
604
+ const vector = vectorize(text);
605
+ return { vector, model: 'deterministic-replay-hash', dimensions: vector.length };
606
+ }
607
+ }
608
+
609
+ function replayMemoryToEvent(memory: ReplayEvaluationMemory, index: number): MemoryEvent {
610
+ const sessionId = memory.sourceSessionId ?? 'replay-fixture';
611
+ const timestamp = memory.timestamp
612
+ ? new Date(memory.timestamp)
613
+ : new Date(Date.UTC(2026, 0, 1, 0, 0, index));
614
+
615
+ return {
616
+ id: memory.id,
617
+ sessionId,
618
+ eventType: memory.eventType ?? 'agent_response',
619
+ content: memory.content,
620
+ canonicalKey: memory.canonicalKey ?? `replay/${memory.id}`,
621
+ dedupeKey: `replay:${sessionId}:${memory.id}`,
622
+ timestamp,
623
+ metadata: memory.metadata ?? {}
624
+ };
625
+ }