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,167 @@
1
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+
6
+ import { describe, expect, it } from 'vitest';
7
+
8
+ const require = createRequire(import.meta.url);
9
+
10
+ type SpawnCall = {
11
+ cmd: string;
12
+ args: string[];
13
+ env: NodeJS.ProcessEnv;
14
+ };
15
+
16
+ type PostinstallEmbeddingBackend = {
17
+ EMBEDDING_BACKEND_PACKAGE: string;
18
+ parseCudaMajor(output: string): number | null;
19
+ shouldAttemptAutoInstall(input: {
20
+ platform: NodeJS.Platform;
21
+ arch: string;
22
+ cudaMajor: number | null;
23
+ transformersAvailable: boolean;
24
+ skipRequested: boolean;
25
+ }): boolean;
26
+ createRepairEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
27
+ createNpmInstallArgs(): string[];
28
+ runPostinstall(input?: {
29
+ rootDir?: string;
30
+ env?: NodeJS.ProcessEnv;
31
+ platform?: NodeJS.Platform;
32
+ arch?: string;
33
+ execFileSyncImpl?: () => string;
34
+ spawnSyncImpl?: (cmd: string, args: string[], options: { env: NodeJS.ProcessEnv }) => { status: number };
35
+ log?: () => void;
36
+ warn?: () => void;
37
+ }): { attempted: boolean; success?: boolean; cudaMajor: number | null; transformersAvailable: boolean; skipRequested: boolean };
38
+ };
39
+
40
+ function loadPostinstallModule(): PostinstallEmbeddingBackend {
41
+ return require('../../scripts/postinstall-embedding-backend.cjs') as PostinstallEmbeddingBackend;
42
+ }
43
+
44
+ describe('embedding backend postinstall repair', () => {
45
+ it('keeps the install-time embedding backend optional and registers postinstall repair', () => {
46
+ const pkg = JSON.parse(readFileSync('package.json', 'utf-8')) as {
47
+ scripts: Record<string, string>;
48
+ dependencies?: Record<string, string>;
49
+ optionalDependencies?: Record<string, string>;
50
+ };
51
+
52
+ expect(pkg.dependencies).not.toHaveProperty('@huggingface/transformers');
53
+ expect(pkg.optionalDependencies).toMatchObject({
54
+ '@huggingface/transformers': '^3.8.1'
55
+ });
56
+ expect(pkg.scripts.postinstall).toBe('node scripts/postinstall-embedding-backend.cjs');
57
+ });
58
+
59
+ it('detects CUDA major version from nvcc output', () => {
60
+ const postinstall = loadPostinstallModule();
61
+
62
+ expect(postinstall.parseCudaMajor('Cuda compilation tools, release 11.8, V11.8.89')).toBe(11);
63
+ expect(postinstall.parseCudaMajor('Cuda compilation tools, release 12.4, V12.4.131')).toBe(12);
64
+ expect(postinstall.parseCudaMajor('nvcc: NVIDIA (R) Cuda compiler driver')).toBeNull();
65
+ });
66
+
67
+ it('only auto-installs the embedding backend for Linux x64 CUDA 11 when it is missing', () => {
68
+ const postinstall = loadPostinstallModule();
69
+
70
+ expect(postinstall.shouldAttemptAutoInstall({
71
+ platform: 'linux',
72
+ arch: 'x64',
73
+ cudaMajor: 11,
74
+ transformersAvailable: false,
75
+ skipRequested: false
76
+ })).toBe(true);
77
+
78
+ expect(postinstall.shouldAttemptAutoInstall({
79
+ platform: 'linux',
80
+ arch: 'x64',
81
+ cudaMajor: 11,
82
+ transformersAvailable: true,
83
+ skipRequested: false
84
+ })).toBe(false);
85
+
86
+ expect(postinstall.shouldAttemptAutoInstall({
87
+ platform: 'linux',
88
+ arch: 'arm64',
89
+ cudaMajor: 11,
90
+ transformersAvailable: false,
91
+ skipRequested: false
92
+ })).toBe(false);
93
+
94
+ expect(postinstall.shouldAttemptAutoInstall({
95
+ platform: 'darwin',
96
+ arch: 'x64',
97
+ cudaMajor: 11,
98
+ transformersAvailable: false,
99
+ skipRequested: false
100
+ })).toBe(false);
101
+
102
+ expect(postinstall.shouldAttemptAutoInstall({
103
+ platform: 'linux',
104
+ arch: 'x64',
105
+ cudaMajor: 12,
106
+ transformersAvailable: false,
107
+ skipRequested: false
108
+ })).toBe(false);
109
+
110
+ expect(postinstall.shouldAttemptAutoInstall({
111
+ platform: 'linux',
112
+ arch: 'x64',
113
+ cudaMajor: 11,
114
+ transformersAvailable: false,
115
+ skipRequested: true
116
+ })).toBe(false);
117
+ });
118
+
119
+ it('repairs missing transformers with CPU-only onnxruntime install settings', () => {
120
+ const postinstall = loadPostinstallModule();
121
+
122
+ expect(postinstall.createRepairEnv({})).toMatchObject({
123
+ ONNXRUNTIME_NODE_INSTALL_CUDA: 'skip',
124
+ CLAUDE_MEMORY_LAYER_EMBEDDING_POSTINSTALL_REPAIR: '1'
125
+ });
126
+ expect(postinstall.createNpmInstallArgs()).toEqual([
127
+ 'install',
128
+ '--no-save',
129
+ '--no-package-lock',
130
+ '--omit=dev',
131
+ postinstall.EMBEDDING_BACKEND_PACKAGE
132
+ ]);
133
+ });
134
+
135
+ it('runs the automatic repair command when Linux x64 CUDA 11 skipped the optional backend', () => {
136
+ const postinstall = loadPostinstallModule();
137
+ const rootDir = mkdtempSync(join(tmpdir(), 'cml-postinstall-test-'));
138
+ const calls: SpawnCall[] = [];
139
+
140
+ try {
141
+ writeFileSync(join(rootDir, 'package.json'), JSON.stringify({ name: 'claude-memory-layer-install-root' }));
142
+
143
+ const result = postinstall.runPostinstall({
144
+ rootDir,
145
+ env: {},
146
+ platform: 'linux',
147
+ arch: 'x64',
148
+ execFileSyncImpl: () => 'Cuda compilation tools, release 11.8, V11.8.89',
149
+ spawnSyncImpl: (cmd, args, options) => {
150
+ calls.push({ cmd, args, env: options.env });
151
+ return { status: 0 };
152
+ },
153
+ log: () => undefined,
154
+ warn: () => undefined
155
+ });
156
+
157
+ expect(result).toMatchObject({ attempted: true, success: true, cudaMajor: 11, transformersAvailable: false });
158
+ expect(calls).toHaveLength(1);
159
+ expect(calls[0]?.cmd).toBe('npm');
160
+ expect(calls[0]?.args).toEqual(postinstall.createNpmInstallArgs());
161
+ expect(calls[0]?.env.ONNXRUNTIME_NODE_INSTALL_CUDA).toBe('skip');
162
+ expect(calls[0]?.env.CLAUDE_MEMORY_LAYER_EMBEDDING_POSTINSTALL_REPAIR).toBe('1');
163
+ } finally {
164
+ rmSync(rootDir, { recursive: true, force: true });
165
+ }
166
+ });
167
+ });
@@ -0,0 +1,162 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { Hono } from 'hono';
3
+
4
+ const mocks = vi.hoisted(() => {
5
+ const service = {
6
+ initialize: vi.fn(),
7
+ shutdown: vi.fn(),
8
+ retrieveMemories: vi.fn(),
9
+ searchDisclosure: vi.fn(),
10
+ expandDisclosure: vi.fn(),
11
+ sourceDisclosure: vi.fn()
12
+ };
13
+
14
+ const lightweightService = {
15
+ initialize: vi.fn(),
16
+ shutdown: vi.fn(),
17
+ searchDisclosure: vi.fn()
18
+ };
19
+
20
+ return {
21
+ service,
22
+ lightweightService,
23
+ getServiceFromQuery: vi.fn(() => service),
24
+ getLightweightServiceFromQuery: vi.fn(() => lightweightService)
25
+ };
26
+ });
27
+
28
+ vi.mock('../../src/apps/server/api/utils.js', () => ({
29
+ getServiceFromQuery: mocks.getServiceFromQuery,
30
+ getLightweightServiceFromQuery: mocks.getLightweightServiceFromQuery
31
+ }));
32
+
33
+ const { searchRouter } = await import('../../src/server/api/search.js');
34
+
35
+ function createApp() {
36
+ const app = new Hono();
37
+ app.route('/api/search', searchRouter);
38
+ return app;
39
+ }
40
+
41
+ describe('search disclosure API', () => {
42
+ beforeEach(() => {
43
+ mocks.service.initialize.mockReset().mockResolvedValue(undefined);
44
+ mocks.service.shutdown.mockReset().mockResolvedValue(undefined);
45
+ mocks.service.retrieveMemories.mockReset();
46
+ mocks.service.searchDisclosure.mockReset();
47
+ mocks.service.expandDisclosure.mockReset();
48
+ mocks.service.sourceDisclosure.mockReset();
49
+ mocks.lightweightService.initialize.mockReset().mockResolvedValue(undefined);
50
+ mocks.lightweightService.shutdown.mockReset().mockResolvedValue(undefined);
51
+ mocks.lightweightService.searchDisclosure.mockReset();
52
+ mocks.getServiceFromQuery.mockClear();
53
+ mocks.getLightweightServiceFromQuery.mockClear();
54
+ });
55
+
56
+ it('POST /api/search/disclosure delegates to MemoryService.searchDisclosure', async () => {
57
+ const responseBody = {
58
+ results: [{ id: 'event:e1', resultType: 'source', snippet: 'checkout fix', score: 0.91, reasons: ['semantic_match'], sourceRef: 'event:e1' }],
59
+ meta: { total: 1, usedVector: true, usedKeyword: true, fallbackApplied: false }
60
+ };
61
+ mocks.service.searchDisclosure.mockResolvedValue(responseBody);
62
+
63
+ const res = await createApp().request('/api/search/disclosure?project=abc12345', {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify({ query: 'checkout fix', options: { topK: 3, includeShared: true } })
67
+ });
68
+
69
+ expect(res.status).toBe(200);
70
+ expect(await res.json()).toEqual(responseBody);
71
+ expect(mocks.getServiceFromQuery).toHaveBeenCalledTimes(1);
72
+ expect(mocks.service.initialize).toHaveBeenCalledTimes(1);
73
+ expect(mocks.service.searchDisclosure).toHaveBeenCalledWith('checkout fix', { topK: 3, includeShared: true });
74
+ expect(mocks.service.shutdown).toHaveBeenCalledTimes(1);
75
+ });
76
+
77
+
78
+ it('POST /api/search/disclosure uses lightweight service for explicit fast search', async () => {
79
+ const responseBody = {
80
+ results: [{ id: 'event:e1', resultType: 'source', snippet: 'checkout fix', score: 0.91, reasons: ['keyword_match'], sourceRef: 'event:e1' }],
81
+ meta: { total: 1, usedVector: false, usedKeyword: true, fallbackApplied: false }
82
+ };
83
+ mocks.lightweightService.searchDisclosure.mockResolvedValue(responseBody);
84
+
85
+ const res = await createApp().request('/api/search/disclosure?project=abc12345', {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify({ query: 'checkout fix', options: { topK: 3, strategy: 'fast', includeShared: true } })
89
+ });
90
+
91
+ expect(res.status).toBe(200);
92
+ expect(await res.json()).toEqual(responseBody);
93
+ expect(mocks.getLightweightServiceFromQuery).toHaveBeenCalledTimes(1);
94
+ expect(mocks.getServiceFromQuery).not.toHaveBeenCalled();
95
+ expect(mocks.lightweightService.initialize).toHaveBeenCalledTimes(1);
96
+ expect(mocks.lightweightService.searchDisclosure).toHaveBeenCalledWith('checkout fix', { topK: 3, strategy: 'fast', includeShared: true });
97
+ expect(mocks.lightweightService.shutdown).toHaveBeenCalledTimes(1);
98
+ expect(mocks.service.initialize).not.toHaveBeenCalled();
99
+ expect(mocks.service.searchDisclosure).not.toHaveBeenCalled();
100
+ });
101
+
102
+ it('POST /api/search/disclosure rejects missing query', async () => {
103
+ const res = await createApp().request('/api/search/disclosure', {
104
+ method: 'POST',
105
+ headers: { 'Content-Type': 'application/json' },
106
+ body: JSON.stringify({ options: { topK: 3 } })
107
+ });
108
+
109
+ expect(res.status).toBe(400);
110
+ expect(await res.json()).toEqual({ error: 'Query is required' });
111
+ expect(mocks.getServiceFromQuery).not.toHaveBeenCalled();
112
+ expect(mocks.getLightweightServiceFromQuery).not.toHaveBeenCalled();
113
+ expect(mocks.service.searchDisclosure).not.toHaveBeenCalled();
114
+ expect(mocks.service.shutdown).not.toHaveBeenCalled();
115
+ });
116
+
117
+ it('GET /api/search/disclosure/:resultId/expand expands a disclosure result', async () => {
118
+ const responseBody = {
119
+ target: { id: 'event:e1', resultType: 'source', snippet: 'checkout fix', score: 1, reasons: ['continuity_link'] },
120
+ surroundingFacts: [],
121
+ relatedSources: [{ sourceRef: 'event:e1', sourceType: 'raw_event', eventIds: ['e1'] }]
122
+ };
123
+ mocks.service.expandDisclosure.mockResolvedValue(responseBody);
124
+
125
+ const res = await createApp().request('/api/search/disclosure/event:e1/expand?windowSize=2');
126
+
127
+ expect(res.status).toBe(200);
128
+ expect(await res.json()).toEqual(responseBody);
129
+ expect(mocks.service.initialize).not.toHaveBeenCalled();
130
+ expect(mocks.service.expandDisclosure).toHaveBeenCalledWith('event:e1', { windowSize: 2 });
131
+ expect(mocks.service.shutdown).toHaveBeenCalledTimes(1);
132
+ });
133
+
134
+ it('GET /api/search/disclosure/:resultId/source resolves a disclosure result source', async () => {
135
+ const responseBody = {
136
+ sourceRef: 'event:e1',
137
+ sourceType: 'raw_event',
138
+ eventIds: ['e1'],
139
+ rawEvents: [{ id: 'e1', content: 'checkout fix' }]
140
+ };
141
+ mocks.service.sourceDisclosure.mockResolvedValue(responseBody);
142
+
143
+ const res = await createApp().request('/api/search/disclosure/event:e1/source');
144
+
145
+ expect(res.status).toBe(200);
146
+ expect(await res.json()).toEqual(responseBody);
147
+ expect(mocks.service.initialize).not.toHaveBeenCalled();
148
+ expect(mocks.service.sourceDisclosure).toHaveBeenCalledWith('event:e1');
149
+ expect(mocks.service.shutdown).toHaveBeenCalledTimes(1);
150
+ });
151
+
152
+ it('GET /api/search/disclosure/:resultId/source returns 404 when source is missing', async () => {
153
+ mocks.service.sourceDisclosure.mockResolvedValue(null);
154
+
155
+ const res = await createApp().request('/api/search/disclosure/event:missing/source');
156
+
157
+ expect(res.status).toBe(404);
158
+ expect(await res.json()).toEqual({ error: 'Source not found' });
159
+ expect(mocks.service.initialize).not.toHaveBeenCalled();
160
+ expect(mocks.service.shutdown).toHaveBeenCalledTimes(1);
161
+ });
162
+ });
@@ -0,0 +1,67 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { Hono } from 'hono';
3
+
4
+ const mocks = vi.hoisted(() => {
5
+ const service = {
6
+ initialize: vi.fn(),
7
+ shutdown: vi.fn(),
8
+ getStats: vi.fn(),
9
+ getRecentEvents: vi.fn(),
10
+ getRetrievalTraceStats: vi.fn()
11
+ };
12
+
13
+ return {
14
+ service,
15
+ getServiceFromQuery: vi.fn(),
16
+ getLightweightServiceFromQuery: vi.fn(() => service)
17
+ };
18
+ });
19
+
20
+ vi.mock('../../src/apps/server/api/utils.js', () => ({
21
+ getServiceFromQuery: mocks.getServiceFromQuery,
22
+ getLightweightServiceFromQuery: mocks.getLightweightServiceFromQuery
23
+ }));
24
+
25
+ vi.mock('../../src/services/memory-service.js', async (importOriginal) => {
26
+ const actual = await importOriginal<typeof import('../../src/services/memory-service.js')>();
27
+ return {
28
+ ...actual,
29
+ getMemoryServiceForProject: vi.fn(() => mocks.service)
30
+ };
31
+ });
32
+
33
+ const { statsRouter } = await import('../../src/server/api/stats.js');
34
+
35
+ function createApp() {
36
+ const app = new Hono();
37
+ app.route('/api/stats', statsRouter);
38
+ return app;
39
+ }
40
+
41
+ describe('stats API lightweight read paths', () => {
42
+ beforeEach(() => {
43
+ mocks.service.initialize.mockReset().mockResolvedValue(undefined);
44
+ mocks.service.shutdown.mockReset().mockResolvedValue(undefined);
45
+ mocks.service.getStats.mockReset().mockResolvedValue({ totalEvents: 2, vectorCount: 0, levelStats: [] });
46
+ mocks.service.getRecentEvents.mockReset().mockResolvedValue([
47
+ { id: 'e1', eventType: 'user_prompt', sessionId: 's1', timestamp: new Date('2026-05-01T00:00:00.000Z'), content: 'prompt', metadata: {} },
48
+ { id: 'e2', eventType: 'agent_response', sessionId: 's1', timestamp: new Date('2026-05-01T00:01:00.000Z'), content: 'response', metadata: {} }
49
+ ]);
50
+ mocks.service.getRetrievalTraceStats.mockReset().mockResolvedValue({ totalQueries: 0, avgCandidateCount: 0, avgSelectedCount: 0, selectionRate: 0 });
51
+ mocks.getServiceFromQuery.mockClear();
52
+ mocks.getLightweightServiceFromQuery.mockClear();
53
+ });
54
+
55
+ it('GET /api/stats uses the lightweight read-only service instead of full initialization service', async () => {
56
+ const res = await createApp().request('/api/stats?project=abc12345');
57
+
58
+ expect(res.status).toBe(200);
59
+ const body = await res.json();
60
+ expect(body.storage).toEqual({ eventCount: 2, vectorCount: 0 });
61
+ expect(body.sessions).toEqual({ total: 1 });
62
+ expect(mocks.getLightweightServiceFromQuery).toHaveBeenCalledTimes(1);
63
+ expect(mocks.getServiceFromQuery).not.toHaveBeenCalled();
64
+ expect(mocks.service.initialize).toHaveBeenCalledTimes(1);
65
+ expect(mocks.service.shutdown).toHaveBeenCalledTimes(1);
66
+ });
67
+ });
@@ -0,0 +1,140 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import * as vm from 'node:vm';
5
+
6
+ class TestElement {
7
+ innerHTML = '';
8
+ textContent = '';
9
+ style: Record<string, string> = {};
10
+ disabled = false;
11
+ classList = { add() {}, remove() {}, toggle() {} };
12
+ dataset: Record<string, string> = {};
13
+ options: unknown[] = [];
14
+ addEventListener() {}
15
+ querySelectorAll() { return []; }
16
+ appendChild() {}
17
+ }
18
+
19
+ function loadDashboardWithElements(elements: Record<string, TestElement>) {
20
+ const dashboardDir = join(process.cwd(), 'src/apps/dashboard/assets/js');
21
+ const source = ['state.js', 'views.js', 'disclosure.js']
22
+ .map(file => readFileSync(join(dashboardDir, file), 'utf-8'))
23
+ .join('\n');
24
+ const context = {
25
+ console,
26
+ URL,
27
+ fetch: async () => ({ ok: true, json: async () => ({}) }),
28
+ window: { location: { origin: 'http://localhost:37777' } },
29
+ document: {
30
+ addEventListener() {},
31
+ getElementById(id: string) { return elements[id] ?? null; },
32
+ querySelectorAll() { return []; },
33
+ querySelector() { return null; },
34
+ createElement() { return new TestElement(); }
35
+ },
36
+ setTimeout,
37
+ clearTimeout
38
+ };
39
+
40
+ vm.runInNewContext(
41
+ `${source}\n;globalThis.__dashboardTestHooks = { state, renderDisclosureResults, renderDisclosureDrilldown };`,
42
+ context
43
+ );
44
+ return (context as unknown as { __dashboardTestHooks: {
45
+ state: Record<string, any>;
46
+ renderDisclosureResults: () => void;
47
+ renderDisclosureDrilldown: () => void;
48
+ }}).__dashboardTestHooks;
49
+ }
50
+
51
+ describe('dashboard retrieval disclosure provenance output', () => {
52
+ it('renders shared search results with source/project/topics provenance', () => {
53
+ const elements = { 'disclosure-results': new TestElement() };
54
+ const hooks = loadDashboardWithElements(elements);
55
+
56
+ hooks.state.isDisclosureLoading = false;
57
+ hooks.state.disclosureSelectedId = 'shared:shared-1';
58
+ hooks.state.disclosureMeta = { total: 1, usedVector: true, usedKeyword: true, fallbackApplied: false };
59
+ hooks.state.disclosureResults = [
60
+ {
61
+ id: 'shared:shared-1',
62
+ resultType: 'rule',
63
+ title: 'Shared checkout troubleshooting',
64
+ snippet: 'clear cache and retry',
65
+ score: 0.88,
66
+ reasons: ['semantic_match'],
67
+ sourceRef: 'shared:shared-1',
68
+ metadata: {
69
+ sourceProjectHash: 'project-a',
70
+ sourceEntryId: 'e-shared',
71
+ topics: ['checkout']
72
+ }
73
+ }
74
+ ];
75
+
76
+ hooks.renderDisclosureResults();
77
+
78
+ const html = elements['disclosure-results'].innerHTML;
79
+ expect(html).toContain('shared:shared-1');
80
+ expect(html).toContain('Shared checkout troubleshooting');
81
+ expect(html).toContain('sourceProjectHash');
82
+ expect(html).toContain('project-a');
83
+ expect(html).toContain('topics');
84
+ expect(html).toContain('checkout');
85
+ });
86
+
87
+ it('renders shared drilldown with explicit source metadata and no fake raw event', () => {
88
+ const elements = { 'disclosure-drilldown': new TestElement() };
89
+ const hooks = loadDashboardWithElements(elements);
90
+
91
+ hooks.state.disclosureSelectedId = 'shared:shared-1';
92
+ hooks.state.disclosureExpansion = {
93
+ target: {
94
+ id: 'shared:shared-1',
95
+ resultType: 'rule',
96
+ title: 'Shared checkout troubleshooting',
97
+ snippet: 'clear cache and retry',
98
+ score: 0.88,
99
+ reasons: ['semantic_match'],
100
+ sourceRef: 'shared:shared-1',
101
+ metadata: { sourceProjectHash: 'project-a', sourceEntryId: 'e-shared', topics: ['checkout'] }
102
+ },
103
+ relatedSources: [
104
+ {
105
+ sourceRef: 'shared:shared-1',
106
+ sourceType: 'shared_troubleshooting',
107
+ eventIds: [],
108
+ metadata: { sourceProjectHash: 'project-a', sourceEntryId: 'e-shared', topics: ['checkout'] }
109
+ }
110
+ ],
111
+ expandedContext: '[shared_troubleshooting] Shared checkout troubleshooting\nRoot cause: stale cache\nSolution: clear cache and retry'
112
+ };
113
+ hooks.state.disclosureSource = {
114
+ sourceRef: 'shared:shared-1',
115
+ sourceType: 'shared_troubleshooting',
116
+ eventIds: [],
117
+ rawEvents: [],
118
+ metadata: {
119
+ sourceProjectHash: 'project-a',
120
+ sourceEntryId: 'e-shared',
121
+ topics: ['checkout'],
122
+ rootCause: 'stale cache',
123
+ solution: 'clear cache and retry'
124
+ }
125
+ };
126
+
127
+ hooks.renderDisclosureDrilldown();
128
+
129
+ const html = elements['disclosure-drilldown'].innerHTML;
130
+ expect(html).toContain('shared_troubleshooting');
131
+ expect(html).toContain('Shared source provenance');
132
+ expect(html).toContain('sourceProjectHash');
133
+ expect(html).toContain('project-a');
134
+ expect(html).toContain('rootCause');
135
+ expect(html).toContain('stale cache');
136
+ expect(html).toContain('solution');
137
+ expect(html).toContain('clear cache and retry');
138
+ expect(html).toContain('No local raw events for this shared source.');
139
+ });
140
+ });
@@ -3,7 +3,7 @@ import * as fs from 'node:fs/promises';
3
3
  import * as os from 'node:os';
4
4
  import * as path from 'node:path';
5
5
  import { execSync } from 'node:child_process';
6
- import { bootstrapKnowledgeBase } from '../src/services/bootstrap-organizer.js';
6
+ import { bootstrapKnowledgeBase } from '../../src/services/bootstrap-organizer.js';
7
7
 
8
8
  async function makeTempRepo(): Promise<string> {
9
9
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'cml-bootstrap-'));
@@ -8,7 +8,7 @@ import {
8
8
  isSameCanonicalKey,
9
9
  makeDedupeKey,
10
10
  hashContent
11
- } from '../src/core/canonical-key.js';
11
+ } from '../../src/core/canonical-key.js';
12
12
 
13
13
  describe('makeCanonicalKey', () => {
14
14
  it('should normalize to lowercase', () => {