hippo-memory 1.44.0 → 1.45.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 (528) hide show
  1. package/README.md +12 -10
  2. package/bin/hippo.js +3 -1
  3. package/dist/api.d.ts +21 -21
  4. package/dist/api.js +69 -42
  5. package/dist/audit.d.ts +1 -1
  6. package/dist/audit.js +9 -0
  7. package/dist/autolearn.d.ts +1 -1
  8. package/dist/autolearn.js +2 -1
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.js +151 -72
  11. package/dist/client.d.ts +4 -21
  12. package/dist/client.js +3 -112
  13. package/dist/config.js +2 -1
  14. package/dist/consolidate.d.ts +3 -0
  15. package/dist/consolidate.js +44 -42
  16. package/dist/dag.d.ts +1 -0
  17. package/dist/dag.js +10 -5
  18. package/dist/dashboard.js +7 -3
  19. package/dist/db.js +12 -23
  20. package/dist/dedupe.d.ts +1 -0
  21. package/dist/dedupe.js +4 -7
  22. package/dist/embedding-provider.js +2 -1
  23. package/dist/embeddings.js +81 -8
  24. package/dist/extract.d.ts +2 -0
  25. package/dist/extract.js +9 -4
  26. package/dist/mcp/server.js +21 -36
  27. package/dist/memory.d.ts +3 -0
  28. package/dist/memory.js +7 -1
  29. package/dist/refine-llm.js +3 -2
  30. package/dist/rerankers/jev.js +3 -2
  31. package/dist/rerankers/llm.js +3 -2
  32. package/dist/search.js +3 -3
  33. package/dist/server.d.ts +5 -0
  34. package/dist/server.js +39 -29
  35. package/dist/shared.js +1 -1
  36. package/dist/store.d.ts +18 -11
  37. package/dist/store.js +144 -63
  38. package/dist/version.d.ts +4 -6
  39. package/dist/version.js +4 -7
  40. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  41. package/extensions/openclaw-plugin/package.json +1 -1
  42. package/openclaw.plugin.json +1 -1
  43. package/package.json +6 -3
  44. package/dist/ablation.d.ts.map +0 -1
  45. package/dist/ablation.js.map +0 -1
  46. package/dist/ambient.d.ts.map +0 -1
  47. package/dist/ambient.js.map +0 -1
  48. package/dist/api.d.ts.map +0 -1
  49. package/dist/api.js.map +0 -1
  50. package/dist/audit-prune.d.ts.map +0 -1
  51. package/dist/audit-prune.js.map +0 -1
  52. package/dist/audit.d.ts.map +0 -1
  53. package/dist/audit.js.map +0 -1
  54. package/dist/auth.d.ts.map +0 -1
  55. package/dist/auth.js.map +0 -1
  56. package/dist/autolearn.d.ts.map +0 -1
  57. package/dist/autolearn.js.map +0 -1
  58. package/dist/availability.d.ts.map +0 -1
  59. package/dist/availability.js.map +0 -1
  60. package/dist/benchmarks/e1.3/incident-recall-eval.js +0 -78
  61. package/dist/benchmarks/e1.3/incident-recall-eval.js.map +0 -1
  62. package/dist/benchmarks/e1.3/slack-1000-event-smoke.js +0 -103
  63. package/dist/benchmarks/e1.3/slack-1000-event-smoke.js.map +0 -1
  64. package/dist/capture.d.ts.map +0 -1
  65. package/dist/capture.js.map +0 -1
  66. package/dist/card-detail.d.ts.map +0 -1
  67. package/dist/card-detail.js.map +0 -1
  68. package/dist/card.d.ts.map +0 -1
  69. package/dist/card.js.map +0 -1
  70. package/dist/cli.d.ts.map +0 -1
  71. package/dist/cli.js.map +0 -1
  72. package/dist/client.d.ts.map +0 -1
  73. package/dist/client.js.map +0 -1
  74. package/dist/compare.d.ts.map +0 -1
  75. package/dist/compare.js.map +0 -1
  76. package/dist/config.d.ts.map +0 -1
  77. package/dist/config.js.map +0 -1
  78. package/dist/connectors/github/backfill.d.ts.map +0 -1
  79. package/dist/connectors/github/backfill.js.map +0 -1
  80. package/dist/connectors/github/cli-impl.d.ts.map +0 -1
  81. package/dist/connectors/github/cli-impl.js.map +0 -1
  82. package/dist/connectors/github/deletion.d.ts.map +0 -1
  83. package/dist/connectors/github/deletion.js.map +0 -1
  84. package/dist/connectors/github/dlq.d.ts.map +0 -1
  85. package/dist/connectors/github/dlq.js.map +0 -1
  86. package/dist/connectors/github/idempotency.d.ts.map +0 -1
  87. package/dist/connectors/github/idempotency.js.map +0 -1
  88. package/dist/connectors/github/ingest.d.ts.map +0 -1
  89. package/dist/connectors/github/ingest.js.map +0 -1
  90. package/dist/connectors/github/octokit-client.d.ts.map +0 -1
  91. package/dist/connectors/github/octokit-client.js.map +0 -1
  92. package/dist/connectors/github/ratelimit.d.ts.map +0 -1
  93. package/dist/connectors/github/ratelimit.js.map +0 -1
  94. package/dist/connectors/github/scope.d.ts.map +0 -1
  95. package/dist/connectors/github/scope.js.map +0 -1
  96. package/dist/connectors/github/signature.d.ts.map +0 -1
  97. package/dist/connectors/github/signature.js.map +0 -1
  98. package/dist/connectors/github/tenant-routing.d.ts.map +0 -1
  99. package/dist/connectors/github/tenant-routing.js.map +0 -1
  100. package/dist/connectors/github/transform.d.ts.map +0 -1
  101. package/dist/connectors/github/transform.js.map +0 -1
  102. package/dist/connectors/github/types.d.ts.map +0 -1
  103. package/dist/connectors/github/types.js.map +0 -1
  104. package/dist/connectors/slack/backfill.d.ts.map +0 -1
  105. package/dist/connectors/slack/backfill.js.map +0 -1
  106. package/dist/connectors/slack/deletion.d.ts.map +0 -1
  107. package/dist/connectors/slack/deletion.js.map +0 -1
  108. package/dist/connectors/slack/dlq.d.ts.map +0 -1
  109. package/dist/connectors/slack/dlq.js.map +0 -1
  110. package/dist/connectors/slack/idempotency.d.ts.map +0 -1
  111. package/dist/connectors/slack/idempotency.js.map +0 -1
  112. package/dist/connectors/slack/ingest.d.ts.map +0 -1
  113. package/dist/connectors/slack/ingest.js.map +0 -1
  114. package/dist/connectors/slack/ratelimit.d.ts.map +0 -1
  115. package/dist/connectors/slack/ratelimit.js.map +0 -1
  116. package/dist/connectors/slack/scope.d.ts.map +0 -1
  117. package/dist/connectors/slack/scope.js.map +0 -1
  118. package/dist/connectors/slack/signature.d.ts.map +0 -1
  119. package/dist/connectors/slack/signature.js.map +0 -1
  120. package/dist/connectors/slack/tenant-routing.d.ts.map +0 -1
  121. package/dist/connectors/slack/tenant-routing.js.map +0 -1
  122. package/dist/connectors/slack/transform.d.ts.map +0 -1
  123. package/dist/connectors/slack/transform.js.map +0 -1
  124. package/dist/connectors/slack/types.d.ts.map +0 -1
  125. package/dist/connectors/slack/types.js.map +0 -1
  126. package/dist/connectors/slack/web-client.d.ts.map +0 -1
  127. package/dist/connectors/slack/web-client.js.map +0 -1
  128. package/dist/connectors/slack/workspaces.d.ts.map +0 -1
  129. package/dist/connectors/slack/workspaces.js.map +0 -1
  130. package/dist/consolidate.d.ts.map +0 -1
  131. package/dist/consolidate.js.map +0 -1
  132. package/dist/correction-latency.d.ts.map +0 -1
  133. package/dist/correction-latency.js.map +0 -1
  134. package/dist/customer-notes.d.ts.map +0 -1
  135. package/dist/customer-notes.js.map +0 -1
  136. package/dist/dag.d.ts.map +0 -1
  137. package/dist/dag.js.map +0 -1
  138. package/dist/dashboard.d.ts.map +0 -1
  139. package/dist/dashboard.js.map +0 -1
  140. package/dist/db.d.ts.map +0 -1
  141. package/dist/db.js.map +0 -1
  142. package/dist/decisions.d.ts.map +0 -1
  143. package/dist/decisions.js.map +0 -1
  144. package/dist/dedupe.d.ts.map +0 -1
  145. package/dist/dedupe.js.map +0 -1
  146. package/dist/embedding-provider.d.ts.map +0 -1
  147. package/dist/embedding-provider.js.map +0 -1
  148. package/dist/embeddings.d.ts.map +0 -1
  149. package/dist/embeddings.js.map +0 -1
  150. package/dist/eval-suite.d.ts.map +0 -1
  151. package/dist/eval-suite.js.map +0 -1
  152. package/dist/eval.d.ts.map +0 -1
  153. package/dist/eval.js.map +0 -1
  154. package/dist/extensions/openclaw-plugin/index.js.map +0 -1
  155. package/dist/extract.d.ts.map +0 -1
  156. package/dist/extract.js.map +0 -1
  157. package/dist/forward-claim-detector.d.ts.map +0 -1
  158. package/dist/forward-claim-detector.js.map +0 -1
  159. package/dist/goals.d.ts.map +0 -1
  160. package/dist/goals.js.map +0 -1
  161. package/dist/graph-extract.d.ts.map +0 -1
  162. package/dist/graph-extract.js.map +0 -1
  163. package/dist/graph-recall.d.ts.map +0 -1
  164. package/dist/graph-recall.js.map +0 -1
  165. package/dist/graph-stream.d.ts.map +0 -1
  166. package/dist/graph-stream.js.map +0 -1
  167. package/dist/graph-view.d.ts.map +0 -1
  168. package/dist/graph-view.js.map +0 -1
  169. package/dist/graph.d.ts.map +0 -1
  170. package/dist/graph.js.map +0 -1
  171. package/dist/handoff.d.ts.map +0 -1
  172. package/dist/handoff.js.map +0 -1
  173. package/dist/hooks.d.ts.map +0 -1
  174. package/dist/hooks.js.map +0 -1
  175. package/dist/importers.d.ts.map +0 -1
  176. package/dist/importers.js.map +0 -1
  177. package/dist/incidents.d.ts.map +0 -1
  178. package/dist/incidents.js.map +0 -1
  179. package/dist/index.d.ts.map +0 -1
  180. package/dist/index.js.map +0 -1
  181. package/dist/invalidation.d.ts.map +0 -1
  182. package/dist/invalidation.js.map +0 -1
  183. package/dist/mcp/framing.d.ts.map +0 -1
  184. package/dist/mcp/framing.js.map +0 -1
  185. package/dist/mcp/server.d.ts.map +0 -1
  186. package/dist/mcp/server.js.map +0 -1
  187. package/dist/memory-value-weights.d.ts.map +0 -1
  188. package/dist/memory-value-weights.js.map +0 -1
  189. package/dist/memory-value.d.ts.map +0 -1
  190. package/dist/memory-value.js.map +0 -1
  191. package/dist/memory.d.ts.map +0 -1
  192. package/dist/memory.js.map +0 -1
  193. package/dist/multihop.d.ts.map +0 -1
  194. package/dist/multihop.js.map +0 -1
  195. package/dist/owner-validation.d.ts.map +0 -1
  196. package/dist/owner-validation.js.map +0 -1
  197. package/dist/path-context.d.ts.map +0 -1
  198. package/dist/path-context.js.map +0 -1
  199. package/dist/physics-config.d.ts.map +0 -1
  200. package/dist/physics-config.js.map +0 -1
  201. package/dist/physics-state.d.ts.map +0 -1
  202. package/dist/physics-state.js.map +0 -1
  203. package/dist/physics.d.ts.map +0 -1
  204. package/dist/physics.js.map +0 -1
  205. package/dist/policies.d.ts.map +0 -1
  206. package/dist/policies.js.map +0 -1
  207. package/dist/postinstall.d.ts.map +0 -1
  208. package/dist/postinstall.js.map +0 -1
  209. package/dist/predictions.d.ts.map +0 -1
  210. package/dist/predictions.js.map +0 -1
  211. package/dist/processes.d.ts.map +0 -1
  212. package/dist/processes.js.map +0 -1
  213. package/dist/project-briefs.d.ts.map +0 -1
  214. package/dist/project-briefs.js.map +0 -1
  215. package/dist/project-identity.d.ts.map +0 -1
  216. package/dist/project-identity.js.map +0 -1
  217. package/dist/provenance-coverage.d.ts.map +0 -1
  218. package/dist/provenance-coverage.js.map +0 -1
  219. package/dist/rate-limit.d.ts.map +0 -1
  220. package/dist/rate-limit.js.map +0 -1
  221. package/dist/raw-archive-mirror-cleanup.d.ts.map +0 -1
  222. package/dist/raw-archive-mirror-cleanup.js.map +0 -1
  223. package/dist/raw-archive.d.ts.map +0 -1
  224. package/dist/raw-archive.js.map +0 -1
  225. package/dist/recall-history.d.ts.map +0 -1
  226. package/dist/recall-history.js.map +0 -1
  227. package/dist/recall-scope.d.ts.map +0 -1
  228. package/dist/recall-scope.js.map +0 -1
  229. package/dist/recall-trace.d.ts.map +0 -1
  230. package/dist/recall-trace.js.map +0 -1
  231. package/dist/refine-llm.d.ts.map +0 -1
  232. package/dist/refine-llm.js.map +0 -1
  233. package/dist/reject-flow.d.ts.map +0 -1
  234. package/dist/reject-flow.js.map +0 -1
  235. package/dist/rejection.d.ts.map +0 -1
  236. package/dist/rejection.js.map +0 -1
  237. package/dist/replay.d.ts.map +0 -1
  238. package/dist/replay.js.map +0 -1
  239. package/dist/rerankers/cross-encoder.d.ts.map +0 -1
  240. package/dist/rerankers/cross-encoder.js.map +0 -1
  241. package/dist/rerankers/index.d.ts.map +0 -1
  242. package/dist/rerankers/index.js.map +0 -1
  243. package/dist/rerankers/jev.d.ts.map +0 -1
  244. package/dist/rerankers/jev.js.map +0 -1
  245. package/dist/rerankers/llm.d.ts.map +0 -1
  246. package/dist/rerankers/llm.js.map +0 -1
  247. package/dist/rerankers/types.d.ts.map +0 -1
  248. package/dist/rerankers/types.js.map +0 -1
  249. package/dist/rrf.d.ts.map +0 -1
  250. package/dist/rrf.js.map +0 -1
  251. package/dist/salience.d.ts.map +0 -1
  252. package/dist/salience.js.map +0 -1
  253. package/dist/scheduler.d.ts.map +0 -1
  254. package/dist/scheduler.js.map +0 -1
  255. package/dist/scope.d.ts.map +0 -1
  256. package/dist/scope.js.map +0 -1
  257. package/dist/search.d.ts.map +0 -1
  258. package/dist/search.js.map +0 -1
  259. package/dist/secret-detect.d.ts.map +0 -1
  260. package/dist/secret-detect.js.map +0 -1
  261. package/dist/server-detect.d.ts.map +0 -1
  262. package/dist/server-detect.js.map +0 -1
  263. package/dist/server.d.ts.map +0 -1
  264. package/dist/server.js.map +0 -1
  265. package/dist/shared.d.ts.map +0 -1
  266. package/dist/shared.js.map +0 -1
  267. package/dist/skills.d.ts.map +0 -1
  268. package/dist/skills.js.map +0 -1
  269. package/dist/sleep-redact.d.ts +0 -58
  270. package/dist/sleep-redact.d.ts.map +0 -1
  271. package/dist/sleep-redact.js +0 -80
  272. package/dist/sleep-redact.js.map +0 -1
  273. package/dist/src/ablation.js +0 -138
  274. package/dist/src/ablation.js.map +0 -1
  275. package/dist/src/ambient.js +0 -148
  276. package/dist/src/ambient.js.map +0 -1
  277. package/dist/src/api.js +0 -2109
  278. package/dist/src/api.js.map +0 -1
  279. package/dist/src/audit-prune.js +0 -109
  280. package/dist/src/audit-prune.js.map +0 -1
  281. package/dist/src/audit.js +0 -213
  282. package/dist/src/audit.js.map +0 -1
  283. package/dist/src/auth.js +0 -97
  284. package/dist/src/auth.js.map +0 -1
  285. package/dist/src/autolearn.js +0 -174
  286. package/dist/src/autolearn.js.map +0 -1
  287. package/dist/src/availability.js +0 -94
  288. package/dist/src/availability.js.map +0 -1
  289. package/dist/src/capture.js +0 -1346
  290. package/dist/src/capture.js.map +0 -1
  291. package/dist/src/card-detail.js +0 -15
  292. package/dist/src/card-detail.js.map +0 -1
  293. package/dist/src/card.js +0 -19
  294. package/dist/src/card.js.map +0 -1
  295. package/dist/src/cli.js +0 -9387
  296. package/dist/src/cli.js.map +0 -1
  297. package/dist/src/client.js +0 -237
  298. package/dist/src/client.js.map +0 -1
  299. package/dist/src/compare.js +0 -122
  300. package/dist/src/compare.js.map +0 -1
  301. package/dist/src/config.js +0 -135
  302. package/dist/src/config.js.map +0 -1
  303. package/dist/src/connectors/github/backfill.js +0 -281
  304. package/dist/src/connectors/github/backfill.js.map +0 -1
  305. package/dist/src/connectors/github/cli-impl.js +0 -234
  306. package/dist/src/connectors/github/cli-impl.js.map +0 -1
  307. package/dist/src/connectors/github/deletion.js +0 -85
  308. package/dist/src/connectors/github/deletion.js.map +0 -1
  309. package/dist/src/connectors/github/dlq.js +0 -190
  310. package/dist/src/connectors/github/dlq.js.map +0 -1
  311. package/dist/src/connectors/github/idempotency.js +0 -27
  312. package/dist/src/connectors/github/idempotency.js.map +0 -1
  313. package/dist/src/connectors/github/ingest.js +0 -156
  314. package/dist/src/connectors/github/ingest.js.map +0 -1
  315. package/dist/src/connectors/github/octokit-client.js +0 -70
  316. package/dist/src/connectors/github/octokit-client.js.map +0 -1
  317. package/dist/src/connectors/github/ratelimit.js +0 -31
  318. package/dist/src/connectors/github/ratelimit.js.map +0 -1
  319. package/dist/src/connectors/github/scope.js +0 -13
  320. package/dist/src/connectors/github/scope.js.map +0 -1
  321. package/dist/src/connectors/github/signature.js +0 -81
  322. package/dist/src/connectors/github/signature.js.map +0 -1
  323. package/dist/src/connectors/github/tenant-routing.js +0 -69
  324. package/dist/src/connectors/github/tenant-routing.js.map +0 -1
  325. package/dist/src/connectors/github/transform.js +0 -103
  326. package/dist/src/connectors/github/transform.js.map +0 -1
  327. package/dist/src/connectors/github/types.js +0 -100
  328. package/dist/src/connectors/github/types.js.map +0 -1
  329. package/dist/src/connectors/slack/backfill.js +0 -78
  330. package/dist/src/connectors/slack/backfill.js.map +0 -1
  331. package/dist/src/connectors/slack/deletion.js +0 -59
  332. package/dist/src/connectors/slack/deletion.js.map +0 -1
  333. package/dist/src/connectors/slack/dlq.js +0 -224
  334. package/dist/src/connectors/slack/dlq.js.map +0 -1
  335. package/dist/src/connectors/slack/idempotency.js +0 -31
  336. package/dist/src/connectors/slack/idempotency.js.map +0 -1
  337. package/dist/src/connectors/slack/ingest.js +0 -109
  338. package/dist/src/connectors/slack/ingest.js.map +0 -1
  339. package/dist/src/connectors/slack/ratelimit.js +0 -18
  340. package/dist/src/connectors/slack/ratelimit.js.map +0 -1
  341. package/dist/src/connectors/slack/scope.js +0 -13
  342. package/dist/src/connectors/slack/scope.js.map +0 -1
  343. package/dist/src/connectors/slack/signature.js +0 -27
  344. package/dist/src/connectors/slack/signature.js.map +0 -1
  345. package/dist/src/connectors/slack/tenant-routing.js +0 -41
  346. package/dist/src/connectors/slack/tenant-routing.js.map +0 -1
  347. package/dist/src/connectors/slack/transform.js +0 -47
  348. package/dist/src/connectors/slack/transform.js.map +0 -1
  349. package/dist/src/connectors/slack/types.js +0 -35
  350. package/dist/src/connectors/slack/types.js.map +0 -1
  351. package/dist/src/connectors/slack/web-client.js +0 -43
  352. package/dist/src/connectors/slack/web-client.js.map +0 -1
  353. package/dist/src/connectors/slack/workspaces.js +0 -62
  354. package/dist/src/connectors/slack/workspaces.js.map +0 -1
  355. package/dist/src/consolidate.js +0 -978
  356. package/dist/src/consolidate.js.map +0 -1
  357. package/dist/src/correction-latency.js +0 -74
  358. package/dist/src/correction-latency.js.map +0 -1
  359. package/dist/src/customer-notes.js +0 -325
  360. package/dist/src/customer-notes.js.map +0 -1
  361. package/dist/src/dag.js +0 -352
  362. package/dist/src/dag.js.map +0 -1
  363. package/dist/src/dashboard.js +0 -258
  364. package/dist/src/dashboard.js.map +0 -1
  365. package/dist/src/db.js +0 -2731
  366. package/dist/src/db.js.map +0 -1
  367. package/dist/src/decisions.js +0 -304
  368. package/dist/src/decisions.js.map +0 -1
  369. package/dist/src/dedupe.js +0 -141
  370. package/dist/src/dedupe.js.map +0 -1
  371. package/dist/src/embedding-provider.js +0 -314
  372. package/dist/src/embedding-provider.js.map +0 -1
  373. package/dist/src/embeddings.js +0 -543
  374. package/dist/src/embeddings.js.map +0 -1
  375. package/dist/src/eval-suite.js +0 -294
  376. package/dist/src/eval-suite.js.map +0 -1
  377. package/dist/src/eval.js +0 -187
  378. package/dist/src/eval.js.map +0 -1
  379. package/dist/src/extract.js +0 -117
  380. package/dist/src/extract.js.map +0 -1
  381. package/dist/src/forward-claim-detector.js +0 -117
  382. package/dist/src/forward-claim-detector.js.map +0 -1
  383. package/dist/src/goals.js +0 -399
  384. package/dist/src/goals.js.map +0 -1
  385. package/dist/src/graph-extract.js +0 -314
  386. package/dist/src/graph-extract.js.map +0 -1
  387. package/dist/src/graph-recall.js +0 -277
  388. package/dist/src/graph-recall.js.map +0 -1
  389. package/dist/src/graph-stream.js +0 -176
  390. package/dist/src/graph-stream.js.map +0 -1
  391. package/dist/src/graph-view.js +0 -310
  392. package/dist/src/graph-view.js.map +0 -1
  393. package/dist/src/graph.js +0 -762
  394. package/dist/src/graph.js.map +0 -1
  395. package/dist/src/handoff.js +0 -65
  396. package/dist/src/handoff.js.map +0 -1
  397. package/dist/src/hooks.js +0 -926
  398. package/dist/src/hooks.js.map +0 -1
  399. package/dist/src/importers.js +0 -881
  400. package/dist/src/importers.js.map +0 -1
  401. package/dist/src/incidents.js +0 -336
  402. package/dist/src/incidents.js.map +0 -1
  403. package/dist/src/index.js +0 -32
  404. package/dist/src/index.js.map +0 -1
  405. package/dist/src/invalidation.js +0 -129
  406. package/dist/src/invalidation.js.map +0 -1
  407. package/dist/src/mcp/framing.js +0 -45
  408. package/dist/src/mcp/framing.js.map +0 -1
  409. package/dist/src/mcp/server.js +0 -1298
  410. package/dist/src/mcp/server.js.map +0 -1
  411. package/dist/src/memory-value-weights.js +0 -31
  412. package/dist/src/memory-value-weights.js.map +0 -1
  413. package/dist/src/memory-value.js +0 -261
  414. package/dist/src/memory-value.js.map +0 -1
  415. package/dist/src/memory.js +0 -425
  416. package/dist/src/memory.js.map +0 -1
  417. package/dist/src/multihop.js +0 -35
  418. package/dist/src/multihop.js.map +0 -1
  419. package/dist/src/owner-validation.js +0 -56
  420. package/dist/src/owner-validation.js.map +0 -1
  421. package/dist/src/path-context.js +0 -48
  422. package/dist/src/path-context.js.map +0 -1
  423. package/dist/src/physics-config.js +0 -26
  424. package/dist/src/physics-config.js.map +0 -1
  425. package/dist/src/physics-state.js +0 -172
  426. package/dist/src/physics-state.js.map +0 -1
  427. package/dist/src/physics.js +0 -378
  428. package/dist/src/physics.js.map +0 -1
  429. package/dist/src/policies.js +0 -414
  430. package/dist/src/policies.js.map +0 -1
  431. package/dist/src/postinstall.js +0 -101
  432. package/dist/src/postinstall.js.map +0 -1
  433. package/dist/src/predictions.js +0 -629
  434. package/dist/src/predictions.js.map +0 -1
  435. package/dist/src/processes.js +0 -349
  436. package/dist/src/processes.js.map +0 -1
  437. package/dist/src/project-briefs.js +0 -492
  438. package/dist/src/project-briefs.js.map +0 -1
  439. package/dist/src/project-identity.js +0 -200
  440. package/dist/src/project-identity.js.map +0 -1
  441. package/dist/src/provenance-coverage.js +0 -23
  442. package/dist/src/provenance-coverage.js.map +0 -1
  443. package/dist/src/rate-limit.js +0 -60
  444. package/dist/src/rate-limit.js.map +0 -1
  445. package/dist/src/raw-archive-mirror-cleanup.js +0 -55
  446. package/dist/src/raw-archive-mirror-cleanup.js.map +0 -1
  447. package/dist/src/raw-archive.js +0 -91
  448. package/dist/src/raw-archive.js.map +0 -1
  449. package/dist/src/recall-history.js +0 -235
  450. package/dist/src/recall-history.js.map +0 -1
  451. package/dist/src/recall-scope.js +0 -89
  452. package/dist/src/recall-scope.js.map +0 -1
  453. package/dist/src/recall-trace.js +0 -186
  454. package/dist/src/recall-trace.js.map +0 -1
  455. package/dist/src/refine-llm.js +0 -156
  456. package/dist/src/refine-llm.js.map +0 -1
  457. package/dist/src/reject-flow.js +0 -209
  458. package/dist/src/reject-flow.js.map +0 -1
  459. package/dist/src/rejection.js +0 -160
  460. package/dist/src/rejection.js.map +0 -1
  461. package/dist/src/replay.js +0 -122
  462. package/dist/src/replay.js.map +0 -1
  463. package/dist/src/rerankers/cross-encoder.js +0 -137
  464. package/dist/src/rerankers/cross-encoder.js.map +0 -1
  465. package/dist/src/rerankers/index.js +0 -20
  466. package/dist/src/rerankers/index.js.map +0 -1
  467. package/dist/src/rerankers/jev.js +0 -119
  468. package/dist/src/rerankers/jev.js.map +0 -1
  469. package/dist/src/rerankers/llm.js +0 -80
  470. package/dist/src/rerankers/llm.js.map +0 -1
  471. package/dist/src/rerankers/types.js +0 -2
  472. package/dist/src/rerankers/types.js.map +0 -1
  473. package/dist/src/rrf.js +0 -61
  474. package/dist/src/rrf.js.map +0 -1
  475. package/dist/src/salience.js +0 -74
  476. package/dist/src/salience.js.map +0 -1
  477. package/dist/src/scheduler.js +0 -77
  478. package/dist/src/scheduler.js.map +0 -1
  479. package/dist/src/scope.js +0 -35
  480. package/dist/src/scope.js.map +0 -1
  481. package/dist/src/search.js +0 -1003
  482. package/dist/src/search.js.map +0 -1
  483. package/dist/src/secret-detect.js +0 -95
  484. package/dist/src/secret-detect.js.map +0 -1
  485. package/dist/src/server-detect.js +0 -231
  486. package/dist/src/server-detect.js.map +0 -1
  487. package/dist/src/server.js +0 -3255
  488. package/dist/src/server.js.map +0 -1
  489. package/dist/src/shared.js +0 -502
  490. package/dist/src/shared.js.map +0 -1
  491. package/dist/src/skills.js +0 -346
  492. package/dist/src/skills.js.map +0 -1
  493. package/dist/src/sleep-redact.js +0 -80
  494. package/dist/src/sleep-redact.js.map +0 -1
  495. package/dist/src/sso.js +0 -22
  496. package/dist/src/sso.js.map +0 -1
  497. package/dist/src/stdin.js +0 -41
  498. package/dist/src/stdin.js.map +0 -1
  499. package/dist/src/store.js +0 -3749
  500. package/dist/src/store.js.map +0 -1
  501. package/dist/src/tenant.js +0 -17
  502. package/dist/src/tenant.js.map +0 -1
  503. package/dist/src/trace.js +0 -72
  504. package/dist/src/trace.js.map +0 -1
  505. package/dist/src/version.js +0 -40
  506. package/dist/src/version.js.map +0 -1
  507. package/dist/src/working-memory.js +0 -157
  508. package/dist/src/working-memory.js.map +0 -1
  509. package/dist/src/yaml.js +0 -107
  510. package/dist/src/yaml.js.map +0 -1
  511. package/dist/sso.d.ts +0 -13
  512. package/dist/sso.d.ts.map +0 -1
  513. package/dist/sso.js +0 -22
  514. package/dist/sso.js.map +0 -1
  515. package/dist/stdin.d.ts.map +0 -1
  516. package/dist/stdin.js.map +0 -1
  517. package/dist/store.d.ts.map +0 -1
  518. package/dist/store.js.map +0 -1
  519. package/dist/tenant.d.ts.map +0 -1
  520. package/dist/tenant.js.map +0 -1
  521. package/dist/trace.d.ts.map +0 -1
  522. package/dist/trace.js.map +0 -1
  523. package/dist/version.d.ts.map +0 -1
  524. package/dist/version.js.map +0 -1
  525. package/dist/working-memory.d.ts.map +0 -1
  526. package/dist/working-memory.js.map +0 -1
  527. package/dist/yaml.d.ts.map +0 -1
  528. package/dist/yaml.js.map +0 -1
@@ -1,978 +0,0 @@
1
- /**
2
- * Consolidation engine ("Sleep") for Hippo.
3
- *
4
- * Steps:
5
- * 1. Decay pass - remove entries below strength threshold
6
- * 2. Merge pass - find episodic entries with high text overlap, create semantic summaries
7
- * 3. Stats tracking
8
- */
9
- import { evalNow, isRecallBoostAblated } from './ablation.js';
10
- import { Layer, calculateStrength, createMemory } from './memory.js';
11
- import { loadAllEntries, batchWriteAndDelete, appendConsolidationRun, replaceDetectedConflicts, loadSessionDecayContext, incrementSleepCount, findPromotableSessions, traceExistsForSession, listSessionEvents, } from './store.js';
12
- import { textOverlap, markRetrieved } from './search.js';
13
- import { compareEntryIdentity } from './compare.js';
14
- import { openHippoDb, closeHippoDb } from './db.js';
15
- import { rejectionDigest, findRejectedValue } from './rejection.js';
16
- import { loadPhysicsState, savePhysicsState, refreshParticleProperties } from './physics-state.js';
17
- import { simulate } from './physics.js';
18
- import { loadConfig } from './config.js';
19
- import { sampleForReplay } from './replay.js';
20
- import { renderTraceContent } from './trace.js';
21
- import { resolveTenantId } from './tenant.js';
22
- import { rescueSet, rankNonPinnedByTenant, validateWeights } from './memory-value.js';
23
- import { MEMORY_VALUE_WEIGHTS, SOURCE_ARTIFACT_SHA256 } from './memory-value-weights.js';
24
- import { appendAuditEvent } from './audit.js';
25
- const DECAY_THRESHOLD = 0.05;
26
- const MERGE_OVERLAP_THRESHOLD = 0.35; // Jaccard similarity for "related"
27
- const MERGE_MIN_CLUSTER = 2; // minimum cluster size to merge
28
- // Half-life scale for merged source episodics. Demotion must go through
29
- // half_life_days: calculateStrength() recomputes live strength from
30
- // last_retrieved/half_life and never reads the stored strength field, so a
31
- // stored-strength write is inert for ranking and gets overwritten by the
32
- // next sleep's decay pass anyway.
33
- const MERGE_SOURCE_HALF_LIFE_FACTOR = 0.3;
34
- // Contradictions should be gated by content overlap, not shared tags. Tags like
35
- // `feedback` / `policy` are too coarse and can make unrelated rules look like
36
- // conflicts before the polarity heuristics run.
37
- // Jaccard threshold on stopword-filtered tokens. Only applied after a polarity
38
- // signal has already been detected (explicit pair or inferred negation), so
39
- // this just filters out drive-by topic similarity, not semantic drift.
40
- const CONFLICT_OVERLAP_THRESHOLD = 0.5;
41
- // Minimum distinctive shared tokens before we trust an overlap score. Filters
42
- // out cases where two memories share only common English + a project name.
43
- const CONFLICT_MIN_RARE_SHARED = 2;
44
- // Polarity is detected on the first N words only. A stray "not" in the middle
45
- // of a long memory shouldn't flip the whole thing negative.
46
- const POLARITY_WINDOW_WORDS = 40;
47
- const CONFLICT_STOPWORDS = new Set([
48
- 'the', 'a', 'an', 'is', 'was', 'are', 'were', 'be', 'been', 'being', 'to', 'of', 'in',
49
- 'for', 'on', 'with', 'at', 'by', 'from', 'it', 'this', 'that', 'and', 'or', 'but', 'so',
50
- 'if', 'as', 'we', 'i', 'you', 'they', 'he', 'she', 'my', 'our', 'your', 'its', 'his',
51
- 'her', 'their', 'up', 'out', 'just', 'also', 'then', 'than', 'some', 'all', 'any',
52
- 'each', 'very', 'too', 'do', 'did', 'does', 'has', 'had', 'have', 'will', 'would',
53
- 'could', 'should', 'may', 'might', 'can', 'shall', 'when', 'where', 'what', 'which',
54
- 'who', 'how', 'why', 'there', 'here', 'about', 'into', 'over', 'after', 'before',
55
- 'between', 'through', 'during', 'against', 'within', 'without', 'toward', 'upon',
56
- 'more', 'most', 'less', 'least', 'other', 'such', 'same', 'new', 'old', 'one', 'two',
57
- ]);
58
- const REPLAY_COUNT_DEFAULT = 5;
59
- function isJsonString(value) {
60
- return typeof value === 'string';
61
- }
62
- /**
63
- * Run a full consolidation pass.
64
- */
65
- export async function consolidate(hippoRoot, options = {}) {
66
- const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
67
- const dryRun = options.dryRun ?? false;
68
- const result = {
69
- decayed: 0,
70
- removed: 0,
71
- merged: 0,
72
- semanticCreated: 0,
73
- replayed: 0,
74
- promotedTraces: 0,
75
- extractionCandidates: 0,
76
- extracted: 0,
77
- dagCandidateClusters: 0,
78
- dagSummariesCreated: 0,
79
- summariesRebuilt: 0,
80
- summariesRebuildFailed: 0,
81
- summariesZeroChildSkipped: 0,
82
- summariesRebuildRefused: 0,
83
- summariesRebuildCapped: false,
84
- entityProfilesCreated: 0,
85
- dryRun,
86
- details: [],
87
- physicsSimulated: 0,
88
- };
89
- // L9: host-wide by design. Consolidation runs across all tenants in one
90
- // pass — per-tenant filtering would create N consolidation runs per host
91
- // with no cross-tenant dedup. The api.sleep audit row tags this with the
92
- // admin synthetic actor; see api.ts:2050 for the rationale.
93
- const all = loadAllEntries(hippoRoot);
94
- // Load decay options from config + session context
95
- const config = loadConfig(hippoRoot);
96
- const sessionCtx = loadSessionDecayContext(hippoRoot);
97
- const decayOpts = {
98
- decayBasis: config.decayBasis,
99
- avgSessionIntervalDays: sessionCtx.avgSessionIntervalDays,
100
- sleepCount: sessionCtx.sleepCount,
101
- };
102
- // Collect all writes/deletes and batch them at the end
103
- const pendingWrites = [];
104
- const pendingDeletes = [];
105
- // -------------------------------------------------------------------------
106
- // 1. Decay pass
107
- // -------------------------------------------------------------------------
108
- // LC2-E3 (opt-in, default off; docs/plans/2026-08-10-lc2-e3-mv-wiring.md):
109
- // flag OFF keeps the single-phase loop below byte-identical to pre-E3
110
- // behavior (pre-registered gate G2). Flag ON restructures into two phases:
111
- // phase 1 classifies every entry (condemned vs survivor) with ZERO
112
- // commits; phase 2 runs rescueSet over the per-tenant candidate groups,
113
- // then commits — rescued entries get the standard survivor bookkeeping
114
- // refresh (stored strength + effective confidence; no half-life edits, no
115
- // rank-derived writes) and are pushed to survivors so they fully
116
- // participate in this cycle's merge/physics/conflict passes; non-rescued
117
- // condemned entries follow the existing pendingDeletes/result.removed/
118
- // details path.
119
- const survivors = [];
120
- let rescuedIds = new Set();
121
- // Carried forward to the post-flush "4. Log run" section below, where the
122
- // mv_rescue audit rows are actually written (code-review fix: writing them
123
- // here, before batchWriteAndDelete, would assert rescues for a cycle whose
124
- // effects might never land if a later phase throws).
125
- let rescuedEntries = [];
126
- let rankById = new Map();
127
- if (config.memoryValue.enabled) {
128
- // --- Phase 1: classify (zero commits) ---
129
- const condemned = [];
130
- const strengthById = new Map();
131
- for (const entry of all) {
132
- const strength = calculateStrength(entry, now, decayOpts);
133
- strengthById.set(entry.id, strength);
134
- if (!entry.pinned && strength < DECAY_THRESHOLD) {
135
- condemned.push(entry);
136
- }
137
- }
138
- // --- Phase 2a: rescue decision (pure compute) ---
139
- // Runs under --dry-run too (only pendingDeletes/the audit write in
140
- // "4. Log run" below stay !dryRun-gated), so the preview matches what a
141
- // real run would decide.
142
- const condemnedIds = new Set(condemned.map((e) => e.id));
143
- // Fail-loud must not depend on condemnation traffic (round-2 code-review
144
- // P2-2): validate the frozen weights constant unconditionally, even on a
145
- // sleep with nothing condemned.
146
- validateWeights();
147
- if (condemnedIds.size > 0) {
148
- // Compute the per-tenant ranking ONCE (round-2 code-review P2-2):
149
- // rankById feeds both rescueSet's decision (via precomputedRanks,
150
- // skipping its own internal rankNonPinnedByTenant call) and the
151
- // detail/audit rank context below, so the whole-store ranking pass
152
- // runs a single time per sleep instead of twice, and only when there
153
- // is actually something condemned to rank against.
154
- rankById = rankNonPinnedByTenant(all, now);
155
- rescuedIds = rescueSet(all, condemnedIds, now, MEMORY_VALUE_WEIGHTS, SOURCE_ARTIFACT_SHA256, rankById);
156
- // Review-round F2: entries with a non-finite computed feature (e.g. a
157
- // malformed `created` string -> Date.parse NaN) score -Infinity in
158
- // rankById and rescueSet's explicit finite-score guard means they can
159
- // never be rescued. Surfaced here as one details warning line naming
160
- // them, rather than leaving it a silent NaN-driven scoring detail.
161
- const nonFiniteIds = [...rankById.entries()]
162
- .filter(([, info]) => !Number.isFinite(info.score))
163
- .map(([id]) => id);
164
- if (nonFiniteIds.length > 0) {
165
- result.details.push(` ⚠️ memory-value: skipped ${nonFiniteIds.length} entr${nonFiniteIds.length === 1 ? 'y' : 'ies'} ` +
166
- `with non-finite computed features (never rescued): ${nonFiniteIds.join(', ')}`);
167
- }
168
- }
169
- // --- Phase 2b: commit, one pass over `all` in ITS ORIGINAL ORDER ---
170
- // (review-round F4: rescued entries used to be appended at the tail of
171
- // survivors, systematically starving them in downstream order-sensitive
172
- // passes like extraction's slice(0,20) — a single pass over `all`
173
- // preserves flag-off's ordering semantics exactly.)
174
- for (const entry of all) {
175
- const strength = strengthById.get(entry.id);
176
- if (!entry.pinned && strength < DECAY_THRESHOLD) {
177
- if (rescuedIds.has(entry.id)) {
178
- // Rescued (D1): standard survivor stored-strength refresh (P2-1).
179
- // Confidence is left alone here: it is an epistemic tier, not a
180
- // cached computation, so resolveConfidence derives it on read.
181
- const updated = { ...entry, strength };
182
- survivors.push(updated);
183
- if (!dryRun && strength !== entry.strength) {
184
- pendingWrites.push(updated);
185
- }
186
- result.decayed++;
187
- rescuedEntries.push(updated);
188
- const rank = rankById.get(entry.id);
189
- const rankNote = rank
190
- ? ` - rescued (rank ${rank.rank}/${rank.totalNonPinned} in tenant ${rank.tenantId}, top ${rank.keepN})`
191
- : ' - rescued';
192
- result.details.push(` 🛟 ${entry.id} (strength ${strength.toFixed(4)} < ${DECAY_THRESHOLD})${rankNote}`);
193
- }
194
- else {
195
- result.removed++;
196
- result.details.push(` 🗑 removed ${entry.id} (strength ${strength.toFixed(4)} < ${DECAY_THRESHOLD})`);
197
- if (!dryRun) {
198
- pendingDeletes.push(entry.id);
199
- }
200
- }
201
- }
202
- else {
203
- const updated = { ...entry, strength };
204
- survivors.push(updated);
205
- if (!dryRun && strength !== entry.strength) {
206
- pendingWrites.push(updated);
207
- }
208
- result.decayed++;
209
- }
210
- }
211
- }
212
- else {
213
- for (const entry of all) {
214
- const strength = calculateStrength(entry, now, decayOpts);
215
- if (!entry.pinned && strength < DECAY_THRESHOLD) {
216
- result.removed++;
217
- result.details.push(` 🗑 removed ${entry.id} (strength ${strength.toFixed(4)} < ${DECAY_THRESHOLD})`);
218
- if (!dryRun) {
219
- pendingDeletes.push(entry.id);
220
- }
221
- }
222
- else {
223
- // Only strength is a cached computation; confidence stays as stored.
224
- const updated = { ...entry, strength };
225
- survivors.push(updated);
226
- if (!dryRun && strength !== entry.strength) {
227
- pendingWrites.push(updated);
228
- }
229
- result.decayed++;
230
- }
231
- }
232
- }
233
- // AT1 rejection-guard db handle (docs/plans/2026-08-15-at1-rejected-value-tombstone.md):
234
- // covers BOTH the auto-promote pass (1.4, immediately below) and the merge
235
- // pass (3, further down) — both build deterministic content that
236
- // batchWriteAndDelete writes through the guard's bypass, so both need a
237
- // producer-side tombstone check before pushing to pendingWrites.
238
- //
239
- // T3 fix (2026-08-15 hardening pass, perf hygiene): memoized lazy getter,
240
- // not an eager open. The handle only serves these two tombstone checks —
241
- // a sleep with zero promotable sessions and zero merge clusters never
242
- // reaches either use site, so opening it unconditionally on every
243
- // non-dry-run sleep paid a db-open cost for nothing. dryRun still never
244
- // opens (getConsolidateDb short-circuits before touching the handle).
245
- // Every `if (consolidateDb)` guard below becomes `const consolidateDb =
246
- // getConsolidateDb();` at its use site — same semantics, opened on first
247
- // real use. consolidateDbOpened (not just a truthy handle check) is the
248
- // single source of truth for "was this ever opened", so the finally below
249
- // closes it exactly once and never double-opens.
250
- //
251
- // AT1 P2 fix (codex, handle-leak restructure): the getter's lifetime must
252
- // start IMMEDIATELY before the try whose finally closes it, covering every
253
- // phase that can touch it — not just the merge pass. Previously the
254
- // try/finally wrapped only section 3 (merge pass); an exception thrown by
255
- // auto-promote (1.4), replay (1.5), batch extraction (1.6), the DAG
256
- // passes (1.7-1.9), or physics (2) would propagate past an open handle
257
- // with nothing to close it. No behavior change on the happy path — each
258
- // of those phases already best-effort catches its own exceptions today
259
- // (physics has its own try/catch below; each DAG pass wraps its own
260
- // dynamic import + call in try/catch) — this only closes the handle on
261
- // the rare path where one of them throws past its own catch. Deliberately
262
- // NOT re-indented (mechanical wrap only, kept surgical): every statement
263
- // between this try and its finally (below, at the end of the merge pass)
264
- // stays at its original indentation.
265
- let consolidateDbHandle = null;
266
- let consolidateDbOpened = false;
267
- const getConsolidateDb = () => {
268
- if (dryRun)
269
- return null;
270
- if (!consolidateDbOpened) {
271
- consolidateDbHandle = openHippoDb(hippoRoot);
272
- consolidateDbOpened = true;
273
- }
274
- return consolidateDbHandle;
275
- };
276
- // Declared here (not at the merge pass, its point of use) so it survives
277
- // this try/finally — a `let` declared INSIDE the try would go out of
278
- // scope before the `if (mergesSkippedRejected > 0)` check that reads it
279
- // after the finally closes the handle.
280
- let mergesSkippedRejected = 0;
281
- try {
282
- // -------------------------------------------------------------------------
283
- // 1.4. Auto-promote complete sessions to traces
284
- // -------------------------------------------------------------------------
285
- //
286
- // For each session within the configured window that has a `session_complete`
287
- // event and no existing trace (idempotency via the source_session_id column),
288
- // render the action sequence as markdown and persist a Layer.Trace memory.
289
- // Traces inherit decay, search, replay, and physics from the base MemoryEntry.
290
- if (!dryRun && config.autoTraceCapture !== false) {
291
- let tracesSkippedRejected = 0;
292
- const windowDays = config.autoTraceWindowDays ?? 7;
293
- const sinceMs = now.getTime() - windowDays * 24 * 60 * 60 * 1000;
294
- // Auto-trace currently runs in a single-tenant context (the env-resolved
295
- // tenant for this process). Multi-tenant deployments that want
296
- // consolidation across all tenants need a per-tenant loop layered on top
297
- // of this — tracked in docs/plans/2026-05-02-continuity-tables-tenant-scope.md.
298
- const consolidationTenant = resolveTenantId({});
299
- const promotable = findPromotableSessions(hippoRoot, consolidationTenant, sinceMs);
300
- for (const session of promotable) {
301
- // Idempotency: skip if a trace for this session already exists.
302
- if (traceExistsForSession(hippoRoot, consolidationTenant, session.session_id))
303
- continue;
304
- const events = listSessionEvents(hippoRoot, consolidationTenant, {
305
- session_id: session.session_id,
306
- limit: 1000,
307
- });
308
- const completeEvent = events.find((e) => e.event_type === 'session_complete');
309
- if (!completeEvent)
310
- continue; // defence-in-depth; findPromotableSessions filters already.
311
- const outcomeRaw = completeEvent.content;
312
- if (outcomeRaw !== 'success' && outcomeRaw !== 'failure' && outcomeRaw !== 'partial') {
313
- // Malformed terminal event — skip rather than crash the whole sleep.
314
- continue;
315
- }
316
- const outcome = outcomeRaw;
317
- const steps = events
318
- .filter((e) => e.event_type !== 'session_complete')
319
- .map((e) => ({ action: e.content, observation: '' }));
320
- // SAFETY: session event metadata is a free-form Record<string, unknown>
321
- // bag; summary is optional and is only trusted once isJsonString below
322
- // confirms it is actually a string.
323
- const summaryValue = completeEvent.metadata.summary;
324
- const summary = isJsonString(summaryValue) ? summaryValue : '(untitled)';
325
- const trace = createMemory(renderTraceContent({ task: summary, steps, outcome }), {
326
- layer: Layer.Trace,
327
- trace_outcome: outcome,
328
- source_session_id: session.session_id,
329
- tags: ['auto-promoted'],
330
- source: 'auto-promote',
331
- // T1 fix (2026-08-15 hardening pass): stamp the trace into the SAME
332
- // tenant the traceExistsForSession idempotency check (above) runs
333
- // under. Before
334
- // this, createMemory omitted tenantId and the trace always landed
335
- // 'default' (memory.ts:535) while the idempotency check ran under
336
- // consolidationTenant — for any non-default tenant that check never
337
- // hit, and the trace regenerated every sleep.
338
- tenantId: consolidationTenant,
339
- });
340
- // AT1 (same producer-side pattern as the merge pass below): traceExistsForSession
341
- // only sees rows CURRENTLY in the store — once a rejected trace is
342
- // removed, that idempotency check no longer blocks regeneration, and
343
- // this write would otherwise reach batchWriteAndDelete's guard bypass
344
- // unchecked, resurrecting it every sleep. Check under THE ENTRY'S OWN
345
- // stamped tenantId (read off `trace` after createMemory — never guess
346
- // the tenant) + the built content's digest. A hit skips the push
347
- // entirely: not counted as promoted, not added to survivors.
348
- const consolidateDb = getConsolidateDb();
349
- if (consolidateDb) {
350
- const traceDigest = rejectionDigest(trace.content);
351
- const tombstone = findRejectedValue(consolidateDb, trace.tenantId, traceDigest);
352
- if (tombstone) {
353
- tracesSkippedRejected++;
354
- try {
355
- appendAuditEvent(consolidateDb, {
356
- tenantId: trace.tenantId,
357
- actor: 'sleep',
358
- op: 'reject_refusal',
359
- metadata: {
360
- digest: traceDigest,
361
- reason: tombstone.reason,
362
- sourceSessionId: session.session_id,
363
- },
364
- });
365
- }
366
- catch {
367
- // Best-effort — mirrors store.ts's audit() semantics.
368
- }
369
- continue;
370
- }
371
- }
372
- pendingWrites.push(trace);
373
- survivors.push(trace);
374
- result.promotedTraces++;
375
- result.details.push(` 🧬 promoted trace ${trace.id} from session ${session.session_id} (${outcome})`);
376
- }
377
- if (result.promotedTraces > 0) {
378
- result.details.push(` 🧬 promoted ${result.promotedTraces} trace${result.promotedTraces === 1 ? '' : 's'} from completed session${result.promotedTraces === 1 ? '' : 's'}`);
379
- }
380
- if (tracesSkippedRejected > 0) {
381
- console.error(`consolidate: skipped ${tracesSkippedRejected} auto-promoted trace(s) whose content matches a rejected value`);
382
- }
383
- }
384
- // -------------------------------------------------------------------------
385
- // 1.5. Replay pass — rehearse high-value survivors
386
- // -------------------------------------------------------------------------
387
- //
388
- // Biologically-inspired counterpart to hippocampal replay during slow-wave
389
- // sleep: sample N memories weighted by outcome + valence + under-rehearsal
390
- // + idle time, then apply the same retrieval-strengthening `markRetrieved`
391
- // applies to real queries. Distinct from decay (removal), physics (motion),
392
- // and merge (compression) — this is the "rehearse the important stuff so
393
- // it doesn't fade" pass.
394
- {
395
- const replayCount = config.replay?.count ?? REPLAY_COUNT_DEFAULT;
396
- // EVAL-ONLY ablation (see ablation.ts): replay rehearsal IS recall
397
- // strengthening (same markRetrieved dynamics), so the strengthen-off arm
398
- // silences the whole pass - markRetrieved would return unmutated entries
399
- // and persisting them anyway would still refresh updated_at / mirrors.
400
- if (replayCount > 0 && survivors.length > 0 && !isRecallBoostAblated()) {
401
- const seed = Math.floor(now.getTime() / 1000) & 0xffffffff;
402
- const picked = sampleForReplay(survivors, replayCount, now, seed);
403
- if (picked.length > 0) {
404
- const rehearsed = markRetrieved(picked, now);
405
- const rehearsedById = new Map(rehearsed.map((e) => [e.id, e]));
406
- // Update survivors in place so downstream passes see rehearsed state.
407
- for (let i = 0; i < survivors.length; i++) {
408
- const replacement = rehearsedById.get(survivors[i].id);
409
- if (replacement)
410
- survivors[i] = replacement;
411
- }
412
- result.replayed = rehearsed.length;
413
- result.details.push(` 💭 replayed ${rehearsed.length} memor${rehearsed.length === 1 ? 'y' : 'ies'}: ` +
414
- rehearsed.map((e) => e.id).join(', '));
415
- if (!dryRun) {
416
- for (const r of rehearsed)
417
- pendingWrites.push(r);
418
- }
419
- }
420
- }
421
- }
422
- // -------------------------------------------------------------------------
423
- // 1.6. Batch extraction — extract facts from episodic memories missing them
424
- // -------------------------------------------------------------------------
425
- const extractedFromIds = new Set(survivors.filter((e) => e.extracted_from).map((e) => e.extracted_from));
426
- const extractionCandidates = survivors.filter((e) => e.layer === Layer.Episodic && !extractedFromIds.has(e.id));
427
- result.extractionCandidates = extractionCandidates.length;
428
- const apiKey = process.env.ANTHROPIC_API_KEY ?? '';
429
- if (apiKey && extractionCandidates.length > 0 && !dryRun) {
430
- const { extractFacts, storeExtractedFacts } = await import('./extract.js');
431
- const batchLimit = 20;
432
- let extractedCount = 0;
433
- for (const candidate of extractionCandidates.slice(0, batchLimit)) {
434
- try {
435
- const facts = await extractFacts(candidate.content, {
436
- apiKey,
437
- model: config.extraction.model,
438
- });
439
- if (facts.length > 0) {
440
- storeExtractedFacts(hippoRoot, candidate, facts);
441
- extractedCount += facts.length;
442
- }
443
- }
444
- catch {
445
- // Best-effort — continue with next candidate
446
- }
447
- }
448
- result.extracted = extractedCount;
449
- }
450
- // -------------------------------------------------------------------------
451
- // 1.7. DAG summarization — cluster extracted facts and generate summaries
452
- // -------------------------------------------------------------------------
453
- const extractedFacts = survivors.filter((e) => e.tags.includes('extracted') && e.dag_level === 1);
454
- if (apiKey && extractedFacts.length >= 3 && !dryRun) {
455
- try {
456
- const { buildDag } = await import('./dag.js');
457
- const dagResult = await buildDag(hippoRoot, extractedFacts, {
458
- apiKey,
459
- model: config.extraction.model,
460
- });
461
- result.dagCandidateClusters = dagResult.candidateClusters;
462
- result.dagSummariesCreated = dagResult.summariesCreated;
463
- if (dagResult.summariesCreated > 0) {
464
- result.details.push(` 🌳 DAG: ${dagResult.summariesCreated} summaries created, ${dagResult.factsLinked} facts linked`);
465
- }
466
- }
467
- catch {
468
- // Best-effort
469
- }
470
- }
471
- // -------------------------------------------------------------------------
472
- // 1.8. DAG summary rebuild — drain dirty queue from E2's child-write hooks
473
- // -------------------------------------------------------------------------
474
- // Consumer of E2's summary_dirty flag. Walks dirty L2 summaries, regenerates
475
- // each via generateDagSummary, atomically refreshes content + 6 metadata
476
- // columns + clears summary_dirty (with FTS sync). Same apiKey/dryRun gate
477
- // as buildDag above. Cap HIPPO_DAG_REBUILD_CAP (default 20, hard ceiling
478
- // 1000) prevents runaway LLM cost.
479
- if (apiKey && !dryRun) {
480
- try {
481
- const { rebuildDirtySummaries } = await import('./dag.js');
482
- const rawCap = parseInt(process.env.HIPPO_DAG_REBUILD_CAP ?? '20', 10);
483
- // R1 MED must-fix: hard ceiling so misconfigured env can't burn
484
- // unbounded LLM cost.
485
- const cap = Number.isFinite(rawCap) && rawCap > 0
486
- ? Math.min(rawCap, 1000)
487
- : 20;
488
- const rebuildResult = await rebuildDirtySummaries(hippoRoot, {
489
- apiKey,
490
- model: config.extraction.model,
491
- cap,
492
- });
493
- result.summariesRebuilt = rebuildResult.rebuilt;
494
- result.summariesRebuildFailed = rebuildResult.failed;
495
- result.summariesZeroChildSkipped = rebuildResult.zeroChildSkipped;
496
- result.summariesRebuildRefused = rebuildResult.refused;
497
- result.summariesRebuildCapped = rebuildResult.capped;
498
- if (rebuildResult.rebuilt > 0 || rebuildResult.zeroChildSkipped > 0 || rebuildResult.failed > 0 || rebuildResult.refused > 0) {
499
- const parts = [];
500
- if (rebuildResult.rebuilt > 0)
501
- parts.push(`${rebuildResult.rebuilt} rebuilt`);
502
- if (rebuildResult.refused > 0)
503
- parts.push(`${rebuildResult.refused} refused`);
504
- if (rebuildResult.zeroChildSkipped > 0)
505
- parts.push(`${rebuildResult.zeroChildSkipped} zero-child-skipped`);
506
- if (rebuildResult.failed > 0)
507
- parts.push(`${rebuildResult.failed} failed`);
508
- if (rebuildResult.capped)
509
- parts.push(`CAPPED@${cap}`);
510
- result.details.push(` 🌳 DAG rebuild: ${parts.join(', ')}`);
511
- }
512
- }
513
- catch {
514
- // Best-effort — same posture as buildDag block above.
515
- }
516
- }
517
- // -------------------------------------------------------------------------
518
- // 1.9. DAG entity profiles — cluster L2 topic summaries into L3 profiles
519
- // -------------------------------------------------------------------------
520
- // E5 phase: aggregate per-entity L2 summaries (e.g. all the speaker:Alice
521
- // topic summaries) into a single L3 entity profile. Runs even when phase
522
- // 1.7 buildDag was skipped (re-clusters existing L2s every sleep).
523
- //
524
- // Uses loadAllL2Summaries (not `survivors`) because phase 1.7 wrote new
525
- // L2s directly via writeEntry without pushing back into survivors.
526
- if (apiKey && !dryRun) {
527
- try {
528
- const { buildEntityProfiles } = await import('./dag.js');
529
- const { loadAllL2Summaries } = await import('./store.js');
530
- const l2Summaries = loadAllL2Summaries(hippoRoot);
531
- if (l2Summaries.length >= 2) {
532
- const profileResult = await buildEntityProfiles(hippoRoot, l2Summaries, {
533
- apiKey,
534
- model: config.extraction.model,
535
- });
536
- result.entityProfilesCreated = profileResult.profilesCreated;
537
- if (profileResult.profilesCreated > 0) {
538
- result.details.push(` 🌲 DAG L3: ${profileResult.profilesCreated} entity profiles, ${profileResult.l2sLinked} L2s linked`);
539
- }
540
- }
541
- }
542
- catch {
543
- // Best-effort.
544
- }
545
- }
546
- // -------------------------------------------------------------------------
547
- // 2. Physics simulation pass
548
- // -------------------------------------------------------------------------
549
- if (!dryRun) {
550
- try {
551
- const physicsEnabled = config.physics.enabled === true
552
- || (config.physics.enabled === 'auto');
553
- if (physicsEnabled) {
554
- const db = openHippoDb(hippoRoot);
555
- try {
556
- const physicsMap = loadPhysicsState(db);
557
- const particles = Array.from(physicsMap.values());
558
- if (particles.length > 0) {
559
- // Build entry lookup for property refresh
560
- const entryMap = new Map(survivors.map(e => [e.id, e]));
561
- refreshParticleProperties(particles, entryMap, now);
562
- // Build conflict pairs from survivors
563
- const conflictPairs = new Map();
564
- for (const entry of survivors) {
565
- if (entry.conflicts_with.length > 0) {
566
- const set = conflictPairs.get(entry.id) ?? new Set();
567
- for (const cid of entry.conflicts_with)
568
- set.add(cid);
569
- conflictPairs.set(entry.id, set);
570
- }
571
- }
572
- // Build half-life lookup
573
- const halfLives = new Map();
574
- for (const entry of survivors) {
575
- halfLives.set(entry.id, entry.half_life_days);
576
- }
577
- const ctx = {
578
- conflictPairs,
579
- halfLives,
580
- config: config.physics,
581
- };
582
- const stats = simulate(particles, ctx);
583
- savePhysicsState(db, particles);
584
- result.physicsSimulated = stats.particleCount;
585
- result.details.push(` ⚛️ physics: ${stats.particleCount} particles, ` +
586
- `avg vel ${stats.avgVelocityMagnitude.toFixed(4)}, ` +
587
- `energy ${stats.energy.total.toFixed(4)}`);
588
- }
589
- }
590
- finally {
591
- closeHippoDb(db);
592
- }
593
- }
594
- }
595
- catch (error) {
596
- result.details.push(` ⚠️ physics simulation skipped: ${error instanceof Error ? error.message : 'unknown error'}`);
597
- }
598
- }
599
- // -------------------------------------------------------------------------
600
- // 3. Merge pass - episodic entries only
601
- // -------------------------------------------------------------------------
602
- const mergeCandidates = survivors.filter((e) => e.layer === Layer.Episodic && !e.tags.includes('extracted'));
603
- const used = new Set();
604
- // T1 fix (2026-08-15 hardening pass): partition by tenantId BEFORE the
605
- // overlap loop so a cluster can never span tenants. Previously textOverlap
606
- // clustered across the whole host-wide `survivors` list with no tenant
607
- // boundary, and mergeContents concatenated cross-tenant content into one
608
- // row. Map preserves insertion order, so single-tenant stores (every row
609
- // 'default') get exactly one partition and iterate in the same order as
610
- // before this fix — byte-identical behavior there.
611
- const mergeCandidatesByTenant = new Map();
612
- for (const entry of mergeCandidates) {
613
- const bucket = mergeCandidatesByTenant.get(entry.tenantId);
614
- if (bucket)
615
- bucket.push(entry);
616
- else
617
- mergeCandidatesByTenant.set(entry.tenantId, [entry]);
618
- }
619
- // AT1 consolidation-loop fix (docs/plans/2026-08-15-at1-rejected-value-tombstone.md):
620
- // reuses the single consolidateDb handle opened once above (before the
621
- // auto-promote pass, 1.4) for the whole non-dry-run consolidate — see that
622
- // declaration's comment. Only needed for real writes — a dry-run preview
623
- // never reaches batchWriteAndDelete's guard bypass, so there is nothing
624
- // here for it to protect against. (mergesSkippedRejected itself is
625
- // declared up at the try's opening above 1.4, not here — it has to
626
- // survive the try/finally that now wraps this whole section; see the
627
- // handle-leak restructure comment there.)
628
- for (const [mergeTenant, tenantCandidates] of mergeCandidatesByTenant) {
629
- for (let i = 0; i < tenantCandidates.length; i++) {
630
- if (used.has(tenantCandidates[i].id))
631
- continue;
632
- const cluster = [tenantCandidates[i]];
633
- for (let j = i + 1; j < tenantCandidates.length; j++) {
634
- if (used.has(tenantCandidates[j].id))
635
- continue;
636
- const overlap = textOverlap(tenantCandidates[i].content, tenantCandidates[j].content);
637
- if (overlap >= MERGE_OVERLAP_THRESHOLD) {
638
- cluster.push(tenantCandidates[j]);
639
- }
640
- }
641
- if (cluster.length < MERGE_MIN_CLUSTER)
642
- continue;
643
- // Create a semantic summary
644
- const mergedContent = mergeContents(cluster);
645
- const allTags = Array.from(new Set(cluster.flatMap((e) => e.tags))).sort();
646
- const maxValence = pickStrongestValence(cluster);
647
- // AT1 P2 fix: build the semantic entry FIRST — createMemory is cheap
648
- // and pure — so the tombstone check below runs under the tenant the
649
- // row will ACTUALLY land in.
650
- // T1 fix: createMemory now receives tenantId: mergeTenant (the
651
- // partition's tenant — every member of `cluster` shares it by
652
- // construction), so the row lands in its source tenant instead of
653
- // always 'default'.
654
- let semantic = null;
655
- if (!dryRun) {
656
- semantic = createMemory(mergedContent, {
657
- layer: Layer.Semantic,
658
- tags: allTags,
659
- emotional_valence: maxValence,
660
- schema_fit: 0.7,
661
- source: 'consolidation',
662
- confidence: 'inferred',
663
- tenantId: mergeTenant,
664
- });
665
- }
666
- // mergeContents is DETERMINISTIC CONCATENATION (not an LLM paraphrase)
667
- // — if a human rejected exactly this byte-identical rollup before, an
668
- // unguarded sleep would regenerate it every cycle and
669
- // batchWriteAndDelete's guard bypass (store.ts) would silently
670
- // re-assert it forever. This producer-side check is what makes that
671
- // bypass safe. A hit skips the WHOLE cluster: sources stay unmerged —
672
- // not demoted, not deleted — so a later sleep gets another chance if
673
- // the tombstone is lifted.
674
- const consolidateDb = getConsolidateDb();
675
- if (consolidateDb && semantic) {
676
- const mergeDigest = rejectionDigest(semantic.content);
677
- const tombstone = findRejectedValue(consolidateDb, semantic.tenantId, mergeDigest);
678
- if (tombstone) {
679
- // Still mark used — these members are not re-tried against a
680
- // DIFFERENT cluster within this same pass; next sleep re-clusters
681
- // them fresh.
682
- for (const e of cluster)
683
- used.add(e.id);
684
- mergesSkippedRejected++;
685
- try {
686
- appendAuditEvent(consolidateDb, {
687
- tenantId: semantic.tenantId,
688
- actor: 'sleep',
689
- op: 'reject_refusal',
690
- metadata: {
691
- digest: mergeDigest,
692
- reason: tombstone.reason,
693
- sourceIds: cluster.map((e) => e.id),
694
- },
695
- });
696
- }
697
- catch {
698
- // Best-effort — mirrors store.ts's audit() semantics.
699
- }
700
- continue;
701
- }
702
- }
703
- // Mark cluster members as used
704
- for (const e of cluster)
705
- used.add(e.id);
706
- result.merged += cluster.length;
707
- result.details.push(` 🔀 merged ${cluster.length} episodic entries into semantic: "${mergedContent.slice(0, 60)}..."`);
708
- if (!dryRun && semantic) {
709
- pendingWrites.push(semantic);
710
- result.semanticCreated++;
711
- // Demote source episodics (they've been compressed into neocortex):
712
- // scale half_life_days so they decay sooner while staying recoverable.
713
- // Immediate ranking is deliberately unchanged: the 2026-06-10 DAG
714
- // slice-1 eval measured that dropping children below a worse-retrieving
715
- // summary regresses budget-bounded QA (docs/evals/). The stored
716
- // strength is refreshed to the live value so inspect, replay sampling,
717
- // and strength-sorted assembly see the truth instead of a fake 0.3.
718
- // Mutate in place (not a copy): `cluster` holds the same object
719
- // references as `survivors`, and the later detectConflicts(survivors)
720
- // pass in this same run must see the post-demotion half-life, or it
721
- // can persist conflicts for entries the just-written state excludes.
722
- for (const e of cluster) {
723
- e.half_life_days = Math.max(1, Math.floor(e.half_life_days * MERGE_SOURCE_HALF_LIFE_FACTOR));
724
- e.strength = calculateStrength(e, now, decayOpts);
725
- pendingWrites.push(e);
726
- }
727
- }
728
- }
729
- }
730
- }
731
- finally {
732
- if (consolidateDbHandle)
733
- closeHippoDb(consolidateDbHandle);
734
- }
735
- if (mergesSkippedRejected > 0) {
736
- console.error(`consolidate: skipped ${mergesSkippedRejected} merge(s) whose content matches a rejected value`);
737
- }
738
- // Flush all writes/deletes in a single transaction
739
- if (!dryRun) {
740
- batchWriteAndDelete(hippoRoot, pendingWrites, pendingDeletes);
741
- }
742
- // -------------------------------------------------------------------------
743
- // 4. Log run
744
- // -------------------------------------------------------------------------
745
- if (!dryRun) {
746
- const detectedConflicts = detectConflicts(survivors, now, decayOpts, rescuedIds);
747
- replaceDetectedConflicts(hippoRoot, detectedConflicts, now.toISOString());
748
- if (detectedConflicts.length > 0) {
749
- result.details.push(` ⚠️ detected ${detectedConflicts.length} memory conflict${detectedConflicts.length === 1 ? '' : 's'}`);
750
- }
751
- appendConsolidationRun(hippoRoot, {
752
- timestamp: now.toISOString(),
753
- decayed: result.decayed,
754
- merged: result.merged,
755
- removed: result.removed,
756
- });
757
- incrementSleepCount(hippoRoot);
758
- // One audit row per rescue (attributability, D1). Written here, AFTER
759
- // batchWriteAndDelete above has committed this cycle's writes/deletes
760
- // (and after conflict detection + run logging), not inline in the decay
761
- // pass — same durability posture as api.ts's top-level 'consolidate'
762
- // summary audit row (written only once the whole sleep has completed).
763
- // Writing it earlier would assert rescues for a cycle whose effects
764
- // never landed if a later phase threw. Real writes only — dry-run
765
- // previews the decision (details line above) but persists nothing.
766
- if (rescuedEntries.length > 0) {
767
- try {
768
- const auditDb = openHippoDb(hippoRoot);
769
- try {
770
- // Review-round F5: per-row try/catch, not one try/catch around the
771
- // whole loop — a single failed appendAuditEvent must not silently
772
- // drop every remaining row. Mirrors the physics pass's
773
- // skipped-warning precedent (line ~564 above): count losses, keep
774
- // the overall fail-soft posture, tell the operator via details.
775
- let auditFailures = 0;
776
- for (const entry of rescuedEntries) {
777
- try {
778
- const rank = rankById.get(entry.id);
779
- appendAuditEvent(auditDb, {
780
- tenantId: entry.tenantId,
781
- actor: 'sleep',
782
- op: 'mv_rescue',
783
- targetId: entry.id,
784
- metadata: rank
785
- ? { rank: rank.rank, totalNonPinned: rank.totalNonPinned, keepN: rank.keepN, score: rank.score }
786
- : {},
787
- });
788
- }
789
- catch {
790
- auditFailures++;
791
- }
792
- }
793
- if (auditFailures > 0) {
794
- result.details.push(` ⚠️ memory-value: ${auditFailures} mv_rescue audit row${auditFailures === 1 ? '' : 's'} ` +
795
- `failed to write (the rescue itself still landed)`);
796
- }
797
- }
798
- finally {
799
- closeHippoDb(auditDb);
800
- }
801
- }
802
- catch {
803
- // openHippoDb/closeHippoDb-level failure: audit must never crash a
804
- // mutation (mirrors store.ts's audit() posture).
805
- result.details.push(` ⚠️ memory-value: mv_rescue audit unavailable this cycle ` +
806
- `(${rescuedEntries.length} rescue${rescuedEntries.length === 1 ? '' : 's'} not audited)`);
807
- }
808
- }
809
- }
810
- return result;
811
- }
812
- // ---------------------------------------------------------------------------
813
- // Helpers
814
- // ---------------------------------------------------------------------------
815
- function mergeContents(entries) {
816
- // Simple merge: take the longest entry as the base, prepend a summary note.
817
- // Equal-length merge bases previously fell to cluster-assembly order;
818
- // compareEntryIdentity is a deterministic tie key (content asc -> metadata -> id asc),
819
- // a no-op when lengths differ (docs/plans/2026-07-16-dedupe-survivor-determinism.md T2).
820
- const sorted = [...entries].sort((a, b) => (b.content.length - a.content.length) || compareEntryIdentity(a, b));
821
- const base = sorted[0].content;
822
- if (entries.length === 2) {
823
- return `[Consolidated from ${entries.length} related memories]\n\n${base}`;
824
- }
825
- // Bullets follow the base order (not raw cluster order) so the merged row
826
- // and its rejection digest are byte-identical across ingest orders.
827
- const bullets = sorted.map((e) => `- ${e.content.split('\n')[0].slice(0, 120)}`).join('\n');
828
- return `[Consolidated pattern from ${entries.length} related memories]\n\n${bullets}`;
829
- }
830
- function pickStrongestValence(entries) {
831
- const order = ['critical', 'negative', 'positive', 'neutral'];
832
- for (const v of order) {
833
- if (entries.some((e) => e.emotional_valence === v))
834
- return v;
835
- }
836
- return 'neutral';
837
- }
838
- function detectConflicts(entries, now, decayOpts = {},
839
- // LC2-E3 (opt-in, default off): ids rescued by this cycle's decay pass.
840
- // detectConflicts recomputes its own strength>=DECAY_THRESHOLD survivor
841
- // filter independently of the decay pass above; without this bypass,
842
- // rescued entries would be silently re-excluded from conflict detection
843
- // every cycle even though the decay pass just decided to keep them.
844
- // Default empty set: flag-off behavior is unchanged.
845
- rescuedIds = new Set()) {
846
- const survivors = entries.filter((entry) => entry.layer !== Layer.Semantic
847
- && (rescuedIds.has(entry.id) || calculateStrength(entry, now, decayOpts) >= DECAY_THRESHOLD));
848
- const detected = [];
849
- for (let i = 0; i < survivors.length; i++) {
850
- for (let j = i + 1; j < survivors.length; j++) {
851
- // Traces are variants of each other, not contradictions. Two
852
- // strategies for the same task can both be valid; conflict detection
853
- // exists for stated-rule disagreement, not strategy diversity.
854
- if (survivors[i].layer === Layer.Trace && survivors[j].layer === Layer.Trace)
855
- continue;
856
- if (survivors[i].superseded_by || survivors[j].superseded_by)
857
- continue;
858
- if (survivors[i].tags.includes('extracted') || survivors[j].tags.includes('extracted'))
859
- continue;
860
- const reasonAndScore = describeConflict(survivors[i], survivors[j]);
861
- if (!reasonAndScore)
862
- continue;
863
- detected.push({
864
- memory_a_id: survivors[i].id,
865
- memory_b_id: survivors[j].id,
866
- reason: reasonAndScore.reason,
867
- score: reasonAndScore.score,
868
- });
869
- }
870
- }
871
- return detected;
872
- }
873
- function describeConflict(a, b) {
874
- const aDistinct = distinctiveTokens(a.content);
875
- const bDistinct = distinctiveTokens(b.content);
876
- // Jaccard on stopword-stripped tokens. Defer the threshold check until we
877
- // know whether an explicit polarity pair is present (lower bar for those).
878
- const overlapScore = jaccardSets(aDistinct, bDistinct);
879
- // Require at least N shared distinctive tokens so two short memories sharing
880
- // only "the project name" don't register.
881
- let shared = 0;
882
- for (const t of aDistinct)
883
- if (bDistinct.has(t))
884
- shared++;
885
- if (shared < CONFLICT_MIN_RARE_SHARED)
886
- return null;
887
- // Polarity is measured only in the first POLARITY_WINDOW_WORDS, so a stray
888
- // negation deep in a prose memory doesn't flip the intent.
889
- const polarityA = inferConflictPolarity(openingWindow(a.content));
890
- const polarityB = inferConflictPolarity(openingWindow(b.content));
891
- const conflictType = classifyConflictType(a.content, b.content, polarityA, polarityB);
892
- if (!conflictType)
893
- return null;
894
- if (overlapScore < CONFLICT_OVERLAP_THRESHOLD)
895
- return null;
896
- return {
897
- reason: conflictType,
898
- score: overlapScore,
899
- };
900
- }
901
- function distinctiveTokens(text) {
902
- return new Set(text
903
- .toLowerCase()
904
- .replace(/[^\w\s]/g, ' ')
905
- .split(/\s+/)
906
- .filter((t) => t.length > 2 && !CONFLICT_STOPWORDS.has(t)));
907
- }
908
- function jaccardSets(a, b) {
909
- if (a.size === 0 && b.size === 0)
910
- return 1;
911
- if (a.size === 0 || b.size === 0)
912
- return 0;
913
- let inter = 0;
914
- for (const t of a)
915
- if (b.has(t))
916
- inter++;
917
- const union = a.size + b.size - inter;
918
- return union === 0 ? 0 : inter / union;
919
- }
920
- function openingWindow(text) {
921
- return text.split(/\s+/).slice(0, POLARITY_WINDOW_WORDS).join(' ');
922
- }
923
- function classifyConflictType(aText, bText, aPolarity, bPolarity) {
924
- // Classifier scans only the opening window of each memory so " on " and
925
- // " off " used as English prepositions deep in a long prose memory don't
926
- // trigger an enabled/disabled flag. The opening window is where a rule or
927
- // declaration is typically stated.
928
- // Pad with spaces so space-delimited patterns match words at the start/end.
929
- const a = ' ' + openingWindow(aText).toLowerCase() + ' ';
930
- const b = ' ' + openingWindow(bText).toLowerCase() + ' ';
931
- // Tightened tokens: require whole-word boundaries so " on " alone doesn't
932
- // match "on/off". Pair only `enabled` ↔ `disabled` and explicit on/off in
933
- // imperative context.
934
- const enabledDisabled = (containsAny(a, [' enabled ', ' enable ']) && containsAny(b, [' disabled ', ' disable ']))
935
- || (containsAny(b, [' enabled ', ' enable ']) && containsAny(a, [' disabled ', ' disable ']));
936
- if (enabledDisabled)
937
- return 'enabled/disabled mismatch on overlapping statement';
938
- const trueFalse = (containsAny(a, [' true ', ' true.', ' true,', ' yes ']) && containsAny(b, [' false ', ' false.', ' false,', ' no ']))
939
- || (containsAny(b, [' true ', ' true.', ' true,', ' yes ']) && containsAny(a, [' false ', ' false.', ' false,', ' no ']));
940
- if (trueFalse)
941
- return 'true/false mismatch on overlapping statement';
942
- const alwaysNever = (containsAny(a, [' always ', ' must ']) && containsAny(b, [' never ', ' must not ']))
943
- || (containsAny(b, [' always ', ' must ']) && containsAny(a, [' never ', ' must not ']));
944
- if (alwaysNever)
945
- return 'always/never mismatch on overlapping statement';
946
- if ((aPolarity === 'positive' && bPolarity === 'negative') || (aPolarity === 'negative' && bPolarity === 'positive')) {
947
- return 'negation polarity mismatch on overlapping statement';
948
- }
949
- return null;
950
- }
951
- function inferConflictPolarity(text) {
952
- const lowered = ` ${text.toLowerCase()} `;
953
- const negativePatterns = [
954
- ' not ', ' never ', ' no ', " don't ", ' do not ', " doesn't ", ' does not ',
955
- " can't ", ' cannot ', " shouldn't ", ' should not ', ' disabled ', ' disable ', ' off ',
956
- ' false ', ' missing ', ' broken ', ' failed ',
957
- ];
958
- const positivePatterns = [
959
- ' enabled ', ' enable ', ' works ', ' working ', ' true ', ' available ', ' present ', ' on ',
960
- ' always ', ' must ',
961
- ];
962
- if (containsAny(lowered, negativePatterns))
963
- return 'negative';
964
- if (containsAny(lowered, positivePatterns))
965
- return 'positive';
966
- return 'neutral';
967
- }
968
- function stripConflictPolarity(text) {
969
- return text
970
- .toLowerCase()
971
- .replace(/\b(?:not|never|no|don['’]?t|do\s+not|doesn['’]?t|does\s+not|can['’]?t|cannot|shouldn['’]?t|should\s+not|enabled|enable|disabled|disable|on|off|true|false|always|must|must\s+not|works?|working|missing|broken|failed|available|present)\b/g, ' ')
972
- .replace(/\s+/g, ' ')
973
- .trim();
974
- }
975
- function containsAny(text, needles) {
976
- return needles.some((needle) => text.includes(needle));
977
- }
978
- //# sourceMappingURL=consolidate.js.map