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