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
@@ -0,0 +1,239 @@
1
+ /**
2
+ * PostToolUse Hook
3
+ * Called after each tool execution - stores tool observations
4
+ *
5
+ * Actual Claude Code input format:
6
+ * {
7
+ * session_id, tool_name, tool_input, tool_use_id,
8
+ * tool_response: { stdout?, stderr?, content?, interrupted?, isImage? },
9
+ * cwd, transcript_path, permission_mode, hook_event_name
10
+ * }
11
+ */
12
+
13
+ import { getLightweightMemoryService } from '../../../services/memory-service.js';
14
+ import { applyPrivacyFilter, maskSensitiveInput, truncateOutput } from '../../../core/privacy/index.js';
15
+ import { extractMetadata } from '../../../core/metadata-extractor.js';
16
+ import { readTurnState } from '../../../core/turn-state.js';
17
+ import type { PostToolUseInput, ToolObservationPayload, Config } from '../../../core/types.js';
18
+
19
+ // Default config
20
+ const DEFAULT_CONFIG: Config['toolObservation'] = {
21
+ enabled: true,
22
+ excludedTools: [
23
+ // Trivial meta tools
24
+ 'TodoWrite', 'TodoRead',
25
+ // Reproducible query tools (no storage value)
26
+ 'Read', 'Grep', 'Glob',
27
+ 'ToolSearch', 'WebFetch', 'WebSearch', 'NotebookRead',
28
+ // Low-value system tools
29
+ 'Skill', 'EnterPlanMode',
30
+ ],
31
+ minOutputLength: parseInt(process.env.CLAUDE_MEMORY_TOOL_MIN_OUTPUT_LEN || '100'),
32
+ maxOutputLength: 10000,
33
+ maxOutputLines: 100,
34
+ storeOnlyOnSuccess: false
35
+ };
36
+
37
+ // Tools that are always stored regardless of output length
38
+ const ALWAYS_STORE_TOOLS = new Set([
39
+ 'Write', 'Edit', 'MultiEdit', 'Agent', 'Task', 'ExitPlanMode'
40
+ ]);
41
+
42
+ // Keywords that indicate a Bash output is worth storing
43
+ const IMPORTANT_BASH_KEYWORDS = [
44
+ 'error', 'failed', 'exception', 'traceback', 'panic',
45
+ 'warning', 'deprecated',
46
+ 'test passed', 'test failed', 'tests passed', 'tests failed',
47
+ 'coverage', 'assert',
48
+ 'published', 'deployed', 'built successfully', 'build complete',
49
+ 'successfully installed', 'successfully created',
50
+ ];
51
+
52
+ /**
53
+ * For Bash commands, only store output that is significant:
54
+ * - Has stderr content
55
+ * - Contains important keywords (errors, test results, deploy events)
56
+ * - Output is very long (> 2000 chars), indicating meaningful work
57
+ */
58
+ function isBashSignificant(output: string, response: PostToolUseInput['tool_response']): boolean {
59
+ if (response?.stderr && response.stderr.trim().length > 20) return true;
60
+ const lower = output.toLowerCase();
61
+ if (IMPORTANT_BASH_KEYWORDS.some((kw) => lower.includes(kw))) return true;
62
+ return output.trim().length > 2000;
63
+ }
64
+
65
+ /**
66
+ * Determine if a tool output is significant enough to store.
67
+ * Always-store tools bypass the length check.
68
+ * Bash uses keyword-based significance detection.
69
+ * Other tools require non-empty stderr or output length >= minLen.
70
+ */
71
+ function hasSignificantOutput(
72
+ toolName: string,
73
+ output: string,
74
+ response: PostToolUseInput['tool_response'],
75
+ minLen: number
76
+ ): boolean {
77
+ if (ALWAYS_STORE_TOOLS.has(toolName)) return true;
78
+ if (toolName === 'Bash') return isBashSignificant(output, response);
79
+ if (response?.stderr && response.stderr.trim().length > 0) return true;
80
+ return output.trim().length >= minLen;
81
+ }
82
+
83
+ const DEFAULT_PRIVACY_CONFIG: Config['privacy'] = {
84
+ excludePatterns: ['password', 'secret', 'api_key', 'token', 'bearer'],
85
+ anonymize: false,
86
+ privateTags: {
87
+ enabled: true,
88
+ marker: '[PRIVATE]',
89
+ preserveLineCount: false,
90
+ supportedFormats: ['xml']
91
+ }
92
+ };
93
+
94
+ /**
95
+ * Extract text output from tool_response object
96
+ */
97
+ function extractToolOutput(response: PostToolUseInput['tool_response']): string {
98
+ if (!response) return '';
99
+
100
+ // Bash tools: stdout + stderr
101
+ if (response.stdout !== undefined) {
102
+ const parts: string[] = [];
103
+ if (response.stdout) parts.push(response.stdout);
104
+ if (response.stderr) parts.push(`[stderr] ${response.stderr}`);
105
+ return parts.join('\n') || '';
106
+ }
107
+
108
+ // Other tools may have content field
109
+ if (response.content !== undefined) {
110
+ return typeof response.content === 'string'
111
+ ? response.content
112
+ : JSON.stringify(response.content);
113
+ }
114
+
115
+ // Fallback: stringify the whole response
116
+ return JSON.stringify(response);
117
+ }
118
+
119
+ /**
120
+ * Determine if the tool execution was successful
121
+ */
122
+ function isToolSuccess(response: PostToolUseInput['tool_response']): boolean {
123
+ if (!response) return false;
124
+ if (response.interrupted) return false;
125
+ // If stderr has content but stdout also has content, still consider success
126
+ return true;
127
+ }
128
+
129
+ export async function main(): Promise<void> {
130
+ // Read input from stdin
131
+ const inputData = await readStdin();
132
+ const input: PostToolUseInput = JSON.parse(inputData);
133
+
134
+ const config = { ...DEFAULT_CONFIG };
135
+ const privacyConfig = DEFAULT_PRIVACY_CONFIG;
136
+
137
+ // Allow env-based blocklist override
138
+ const envBlocklist = process.env.CLAUDE_MEMORY_TOOL_BLOCKLIST;
139
+ if (envBlocklist !== undefined) {
140
+ config.excludedTools = envBlocklist.split(',').map((s) => s.trim()).filter(Boolean);
141
+ }
142
+
143
+ // 1. Check if tool observation is enabled
144
+ if (!config.enabled) {
145
+ console.log(JSON.stringify({}));
146
+ return;
147
+ }
148
+
149
+ // 2. Check if tool is excluded
150
+ if (config.excludedTools?.includes(input.tool_name)) {
151
+ console.log(JSON.stringify({}));
152
+ return;
153
+ }
154
+
155
+ // 3. Extract output from tool_response object
156
+ const toolOutput = extractToolOutput(input.tool_response);
157
+ const success = isToolSuccess(input.tool_response);
158
+
159
+ // 4. Check success filter
160
+ if (!success && config.storeOnlyOnSuccess) {
161
+ console.log(JSON.stringify({}));
162
+ return;
163
+ }
164
+
165
+ // 4.5. Output-level filter: skip low-signal outputs
166
+ if (!hasSignificantOutput(
167
+ input.tool_name, toolOutput, input.tool_response,
168
+ config.minOutputLength ?? 100
169
+ )) {
170
+ console.log(JSON.stringify({}));
171
+ return;
172
+ }
173
+
174
+ try {
175
+ const memoryService = getLightweightMemoryService(input.session_id);
176
+
177
+ // 5. Mask sensitive data in input
178
+ const maskedInput = maskSensitiveInput(input.tool_input);
179
+
180
+ // 6. Apply privacy filter to output
181
+ const filterResult = applyPrivacyFilter(toolOutput, privacyConfig);
182
+ const maskedOutput = filterResult.content;
183
+
184
+ // 7. Truncate output
185
+ const truncatedOutput = truncateOutput(maskedOutput, {
186
+ maxLength: config.maxOutputLength,
187
+ maxLines: config.maxOutputLines
188
+ });
189
+
190
+ // 8. Extract metadata
191
+ const metadata = extractMetadata(
192
+ input.tool_name,
193
+ maskedInput,
194
+ toolOutput,
195
+ success
196
+ );
197
+
198
+ // 8.5. Read current turn_id from state file
199
+ const turnId = readTurnState(input.session_id);
200
+
201
+ // 9. Create payload (include turnId in metadata for grouping)
202
+ const payload: ToolObservationPayload = {
203
+ toolName: input.tool_name,
204
+ toolInput: maskedInput,
205
+ toolOutput: truncatedOutput,
206
+ durationMs: 0, // Claude Code doesn't provide timing info
207
+ success,
208
+ errorMessage: input.tool_response?.stderr || undefined,
209
+ metadata: {
210
+ ...metadata,
211
+ ...(turnId ? { turnId } : {})
212
+ }
213
+ };
214
+
215
+ // 10. Store observation
216
+ await memoryService.storeToolObservation(input.session_id, payload);
217
+
218
+ // Output empty (hook doesn't return context)
219
+ console.log(JSON.stringify({}));
220
+ } catch (error) {
221
+ if (process.env.CLAUDE_MEMORY_DEBUG) {
222
+ console.error('PostToolUse hook error:', error);
223
+ }
224
+ console.log(JSON.stringify({}));
225
+ }
226
+ }
227
+
228
+ function readStdin(): Promise<string> {
229
+ return new Promise((resolve) => {
230
+ let data = '';
231
+ process.stdin.setEncoding('utf8');
232
+ process.stdin.on('data', (chunk) => {
233
+ data += chunk;
234
+ });
235
+ process.stdin.on('end', () => {
236
+ resolve(data);
237
+ });
238
+ });
239
+ }
@@ -0,0 +1,104 @@
1
+ export type HookMemorySource = 'semantic' | 'keyword' | 'unknown';
2
+
3
+ export interface HookMemoryCandidate {
4
+ type: string;
5
+ content: string;
6
+ id?: string;
7
+ score?: number;
8
+ source?: HookMemorySource;
9
+ fallback?: boolean;
10
+ }
11
+
12
+ export interface HookInjectionPolicy {
13
+ minScore: number;
14
+ semanticMinScore: number;
15
+ keywordMinScore: number;
16
+ fallbackKeywordMinScore: number;
17
+ maxMemories: number;
18
+ }
19
+
20
+ export function getHookInjectionPolicy(env: NodeJS.ProcessEnv = process.env): HookInjectionPolicy {
21
+ const minScore = readScoreThreshold(env.CLAUDE_MEMORY_HOOK_INJECTION_MIN_SCORE, 0.65);
22
+ const keywordMinScore = readNumber(
23
+ env.CLAUDE_MEMORY_HOOK_KEYWORD_MIN_SCORE,
24
+ Math.max(minScore, 0.7),
25
+ { min: 0, max: 1 }
26
+ );
27
+
28
+ return {
29
+ minScore,
30
+ semanticMinScore: readScoreThreshold(env.CLAUDE_MEMORY_HOOK_SEMANTIC_MIN_SCORE, minScore),
31
+ keywordMinScore,
32
+ fallbackKeywordMinScore: readNumber(
33
+ env.CLAUDE_MEMORY_HOOK_FALLBACK_KEYWORD_MIN_SCORE,
34
+ Math.max(keywordMinScore, 0.8),
35
+ { min: 0, max: 1 }
36
+ ),
37
+ maxMemories: Math.max(1, Math.floor(readNumber(env.CLAUDE_MEMORY_HOOK_MAX_INJECTED, 5)))
38
+ };
39
+ }
40
+
41
+ export function filterHookInjectableMemories(
42
+ candidates: HookMemoryCandidate[],
43
+ policy: HookInjectionPolicy = getHookInjectionPolicy()
44
+ ): HookMemoryCandidate[] {
45
+ return candidates
46
+ .map((candidate, index) => ({ candidate, index }))
47
+ .filter(({ candidate }) => isHookInjectableMemory(candidate, policy))
48
+ .sort((a, b) => {
49
+ const scoreDelta = (b.candidate.score ?? Number.NEGATIVE_INFINITY)
50
+ - (a.candidate.score ?? Number.NEGATIVE_INFINITY);
51
+ return scoreDelta || a.index - b.index;
52
+ })
53
+ .slice(0, policy.maxMemories)
54
+ .map(({ candidate }) => candidate);
55
+ }
56
+
57
+ export function summarizeHookInjectionConfidence(candidates: HookMemoryCandidate[]): 'none' | 'medium' | 'high' {
58
+ const scores = candidates
59
+ .map((candidate) => candidate.score)
60
+ .filter((score): score is number => typeof score === 'number' && Number.isFinite(score));
61
+ if (scores.length === 0) return 'none';
62
+
63
+ const maxScore = Math.max(...scores);
64
+ if (maxScore >= 0.8) return 'high';
65
+ if (maxScore >= 0.65) return 'medium';
66
+ return 'none';
67
+ }
68
+
69
+ function isHookInjectableMemory(candidate: HookMemoryCandidate, policy: HookInjectionPolicy): boolean {
70
+ if (typeof candidate.score !== 'number' || !Number.isFinite(candidate.score)) {
71
+ return false;
72
+ }
73
+
74
+ return candidate.score >= thresholdFor(candidate, policy);
75
+ }
76
+
77
+ function thresholdFor(candidate: HookMemoryCandidate, policy: HookInjectionPolicy): number {
78
+ if (candidate.source === 'keyword') {
79
+ return candidate.fallback
80
+ ? policy.fallbackKeywordMinScore
81
+ : policy.keywordMinScore;
82
+ }
83
+ if (candidate.source === 'semantic') {
84
+ return policy.semanticMinScore;
85
+ }
86
+ return policy.minScore;
87
+ }
88
+
89
+ function readScoreThreshold(value: string | undefined, fallback: number): number {
90
+ return readNumber(value, fallback, { min: 0, max: 1 });
91
+ }
92
+
93
+ function readNumber(
94
+ value: string | undefined,
95
+ fallback: number,
96
+ bounds?: { min?: number; max?: number }
97
+ ): number {
98
+ if (value === undefined || value.trim() === '') return fallback;
99
+ const parsed = Number(value);
100
+ if (!Number.isFinite(parsed)) return fallback;
101
+ if (bounds?.min !== undefined && parsed < bounds.min) return fallback;
102
+ if (bounds?.max !== undefined && parsed > bounds.max) return fallback;
103
+ return parsed;
104
+ }
@@ -0,0 +1,209 @@
1
+ import { spawn } from 'child_process';
2
+ import * as fs from 'fs';
3
+ import * as net from 'net';
4
+ import * as os from 'os';
5
+ import * as path from 'path';
6
+
7
+ interface SemanticRequest {
8
+ sessionId: string;
9
+ prompt: string;
10
+ topK: number;
11
+ minScore: number;
12
+ }
13
+
14
+ interface SemanticMemory {
15
+ type: string;
16
+ content: string;
17
+ id?: string;
18
+ score?: number;
19
+ }
20
+
21
+ interface SemanticDaemonRequest {
22
+ type: 'retrieve';
23
+ sessionId: string;
24
+ prompt: string;
25
+ topK: number;
26
+ minScore: number;
27
+ }
28
+
29
+ interface SemanticDaemonResponse {
30
+ ok: boolean;
31
+ memories?: SemanticMemory[];
32
+ error?: string;
33
+ }
34
+
35
+ const DEFAULT_SOCKET_PATH = path.join(
36
+ os.homedir(),
37
+ '.claude-code',
38
+ 'memory',
39
+ 'semantic-daemon.sock'
40
+ );
41
+
42
+ const DAEMON_SOCKET_PATH = process.env.CLAUDE_MEMORY_SEMANTIC_SOCKET || DEFAULT_SOCKET_PATH;
43
+ const DAEMON_START_TIMEOUT_MS = parseInt(process.env.CLAUDE_MEMORY_SEMANTIC_DAEMON_START_MS || '1500');
44
+
45
+ let daemonStartPromise: Promise<void> | null = null;
46
+
47
+ export async function retrieveSemanticMemories(
48
+ request: SemanticRequest,
49
+ timeoutMs: number
50
+ ): Promise<SemanticMemory[]> {
51
+ const payload: SemanticDaemonRequest = {
52
+ type: 'retrieve',
53
+ sessionId: request.sessionId,
54
+ prompt: request.prompt,
55
+ topK: request.topK,
56
+ minScore: request.minScore
57
+ };
58
+
59
+ try {
60
+ return await requestFromDaemon(payload, timeoutMs);
61
+ } catch (error) {
62
+ if (!isConnectionError(error)) {
63
+ throw error;
64
+ }
65
+
66
+ await ensureDaemonRunning();
67
+ return requestFromDaemon(payload, timeoutMs).catch((retryError) => {
68
+ if (process.env.CLAUDE_MEMORY_DEBUG) {
69
+ console.error('[semantic-client] retry failed after daemon start:', retryError);
70
+ }
71
+ throw retryError;
72
+ });
73
+ }
74
+ }
75
+
76
+ function requestFromDaemon(
77
+ payload: SemanticDaemonRequest,
78
+ timeoutMs: number
79
+ ): Promise<SemanticMemory[]> {
80
+ return new Promise((resolve, reject) => {
81
+ const client = net.createConnection(DAEMON_SOCKET_PATH);
82
+ client.setEncoding('utf8');
83
+
84
+ let settled = false;
85
+ let responseRaw = '';
86
+ const timer = setTimeout(() => {
87
+ const timeoutError = new Error(`semantic daemon timeout (${timeoutMs}ms)`);
88
+ (timeoutError as NodeJS.ErrnoException).code = 'ETIMEDOUT';
89
+ settle(timeoutError);
90
+ client.destroy();
91
+ }, timeoutMs);
92
+
93
+ const settle = (error?: Error, memories?: SemanticMemory[]) => {
94
+ if (settled) return;
95
+ settled = true;
96
+ clearTimeout(timer);
97
+ if (error) {
98
+ reject(error);
99
+ } else {
100
+ resolve(memories || []);
101
+ }
102
+ };
103
+
104
+ client.on('connect', () => {
105
+ client.end(JSON.stringify(payload));
106
+ });
107
+
108
+ client.on('data', (chunk) => {
109
+ responseRaw += chunk;
110
+ if (responseRaw.length > 4 * 1024 * 1024) {
111
+ settle(new Error('semantic daemon response too large'));
112
+ client.destroy();
113
+ }
114
+ });
115
+
116
+ client.on('end', () => {
117
+ try {
118
+ const parsed = JSON.parse(responseRaw || '{}') as SemanticDaemonResponse;
119
+ if (!parsed.ok) {
120
+ settle(new Error(parsed.error || 'semantic daemon error'));
121
+ return;
122
+ }
123
+ settle(undefined, parsed.memories || []);
124
+ } catch (error) {
125
+ settle(error as Error);
126
+ }
127
+ });
128
+
129
+ client.on('error', (error) => {
130
+ settle(error as Error);
131
+ });
132
+ });
133
+ }
134
+
135
+ export async function ensureDaemonRunning(): Promise<void> {
136
+ if (daemonStartPromise) {
137
+ return daemonStartPromise;
138
+ }
139
+
140
+ daemonStartPromise = (async () => {
141
+ if (await canConnect()) {
142
+ return;
143
+ }
144
+
145
+ const daemonScriptPath = getDaemonScriptPath();
146
+ if (!fs.existsSync(daemonScriptPath)) {
147
+ throw new Error(`semantic daemon script not found: ${daemonScriptPath}`);
148
+ }
149
+
150
+ const daemonDir = path.dirname(DAEMON_SOCKET_PATH);
151
+ if (!fs.existsSync(daemonDir)) {
152
+ fs.mkdirSync(daemonDir, { recursive: true });
153
+ }
154
+
155
+ const child = spawn(process.execPath, [daemonScriptPath], {
156
+ detached: true,
157
+ stdio: 'ignore',
158
+ env: process.env
159
+ });
160
+ child.unref();
161
+
162
+ const startDeadline = Date.now() + DAEMON_START_TIMEOUT_MS;
163
+ while (Date.now() < startDeadline) {
164
+ if (await canConnect()) {
165
+ return;
166
+ }
167
+ await sleep(60);
168
+ }
169
+
170
+ throw new Error(`semantic daemon start timeout (${DAEMON_START_TIMEOUT_MS}ms)`);
171
+ })();
172
+
173
+ try {
174
+ await daemonStartPromise;
175
+ } finally {
176
+ daemonStartPromise = null;
177
+ }
178
+ }
179
+
180
+ function getDaemonScriptPath(): string {
181
+ return path.join(path.dirname(new URL(import.meta.url).pathname), 'semantic-daemon.js');
182
+ }
183
+
184
+ function canConnect(): Promise<boolean> {
185
+ return new Promise((resolve) => {
186
+ let settled = false;
187
+ const client = net.createConnection(DAEMON_SOCKET_PATH);
188
+ const finalize = (ok: boolean) => {
189
+ if (settled) return;
190
+ settled = true;
191
+ client.destroy();
192
+ resolve(ok);
193
+ };
194
+
195
+ client.on('connect', () => finalize(true));
196
+ client.on('error', () => finalize(false));
197
+ setTimeout(() => finalize(false), 120).unref();
198
+ });
199
+ }
200
+
201
+ function isConnectionError(error: unknown): boolean {
202
+ const code = (error as NodeJS.ErrnoException | undefined)?.code;
203
+ return code === 'ENOENT' || code === 'ECONNREFUSED' || code === 'EPIPE' || code === 'ECONNRESET';
204
+ }
205
+
206
+ function sleep(ms: number): Promise<void> {
207
+ return new Promise((resolve) => setTimeout(resolve, ms));
208
+ }
209
+