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
@@ -1,344 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * User Prompt Submit Hook
4
- * Called when user submits a prompt - retrieves relevant memories.
3
+ * Compatibility entrypoint for the Claude user-prompt-submit hook.
5
4
  *
6
- * Retrieval mode (CLAUDE_MEMORY_RETRIEVAL_MODE):
7
- * - keyword (default-fast): SQLite FTS5 only, no ML model (~10ms)
8
- * - semantic: vector search via long-running semantic daemon (~15-20ms warm)
9
- * - hybrid: semantic first, keyword fallback (default)
10
- *
11
- * The semantic daemon keeps the embedding model in memory across hook invocations,
12
- * avoiding per-request model initialization (~730ms cold start).
13
- *
14
- * Turn Grouping: Generates a turn_id and persists it to a state file
15
- * so PostToolUse and Stop hooks can associate their events with this turn.
5
+ * Implementation lives in the Claude adapter layer so core stays platform-agnostic.
16
6
  */
17
-
18
- import { randomUUID } from 'crypto';
19
- import * as fs from 'fs';
20
- import * as path from 'path';
21
- import * as os from 'os';
22
- import { getLightweightMemoryService } from '../services/memory-service.js';
23
- import { writeTurnState, readLastAssistantSnippet } from '../core/turn-state.js';
24
- import { retrieveSemanticMemories } from './semantic-daemon-client.js';
25
- import type { UserPromptSubmitInput, UserPromptSubmitOutput } from '../core/types.js';
26
-
27
- // Configuration
28
- const MAX_MEMORIES = parseInt(process.env.CLAUDE_MEMORY_MAX_COUNT || '5');
29
- // Tuned default for noise/recall balance on shopping_assistant-like corpus
30
- const BASE_MIN_SCORE = parseFloat(process.env.CLAUDE_MEMORY_MIN_SCORE || '0.4');
31
- const FALLBACK_MIN_SCORE = parseFloat(process.env.CLAUDE_MEMORY_FALLBACK_MIN_SCORE || '0.3');
32
- const ENABLE_SEARCH = process.env.CLAUDE_MEMORY_SEARCH !== 'false';
33
- const RETRIEVAL_MODE = (process.env.CLAUDE_MEMORY_RETRIEVAL_MODE || 'hybrid') as 'keyword' | 'semantic' | 'hybrid';
34
- const SEMANTIC_TIMEOUT_MS = parseInt(process.env.CLAUDE_MEMORY_SEMANTIC_TIMEOUT_MS || '2000');
35
- const ADHERENCE_INTERVAL_TURNS = parseInt(process.env.CLAUDE_MEMORY_ADHERENCE_INTERVAL_TURNS || '3');
36
-
37
- const ADHERENCE_STATE_DIR = path.join(os.homedir(), '.claude-code', 'memory');
38
-
39
- interface AdherenceState {
40
- sessionId: string;
41
- turnCount: number;
42
- lastCheckedTurn: number;
43
- lastPrompt: string;
44
- lastReason?: string;
45
- updatedAt: string;
46
- }
47
-
48
- /**
49
- * Determine if a prompt is worth storing as a memory.
50
- * Filters slash commands, very short inputs, and trivial patterns.
51
- */
52
- function shouldStorePrompt(prompt: string): boolean {
53
- const trimmed = prompt.trim();
54
- if (trimmed.startsWith('/')) return false;
55
- if (trimmed.length < 15) return false;
56
- if (!/[a-zA-Z가-힣]{2,}/.test(trimmed)) return false;
57
- return true;
58
- }
59
-
60
-
61
- function getDynamicMinScore(prompt: string): number {
62
- const len = prompt.trim().length;
63
- if (len <= 20) return Math.min(0.55, BASE_MIN_SCORE + 0.1); // short query → stricter
64
- if (len >= 80) return Math.max(0.3, BASE_MIN_SCORE - 0.05); // long query → slightly looser
65
- return BASE_MIN_SCORE;
66
- }
67
-
68
- function formatMemoryContext(items: Array<{ type: string; content: string }>): string {
69
- if (items.length === 0) return '';
70
- const lines = items.map((m) => {
71
- const preview = m.content.length > 300 ? m.content.substring(0, 300) + '...' : m.content;
72
- return `- [${m.type}] ${preview}`;
73
- });
74
- return `💡 **Related memories found:**\n\n${lines.join('\n\n')}`;
75
- }
76
-
77
- function getAdherenceStatePath(sessionId: string): string {
78
- return path.join(ADHERENCE_STATE_DIR, `.adherence-state-${sessionId}.json`);
79
- }
80
-
81
- function readAdherenceState(sessionId: string): AdherenceState {
82
- try {
83
- const filePath = getAdherenceStatePath(sessionId);
84
- if (!fs.existsSync(filePath)) {
85
- return {
86
- sessionId,
87
- turnCount: 0,
88
- lastCheckedTurn: 0,
89
- lastPrompt: '',
90
- lastReason: 'init',
91
- updatedAt: new Date().toISOString()
92
- };
93
- }
94
-
95
- const data = fs.readFileSync(filePath, 'utf8');
96
- const parsed = JSON.parse(data) as AdherenceState;
97
- if (parsed.sessionId !== sessionId) throw new Error('session mismatch');
98
- return parsed;
99
- } catch {
100
- return {
101
- sessionId,
102
- turnCount: 0,
103
- lastCheckedTurn: 0,
104
- lastPrompt: '',
105
- lastReason: 'init',
106
- updatedAt: new Date().toISOString()
107
- };
108
- }
109
- }
110
-
111
- function writeAdherenceState(state: AdherenceState): void {
112
- try {
113
- if (!fs.existsSync(ADHERENCE_STATE_DIR)) {
114
- fs.mkdirSync(ADHERENCE_STATE_DIR, { recursive: true });
115
- }
116
- const filePath = getAdherenceStatePath(state.sessionId);
117
- const tempPath = filePath + '.tmp';
118
- fs.writeFileSync(tempPath, JSON.stringify(state));
119
- fs.renameSync(tempPath, filePath);
120
- } catch {
121
- // non-critical
122
- }
123
- }
124
-
125
- function hasWriteIntent(prompt: string): boolean {
126
- return /(fix|refactor|implement|change|modify|edit|update|rewrite|patch|create|add|remove|delete|버그|수정|리팩터|구현|추가|삭제|개선)/i.test(prompt);
127
- }
128
-
129
- function tokenize(text: string): string[] {
130
- const stopwords = new Set(['the', 'and', 'for', 'with', 'that', 'this', 'from', 'have', 'what', 'when', 'where', 'how', 'why', '그리고', '그리고요', '이거', '그거', '해주세요', '해줘', '좀', '에서', '으로', '하는', '해']);
131
- return text
132
- .toLowerCase()
133
- .replace(/[^a-z0-9가-힣\s]/g, ' ')
134
- .split(/\s+/)
135
- .filter((w) => w.length >= 2 && !stopwords.has(w));
136
- }
137
-
138
- function isTopicShift(currentPrompt: string, lastPrompt: string): boolean {
139
- if (!lastPrompt || lastPrompt.length < 10) return false;
140
- const a = new Set(tokenize(currentPrompt));
141
- const b = new Set(tokenize(lastPrompt));
142
- if (a.size === 0 || b.size === 0) return false;
143
-
144
- let intersection = 0;
145
- for (const token of a) {
146
- if (b.has(token)) intersection++;
147
- }
148
- const union = a.size + b.size - intersection;
149
- const similarity = union > 0 ? intersection / union : 0;
150
- return similarity < 0.2;
151
- }
152
-
153
- function shouldRunAdherenceCheck(turnCount: number, prompt: string, state: AdherenceState): { run: boolean; reason: string } {
154
- if (turnCount === 1) return { run: true, reason: 'first-turn' };
155
- if (hasWriteIntent(prompt)) return { run: true, reason: 'write-intent' };
156
- if (isTopicShift(prompt, state.lastPrompt)) return { run: true, reason: 'topic-shift' };
157
- if (turnCount - state.lastCheckedTurn >= ADHERENCE_INTERVAL_TURNS) return { run: true, reason: 'interval' };
158
- return { run: false, reason: 'skip' };
159
- }
160
-
161
- function logAdherenceDecision(sessionId: string, turn: number, run: boolean, reason: string): void {
162
- if (!process.env.CLAUDE_MEMORY_DEBUG) return;
163
- const mode = run ? 'enforced' : 'skipped';
164
- console.error(`[adherence] session=${sessionId} turn=${turn} mode=${mode} reason=${reason}`);
165
- }
166
-
167
- async function main(): Promise<void> {
168
- // Read input from stdin
169
- const inputData = await readStdin();
170
- const input: UserPromptSubmitInput = JSON.parse(inputData);
171
-
172
- // Generate a new turn_id for this user prompt
173
- // This groups the prompt with subsequent tool calls and the final agent response
174
- const turnId = randomUUID();
175
-
176
- // Persist turn state so PostToolUse and Stop hooks can read it
177
- writeTurnState(input.session_id, turnId);
178
-
179
- // Use lightweight service (SQLite only, no embedder/vector - FAST!)
180
- const memoryService = getLightweightMemoryService(input.session_id);
181
-
182
- try {
183
- let context = '';
184
-
185
- const adherenceState = readAdherenceState(input.session_id);
186
- const currentTurn = adherenceState.turnCount + 1;
187
- const adherenceDecision = shouldRunAdherenceCheck(currentTurn, input.prompt, adherenceState);
188
- logAdherenceDecision(input.session_id, currentTurn, adherenceDecision.run, adherenceDecision.reason);
189
-
190
- // On first turn of a new session, backfill helpfulness for sessions
191
- // that ended without Stop hook (crash, force-close, etc.)
192
- if (currentTurn === 1) {
193
- memoryService.evaluatePendingSessions(input.session_id).catch(() => {});
194
- }
195
-
196
- // Store only non-trivial prompts (skip /commands, short inputs)
197
- if (shouldStorePrompt(input.prompt)) {
198
- await memoryService.storeUserPrompt(
199
- input.session_id,
200
- input.prompt,
201
- {
202
- turnId,
203
- adherence: {
204
- checked: adherenceDecision.run,
205
- reason: adherenceDecision.reason,
206
- turn: currentTurn
207
- }
208
- }
209
- );
210
- }
211
-
212
- // Search strategy: turn-1 always enforce adherence check,
213
- // then adaptively enforce on write-intent/topic-shift/interval
214
- const isSlashCommand = input.prompt.trimStart().startsWith('/');
215
- if (ENABLE_SEARCH && !isSlashCommand && input.prompt.length > 10 && adherenceDecision.run) {
216
- const minScore = getDynamicMinScore(input.prompt);
217
- let mergedMemories: Array<{ type: string; content: string; id?: string; score?: number }> = [];
218
-
219
- // On turn 2+, enrich the retrieval query with the previous assistant response
220
- // so short/ambiguous follow-ups ("그거 고쳐줘") resolve correctly.
221
- const lastSnippet = currentTurn > 1 ? readLastAssistantSnippet(input.session_id) : null;
222
- const retrievalQuery = lastSnippet
223
- ? `${lastSnippet}\n\n${input.prompt}`
224
- : input.prompt;
225
-
226
- const canUseSemantic = RETRIEVAL_MODE === 'semantic' || RETRIEVAL_MODE === 'hybrid';
227
- if (canUseSemantic) {
228
- try {
229
- mergedMemories = await retrieveSemanticMemories(
230
- {
231
- sessionId: input.session_id,
232
- prompt: retrievalQuery,
233
- topK: MAX_MEMORIES,
234
- minScore
235
- },
236
- SEMANTIC_TIMEOUT_MS
237
- );
238
- } catch {
239
- // Semantic retrieval is best-effort; fallback below handles the rest
240
- }
241
- }
242
-
243
- const shouldUseKeywordFallback =
244
- RETRIEVAL_MODE === 'keyword' ||
245
- RETRIEVAL_MODE === 'hybrid' ||
246
- mergedMemories.length === 0;
247
-
248
- if (shouldUseKeywordFallback && mergedMemories.length < MAX_MEMORIES) {
249
- let results = await memoryService.keywordSearch(retrievalQuery, {
250
- topK: MAX_MEMORIES,
251
- minScore
252
- });
253
-
254
- // recall rescue: if nothing found at tuned threshold, retry with fallback floor
255
- if (results.length === 0 && FALLBACK_MIN_SCORE < minScore) {
256
- results = await memoryService.keywordSearch(retrievalQuery, {
257
- topK: MAX_MEMORIES,
258
- minScore: FALLBACK_MIN_SCORE
259
- });
260
- }
261
-
262
- const existingIds = new Set(mergedMemories.map((m) => m.id).filter(Boolean));
263
- for (const r of results) {
264
- if (existingIds.has(r.event.id)) continue;
265
- mergedMemories.push({
266
- type: r.event.eventType,
267
- content: r.event.content,
268
- id: r.event.id,
269
- score: r.score
270
- });
271
- if (mergedMemories.length >= MAX_MEMORIES) break;
272
- }
273
- }
274
-
275
- if (mergedMemories.length > 0) {
276
- // Increment access count for found memories
277
- const eventIds = mergedMemories.map((m) => m.id).filter((v): v is string => Boolean(v));
278
- if (eventIds.length > 0) {
279
- await memoryService.incrementMemoryAccess(eventIds);
280
- }
281
-
282
- // Record each retrieval for helpfulness tracking
283
- for (const m of mergedMemories) {
284
- if (!m.id) continue;
285
- try {
286
- await memoryService.recordRetrieval(
287
- m.id,
288
- input.session_id,
289
- m.score ?? minScore,
290
- input.prompt
291
- );
292
- } catch { /* non-critical */ }
293
- }
294
-
295
- context = formatMemoryContext(mergedMemories);
296
- }
297
-
298
- // Record query-level trace for dashboard stats (retrieval_traces table)
299
- const allCandidateIds = mergedMemories.map((m) => m.id).filter((v): v is string => Boolean(v));
300
- try {
301
- await memoryService.recordQueryTrace({
302
- sessionId: input.session_id,
303
- queryText: retrievalQuery,
304
- strategy: RETRIEVAL_MODE,
305
- candidateEventIds: allCandidateIds,
306
- selectedEventIds: allCandidateIds,
307
- confidence: mergedMemories.length > 0 ? 'medium' : 'none'
308
- });
309
- } catch { /* non-critical */ }
310
- }
311
-
312
- writeAdherenceState({
313
- sessionId: input.session_id,
314
- turnCount: currentTurn,
315
- lastCheckedTurn: adherenceDecision.run ? currentTurn : adherenceState.lastCheckedTurn,
316
- lastPrompt: input.prompt,
317
- lastReason: adherenceDecision.reason,
318
- updatedAt: new Date().toISOString()
319
- });
320
-
321
- const output: UserPromptSubmitOutput = { context };
322
- console.log(JSON.stringify(output));
323
- } catch (error) {
324
- if (process.env.CLAUDE_MEMORY_DEBUG) {
325
- console.error('Memory hook error:', error);
326
- }
327
- console.log(JSON.stringify({ context: '' }));
328
- }
329
- }
330
-
331
- function readStdin(): Promise<string> {
332
- return new Promise((resolve) => {
333
- let data = '';
334
- process.stdin.setEncoding('utf8');
335
- process.stdin.on('data', (chunk) => {
336
- data += chunk;
337
- });
338
- process.stdin.on('end', () => {
339
- resolve(data);
340
- });
341
- });
342
- }
7
+ import { main } from '../adapters/claude/hooks/user-prompt-submit.js';
343
8
 
344
9
  main().catch(console.error);
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Public package entrypoint.
3
+ *
4
+ * Keep this file lightweight: consumers importing `claude-memory-layer` should
5
+ * receive the core/service APIs described by package.json `main` without having
6
+ * to import CLI or hook executables.
7
+ */
8
+
9
+ export * from './core/index.js';
10
+ export * from './services/memory-service.js';
11
+ export * from './services/memory-service-config.js';
12
+ export * from './services/memory-service-registry.js';
13
+ export * from './services/codex-session-history-importer.js';
@@ -1,212 +1,2 @@
1
- /**
2
- * MCP Tool Handlers
3
- * Implementation of tool calls
4
- */
5
-
6
- import { getDefaultMemoryService } from '../services/memory-service.js';
7
- import { generateCitationId } from '../core/citation-generator.js';
8
-
9
- interface ToolResult {
10
- content: Array<{ type: string; text: string }>;
11
- isError?: boolean;
12
- }
13
-
14
- export async function handleToolCall(
15
- name: string,
16
- args: Record<string, unknown>
17
- ): Promise<ToolResult> {
18
- try {
19
- const memoryService = getDefaultMemoryService();
20
- await memoryService.initialize();
21
-
22
- switch (name) {
23
- case 'mem-search':
24
- return await handleMemSearch(args);
25
-
26
- case 'mem-timeline':
27
- return await handleMemTimeline(args);
28
-
29
- case 'mem-details':
30
- return await handleMemDetails(args);
31
-
32
- case 'mem-stats':
33
- return await handleMemStats();
34
-
35
- default:
36
- return {
37
- content: [{ type: 'text', text: `Unknown tool: ${name}` }],
38
- isError: true
39
- };
40
- }
41
- } catch (error) {
42
- return {
43
- content: [{ type: 'text', text: `Error: ${(error as Error).message}` }],
44
- isError: true
45
- };
46
- }
47
- }
48
-
49
- async function handleMemSearch(args: Record<string, unknown>): Promise<ToolResult> {
50
- const query = args.query as string;
51
- const topK = Math.min((args.topK as number) || 5, 20);
52
-
53
- const memoryService = getDefaultMemoryService();
54
- const result = await memoryService.retrieveMemories(query, {
55
- topK,
56
- sessionId: args.sessionId as string
57
- });
58
-
59
- const lines: string[] = [
60
- '## Memory Search Results',
61
- '',
62
- `Found ${result.memories.length} relevant memories:`,
63
- ''
64
- ];
65
-
66
- for (let i = 0; i < result.memories.length; i++) {
67
- const m = result.memories[i];
68
- const citationId = generateCitationId(m.event.id);
69
- const date = m.event.timestamp.toISOString().split('T')[0];
70
- const preview = m.event.content.slice(0, 100) + (m.event.content.length > 100 ? '...' : '');
71
-
72
- lines.push(`### ${i + 1}. [mem:${citationId}] (score: ${m.score.toFixed(2)})`);
73
- lines.push(`**Type**: ${m.event.eventType} | **Date**: ${date}`);
74
- lines.push(`> ${preview}`);
75
- lines.push('');
76
- }
77
-
78
- lines.push('---');
79
- lines.push('*Use `mem-details` with IDs for full content.*');
80
-
81
- return {
82
- content: [{ type: 'text', text: lines.join('\n') }]
83
- };
84
- }
85
-
86
- async function handleMemTimeline(args: Record<string, unknown>): Promise<ToolResult> {
87
- const ids = args.ids as string[];
88
- const windowSize = (args.windowSize as number) || 3;
89
-
90
- const memoryService = getDefaultMemoryService();
91
- const recentEvents = await memoryService.getRecentEvents(10000);
92
-
93
- const lines: string[] = [
94
- '## Timeline Context',
95
- ''
96
- ];
97
-
98
- for (const targetId of ids) {
99
- // Find the target event
100
- const targetEvent = recentEvents.find(e =>
101
- e.id === targetId || generateCitationId(e.id) === targetId
102
- );
103
-
104
- if (!targetEvent) {
105
- lines.push(`Event ${targetId} not found.`);
106
- continue;
107
- }
108
-
109
- // Get session events
110
- const sessionEvents = recentEvents
111
- .filter(e => e.sessionId === targetEvent.sessionId)
112
- .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
113
-
114
- const eventIndex = sessionEvents.findIndex(e => e.id === targetEvent.id);
115
- const start = Math.max(0, eventIndex - windowSize);
116
- const end = Math.min(sessionEvents.length, eventIndex + windowSize + 1);
117
-
118
- lines.push(`### Session: ${targetEvent.sessionId.slice(0, 8)}`);
119
- lines.push('');
120
-
121
- for (let i = start; i < end; i++) {
122
- const e = sessionEvents[i];
123
- const isTarget = e.id === targetEvent.id;
124
- const marker = isTarget ? '**→**' : ' ';
125
- const time = e.timestamp.toLocaleTimeString();
126
- const preview = e.content.slice(0, 60) + (e.content.length > 60 ? '...' : '');
127
- const citationId = generateCitationId(e.id);
128
-
129
- lines.push(`${marker} ${time} [${citationId}] ${e.eventType}: ${preview}`);
130
- }
131
-
132
- lines.push('');
133
- }
134
-
135
- return {
136
- content: [{ type: 'text', text: lines.join('\n') }]
137
- };
138
- }
139
-
140
- async function handleMemDetails(args: Record<string, unknown>): Promise<ToolResult> {
141
- const ids = args.ids as string[];
142
-
143
- const memoryService = getDefaultMemoryService();
144
- const recentEvents = await memoryService.getRecentEvents(10000);
145
-
146
- const lines: string[] = [];
147
-
148
- for (const targetId of ids) {
149
- const event = recentEvents.find(e =>
150
- e.id === targetId || generateCitationId(e.id) === targetId
151
- );
152
-
153
- if (!event) {
154
- lines.push(`## Event ${targetId} not found.`);
155
- lines.push('');
156
- continue;
157
- }
158
-
159
- const citationId = generateCitationId(event.id);
160
- const date = event.timestamp.toISOString();
161
-
162
- lines.push(`## Memory: [mem:${citationId}]`);
163
- lines.push('');
164
- lines.push(`**Session**: ${event.sessionId}`);
165
- lines.push(`**Type**: ${event.eventType}`);
166
- lines.push(`**Date**: ${date}`);
167
- lines.push('');
168
- lines.push('**Content**:');
169
- lines.push('```');
170
- lines.push(event.content);
171
- lines.push('```');
172
- lines.push('');
173
- lines.push('---');
174
- lines.push('');
175
- }
176
-
177
- return {
178
- content: [{ type: 'text', text: lines.join('\n') }]
179
- };
180
- }
181
-
182
- async function handleMemStats(): Promise<ToolResult> {
183
- const memoryService = getDefaultMemoryService();
184
- const stats = await memoryService.getStats();
185
- const recentEvents = await memoryService.getRecentEvents(10000);
186
-
187
- const uniqueSessions = new Set(recentEvents.map(e => e.sessionId));
188
-
189
- const lines: string[] = [
190
- '## Memory Statistics',
191
- '',
192
- `- **Total Events**: ${stats.totalEvents}`,
193
- `- **Total Vectors**: ${stats.vectorCount}`,
194
- `- **Sessions**: ${uniqueSessions.size}`,
195
- '',
196
- '### Events by Type',
197
- ''
198
- ];
199
-
200
- const eventsByType = recentEvents.reduce((acc, e) => {
201
- acc[e.eventType] = (acc[e.eventType] || 0) + 1;
202
- return acc;
203
- }, {} as Record<string, number>);
204
-
205
- for (const [type, count] of Object.entries(eventsByType)) {
206
- lines.push(`- ${type}: ${count}`);
207
- }
208
-
209
- return {
210
- content: [{ type: 'text', text: lines.join('\n') }]
211
- };
212
- }
1
+ // Compatibility re-export for the MCP extension tool handlers.
2
+ export * from '../extensions/mcp/handlers.js';
package/src/mcp/index.ts CHANGED
@@ -1,47 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * MCP Server for Claude Desktop
4
- * Provides memory search tools via Model Context Protocol
5
- */
6
-
7
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
8
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
- import {
10
- ListToolsRequestSchema,
11
- CallToolRequestSchema
12
- } from '@modelcontextprotocol/sdk/types.js';
13
-
14
- import { tools } from './tools.js';
15
- import { handleToolCall } from './handlers.js';
16
-
17
- const server = new Server(
18
- {
19
- name: 'claude-memory-layer-mcp',
20
- version: '1.0.0'
21
- },
22
- {
23
- capabilities: {
24
- tools: {}
25
- }
26
- }
27
- );
28
-
29
- // Tool listing handler
30
- server.setRequestHandler(ListToolsRequestSchema, async () => {
31
- return { tools };
32
- });
33
-
34
- // Tool call handler
35
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
36
- const { name, arguments: args } = request.params;
37
- return handleToolCall(name, args || {});
38
- });
39
-
40
- // Start server
41
- async function main() {
42
- const transport = new StdioServerTransport();
43
- await server.connect(transport);
44
- console.error('claude-memory-layer MCP server started');
45
- }
46
-
47
- main().catch(console.error);
2
+ // Compatibility entrypoint. MCP is an optional extension, but existing package
3
+ // and local paths still resolve through src/mcp during the strangler migration.
4
+ import '../extensions/mcp/index.js';