claude-memory-layer 1.0.27 → 1.0.29

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 (331) hide show
  1. package/.env.example +7 -0
  2. package/AGENTS.md +11 -0
  3. package/README.md +374 -49
  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 +12 -5
  56. package/scripts/build.ts +25 -8
  57. package/scripts/generate-session-qrels.ts +126 -0
  58. package/scripts/postinstall-embedding-backend.cjs +142 -0
  59. package/scripts/replay-retrieval-benchmark.ts +69 -0
  60. package/specs/thin-core-refactor/context.md +275 -0
  61. package/specs/thin-core-refactor/plan.md +536 -0
  62. package/specs/thin-core-refactor/spec.md +465 -0
  63. package/src/adapters/claude/capture/index.ts +3 -0
  64. package/src/adapters/claude/context/index.ts +3 -0
  65. package/src/adapters/claude/hooks/index.ts +21 -0
  66. package/src/adapters/claude/hooks/post-tool-use.ts +239 -0
  67. package/src/adapters/claude/hooks/prompt-injection-policy.ts +104 -0
  68. package/src/adapters/claude/hooks/semantic-daemon-client.ts +209 -0
  69. package/src/adapters/claude/hooks/semantic-daemon.ts +283 -0
  70. package/src/adapters/claude/hooks/session-end.ts +59 -0
  71. package/src/adapters/claude/hooks/session-start.ts +73 -0
  72. package/src/adapters/claude/hooks/stop.ts +128 -0
  73. package/src/adapters/claude/hooks/user-prompt-submit.ts +361 -0
  74. package/src/adapters/claude/index.ts +4 -0
  75. package/src/adapters/claude/transcript/index.ts +4 -0
  76. package/src/adapters/claude/transcript/transcript-reader.ts +57 -0
  77. package/src/adapters/claude/transcript/turn-reconstructor.ts +65 -0
  78. package/src/apps/cli/claude-settings-hooks.ts +138 -0
  79. package/src/apps/cli/codex-import-runner.ts +125 -0
  80. package/src/apps/cli/codex-validation-output.ts +95 -0
  81. package/src/apps/cli/hermes-import-runner.ts +130 -0
  82. package/src/apps/cli/hermes-validation-output.ts +91 -0
  83. package/src/apps/cli/index.ts +1731 -0
  84. package/src/apps/cli/mcp-install.ts +106 -0
  85. package/src/apps/cli/retrieval-disclosure-output.ts +196 -0
  86. package/src/apps/dashboard/assets/js/bootstrap.js +244 -0
  87. package/src/apps/dashboard/assets/js/chat.js +373 -0
  88. package/src/apps/dashboard/assets/js/disclosure.js +232 -0
  89. package/src/apps/dashboard/assets/js/modals.js +298 -0
  90. package/src/apps/dashboard/assets/js/overview.js +655 -0
  91. package/src/apps/dashboard/assets/js/state.js +72 -0
  92. package/src/apps/dashboard/assets/js/views.js +468 -0
  93. package/src/{ui → apps/dashboard}/index.html +43 -1
  94. package/src/apps/dashboard/index.ts +3 -0
  95. package/src/{ui → apps/dashboard}/style.css +222 -0
  96. package/src/apps/index.ts +5 -0
  97. package/src/apps/server/api/chat.ts +244 -0
  98. package/src/apps/server/api/citations.ts +105 -0
  99. package/src/apps/server/api/events.ts +137 -0
  100. package/src/apps/server/api/health.ts +53 -0
  101. package/src/apps/server/api/index.ts +26 -0
  102. package/src/apps/server/api/projects.ts +74 -0
  103. package/src/apps/server/api/search.ts +184 -0
  104. package/src/apps/server/api/sessions.ts +115 -0
  105. package/src/apps/server/api/stats.ts +723 -0
  106. package/src/apps/server/api/turns.ts +143 -0
  107. package/src/apps/server/api/utils.ts +65 -0
  108. package/src/apps/server/index.ts +111 -0
  109. package/src/cli/index.ts +2 -1311
  110. package/src/cli/retrieval-disclosure-output.ts +2 -0
  111. package/src/compat/index.ts +5 -0
  112. package/src/core/derive/fact-deriver.ts +170 -0
  113. package/src/core/derive/index.ts +2 -0
  114. package/src/core/derive/summary-deriver.ts +76 -0
  115. package/src/core/embedder.ts +4 -152
  116. package/src/core/engine/embedding-maintenance-service.ts +187 -0
  117. package/src/core/engine/endless-memory-services.ts +4 -0
  118. package/src/core/engine/index.ts +19 -0
  119. package/src/core/engine/memory-engine-services.ts +170 -0
  120. package/src/core/engine/memory-ingest-service.ts +317 -0
  121. package/src/core/engine/memory-query-service.ts +173 -0
  122. package/src/core/engine/memory-runtime-service.ts +162 -0
  123. package/src/core/engine/memory-service-composition.ts +231 -0
  124. package/src/core/engine/retrieval-analytics-service.ts +181 -0
  125. package/src/core/engine/retrieval-disclosure-service.ts +420 -0
  126. package/src/core/engine/retrieval-orchestrator.ts +377 -0
  127. package/src/core/engine/retrieval-services.ts +176 -0
  128. package/src/core/engine/shared-memory-services.ts +4 -0
  129. package/src/core/entity-repo.ts +1 -3
  130. package/src/core/event-store.ts +3 -3
  131. package/src/core/evidence-aligner.ts +2 -2
  132. package/src/core/external-market-context.ts +582 -0
  133. package/src/core/graduation.ts +2 -3
  134. package/src/core/index.ts +21 -0
  135. package/src/core/matcher.ts +2 -4
  136. package/src/core/model/memory-fact.ts +30 -0
  137. package/src/core/model/memory-rule.ts +14 -0
  138. package/src/core/model/memory-summary.ts +21 -0
  139. package/src/core/model/raw-event.ts +28 -0
  140. package/src/core/model/retrieval-result.ts +35 -0
  141. package/src/core/privacy/filter.ts +21 -10
  142. package/src/core/product-validation-matrix.ts +314 -0
  143. package/src/core/progressive-retriever.ts +1 -2
  144. package/src/core/registry/project-path.ts +54 -0
  145. package/src/core/registry/session-registry.ts +69 -0
  146. package/src/core/replay-evaluator.ts +625 -0
  147. package/src/core/retrieval-benchmark.ts +117 -0
  148. package/src/core/retrieval-quality.ts +109 -0
  149. package/src/core/retriever.ts +53 -15
  150. package/src/core/session-qrels.ts +360 -0
  151. package/src/core/shared-event-store.ts +1 -1
  152. package/src/core/sqlite-event-store.ts +35 -11
  153. package/src/core/task/blocker-resolver.ts +2 -2
  154. package/src/core/task/task-resolver.ts +0 -1
  155. package/src/core/vector-outbox.ts +1 -10
  156. package/src/core/vector-worker.ts +1 -1
  157. package/src/extensions/endless-memory/endless-memory-services.ts +350 -0
  158. package/src/extensions/endless-memory/index.ts +1 -0
  159. package/src/extensions/index.ts +5 -0
  160. package/src/extensions/mcp/handlers.ts +960 -0
  161. package/src/extensions/mcp/index.ts +48 -0
  162. package/src/extensions/mcp/tools.ts +252 -0
  163. package/src/extensions/shared-memory/index.ts +1 -0
  164. package/src/extensions/shared-memory/shared-memory-services.ts +211 -0
  165. package/src/extensions/vector/embedder.ts +197 -0
  166. package/src/extensions/vector/index.ts +1 -0
  167. package/src/hooks/post-tool-use.ts +3 -236
  168. package/src/hooks/semantic-daemon-client.ts +1 -208
  169. package/src/hooks/semantic-daemon.ts +6 -271
  170. package/src/hooks/session-end.ts +4 -79
  171. package/src/hooks/session-start.ts +4 -73
  172. package/src/hooks/stop.ts +3 -173
  173. package/src/hooks/user-prompt-submit.ts +3 -338
  174. package/src/index.ts +13 -0
  175. package/src/mcp/handlers.ts +2 -212
  176. package/src/mcp/index.ts +3 -46
  177. package/src/mcp/tools.ts +2 -78
  178. package/src/server/api/chat.ts +2 -244
  179. package/src/server/api/citations.ts +2 -105
  180. package/src/server/api/events.ts +2 -137
  181. package/src/server/api/health.ts +2 -53
  182. package/src/server/api/index.ts +2 -26
  183. package/src/server/api/projects.ts +2 -74
  184. package/src/server/api/search.ts +2 -102
  185. package/src/server/api/sessions.ts +2 -115
  186. package/src/server/api/stats.ts +2 -724
  187. package/src/server/api/turns.ts +2 -143
  188. package/src/server/api/utils.ts +2 -46
  189. package/src/server/index.ts +2 -100
  190. package/src/services/bootstrap-organizer.ts +46 -26
  191. package/src/services/codex-session-history-importer.ts +521 -29
  192. package/src/services/hermes-session-history-importer.ts +733 -0
  193. package/src/services/memory-service-config.ts +36 -0
  194. package/src/services/memory-service-registry.ts +150 -0
  195. package/src/services/memory-service.ts +211 -1325
  196. package/src/services/session-history-importer.ts +58 -14
  197. package/tests/README.md +23 -0
  198. package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +54 -0
  199. package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +98 -0
  200. package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +99 -0
  201. package/tests/apps/app-layer-boundary.test.ts +48 -0
  202. package/tests/apps/claude-settings-hooks.test.ts +107 -0
  203. package/tests/apps/cli-disclosure-output.test.ts +212 -0
  204. package/tests/apps/codex-import-runner.test.ts +99 -0
  205. package/tests/apps/codex-validation-output.test.ts +100 -0
  206. package/tests/apps/hermes-import-runner.test.ts +99 -0
  207. package/tests/apps/mcp-install-command.test.ts +59 -0
  208. package/tests/apps/package-build-entrypoints.test.ts +30 -0
  209. package/tests/apps/postinstall-embedding-backend.test.ts +167 -0
  210. package/tests/apps/search-api-disclosure.test.ts +162 -0
  211. package/tests/apps/stats-api-lightweight.test.ts +67 -0
  212. package/tests/apps/ui-disclosure-output.test.ts +140 -0
  213. package/tests/{bootstrap-organizer.test.ts → core/bootstrap-organizer.test.ts} +1 -1
  214. package/tests/{canonical-key.test.ts → core/canonical-key.test.ts} +1 -1
  215. package/tests/core/codex-session-history-importer-validation.test.ts +185 -0
  216. package/tests/{consolidation-worker.test.ts → core/consolidation-worker.test.ts} +2 -2
  217. package/tests/core/embedding-maintenance-service.test.ts +282 -0
  218. package/tests/{evidence-aligner.test.ts → core/evidence-aligner.test.ts} +1 -1
  219. package/tests/core/external-market-context.test.ts +209 -0
  220. package/tests/core/fact-deriver.test.ts +79 -0
  221. package/tests/core/hermes-session-history-importer-validation.test.ts +609 -0
  222. package/tests/{ingest-interceptor.test.ts → core/ingest-interceptor.test.ts} +1 -1
  223. package/tests/{markdown-mirror.test.ts → core/markdown-mirror.test.ts} +2 -2
  224. package/tests/{matcher.test.ts → core/matcher.test.ts} +1 -1
  225. package/tests/{md-mirror.test.ts → core/md-mirror.test.ts} +2 -2
  226. package/tests/core/memory-engine-services.test.ts +240 -0
  227. package/tests/core/memory-ingest-service.test.ts +296 -0
  228. package/tests/core/memory-query-service.test.ts +129 -0
  229. package/tests/core/memory-runtime-service.test.ts +201 -0
  230. package/tests/core/memory-service-composition.test.ts +192 -0
  231. package/tests/core/memory-service-config.test.ts +41 -0
  232. package/tests/core/memory-service-facade.test.ts +30 -0
  233. package/tests/core/memory-service-registry.test.ts +206 -0
  234. package/tests/core/product-validation-matrix.test.ts +61 -0
  235. package/tests/core/project-registry.test.ts +78 -0
  236. package/tests/core/replay-evaluator.test.ts +181 -0
  237. package/tests/core/retrieval-analytics-service.test.ts +210 -0
  238. package/tests/core/retrieval-benchmark.test.ts +93 -0
  239. package/tests/core/retrieval-disclosure-service.test.ts +264 -0
  240. package/tests/core/retrieval-orchestrator.test.ts +403 -0
  241. package/tests/core/retrieval-quality.test.ts +31 -0
  242. package/tests/core/retrieval-services.test.ts +185 -0
  243. package/tests/{retriever-fallback-chain.test.ts → core/retriever-fallback-chain.test.ts} +3 -3
  244. package/tests/{retriever-strategy-scope.test.ts → core/retriever-strategy-scope.test.ts} +70 -3
  245. package/tests/{retriever.memu-adoption.test.ts → core/retriever.memu-adoption.test.ts} +3 -3
  246. package/tests/core/session-history-importer-filter.test.ts +78 -0
  247. package/tests/core/session-qrels.test.ts +250 -0
  248. package/tests/{sqlite-event-store-replication.test.ts → core/sqlite-event-store-replication.test.ts} +36 -1
  249. package/tests/core/summary-deriver.test.ts +66 -0
  250. package/tests/extensions/embedder-warning-suppression.test.ts +53 -0
  251. package/tests/extensions/endless-memory-extension-boundary.test.ts +17 -0
  252. package/tests/extensions/endless-memory-services.test.ts +325 -0
  253. package/tests/extensions/mcp-context-tools.test.ts +905 -0
  254. package/tests/extensions/mcp-extension-boundary.test.ts +21 -0
  255. package/tests/extensions/mcp-package-build.test.ts +22 -0
  256. package/tests/extensions/mcp-project-aware-tools.test.ts +102 -0
  257. package/tests/extensions/shared-memory-extension-boundary.test.ts +24 -0
  258. package/tests/extensions/shared-memory-services.test.ts +309 -0
  259. package/tests/extensions/vector-extension-boundary.test.ts +21 -0
  260. package/.claude/settings.local.json +0 -25
  261. package/.npm-cache/_cacache/content-v2/sha512/04/76/c098f88dfe584a2b80870bff7421b05d17d3d9ee1027f77772332a22d3f93a9a57101a2855107f6ad82077a818bba912b2bc317f2361b5ddb09ad284d9ce +0 -0
  262. package/.npm-cache/_cacache/content-v2/sha512/60/25/d2ecd39cfc7cab58351162814be77f935c6d6491c10c3745d456da7ddb2117ffd90c10e53fe3c0f1ed16b403307841543634504398b16ee4e6b6dd8e0c45 +0 -0
  263. package/.npm-cache/_cacache/index-v5/2b/9a/7f8f40206ed8a2e0a84efaa953ccaed1f5d001e14b931083f2e7a0738007 +0 -2
  264. package/.npm-cache/_cacache/index-v5/2e/d9/fcfa5c6a6abdc2a3644ab84a95936047298c465a2f47ee03db8f7fe1e946 +0 -3
  265. package/.npm-cache/_cacache/index-v5/a9/42/e519633356d12d3d2f19da66a8301016d496c8f5c3e0554124aaa62dc043 +0 -2
  266. package/.npm-cache/_logs/2026-02-26T12_04_52_729Z-debug-0.log +0 -256
  267. package/.npm-cache/_logs/2026-02-26T12_05_36_835Z-debug-0.log +0 -18
  268. package/.npm-cache/_logs/2026-02-26T12_05_45_982Z-debug-0.log +0 -32
  269. package/.npm-cache/_logs/2026-02-26T12_05_48_515Z-debug-0.log +0 -260
  270. package/.npm-cache/_logs/2026-02-26T12_05_53_567Z-debug-0.log +0 -69
  271. package/.npm-cache/_update-notifier-last-checked +0 -0
  272. package/bootstrap-kb/decisions/decisions.md +0 -244
  273. package/bootstrap-kb/glossary/glossary.md +0 -46
  274. package/bootstrap-kb/modules/.claude-plugin.md +0 -22
  275. package/bootstrap-kb/modules/agents.md.md +0 -15
  276. package/bootstrap-kb/modules/claude.md.md +0 -15
  277. package/bootstrap-kb/modules/context.md.md +0 -15
  278. package/bootstrap-kb/modules/docs.md +0 -18
  279. package/bootstrap-kb/modules/handoff.md.md +0 -15
  280. package/bootstrap-kb/modules/package-lock.json.md +0 -15
  281. package/bootstrap-kb/modules/package.json.md +0 -15
  282. package/bootstrap-kb/modules/plan.md.md +0 -15
  283. package/bootstrap-kb/modules/readme.md.md +0 -15
  284. package/bootstrap-kb/modules/scripts.md +0 -26
  285. package/bootstrap-kb/modules/spec.md.md +0 -15
  286. package/bootstrap-kb/modules/specs.md +0 -20
  287. package/bootstrap-kb/modules/src.md +0 -51
  288. package/bootstrap-kb/modules/tests.md +0 -42
  289. package/bootstrap-kb/modules/tsconfig.json.md +0 -15
  290. package/bootstrap-kb/modules/vitest.config.ts.md +0 -15
  291. package/bootstrap-kb/overview/overview.md +0 -40
  292. package/bootstrap-kb/sources/manifest.json +0 -950
  293. package/bootstrap-kb/sources/manifest.md +0 -227
  294. package/bootstrap-kb/timeline/timeline.md +0 -57
  295. package/claude-memory-layer-1.0.14.tgz +0 -0
  296. package/d.sh +0 -3
  297. package/deploy.sh +0 -3
  298. package/dist/ui/app.js +0 -2101
  299. package/memory/.claude-plugin/commands/2026-02-25.md +0 -263
  300. package/memory/_index.md +0 -419
  301. package/memory/agent_response/uncategorized/2026-02-26.md +0 -176
  302. package/memory/agent_response/uncategorized/2026-03-03.md +0 -14
  303. package/memory/agent_response/uncategorized/2026-03-04.md +0 -1421
  304. package/memory/agent_response/uncategorized/2026-03-05.md +0 -157
  305. package/memory/default/uncategorized/2026-02-25.md +0 -4839
  306. package/memory/session_summary/uncategorized/2026-02-26.md +0 -13
  307. package/memory/session_summary/uncategorized/2026-03-03.md +0 -5
  308. package/memory/session_summary/uncategorized/2026-03-04.md +0 -50
  309. package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +0 -142
  310. package/memory/specs/citations-system/2026-02-25.md +0 -1121
  311. package/memory/specs/endless-mode/2026-02-25.md +0 -1392
  312. package/memory/specs/entity-edge-model/2026-02-25.md +0 -1263
  313. package/memory/specs/evidence-aligner-v2/2026-02-25.md +0 -1028
  314. package/memory/specs/mcp-desktop-integration/2026-02-25.md +0 -1334
  315. package/memory/specs/post-tool-use-hook/2026-02-25.md +0 -1164
  316. package/memory/specs/private-tags/2026-02-25.md +0 -1057
  317. package/memory/specs/progressive-disclosure/2026-02-25.md +0 -1436
  318. package/memory/specs/task-entity-system/2026-02-25.md +0 -924
  319. package/memory/specs/vector-outbox-v2/2026-02-25.md +0 -1510
  320. package/memory/specs/web-viewer-ui/2026-02-25.md +0 -1709
  321. package/memory/tool_observation/uncategorized/2026-02-26.md +0 -209
  322. package/memory/tool_observation/uncategorized/2026-03-03.md +0 -21
  323. package/memory/tool_observation/uncategorized/2026-03-04.md +0 -1033
  324. package/memory/tool_observation/uncategorized/2026-03-05.md +0 -33
  325. package/memory/user_prompt/uncategorized/2026-02-26.md +0 -25
  326. package/memory/user_prompt/uncategorized/2026-03-04.md +0 -634
  327. package/memory/user_prompt/uncategorized/2026-03-05.md +0 -6
  328. package/specs/optional-duckdb/context.md +0 -77
  329. package/specs/optional-duckdb/plan.md +0 -142
  330. package/specs/optional-duckdb/spec.md +0 -35
  331. package/src/ui/app.js +0 -2101
@@ -3,479 +3,170 @@
3
3
  * Coordinates EventStore, VectorStore, Retriever, and Graduation
4
4
  */
5
5
 
6
- import * as path from 'path';
7
6
  import * as os from 'os';
8
- import * as fs from 'fs';
9
- import * as crypto from 'crypto';
10
-
11
- import { EventStore } from '../core/event-store.js';
12
- import { SQLiteEventStore } from '../core/sqlite-event-store.js';
13
- import { VectorStore } from '../core/vector-store.js';
14
- import { Embedder, getDefaultEmbedder } from '../core/embedder.js';
15
- import { VectorWorker, createVectorWorker } from '../core/vector-worker.js';
16
- import { Matcher, getDefaultMatcher } from '../core/matcher.js';
17
- import { Retriever, createRetriever, RetrievalResult, UnifiedRetrievalResult } from '../core/retriever.js';
18
- import { GraduationPipeline, createGraduationPipeline } from '../core/graduation.js';
19
- import { SharedEventStore, createSharedEventStore } from '../core/shared-event-store.js';
20
- import { SharedStore, createSharedStore } from '../core/shared-store.js';
21
- import { SharedVectorStore, createSharedVectorStore } from '../core/shared-vector-store.js';
22
- import { SharedPromoter, createSharedPromoter, PromotionResult } from '../core/shared-promoter.js';
7
+
8
+ import type { RetrievalResult, UnifiedRetrievalResult } from '../core/retriever.js';
9
+ import type { PromotionResult } from '../core/shared-promoter.js';
10
+ import type { SharedMemoryServices } from '../extensions/shared-memory/index.js';
23
11
  import type {
24
- MemoryEventInput,
25
12
  AppendResult,
26
13
  MemoryEvent,
27
- Config,
28
- ConfigSchema,
29
14
  ToolObservationPayload,
30
15
  MemoryMode,
31
16
  EndlessModeConfig,
32
- EndlessModeConfigSchema,
33
17
  WorkingSet,
34
18
  ConsolidatedMemory,
35
19
  EndlessModeStatus,
36
- ContextSnapshot,
37
20
  ContinuityScore,
38
21
  SharedStoreConfig,
39
22
  Entry
40
23
  } from '../core/types.js';
41
- import { createToolObservationEmbedding } from '../core/metadata-extractor.js';
42
- import { WorkingSetStore, createWorkingSetStore } from '../core/working-set-store.js';
43
- import { ConsolidatedStore, createConsolidatedStore } from '../core/consolidated-store.js';
44
- import { ConsolidationWorker, createConsolidationWorker } from '../core/consolidation-worker.js';
45
- import { ContinuityManager, createContinuityManager } from '../core/continuity-manager.js';
46
- import { GraduationWorker, createGraduationWorker, GraduationRunResult } from '../core/graduation-worker.js';
47
- import { MarkdownMirror } from '../core/md-mirror.js';
24
+ import type { EndlessMemoryServices } from '../extensions/endless-memory/index.js';
48
25
  import {
49
- IngestInterceptor,
50
- IngestInterceptorRegistry,
51
- mergeHierarchicalMetadata
52
- } from '../core/ingest-interceptor.js';
53
- import { normalizeTags } from '../core/tag-taxonomy.js';
54
-
55
- export interface MemoryServiceConfig {
56
- storagePath: string;
57
- embeddingModel?: string;
58
- readOnly?: boolean;
59
- /** Enable DuckDB analytics store (default: true for server, false for hooks) */
60
- analyticsEnabled?: boolean;
61
- /** Lightweight mode for hooks - skip heavy initialization (default: false) */
62
- lightweightMode?: boolean;
63
- /** Start only VectorWorker, skip GraduationWorker and SyncWorker (default: false) */
64
- embeddingOnly?: boolean;
65
- }
66
-
67
- // ============================================================
68
- // Project Path Utilities
69
- // ============================================================
70
-
71
- /**
72
- * Normalize and resolve a project path, handling symlinks
73
- */
74
- function normalizePath(projectPath: string): string {
75
- const expanded = projectPath.startsWith('~')
76
- ? path.join(os.homedir(), projectPath.slice(1))
77
- : projectPath;
78
-
79
- try {
80
- // Resolve symlinks for consistent paths
81
- return fs.realpathSync(expanded);
82
- } catch {
83
- // Path doesn't exist yet, just resolve it
84
- return path.resolve(expanded);
85
- }
86
- }
87
-
88
- /**
89
- * Generate a stable 8-character hash from a project path
90
- */
91
- export function hashProjectPath(projectPath: string): string {
92
- const normalizedPath = normalizePath(projectPath);
93
- return crypto.createHash('sha256')
94
- .update(normalizedPath)
95
- .digest('hex')
96
- .slice(0, 8);
97
- }
98
-
99
- /**
100
- * Get the storage path for a specific project
101
- */
102
- export function getProjectStoragePath(projectPath: string): string {
103
- const hash = hashProjectPath(projectPath);
104
- return path.join(os.homedir(), '.claude-code', 'memory', 'projects', hash);
105
- }
106
-
107
- // ============================================================
108
- // Session Registry
109
- // ============================================================
110
-
111
- const REGISTRY_PATH = path.join(os.homedir(), '.claude-code', 'memory', 'session-registry.json');
112
- const SHARED_STORAGE_PATH = path.join(os.homedir(), '.claude-code', 'memory', 'shared');
113
-
114
- export interface SessionRegistryEntry {
115
- projectPath: string;
116
- projectHash: string;
117
- registeredAt: string;
118
- }
119
-
120
- export interface SessionRegistry {
121
- version: number;
122
- sessions: Record<string, SessionRegistryEntry>;
123
- }
124
-
125
- export function loadSessionRegistry(): SessionRegistry {
126
- try {
127
- if (fs.existsSync(REGISTRY_PATH)) {
128
- const data = fs.readFileSync(REGISTRY_PATH, 'utf-8');
129
- return JSON.parse(data);
130
- }
131
- } catch (error) {
132
- console.error('Failed to load session registry:', error);
133
- }
134
- return { version: 1, sessions: {} };
135
- }
136
-
137
- function saveSessionRegistry(registry: SessionRegistry): void {
138
- const dir = path.dirname(REGISTRY_PATH);
139
- if (!fs.existsSync(dir)) {
140
- fs.mkdirSync(dir, { recursive: true });
141
- }
142
-
143
- // Atomic write using temp file
144
- const tempPath = REGISTRY_PATH + '.tmp';
145
- fs.writeFileSync(tempPath, JSON.stringify(registry, null, 2));
146
- fs.renameSync(tempPath, REGISTRY_PATH);
147
- }
148
-
149
- /**
150
- * Register a session with its project path
151
- */
152
- export function registerSession(sessionId: string, projectPath: string): void {
153
- const registry = loadSessionRegistry();
154
-
155
- registry.sessions[sessionId] = {
156
- projectPath: normalizePath(projectPath),
157
- projectHash: hashProjectPath(projectPath),
158
- registeredAt: new Date().toISOString()
159
- };
160
-
161
- // Clean up old sessions (keep last 1000)
162
- const entries = Object.entries(registry.sessions);
163
- if (entries.length > 1000) {
164
- const sorted = entries.sort((a, b) =>
165
- new Date(b[1].registeredAt).getTime() - new Date(a[1].registeredAt).getTime()
166
- );
167
- registry.sessions = Object.fromEntries(sorted.slice(0, 1000));
168
- }
169
-
170
- saveSessionRegistry(registry);
171
- }
172
-
173
- /**
174
- * Get the project path for a session
175
- */
176
- export function getSessionProject(sessionId: string): SessionRegistryEntry | null {
177
- const registry = loadSessionRegistry();
178
- return registry.sessions[sessionId] || null;
179
- }
26
+ type EmbeddingMaintenanceService,
27
+ type EmbeddingModelMaintenanceOptions,
28
+ type EmbeddingModelMaintenanceResult
29
+ } from '../core/engine/embedding-maintenance-service.js';
30
+ import type { MemoryRuntimeService } from '../core/engine/memory-runtime-service.js';
31
+ import type { GraduationRunResult } from '../core/graduation-worker.js';
32
+ import type { IngestInterceptor } from '../core/ingest-interceptor.js';
33
+ import type { MemoryIngestService } from '../core/engine/memory-ingest-service.js';
34
+ import type { MemoryQueryService } from '../core/engine/memory-query-service.js';
35
+ import { createMemoryServiceComposition } from '../core/engine/memory-service-composition.js';
36
+ import {
37
+ getProjectStoragePath as defaultGetProjectStoragePath,
38
+ hashProjectPath as defaultHashProjectPath
39
+ } from '../core/registry/project-path.js';
40
+ import { getSessionProject as defaultGetSessionProject } from '../core/registry/session-registry.js';
41
+ import {
42
+ DEFAULT_ENABLED_SHARED_STORE_CONFIG,
43
+ DEFAULT_SHARED_STORAGE_PATH,
44
+ DISABLED_SHARED_STORE_CONFIG,
45
+ type MemoryServiceConfig
46
+ } from './memory-service-config.js';
47
+ import { createMemoryServiceRegistry } from './memory-service-registry.js';
48
+ import {
49
+ type AccessedMemory,
50
+ type HelpfulMemory,
51
+ type HelpfulnessStats,
52
+ type RecordQueryTraceInput,
53
+ type RetrievalAnalyticsService,
54
+ type RetrievalDisclosureExpansion,
55
+ type RetrievalDisclosureExpandOptions,
56
+ type RetrievalDisclosureSearchOptions,
57
+ type RetrievalDisclosureSearchResponse,
58
+ type RetrievalDisclosureService,
59
+ type RetrievalDisclosureSource,
60
+ type RetrievalOrchestrator,
61
+ type RetrievalTrace,
62
+ type RetrievalTraceStats,
63
+ type RetrieveMemoriesOptions
64
+ } from '../core/engine/retrieval-services.js';
65
+ export { getProjectStoragePath, hashProjectPath } from '../core/registry/project-path.js';
66
+ export {
67
+ getSessionProject,
68
+ registerSession,
69
+ type SessionRegistry,
70
+ type SessionRegistryEntry,
71
+ loadSessionRegistry
72
+ } from '../core/registry/session-registry.js';
73
+
74
+ export {
75
+ DEFAULT_ENABLED_SHARED_STORE_CONFIG,
76
+ DEFAULT_SHARED_STORAGE_PATH,
77
+ DISABLED_SHARED_STORE_CONFIG,
78
+ type MemoryServiceConfig
79
+ } from './memory-service-config.js';
180
80
 
181
81
  export class MemoryService {
182
- // Primary store: SQLite (WAL mode) - for hooks, always available
183
- private readonly sqliteStore: SQLiteEventStore;
184
-
185
- private readonly vectorStore: VectorStore;
186
- private readonly embedder: Embedder;
187
- private readonly matcher: Matcher;
188
- private readonly retriever: Retriever;
189
- private readonly graduation: GraduationPipeline;
190
- private vectorWorker: VectorWorker | null = null;
191
- private graduationWorker: GraduationWorker | null = null;
192
- private initialized = false;
193
- private readonly ingestInterceptors = new IngestInterceptorRegistry();
82
+ private readonly retrievalOrchestrator: RetrievalOrchestrator;
83
+ private readonly retrievalDisclosureService: RetrievalDisclosureService;
84
+ private readonly retrievalAnalyticsService: RetrievalAnalyticsService;
85
+ private readonly embeddingMaintenanceService: EmbeddingMaintenanceService;
86
+ private readonly runtimeService: MemoryRuntimeService;
194
87
 
195
88
  // Endless Mode components
196
- private workingSetStore: WorkingSetStore | null = null;
197
- private consolidatedStore: ConsolidatedStore | null = null;
198
- private consolidationWorker: ConsolidationWorker | null = null;
199
- private continuityManager: ContinuityManager | null = null;
200
- private endlessMode: MemoryMode = 'session';
89
+ private readonly endlessMemoryServices: EndlessMemoryServices;
201
90
 
202
91
  // Shared Store components (cross-project knowledge)
203
- private sharedEventStore: SharedEventStore | null = null;
204
- private sharedStore: SharedStore | null = null;
205
- private sharedVectorStore: SharedVectorStore | null = null;
206
- private sharedPromoter: SharedPromoter | null = null;
207
- private sharedStoreConfig: SharedStoreConfig | null = null;
92
+ private sharedMemoryServices!: SharedMemoryServices;
208
93
  private projectHash: string | null = null;
209
94
  private projectPath: string | null = null;
210
95
 
211
96
  private readonly readOnly: boolean;
212
97
  private readonly lightweightMode: boolean;
213
98
  private readonly embeddingOnly: boolean;
214
- private readonly mdMirror: MarkdownMirror;
215
- private readonly storagePath: string;
99
+ private readonly ingestService: MemoryIngestService;
100
+ private readonly queryService: MemoryQueryService;
216
101
 
217
102
  constructor(config: MemoryServiceConfig & { projectHash?: string; projectPath?: string; sharedStoreConfig?: SharedStoreConfig }) {
218
- const storagePath = this.expandPath(config.storagePath);
219
- this.storagePath = storagePath;
220
103
  this.readOnly = config.readOnly ?? false;
221
104
  this.lightweightMode = config.lightweightMode ?? false;
222
105
  this.embeddingOnly = config.embeddingOnly ?? false;
223
- this.mdMirror = new MarkdownMirror(process.cwd());
224
-
225
- // Ensure storage directory exists (only if not read-only)
226
- if (!this.readOnly && !fs.existsSync(storagePath)) {
227
- fs.mkdirSync(storagePath, { recursive: true });
228
- }
229
106
 
230
107
  // Store project hash for shared store operations
231
108
  this.projectHash = config.projectHash || null;
232
109
  this.projectPath = config.projectPath || null;
233
- // Default: shared store enabled
234
- this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
235
-
236
- // Initialize PRIMARY store: SQLite (WAL mode)
237
- // This is always used for writes and is the source of truth
238
- this.sqliteStore = new SQLiteEventStore(
239
- path.join(storagePath, 'events.sqlite'),
240
- {
241
- readonly: this.readOnly,
242
- markdownMirrorRoot: storagePath
243
- }
244
- );
245
-
246
- this.vectorStore = new VectorStore(path.join(storagePath, 'vectors'));
247
- const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
248
- this.embedder = embeddingModel
249
- ? new Embedder(embeddingModel)
250
- : getDefaultEmbedder();
251
- this.matcher = getDefaultMatcher();
252
- // Retriever uses SQLite as primary (always available)
253
- this.retriever = createRetriever(
254
- this.sqliteStore as unknown as EventStore, // Interface compatible
255
- this.vectorStore,
256
- this.embedder,
257
- this.matcher
258
- );
259
- this.retriever.setQueryRewriter((q) => this.rewriteQueryIntent(q));
260
- this.graduation = createGraduationPipeline(this.sqliteStore as unknown as EventStore);
110
+ const sharedStoreConfig = config.sharedStoreConfig ?? DEFAULT_ENABLED_SHARED_STORE_CONFIG;
111
+
112
+ const composition = createMemoryServiceComposition({
113
+ config: {
114
+ ...config,
115
+ storagePath: config.storagePath,
116
+ readOnly: this.readOnly,
117
+ lightweightMode: this.lightweightMode,
118
+ embeddingOnly: this.embeddingOnly,
119
+ sharedStoreConfig
120
+ },
121
+ defaultSharedStoragePath: DEFAULT_SHARED_STORAGE_PATH,
122
+ defaultSharedStoreConfig: DEFAULT_ENABLED_SHARED_STORE_CONFIG,
123
+ initialize: () => this.initialize(),
124
+ getProjectHash: () => this.projectHash,
125
+ getProjectPath: () => this.projectPath
126
+ });
127
+
128
+ this.retrievalOrchestrator = composition.retrievalOrchestrator;
129
+ this.retrievalDisclosureService = composition.retrievalDisclosureService;
130
+ this.retrievalAnalyticsService = composition.retrievalAnalyticsService;
131
+ this.ingestService = composition.ingestService;
132
+ this.queryService = composition.queryService;
133
+ this.endlessMemoryServices = composition.endlessMemoryServices;
134
+ this.sharedMemoryServices = composition.sharedMemoryServices;
135
+ this.runtimeService = composition.runtimeService;
136
+ this.embeddingMaintenanceService = composition.embeddingMaintenanceService;
261
137
  }
262
138
 
263
139
  /**
264
140
  * Initialize all components
265
141
  */
266
142
  async initialize(): Promise<void> {
267
- if (this.initialized) return;
268
-
269
- // Initialize PRIMARY store: SQLite (always)
270
- await this.sqliteStore.initialize();
271
-
272
- // Lightweight mode: only SQLite, no embedder/vector/workers
273
- // Used for hooks that just need to store data quickly
274
- if (this.lightweightMode) {
275
- this.initialized = true;
276
- return;
277
- }
278
-
279
- await this.vectorStore.initialize();
280
- await this.embedder.initialize();
281
-
282
- // Skip write-related workers in read-only mode
283
- if (!this.readOnly) {
284
- // Start vector worker (uses SQLite as source)
285
- this.vectorWorker = createVectorWorker(
286
- this.sqliteStore as unknown as EventStore,
287
- this.vectorStore,
288
- this.embedder
289
- );
290
- this.vectorWorker.start();
291
-
292
- if (!this.embeddingOnly) {
293
- // Connect graduation pipeline to retriever for access tracking
294
- this.retriever.setGraduationPipeline(this.graduation);
295
-
296
- // Start graduation worker for automatic level promotion
297
- this.graduationWorker = createGraduationWorker(
298
- this.sqliteStore as unknown as EventStore,
299
- this.graduation
300
- );
301
- this.graduationWorker.start();
302
-
303
- }
304
-
305
- // Load endless mode setting
306
- const savedMode = await this.sqliteStore.getEndlessConfig('mode') as MemoryMode | null;
307
- if (savedMode === 'endless') {
308
- this.endlessMode = 'endless';
309
- await this.initializeEndlessMode();
310
- }
311
-
312
- // Initialize shared store (enabled by default)
313
- if (this.sharedStoreConfig?.enabled !== false) {
314
- await this.initializeSharedStore();
315
- }
316
- }
317
-
318
- this.initialized = true;
319
- }
320
-
321
- /**
322
- * Initialize Shared Store components
323
- */
324
- private async initializeSharedStore(): Promise<void> {
325
- const sharedPath = this.sharedStoreConfig?.sharedStoragePath
326
- ? this.expandPath(this.sharedStoreConfig.sharedStoragePath)
327
- : SHARED_STORAGE_PATH;
328
-
329
- // Ensure shared directory exists
330
- if (!fs.existsSync(sharedPath)) {
331
- fs.mkdirSync(sharedPath, { recursive: true });
332
- }
333
-
334
- this.sharedEventStore = createSharedEventStore(
335
- path.join(sharedPath, 'shared.duckdb')
336
- );
337
- await this.sharedEventStore.initialize();
338
-
339
- this.sharedStore = createSharedStore(this.sharedEventStore);
340
- this.sharedVectorStore = createSharedVectorStore(
341
- path.join(sharedPath, 'vectors')
342
- );
343
- await this.sharedVectorStore.initialize();
344
-
345
- this.sharedPromoter = createSharedPromoter(
346
- this.sharedStore,
347
- this.sharedVectorStore,
348
- this.embedder,
349
- this.sharedStoreConfig || undefined
350
- );
351
-
352
- // Connect shared stores to retriever
353
- this.retriever.setSharedStores(this.sharedStore, this.sharedVectorStore);
143
+ await this.runtimeService.initialize();
354
144
  }
355
145
 
356
146
  registerIngestBefore(interceptor: IngestInterceptor): () => void {
357
- return this.ingestInterceptors.registerBefore(interceptor);
147
+ return this.ingestService.registerIngestBefore(interceptor);
358
148
  }
359
149
 
360
150
  registerIngestAfter(interceptor: IngestInterceptor): () => void {
361
- return this.ingestInterceptors.registerAfter(interceptor);
151
+ return this.ingestService.registerIngestAfter(interceptor);
362
152
  }
363
153
 
364
154
  registerIngestOnError(interceptor: IngestInterceptor): () => void {
365
- return this.ingestInterceptors.registerOnError(interceptor);
366
- }
367
-
368
- private async ingestWithInterceptors(
369
- operation: 'user_prompt' | 'agent_response' | 'session_summary' | 'tool_observation',
370
- input: MemoryEventInput,
371
- onSuccess?: (eventId: string) => Promise<void>
372
- ): Promise<AppendResult> {
373
- const normalizedInput: MemoryEventInput = {
374
- ...input,
375
- metadata: mergeHierarchicalMetadata(
376
- {
377
- ingest: {
378
- operation,
379
- pipeline: 'default',
380
- ts: new Date().toISOString()
381
- },
382
- ...(this.projectHash
383
- ? {
384
- scope: {
385
- project: {
386
- hash: this.projectHash,
387
- ...(this.projectPath ? { path: this.projectPath } : {})
388
- }
389
- },
390
- tags: [`proj:${this.projectHash}`]
391
- }
392
- : {})
393
- },
394
- input.metadata
395
- )
396
- };
397
-
398
- if (this.projectHash && normalizedInput.metadata) {
399
- const meta = normalizedInput.metadata as Record<string, unknown>;
400
- const currentTags = Array.isArray(meta.tags)
401
- ? meta.tags.filter((x): x is string => typeof x === 'string')
402
- : [];
403
- const projectTag = `proj:${this.projectHash}`;
404
- if (!currentTags.includes(projectTag)) {
405
- meta.tags = [...currentTags, projectTag];
406
- }
407
- }
408
-
409
- if (normalizedInput.metadata) {
410
- const meta = normalizedInput.metadata as Record<string, unknown>;
411
- const normalizedTags = normalizeTags(meta.tags);
412
- if (normalizedTags.length > 0) {
413
- meta.tags = normalizedTags;
414
- }
415
- }
416
-
417
- await this.ingestInterceptors.run('before', {
418
- operation,
419
- sessionId: normalizedInput.sessionId,
420
- event: normalizedInput
421
- });
422
-
423
- try {
424
- const result = await this.sqliteStore.append(normalizedInput);
425
- if (result.success && !result.isDuplicate) {
426
- if (onSuccess) {
427
- await onSuccess(result.eventId);
428
- }
429
- try {
430
- await this.mdMirror.append(normalizedInput, result.eventId);
431
- } catch {
432
- // non-breaking markdown mirror write
433
- }
434
- }
435
-
436
- await this.ingestInterceptors.run('after', {
437
- operation,
438
- sessionId: normalizedInput.sessionId,
439
- event: normalizedInput
440
- });
441
-
442
- return result;
443
- } catch (error) {
444
- const normalizedError = error instanceof Error ? error : new Error(String(error));
445
- await this.ingestInterceptors.run('error', {
446
- operation,
447
- sessionId: normalizedInput.sessionId,
448
- event: normalizedInput,
449
- error: normalizedError
450
- });
451
- throw error;
452
- }
155
+ return this.ingestService.registerIngestOnError(interceptor);
453
156
  }
454
157
 
455
158
  /**
456
159
  * Start a new session
457
160
  */
458
161
  async startSession(sessionId: string, projectPath?: string): Promise<void> {
459
- await this.initialize();
460
-
461
- await this.sqliteStore.upsertSession({
462
- id: sessionId,
463
- startedAt: new Date(),
464
- projectPath
465
- });
162
+ return this.ingestService.startSession(sessionId, projectPath);
466
163
  }
467
164
 
468
165
  /**
469
166
  * End a session
470
167
  */
471
168
  async endSession(sessionId: string, summary?: string): Promise<void> {
472
- await this.initialize();
473
-
474
- await this.sqliteStore.upsertSession({
475
- id: sessionId,
476
- endedAt: new Date(),
477
- summary
478
- });
169
+ return this.ingestService.endSession(sessionId, summary);
479
170
  }
480
171
 
481
172
  /**
@@ -486,21 +177,7 @@ export class MemoryService {
486
177
  content: string,
487
178
  metadata?: Record<string, unknown>
488
179
  ): Promise<AppendResult> {
489
- await this.initialize();
490
-
491
- return this.ingestWithInterceptors(
492
- 'user_prompt',
493
- {
494
- eventType: 'user_prompt',
495
- sessionId,
496
- timestamp: new Date(),
497
- content,
498
- metadata
499
- },
500
- async (eventId) => {
501
- await this.sqliteStore.enqueueForEmbedding(eventId, content);
502
- }
503
- );
180
+ return this.ingestService.storeUserPrompt(sessionId, content, metadata);
504
181
  }
505
182
 
506
183
  /**
@@ -511,21 +188,7 @@ export class MemoryService {
511
188
  content: string,
512
189
  metadata?: Record<string, unknown>
513
190
  ): Promise<AppendResult> {
514
- await this.initialize();
515
-
516
- return this.ingestWithInterceptors(
517
- 'agent_response',
518
- {
519
- eventType: 'agent_response',
520
- sessionId,
521
- timestamp: new Date(),
522
- content,
523
- metadata
524
- },
525
- async (eventId) => {
526
- await this.sqliteStore.enqueueForEmbedding(eventId, content);
527
- }
528
- );
191
+ return this.ingestService.storeAgentResponse(sessionId, content, metadata);
529
192
  }
530
193
 
531
194
  /**
@@ -536,21 +199,7 @@ export class MemoryService {
536
199
  summary: string,
537
200
  metadata?: Record<string, unknown>
538
201
  ): Promise<AppendResult> {
539
- await this.initialize();
540
-
541
- return this.ingestWithInterceptors(
542
- 'session_summary',
543
- {
544
- eventType: 'session_summary',
545
- sessionId,
546
- timestamp: new Date(),
547
- content: summary,
548
- metadata
549
- },
550
- async (eventId) => {
551
- await this.sqliteStore.enqueueForEmbedding(eventId, summary);
552
- }
553
- );
202
+ return this.ingestService.storeSessionSummary(sessionId, summary, metadata);
554
203
  }
555
204
 
556
205
  /**
@@ -558,17 +207,7 @@ export class MemoryService {
558
207
  * Called from session-start hook to catch sessions that ended without Stop hook.
559
208
  */
560
209
  async backfillMissingSummaries(currentSessionId: string, limit = 5): Promise<void> {
561
- await this.initialize();
562
-
563
- // Get recent sessions that don't have a summary event
564
- const recentSessionIds = await this.sqliteStore.getSessionsWithoutSummary(currentSessionId, limit);
565
- for (const sid of recentSessionIds) {
566
- try {
567
- await this.generateSessionSummary(sid);
568
- } catch {
569
- // non-critical
570
- }
571
- }
210
+ return this.ingestService.backfillMissingSummaries(currentSessionId, limit);
572
211
  }
573
212
 
574
213
  /**
@@ -577,41 +216,7 @@ export class MemoryService {
577
216
  * Skips if a summary already exists for this session.
578
217
  */
579
218
  async generateSessionSummary(sessionId: string): Promise<void> {
580
- await this.initialize();
581
-
582
- const events = await this.sqliteStore.getSessionEvents(sessionId);
583
- if (events.length < 3) return; // Too short to summarize
584
-
585
- // Skip if summary already exists
586
- const hasSummary = events.some((e) => e.eventType === 'session_summary');
587
- if (hasSummary) return;
588
-
589
- const prompts = events.filter((e) => e.eventType === 'user_prompt');
590
- const toolObs = events.filter((e) => e.eventType === 'tool_observation');
591
- const toolNames = [...new Set(
592
- toolObs.map((e) => (e.metadata as Record<string, unknown>)?.toolName as string).filter(Boolean)
593
- )];
594
- const errorObs = toolObs.filter((e) => {
595
- const meta = e.metadata as Record<string, unknown>;
596
- return meta?.exitCode !== undefined && meta.exitCode !== 0;
597
- });
598
-
599
- const datePart = events[0].timestamp.toISOString().split('T')[0];
600
- const parts: string[] = [`[${datePart}] ${prompts.length}턴 세션.`];
601
-
602
- if (prompts.length > 0) {
603
- const firstPrompt = prompts[0].content.slice(0, 120).replace(/\n/g, ' ');
604
- parts.push(`주요 작업: ${firstPrompt}`);
605
- }
606
- if (toolNames.length > 0) {
607
- parts.push(`사용 툴: ${toolNames.slice(0, 6).join(', ')}`);
608
- }
609
- if (errorObs.length > 0) {
610
- parts.push(`오류 ${errorObs.length}건 발생`);
611
- }
612
-
613
- const summary = parts.join('. ');
614
- await this.storeSessionSummary(sessionId, summary, { generated: 'rule-based', eventCount: events.length });
219
+ return this.ingestService.generateSessionSummary(sessionId);
615
220
  }
616
221
 
617
222
  /**
@@ -621,36 +226,7 @@ export class MemoryService {
621
226
  sessionId: string,
622
227
  payload: ToolObservationPayload
623
228
  ): Promise<AppendResult> {
624
- await this.initialize();
625
-
626
- // Create content for storage (JSON stringified payload)
627
- const content = JSON.stringify(payload);
628
-
629
- // Extract turnId from payload metadata if present (set by PostToolUse hook)
630
- const turnId = payload.metadata?.turnId;
631
-
632
- return this.ingestWithInterceptors(
633
- 'tool_observation',
634
- {
635
- eventType: 'tool_observation',
636
- sessionId,
637
- timestamp: new Date(),
638
- content,
639
- metadata: {
640
- toolName: payload.toolName,
641
- success: payload.success,
642
- ...(turnId ? { turnId } : {})
643
- }
644
- },
645
- async (eventId) => {
646
- const embeddingContent = createToolObservationEmbedding(
647
- payload.toolName,
648
- payload.metadata || {},
649
- payload.success
650
- );
651
- await this.sqliteStore.enqueueForEmbedding(eventId, embeddingContent);
652
- }
653
- );
229
+ return this.ingestService.storeToolObservation(sessionId, payload);
654
230
  }
655
231
 
656
232
  /**
@@ -658,193 +234,36 @@ export class MemoryService {
658
234
  */
659
235
  async retrieveMemories(
660
236
  query: string,
661
- options?: {
662
- topK?: number;
663
- minScore?: number;
664
- sessionId?: string;
665
- includeShared?: boolean;
666
- adaptiveRerank?: boolean;
667
- intentRewrite?: boolean;
668
- projectScopeMode?: 'strict' | 'prefer' | 'global';
669
- allowedProjectHashes?: string[];
670
- }
237
+ options?: RetrieveMemoriesOptions
671
238
  ): Promise<UnifiedRetrievalResult> {
672
- await this.initialize();
673
-
674
- // Note: Pending embeddings are processed by the background worker
675
- // Don't block retrieval - search with whatever vectors are available
676
-
677
- const rerankWeights = await this.getRerankWeights(options?.adaptiveRerank === true);
678
-
679
- // Use unified retrieval if shared search is requested
680
- let result: UnifiedRetrievalResult;
681
-
682
- if (options?.includeShared && this.sharedStore) {
683
- result = await this.retriever.retrieveUnified(query, {
684
- ...options,
685
- intentRewrite: options?.intentRewrite === true,
686
- rerankWeights,
687
- includeShared: true,
688
- projectHash: this.projectHash || undefined,
689
- projectScopeMode: options?.projectScopeMode ?? (this.projectHash ? 'strict' : 'global'),
690
- allowedProjectHashes: options?.allowedProjectHashes
691
- });
692
- } else {
693
- result = await this.retriever.retrieve(query, {
694
- ...options,
695
- intentRewrite: options?.intentRewrite === true,
696
- rerankWeights,
697
- projectHash: this.projectHash || undefined,
698
- projectScopeMode: options?.projectScopeMode ?? (this.projectHash ? 'strict' : 'global'),
699
- allowedProjectHashes: options?.allowedProjectHashes
700
- });
701
- }
702
-
703
- try {
704
- const selectedEventIds = result.memories.map((m) => m.event.id);
705
- const selectedDetails = (result.selectedDebug || []).map((d) => ({
706
- eventId: d.eventId,
707
- score: d.score,
708
- semanticScore: d.semanticScore,
709
- lexicalScore: d.lexicalScore,
710
- recencyScore: d.recencyScore,
711
- }));
712
- const candidateDetails = (result.candidateDebug || []).map((d) => ({
713
- eventId: d.eventId,
714
- score: d.score,
715
- semanticScore: d.semanticScore,
716
- lexicalScore: d.lexicalScore,
717
- recencyScore: d.recencyScore,
718
- }));
719
- const candidateEventIds = candidateDetails.length > 0
720
- ? candidateDetails.map((d) => d.eventId)
721
- : selectedEventIds;
722
- await this.sqliteStore.recordRetrievalTrace({
723
- sessionId: options?.sessionId,
724
- projectHash: this.projectHash || undefined,
725
- queryText: query,
726
- strategy: options?.strategy || 'auto',
727
- candidateEventIds,
728
- selectedEventIds,
729
- candidateDetails,
730
- selectedDetails,
731
- confidence: result.matchResult.confidence,
732
- fallbackTrace: result.fallbackTrace || []
733
- });
734
- } catch {
735
- // non-blocking telemetry
736
- }
737
-
738
- return result;
739
- }
740
-
741
- private getConfiguredRerankWeights(): { semantic: number; lexical: number; recency: number } | undefined {
742
- const semantic = Number(process.env.MEMORY_RERANK_WEIGHT_SEMANTIC ?? '');
743
- const lexical = Number(process.env.MEMORY_RERANK_WEIGHT_LEXICAL ?? '');
744
- const recency = Number(process.env.MEMORY_RERANK_WEIGHT_RECENCY ?? '');
745
-
746
- const allFinite = [semantic, lexical, recency].every((v) => Number.isFinite(v));
747
- if (!allFinite) return undefined;
748
-
749
- const nonNegative = [semantic, lexical, recency].every((v) => v >= 0);
750
- const total = semantic + lexical + recency;
751
- if (!nonNegative || total <= 0) return undefined;
752
-
753
- return {
754
- semantic: semantic / total,
755
- lexical: lexical / total,
756
- recency: recency / total,
757
- };
758
- }
759
-
760
- private async getRerankWeights(adaptive: boolean): Promise<{ semantic: number; lexical: number; recency: number } | undefined> {
761
- const configured = this.getConfiguredRerankWeights();
762
- if (configured) return configured;
763
- if (adaptive) return this.getAdaptiveRerankWeights();
764
- return undefined;
765
- }
766
-
767
- private async rewriteQueryIntent(query: string): Promise<string | null> {
768
- if (process.env.MEMORY_INTENT_REWRITE_ENABLED !== '1') return null;
769
-
770
- const apiUrl = process.env.COMPANY_STOCK_API_URL || process.env.COMPANY_INT_API_URL;
771
- if (!apiUrl) return null;
772
-
773
- const controller = new AbortController();
774
- const timeoutMs = Number(process.env.MEMORY_INTENT_REWRITE_TIMEOUT_MS || 5000);
775
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
776
-
777
- try {
778
- const prompt = [
779
- 'Rewrite user query for memory retrieval intent expansion.',
780
- 'Return plain text only, one line, no markdown.',
781
- `Query: ${query}`,
782
- ].join('\n');
783
-
784
- const res = await fetch(apiUrl, {
785
- method: 'POST',
786
- headers: {
787
- 'Content-Type': 'application/json',
788
- Accept: '*/*',
789
- Origin: process.env.COMPANY_INT_ORIGIN || 'http://company-int.aplusai.ai',
790
- Referer: process.env.COMPANY_INT_REFERER || 'http://company-int.aplusai.ai/',
791
- },
792
- body: JSON.stringify({
793
- question: prompt,
794
- company_name: null,
795
- conversation_id: null,
796
- }),
797
- signal: controller.signal,
798
- });
799
-
800
- const text = (await res.text()).trim();
801
- if (!text) return null;
802
-
803
- const oneLine = text
804
- .replace(/^data:\s*/gm, '')
805
- .split(/\r?\n/)
806
- .map((x) => x.trim())
807
- .filter(Boolean)
808
- .join(' ')
809
- .slice(0, 240);
810
-
811
- if (!oneLine || oneLine.toLowerCase() === query.toLowerCase()) return null;
812
- return oneLine;
813
- } catch {
814
- return null;
815
- } finally {
816
- clearTimeout(timeout);
817
- }
818
- }
819
-
820
- private async getAdaptiveRerankWeights(): Promise<{ semantic: number; lexical: number; recency: number } | undefined> {
821
- try {
822
- const s = await this.sqliteStore.getHelpfulnessStats();
823
- if (s.totalEvaluated < 20) return undefined;
824
-
825
- // base weights
826
- let semantic = 0.7;
827
- let lexical = 0.2;
828
- let recency = 0.1;
829
-
830
- if (s.avgScore < 0.45) {
831
- semantic -= 0.1;
832
- lexical += 0.1;
833
- } else if (s.avgScore > 0.75) {
834
- semantic += 0.05;
835
- lexical -= 0.05;
836
- }
837
-
838
- if (s.unhelpful > s.helpful) {
839
- recency += 0.05;
840
- semantic -= 0.03;
841
- lexical -= 0.02;
842
- }
843
-
844
- return { semantic, lexical, recency };
845
- } catch {
846
- return undefined;
847
- }
239
+ return this.retrievalOrchestrator.retrieveMemories(query, options);
240
+ }
241
+
242
+ /**
243
+ * Layer 1 retrieval disclosure: lightweight search envelopes for UI/API/agent use.
244
+ */
245
+ async searchDisclosure(
246
+ query: string,
247
+ options?: RetrievalDisclosureSearchOptions
248
+ ): Promise<RetrievalDisclosureSearchResponse> {
249
+ return this.retrievalDisclosureService.search(query, options);
250
+ }
251
+
252
+ /**
253
+ * Layer 2 retrieval disclosure: expand a search result into surrounding timeline context.
254
+ */
255
+ async expandDisclosure(
256
+ resultId: string,
257
+ options?: RetrievalDisclosureExpandOptions
258
+ ): Promise<RetrievalDisclosureExpansion | null> {
259
+ return this.retrievalDisclosureService.expand(resultId, options);
260
+ }
261
+
262
+ /**
263
+ * Layer 3 retrieval disclosure: resolve a search result to its raw source event.
264
+ */
265
+ async sourceDisclosure(resultId: string): Promise<RetrievalDisclosureSource | null> {
266
+ return this.retrievalDisclosureService.source(resultId);
848
267
  }
849
268
 
850
269
  /**
@@ -855,44 +274,28 @@ export class MemoryService {
855
274
  query: string,
856
275
  options?: { topK?: number; minScore?: number }
857
276
  ): Promise<Array<{event: MemoryEvent; score: number}>> {
858
- await this.initialize();
859
-
860
- const results = await this.sqliteStore.keywordSearch(query, options?.topK ?? 10);
861
-
862
- // Normalize FTS5 rank to a score (0-1 range)
863
- // FTS5 rank is negative (higher is worse), so we convert it
864
- const maxRank = Math.min(...results.map(r => r.rank), -0.001);
865
- const minRank = Math.max(...results.map(r => r.rank), -1000);
866
- const rankRange = maxRank - minRank || 1;
867
-
868
- return results.map(r => ({
869
- event: r.event,
870
- score: 1 - (r.rank - minRank) / rankRange // Normalize to 0-1
871
- })).filter(r => !options?.minScore || r.score >= options.minScore);
277
+ return this.queryService.keywordSearch(query, options);
872
278
  }
873
279
 
874
280
  /**
875
281
  * Rebuild FTS index (call after database upgrade)
876
282
  */
877
283
  async rebuildFtsIndex(): Promise<number> {
878
- await this.initialize();
879
- return this.sqliteStore.rebuildFtsIndex();
284
+ return this.queryService.rebuildFtsIndex();
880
285
  }
881
286
 
882
287
  /**
883
288
  * Get session history
884
289
  */
885
290
  async getSessionHistory(sessionId: string): Promise<MemoryEvent[]> {
886
- await this.initialize();
887
- return this.sqliteStore.getSessionEvents(sessionId);
291
+ return this.queryService.getSessionHistory(sessionId);
888
292
  }
889
293
 
890
294
  /**
891
295
  * Get recent events
892
296
  */
893
297
  async getRecentEvents(limit: number = 100): Promise<MemoryEvent[]> {
894
- await this.initialize();
895
- return this.sqliteStore.getRecentEvents(limit);
298
+ return this.queryService.getRecentEvents(limit);
896
299
  }
897
300
 
898
301
  /**
@@ -903,23 +306,15 @@ export class MemoryService {
903
306
  embedding: { pending: number; processing: number; failed: number; total: number };
904
307
  vector: { pending: number; processing: number; failed: number; total: number };
905
308
  }> {
906
- await this.initialize();
907
- return this.sqliteStore.getOutboxStats();
309
+ return this.queryService.getOutboxStats();
908
310
  }
909
311
 
910
- async getRetrievalTraceStats(): Promise<{
911
- totalQueries: number;
912
- avgCandidateCount: number;
913
- avgSelectedCount: number;
914
- selectionRate: number;
915
- }> {
916
- await this.initialize();
917
- return this.sqliteStore.getRetrievalTraceStats();
312
+ async getRetrievalTraceStats(): Promise<RetrievalTraceStats> {
313
+ return this.retrievalAnalyticsService.getRetrievalTraceStats();
918
314
  }
919
315
 
920
- async getRecentRetrievalTraces(limit: number = 50) {
921
- await this.initialize();
922
- return this.sqliteStore.getRecentRetrievalTraces(limit);
316
+ async getRecentRetrievalTraces(limit: number = 50): Promise<RetrievalTrace[]> {
317
+ return this.retrievalAnalyticsService.getRecentRetrievalTraces(limit);
923
318
  }
924
319
 
925
320
  async getStats(): Promise<{
@@ -927,63 +322,35 @@ export class MemoryService {
927
322
  vectorCount: number;
928
323
  levelStats: Array<{ level: string; count: number }>;
929
324
  }> {
930
- await this.initialize();
931
-
932
- const recentEvents = await this.sqliteStore.getRecentEvents(10000);
933
- const vectorCount = await this.vectorStore.count();
934
- const levelStats = await this.graduation.getStats();
935
-
936
- return {
937
- totalEvents: recentEvents.length,
938
- vectorCount,
939
- levelStats
940
- };
325
+ return this.queryService.getStats();
941
326
  }
942
327
 
943
328
  /**
944
329
  * Process pending embeddings
945
330
  */
946
331
  async processPendingEmbeddings(): Promise<number> {
947
- if (this.vectorWorker) {
948
- return this.vectorWorker.processAll();
949
- }
950
- return 0;
332
+ return this.runtimeService.processPendingEmbeddings();
951
333
  }
952
334
 
953
335
  /**
954
336
  * Get events by memory level
955
337
  */
956
338
  async getEventsByLevel(level: string, options?: { limit?: number; offset?: number }): Promise<MemoryEvent[]> {
957
- await this.initialize();
958
- return this.sqliteStore.getEventsByLevel(level, options);
339
+ return this.queryService.getEventsByLevel(level, options);
959
340
  }
960
341
 
961
342
  /**
962
343
  * Get memory level for a specific event
963
344
  */
964
345
  async getEventLevel(eventId: string): Promise<string | null> {
965
- await this.initialize();
966
- return this.sqliteStore.getEventLevel(eventId);
346
+ return this.queryService.getEventLevel(eventId);
967
347
  }
968
348
 
969
349
  /**
970
350
  * Format retrieval results as context for Claude
971
351
  */
972
352
  formatAsContext(result: RetrievalResult): string {
973
- if (!result.context) {
974
- return '';
975
- }
976
-
977
- const confidence = result.matchResult.confidence;
978
- let header = '';
979
-
980
- if (confidence === 'high') {
981
- header = '🎯 **High-confidence memory match found:**\n\n';
982
- } else if (confidence === 'suggested') {
983
- header = '💡 **Suggested memories (may be relevant):**\n\n';
984
- }
985
-
986
- return header + result.context;
353
+ return this.retrievalOrchestrator.formatAsContext(result);
987
354
  }
988
355
 
989
356
  // ============================================================
@@ -994,21 +361,14 @@ export class MemoryService {
994
361
  * Check if shared store is enabled and initialized
995
362
  */
996
363
  isSharedStoreEnabled(): boolean {
997
- return this.sharedStore !== null;
364
+ return this.sharedMemoryServices.isEnabled();
998
365
  }
999
366
 
1000
367
  /**
1001
368
  * Promote an entry to shared storage
1002
369
  */
1003
370
  async promoteToShared(entry: Entry): Promise<PromotionResult> {
1004
- if (!this.sharedPromoter || !this.projectHash) {
1005
- return {
1006
- success: false,
1007
- error: 'Shared store not initialized or project hash not set'
1008
- };
1009
- }
1010
-
1011
- return this.sharedPromoter.promoteEntry(entry, this.projectHash);
371
+ return this.sharedMemoryServices.promoteToShared(entry, this.projectHash);
1012
372
  }
1013
373
 
1014
374
  /**
@@ -1020,8 +380,7 @@ export class MemoryService {
1020
380
  topTopics: Array<{ topic: string; count: number }>;
1021
381
  totalUsageCount: number;
1022
382
  } | null> {
1023
- if (!this.sharedStore) return null;
1024
- return this.sharedStore.getStats();
383
+ return this.sharedMemoryServices.getStats();
1025
384
  }
1026
385
 
1027
386
  /**
@@ -1031,8 +390,7 @@ export class MemoryService {
1031
390
  query: string,
1032
391
  options?: { topK?: number; minConfidence?: number }
1033
392
  ) {
1034
- if (!this.sharedStore) return [];
1035
- return this.sharedStore.search(query, options);
393
+ return this.sharedMemoryServices.search(query, options);
1036
394
  }
1037
395
 
1038
396
  /**
@@ -1046,119 +404,60 @@ export class MemoryService {
1046
404
  // Endless Mode Methods
1047
405
  // ============================================================
1048
406
 
1049
- /**
1050
- * Get the default endless mode config
1051
- */
1052
- private getDefaultEndlessConfig(): EndlessModeConfig {
1053
- return {
1054
- enabled: true,
1055
- workingSet: {
1056
- maxEvents: 100,
1057
- timeWindowHours: 24,
1058
- minRelevanceScore: 0.5
1059
- },
1060
- consolidation: {
1061
- triggerIntervalMs: 3600000, // 1 hour
1062
- triggerEventCount: 100,
1063
- triggerIdleMs: 1800000, // 30 minutes
1064
- useLLMSummarization: false
1065
- },
1066
- continuity: {
1067
- minScoreForSeamless: 0.7,
1068
- topicDecayHours: 48
1069
- }
1070
- };
1071
- }
1072
-
1073
407
  /**
1074
408
  * Initialize Endless Mode components
1075
409
  */
1076
410
  async initializeEndlessMode(): Promise<void> {
1077
- const config = await this.getEndlessConfig();
1078
-
1079
- this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
1080
- this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
1081
- this.consolidationWorker = createConsolidationWorker(
1082
- this.workingSetStore,
1083
- this.consolidatedStore,
1084
- config
1085
- );
1086
- this.continuityManager = createContinuityManager(this.sqliteStore, config);
1087
-
1088
- // Start consolidation worker
1089
- this.consolidationWorker.start();
411
+ return this.endlessMemoryServices.initializeEndlessMode();
1090
412
  }
1091
413
 
1092
414
  /**
1093
415
  * Get Endless Mode configuration
1094
416
  */
1095
417
  async getEndlessConfig(): Promise<EndlessModeConfig> {
1096
- const savedConfig = await this.sqliteStore.getEndlessConfig('config') as EndlessModeConfig | null;
1097
- return savedConfig || this.getDefaultEndlessConfig();
418
+ return this.endlessMemoryServices.getEndlessConfig();
1098
419
  }
1099
420
 
1100
421
  /**
1101
422
  * Set Endless Mode configuration
1102
423
  */
1103
424
  async setEndlessConfig(config: Partial<EndlessModeConfig>): Promise<void> {
1104
- const current = await this.getEndlessConfig();
1105
- const merged = { ...current, ...config };
1106
- await this.sqliteStore.setEndlessConfig('config', merged);
425
+ return this.endlessMemoryServices.setEndlessConfig(config);
1107
426
  }
1108
427
 
1109
428
  /**
1110
429
  * Set memory mode (session or endless)
1111
430
  */
1112
431
  async setMode(mode: MemoryMode): Promise<void> {
1113
- await this.initialize();
1114
-
1115
- if (mode === this.endlessMode) return;
1116
-
1117
- this.endlessMode = mode;
1118
- await this.sqliteStore.setEndlessConfig('mode', mode);
1119
-
1120
- if (mode === 'endless') {
1121
- await this.initializeEndlessMode();
1122
- } else {
1123
- // Stop endless mode components
1124
- if (this.consolidationWorker) {
1125
- this.consolidationWorker.stop();
1126
- this.consolidationWorker = null;
1127
- }
1128
- this.workingSetStore = null;
1129
- this.consolidatedStore = null;
1130
- this.continuityManager = null;
1131
- }
432
+ return this.endlessMemoryServices.setMode(mode);
1132
433
  }
1133
434
 
1134
435
  /**
1135
436
  * Get current memory mode
1136
437
  */
1137
438
  getMode(): MemoryMode {
1138
- return this.endlessMode;
439
+ return this.endlessMemoryServices.getMode();
1139
440
  }
1140
441
 
1141
442
  /**
1142
443
  * Check if endless mode is active
1143
444
  */
1144
445
  isEndlessModeActive(): boolean {
1145
- return this.endlessMode === 'endless';
446
+ return this.endlessMemoryServices.isEndlessModeActive();
1146
447
  }
1147
448
 
1148
449
  /**
1149
450
  * Add event to Working Set (Endless Mode)
1150
451
  */
1151
452
  async addToWorkingSet(eventId: string, relevanceScore?: number): Promise<void> {
1152
- if (!this.workingSetStore) return;
1153
- await this.workingSetStore.add(eventId, relevanceScore);
453
+ return this.endlessMemoryServices.addToWorkingSet(eventId, relevanceScore);
1154
454
  }
1155
455
 
1156
456
  /**
1157
457
  * Get the current Working Set
1158
458
  */
1159
459
  async getWorkingSet(): Promise<WorkingSet | null> {
1160
- if (!this.workingSetStore) return null;
1161
- return this.workingSetStore.get();
460
+ return this.endlessMemoryServices.getWorkingSet();
1162
461
  }
1163
462
 
1164
463
  /**
@@ -1168,138 +467,50 @@ export class MemoryService {
1168
467
  query: string,
1169
468
  options?: { topK?: number }
1170
469
  ): Promise<ConsolidatedMemory[]> {
1171
- if (!this.consolidatedStore) return [];
1172
- return this.consolidatedStore.search(query, options);
470
+ return this.endlessMemoryServices.searchConsolidated(query, options);
1173
471
  }
1174
472
 
1175
473
  /**
1176
474
  * Get all consolidated memories
1177
475
  */
1178
476
  async getConsolidatedMemories(limit?: number): Promise<ConsolidatedMemory[]> {
1179
- if (!this.consolidatedStore) return [];
1180
- return this.consolidatedStore.getAll({ limit });
1181
- }
1182
-
1183
- /**
1184
- * Extract topic keywords from event content (markdown headings and key terms)
1185
- */
1186
- private extractTopicsFromContent(content: string): string[] {
1187
- const topics: Set<string> = new Set();
1188
-
1189
- // Extract markdown headings (## heading)
1190
- const headings = content.match(/^#{1,3}\s+(.+)$/gm);
1191
- if (headings) {
1192
- for (const h of headings.slice(0, 5)) {
1193
- const text = h.replace(/^#+\s+/, '').replace(/[*_`#]/g, '').trim();
1194
- if (text.length > 2 && text.length < 50) {
1195
- topics.add(text);
1196
- }
1197
- }
1198
- }
1199
-
1200
- // Extract bold terms (**term**)
1201
- const boldTerms = content.match(/\*\*([^*]+)\*\*/g);
1202
- if (boldTerms) {
1203
- for (const b of boldTerms.slice(0, 5)) {
1204
- const text = b.replace(/\*\*/g, '').trim();
1205
- if (text.length > 2 && text.length < 30) {
1206
- topics.add(text);
1207
- }
1208
- }
1209
- }
1210
-
1211
- return Array.from(topics).slice(0, 5);
477
+ return this.endlessMemoryServices.getConsolidatedMemories(limit);
1212
478
  }
1213
479
 
1214
480
  /**
1215
481
  * Increment access count for memories that were used in prompts
1216
482
  */
1217
483
  async incrementMemoryAccess(eventIds: string[]): Promise<void> {
1218
- if (eventIds.length === 0) return;
1219
-
1220
- // Use SQLite event store if available
1221
- if (this.sqliteStore) {
1222
- await this.sqliteStore.incrementAccessCount(eventIds);
1223
- } else if (this.eventStore) {
1224
- // Fallback to regular event store (which has a stub implementation)
1225
- await this.eventStore.incrementAccessCount(eventIds);
1226
- }
484
+ return this.retrievalOrchestrator.incrementMemoryAccess(eventIds);
1227
485
  }
1228
486
 
1229
487
  /**
1230
488
  * Get most accessed memories from events
1231
489
  */
1232
- async getMostAccessedMemories(limit: number = 10): Promise<any[]> {
1233
- console.log('[getMostAccessedMemories] sqliteStore available:', !!this.sqliteStore);
1234
-
1235
- // Try to get from SQLite event store if available
1236
- if (this.sqliteStore) {
1237
- const events = await this.sqliteStore.getMostAccessed(limit);
1238
- console.log('[getMostAccessedMemories] Got events from SQLite:', events.length);
1239
- return events.map(event => ({
1240
- memoryId: event.id,
1241
- summary: event.content.substring(0, 200) + (event.content.length > 200 ? '...' : ''),
1242
- topics: this.extractTopicsFromContent(event.content),
1243
- accessCount: (event as any).access_count || 0,
1244
- lastAccessed: (event as any).last_accessed_at || null,
1245
- confidence: 1.0,
1246
- createdAt: event.timestamp
1247
- }));
1248
- }
1249
-
1250
- // Fallback to consolidated store if available
1251
- if (this.consolidatedStore) {
1252
- const consolidated = await this.consolidatedStore.getMostAccessed(limit);
1253
- return consolidated.map(m => ({
1254
- memoryId: m.memoryId,
1255
- summary: m.summary,
1256
- topics: m.topics,
1257
- accessCount: m.accessCount,
1258
- lastAccessed: m.accessedAt,
1259
- confidence: m.confidence,
1260
- createdAt: m.createdAt
1261
- }));
1262
- }
1263
-
1264
- return [];
490
+ async getMostAccessedMemories(limit: number = 10): Promise<AccessedMemory[]> {
491
+ return this.retrievalAnalyticsService.getMostAccessedMemories(limit);
1265
492
  }
1266
493
 
1267
494
  /**
1268
495
  * Record a memory retrieval for helpfulness tracking
1269
496
  */
1270
497
  async recordRetrieval(eventId: string, sessionId: string, score: number, query: string): Promise<void> {
1271
- await this.initialize();
1272
- await this.sqliteStore.recordRetrieval(eventId, sessionId, score, query);
498
+ return this.retrievalOrchestrator.recordRetrieval(eventId, sessionId, score, query);
1273
499
  }
1274
500
 
1275
501
  /**
1276
502
  * Record a query-level retrieval trace (used by user-prompt-submit hook).
1277
503
  * Feeds the retrieval_traces table that powers dashboard stats.
1278
504
  */
1279
- async recordQueryTrace(input: {
1280
- sessionId: string;
1281
- queryText: string;
1282
- strategy: string;
1283
- candidateEventIds: string[];
1284
- selectedEventIds: string[];
1285
- confidence: string;
1286
- }): Promise<void> {
1287
- await this.initialize();
1288
- await this.sqliteStore.recordRetrievalTrace({
1289
- ...input,
1290
- projectHash: this.projectHash || undefined,
1291
- candidateDetails: [],
1292
- selectedDetails: [],
1293
- fallbackTrace: [],
1294
- });
505
+ async recordQueryTrace(input: RecordQueryTraceInput): Promise<void> {
506
+ return this.retrievalOrchestrator.recordQueryTrace(input);
1295
507
  }
1296
508
 
1297
509
  /**
1298
510
  * Evaluate helpfulness of retrievals in a session (called at session end)
1299
511
  */
1300
512
  async evaluateSessionHelpfulness(sessionId: string): Promise<void> {
1301
- await this.initialize();
1302
- await this.sqliteStore.evaluateSessionHelpfulness(sessionId);
513
+ await this.retrievalAnalyticsService.evaluateSessionHelpfulness(sessionId);
1303
514
  }
1304
515
 
1305
516
  /**
@@ -1307,52 +518,28 @@ export class MemoryService {
1307
518
  * Call on first turn of a new session to catch missed evaluations.
1308
519
  */
1309
520
  async evaluatePendingSessions(currentSessionId: string): Promise<void> {
1310
- await this.initialize();
1311
- const sessions = await this.sqliteStore.getUnevaluatedSessions(currentSessionId, 5);
1312
- for (const sid of sessions) {
1313
- try {
1314
- await this.sqliteStore.evaluateSessionHelpfulness(sid);
1315
- } catch {
1316
- // non-critical, skip failed
1317
- }
1318
- }
521
+ await this.retrievalAnalyticsService.evaluatePendingSessions(currentSessionId);
1319
522
  }
1320
523
 
1321
524
  /**
1322
525
  * Get most helpful memories ranked by helpfulness score
1323
526
  */
1324
- async getHelpfulMemories(limit: number = 10): Promise<Array<{
1325
- eventId: string;
1326
- summary: string;
1327
- helpfulnessScore: number;
1328
- accessCount: number;
1329
- evaluationCount: number;
1330
- }>> {
1331
- await this.initialize();
1332
- return this.sqliteStore.getHelpfulMemories(limit);
527
+ async getHelpfulMemories(limit: number = 10): Promise<HelpfulMemory[]> {
528
+ return this.retrievalAnalyticsService.getHelpfulMemories(limit);
1333
529
  }
1334
530
 
1335
531
  /**
1336
532
  * Get helpfulness statistics for dashboard
1337
533
  */
1338
- async getHelpfulnessStats(): Promise<{
1339
- avgScore: number;
1340
- totalEvaluated: number;
1341
- totalRetrievals: number;
1342
- helpful: number;
1343
- neutral: number;
1344
- unhelpful: number;
1345
- }> {
1346
- await this.initialize();
1347
- return this.sqliteStore.getHelpfulnessStats();
534
+ async getHelpfulnessStats(): Promise<HelpfulnessStats> {
535
+ return this.retrievalAnalyticsService.getHelpfulnessStats();
1348
536
  }
1349
537
 
1350
538
  /**
1351
539
  * Mark a consolidated memory as accessed
1352
540
  */
1353
541
  async markMemoryAccessed(memoryId: string): Promise<void> {
1354
- if (!this.consolidatedStore) return;
1355
- await this.consolidatedStore.markAccessed(memoryId);
542
+ return this.endlessMemoryServices.markMemoryAccessed(memoryId);
1356
543
  }
1357
544
 
1358
545
  /**
@@ -1362,63 +549,28 @@ export class MemoryService {
1362
549
  content: string,
1363
550
  metadata?: { files?: string[]; entities?: string[] }
1364
551
  ): Promise<ContinuityScore | null> {
1365
- if (!this.continuityManager) return null;
1366
-
1367
- const snapshot = this.continuityManager.createSnapshot(
1368
- crypto.randomUUID(),
1369
- content,
1370
- metadata
1371
- );
1372
-
1373
- return this.continuityManager.calculateScore(snapshot);
552
+ return this.endlessMemoryServices.calculateContinuity(content, metadata);
1374
553
  }
1375
554
 
1376
555
  /**
1377
556
  * Record activity (for consolidation idle trigger)
1378
557
  */
1379
558
  recordActivity(): void {
1380
- if (this.consolidationWorker) {
1381
- this.consolidationWorker.recordActivity();
1382
- }
559
+ this.endlessMemoryServices.recordActivity();
1383
560
  }
1384
561
 
1385
562
  /**
1386
563
  * Force a consolidation run
1387
564
  */
1388
565
  async forceConsolidation(): Promise<number> {
1389
- if (!this.consolidationWorker) return 0;
1390
- return this.consolidationWorker.forceRun();
566
+ return this.endlessMemoryServices.forceConsolidation();
1391
567
  }
1392
568
 
1393
569
  /**
1394
570
  * Get Endless Mode status
1395
571
  */
1396
572
  async getEndlessModeStatus(): Promise<EndlessModeStatus> {
1397
- await this.initialize();
1398
-
1399
- let workingSetSize = 0;
1400
- let continuityScore = 0.5;
1401
- let consolidatedCount = 0;
1402
- let lastConsolidation: Date | null = null;
1403
-
1404
- if (this.workingSetStore) {
1405
- workingSetSize = await this.workingSetStore.count();
1406
- const workingSet = await this.workingSetStore.get();
1407
- continuityScore = workingSet.continuityScore;
1408
- }
1409
-
1410
- if (this.consolidatedStore) {
1411
- consolidatedCount = await this.consolidatedStore.count();
1412
- lastConsolidation = await this.consolidatedStore.getLastConsolidationTime();
1413
- }
1414
-
1415
- return {
1416
- mode: this.endlessMode,
1417
- workingSetSize,
1418
- continuityScore,
1419
- consolidatedCount,
1420
- lastConsolidation
1421
- };
573
+ return this.endlessMemoryServices.getEndlessModeStatus();
1422
574
  }
1423
575
 
1424
576
  // ============================================================
@@ -1437,207 +589,70 @@ export class MemoryService {
1437
589
  toolCount: number;
1438
590
  hasResponse: boolean;
1439
591
  }>> {
1440
- await this.initialize();
1441
- return this.sqliteStore.getSessionTurns(sessionId, options);
592
+ return this.queryService.getSessionTurns(sessionId, options);
1442
593
  }
1443
594
 
1444
595
  /**
1445
596
  * Get all events for a specific turn
1446
597
  */
1447
598
  async getEventsByTurn(turnId: string): Promise<MemoryEvent[]> {
1448
- await this.initialize();
1449
- return this.sqliteStore.getEventsByTurn(turnId);
599
+ return this.queryService.getEventsByTurn(turnId);
1450
600
  }
1451
601
 
1452
602
  /**
1453
603
  * Count total turns for a session
1454
604
  */
1455
605
  async countSessionTurns(sessionId: string): Promise<number> {
1456
- await this.initialize();
1457
- return this.sqliteStore.countSessionTurns(sessionId);
606
+ return this.queryService.countSessionTurns(sessionId);
1458
607
  }
1459
608
 
1460
609
  /**
1461
610
  * Backfill turn_ids from metadata for events stored before the migration
1462
611
  */
1463
612
  async backfillTurnIds(): Promise<number> {
1464
- await this.initialize();
1465
- return this.sqliteStore.backfillTurnIds();
613
+ return this.queryService.backfillTurnIds();
1466
614
  }
1467
615
 
1468
616
  /**
1469
617
  * Delete all events for a session (for force reimport)
1470
618
  */
1471
619
  async deleteSessionEvents(sessionId: string): Promise<number> {
1472
- await this.initialize();
1473
- return this.sqliteStore.deleteSessionEvents(sessionId);
620
+ return this.queryService.deleteSessionEvents(sessionId);
1474
621
  }
1475
622
 
1476
623
  /**
1477
624
  * Format Endless Mode context for Claude
1478
625
  */
1479
626
  async formatEndlessContext(query: string): Promise<string> {
1480
- if (!this.isEndlessModeActive()) {
1481
- return '';
1482
- }
1483
-
1484
- const workingSet = await this.getWorkingSet();
1485
- const consolidated = await this.searchConsolidated(query, { topK: 3 });
1486
- const continuity = await this.calculateContinuity(query);
1487
-
1488
- const parts: string[] = [];
1489
-
1490
- // Continuity status
1491
- if (continuity) {
1492
- const statusEmoji = continuity.transitionType === 'seamless' ? '🔗' :
1493
- continuity.transitionType === 'topic_shift' ? '↪️' : '🆕';
1494
- parts.push(`${statusEmoji} Context: ${continuity.transitionType} (score: ${continuity.score.toFixed(2)})`);
1495
- }
1496
-
1497
- // Working set summary
1498
- if (workingSet && workingSet.recentEvents.length > 0) {
1499
- parts.push('\n## Recent Context (Working Set)');
1500
- const recent = workingSet.recentEvents.slice(0, 5);
1501
- for (const event of recent) {
1502
- const preview = event.content.slice(0, 80) + (event.content.length > 80 ? '...' : '');
1503
- const time = event.timestamp.toLocaleTimeString();
1504
- parts.push(`- ${time} [${event.eventType}] ${preview}`);
1505
- }
1506
- }
1507
-
1508
- // Consolidated memories
1509
- if (consolidated.length > 0) {
1510
- parts.push('\n## Related Knowledge (Consolidated)');
1511
- for (const memory of consolidated) {
1512
- parts.push(`- ${memory.topics.slice(0, 3).join(', ')}: ${memory.summary.slice(0, 100)}...`);
1513
- }
1514
- }
1515
-
1516
- return parts.join('\n');
627
+ return this.endlessMemoryServices.formatEndlessContext(query);
1517
628
  }
1518
629
 
1519
630
  /**
1520
631
  * Force a graduation evaluation run
1521
632
  */
1522
633
  async forceGraduation(): Promise<GraduationRunResult> {
1523
- if (!this.graduationWorker) {
1524
- return { evaluated: 0, graduated: 0, byLevel: {} };
1525
- }
1526
- return this.graduationWorker.forceRun();
634
+ return this.runtimeService.forceGraduation();
1527
635
  }
1528
636
 
1529
637
  /**
1530
638
  * Record access to a memory event (for graduation scoring)
1531
639
  */
1532
640
  recordMemoryAccess(eventId: string, sessionId: string, confidence: number = 1.0): void {
1533
- this.graduation.recordAccess(eventId, sessionId, confidence);
641
+ this.runtimeService.recordMemoryAccess(eventId, sessionId, confidence);
1534
642
  }
1535
643
 
1536
644
  getEmbeddingModelName(): string {
1537
- return this.embedder.getModelName();
645
+ return this.embeddingMaintenanceService.getEmbeddingModelName();
1538
646
  }
1539
647
 
1540
648
  /**
1541
649
  * Ensure embedding model metadata is in sync and optionally migrate vectors.
1542
650
  * Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
1543
651
  */
1544
- async ensureEmbeddingModelForImport(options?: { autoMigrate?: boolean }): Promise<{
1545
- changed: boolean;
1546
- previousModel: string | null;
1547
- currentModel: string;
1548
- enqueued: number;
1549
- reason?: string;
1550
- }> {
1551
- await this.initialize();
1552
-
1553
- const currentModel = this.getEmbeddingModelName();
1554
- const metaPath = path.join(this.storagePath, 'embedding-meta.json');
1555
-
1556
- let previousModel: string | null = null;
1557
- try {
1558
- if (fs.existsSync(metaPath)) {
1559
- const parsed = JSON.parse(fs.readFileSync(metaPath, 'utf-8')) as { model?: string };
1560
- previousModel = parsed?.model || null;
1561
- }
1562
- } catch {
1563
- previousModel = null;
1564
- }
1565
-
1566
- const stats = await this.getStats();
1567
- const hasExistingVectors = (stats.vectorCount || 0) > 0;
1568
-
1569
- // First-time metadata write (no migration needed unless legacy vectors exist)
1570
- if (!previousModel && !hasExistingVectors) {
1571
- fs.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: new Date().toISOString() }, null, 2));
1572
- return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: 'initialized-meta' };
1573
- }
1574
-
1575
- const modelChanged = previousModel !== currentModel;
1576
- const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
1577
-
1578
- if (!modelChanged && !legacyUnknownButVectorsExist) {
1579
- return { changed: false, previousModel, currentModel, enqueued: 0 };
1580
- }
1581
-
1582
- if (options?.autoMigrate === false) {
1583
- return {
1584
- changed: true,
1585
- previousModel,
1586
- currentModel,
1587
- enqueued: 0,
1588
- reason: legacyUnknownButVectorsExist ? 'legacy-vectors-without-meta' : 'model-mismatch'
1589
- };
1590
- }
1591
-
1592
- // Pause background vector processing while preparing migration
1593
- const wasRunning = this.vectorWorker?.isRunning() || false;
1594
- if (wasRunning) this.vectorWorker?.stop();
1595
-
1596
- // Reset vector and outbox state
1597
- await this.vectorStore.clearAll();
1598
- await this.sqliteStore.clearEmbeddingOutbox();
1599
-
1600
- // Re-enqueue all events for new embeddings
1601
- const pageSize = 1000;
1602
- let offset = 0;
1603
- let enqueued = 0;
1604
-
1605
- while (true) {
1606
- const page = await this.sqliteStore.getEventsPage(pageSize, offset);
1607
- if (page.length === 0) break;
1608
-
1609
- for (const event of page) {
1610
- await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
1611
- enqueued += 1;
1612
- }
1613
-
1614
- offset += page.length;
1615
- if (page.length < pageSize) break;
1616
- }
1617
-
1618
- fs.writeFileSync(
1619
- metaPath,
1620
- JSON.stringify(
1621
- {
1622
- model: currentModel,
1623
- previousModel,
1624
- migratedAt: new Date().toISOString(),
1625
- enqueued
1626
- },
1627
- null,
1628
- 2
1629
- )
1630
- );
1631
-
1632
- if (wasRunning) this.vectorWorker?.start();
1633
-
1634
- return {
1635
- changed: true,
1636
- previousModel,
1637
- currentModel,
1638
- enqueued,
1639
- reason: legacyUnknownButVectorsExist ? 'legacy-vectors-without-meta' : 'model-mismatch'
1640
- };
652
+ async ensureEmbeddingModelForImport(
653
+ options?: EmbeddingModelMaintenanceOptions
654
+ ): Promise<EmbeddingModelMaintenanceResult> {
655
+ return this.embeddingMaintenanceService.ensureEmbeddingModelForImport(options);
1641
656
  }
1642
657
 
1643
658
  /**
@@ -1651,152 +666,23 @@ export class MemoryService {
1651
666
  * Shutdown service
1652
667
  */
1653
668
  async shutdown(): Promise<void> {
1654
- // Stop graduation worker
1655
- if (this.graduationWorker) {
1656
- this.graduationWorker.stop();
1657
- }
1658
-
1659
- // Stop endless mode components
1660
- if (this.consolidationWorker) {
1661
- this.consolidationWorker.stop();
1662
- }
1663
-
1664
- if (this.vectorWorker) {
1665
- this.vectorWorker.stop();
1666
- }
1667
-
1668
- // Close shared store
1669
- if (this.sharedEventStore) {
1670
- await this.sharedEventStore.close();
1671
- }
1672
-
1673
- // Close primary store (SQLite)
1674
- await this.sqliteStore.close();
1675
-
1676
- }
1677
-
1678
- /**
1679
- * Expand ~ to home directory
1680
- */
1681
- private expandPath(p: string): string {
1682
- if (p.startsWith('~')) {
1683
- return path.join(os.homedir(), p.slice(1));
1684
- }
1685
- return p;
1686
- }
1687
- }
1688
-
1689
- // ============================================================
1690
- // Service Instance Management
1691
- // ============================================================
1692
-
1693
- // Instance cache: Map from project hash (or '__global__') to MemoryService
1694
- const serviceCache = new Map<string, MemoryService>();
1695
- const GLOBAL_KEY = '__global__';
1696
- const GLOBAL_READONLY_KEY = '__global_readonly__';
1697
-
1698
- /**
1699
- * Get the global memory service (backward compatibility)
1700
- * Use this for operations not tied to a specific project
1701
- * Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
1702
- */
1703
- export function getDefaultMemoryService(): MemoryService {
1704
- if (!serviceCache.has(GLOBAL_KEY)) {
1705
- serviceCache.set(GLOBAL_KEY, new MemoryService({
1706
- storagePath: '~/.claude-code/memory',
1707
- analyticsEnabled: false, // Hooks don't need DuckDB
1708
- sharedStoreConfig: { enabled: false } // Shared store uses DuckDB too
1709
- }));
1710
- }
1711
- return serviceCache.get(GLOBAL_KEY)!;
1712
- }
1713
-
1714
- /**
1715
- * Get a read-only global memory service
1716
- * Use this for web server/dashboard that only needs to read data
1717
- * Creates a fresh connection each time to avoid blocking the main writer process
1718
- * Uses SQLite (WAL mode) which supports concurrent readers
1719
- */
1720
- export function getReadOnlyMemoryService(): MemoryService {
1721
- // Don't cache - create fresh instance each time to avoid holding locks
1722
- // The connection will be closed when the request completes
1723
- // Uses SQLite which supports concurrent readers via WAL mode
1724
- return new MemoryService({
1725
- storagePath: '~/.claude-code/memory',
1726
- readOnly: true,
1727
- analyticsEnabled: false, // Use SQLite for reads (WAL supports concurrent readers)
1728
- sharedStoreConfig: { enabled: false } // Skip shared store for now
1729
- });
1730
- }
1731
-
1732
- /**
1733
- * Get memory service for a specific project path
1734
- * Creates isolated storage at ~/.claude-code/memory/projects/{hash}/
1735
- * Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
1736
- */
1737
- export function getMemoryServiceForProject(
1738
- projectPath: string,
1739
- sharedStoreConfig?: SharedStoreConfig
1740
- ): MemoryService {
1741
- const hash = hashProjectPath(projectPath);
1742
-
1743
- if (!serviceCache.has(hash)) {
1744
- const storagePath = getProjectStoragePath(projectPath);
1745
- serviceCache.set(hash, new MemoryService({
1746
- storagePath,
1747
- projectHash: hash,
1748
- projectPath,
1749
- // Override shared store config - hooks don't need DuckDB
1750
- sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
1751
- analyticsEnabled: false // Hooks don't need DuckDB
1752
- }));
1753
- }
1754
-
1755
- return serviceCache.get(hash)!;
1756
- }
1757
-
1758
- /**
1759
- * Get memory service for a session by looking up its project
1760
- * Falls back to global storage if session not found in registry
1761
- */
1762
- export function getMemoryServiceForSession(sessionId: string): MemoryService {
1763
- const projectInfo = getSessionProject(sessionId);
1764
-
1765
- if (projectInfo) {
1766
- return getMemoryServiceForProject(projectInfo.projectPath);
669
+ await this.runtimeService.shutdown();
1767
670
  }
1768
-
1769
- // Fallback to global storage for unknown sessions (backward compat)
1770
- return getDefaultMemoryService();
1771
671
  }
1772
672
 
1773
- /**
1774
- * Get a lightweight memory service for hooks
1775
- * Only initializes SQLite - no embedder, no vector store, no workers
1776
- * This is FAST (<100ms) compared to full initialization (3-5s)
1777
- */
1778
- export function getLightweightMemoryService(sessionId: string): MemoryService {
1779
- const projectInfo = getSessionProject(sessionId);
1780
- const key = projectInfo ? `lightweight_${projectInfo.projectHash}` : 'lightweight_global';
1781
-
1782
- if (!serviceCache.has(key)) {
1783
- const storagePath = projectInfo
1784
- ? getProjectStoragePath(projectInfo.projectPath)
1785
- : path.join(os.homedir(), '.claude-code', 'memory');
1786
-
1787
- serviceCache.set(key, new MemoryService({
1788
- storagePath,
1789
- projectHash: projectInfo?.projectHash,
1790
- projectPath: projectInfo?.projectPath,
1791
- lightweightMode: true, // Skip embedder/vector/workers
1792
- analyticsEnabled: false,
1793
- sharedStoreConfig: { enabled: false }
1794
- }));
1795
- }
1796
-
1797
- return serviceCache.get(key)!;
1798
- }
1799
-
1800
- export function createMemoryService(config: MemoryServiceConfig): MemoryService {
1801
- return new MemoryService(config);
1802
- }
673
+ const defaultRegistry = createMemoryServiceRegistry<MemoryService>({
674
+ createService: (config) => new MemoryService(config),
675
+ hashProjectPath: defaultHashProjectPath,
676
+ getProjectStoragePath: defaultGetProjectStoragePath,
677
+ getSessionProject: defaultGetSessionProject,
678
+ homedir: os.homedir,
679
+ disabledSharedStoreConfig: DISABLED_SHARED_STORE_CONFIG
680
+ });
681
+
682
+ export const getDefaultMemoryService = defaultRegistry.getDefaultMemoryService;
683
+ export const getReadOnlyMemoryService = defaultRegistry.getReadOnlyMemoryService;
684
+ export const getMemoryServiceForProject = defaultRegistry.getMemoryServiceForProject;
685
+ export const getMemoryServiceForSession = defaultRegistry.getMemoryServiceForSession;
686
+ export const getLightweightMemoryService = defaultRegistry.getLightweightMemoryService;
687
+ export const getLightweightMemoryServiceForProject = defaultRegistry.getLightweightMemoryServiceForProject;
688
+ export const createMemoryService = defaultRegistry.createMemoryService;