hippo-memory 1.59.0 → 1.61.0

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 (596) hide show
  1. package/README.md +1 -1
  2. package/dist/ablation.js +9 -27
  3. package/dist/agent-memories/apply.js +4 -1
  4. package/dist/agent-memories/claude-code.js +1 -1
  5. package/dist/agent-memories/codex.js +1 -1
  6. package/dist/agent-memories/gemini.js +1 -1
  7. package/dist/agent-memories/legacy.js +1 -1
  8. package/dist/agent-memories/source.js +1 -1
  9. package/dist/agent-memories/sync.js +8 -3
  10. package/dist/ambient-store.d.ts +14 -0
  11. package/dist/ambient-store.js +90 -0
  12. package/dist/ambient.d.ts +23 -0
  13. package/dist/ambient.js +72 -50
  14. package/dist/api/assemble.d.ts +93 -0
  15. package/dist/api/assemble.js +152 -0
  16. package/dist/api/audit.d.ts +17 -0
  17. package/dist/api/audit.js +23 -0
  18. package/dist/api/auth.d.ts +79 -0
  19. package/dist/api/auth.js +178 -0
  20. package/dist/api/context-types.d.ts +105 -0
  21. package/dist/api/context-types.js +3 -0
  22. package/dist/api/context.d.ts +31 -0
  23. package/dist/api/context.js +705 -0
  24. package/dist/api/dormant.d.ts +30 -0
  25. package/dist/api/dormant.js +140 -0
  26. package/dist/api/drill-down.d.ts +84 -0
  27. package/dist/api/drill-down.js +123 -0
  28. package/dist/api/forget.d.ts +57 -0
  29. package/dist/api/forget.js +87 -0
  30. package/dist/api/goals.d.ts +18 -0
  31. package/dist/api/goals.js +33 -0
  32. package/dist/api/learn.d.ts +31 -0
  33. package/dist/api/learn.js +88 -0
  34. package/dist/api/outcome.d.ts +61 -0
  35. package/dist/api/outcome.js +66 -0
  36. package/dist/api/promote.d.ts +54 -0
  37. package/dist/api/promote.js +203 -0
  38. package/dist/api/quarantine.d.ts +24 -0
  39. package/dist/api/quarantine.js +121 -0
  40. package/dist/api/recall-types.d.ts +390 -0
  41. package/dist/api/recall-types.js +3 -0
  42. package/dist/api/recall.d.ts +36 -0
  43. package/dist/api/recall.js +634 -0
  44. package/dist/api/remember.d.ts +35 -0
  45. package/dist/api/remember.js +43 -0
  46. package/dist/api/sleep.d.ts +136 -0
  47. package/dist/api/sleep.js +271 -0
  48. package/dist/api/tokens.d.ts +26 -0
  49. package/dist/api/tokens.js +60 -0
  50. package/dist/api/types.d.ts +56 -0
  51. package/dist/api/types.js +38 -0
  52. package/dist/api.d.ts +20 -1260
  53. package/dist/api.js +25 -2711
  54. package/dist/audit.d.ts +3 -0
  55. package/dist/audit.js +7 -3
  56. package/dist/auth.d.ts +45 -4
  57. package/dist/auth.js +125 -48
  58. package/dist/autolearn.js +7 -4
  59. package/dist/capture/command.d.ts +33 -0
  60. package/dist/capture/command.js +264 -0
  61. package/dist/capture/compact.d.ts +44 -0
  62. package/dist/capture/compact.js +354 -0
  63. package/dist/capture/extract.d.ts +21 -0
  64. package/dist/capture/extract.js +464 -0
  65. package/dist/capture/transcript.d.ts +40 -0
  66. package/dist/capture/transcript.js +193 -0
  67. package/dist/capture-error.js +2 -1
  68. package/dist/churn-git.js +4 -2
  69. package/dist/cli/audit.d.ts +3 -0
  70. package/dist/cli/audit.js +159 -0
  71. package/dist/cli/auth.d.ts +2 -0
  72. package/dist/cli/auth.js +171 -0
  73. package/dist/cli/briefs.d.ts +4 -0
  74. package/dist/cli/briefs.js +435 -0
  75. package/dist/cli/card.d.ts +3 -0
  76. package/dist/cli/card.js +333 -0
  77. package/dist/cli/context.d.ts +15 -0
  78. package/dist/cli/context.js +366 -0
  79. package/dist/cli/continuity.d.ts +6 -0
  80. package/dist/cli/continuity.js +445 -0
  81. package/dist/cli/curate.d.ts +19 -0
  82. package/dist/cli/curate.js +555 -0
  83. package/dist/cli/dag.d.ts +5 -0
  84. package/dist/cli/dag.js +177 -0
  85. package/dist/cli/decisions.d.ts +4 -0
  86. package/dist/cli/decisions.js +528 -0
  87. package/dist/cli/eval.d.ts +5 -0
  88. package/dist/cli/eval.js +213 -0
  89. package/dist/cli/explain.d.ts +4 -0
  90. package/dist/cli/explain.js +150 -0
  91. package/dist/cli/goals.d.ts +2 -0
  92. package/dist/cli/goals.js +196 -0
  93. package/dist/cli/hook-blocks.d.ts +18 -0
  94. package/dist/cli/hook-blocks.js +233 -0
  95. package/dist/cli/init.d.ts +2 -0
  96. package/dist/cli/init.js +305 -0
  97. package/dist/cli/maintenance.d.ts +4 -0
  98. package/dist/cli/maintenance.js +179 -0
  99. package/dist/cli/playbooks.d.ts +4 -0
  100. package/dist/cli/playbooks.js +556 -0
  101. package/dist/cli/projects.js +1 -1
  102. package/dist/cli/recall.d.ts +7 -0
  103. package/dist/cli/recall.js +597 -0
  104. package/dist/cli/remember.d.ts +5 -0
  105. package/dist/cli/remember.js +442 -0
  106. package/dist/cli/serve.d.ts +5 -0
  107. package/dist/cli/serve.js +40 -0
  108. package/dist/cli/session-hooks.d.ts +29 -0
  109. package/dist/cli/session-hooks.js +637 -0
  110. package/dist/cli/setup.d.ts +4 -0
  111. package/dist/cli/setup.js +376 -0
  112. package/dist/cli/shared.d.ts +15 -19
  113. package/dist/cli/shared.js +50 -341
  114. package/dist/cli/slack.d.ts +2 -0
  115. package/dist/cli/slack.js +171 -0
  116. package/dist/cli/status.d.ts +18 -0
  117. package/dist/cli/status.js +400 -0
  118. package/dist/cli/transfer.d.ts +10 -0
  119. package/dist/cli/transfer.js +438 -0
  120. package/dist/cli/usage.d.ts +85 -0
  121. package/dist/cli/usage.js +741 -0
  122. package/dist/cli.d.ts +319 -13
  123. package/dist/cli.js +436 -8580
  124. package/dist/client.js +15 -8
  125. package/dist/compaction-record.js +12 -9
  126. package/dist/config.js +12 -11
  127. package/dist/connectors/github/backfill.js +94 -87
  128. package/dist/connectors/github/cli-impl.js +5 -3
  129. package/dist/connectors/github/dlq.js +67 -54
  130. package/dist/connectors/github/ingest.js +34 -35
  131. package/dist/connectors/github/tenant-routing.js +3 -2
  132. package/dist/connectors/github/webhook.js +135 -216
  133. package/dist/connectors/slack/dlq.js +49 -61
  134. package/dist/connectors/slack/ingest.js +55 -48
  135. package/dist/connectors/slack/tenant-routing.js +4 -3
  136. package/dist/connectors/slack/webhook.js +72 -74
  137. package/dist/consolidate/conflicts.d.ts +10 -0
  138. package/dist/consolidate/conflicts.js +178 -0
  139. package/dist/consolidate/decay.d.ts +12 -0
  140. package/dist/consolidate/decay.js +145 -0
  141. package/dist/consolidate/llm-passes.d.ts +3 -0
  142. package/dist/consolidate/llm-passes.js +141 -0
  143. package/dist/consolidate/merge.d.ts +7 -0
  144. package/dist/consolidate/merge.js +251 -0
  145. package/dist/consolidate/physics-pass.d.ts +3 -0
  146. package/dist/consolidate/physics-pass.js +60 -0
  147. package/dist/consolidate/run.d.ts +69 -0
  148. package/dist/consolidate/run.js +76 -0
  149. package/dist/consolidate/sleep.d.ts +18 -0
  150. package/dist/consolidate/sleep.js +209 -0
  151. package/dist/consolidate/traces.d.ts +4 -0
  152. package/dist/consolidate/traces.js +178 -0
  153. package/dist/context-auto.js +7 -11
  154. package/dist/context-render.d.ts +1 -1
  155. package/dist/context-render.js +1 -1
  156. package/dist/customer-notes.d.ts +3 -0
  157. package/dist/customer-notes.js +6 -4
  158. package/dist/dag.js +13 -12
  159. package/dist/dashboard-actions.d.ts +20 -0
  160. package/dist/dashboard-actions.js +88 -0
  161. package/dist/dashboard-params.d.ts +45 -0
  162. package/dist/dashboard-params.js +127 -0
  163. package/dist/dashboard-queries.d.ts +15 -0
  164. package/dist/dashboard-queries.js +355 -0
  165. package/dist/dashboard-snapshot.d.ts +138 -0
  166. package/dist/dashboard-snapshot.js +308 -0
  167. package/dist/dashboard-types.d.ts +163 -0
  168. package/dist/dashboard-types.js +3 -0
  169. package/dist/dashboard.d.ts +6 -7
  170. package/dist/dashboard.js +231 -203
  171. package/dist/db/busy.d.ts +5 -0
  172. package/dist/db/busy.js +24 -0
  173. package/dist/db/continuity.d.ts +5 -0
  174. package/dist/db/continuity.js +145 -0
  175. package/dist/db/meta.d.ts +8 -0
  176. package/dist/db/meta.js +35 -0
  177. package/dist/db/migrate.d.ts +9 -0
  178. package/dist/db/migrate.js +138 -0
  179. package/dist/db/migrations/index.d.ts +5 -0
  180. package/dist/db/migrations/index.js +109 -0
  181. package/dist/db/migrations/types.d.ts +14 -0
  182. package/dist/db/migrations/types.js +2 -0
  183. package/dist/db/migrations/v01.d.ts +3 -0
  184. package/dist/db/migrations/v01.js +38 -0
  185. package/dist/db/migrations/v02.d.ts +3 -0
  186. package/dist/db/migrations/v02.js +21 -0
  187. package/dist/db/migrations/v03.d.ts +3 -0
  188. package/dist/db/migrations/v03.js +22 -0
  189. package/dist/db/migrations/v04.d.ts +3 -0
  190. package/dist/db/migrations/v04.js +28 -0
  191. package/dist/db/migrations/v05.d.ts +3 -0
  192. package/dist/db/migrations/v05.js +21 -0
  193. package/dist/db/migrations/v06.d.ts +3 -0
  194. package/dist/db/migrations/v06.js +25 -0
  195. package/dist/db/migrations/v07.d.ts +3 -0
  196. package/dist/db/migrations/v07.js +13 -0
  197. package/dist/db/migrations/v08.d.ts +3 -0
  198. package/dist/db/migrations/v08.js +8 -0
  199. package/dist/db/migrations/v09.d.ts +3 -0
  200. package/dist/db/migrations/v09.js +13 -0
  201. package/dist/db/migrations/v10.d.ts +3 -0
  202. package/dist/db/migrations/v10.js +17 -0
  203. package/dist/db/migrations/v11.d.ts +3 -0
  204. package/dist/db/migrations/v11.js +15 -0
  205. package/dist/db/migrations/v12.d.ts +3 -0
  206. package/dist/db/migrations/v12.js +11 -0
  207. package/dist/db/migrations/v13.d.ts +3 -0
  208. package/dist/db/migrations/v13.js +15 -0
  209. package/dist/db/migrations/v14.d.ts +3 -0
  210. package/dist/db/migrations/v14.js +66 -0
  211. package/dist/db/migrations/v15.d.ts +3 -0
  212. package/dist/db/migrations/v15.js +42 -0
  213. package/dist/db/migrations/v16.d.ts +3 -0
  214. package/dist/db/migrations/v16.js +61 -0
  215. package/dist/db/migrations/v17.d.ts +3 -0
  216. package/dist/db/migrations/v17.js +46 -0
  217. package/dist/db/migrations/v18.d.ts +3 -0
  218. package/dist/db/migrations/v18.js +60 -0
  219. package/dist/db/migrations/v19.d.ts +3 -0
  220. package/dist/db/migrations/v19.js +28 -0
  221. package/dist/db/migrations/v20.d.ts +3 -0
  222. package/dist/db/migrations/v20.js +42 -0
  223. package/dist/db/migrations/v21.d.ts +3 -0
  224. package/dist/db/migrations/v21.js +16 -0
  225. package/dist/db/migrations/v22.d.ts +3 -0
  226. package/dist/db/migrations/v22.js +82 -0
  227. package/dist/db/migrations/v23.d.ts +3 -0
  228. package/dist/db/migrations/v23.js +48 -0
  229. package/dist/db/migrations/v24.d.ts +3 -0
  230. package/dist/db/migrations/v24.js +72 -0
  231. package/dist/db/migrations/v25.d.ts +3 -0
  232. package/dist/db/migrations/v25.js +46 -0
  233. package/dist/db/migrations/v26.d.ts +3 -0
  234. package/dist/db/migrations/v26.js +23 -0
  235. package/dist/db/migrations/v27.d.ts +3 -0
  236. package/dist/db/migrations/v27.js +57 -0
  237. package/dist/db/migrations/v28.d.ts +3 -0
  238. package/dist/db/migrations/v28.js +38 -0
  239. package/dist/db/migrations/v29.d.ts +3 -0
  240. package/dist/db/migrations/v29.js +78 -0
  241. package/dist/db/migrations/v30.d.ts +3 -0
  242. package/dist/db/migrations/v30.js +92 -0
  243. package/dist/db/migrations/v31.d.ts +3 -0
  244. package/dist/db/migrations/v31.js +74 -0
  245. package/dist/db/migrations/v32.d.ts +3 -0
  246. package/dist/db/migrations/v32.js +102 -0
  247. package/dist/db/migrations/v33.d.ts +3 -0
  248. package/dist/db/migrations/v33.js +104 -0
  249. package/dist/db/migrations/v34.d.ts +3 -0
  250. package/dist/db/migrations/v34.js +93 -0
  251. package/dist/db/migrations/v35.d.ts +3 -0
  252. package/dist/db/migrations/v35.js +98 -0
  253. package/dist/db/migrations/v36.d.ts +3 -0
  254. package/dist/db/migrations/v36.js +98 -0
  255. package/dist/db/migrations/v37.d.ts +3 -0
  256. package/dist/db/migrations/v37.js +219 -0
  257. package/dist/db/migrations/v38.d.ts +3 -0
  258. package/dist/db/migrations/v38.js +277 -0
  259. package/dist/db/migrations/v39.d.ts +3 -0
  260. package/dist/db/migrations/v39.js +59 -0
  261. package/dist/db/migrations/v40.d.ts +3 -0
  262. package/dist/db/migrations/v40.js +74 -0
  263. package/dist/db/migrations/v41.d.ts +3 -0
  264. package/dist/db/migrations/v41.js +43 -0
  265. package/dist/db/migrations/v42.d.ts +3 -0
  266. package/dist/db/migrations/v42.js +41 -0
  267. package/dist/db/migrations/v43.d.ts +3 -0
  268. package/dist/db/migrations/v43.js +67 -0
  269. package/dist/db/migrations/v44.d.ts +3 -0
  270. package/dist/db/migrations/v44.js +28 -0
  271. package/dist/db/migrations/v45.d.ts +3 -0
  272. package/dist/db/migrations/v45.js +30 -0
  273. package/dist/db/migrations/v46.d.ts +3 -0
  274. package/dist/db/migrations/v46.js +25 -0
  275. package/dist/db/migrations/v47.d.ts +3 -0
  276. package/dist/db/migrations/v47.js +17 -0
  277. package/dist/db/migrations/v48.d.ts +3 -0
  278. package/dist/db/migrations/v48.js +10 -0
  279. package/dist/db/migrations/v49.d.ts +3 -0
  280. package/dist/db/migrations/v49.js +31 -0
  281. package/dist/db/migrations/v50.d.ts +3 -0
  282. package/dist/db/migrations/v50.js +67 -0
  283. package/dist/db/migrations/v51.d.ts +3 -0
  284. package/dist/db/migrations/v51.js +14 -0
  285. package/dist/db/migrations/v52.d.ts +3 -0
  286. package/dist/db/migrations/v52.js +7 -0
  287. package/dist/db/open.d.ts +23 -0
  288. package/dist/db/open.js +146 -0
  289. package/dist/db/sqlite.d.ts +21 -0
  290. package/dist/db/sqlite.js +8 -0
  291. package/dist/db/tables.d.ts +7 -0
  292. package/dist/db/tables.js +38 -0
  293. package/dist/db.d.ts +6 -46
  294. package/dist/db.js +5 -3036
  295. package/dist/decisions.d.ts +4 -1
  296. package/dist/decisions.js +9 -7
  297. package/dist/dedupe.js +3 -2
  298. package/dist/delivery-recorder.js +5 -1
  299. package/dist/doctor.js +4 -3
  300. package/dist/dormant.js +1 -0
  301. package/dist/embedding-provider.d.ts +1 -1
  302. package/dist/embedding-provider.js +7 -5
  303. package/dist/embeddings.d.ts +9 -52
  304. package/dist/embeddings.js +47 -298
  305. package/dist/env.d.ts +75 -0
  306. package/dist/env.js +119 -0
  307. package/dist/eval-suite.js +1 -1
  308. package/dist/eval.js +2 -2
  309. package/dist/extract.js +5 -4
  310. package/dist/gated-write.js +3 -1
  311. package/dist/goals.d.ts +3 -1
  312. package/dist/goals.js +18 -0
  313. package/dist/graph/read.d.ts +73 -0
  314. package/dist/graph/read.js +325 -0
  315. package/dist/graph/rows.d.ts +45 -0
  316. package/dist/graph/rows.js +51 -0
  317. package/dist/graph/types.d.ts +83 -0
  318. package/dist/graph/types.js +11 -0
  319. package/dist/graph/write.d.ts +93 -0
  320. package/dist/{graph.js → graph/write.js} +8 -391
  321. package/dist/graph-extract.js +2 -1
  322. package/dist/graph-recall.d.ts +1 -1
  323. package/dist/graph-recall.js +2 -2
  324. package/dist/graph-stream.js +1 -1
  325. package/dist/graph-view.d.ts +1 -1
  326. package/dist/graph-view.js +1 -1
  327. package/dist/half-life-migration.d.ts +1 -1
  328. package/dist/half-life-migration.js +2 -1
  329. package/dist/handoff.js +3 -0
  330. package/dist/hooks/codex-session.d.ts +8 -0
  331. package/dist/hooks/codex-session.js +76 -0
  332. package/dist/hooks/codex-wrapper.d.ts +55 -0
  333. package/dist/hooks/codex-wrapper.js +288 -0
  334. package/dist/hooks/json-hooks.d.ts +63 -0
  335. package/dist/hooks/json-hooks.js +356 -0
  336. package/dist/hooks/opencode.d.ts +50 -0
  337. package/dist/hooks/opencode.js +202 -0
  338. package/dist/hooks/shared.d.ts +54 -0
  339. package/dist/hooks/shared.js +77 -0
  340. package/dist/http-retry.d.ts +2 -0
  341. package/dist/http-retry.js +4 -3
  342. package/dist/http-util.d.ts +3 -0
  343. package/dist/http-util.js +10 -0
  344. package/dist/{importers.d.ts → importers/core.d.ts} +13 -17
  345. package/dist/importers/core.js +141 -0
  346. package/dist/importers/markdown-parse.d.ts +41 -0
  347. package/dist/importers/markdown-parse.js +132 -0
  348. package/dist/importers/markdown.d.ts +3 -0
  349. package/dist/importers/markdown.js +92 -0
  350. package/dist/importers/sources.d.ts +7 -0
  351. package/dist/importers/sources.js +229 -0
  352. package/dist/importers/vault.d.ts +11 -0
  353. package/dist/importers/vault.js +352 -0
  354. package/dist/incidents.d.ts +3 -0
  355. package/dist/incidents.js +8 -5
  356. package/dist/index.d.ts +25 -6
  357. package/dist/index.js +23 -6
  358. package/dist/invalidation.js +2 -1
  359. package/dist/judgment.js +7 -3
  360. package/dist/keyset.d.ts +13 -0
  361. package/dist/keyset.js +8 -0
  362. package/dist/local-embedding.d.ts +13 -0
  363. package/dist/local-embedding.js +165 -0
  364. package/dist/log.d.ts +7 -0
  365. package/dist/log.js +19 -1
  366. package/dist/mcp/admin-tools.d.ts +8 -0
  367. package/dist/mcp/admin-tools.js +116 -0
  368. package/dist/mcp/format.d.ts +27 -0
  369. package/dist/mcp/format.js +135 -0
  370. package/dist/mcp/memory-tools.d.ts +5 -0
  371. package/dist/mcp/memory-tools.js +83 -0
  372. package/dist/mcp/protocol.d.ts +83 -0
  373. package/dist/mcp/protocol.js +55 -0
  374. package/dist/mcp/recall-tools.d.ts +6 -0
  375. package/dist/mcp/recall-tools.js +320 -0
  376. package/dist/mcp/request.d.ts +10 -0
  377. package/dist/mcp/request.js +163 -0
  378. package/dist/mcp/server.d.ts +4 -70
  379. package/dist/mcp/server.js +8 -1172
  380. package/dist/mcp/session-state.d.ts +11 -0
  381. package/dist/mcp/session-state.js +28 -0
  382. package/dist/mcp/stdio.d.ts +8 -0
  383. package/dist/mcp/stdio.js +79 -0
  384. package/dist/mcp/tools.d.ts +11 -0
  385. package/dist/mcp/tools.js +247 -0
  386. package/dist/memory.d.ts +8 -3
  387. package/dist/memory.js +27 -1
  388. package/dist/multihop.d.ts +1 -1
  389. package/dist/multihop.js +2 -1
  390. package/dist/owner-validation.js +2 -1
  391. package/dist/physics-state.js +10 -7
  392. package/dist/policies.d.ts +3 -0
  393. package/dist/policies.js +8 -6
  394. package/dist/postinstall.js +3 -2
  395. package/dist/predictions/planning-fallacy.d.ts +100 -0
  396. package/dist/predictions/planning-fallacy.js +190 -0
  397. package/dist/{predictions.d.ts → predictions/store.d.ts} +7 -102
  398. package/dist/predictions/store.js +434 -0
  399. package/dist/processes.d.ts +3 -0
  400. package/dist/processes.js +8 -5
  401. package/dist/project-briefs.d.ts +3 -0
  402. package/dist/project-briefs.js +7 -5
  403. package/dist/project-identity.d.ts +1 -1
  404. package/dist/project-identity.js +12 -6
  405. package/dist/project-merge.js +3 -1
  406. package/dist/quarantine.d.ts +2 -1
  407. package/dist/quarantine.js +8 -5
  408. package/dist/raw-archive-mirror-cleanup.js +2 -1
  409. package/dist/raw-archive.js +1 -1
  410. package/dist/recall-history.js +3 -2
  411. package/dist/recall-pipeline.d.ts +2 -2
  412. package/dist/recall-pipeline.js +16 -8
  413. package/dist/recall-scope.js +1 -1
  414. package/dist/recall-trace.d.ts +3 -3
  415. package/dist/recall-trace.js +10 -14
  416. package/dist/refine-llm.js +20 -11
  417. package/dist/reject-flow.js +6 -1
  418. package/dist/rerankers/clef.d.ts +2 -2
  419. package/dist/rerankers/clef.js +72 -30
  420. package/dist/rerankers/cross-encoder.js +6 -4
  421. package/dist/rerankers/jev.d.ts +4 -2
  422. package/dist/rerankers/jev.js +24 -19
  423. package/dist/rerankers/llm.d.ts +4 -2
  424. package/dist/rerankers/llm.js +61 -41
  425. package/dist/rerankers/types.d.ts +1 -1
  426. package/dist/salience.js +1 -1
  427. package/dist/same-text.d.ts +2 -0
  428. package/dist/same-text.js +4 -0
  429. package/dist/scheduler.d.ts +1 -0
  430. package/dist/scheduler.js +26 -3
  431. package/dist/scope.js +4 -3
  432. package/dist/search/as-of.d.ts +10 -0
  433. package/dist/search/as-of.js +22 -0
  434. package/dist/search/bm25-search.d.ts +14 -0
  435. package/dist/search/bm25-search.js +43 -0
  436. package/dist/search/bm25.d.ts +15 -0
  437. package/dist/search/bm25.js +54 -0
  438. package/dist/search/boosts.d.ts +54 -0
  439. package/dist/search/boosts.js +94 -0
  440. package/dist/search/breakdown.d.ts +7 -0
  441. package/dist/search/breakdown.js +20 -0
  442. package/dist/search/explain.d.ts +25 -0
  443. package/dist/search/explain.js +31 -0
  444. package/dist/search/finalize.d.ts +8 -0
  445. package/dist/search/finalize.js +52 -0
  446. package/dist/search/fusion.d.ts +27 -0
  447. package/dist/search/fusion.js +42 -0
  448. package/dist/search/hybrid-score.d.ts +20 -0
  449. package/dist/search/hybrid-score.js +73 -0
  450. package/dist/search/hybrid.d.ts +46 -0
  451. package/dist/search/hybrid.js +64 -0
  452. package/dist/search/physics-search.d.ts +29 -0
  453. package/dist/search/physics-search.js +162 -0
  454. package/dist/search/rerank.d.ts +10 -0
  455. package/dist/search/rerank.js +72 -0
  456. package/dist/search/temporal.d.ts +15 -0
  457. package/dist/search/temporal.js +45 -0
  458. package/dist/search/types.d.ts +91 -0
  459. package/dist/search/types.js +2 -0
  460. package/dist/search/vector.d.ts +30 -0
  461. package/dist/search/vector.js +71 -0
  462. package/dist/secret-detect.d.ts +2 -0
  463. package/dist/secret-detect.js +3 -1
  464. package/dist/server/auth.d.ts +52 -0
  465. package/dist/server/auth.js +221 -0
  466. package/dist/server/client-ip.d.ts +25 -0
  467. package/dist/server/client-ip.js +92 -0
  468. package/dist/server/cursor.d.ts +23 -0
  469. package/dist/server/cursor.js +58 -0
  470. package/dist/server/lifecycle.d.ts +7 -0
  471. package/dist/server/lifecycle.js +29 -0
  472. package/dist/server/mcp-http.d.ts +5 -0
  473. package/dist/server/mcp-http.js +199 -0
  474. package/dist/server/request.d.ts +33 -0
  475. package/dist/server/request.js +103 -0
  476. package/dist/server/routes/admin.d.ts +9 -0
  477. package/dist/server/routes/admin.js +158 -0
  478. package/dist/server/routes/customer-notes.d.ts +7 -0
  479. package/dist/server/routes/customer-notes.js +112 -0
  480. package/dist/server/routes/decisions.d.ts +7 -0
  481. package/dist/server/routes/decisions.js +133 -0
  482. package/dist/server/routes/incidents.d.ts +7 -0
  483. package/dist/server/routes/incidents.js +126 -0
  484. package/dist/server/routes/memories.d.ts +10 -0
  485. package/dist/server/routes/memories.js +179 -0
  486. package/dist/server/routes/policies.d.ts +8 -0
  487. package/dist/server/routes/policies.js +151 -0
  488. package/dist/server/routes/predictions.d.ts +7 -0
  489. package/dist/server/routes/predictions.js +164 -0
  490. package/dist/server/routes/processes.d.ts +7 -0
  491. package/dist/server/routes/processes.js +161 -0
  492. package/dist/server/routes/project-briefs.d.ts +8 -0
  493. package/dist/server/routes/project-briefs.js +136 -0
  494. package/dist/server/routes/recall.d.ts +8 -0
  495. package/dist/server/routes/recall.js +340 -0
  496. package/dist/server/routes/skills.d.ts +8 -0
  497. package/dist/server/routes/skills.js +150 -0
  498. package/dist/server/types.d.ts +56 -0
  499. package/dist/server/types.js +2 -0
  500. package/dist/server/validation.d.ts +14 -0
  501. package/dist/server/validation.js +91 -0
  502. package/dist/server.d.ts +7 -61
  503. package/dist/server.js +92 -2368
  504. package/dist/session-digest.d.ts +1 -1
  505. package/dist/session-digest.js +9 -2
  506. package/dist/shared.d.ts +3 -1
  507. package/dist/shared.js +46 -40
  508. package/dist/skills.d.ts +3 -0
  509. package/dist/skills.js +7 -5
  510. package/dist/stdin.js +3 -2
  511. package/dist/store/audit-event.d.ts +19 -0
  512. package/dist/store/audit-event.js +33 -0
  513. package/dist/store/candidates.d.ts +42 -0
  514. package/dist/store/candidates.js +152 -0
  515. package/dist/store/conflicts.d.ts +44 -0
  516. package/dist/store/conflicts.js +444 -0
  517. package/dist/store/delete-and-batch.d.ts +63 -0
  518. package/dist/store/delete-and-batch.js +310 -0
  519. package/dist/store/entry-reads.d.ts +92 -0
  520. package/dist/store/entry-reads.js +255 -0
  521. package/dist/store/entry-row.d.ts +67 -0
  522. package/dist/store/entry-row.js +208 -0
  523. package/dist/store/entry-writes.d.ts +46 -0
  524. package/dist/store/entry-writes.js +148 -0
  525. package/dist/store/handoffs.d.ts +26 -0
  526. package/dist/store/handoffs.js +184 -0
  527. package/dist/store/index-and-stats.d.ts +52 -0
  528. package/dist/store/index-and-stats.js +214 -0
  529. package/dist/store/markdown.d.ts +10 -0
  530. package/dist/store/markdown.js +108 -0
  531. package/dist/store/mirrors.d.ts +49 -0
  532. package/dist/store/mirrors.js +312 -0
  533. package/dist/store/open.d.ts +15 -0
  534. package/dist/store/open.js +223 -0
  535. package/dist/store/rows.d.ts +178 -0
  536. package/dist/store/rows.js +158 -0
  537. package/dist/store/search-rows.d.ts +86 -0
  538. package/dist/store/search-rows.js +254 -0
  539. package/dist/store/sessions.d.ts +83 -0
  540. package/dist/store/sessions.js +272 -0
  541. package/dist/store/summaries.d.ts +94 -0
  542. package/dist/store/summaries.js +377 -0
  543. package/dist/store/tenant-lookup.d.ts +12 -0
  544. package/dist/store/tenant-lookup.js +16 -0
  545. package/dist/store-cards.js +2 -1
  546. package/dist/summary-dirty.d.ts +4 -0
  547. package/dist/summary-dirty.js +32 -0
  548. package/dist/support-bundle.js +4 -3
  549. package/dist/tenant.js +2 -2
  550. package/dist/token-ledger.js +1 -0
  551. package/dist/tokenize.d.ts +2 -0
  552. package/dist/tokenize.js +16 -0
  553. package/dist/transcript-tail.d.ts +7 -0
  554. package/dist/transcript-tail.js +48 -0
  555. package/dist/vector-store.d.ts +27 -0
  556. package/dist/vector-store.js +210 -0
  557. package/dist/version.d.ts +2 -2
  558. package/dist/version.js +2 -2
  559. package/dist/working-memory.js +1 -1
  560. package/dist/yaml.js +36 -11
  561. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  562. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  563. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  564. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  565. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  566. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  567. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  568. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  569. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  570. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  571. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  572. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  573. package/dist-ui/assets/index-DPN7cP19.js +33 -0
  574. package/dist-ui/assets/index-dFloKRVr.css +1 -0
  575. package/dist-ui/index.html +3 -25
  576. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  577. package/extensions/openclaw-plugin/package.json +1 -1
  578. package/openclaw.plugin.json +1 -1
  579. package/package.json +2 -1
  580. package/dist/capture.d.ts +0 -155
  581. package/dist/capture.js +0 -1293
  582. package/dist/consolidate.d.ts +0 -58
  583. package/dist/consolidate.js +0 -1123
  584. package/dist/graph.d.ts +0 -245
  585. package/dist/hooks.d.ts +0 -208
  586. package/dist/hooks.js +0 -1073
  587. package/dist/importers.js +0 -897
  588. package/dist/predictions.js +0 -620
  589. package/dist/search.d.ts +0 -320
  590. package/dist/search.js +0 -968
  591. package/dist/store.d.ts +0 -744
  592. package/dist/store.js +0 -3372
  593. package/dist-ui/assets/d3-BiWEKnn4.js +0 -1
  594. package/dist-ui/assets/index-BhT8RvO6.js +0 -61
  595. package/dist-ui/assets/index-RoXXJ5dq.css +0 -1
  596. package/dist-ui/assets/three-BDgTxR1l.js +0 -4112
package/dist/search.js DELETED
@@ -1,968 +0,0 @@
1
- /**
2
- * BM25 search + optional embedding hybrid search for Hippo.
3
- * Zero external dependencies when embeddings are not available.
4
- */
5
- import { estimateTokens } from './token-ledger.js';
6
- import { calculateStrength, CHURN_STALE_TAG } from './memory.js';
7
- import { tokenize } from './tokenize.js';
8
- import { isOutcomeFastAblated, isRecallBoostAblated, isRecencyAblated, evalRecencyScaleDays, evalNow } from './ablation.js';
9
- import { extractPathTags, pathBoostMultiplier } from './path-context.js';
10
- import { detectScope, scopeMatch } from './scope.js';
11
- import { cosineSimilarity, embeddingModelRequiresReindex, loadEmbeddingIndex, } from './embeddings.js';
12
- import { resolveEmbeddingProvider } from './embedding-provider.js';
13
- import { physicsScore as computePhysicsScores, computeMass } from './physics.js';
14
- import { DEFAULT_PHYSICS_CONFIG } from './physics-config.js';
15
- import { loadPhysicsState } from './physics-state.js';
16
- import { openHippoDb, closeHippoDb } from './db.js';
17
- import { rrfFuse } from './rrf.js';
18
- import { graphRankStream, selectGraphSeeds, DEFAULT_GRAPH_SEED_COUNT } from './graph-stream.js';
19
- import { compareEntryIdentity, compareScoredResults } from './compare.js';
20
- import { log } from './log.js';
21
- import { redactSecretsStrict } from './secret-detect.js';
22
- export const CHURN_STALE_RANK_MULTIPLIER = 0.5; // SHORTCUT: untuned; FE3 measures before any default.
23
- // Search runs on every hook prompt, so one line per reason per process says why vectors went unused without flooding stderr.
24
- function warnBm25Fallback(reason, detail) {
25
- log.once(`search.bm25-fallback.${reason}`, 'warn', `hybrid search fell back to BM25 only: ${redactSecretsStrict(detail)}`);
26
- }
27
- export function churnStaleFactor(entry) {
28
- return entry.tags.includes(CHURN_STALE_TAG) ? CHURN_STALE_RANK_MULTIPLIER : 1.0;
29
- }
30
- const BM25_K1 = 1.5;
31
- const BM25_B = 0.75;
32
- export function buildCorpus(texts) {
33
- const docs = texts.map(tokenize);
34
- const N = docs.length;
35
- const df = new Map();
36
- let totalLen = 0;
37
- for (const doc of docs) {
38
- totalLen += doc.length;
39
- const seen = new Set();
40
- for (const term of doc) {
41
- if (!seen.has(term)) {
42
- df.set(term, (df.get(term) ?? 0) + 1);
43
- seen.add(term);
44
- }
45
- }
46
- }
47
- const avgLen = N > 0 ? totalLen / N : 1;
48
- return { docs, avgLen, df, N };
49
- }
50
- function bm25Score(corpus, docIdx, queryTerms) {
51
- const doc = corpus.docs[docIdx];
52
- const docLen = doc.length;
53
- let score = 0;
54
- // Term frequency map for this doc
55
- const tf = new Map();
56
- for (const t of doc)
57
- tf.set(t, (tf.get(t) ?? 0) + 1);
58
- for (const term of queryTerms) {
59
- const f = tf.get(term) ?? 0;
60
- if (f === 0)
61
- continue;
62
- const df = corpus.df.get(term) ?? 0;
63
- const idf = Math.log((corpus.N - df + 0.5) / (df + 0.5) + 1);
64
- const numerator = f * (BM25_K1 + 1);
65
- const denominator = f + BM25_K1 * (1 - BM25_B + BM25_B * (docLen / corpus.avgLen));
66
- score += idf * (numerator / denominator);
67
- }
68
- return score;
69
- }
70
- // ---------------------------------------------------------------------------
71
- // Token budget estimation
72
- // ---------------------------------------------------------------------------
73
- // Rough token estimate (characters / 4). Defined once in token-ledger.ts and
74
- // re-exported here, where callers have always imported it from.
75
- export { estimateTokens };
76
- // ---------------------------------------------------------------------------
77
- // Recency boost
78
- // ---------------------------------------------------------------------------
79
- function recencyBoost(entry, now) {
80
- if (isRecencyAblated())
81
- return 1; // EVAL-ONLY ablation (see ablation.ts)
82
- const created = new Date(entry.created);
83
- const ageDays = (now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24);
84
- // Exponential decay: memories < 1 day get boost ~1.0, older get less
85
- return Math.exp(-ageDays / (evalRecencyScaleDays() ?? 30));
86
- }
87
- /** Retrieval-time outcome nudge in [0.85, 1.15]; the E1 bm25-outcome baseline ranks with it too. */
88
- export function outcomeMultiplier(entry) {
89
- const pos = entry.outcome_positive ?? 0;
90
- const neg = entry.outcome_negative ?? 0;
91
- // EVAL-ONLY ablation (see ablation.ts): the fast outcome channel.
92
- if (isOutcomeFastAblated() || (pos === 0 && neg === 0))
93
- return 1.0;
94
- return Math.max(0.85, Math.min(1.15, 1 + 0.15 * Math.tanh((pos - neg) / 2)));
95
- }
96
- // ---------------------------------------------------------------------------
97
- // Temporal-aware scoring
98
- // ---------------------------------------------------------------------------
99
- const TEMPORAL_RECENT_CUES = new Set(['recently', 'latest', 'last', 'newest', 'current', 'today']);
100
- const TEMPORAL_OLDEST_CUES = new Set(['first', 'earliest', 'oldest', 'initially', 'originally']);
101
- export function detectTemporalDirection(query) {
102
- const words = query.toLowerCase().split(/\s+/);
103
- for (const w of words) {
104
- if (TEMPORAL_RECENT_CUES.has(w))
105
- return 'recent';
106
- if (TEMPORAL_OLDEST_CUES.has(w))
107
- return 'oldest';
108
- }
109
- return null;
110
- }
111
- export function computeTemporalRange(entries) {
112
- let minTime = Infinity;
113
- let maxTime = -Infinity;
114
- for (const e of entries) {
115
- const t = new Date(e.created).getTime();
116
- if (t < minTime)
117
- minTime = t;
118
- if (t > maxTime)
119
- maxTime = t;
120
- }
121
- return { minTime, maxTime };
122
- }
123
- export function temporalBoost(entry, direction, range) {
124
- if (!direction)
125
- return 1.0;
126
- const span = range.maxTime - range.minTime;
127
- if (span === 0)
128
- return 1.0;
129
- const entryTime = new Date(entry.created).getTime();
130
- const normalized = (entryTime - range.minTime) / span;
131
- if (direction === 'recent') {
132
- return 0.8 + 0.4 * normalized;
133
- }
134
- else {
135
- return 0.8 + 0.4 * (1 - normalized);
136
- }
137
- }
138
- // Skip-and-continue, with the first minResults kept whatever they cost; one loop so every engine spends alike.
139
- export function fitBudget(ordered, budget, minResults, cost) {
140
- const results = [];
141
- let used = 0;
142
- for (const r of ordered) {
143
- const tokens = cost ? cost(r) : r.tokens;
144
- if (results.length >= minResults && used + tokens > budget)
145
- continue;
146
- used += tokens;
147
- results.push(r);
148
- }
149
- return results;
150
- }
151
- // ---------------------------------------------------------------------------
152
- // v0.30 / E4 — DAG L2 summary scoring helpers
153
- // ---------------------------------------------------------------------------
154
- const DEFAULT_SUMMARY_DEBOOST = 0.85;
155
- const DEFAULT_FRESHNESS_BOOST = 1.05;
156
- const FRESHNESS_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
157
- /**
158
- * v0.30 / E4-E5 — single source of truth for "is this a DAG summary".
159
- * E4 originally checked dag_level === 2; E5 widens to L2 + L3 since L3
160
- * entity profiles also get the same deboost factor. Differentiated
161
- * deboost (e.g. 0.7 for L3) is flagged as follow-up.
162
- *
163
- * Existing drill-down at search.ts:506-529/923-946 uses tag check
164
- * ('dag-summary'); structural truth is dag_level === 2 || === 3. New
165
- * E4/E5 code uses this helper; existing drill-down NOT modified.
166
- */
167
- export function isDagSummary(entry) {
168
- return entry.dag_level === 2 || entry.dag_level === 3;
169
- }
170
- /**
171
- * v0.30 / E4 — resolve summaryDeboost: per-call > env > 0.85 default.
172
- * Invalid values (≤0, >1, NaN) at any level fall back to 0.85.
173
- */
174
- function resolveSummaryDeboost(perCall) {
175
- if (perCall !== undefined && Number.isFinite(perCall) && perCall > 0 && perCall <= 1) {
176
- return perCall;
177
- }
178
- const raw = process.env.HIPPO_SUMMARY_DEBOOST;
179
- if (raw !== undefined) {
180
- const parsed = parseFloat(raw);
181
- if (Number.isFinite(parsed) && parsed > 0 && parsed <= 1) {
182
- return parsed;
183
- }
184
- }
185
- return DEFAULT_SUMMARY_DEBOOST;
186
- }
187
- /**
188
- * v0.30 / E4 — micro-boost for L2 summaries rebuilt within window.
189
- * Returns 1.05 only when isDagSummary AND last_rebuilt_at parses to a
190
- * finite timestamp within FRESHNESS_WINDOW_MS of `now`. Handles null +
191
- * garbage string + future-dated via the Number.isFinite + ageMs >= 0 gates.
192
- */
193
- function summaryFreshnessMultiplier(entry, now) {
194
- if (!isDagSummary(entry) || !entry.last_rebuilt_at)
195
- return 1.0;
196
- const rebuiltMs = new Date(entry.last_rebuilt_at).getTime();
197
- if (!Number.isFinite(rebuiltMs))
198
- return 1.0;
199
- const ageMs = now.getTime() - rebuiltMs;
200
- return ageMs >= 0 && ageMs <= FRESHNESS_WINDOW_MS ? DEFAULT_FRESHNESS_BOOST : 1.0;
201
- }
202
- /**
203
- * Hybrid search: BM25 + cosine similarity (when embeddings are available).
204
- * score = 0.4 * bm25_norm + 0.6 * cosine_sim (with embeddings)
205
- * score = bm25_norm * strength * recency (BM25-only fallback)
206
- *
207
- * embeddingWeight: weight for the cosine similarity component (0.0 to 1.0).
208
- */
209
- export async function hybridSearch(query, entries, options = {}) {
210
- const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
211
- const budget = options.budget ?? 4000;
212
- const minResults = options.minResults ?? 1;
213
- const embeddingWeight = options.embeddingWeight ?? 0.6;
214
- const bm25Weight = 1 - embeddingWeight;
215
- const scoringMode = options.scoring ?? 'blend';
216
- const explain = options.explain ?? false;
217
- const mmrEnabled = options.mmr ?? true;
218
- const mmrLambda = options.mmrLambda ?? 0.7;
219
- // v0.30 / E4 — DAG L2 summary scoring controls
220
- const summaryDeboost = resolveSummaryDeboost(options.summaryDeboost);
221
- const summaryFreshness = options.summaryFreshness ?? true;
222
- // Bi-temporal filtering
223
- if (options.asOf) {
224
- const asOfDate = new Date(options.asOf);
225
- const successorValidFrom = new Map();
226
- for (const e of entries) {
227
- if (e.superseded_by) {
228
- const successor = entries.find(s => s.id === e.superseded_by);
229
- if (successor)
230
- successorValidFrom.set(e.id, successor.valid_from);
231
- }
232
- }
233
- entries = entries.filter(e => {
234
- if (new Date(e.valid_from) > asOfDate)
235
- return false;
236
- if (!e.superseded_by)
237
- return true;
238
- const succVf = successorValidFrom.get(e.id);
239
- return succVf ? new Date(succVf) > asOfDate : true;
240
- });
241
- }
242
- else if (!options.includeSuperseded) {
243
- entries = entries.filter(e => !e.superseded_by);
244
- }
245
- if (entries.length === 0)
246
- return [];
247
- const queryTerms = tokenize(query);
248
- if (queryTerms.length === 0)
249
- return [];
250
- // Build BM25 corpus (or reuse one the caller already built).
251
- const corpus = options.preparedCorpus
252
- ?? buildCorpus(entries.map((e) => `${e.content} ${e.tags.join(' ')}`));
253
- // Score all entries with BM25
254
- const bm25Scores = entries.map((_, i) => bm25Score(corpus, i, queryTerms));
255
- const maxBm25 = bm25Scores.reduce((a, b) => Math.max(a, b), 1e-9);
256
- // Try to get embedding scores if available
257
- let useEmbeddings = false;
258
- let embeddingIndex = {};
259
- let queryVector = [];
260
- if (options.hippoRoot) {
261
- try {
262
- const provider = resolveEmbeddingProvider(options.hippoRoot);
263
- const idx = provider.isAvailable() ? loadEmbeddingIndex(options.hippoRoot) : {};
264
- if (provider.isAvailable() && !embeddingModelRequiresReindex(options.hippoRoot, provider.id, idx)) {
265
- // Only spend a (possibly paid, off-box) query embedding when at least one
266
- // of THIS search's entries has a cached vector to compare against. An
267
- // index of only orphaned / out-of-scope vectors yields a meaningless
268
- // dense ranking, so stay BM25-only in that case.
269
- if (entries.some((e) => (idx[e.id]?.length ?? 0) > 0)) {
270
- const [vec] = await provider.embed([query], 'query');
271
- queryVector = vec ?? [];
272
- if (queryVector.length > 0) {
273
- embeddingIndex = idx;
274
- useEmbeddings = true;
275
- }
276
- else {
277
- warnBm25Fallback('empty-query-vector', 'the embedding provider returned no vector for the query');
278
- }
279
- }
280
- }
281
- else if (provider.isAvailable()) {
282
- warnBm25Fallback('reindex', "the embedding index was built by another model or is being rebuilt; run 'hippo embed'");
283
- }
284
- }
285
- catch (err) {
286
- warnBm25Fallback('error', err instanceof Error ? err.message : String(err));
287
- }
288
- }
289
- // Compute cosine similarities for RRF ranking (need all before scoring)
290
- const cosineScores = new Array(entries.length).fill(0);
291
- const hadCachedVecs = new Array(entries.length).fill(false);
292
- if (useEmbeddings) {
293
- for (let i = 0; i < entries.length; i++) {
294
- const cached = embeddingIndex[entries[i].id];
295
- hadCachedVecs[i] = Boolean(cached && queryVector.length > 0);
296
- cosineScores[i] = hadCachedVecs[i]
297
- ? Math.max(0, cosineSimilarity(queryVector, cached))
298
- : 0;
299
- }
300
- }
301
- // RRF fusion: shared with the F9 LongMemEval benchmark via src/rrf.ts.
302
- // `absentRank: entries.length + 1` preserves the pre-extraction convention
303
- // (rrfFuse's default would otherwise use the max ranked-list length, which
304
- // matches in the current filter path but differs if filtering ever changes).
305
- let rrfScores = null;
306
- if (useEmbeddings && scoringMode === 'rrf') {
307
- const eligible = entries
308
- .map((_, i) => i)
309
- .filter((i) => bm25Scores[i] > 0 || cosineScores[i] > 0);
310
- // score desc -> compareEntryIdentity tail (deterministic across fresh
311
- // ingests): these two rankings feed selectGraphSeeds + RRF rank
312
- // assignment, so an unbroken tie here would leak into seed selection.
313
- const bm25Ranked = [...eligible].sort((a, b) => {
314
- const d = bm25Scores[b] - bm25Scores[a];
315
- return d !== 0 ? d : compareEntryIdentity(entries[a], entries[b]);
316
- });
317
- const cosineRanked = [...eligible].sort((a, b) => {
318
- const d = cosineScores[b] - cosineScores[a];
319
- return d !== 0 ? d : compareEntryIdentity(entries[a], entries[b]);
320
- });
321
- // L1 graph-retrieval stream (opt-in): a 3rd RRF input ranking in-pool candidates by
322
- // graph proximity to the strong lexical seeds. When absent / weight<=0 / the graph
323
- // yields nothing in-pool, `graphRanked` is empty and the fusion below is the
324
- // byte-identical 2-list (BM25 + dense) path. An all-absent 3rd list would NOT be a
325
- // no-op: it adds a uniform constant to the RRF base, which the per-entry multipliers
326
- // below turn non-uniform — so we SKIP it rather than fuse an empty list.
327
- // Gate the graph stream on the candidate set actually having cached document vectors
328
- // (codex P2): when the transformers package is installed but this store has not been
329
- // embedded yet, `useEmbeddings` is true (only the QUERY vector was produced) while every
330
- // `cosineScore` is 0, so `cosineRanked` collapses to entry order. Without this gate the
331
- // stream would select seeds from that meaningless dense ranking; instead it stays inert
332
- // (degrades to the 2-list path) until `hippo embed` has run.
333
- const hasDocVectors = hadCachedVecs.some(Boolean);
334
- const gs = options.graphStream;
335
- let graphRanked = [];
336
- if (gs && gs.weight > 0 && options.hippoRoot && hasDocVectors) {
337
- const seedCount = Math.min(gs.seedCount ?? DEFAULT_GRAPH_SEED_COUNT, eligible.length);
338
- const seeds = selectGraphSeeds(bm25Ranked, cosineRanked, seedCount);
339
- graphRanked = graphRankStream(entries, seeds, {
340
- hippoRoot: options.hippoRoot,
341
- tenantId: gs.tenantId,
342
- globalRoot: gs.globalRoot,
343
- hops: gs.hops,
344
- decay: gs.decay,
345
- maxNeighbors: gs.maxNeighbors,
346
- });
347
- }
348
- rrfScores = graphRanked.length > 0
349
- ? rrfFuse([bm25Ranked, cosineRanked, graphRanked], [bm25Weight, embeddingWeight, gs.weight], { absentRank: entries.length + 1 })
350
- : rrfFuse([bm25Ranked, cosineRanked], [bm25Weight, embeddingWeight], { absentRank: entries.length + 1 });
351
- }
352
- // Score each entry
353
- const scored = [];
354
- const currentPathTags = extractPathTags(process.cwd());
355
- const activeScope = options.scope !== undefined ? options.scope : detectScope();
356
- const queryTermSet = new Set(queryTerms);
357
- const temporalDirAsync = detectTemporalDirection(query);
358
- const temporalRangeAsync = temporalDirAsync ? computeTemporalRange(entries) : { minTime: 0, maxTime: 0 };
359
- for (let i = 0; i < entries.length; i++) {
360
- const rawBm25 = bm25Scores[i];
361
- const cosineScore = cosineScores[i];
362
- const hadCachedVec = hadCachedVecs[i];
363
- if (!useEmbeddings && rawBm25 <= 0)
364
- continue;
365
- const normBm25 = rawBm25 / maxBm25;
366
- const strength = calculateStrength(entries[i], now);
367
- const recency = recencyBoost(entries[i], now);
368
- const strengthMultiplier = 0.5 + 0.5 * strength;
369
- const recencyMultiplier = 0.8 + 0.2 * recency;
370
- let compositeScore;
371
- let base;
372
- let modeLabel;
373
- if (useEmbeddings) {
374
- if (rrfScores) {
375
- base = rrfScores.get(i) ?? 0;
376
- }
377
- else {
378
- base = bm25Weight * normBm25 + embeddingWeight * cosineScore;
379
- }
380
- compositeScore = base * strengthMultiplier * recencyMultiplier;
381
- modeLabel = hadCachedVec ? 'hybrid' : 'hybrid-no-vec';
382
- }
383
- else {
384
- base = queryTerms.length > 0 ? rawBm25 / queryTerms.length : rawBm25;
385
- compositeScore = base * strengthMultiplier * recencyMultiplier;
386
- modeLabel = 'bm25-only';
387
- }
388
- // Decision-tagged memories get a 1.2x recall boost
389
- const decisionBoost = entries[i].tags.includes('decision') ? 1.2 : 1.0;
390
- compositeScore *= decisionBoost;
391
- const churnStaleMultiplier = churnStaleFactor(entries[i]);
392
- compositeScore *= churnStaleMultiplier;
393
- // Path-based boost: memories tagged with matching path segments get up to 1.3x
394
- const pathBoost = pathBoostMultiplier(entries[i].tags, currentPathTags);
395
- compositeScore *= pathBoost;
396
- // Retrieval-time outcome personalization: nudge up/down from user feedback.
397
- // Distinct from reward-factor-via-strength (slow); this is immediate.
398
- const outcomeBoost = outcomeMultiplier(entries[i]);
399
- compositeScore *= outcomeBoost;
400
- // Scope boost: memories tagged with the active scope get 1.5x; mismatching scopes get 0.5x
401
- const scopeSignal = scopeMatch(entries[i].tags, activeScope);
402
- const scopeBoost = scopeSignal === 1 ? 1.5 : scopeSignal === -1 ? 0.5 : 1.0;
403
- compositeScore *= scopeBoost;
404
- const extractionBoost = entries[i].tags.includes('extracted') ? 1.3 : 1.0;
405
- compositeScore *= extractionBoost;
406
- compositeScore *= temporalBoost(entries[i], temporalDirAsync, temporalRangeAsync);
407
- // v0.30 / E4 — DAG L2 summary deboost + freshness micro-boost.
408
- // Applied last so it composes with all other multipliers.
409
- let summaryDeboostMultiplier = 1.0;
410
- let freshnessMultiplier = 1.0;
411
- if (isDagSummary(entries[i])) {
412
- summaryDeboostMultiplier = summaryDeboost;
413
- if (summaryFreshness) {
414
- freshnessMultiplier = summaryFreshnessMultiplier(entries[i], now);
415
- }
416
- }
417
- compositeScore *= summaryDeboostMultiplier * freshnessMultiplier;
418
- if (compositeScore <= 0)
419
- continue;
420
- const tokens = estimateTokens(entries[i].content);
421
- const result = {
422
- entry: entries[i],
423
- score: compositeScore,
424
- bm25: rawBm25,
425
- cosine: cosineScore,
426
- tokens,
427
- };
428
- if (explain) {
429
- const docTerms = new Set(tokenize(`${entries[i].content} ${entries[i].tags.join(' ')}`));
430
- const matchedTerms = [];
431
- for (const t of queryTermSet)
432
- if (docTerms.has(t))
433
- matchedTerms.push(t);
434
- const ageDays = Math.max(0, Math.floor((now.getTime() - new Date(entries[i].created).getTime()) / 86_400_000));
435
- result.breakdown = {
436
- mode: modeLabel,
437
- normBm25,
438
- bm25Weight: useEmbeddings ? bm25Weight : 1,
439
- embeddingWeight: useEmbeddings ? embeddingWeight : 0,
440
- cosine: cosineScore,
441
- base,
442
- strengthMultiplier,
443
- recencyMultiplier,
444
- decisionBoost,
445
- pathBoost,
446
- scopeBoost,
447
- sourceBump: 1,
448
- outcomeBoost,
449
- churnStaleMultiplier,
450
- matchedTerms,
451
- final: compositeScore,
452
- ageDays,
453
- };
454
- // v0.30 / E4 — DAG metadata
455
- if (entries[i].dag_level !== undefined) {
456
- result.breakdown.dagLevel = entries[i].dag_level;
457
- }
458
- if (entries[i].descendant_count !== undefined) {
459
- result.breakdown.descendantCount = entries[i].descendant_count;
460
- }
461
- if (isDagSummary(entries[i])) {
462
- result.breakdown.lastRebuiltAt = entries[i].last_rebuilt_at ?? null;
463
- result.breakdown.rebuildCount = entries[i].rebuild_count ?? 0;
464
- result.breakdown.summaryDeboost = summaryDeboostMultiplier;
465
- result.breakdown.summaryFreshnessBoost = freshnessMultiplier;
466
- }
467
- }
468
- scored.push(result);
469
- }
470
- // Sort by composite score descending, deterministic tiebreak on ties.
471
- scored.sort(compareScoredResults);
472
- // Deduplicate: when an extracted fact and its source both appear,
473
- // keep only the higher-scoring one (typically the fact).
474
- const seenExtractedFrom = new Set();
475
- const deduped = [];
476
- for (const result of scored) {
477
- const entry = result.entry;
478
- if (entry.extracted_from) {
479
- seenExtractedFrom.add(entry.extracted_from);
480
- const sourceIdx = deduped.findIndex((d) => d.entry.id === entry.extracted_from);
481
- if (sourceIdx >= 0)
482
- deduped.splice(sourceIdx, 1);
483
- deduped.push(result);
484
- }
485
- else if (seenExtractedFrom.has(entry.id)) {
486
- continue;
487
- }
488
- else {
489
- deduped.push(result);
490
- }
491
- }
492
- const scoredDeduped = deduped;
493
- // DAG drill-down: when a summary node matches, inject its children
494
- const summaryIdsAsync = scoredDeduped
495
- .filter((r) => r.entry.tags.includes('dag-summary'))
496
- .map((r) => r.entry.id);
497
- if (summaryIdsAsync.length > 0) {
498
- const childEntriesAsync = entries.filter((e) => e.dag_parent_id && summaryIdsAsync.includes(e.dag_parent_id));
499
- for (const child of childEntriesAsync) {
500
- if (!scoredDeduped.some((r) => r.entry.id === child.id)) {
501
- const parentResult = scoredDeduped.find((r) => r.entry.id === child.dag_parent_id);
502
- const childScore = parentResult ? parentResult.score * 0.9 * churnStaleFactor(child) : 0;
503
- scoredDeduped.push({
504
- entry: child,
505
- score: childScore,
506
- bm25: 0,
507
- cosine: 0,
508
- tokens: estimateTokens(child.content),
509
- });
510
- }
511
- }
512
- scoredDeduped.sort(compareScoredResults);
513
- }
514
- // MMR re-ranking: de-cluster near-duplicates by trading relevance for
515
- // diversity. Only applies when embeddings are loaded (doc-to-doc similarity
516
- // is via cosine of cached vectors); otherwise we return the pure-relevance
517
- // ordering unchanged.
518
- //
519
- // MMR is O(K^2) in cosine similarity ops, which on large corpora (1000+
520
- // candidates) dominates query time. Cap the re-ranking window to the top
521
- // relevance-scored candidates — anything below top-K was never going to
522
- // surface anyway after budget filtering.
523
- const MMR_CANDIDATE_CAP = 100;
524
- const applyMmr = mmrEnabled && useEmbeddings && scoredDeduped.length > 1 && mmrLambda < 1;
525
- let ordered;
526
- if (applyMmr) {
527
- const head = scoredDeduped.slice(0, MMR_CANDIDATE_CAP);
528
- const tail = scoredDeduped.slice(MMR_CANDIDATE_CAP);
529
- ordered = [...mmrRerank(head, embeddingIndex, mmrLambda, explain), ...tail];
530
- }
531
- else {
532
- ordered = scoredDeduped;
533
- }
534
- // Reranker pass: see src/rerankers/types.ts and
535
- // docs/plans/2026-05-10-f6-reranker-hardening.md.
536
- if (options.reranker) {
537
- const topK = options.rerankerOptions?.topK ?? 50;
538
- const head = ordered.slice(0, topK);
539
- const tail = ordered.slice(topK);
540
- const rerankInputWithRank = head.map((r, i) => ({ ...r, preRerankRank: i + 1 }));
541
- const reranked = await options.reranker(query, rerankInputWithRank, { ...options.rerankerOptions, topK });
542
- const withPostRank = reranked.map((r, i) => ({ ...r, postRerankRank: i + 1 }));
543
- ordered = [...withPostRank, ...tail];
544
- }
545
- return fitBudget(ordered, budget, minResults, options.cost);
546
- }
547
- /**
548
- * MMR (Maximal Marginal Relevance) re-ranking.
549
- *
550
- * Iteratively picks the candidate that maximises
551
- * lambda * relevance - (1 - lambda) * max(cos(cand, picked))
552
- *
553
- * Inputs must already be sorted by relevance descending. When `explain` is
554
- * true, attaches `preMmrRank` / `postMmrRank` to each result's breakdown.
555
- * Exported for unit tests; production callers go through hybridSearch.
556
- *
557
- * Determinism note (T2): the picking loop below uses strict `mmr > bestMmr`
558
- * (first-wins on ties), so it is already deterministic GIVEN a
559
- * deterministic `scored` input order — no comparator change needed here;
560
- * the tiebreak lives upstream, in how `scored` was sorted before this runs.
561
- */
562
- export function mmrRerank(scored, embeddingIndex, lambda, explain) {
563
- if (scored.length === 0)
564
- return scored;
565
- const maxScore = scored[0].score || 1;
566
- const normScore = scored.map((r) => r.score / maxScore);
567
- const vectors = scored.map((r) => embeddingIndex[r.entry.id] ?? null);
568
- const picked = [];
569
- const remaining = new Set(scored.map((_, i) => i));
570
- while (remaining.size > 0) {
571
- let bestIdx = -1;
572
- let bestMmr = -Infinity;
573
- for (const i of remaining) {
574
- const rel = normScore[i];
575
- let maxSim = 0;
576
- const vi = vectors[i];
577
- if (vi) {
578
- for (const p of picked) {
579
- const vp = embeddingIndex[p.entry.id];
580
- if (!vp || vp.length !== vi.length)
581
- continue;
582
- const sim = Math.max(0, cosineSimilarity(vi, vp));
583
- if (sim > maxSim)
584
- maxSim = sim;
585
- }
586
- }
587
- const mmr = lambda * rel - (1 - lambda) * maxSim;
588
- if (mmr > bestMmr) {
589
- bestMmr = mmr;
590
- bestIdx = i;
591
- }
592
- }
593
- if (bestIdx === -1)
594
- break;
595
- remaining.delete(bestIdx);
596
- picked.push(scored[bestIdx]);
597
- }
598
- if (explain) {
599
- const preRank = new Map();
600
- scored.forEach((r, i) => preRank.set(r.entry.id, i + 1));
601
- picked.forEach((r, i) => {
602
- if (r.breakdown) {
603
- r.breakdown.preMmrRank = preRank.get(r.entry.id);
604
- r.breakdown.postMmrRank = i + 1;
605
- }
606
- });
607
- }
608
- return picked;
609
- }
610
- /**
611
- * Physics-based search: scores memories using gravitational force, momentum,
612
- * and cluster amplification. Falls back to classic hybrid for memories
613
- * without physics state.
614
- */
615
- export async function physicsSearch(query, entries, options = {}) {
616
- const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
617
- const budget = options.budget ?? 4000;
618
- const minResults = options.minResults ?? 1;
619
- const config = options.physicsConfig ?? DEFAULT_PHYSICS_CONFIG;
620
- const explain = options.explain ?? false;
621
- // v0.30 / E4 — DAG L2 summary scoring controls (same as hybridSearch)
622
- const summaryDeboost = resolveSummaryDeboost(options.summaryDeboost);
623
- const summaryFreshness = options.summaryFreshness ?? true;
624
- if (entries.length === 0 || !options.hippoRoot)
625
- return [];
626
- // Get query embedding (use pre-computed if provided)
627
- let queryVector = options.queryEmbedding ?? [];
628
- if (queryVector.length === 0) {
629
- // Both resolveEmbeddingProvider (invalid config) and provider.embed (API
630
- // network/auth/5xx) can throw; fall back to hybrid/BM25 rather than reject
631
- // recall, matching the surrounding fallback contract. NOTE: physics search
632
- // scores against its own cached particle-state vectors (loaded below), NOT
633
- // embeddings.json, so we do not gate the query embedding on the index here
634
- // (doing so would skip valid physics when the embedding index is pruned).
635
- try {
636
- const provider = resolveEmbeddingProvider(options.hippoRoot);
637
- if (!provider.isAvailable() || embeddingModelRequiresReindex(options.hippoRoot, provider.id)) {
638
- return hybridSearch(query, entries, options);
639
- }
640
- const [vec] = await provider.embed([query], 'query');
641
- queryVector = vec ?? [];
642
- }
643
- catch {
644
- return hybridSearch(query, entries, options);
645
- }
646
- if (queryVector.length === 0) {
647
- return hybridSearch(query, entries, options);
648
- }
649
- }
650
- // Load physics state
651
- let physicsMap;
652
- try {
653
- const db = openHippoDb(options.hippoRoot);
654
- try {
655
- physicsMap = loadPhysicsState(db);
656
- }
657
- finally {
658
- closeHippoDb(db);
659
- }
660
- }
661
- catch {
662
- return hybridSearch(query, entries, options);
663
- }
664
- // Split entries into physics-enabled and classic
665
- const physicsEntries = [];
666
- const physicsParticles = [];
667
- const classicEntries = [];
668
- for (const entry of entries) {
669
- const particle = physicsMap.get(entry.id);
670
- if (particle
671
- && particle.position.length > 0
672
- && particle.position.length === queryVector.length
673
- && particle.velocity.length === queryVector.length) {
674
- physicsEntries.push(entry);
675
- // EVAL-ONLY ablation (see ablation.ts): persisted masses embed recall
676
- // history TWICE - the outer retrieval-count multiplier AND the frozen
677
- // strength inside it (computed at sleep time from clock-reset
678
- // last_retrieved + the read-side boost). Stripping the outer multiplier
679
- // alone is not enough (codex round-7 P2); under the flag, recompute the
680
- // mass LIVE from the current entry under ablated strength rules
681
- // (created-anchored decay, no boost - both applied inside
682
- // calculateStrength/computeMass by the same flag). The frozen-at-sleep
683
- // semantics is unrecoverable for the ablated arm by definition.
684
- physicsParticles.push(isRecallBoostAblated()
685
- ? { ...particle, mass: computeMass(calculateStrength(entry, now), entry.retrieval_count) }
686
- : particle);
687
- }
688
- else {
689
- classicEntries.push(entry);
690
- }
691
- }
692
- // Score physics-enabled memories
693
- const physicsResults = [];
694
- if (physicsParticles.length > 0) {
695
- const entryMap = new Map(physicsEntries.map(e => [e.id, e]));
696
- // Content tie key: the physics baseScore tie order selects the
697
- // cluster_top_k amplification set, so it must be cross-ingest-stable
698
- // (codex review). memoryId fallback covers particles whose entry was
699
- // filtered from physicsEntries.
700
- const scored = computePhysicsScores(physicsParticles, queryVector, config, (id) => entryMap.get(id)?.content ?? id);
701
- for (const s of scored) {
702
- if (s.finalScore <= 0)
703
- continue;
704
- const entry = entryMap.get(s.memoryId);
705
- if (!entry)
706
- continue;
707
- // v0.30 / E4 — apply DAG L2 summary deboost + freshness in physics path.
708
- let summaryDeboostMultiplier = 1.0;
709
- let freshnessMultiplier = 1.0;
710
- if (isDagSummary(entry)) {
711
- summaryDeboostMultiplier = summaryDeboost;
712
- if (summaryFreshness) {
713
- freshnessMultiplier = summaryFreshnessMultiplier(entry, now);
714
- }
715
- }
716
- const churnStaleMultiplier = churnStaleFactor(entry);
717
- const finalScore = s.finalScore * summaryDeboostMultiplier * freshnessMultiplier * churnStaleMultiplier;
718
- if (finalScore <= 0)
719
- continue;
720
- const result = {
721
- entry,
722
- score: finalScore,
723
- bm25: 0,
724
- cosine: s.baseScore,
725
- tokens: estimateTokens(entry.content),
726
- };
727
- if (explain) {
728
- const ageDays = Math.max(0, Math.floor((now.getTime() - new Date(entry.created).getTime()) / 86_400_000));
729
- result.breakdown = {
730
- mode: 'physics',
731
- normBm25: 0,
732
- bm25Weight: 0,
733
- embeddingWeight: 1,
734
- cosine: s.baseScore,
735
- base: s.baseScore,
736
- strengthMultiplier: 1,
737
- recencyMultiplier: 1,
738
- decisionBoost: 1,
739
- pathBoost: 1,
740
- scopeBoost: 1,
741
- sourceBump: 1,
742
- outcomeBoost: 1,
743
- churnStaleMultiplier,
744
- matchedTerms: [],
745
- final: finalScore,
746
- ageDays,
747
- };
748
- // v0.30 / E4 — DAG metadata
749
- if (entry.dag_level !== undefined) {
750
- result.breakdown.dagLevel = entry.dag_level;
751
- }
752
- if (entry.descendant_count !== undefined) {
753
- result.breakdown.descendantCount = entry.descendant_count;
754
- }
755
- if (isDagSummary(entry)) {
756
- result.breakdown.lastRebuiltAt = entry.last_rebuilt_at ?? null;
757
- result.breakdown.rebuildCount = entry.rebuild_count ?? 0;
758
- result.breakdown.summaryDeboost = summaryDeboostMultiplier;
759
- result.breakdown.summaryFreshnessBoost = freshnessMultiplier;
760
- }
761
- }
762
- physicsResults.push(result);
763
- }
764
- }
765
- // Score classic memories (no physics state)
766
- const classicResults = classicEntries.length > 0
767
- ? await hybridSearch(query, classicEntries, { ...options, budget: Infinity, explain })
768
- : [];
769
- // Normalize both pools to [0, 1] and merge
770
- const merged = mergeScorePools(physicsResults, classicResults);
771
- // Sort and apply budget, deterministic tiebreak on ties.
772
- merged.sort(compareScoredResults);
773
- return fitBudget(merged, budget, minResults, options.cost);
774
- }
775
- /** Normalize two score pools to [0,1] and combine. */
776
- function mergeScorePools(poolA, poolB) {
777
- const unpenalised = (r) => r.score / churnStaleFactor(r.entry);
778
- const maxA = poolA.reduce((m, r) => Math.max(m, unpenalised(r)), 1e-9);
779
- const maxB = poolB.reduce((m, r) => Math.max(m, unpenalised(r)), 1e-9);
780
- const merged = [];
781
- for (const r of poolA) {
782
- merged.push({ ...r, score: r.score / maxA });
783
- }
784
- for (const r of poolB) {
785
- merged.push({ ...r, score: r.score / maxB });
786
- }
787
- return merged;
788
- }
789
- /**
790
- * Search entries using BM25 + strength + recency composite score.
791
- * When embeddings are available and hippoRoot is provided, uses hybrid scoring.
792
- * Returns results sorted by score, capped at token budget.
793
- *
794
- * Also updates retrieval metadata on returned entries (side effect: caller
795
- * must persist the updated entries).
796
- */
797
- export function search(query, entries, options = {}) {
798
- // Synchronous path: BM25 only (no async hybrid)
799
- const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
800
- const budget = options.budget ?? 4000;
801
- const minResults = options.minResults ?? 1;
802
- // Bi-temporal filtering
803
- if (options.asOf) {
804
- const asOfDate = new Date(options.asOf);
805
- const successorValidFrom = new Map();
806
- for (const e of entries) {
807
- if (e.superseded_by) {
808
- const successor = entries.find(s => s.id === e.superseded_by);
809
- if (successor)
810
- successorValidFrom.set(e.id, successor.valid_from);
811
- }
812
- }
813
- entries = entries.filter(e => {
814
- if (new Date(e.valid_from) > asOfDate)
815
- return false;
816
- if (!e.superseded_by)
817
- return true;
818
- const succVf = successorValidFrom.get(e.id);
819
- return succVf ? new Date(succVf) > asOfDate : true;
820
- });
821
- }
822
- else if (!options.includeSuperseded) {
823
- entries = entries.filter(e => !e.superseded_by);
824
- }
825
- if (entries.length === 0)
826
- return [];
827
- const queryTerms = tokenize(query);
828
- if (queryTerms.length === 0)
829
- return [];
830
- // Build corpus from all entries (content + tags joined)
831
- const texts = entries.map((e) => `${e.content} ${e.tags.join(' ')}`);
832
- const corpus = buildCorpus(texts);
833
- // Score each entry
834
- const scored = [];
835
- const currentPathTagsSync = extractPathTags(process.cwd());
836
- const activeScopeSync = detectScope();
837
- const temporalDir = detectTemporalDirection(query);
838
- const temporalRangeSync = temporalDir ? computeTemporalRange(entries) : { minTime: 0, maxTime: 0 };
839
- for (let i = 0; i < entries.length; i++) {
840
- const bm25 = bm25Score(corpus, i, queryTerms);
841
- if (bm25 <= 0)
842
- continue;
843
- const strength = calculateStrength(entries[i], now);
844
- const recency = recencyBoost(entries[i], now);
845
- // Composite: BM25 relevance * strength * recency
846
- // Normalise BM25 against query term count to keep scale consistent
847
- const normBm25 = queryTerms.length > 0 ? bm25 / queryTerms.length : bm25;
848
- let composite = normBm25 * (0.5 + 0.5 * strength) * (0.8 + 0.2 * recency);
849
- // Decision-tagged memories get a 1.2x recall boost
850
- const decisionBoost = entries[i].tags.includes('decision') ? 1.2 : 1.0;
851
- composite *= decisionBoost;
852
- composite *= churnStaleFactor(entries[i]);
853
- // Path-based boost: memories tagged with matching path segments get up to 1.3x
854
- composite *= pathBoostMultiplier(entries[i].tags, currentPathTagsSync);
855
- // Scope boost (sync path)
856
- const scopeSignalSync = scopeMatch(entries[i].tags, activeScopeSync);
857
- const scopeBoostSync = scopeSignalSync === 1 ? 1.5 : scopeSignalSync === -1 ? 0.5 : 1.0;
858
- composite *= scopeBoostSync;
859
- const extractionBoostSync = entries[i].tags.includes('extracted') ? 1.3 : 1.0;
860
- composite *= extractionBoostSync;
861
- composite *= temporalBoost(entries[i], temporalDir, temporalRangeSync);
862
- const tokens = estimateTokens(entries[i].content);
863
- scored.push({ entry: entries[i], score: composite, bm25, cosine: 0, tokens });
864
- }
865
- // Sort by composite score descending, deterministic tiebreak on ties.
866
- scored.sort(compareScoredResults);
867
- const seenExtractedFromSync = new Set();
868
- const dedupedSync = [];
869
- for (const result of scored) {
870
- const entry = result.entry;
871
- if (entry.extracted_from) {
872
- seenExtractedFromSync.add(entry.extracted_from);
873
- const sourceIdx = dedupedSync.findIndex((d) => d.entry.id === entry.extracted_from);
874
- if (sourceIdx >= 0)
875
- dedupedSync.splice(sourceIdx, 1);
876
- dedupedSync.push(result);
877
- }
878
- else if (seenExtractedFromSync.has(entry.id)) {
879
- continue;
880
- }
881
- else {
882
- dedupedSync.push(result);
883
- }
884
- }
885
- // DAG drill-down: when a summary node matches, inject its children
886
- const summaryIdsSync = dedupedSync
887
- .filter((r) => r.entry.tags.includes('dag-summary'))
888
- .map((r) => r.entry.id);
889
- if (summaryIdsSync.length > 0) {
890
- const childEntries = entries.filter((e) => e.dag_parent_id && summaryIdsSync.includes(e.dag_parent_id));
891
- for (const child of childEntries) {
892
- if (!dedupedSync.some((r) => r.entry.id === child.id)) {
893
- const parentResult = dedupedSync.find((r) => r.entry.id === child.dag_parent_id);
894
- const childScore = parentResult ? parentResult.score * 0.9 * churnStaleFactor(child) : 0;
895
- dedupedSync.push({
896
- entry: child,
897
- score: childScore,
898
- bm25: 0,
899
- cosine: 0,
900
- tokens: estimateTokens(child.content),
901
- });
902
- }
903
- }
904
- dedupedSync.sort(compareScoredResults);
905
- }
906
- return fitBudget(dedupedSync, budget, minResults, options.cost);
907
- }
908
- /**
909
- * Explain why a search result matched a query.
910
- * Computes which query terms overlapped with the document and whether
911
- * BM25 and/or embedding similarity contributed to the composite score.
912
- */
913
- export function explainMatch(query, result) {
914
- const queryTerms = new Set(tokenize(query));
915
- const docTerms = new Set(tokenize(`${result.entry.content} ${result.entry.tags.join(' ')}`));
916
- const matchedTerms = [];
917
- for (const term of queryTerms) {
918
- if (docTerms.has(term)) {
919
- matchedTerms.push(term);
920
- }
921
- }
922
- const hasBm25 = result.bm25 > 0;
923
- const hasEmbedding = result.cosine > 0;
924
- const parts = [];
925
- if (hasBm25) {
926
- parts.push(`BM25: matched terms [${matchedTerms.join(', ')}]`);
927
- }
928
- if (hasEmbedding) {
929
- parts.push(`embedding similarity: ${result.cosine.toFixed(3)}`);
930
- }
931
- if (parts.length === 0) {
932
- parts.push('no direct term or embedding match');
933
- }
934
- return {
935
- reason: parts.join('; '),
936
- matchedTerms,
937
- hasBm25,
938
- hasEmbedding,
939
- cosineSimilarity: result.cosine,
940
- envelope: {
941
- kind: result.entry.kind ?? 'distilled',
942
- scope: result.entry.scope ?? null,
943
- owner: result.entry.owner ?? null,
944
- artifact_ref: result.entry.artifact_ref ?? null,
945
- session_id: result.entry.source_session_id ?? null,
946
- confidence: result.entry.confidence ?? 'observed',
947
- },
948
- };
949
- }
950
- /**
951
- * Compute text overlap ratio between two strings (Jaccard on token sets).
952
- */
953
- export function textOverlap(a, b) {
954
- const setA = new Set(tokenize(a));
955
- const setB = new Set(tokenize(b));
956
- if (setA.size === 0 && setB.size === 0)
957
- return 1;
958
- if (setA.size === 0 || setB.size === 0)
959
- return 0;
960
- let intersection = 0;
961
- for (const t of setA) {
962
- if (setB.has(t))
963
- intersection++;
964
- }
965
- const union = setA.size + setB.size - intersection;
966
- return intersection / union;
967
- }
968
- //# sourceMappingURL=search.js.map