claude-memory-layer 1.0.26 → 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 (328) 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 -418
  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 -48
  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 -29
  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/specs/optional-duckdb/context.md +0 -77
  326. package/specs/optional-duckdb/plan.md +0 -142
  327. package/specs/optional-duckdb/spec.md +0 -35
  328. package/src/ui/app.js +0 -2101
@@ -0,0 +1,360 @@
1
+ import { readdir, stat } from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+
4
+ export interface SessionQrelsMemory {
5
+ id: string;
6
+ content: string;
7
+ sourceSessionId: string;
8
+ sourceTurnIndex: number;
9
+ timestamp?: string;
10
+ }
11
+
12
+ export type SessionQrelsExpectation = 'match' | 'no_match';
13
+
14
+ export interface SessionQrelsQuery {
15
+ queryId: string;
16
+ query: string;
17
+ expectedIds: string[];
18
+ expectedRelevance: Record<string, number>;
19
+ sourceSessionId: string;
20
+ sourceTurnIndex: number;
21
+ expectation?: SessionQrelsExpectation;
22
+ forbiddenIds?: string[];
23
+ knownAnswer?: string;
24
+ }
25
+
26
+ export interface SessionQrelsFixtureMetadata {
27
+ sourceFileCount?: number;
28
+ rawContentIncluded: boolean;
29
+ generatedAt?: string;
30
+ }
31
+
32
+ export interface SessionQrelsFixture {
33
+ name: string;
34
+ description: string;
35
+ ks: number[];
36
+ queries: SessionQrelsQuery[];
37
+ memories: SessionQrelsMemory[];
38
+ metadata?: SessionQrelsFixtureMetadata;
39
+ }
40
+
41
+ export interface SessionQrelsNoMatchQueryInput {
42
+ queryId?: string;
43
+ query: string;
44
+ forbiddenIds?: string[];
45
+ sourceSessionId?: string;
46
+ sourceTurnIndex?: number;
47
+ }
48
+
49
+ export interface SessionQrelsOptions {
50
+ name?: string;
51
+ description?: string;
52
+ ks?: number[];
53
+ maxQueries?: number;
54
+ redactContent?: boolean;
55
+ sourceFileCount?: number;
56
+ rawContentIncluded?: boolean;
57
+ generatedAt?: string;
58
+ noMatchQueries?: SessionQrelsNoMatchQueryInput[];
59
+ }
60
+
61
+ export interface SessionQrelsFileCollectionOptions {
62
+ includeSubagents?: boolean;
63
+ maxFiles?: number;
64
+ minBytes?: number;
65
+ }
66
+
67
+ export interface SessionQrelsPerSessionSummary {
68
+ sourceSessionId: string;
69
+ queryCount: number;
70
+ memoryCount: number;
71
+ firstTurnIndex: number;
72
+ lastTurnIndex: number;
73
+ }
74
+
75
+ export interface SessionQrelsSummary {
76
+ name: string;
77
+ description: string;
78
+ ks: number[];
79
+ queryCount: number;
80
+ positiveQueryCount: number;
81
+ noMatchQueryCount: number;
82
+ knownAnswerCount: number;
83
+ memoryCount: number;
84
+ sourceSessionCount: number;
85
+ sourceFileCount?: number;
86
+ rawContentIncluded: boolean;
87
+ perSession: SessionQrelsPerSessionSummary[];
88
+ }
89
+
90
+ interface ClaudeContentBlock {
91
+ type: string;
92
+ text?: string;
93
+ }
94
+
95
+ type ClaudeMessageContent = string | ClaudeContentBlock[];
96
+
97
+ interface ClaudeJsonlEntry {
98
+ type?: string;
99
+ sessionId?: string;
100
+ timestamp?: string;
101
+ message?: {
102
+ role?: string;
103
+ content?: ClaudeMessageContent;
104
+ };
105
+ }
106
+
107
+ interface PendingPrompt {
108
+ sessionId: string;
109
+ text: string;
110
+ turnIndex: number;
111
+ timestamp?: string;
112
+ }
113
+
114
+ export function buildSessionQrelsFixtureFromJsonl(
115
+ jsonl: string | string[],
116
+ options: SessionQrelsOptions = {}
117
+ ): SessionQrelsFixture {
118
+ const lines = Array.isArray(jsonl) ? jsonl : jsonl.split(/\r?\n/);
119
+ const queries: SessionQrelsQuery[] = [];
120
+ const memories: SessionQrelsMemory[] = [];
121
+ const sessionCounters = new Map<string, number>();
122
+ const pendingBySession = new Map<string, PendingPrompt>();
123
+
124
+ for (const line of lines) {
125
+ if (options.maxQueries !== undefined && queries.length >= options.maxQueries) break;
126
+ const entry = parseEntry(line);
127
+ if (!entry) continue;
128
+
129
+ const sessionId = entry.sessionId || 'unknown-session';
130
+ const content = extractTextContent(entry.message?.content);
131
+ if (!content) continue;
132
+
133
+ if (entry.type === 'user') {
134
+ if (isWorthBenchmarkingPrompt(content)) {
135
+ const turnIndex = nextSessionCounter(sessionCounters, sessionId);
136
+ pendingBySession.set(sessionId, { sessionId, text: content, turnIndex, timestamp: entry.timestamp });
137
+ } else {
138
+ pendingBySession.delete(sessionId);
139
+ }
140
+ continue;
141
+ }
142
+
143
+ if (entry.type === 'assistant') {
144
+ const pending = pendingBySession.get(sessionId);
145
+ if (!pending) continue;
146
+
147
+ const answer = content.trim();
148
+ if (answer.length === 0) continue;
149
+ const idSuffix = `${pending.sessionId}-${pending.turnIndex}`;
150
+ const memoryId = `m-${idSuffix}`;
151
+ const queryId = `q-${idSuffix}`;
152
+ const memoryContent = options.redactContent ? `[redacted memory ${memoryId}]` : answer;
153
+ memories.push({
154
+ id: memoryId,
155
+ content: memoryContent,
156
+ sourceSessionId: pending.sessionId,
157
+ sourceTurnIndex: pending.turnIndex,
158
+ timestamp: entry.timestamp ?? pending.timestamp
159
+ });
160
+ queries.push({
161
+ queryId,
162
+ query: options.redactContent ? `[redacted query ${queryId}]` : pending.text,
163
+ expectedIds: [memoryId],
164
+ expectedRelevance: { [memoryId]: 2 },
165
+ sourceSessionId: pending.sessionId,
166
+ sourceTurnIndex: pending.turnIndex,
167
+ expectation: 'match',
168
+ knownAnswer: memoryContent
169
+ });
170
+ pendingBySession.delete(sessionId);
171
+ }
172
+ }
173
+
174
+ appendExplicitNoMatchQueries(queries, options);
175
+
176
+ const redactContent = options.redactContent === true;
177
+ const rawContentIncluded = options.rawContentIncluded ?? !redactContent;
178
+
179
+ return {
180
+ name: options.name ?? 'session-qrels-fixture',
181
+ description: options.description ?? 'Session-derived qrels fixture generated from Claude JSONL user/assistant turns.',
182
+ ks: options.ks ?? [1, 3, 5],
183
+ queries,
184
+ memories,
185
+ metadata: {
186
+ sourceFileCount: options.sourceFileCount,
187
+ rawContentIncluded,
188
+ generatedAt: options.generatedAt
189
+ }
190
+ };
191
+ }
192
+
193
+ export async function collectClaudeSessionJsonlFiles(
194
+ rootDir: string,
195
+ options: SessionQrelsFileCollectionOptions = {}
196
+ ): Promise<string[]> {
197
+ const maxFiles = options.maxFiles ?? Number.POSITIVE_INFINITY;
198
+ const minBytes = options.minBytes ?? 0;
199
+ const files: string[] = [];
200
+
201
+ async function walk(dir: string): Promise<void> {
202
+ if (files.length >= maxFiles) return;
203
+
204
+ let entries;
205
+ try {
206
+ entries = await readdir(dir, { withFileTypes: true });
207
+ } catch {
208
+ return;
209
+ }
210
+
211
+ entries.sort((a, b) => a.name.localeCompare(b.name));
212
+
213
+ for (const entry of entries) {
214
+ if (files.length >= maxFiles) return;
215
+ const fullPath = path.join(dir, entry.name);
216
+ if (entry.isDirectory()) {
217
+ if (!options.includeSubagents && entry.name.toLowerCase() === 'subagents') continue;
218
+ await walk(fullPath);
219
+ continue;
220
+ }
221
+
222
+ if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;
223
+ if (minBytes > 0) {
224
+ try {
225
+ const info = await stat(fullPath);
226
+ if (info.size < minBytes) continue;
227
+ } catch {
228
+ continue;
229
+ }
230
+ }
231
+ files.push(fullPath);
232
+ }
233
+ }
234
+
235
+ await walk(rootDir);
236
+ return files;
237
+ }
238
+
239
+ export function summarizeSessionQrelsFixture(fixture: SessionQrelsFixture): SessionQrelsSummary {
240
+ const bySession = new Map<string, SessionQrelsPerSessionSummary>();
241
+
242
+ function ensureSession(sourceSessionId: string, turnIndex: number): SessionQrelsPerSessionSummary {
243
+ const existing = bySession.get(sourceSessionId);
244
+ if (existing) {
245
+ existing.firstTurnIndex = Math.min(existing.firstTurnIndex, turnIndex);
246
+ existing.lastTurnIndex = Math.max(existing.lastTurnIndex, turnIndex);
247
+ return existing;
248
+ }
249
+ const created: SessionQrelsPerSessionSummary = {
250
+ sourceSessionId,
251
+ queryCount: 0,
252
+ memoryCount: 0,
253
+ firstTurnIndex: turnIndex,
254
+ lastTurnIndex: turnIndex
255
+ };
256
+ bySession.set(sourceSessionId, created);
257
+ return created;
258
+ }
259
+
260
+ for (const query of fixture.queries) {
261
+ ensureSession(query.sourceSessionId, query.sourceTurnIndex).queryCount += 1;
262
+ }
263
+ for (const memory of fixture.memories) {
264
+ ensureSession(memory.sourceSessionId, memory.sourceTurnIndex).memoryCount += 1;
265
+ }
266
+
267
+ const perSession = Array.from(bySession.values()).sort((a, b) => a.sourceSessionId.localeCompare(b.sourceSessionId));
268
+
269
+ const positiveQueryCount = fixture.queries.filter((query) => getQueryExpectation(query) === 'match').length;
270
+ const noMatchQueryCount = fixture.queries.filter((query) => getQueryExpectation(query) === 'no_match').length;
271
+ const knownAnswerCount = fixture.queries.filter((query) => typeof query.knownAnswer === 'string' && query.knownAnswer.length > 0).length;
272
+
273
+ return {
274
+ name: fixture.name,
275
+ description: fixture.description,
276
+ ks: fixture.ks,
277
+ queryCount: fixture.queries.length,
278
+ positiveQueryCount,
279
+ noMatchQueryCount,
280
+ knownAnswerCount,
281
+ memoryCount: fixture.memories.length,
282
+ sourceSessionCount: perSession.length,
283
+ sourceFileCount: fixture.metadata?.sourceFileCount,
284
+ rawContentIncluded: fixture.metadata?.rawContentIncluded ?? true,
285
+ perSession
286
+ };
287
+ }
288
+
289
+ function appendExplicitNoMatchQueries(
290
+ queries: SessionQrelsQuery[],
291
+ options: SessionQrelsOptions
292
+ ): void {
293
+ const inputs = options.noMatchQueries ?? [];
294
+ if (inputs.length === 0) return;
295
+
296
+ const remaining = options.maxQueries === undefined
297
+ ? Number.POSITIVE_INFINITY
298
+ : Math.max(0, options.maxQueries - queries.length);
299
+
300
+ const startIndex = queries.length;
301
+ inputs.slice(0, remaining).forEach((input, index) => {
302
+ const queryId = input.queryId ?? `q-no-match-${startIndex + index + 1}`;
303
+ queries.push({
304
+ queryId,
305
+ query: options.redactContent ? `[redacted query ${queryId}]` : input.query,
306
+ expectedIds: [],
307
+ expectedRelevance: {},
308
+ sourceSessionId: input.sourceSessionId ?? 'no-match',
309
+ sourceTurnIndex: input.sourceTurnIndex ?? 0,
310
+ expectation: 'no_match',
311
+ forbiddenIds: [...(input.forbiddenIds ?? [])]
312
+ });
313
+ });
314
+ }
315
+
316
+ function getQueryExpectation(query: SessionQrelsQuery): SessionQrelsExpectation {
317
+ return query.expectation ?? (query.expectedIds.length === 0 ? 'no_match' : 'match');
318
+ }
319
+
320
+ function parseEntry(line: string): ClaudeJsonlEntry | null {
321
+ const trimmed = line.trim();
322
+ if (!trimmed) return null;
323
+ try {
324
+ return JSON.parse(trimmed) as ClaudeJsonlEntry;
325
+ } catch {
326
+ return null;
327
+ }
328
+ }
329
+
330
+ function extractTextContent(content: ClaudeMessageContent | undefined): string | null {
331
+ if (!content) return null;
332
+ if (typeof content === 'string') return content.trim();
333
+ const texts = content
334
+ .filter((block) => block.type === 'text' && block.text)
335
+ .map((block) => block.text?.trim() ?? '')
336
+ .filter(Boolean);
337
+ return texts.length > 0 ? texts.join('\n') : null;
338
+ }
339
+
340
+ function isWorthBenchmarkingPrompt(content: string): boolean {
341
+ const trimmed = content.trim();
342
+ if (isClaudeLocalCommandArtifact(trimmed)) return false;
343
+ if (trimmed.startsWith('/')) return false;
344
+ if (trimmed.length < 15) return false;
345
+ return /[a-zA-Z가-힣]{2,}/.test(trimmed);
346
+ }
347
+
348
+ function isClaudeLocalCommandArtifact(content: string): boolean {
349
+ return (
350
+ /^<local-command-(stdout|stderr)>/.test(content) ||
351
+ /^<command-(name|message)>/.test(content) ||
352
+ (content.includes('<command-name>') && content.includes('<local-command-stdout>'))
353
+ );
354
+ }
355
+
356
+ function nextSessionCounter(counters: Map<string, number>, sessionId: string): number {
357
+ const next = (counters.get(sessionId) ?? 0) + 1;
358
+ counters.set(sessionId, next);
359
+ return next;
360
+ }
@@ -14,7 +14,7 @@ export class SharedEventStore {
14
14
  private db: Database;
15
15
  private initialized = false;
16
16
 
17
- constructor(private dbPath: string) {
17
+ constructor(dbPath: string) {
18
18
  this.db = createDatabase(dbPath);
19
19
  }
20
20
 
@@ -36,7 +36,7 @@ export class SQLiteEventStore {
36
36
  private readonly readOnly: boolean;
37
37
  private readonly markdownMirror: MarkdownMirror | null;
38
38
 
39
- constructor(private dbPath: string, options?: SQLiteEventStoreOptions) {
39
+ constructor(dbPath: string, options?: SQLiteEventStoreOptions) {
40
40
  this.readOnly = options?.readonly ?? false;
41
41
  this.db = createSQLiteDatabase(dbPath, {
42
42
  readonly: this.readOnly,
@@ -346,8 +346,7 @@ export class SQLiteEventStore {
346
346
  CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(
347
347
  content,
348
348
  event_id UNINDEXED,
349
- content='events',
350
- content_rowid='rowid'
349
+ tokenize='porter unicode61'
351
350
  );
352
351
 
353
352
  -- Triggers to keep FTS in sync with events table
@@ -356,11 +355,11 @@ export class SQLiteEventStore {
356
355
  END;
357
356
 
358
357
  CREATE TRIGGER IF NOT EXISTS events_fts_delete AFTER DELETE ON events BEGIN
359
- INSERT INTO events_fts(events_fts, rowid, content, event_id) VALUES('delete', OLD.rowid, OLD.content, OLD.id);
358
+ DELETE FROM events_fts WHERE rowid = OLD.rowid;
360
359
  END;
361
360
 
362
361
  CREATE TRIGGER IF NOT EXISTS events_fts_update AFTER UPDATE ON events BEGIN
363
- INSERT INTO events_fts(events_fts, rowid, content, event_id) VALUES('delete', OLD.rowid, OLD.content, OLD.id);
362
+ DELETE FROM events_fts WHERE rowid = OLD.rowid;
364
363
  INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
365
364
  END;
366
365
  `);
@@ -1422,11 +1421,36 @@ export class SQLiteEventStore {
1422
1421
  const countRow = sqliteGet<{count: number}>(this.db, 'SELECT COUNT(*) as count FROM events', []);
1423
1422
  const totalEvents = countRow?.count ?? 0;
1424
1423
 
1425
- // Clear and rebuild FTS index
1424
+ // Clear and rebuild FTS index. Recreate the virtual table instead of
1425
+ // issuing DELETE against it: older migrated FTS5 tables/triggers can fail
1426
+ // with `no such column: T.event_id` when processing synthetic deletes.
1426
1427
  sqliteExec(this.db, `
1427
- DELETE FROM events_fts;
1428
+ DROP TRIGGER IF EXISTS events_fts_insert;
1429
+ DROP TRIGGER IF EXISTS events_fts_delete;
1430
+ DROP TRIGGER IF EXISTS events_fts_update;
1431
+ DROP TABLE IF EXISTS events_fts;
1432
+
1433
+ CREATE VIRTUAL TABLE events_fts USING fts5(
1434
+ content,
1435
+ event_id UNINDEXED,
1436
+ tokenize='porter unicode61'
1437
+ );
1438
+
1428
1439
  INSERT INTO events_fts(rowid, content, event_id)
1429
1440
  SELECT rowid, content, id FROM events;
1441
+
1442
+ CREATE TRIGGER events_fts_insert AFTER INSERT ON events BEGIN
1443
+ INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
1444
+ END;
1445
+
1446
+ CREATE TRIGGER events_fts_delete AFTER DELETE ON events BEGIN
1447
+ DELETE FROM events_fts WHERE rowid = OLD.rowid;
1448
+ END;
1449
+
1450
+ CREATE TRIGGER events_fts_update AFTER UPDATE ON events BEGIN
1451
+ DELETE FROM events_fts WHERE rowid = OLD.rowid;
1452
+ INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
1453
+ END;
1430
1454
  `);
1431
1455
 
1432
1456
  return totalEvents;
@@ -1773,14 +1797,14 @@ export class SQLiteEventStore {
1773
1797
 
1774
1798
  // Recreate triggers
1775
1799
  sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_insert AFTER INSERT ON events BEGIN
1776
- INSERT INTO events_fts(rowid, content) VALUES (NEW.rowid, NEW.content);
1800
+ INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
1777
1801
  END`);
1778
1802
  sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_delete AFTER DELETE ON events BEGIN
1779
- INSERT INTO events_fts(events_fts, rowid, content) VALUES('delete', OLD.rowid, OLD.content);
1803
+ DELETE FROM events_fts WHERE rowid = OLD.rowid;
1780
1804
  END`);
1781
1805
  sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_update AFTER UPDATE ON events BEGIN
1782
- INSERT INTO events_fts(events_fts, rowid, content) VALUES('delete', OLD.rowid, OLD.content);
1783
- INSERT INTO events_fts(rowid, content) VALUES (NEW.rowid, NEW.content);
1806
+ DELETE FROM events_fts WHERE rowid = OLD.rowid;
1807
+ INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
1784
1808
  END`);
1785
1809
  } catch {
1786
1810
  // FTS rebuild failed - non-critical, will be rebuilt on next initialize
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { dbRun, dbAll, type Database } from '../db-wrapper.js';
7
7
  import { randomUUID } from 'crypto';
8
- import type { BlockerRef, BlockerKind, Entity } from '../types.js';
8
+ import type { BlockerRef, Entity } from '../types.js';
9
9
  import { makeEntityCanonicalKey, makeArtifactKey } from '../canonical-key.js';
10
10
  import { TaskMatcher } from './task-matcher.js';
11
11
 
@@ -39,7 +39,7 @@ export class BlockerResolver {
39
39
  */
40
40
  async resolveBlocker(
41
41
  text: string,
42
- sourceEntryId?: string
42
+ _sourceEntryId?: string
43
43
  ): Promise<BlockerRef> {
44
44
  const trimmedText = text.trim();
45
45
 
@@ -9,7 +9,6 @@ import type {
9
9
  Entity,
10
10
  TaskStatus,
11
11
  TaskPriority,
12
- BlockerRef,
13
12
  BlockerMode
14
13
  } from '../types.js';
15
14
  import { makeEntityCanonicalKey, makeTaskEventDedupeKey } from '../canonical-key.js';
@@ -8,8 +8,7 @@ import { randomUUID } from 'crypto';
8
8
  import type {
9
9
  OutboxJob,
10
10
  OutboxStatus,
11
- OutboxItemKind,
12
- VALID_OUTBOX_TRANSITIONS
11
+ OutboxItemKind
13
12
  } from './types.js';
14
13
 
15
14
  export interface OutboxConfig {
@@ -191,14 +190,6 @@ export class VectorOutbox {
191
190
  [now.toISOString(), this.config.maxRetries]
192
191
  );
193
192
 
194
- // Get counts (DuckDB doesn't return affected rows easily)
195
- const recoveredRows = await dbAll<{ count: number }>(
196
- this.db,
197
- `SELECT COUNT(*) as count FROM vector_outbox
198
- WHERE status = 'pending' AND updated_at = ?`,
199
- [now.toISOString()]
200
- );
201
-
202
193
  return {
203
194
  recovered: 0, // Approximate
204
195
  retried: 0 // Approximate
@@ -6,7 +6,7 @@
6
6
  import { EventStore } from './event-store.js';
7
7
  import { VectorStore } from './vector-store.js';
8
8
  import { Embedder } from './embedder.js';
9
- import type { OutboxItem, VectorRecord } from './types.js';
9
+ import type { VectorRecord } from './types.js';
10
10
 
11
11
  export interface WorkerConfig {
12
12
  batchSize: number;