hippo-memory 1.60.0 → 1.62.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 (593) hide show
  1. package/README.md +29 -54
  2. package/dist/ablation.js +9 -27
  3. package/dist/agent-memories/apply.d.ts +1 -1
  4. package/dist/agent-memories/apply.js +4 -1
  5. package/dist/agent-memories/claude-code.d.ts +3 -1
  6. package/dist/agent-memories/claude-code.js +54 -10
  7. package/dist/agent-memories/codex.js +1 -1
  8. package/dist/agent-memories/gemini.js +1 -1
  9. package/dist/agent-memories/legacy.js +1 -1
  10. package/dist/agent-memories/source.js +1 -1
  11. package/dist/agent-memories/sync.d.ts +3 -3
  12. package/dist/agent-memories/sync.js +22 -9
  13. package/dist/agent-memories/types.d.ts +0 -2
  14. package/dist/ambient-store.d.ts +14 -0
  15. package/dist/ambient-store.js +90 -0
  16. package/dist/ambient.d.ts +23 -0
  17. package/dist/ambient.js +72 -50
  18. package/dist/api/assemble.d.ts +93 -0
  19. package/dist/api/assemble.js +153 -0
  20. package/dist/api/audit.d.ts +17 -0
  21. package/dist/api/audit.js +23 -0
  22. package/dist/api/auth.d.ts +79 -0
  23. package/dist/api/auth.js +178 -0
  24. package/dist/api/context-select.d.ts +49 -0
  25. package/dist/api/context-select.js +344 -0
  26. package/dist/api/context-types.d.ts +105 -0
  27. package/dist/api/context-types.js +3 -0
  28. package/dist/api/context.d.ts +31 -0
  29. package/dist/api/context.js +378 -0
  30. package/dist/api/dormant.d.ts +30 -0
  31. package/dist/api/dormant.js +140 -0
  32. package/dist/api/drill-down.d.ts +84 -0
  33. package/dist/api/drill-down.js +126 -0
  34. package/dist/api/forget.d.ts +57 -0
  35. package/dist/api/forget.js +87 -0
  36. package/dist/api/goals.d.ts +18 -0
  37. package/dist/api/goals.js +33 -0
  38. package/dist/api/learn.d.ts +31 -0
  39. package/dist/api/learn.js +88 -0
  40. package/dist/api/outcome.d.ts +61 -0
  41. package/dist/api/outcome.js +66 -0
  42. package/dist/api/promote.d.ts +54 -0
  43. package/dist/api/promote.js +192 -0
  44. package/dist/api/quarantine.d.ts +24 -0
  45. package/dist/api/quarantine.js +121 -0
  46. package/dist/api/recall-types.d.ts +390 -0
  47. package/dist/api/recall-types.js +3 -0
  48. package/dist/api/recall.d.ts +36 -0
  49. package/dist/api/recall.js +499 -0
  50. package/dist/api/remember.d.ts +35 -0
  51. package/dist/api/remember.js +43 -0
  52. package/dist/api/sleep.d.ts +136 -0
  53. package/dist/api/sleep.js +256 -0
  54. package/dist/api/tokens.d.ts +26 -0
  55. package/dist/api/tokens.js +60 -0
  56. package/dist/api/types.d.ts +56 -0
  57. package/dist/api/types.js +38 -0
  58. package/dist/api.d.ts +20 -1262
  59. package/dist/api.js +25 -2727
  60. package/dist/audit.d.ts +3 -0
  61. package/dist/audit.js +6 -3
  62. package/dist/auth.d.ts +45 -4
  63. package/dist/auth.js +125 -48
  64. package/dist/autolearn.js +2 -2
  65. package/dist/capture/command.d.ts +33 -0
  66. package/dist/capture/command.js +264 -0
  67. package/dist/capture/compact.d.ts +44 -0
  68. package/dist/capture/compact.js +354 -0
  69. package/dist/capture/extract.d.ts +21 -0
  70. package/dist/capture/extract.js +464 -0
  71. package/dist/capture/transcript.d.ts +40 -0
  72. package/dist/capture/transcript.js +193 -0
  73. package/dist/capture-error.js +2 -1
  74. package/dist/churn-git.js +4 -2
  75. package/dist/cli/audit.d.ts +3 -0
  76. package/dist/cli/audit.js +159 -0
  77. package/dist/cli/auth.d.ts +2 -0
  78. package/dist/cli/auth.js +171 -0
  79. package/dist/cli/briefs.d.ts +4 -0
  80. package/dist/cli/briefs.js +453 -0
  81. package/dist/cli/card.d.ts +3 -0
  82. package/dist/cli/card.js +333 -0
  83. package/dist/cli/context.d.ts +15 -0
  84. package/dist/cli/context.js +376 -0
  85. package/dist/cli/continuity.d.ts +6 -0
  86. package/dist/cli/continuity.js +457 -0
  87. package/dist/cli/curate.d.ts +19 -0
  88. package/dist/cli/curate.js +556 -0
  89. package/dist/cli/dag.d.ts +5 -0
  90. package/dist/cli/dag.js +177 -0
  91. package/dist/cli/decisions.d.ts +4 -0
  92. package/dist/cli/decisions.js +528 -0
  93. package/dist/cli/eval.d.ts +5 -0
  94. package/dist/cli/eval.js +213 -0
  95. package/dist/cli/explain.d.ts +4 -0
  96. package/dist/cli/explain.js +156 -0
  97. package/dist/cli/goals.d.ts +2 -0
  98. package/dist/cli/goals.js +196 -0
  99. package/dist/cli/hook-blocks.d.ts +18 -0
  100. package/dist/cli/hook-blocks.js +233 -0
  101. package/dist/cli/init.d.ts +2 -0
  102. package/dist/cli/init.js +305 -0
  103. package/dist/cli/maintenance.d.ts +4 -0
  104. package/dist/cli/maintenance.js +190 -0
  105. package/dist/cli/playbooks.d.ts +4 -0
  106. package/dist/cli/playbooks.js +573 -0
  107. package/dist/cli/projects.js +9 -6
  108. package/dist/cli/recall.d.ts +7 -0
  109. package/dist/cli/recall.js +582 -0
  110. package/dist/cli/remember.d.ts +5 -0
  111. package/dist/cli/remember.js +485 -0
  112. package/dist/cli/serve.d.ts +5 -0
  113. package/dist/cli/serve.js +40 -0
  114. package/dist/cli/session-hooks.d.ts +29 -0
  115. package/dist/cli/session-hooks.js +647 -0
  116. package/dist/cli/setup.d.ts +4 -0
  117. package/dist/cli/setup.js +397 -0
  118. package/dist/cli/shared.d.ts +15 -19
  119. package/dist/cli/shared.js +50 -341
  120. package/dist/cli/slack.d.ts +2 -0
  121. package/dist/cli/slack.js +171 -0
  122. package/dist/cli/status.d.ts +18 -0
  123. package/dist/cli/status.js +409 -0
  124. package/dist/cli/transfer.d.ts +10 -0
  125. package/dist/cli/transfer.js +424 -0
  126. package/dist/cli/usage.d.ts +85 -0
  127. package/dist/cli/usage.js +741 -0
  128. package/dist/cli.d.ts +71 -120
  129. package/dist/cli.js +170 -8469
  130. package/dist/client.js +15 -8
  131. package/dist/compaction-record.d.ts +0 -2
  132. package/dist/compaction-record.js +7 -5
  133. package/dist/connectors/github/backfill.js +94 -87
  134. package/dist/connectors/github/cli-impl.js +4 -3
  135. package/dist/connectors/github/dlq.js +67 -54
  136. package/dist/connectors/github/ingest.js +34 -35
  137. package/dist/connectors/github/tenant-routing.js +3 -2
  138. package/dist/connectors/github/webhook.js +135 -216
  139. package/dist/connectors/slack/dlq.js +49 -61
  140. package/dist/connectors/slack/ingest.js +55 -48
  141. package/dist/connectors/slack/tenant-routing.js +4 -3
  142. package/dist/connectors/slack/webhook.js +72 -74
  143. package/dist/consolidate/conflicts.d.ts +10 -0
  144. package/dist/consolidate/conflicts.js +178 -0
  145. package/dist/consolidate/decay.d.ts +12 -0
  146. package/dist/consolidate/decay.js +145 -0
  147. package/dist/consolidate/llm-passes.d.ts +3 -0
  148. package/dist/consolidate/llm-passes.js +141 -0
  149. package/dist/consolidate/merge.d.ts +7 -0
  150. package/dist/consolidate/merge.js +251 -0
  151. package/dist/consolidate/physics-pass.d.ts +3 -0
  152. package/dist/consolidate/physics-pass.js +60 -0
  153. package/dist/consolidate/run.d.ts +69 -0
  154. package/dist/consolidate/run.js +76 -0
  155. package/dist/consolidate/sleep.d.ts +18 -0
  156. package/dist/consolidate/sleep.js +209 -0
  157. package/dist/consolidate/traces.d.ts +4 -0
  158. package/dist/consolidate/traces.js +178 -0
  159. package/dist/context-auto.js +7 -11
  160. package/dist/context-render.d.ts +1 -1
  161. package/dist/context-render.js +1 -1
  162. package/dist/customer-notes.d.ts +3 -0
  163. package/dist/customer-notes.js +83 -72
  164. package/dist/dag.js +225 -188
  165. package/dist/dashboard-actions.d.ts +20 -0
  166. package/dist/dashboard-actions.js +88 -0
  167. package/dist/dashboard-params.d.ts +45 -0
  168. package/dist/dashboard-params.js +127 -0
  169. package/dist/dashboard-queries.d.ts +15 -0
  170. package/dist/dashboard-queries.js +355 -0
  171. package/dist/dashboard-snapshot.d.ts +138 -0
  172. package/dist/dashboard-snapshot.js +308 -0
  173. package/dist/dashboard-types.d.ts +163 -0
  174. package/dist/dashboard-types.js +3 -0
  175. package/dist/dashboard.d.ts +6 -7
  176. package/dist/dashboard.js +228 -202
  177. package/dist/db/busy.d.ts +5 -0
  178. package/dist/db/busy.js +24 -0
  179. package/dist/db/continuity.d.ts +5 -0
  180. package/dist/db/continuity.js +145 -0
  181. package/dist/db/meta.d.ts +8 -0
  182. package/dist/db/meta.js +35 -0
  183. package/dist/db/migrate.d.ts +9 -0
  184. package/dist/db/migrate.js +138 -0
  185. package/dist/db/migrations/index.d.ts +5 -0
  186. package/dist/db/migrations/index.js +109 -0
  187. package/dist/db/migrations/types.d.ts +14 -0
  188. package/dist/db/migrations/types.js +2 -0
  189. package/dist/db/migrations/v01.d.ts +3 -0
  190. package/dist/db/migrations/v01.js +38 -0
  191. package/dist/db/migrations/v02.d.ts +3 -0
  192. package/dist/db/migrations/v02.js +21 -0
  193. package/dist/db/migrations/v03.d.ts +3 -0
  194. package/dist/db/migrations/v03.js +22 -0
  195. package/dist/db/migrations/v04.d.ts +3 -0
  196. package/dist/db/migrations/v04.js +28 -0
  197. package/dist/db/migrations/v05.d.ts +3 -0
  198. package/dist/db/migrations/v05.js +21 -0
  199. package/dist/db/migrations/v06.d.ts +3 -0
  200. package/dist/db/migrations/v06.js +25 -0
  201. package/dist/db/migrations/v07.d.ts +3 -0
  202. package/dist/db/migrations/v07.js +13 -0
  203. package/dist/db/migrations/v08.d.ts +3 -0
  204. package/dist/db/migrations/v08.js +8 -0
  205. package/dist/db/migrations/v09.d.ts +3 -0
  206. package/dist/db/migrations/v09.js +13 -0
  207. package/dist/db/migrations/v10.d.ts +3 -0
  208. package/dist/db/migrations/v10.js +17 -0
  209. package/dist/db/migrations/v11.d.ts +3 -0
  210. package/dist/db/migrations/v11.js +15 -0
  211. package/dist/db/migrations/v12.d.ts +3 -0
  212. package/dist/db/migrations/v12.js +11 -0
  213. package/dist/db/migrations/v13.d.ts +3 -0
  214. package/dist/db/migrations/v13.js +15 -0
  215. package/dist/db/migrations/v14.d.ts +3 -0
  216. package/dist/db/migrations/v14.js +66 -0
  217. package/dist/db/migrations/v15.d.ts +3 -0
  218. package/dist/db/migrations/v15.js +42 -0
  219. package/dist/db/migrations/v16.d.ts +3 -0
  220. package/dist/db/migrations/v16.js +61 -0
  221. package/dist/db/migrations/v17.d.ts +3 -0
  222. package/dist/db/migrations/v17.js +46 -0
  223. package/dist/db/migrations/v18.d.ts +3 -0
  224. package/dist/db/migrations/v18.js +60 -0
  225. package/dist/db/migrations/v19.d.ts +3 -0
  226. package/dist/db/migrations/v19.js +28 -0
  227. package/dist/db/migrations/v20.d.ts +3 -0
  228. package/dist/db/migrations/v20.js +42 -0
  229. package/dist/db/migrations/v21.d.ts +3 -0
  230. package/dist/db/migrations/v21.js +16 -0
  231. package/dist/db/migrations/v22.d.ts +3 -0
  232. package/dist/db/migrations/v22.js +82 -0
  233. package/dist/db/migrations/v23.d.ts +3 -0
  234. package/dist/db/migrations/v23.js +48 -0
  235. package/dist/db/migrations/v24.d.ts +3 -0
  236. package/dist/db/migrations/v24.js +72 -0
  237. package/dist/db/migrations/v25.d.ts +3 -0
  238. package/dist/db/migrations/v25.js +46 -0
  239. package/dist/db/migrations/v26.d.ts +3 -0
  240. package/dist/db/migrations/v26.js +23 -0
  241. package/dist/db/migrations/v27.d.ts +3 -0
  242. package/dist/db/migrations/v27.js +57 -0
  243. package/dist/db/migrations/v28.d.ts +3 -0
  244. package/dist/db/migrations/v28.js +38 -0
  245. package/dist/db/migrations/v29.d.ts +3 -0
  246. package/dist/db/migrations/v29.js +78 -0
  247. package/dist/db/migrations/v30.d.ts +3 -0
  248. package/dist/db/migrations/v30.js +92 -0
  249. package/dist/db/migrations/v31.d.ts +3 -0
  250. package/dist/db/migrations/v31.js +74 -0
  251. package/dist/db/migrations/v32.d.ts +3 -0
  252. package/dist/db/migrations/v32.js +102 -0
  253. package/dist/db/migrations/v33.d.ts +3 -0
  254. package/dist/db/migrations/v33.js +104 -0
  255. package/dist/db/migrations/v34.d.ts +3 -0
  256. package/dist/db/migrations/v34.js +93 -0
  257. package/dist/db/migrations/v35.d.ts +3 -0
  258. package/dist/db/migrations/v35.js +98 -0
  259. package/dist/db/migrations/v36.d.ts +3 -0
  260. package/dist/db/migrations/v36.js +98 -0
  261. package/dist/db/migrations/v37.d.ts +3 -0
  262. package/dist/db/migrations/v37.js +219 -0
  263. package/dist/db/migrations/v38.d.ts +3 -0
  264. package/dist/db/migrations/v38.js +277 -0
  265. package/dist/db/migrations/v39.d.ts +3 -0
  266. package/dist/db/migrations/v39.js +59 -0
  267. package/dist/db/migrations/v40.d.ts +3 -0
  268. package/dist/db/migrations/v40.js +74 -0
  269. package/dist/db/migrations/v41.d.ts +3 -0
  270. package/dist/db/migrations/v41.js +43 -0
  271. package/dist/db/migrations/v42.d.ts +3 -0
  272. package/dist/db/migrations/v42.js +41 -0
  273. package/dist/db/migrations/v43.d.ts +3 -0
  274. package/dist/db/migrations/v43.js +67 -0
  275. package/dist/db/migrations/v44.d.ts +3 -0
  276. package/dist/db/migrations/v44.js +28 -0
  277. package/dist/db/migrations/v45.d.ts +3 -0
  278. package/dist/db/migrations/v45.js +30 -0
  279. package/dist/db/migrations/v46.d.ts +3 -0
  280. package/dist/db/migrations/v46.js +25 -0
  281. package/dist/db/migrations/v47.d.ts +3 -0
  282. package/dist/db/migrations/v47.js +17 -0
  283. package/dist/db/migrations/v48.d.ts +3 -0
  284. package/dist/db/migrations/v48.js +10 -0
  285. package/dist/db/migrations/v49.d.ts +3 -0
  286. package/dist/db/migrations/v49.js +31 -0
  287. package/dist/db/migrations/v50.d.ts +3 -0
  288. package/dist/db/migrations/v50.js +67 -0
  289. package/dist/db/migrations/v51.d.ts +3 -0
  290. package/dist/db/migrations/v51.js +14 -0
  291. package/dist/db/migrations/v52.d.ts +3 -0
  292. package/dist/db/migrations/v52.js +7 -0
  293. package/dist/db/open.d.ts +23 -0
  294. package/dist/db/open.js +146 -0
  295. package/dist/db/sqlite.d.ts +21 -0
  296. package/dist/db/sqlite.js +8 -0
  297. package/dist/db/tables.d.ts +7 -0
  298. package/dist/db/tables.js +38 -0
  299. package/dist/db.d.ts +6 -46
  300. package/dist/db.js +5 -3049
  301. package/dist/decisions.d.ts +4 -1
  302. package/dist/decisions.js +102 -83
  303. package/dist/dedupe.js +3 -2
  304. package/dist/delivery-recorder.js +4 -1
  305. package/dist/doctor.js +14 -10
  306. package/dist/embedding-provider.d.ts +1 -1
  307. package/dist/embedding-provider.js +4 -3
  308. package/dist/embeddings.d.ts +9 -52
  309. package/dist/embeddings.js +43 -297
  310. package/dist/env.d.ts +75 -0
  311. package/dist/env.js +119 -0
  312. package/dist/eval-suite.js +1 -1
  313. package/dist/eval.js +2 -2
  314. package/dist/extract.js +1 -1
  315. package/dist/gated-write.js +3 -1
  316. package/dist/goals.d.ts +3 -1
  317. package/dist/goals.js +117 -86
  318. package/dist/graph/read.d.ts +73 -0
  319. package/dist/graph/read.js +325 -0
  320. package/dist/graph/rows.d.ts +45 -0
  321. package/dist/graph/rows.js +51 -0
  322. package/dist/graph/types.d.ts +83 -0
  323. package/dist/graph/types.js +11 -0
  324. package/dist/graph/write.d.ts +93 -0
  325. package/dist/{graph.js → graph/write.js} +6 -384
  326. package/dist/graph-extract.js +2 -1
  327. package/dist/graph-recall.d.ts +1 -1
  328. package/dist/graph-recall.js +2 -2
  329. package/dist/graph-stream.js +1 -1
  330. package/dist/graph-view.d.ts +1 -1
  331. package/dist/graph-view.js +1 -1
  332. package/dist/half-life-migration.d.ts +1 -1
  333. package/dist/half-life-migration.js +2 -1
  334. package/dist/hooks/codex-session.d.ts +8 -0
  335. package/dist/hooks/codex-session.js +76 -0
  336. package/dist/hooks/codex-wrapper.d.ts +55 -0
  337. package/dist/hooks/codex-wrapper.js +288 -0
  338. package/dist/hooks/json-hooks.d.ts +63 -0
  339. package/dist/hooks/json-hooks.js +356 -0
  340. package/dist/hooks/opencode.d.ts +50 -0
  341. package/dist/hooks/opencode.js +202 -0
  342. package/dist/hooks/shared.d.ts +54 -0
  343. package/dist/hooks/shared.js +77 -0
  344. package/dist/http-retry.d.ts +2 -0
  345. package/dist/http-retry.js +4 -3
  346. package/dist/http-util.d.ts +3 -0
  347. package/dist/http-util.js +10 -0
  348. package/dist/{importers.d.ts → importers/core.d.ts} +13 -17
  349. package/dist/importers/core.js +141 -0
  350. package/dist/importers/markdown-parse.d.ts +41 -0
  351. package/dist/importers/markdown-parse.js +132 -0
  352. package/dist/importers/markdown.d.ts +3 -0
  353. package/dist/importers/markdown.js +92 -0
  354. package/dist/importers/sources.d.ts +7 -0
  355. package/dist/importers/sources.js +229 -0
  356. package/dist/importers/vault.d.ts +11 -0
  357. package/dist/importers/vault.js +352 -0
  358. package/dist/incidents.d.ts +3 -0
  359. package/dist/incidents.js +52 -43
  360. package/dist/index.d.ts +25 -6
  361. package/dist/index.js +23 -6
  362. package/dist/invalidation.js +2 -1
  363. package/dist/judgment.js +2 -1
  364. package/dist/keyset.d.ts +13 -0
  365. package/dist/keyset.js +8 -0
  366. package/dist/local-embedding.d.ts +13 -0
  367. package/dist/local-embedding.js +165 -0
  368. package/dist/log.d.ts +7 -0
  369. package/dist/log.js +19 -1
  370. package/dist/mcp/admin-tools.d.ts +8 -0
  371. package/dist/mcp/admin-tools.js +116 -0
  372. package/dist/mcp/format.d.ts +27 -0
  373. package/dist/mcp/format.js +135 -0
  374. package/dist/mcp/memory-tools.d.ts +5 -0
  375. package/dist/mcp/memory-tools.js +83 -0
  376. package/dist/mcp/protocol.d.ts +83 -0
  377. package/dist/mcp/protocol.js +55 -0
  378. package/dist/mcp/recall-tools.d.ts +6 -0
  379. package/dist/mcp/recall-tools.js +320 -0
  380. package/dist/mcp/request.d.ts +10 -0
  381. package/dist/mcp/request.js +163 -0
  382. package/dist/mcp/server.d.ts +4 -75
  383. package/dist/mcp/server.js +7 -1190
  384. package/dist/mcp/session-state.d.ts +11 -0
  385. package/dist/mcp/session-state.js +28 -0
  386. package/dist/mcp/stdio.d.ts +8 -0
  387. package/dist/mcp/stdio.js +79 -0
  388. package/dist/mcp/tools.d.ts +11 -0
  389. package/dist/mcp/tools.js +247 -0
  390. package/dist/memory.d.ts +3 -0
  391. package/dist/memory.js +27 -1
  392. package/dist/multihop.d.ts +1 -1
  393. package/dist/multihop.js +2 -1
  394. package/dist/owner-validation.js +2 -1
  395. package/dist/physics-state.js +10 -7
  396. package/dist/policies.d.ts +3 -0
  397. package/dist/policies.js +93 -74
  398. package/dist/postinstall.js +3 -2
  399. package/dist/predictions/planning-fallacy.d.ts +100 -0
  400. package/dist/predictions/planning-fallacy.js +190 -0
  401. package/dist/{predictions.d.ts → predictions/store.d.ts} +7 -102
  402. package/dist/predictions/store.js +434 -0
  403. package/dist/processes.d.ts +3 -0
  404. package/dist/processes.js +94 -76
  405. package/dist/project-briefs.d.ts +3 -0
  406. package/dist/project-briefs.js +142 -113
  407. package/dist/project-identity.js +3 -2
  408. package/dist/project-merge.d.ts +12 -4
  409. package/dist/project-merge.js +132 -45
  410. package/dist/quarantine.d.ts +2 -1
  411. package/dist/quarantine.js +8 -5
  412. package/dist/raw-archive.js +1 -1
  413. package/dist/recall-history.js +3 -2
  414. package/dist/recall-pipeline.d.ts +2 -2
  415. package/dist/recall-pipeline.js +16 -8
  416. package/dist/recall-scope.js +1 -1
  417. package/dist/recall-trace.d.ts +1 -1
  418. package/dist/refine-llm.js +2 -1
  419. package/dist/reject-flow.js +6 -1
  420. package/dist/rerankers/clef.js +6 -5
  421. package/dist/rerankers/jev.d.ts +1 -1
  422. package/dist/rerankers/jev.js +4 -4
  423. package/dist/rerankers/llm.d.ts +4 -2
  424. package/dist/rerankers/llm.js +58 -38
  425. package/dist/rerankers/types.d.ts +1 -1
  426. package/dist/salience.js +1 -1
  427. package/dist/scheduler.d.ts +1 -0
  428. package/dist/scheduler.js +26 -4
  429. package/dist/scope.js +4 -3
  430. package/dist/search/as-of.d.ts +10 -0
  431. package/dist/search/as-of.js +22 -0
  432. package/dist/search/bm25-search.d.ts +14 -0
  433. package/dist/search/bm25-search.js +43 -0
  434. package/dist/search/bm25.d.ts +15 -0
  435. package/dist/search/bm25.js +54 -0
  436. package/dist/search/boosts.d.ts +54 -0
  437. package/dist/search/boosts.js +94 -0
  438. package/dist/search/breakdown.d.ts +7 -0
  439. package/dist/search/breakdown.js +20 -0
  440. package/dist/search/explain.d.ts +25 -0
  441. package/dist/search/explain.js +31 -0
  442. package/dist/search/finalize.d.ts +8 -0
  443. package/dist/search/finalize.js +52 -0
  444. package/dist/search/fusion.d.ts +27 -0
  445. package/dist/search/fusion.js +42 -0
  446. package/dist/search/hybrid-score.d.ts +20 -0
  447. package/dist/search/hybrid-score.js +73 -0
  448. package/dist/search/hybrid.d.ts +46 -0
  449. package/dist/search/hybrid.js +64 -0
  450. package/dist/search/physics-search.d.ts +29 -0
  451. package/dist/search/physics-search.js +162 -0
  452. package/dist/search/rerank.d.ts +10 -0
  453. package/dist/search/rerank.js +72 -0
  454. package/dist/search/temporal.d.ts +15 -0
  455. package/dist/search/temporal.js +45 -0
  456. package/dist/search/types.d.ts +91 -0
  457. package/dist/search/types.js +2 -0
  458. package/dist/search/vector.d.ts +30 -0
  459. package/dist/search/vector.js +71 -0
  460. package/dist/secret-detect.d.ts +2 -0
  461. package/dist/secret-detect.js +2 -1
  462. package/dist/server/auth.d.ts +52 -0
  463. package/dist/server/auth.js +221 -0
  464. package/dist/server/client-ip.d.ts +25 -0
  465. package/dist/server/client-ip.js +92 -0
  466. package/dist/server/cursor.d.ts +23 -0
  467. package/dist/server/cursor.js +58 -0
  468. package/dist/server/lifecycle.d.ts +7 -0
  469. package/dist/server/lifecycle.js +29 -0
  470. package/dist/server/mcp-http.d.ts +5 -0
  471. package/dist/server/mcp-http.js +199 -0
  472. package/dist/server/request.d.ts +33 -0
  473. package/dist/server/request.js +103 -0
  474. package/dist/server/routes/admin.d.ts +9 -0
  475. package/dist/server/routes/admin.js +158 -0
  476. package/dist/server/routes/customer-notes.d.ts +7 -0
  477. package/dist/server/routes/customer-notes.js +112 -0
  478. package/dist/server/routes/decisions.d.ts +7 -0
  479. package/dist/server/routes/decisions.js +133 -0
  480. package/dist/server/routes/incidents.d.ts +7 -0
  481. package/dist/server/routes/incidents.js +126 -0
  482. package/dist/server/routes/memories.d.ts +10 -0
  483. package/dist/server/routes/memories.js +179 -0
  484. package/dist/server/routes/policies.d.ts +8 -0
  485. package/dist/server/routes/policies.js +151 -0
  486. package/dist/server/routes/predictions.d.ts +7 -0
  487. package/dist/server/routes/predictions.js +164 -0
  488. package/dist/server/routes/processes.d.ts +7 -0
  489. package/dist/server/routes/processes.js +161 -0
  490. package/dist/server/routes/project-briefs.d.ts +8 -0
  491. package/dist/server/routes/project-briefs.js +136 -0
  492. package/dist/server/routes/recall.d.ts +8 -0
  493. package/dist/server/routes/recall.js +340 -0
  494. package/dist/server/routes/skills.d.ts +8 -0
  495. package/dist/server/routes/skills.js +150 -0
  496. package/dist/server/types.d.ts +56 -0
  497. package/dist/server/types.js +2 -0
  498. package/dist/server/validation.d.ts +14 -0
  499. package/dist/server/validation.js +91 -0
  500. package/dist/server.d.ts +7 -61
  501. package/dist/server.js +69 -2362
  502. package/dist/session-digest.d.ts +1 -1
  503. package/dist/session-digest.js +9 -2
  504. package/dist/shared.d.ts +12 -1
  505. package/dist/shared.js +33 -27
  506. package/dist/skills.d.ts +3 -0
  507. package/dist/skills.js +88 -70
  508. package/dist/stdin.js +2 -2
  509. package/dist/store/audit-event.d.ts +19 -0
  510. package/dist/store/audit-event.js +33 -0
  511. package/dist/store/candidates.d.ts +42 -0
  512. package/dist/store/candidates.js +152 -0
  513. package/dist/store/conflicts.d.ts +44 -0
  514. package/dist/store/conflicts.js +444 -0
  515. package/dist/store/delete-and-batch.d.ts +63 -0
  516. package/dist/store/delete-and-batch.js +310 -0
  517. package/dist/store/entry-reads.d.ts +92 -0
  518. package/dist/store/entry-reads.js +255 -0
  519. package/dist/store/entry-row.d.ts +67 -0
  520. package/dist/store/entry-row.js +208 -0
  521. package/dist/store/entry-writes.d.ts +46 -0
  522. package/dist/store/entry-writes.js +148 -0
  523. package/dist/store/handoffs.d.ts +26 -0
  524. package/dist/store/handoffs.js +184 -0
  525. package/dist/store/index-and-stats.d.ts +52 -0
  526. package/dist/store/index-and-stats.js +214 -0
  527. package/dist/store/markdown.d.ts +10 -0
  528. package/dist/store/markdown.js +108 -0
  529. package/dist/store/mirrors.d.ts +49 -0
  530. package/dist/store/mirrors.js +312 -0
  531. package/dist/store/open.d.ts +15 -0
  532. package/dist/store/open.js +223 -0
  533. package/dist/store/rows.d.ts +178 -0
  534. package/dist/store/rows.js +158 -0
  535. package/dist/store/search-rows.d.ts +86 -0
  536. package/dist/store/search-rows.js +260 -0
  537. package/dist/store/sessions.d.ts +83 -0
  538. package/dist/store/sessions.js +272 -0
  539. package/dist/store/summaries.d.ts +94 -0
  540. package/dist/store/summaries.js +377 -0
  541. package/dist/store/tenant-lookup.d.ts +12 -0
  542. package/dist/store/tenant-lookup.js +16 -0
  543. package/dist/store-cards.js +2 -1
  544. package/dist/summary-dirty.d.ts +4 -0
  545. package/dist/summary-dirty.js +32 -0
  546. package/dist/support-bundle.js +4 -3
  547. package/dist/tenant.js +2 -2
  548. package/dist/tokenize.d.ts +2 -0
  549. package/dist/tokenize.js +16 -0
  550. package/dist/transcript-tail.d.ts +7 -0
  551. package/dist/transcript-tail.js +48 -0
  552. package/dist/vector-store.d.ts +27 -0
  553. package/dist/vector-store.js +210 -0
  554. package/dist/version.d.ts +2 -2
  555. package/dist/version.js +2 -2
  556. package/dist/working-memory.js +1 -1
  557. package/dist/yaml.js +36 -11
  558. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  559. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  560. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  561. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  562. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  563. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  564. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  565. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  566. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  567. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  568. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  569. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  570. package/dist-ui/assets/index-DPN7cP19.js +33 -0
  571. package/dist-ui/assets/index-dFloKRVr.css +1 -0
  572. package/dist-ui/index.html +3 -25
  573. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  574. package/extensions/openclaw-plugin/package.json +1 -1
  575. package/openclaw.plugin.json +1 -1
  576. package/package.json +1 -1
  577. package/dist/capture.d.ts +0 -155
  578. package/dist/capture.js +0 -1295
  579. package/dist/consolidate.d.ts +0 -58
  580. package/dist/consolidate.js +0 -1124
  581. package/dist/graph.d.ts +0 -245
  582. package/dist/hooks.d.ts +0 -208
  583. package/dist/hooks.js +0 -1076
  584. package/dist/importers.js +0 -900
  585. package/dist/predictions.js +0 -620
  586. package/dist/search.d.ts +0 -320
  587. package/dist/search.js +0 -970
  588. package/dist/store.d.ts +0 -776
  589. package/dist/store.js +0 -3473
  590. package/dist-ui/assets/d3-BiWEKnn4.js +0 -1
  591. package/dist-ui/assets/index-BhT8RvO6.js +0 -61
  592. package/dist-ui/assets/index-RoXXJ5dq.css +0 -1
  593. package/dist-ui/assets/three-BDgTxR1l.js +0 -4112
@@ -21,12 +21,13 @@
21
21
  */
22
22
  import { BadRequestError, ConflictError, NotFoundError } from './api-errors.js';
23
23
  import { openHippoDb, closeHippoDb } from './db.js';
24
- import { writeEntry } from './store.js';
24
+ import { writeEntry } from './store/entry-writes.js';
25
25
  import { assertTenantId } from './tenant.js';
26
- import { markGraphDirty, removeGraphEntitiesForObject } from './graph.js';
26
+ import { markGraphDirty, removeGraphEntitiesForObject } from './graph/write.js';
27
27
  import { createMemory, Layer } from './memory.js';
28
28
  import { appendAuditEvent } from './audit.js';
29
29
  import { objectHalfLifeDays } from './half-life-migration.js';
30
+ import { keysetAfter } from './keyset.js';
30
31
  export const VALID_NOTE_STATES = new Set([
31
32
  'active',
32
33
  'superseded',
@@ -93,9 +94,79 @@ const NOTE_COLS = `
93
94
  function buildNoteContent(customer, note) {
94
95
  return `${customer}\n\n${note}`;
95
96
  }
96
- // ---------------------------------------------------------------------------
97
- // Public API
98
- // ---------------------------------------------------------------------------
97
+ // Preflight the supersede target BEFORE inserting the new row (so the new
98
+ // autoincrement id can never be its own supersede target); read the
99
+ // predecessor version in the same SELECT for server-derived versioning.
100
+ // Mirrors saveProjectBrief / saveSkill (codex P1 2026-05-28).
101
+ function preflightNoteSupersede(db, tenantId, supersedesId) {
102
+ // SAFETY: SELECT projects exactly status, version; .get() returns that
103
+ // shape for the matching row, or undefined when no note/tenant pair matches.
104
+ const pred = db.prepare(`SELECT status, version FROM customer_notes WHERE id = ? AND tenant_id = ?`).get(supersedesId, tenantId);
105
+ if (!pred) {
106
+ throw new NotFoundError(`saveCustomerNote: note ${supersedesId} to supersede not found for tenant ${tenantId}`);
107
+ }
108
+ if (pred.status !== 'active') {
109
+ throw new ConflictError(`saveCustomerNote: note ${supersedesId} is not active (status='${pred.status}'); only active notes can be superseded.`);
110
+ }
111
+ return pred.version + 1;
112
+ }
113
+ function insertNoteRow(db, memoryId, w, version) {
114
+ const result = db.prepare(`
115
+ INSERT INTO customer_notes(
116
+ memory_id, tenant_id, customer, note, version,
117
+ status, superseded_by, superseded_at, change_summary, closed_at, created_at
118
+ ) VALUES (?, ?, ?, ?, ?, 'active', NULL, NULL, ?, NULL, ?)
119
+ `).run(memoryId, w.tenantId, w.customer, w.note, version, w.changeSummary, w.now);
120
+ return Number(result.lastInsertRowid ?? 0);
121
+ }
122
+ function supersedeNoteRow(db, w, supersedesId, noteId, version) {
123
+ const sup = db.prepare(`
124
+ UPDATE customer_notes
125
+ SET status = 'superseded', superseded_by = ?, superseded_at = ?
126
+ WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
127
+ `).run(noteId, w.now, supersedesId, w.tenantId, noteId);
128
+ if (sup.changes === 0) {
129
+ throw new ConflictError(`saveCustomerNote: note ${supersedesId} could not be superseded (no longer active or self-reference).`);
130
+ }
131
+ appendAuditEvent(db, {
132
+ tenantId: w.tenantId,
133
+ actor: w.actor,
134
+ op: 'customer_note_supersede',
135
+ targetId: String(supersedesId),
136
+ metadata: {
137
+ note_id: supersedesId,
138
+ superseded_by: noteId,
139
+ new_version: version,
140
+ },
141
+ });
142
+ }
143
+ /** The afterWrite body: preflight, INSERT, supersede, reload, create audit, all in one SAVEPOINT. */
144
+ function writeNoteRow(db, memoryId, w) {
145
+ const version = w.supersedesId !== undefined ? preflightNoteSupersede(db, w.tenantId, w.supersedesId) : 1;
146
+ const noteId = insertNoteRow(db, memoryId, w, version);
147
+ if (w.supersedesId !== undefined)
148
+ supersedeNoteRow(db, w, w.supersedesId, noteId, version);
149
+ // SAFETY: SELECT ${NOTE_COLS} projects exactly the CustomerNoteRow columns;
150
+ // .get() returns that row, or undefined only if the just-inserted id can't
151
+ // be found.
152
+ const row = db.prepare(`SELECT ${NOTE_COLS} FROM customer_notes WHERE id = ?`)
153
+ .get(noteId);
154
+ if (!row)
155
+ throw new Error('saveCustomerNote: failed to reload saved note row');
156
+ // GDPR-light metadata: ids + flags only, no note text.
157
+ appendAuditEvent(db, {
158
+ tenantId: w.tenantId,
159
+ actor: w.actor,
160
+ op: 'customer_note_create',
161
+ targetId: String(noteId),
162
+ metadata: {
163
+ note_id: noteId,
164
+ customer: w.customer,
165
+ version,
166
+ },
167
+ });
168
+ return row;
169
+ }
99
170
  /**
100
171
  * Create a customer_note (or a new version that supersedes an existing one). Writes
101
172
  * the memory mirror + the customer_notes row atomically inside writeEntry's SAVEPOINT.
@@ -124,75 +195,14 @@ export function saveCustomerNote(hippoRoot, tenantId, opts, actor = 'cli') {
124
195
  baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
125
196
  tenantId,
126
197
  });
198
+ const w = {
199
+ tenantId, actor, customer, note: opts.note, changeSummary, supersedesId: opts.supersedesNoteId, now,
200
+ };
127
201
  let savedRow;
128
202
  writeEntry(hippoRoot, mem, {
129
203
  actor,
130
204
  afterWrite: (db, memoryId) => {
131
- // Preflight the supersede target BEFORE inserting the new row (so the new
132
- // autoincrement id can never be its own supersede target); read the
133
- // predecessor version in the same SELECT for server-derived versioning.
134
- // Mirrors saveProjectBrief / saveSkill (codex P1 2026-05-28).
135
- let version = 1;
136
- if (opts.supersedesNoteId !== undefined) {
137
- // SAFETY: SELECT projects exactly status, version; .get() returns that
138
- // shape for the matching row, or undefined when no note/tenant pair matches.
139
- const pred = db.prepare(`SELECT status, version FROM customer_notes WHERE id = ? AND tenant_id = ?`).get(opts.supersedesNoteId, tenantId);
140
- if (!pred) {
141
- throw new NotFoundError(`saveCustomerNote: note ${opts.supersedesNoteId} to supersede not found for tenant ${tenantId}`);
142
- }
143
- if (pred.status !== 'active') {
144
- throw new ConflictError(`saveCustomerNote: note ${opts.supersedesNoteId} is not active (status='${pred.status}'); only active notes can be superseded.`);
145
- }
146
- version = pred.version + 1;
147
- }
148
- const result = db.prepare(`
149
- INSERT INTO customer_notes(
150
- memory_id, tenant_id, customer, note, version,
151
- status, superseded_by, superseded_at, change_summary, closed_at, created_at
152
- ) VALUES (?, ?, ?, ?, ?, 'active', NULL, NULL, ?, NULL, ?)
153
- `).run(memoryId, tenantId, customer, opts.note, version, changeSummary, now);
154
- const noteId = Number(result.lastInsertRowid ?? 0);
155
- if (opts.supersedesNoteId !== undefined) {
156
- const sup = db.prepare(`
157
- UPDATE customer_notes
158
- SET status = 'superseded', superseded_by = ?, superseded_at = ?
159
- WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
160
- `).run(noteId, now, opts.supersedesNoteId, tenantId, noteId);
161
- if (sup.changes === 0) {
162
- throw new ConflictError(`saveCustomerNote: note ${opts.supersedesNoteId} could not be superseded (no longer active or self-reference).`);
163
- }
164
- appendAuditEvent(db, {
165
- tenantId,
166
- actor,
167
- op: 'customer_note_supersede',
168
- targetId: String(opts.supersedesNoteId),
169
- metadata: {
170
- note_id: opts.supersedesNoteId,
171
- superseded_by: noteId,
172
- new_version: version,
173
- },
174
- });
175
- }
176
- // SAFETY: SELECT ${NOTE_COLS} projects exactly the CustomerNoteRow columns;
177
- // .get() returns that row, or undefined only if the just-inserted id can't
178
- // be found.
179
- const row = db.prepare(`SELECT ${NOTE_COLS} FROM customer_notes WHERE id = ?`)
180
- .get(noteId);
181
- if (!row)
182
- throw new Error('saveCustomerNote: failed to reload saved note row');
183
- savedRow = row;
184
- // GDPR-light metadata: ids + flags only, no note text.
185
- appendAuditEvent(db, {
186
- tenantId,
187
- actor,
188
- op: 'customer_note_create',
189
- targetId: String(noteId),
190
- metadata: {
191
- note_id: noteId,
192
- customer,
193
- version,
194
- },
195
- });
205
+ savedRow = writeNoteRow(db, memoryId, w);
196
206
  },
197
207
  afterCommit: () => markGraphDirty(hippoRoot, tenantId, mem.id),
198
208
  });
@@ -284,6 +294,7 @@ export function loadCustomerNoteById(hippoRoot, tenantId, id) {
284
294
  export function loadCustomerNotes(hippoRoot, tenantId, opts = {}) {
285
295
  assertTenantId('loadCustomerNotes', tenantId);
286
296
  const limit = opts.limit ?? 100;
297
+ const after = keysetAfter('created_at', 'id', opts.after);
287
298
  if (opts.status && !VALID_NOTE_STATES.has(opts.status)) {
288
299
  throw new BadRequestError(`loadCustomerNotes: status must be one of ${Array.from(VALID_NOTE_STATES).join('|')}; got ${opts.status}`);
289
300
  }
@@ -299,13 +310,13 @@ export function loadCustomerNotes(hippoRoot, tenantId, opts = {}) {
299
310
  clauses.push('customer = ?');
300
311
  params.push(opts.customer);
301
312
  }
302
- params.push(limit);
313
+ params.push(...after.params, limit);
303
314
  // SAFETY: SELECT ${NOTE_COLS} projects exactly the CustomerNoteRow columns
304
315
  // regardless of the dynamic WHERE clause built above; .all() returns rows
305
316
  // in that shape.
306
317
  const rows = db.prepare(`
307
318
  SELECT ${NOTE_COLS} FROM customer_notes
308
- WHERE ${clauses.join(' AND ')}
319
+ WHERE ${clauses.join(' AND ')}${after.sql}
309
320
  ORDER BY created_at DESC, id DESC
310
321
  LIMIT ?
311
322
  `).all(...params);
package/dist/dag.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createMemory, Layer } from './memory.js';
2
- import { writeEntry, loadAllDirtySummaries, loadChildrenOfSummary, applyRebuildResult, clearSummaryDirtyAfterBuild, } from './store.js';
2
+ import { writeEntry } from './store/entry-writes.js';
3
+ import { loadAllDirtySummaries, loadChildrenOfSummary, applyRebuildResult, clearSummaryDirtyAfterBuild, } from './store/summaries.js';
3
4
  import { RejectedValueError } from './rejection.js';
4
5
  import { redactSecretsStrict } from './secret-detect.js';
5
6
  import { fetchWithRetry, llmTimeoutMs } from './http-retry.js';
@@ -86,17 +87,14 @@ export async function generateDagSummary(label, factContents, opts) {
86
87
  return null;
87
88
  }
88
89
  }
89
- export async function buildDag(hippoRoot, facts, opts) {
90
- const result = { candidateClusters: 0, summariesCreated: 0, factsLinked: 0, rejected: 0 };
91
- const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
92
- const unparented = facts.filter((f) => f.dag_level === 1 && !f.dag_parent_id && f.tags.includes('extracted'));
93
- // Hardening follow-up (mirrors consolidate.ts's mergeCandidatesByTenant,
94
- // T1): partition unparented facts by tenantId BEFORE clustering so a
95
- // cluster can never mix facts from different tenants into one
96
- // LLM-synthesized summary. Map preserves insertion order, so
97
- // single-tenant stores (every row 'default') get exactly one partition
98
- // and iterate in the same order as before this fix — byte-identical
99
- // behavior there.
90
+ // Hardening follow-up (mirrors consolidate.ts's mergeCandidatesByTenant,
91
+ // T1): partition unparented facts by tenantId BEFORE clustering so a
92
+ // cluster can never mix facts from different tenants into one
93
+ // LLM-synthesized summary. Map preserves insertion order, so
94
+ // single-tenant stores (every row 'default') get exactly one partition
95
+ // and iterate in the same order as before this fix — byte-identical
96
+ // behavior there.
97
+ function partitionFactsByTenant(unparented) {
100
98
  const unparentedByTenant = new Map();
101
99
  for (const fact of unparented) {
102
100
  const key = derivationPartitionKey(fact.tenantId, fact.scope, fact.origin_project);
@@ -106,79 +104,149 @@ export async function buildDag(hippoRoot, facts, opts) {
106
104
  else
107
105
  unparentedByTenant.set(key, [fact]);
108
106
  }
109
- for (const [, tenantFacts] of unparentedByTenant) {
110
- const factTenant = tenantFacts[0].tenantId;
111
- const factScope = derivationScope(tenantFacts[0].scope);
107
+ return unparentedByTenant;
108
+ }
109
+ /** The L2 summary entry for one cluster, landing in the facts' own tenant and scope. */
110
+ function createClusterSummaryEntry(summary, cluster, home) {
111
+ const memberCreatedAts = cluster.members.map((m) => m.created).sort();
112
+ // Every member of `cluster` shares home.tenantId by construction (the
113
+ // tenant partition above), so the summary lands in the same tenant
114
+ // as the facts it summarizes instead of always 'default'
115
+ // (memory.ts:535 defaults tenantId when the option is omitted).
116
+ const summaryEntry = createMemory(summary, {
117
+ layer: Layer.Semantic,
118
+ tags: [...cluster.entityTags, ...neverAutoShareTags(cluster.members), 'dag-summary'],
119
+ confidence: 'inferred',
120
+ dag_level: 2,
121
+ tenantId: home.tenantId,
122
+ scope: home.scope,
123
+ baseHalfLifeDays: home.baseHalfLifeDays,
124
+ });
125
+ summaryEntry.origin_project = home.originProject;
126
+ // Schema v25: cache descendant_count + earliest/latest_at on the summary
127
+ // row so DAG-aware recall (docs/plans/2026-05-05-dag-recall.md Task 2)
128
+ // can reason about scope without walking the children.
129
+ summaryEntry.descendant_count = cluster.members.length;
130
+ summaryEntry.earliest_at = memberCreatedAts[0];
131
+ summaryEntry.latest_at = memberCreatedAts[memberCreatedAts.length - 1];
132
+ return summaryEntry;
133
+ }
134
+ /** Summarize one eligible cluster, write the summary, then re-parent its members under it. */
135
+ async function summarizeCluster(hippoRoot, cluster, home, opts, result) {
136
+ const summary = await generateDagSummary(cluster.label, cluster.members.map((m) => m.content), opts);
137
+ if (!summary)
138
+ return;
139
+ const summaryEntry = createClusterSummaryEntry(summary, cluster, home);
140
+ // AT1 (plan §3 containment): a refused LLM-synthesized summary skips
141
+ // ONLY this cluster — the sleep cycle continues to the next one. The
142
+ // member re-parenting writes below never run for a skipped cluster
143
+ // (there is no summary id to parent them under).
144
+ //
145
+ // The tombstone check itself is tenant-scoped for free: writeEntry ->
146
+ // writeEntryDbOnly -> upsertEntryRow calls
147
+ // checkRejectionGuard(db, entry.tenantId ?? 'default', ...)
148
+ // (store/entry-writes.ts), reading tenantId off the entry being written. Now
149
+ // that summaryEntry carries home.tenantId instead of the implicit
150
+ // 'default', the guard consults that tenant's tombstones — no
151
+ // separate check needed here (unlike consolidate.ts's merge pass,
152
+ // which pre-checks via findRejectedValue because it writes through
153
+ // batchWriteAndDelete's bypassRejectionGuard path instead of
154
+ // writeEntry).
155
+ try {
156
+ writeEntry(hippoRoot, summaryEntry);
157
+ }
158
+ catch (err) {
159
+ if (err instanceof RejectedValueError) {
160
+ result.rejected++;
161
+ log.warn(`buildDag: cluster "${cluster.label}" skipped: summary matches a rejected value`);
162
+ return;
163
+ }
164
+ throw err;
165
+ }
166
+ result.summariesCreated++;
167
+ for (const member of cluster.members) {
168
+ const updated = { ...member, dag_parent_id: summaryEntry.id };
169
+ writeEntry(hippoRoot, updated);
170
+ result.factsLinked++;
171
+ }
172
+ // v0.30 / E3 — cancel the cascade of dirty-marks fired by member
173
+ // writeEntry calls (E2 hook on writeEntryDbOnly in store/entry-writes.ts).
174
+ // The summary we just built IS fresh, no rebuild needed. Without this,
175
+ // E3 in the SAME sleep cycle would re-rebuild every new summary
176
+ // (2x LLM cost). plan-eng-r1 HIGH must-fix.
177
+ clearSummaryDirtyAfterBuild(hippoRoot, summaryEntry.id, summaryEntry.tenantId, 'buildDag');
178
+ }
179
+ export async function buildDag(hippoRoot, facts, opts) {
180
+ const result = { candidateClusters: 0, summariesCreated: 0, factsLinked: 0, rejected: 0 };
181
+ const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
182
+ const unparented = facts.filter((f) => f.dag_level === 1 && !f.dag_parent_id && f.tags.includes('extracted'));
183
+ for (const [, tenantFacts] of partitionFactsByTenant(unparented)) {
184
+ const home = {
185
+ tenantId: tenantFacts[0].tenantId,
186
+ scope: derivationScope(tenantFacts[0].scope),
187
+ originProject: tenantFacts[0].origin_project,
188
+ baseHalfLifeDays,
189
+ };
112
190
  const clusters = clusterFacts(tenantFacts);
113
191
  const eligibleClusters = clusters.filter((c) => c.members.length >= 3);
114
192
  result.candidateClusters += eligibleClusters.length;
115
193
  for (const cluster of eligibleClusters) {
116
- const summary = await generateDagSummary(cluster.label, cluster.members.map((m) => m.content), opts);
117
- if (!summary)
118
- continue;
119
- const memberCreatedAts = cluster.members.map((m) => m.created).sort();
120
- // Every member of `cluster` shares factTenant by construction (the
121
- // tenant partition above), so the summary lands in the same tenant
122
- // as the facts it summarizes instead of always 'default'
123
- // (memory.ts:535 defaults tenantId when the option is omitted).
124
- const summaryEntry = createMemory(summary, {
125
- layer: Layer.Semantic,
126
- tags: [...cluster.entityTags, ...neverAutoShareTags(cluster.members), 'dag-summary'],
127
- confidence: 'inferred',
128
- dag_level: 2,
129
- tenantId: factTenant,
130
- scope: factScope,
131
- baseHalfLifeDays,
132
- });
133
- summaryEntry.origin_project = tenantFacts[0].origin_project;
134
- // Schema v25: cache descendant_count + earliest/latest_at on the summary
135
- // row so DAG-aware recall (docs/plans/2026-05-05-dag-recall.md Task 2)
136
- // can reason about scope without walking the children.
137
- summaryEntry.descendant_count = cluster.members.length;
138
- summaryEntry.earliest_at = memberCreatedAts[0];
139
- summaryEntry.latest_at = memberCreatedAts[memberCreatedAts.length - 1];
140
- // AT1 (plan §3 containment): a refused LLM-synthesized summary skips
141
- // ONLY this cluster — the sleep cycle continues to the next one. The
142
- // member re-parenting writes below never run for a skipped cluster
143
- // (there is no summary id to parent them under).
144
- //
145
- // The tombstone check itself is tenant-scoped for free: writeEntry ->
146
- // writeEntryDbOnly -> upsertEntryRow calls
147
- // checkRejectionGuard(db, entry.tenantId ?? 'default', ...)
148
- // (store.ts:1172), reading tenantId off the entry being written. Now
149
- // that summaryEntry carries factTenant instead of the implicit
150
- // 'default', the guard consults that tenant's tombstones — no
151
- // separate check needed here (unlike consolidate.ts's merge pass,
152
- // which pre-checks via findRejectedValue because it writes through
153
- // batchWriteAndDelete's bypassRejectionGuard path instead of
154
- // writeEntry).
155
- try {
156
- writeEntry(hippoRoot, summaryEntry);
157
- }
158
- catch (err) {
159
- if (err instanceof RejectedValueError) {
160
- result.rejected++;
161
- log.warn(`buildDag: cluster "${cluster.label}" skipped: summary matches a rejected value`);
162
- continue;
163
- }
164
- throw err;
165
- }
166
- result.summariesCreated++;
167
- for (const member of cluster.members) {
168
- const updated = { ...member, dag_parent_id: summaryEntry.id };
169
- writeEntry(hippoRoot, updated);
170
- result.factsLinked++;
171
- }
172
- // v0.30 / E3 — cancel the cascade of dirty-marks fired by member
173
- // writeEntry calls (E2 hook on writeEntryDbOnly at store.ts:1214).
174
- // The summary we just built IS fresh, no rebuild needed. Without this,
175
- // E3 in the SAME sleep cycle would re-rebuild every new summary
176
- // (2x LLM cost). plan-eng-r1 HIGH must-fix.
177
- clearSummaryDirtyAfterBuild(hippoRoot, summaryEntry.id, summaryEntry.tenantId, 'buildDag');
194
+ await summarizeCluster(hippoRoot, cluster, home, opts, result);
178
195
  }
179
196
  }
180
197
  return result;
181
198
  }
199
+ /** Regenerate one dirty summary from its children and tally the outcome; throws reach the caller's isolation. */
200
+ async function rebuildOneSummary(hippoRoot, summary, opts, result) {
201
+ const children = loadChildrenOfSummary(hippoRoot, summary.id, summary.tenantId);
202
+ if (children.length === 0) {
203
+ // Zero-child case: clear dirty + zero counts, no LLM call, no rebuild_count bump.
204
+ const { changed } = applyRebuildResult(hippoRoot, summary, {
205
+ content: summary.content,
206
+ descendant_count: 0,
207
+ earliest_at: null,
208
+ latest_at: null,
209
+ bumpRebuildCount: false,
210
+ zeroChildren: true,
211
+ actor: 'sleep',
212
+ });
213
+ if (changed)
214
+ result.zeroChildSkipped++;
215
+ // changed=false → race lost / row vanished; silently skip. `refused`
216
+ // is always false here — applyRebuildResult only checks the
217
+ // tombstone when bumpRebuildCount is true (store/summaries.ts).
218
+ return;
219
+ }
220
+ // Derive label from summary's existing entity tags (mirrors clusterFacts)
221
+ const entityTags = summary.tags.filter((t) => t.startsWith('speaker:') || t.startsWith('topic:'));
222
+ const label = entityTags.length > 0
223
+ ? entityTags.map((t) => t.split(':')[1]).join(': ')
224
+ : summary.content.slice(0, 40);
225
+ const newContent = await generateDagSummary(label, children.map((c) => c.content), opts);
226
+ if (!newContent) {
227
+ // LLM null / fetch error → leave dirty for next cycle
228
+ result.failed++;
229
+ return;
230
+ }
231
+ const childCreatedAts = children.map((c) => c.created).sort();
232
+ const { changed, refused } = applyRebuildResult(hippoRoot, summary, {
233
+ content: newContent,
234
+ descendant_count: children.length,
235
+ earliest_at: childCreatedAts[0],
236
+ latest_at: childCreatedAts[childCreatedAts.length - 1],
237
+ bumpRebuildCount: true,
238
+ zeroChildren: false,
239
+ actor: 'sleep',
240
+ });
241
+ if (refused) {
242
+ result.refused++;
243
+ }
244
+ else if (changed) {
245
+ result.rebuilt++;
246
+ }
247
+ // changed=false (refused also false) → race lost; not failure, not
248
+ // success, silently skip
249
+ }
182
250
  /**
183
251
  * v0.30 / E3 — sleep-cycle phase that drains the dirty L2 summary queue.
184
252
  * Thin orchestrator; the heavy lifting lives in store.ts (load + apply)
@@ -220,59 +288,12 @@ export async function rebuildDirtySummaries(hippoRoot, opts) {
220
288
  await new Promise((resolve) => setImmediate(resolve));
221
289
  }
222
290
  try {
223
- const children = loadChildrenOfSummary(hippoRoot, summary.id, summary.tenantId);
224
- if (children.length === 0) {
225
- // Zero-child case: clear dirty + zero counts, no LLM call, no rebuild_count bump.
226
- const { changed } = applyRebuildResult(hippoRoot, summary, {
227
- content: summary.content,
228
- descendant_count: 0,
229
- earliest_at: null,
230
- latest_at: null,
231
- bumpRebuildCount: false,
232
- zeroChildren: true,
233
- actor: 'sleep',
234
- });
235
- if (changed)
236
- result.zeroChildSkipped++;
237
- // changed=false → race lost / row vanished; silently skip. `refused`
238
- // is always false here — applyRebuildResult only checks the
239
- // tombstone when bumpRebuildCount is true (store.ts:3398).
240
- continue;
241
- }
242
- // Derive label from summary's existing entity tags (mirrors clusterFacts)
243
- const entityTags = summary.tags.filter((t) => t.startsWith('speaker:') || t.startsWith('topic:'));
244
- const label = entityTags.length > 0
245
- ? entityTags.map((t) => t.split(':')[1]).join(': ')
246
- : summary.content.slice(0, 40);
247
- const newContent = await generateDagSummary(label, children.map((c) => c.content), opts);
248
- if (!newContent) {
249
- // LLM null / fetch error → leave dirty for next cycle
250
- result.failed++;
251
- continue;
252
- }
253
- const childCreatedAts = children.map((c) => c.created).sort();
254
- const { changed, refused } = applyRebuildResult(hippoRoot, summary, {
255
- content: newContent,
256
- descendant_count: children.length,
257
- earliest_at: childCreatedAts[0],
258
- latest_at: childCreatedAts[childCreatedAts.length - 1],
259
- bumpRebuildCount: true,
260
- zeroChildren: false,
261
- actor: 'sleep',
262
- });
263
- if (refused) {
264
- result.refused++;
265
- }
266
- else if (changed) {
267
- result.rebuilt++;
268
- }
269
- // changed=false (refused also false) → race lost; not failure, not
270
- // success, silently skip
291
+ await rebuildOneSummary(hippoRoot, summary, opts, result);
271
292
  }
272
293
  catch (err) {
273
294
  // Per-summary failure isolation — one throw doesn't abort the queue.
274
295
  // independent-review MED #2 fold: log enough to triage in production
275
- // (audit() wraps its own writes try/catch per store.ts:2566, so a
296
+ // (audit() wraps its own writes try/catch per store/audit-event.ts, so a
276
297
  // throw here is exotic: SQLite I/O error, prepare failure, etc).
277
298
  result.failed++;
278
299
  log.error(`rebuildDirtySummaries: summary ${summary.id} (tenant ${summary.tenantId}) failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -280,6 +301,76 @@ export async function rebuildDirtySummaries(hippoRoot, opts) {
280
301
  }
281
302
  return result;
282
303
  }
304
+ // independent-review HIGH #1 fold: cluster ONLY within-tenant.
305
+ // clusterFacts has no tenant awareness; without this partition step a
306
+ // multi-tenant host could form a cluster spanning tenants and produce
307
+ // a single L3 with tenantId='default' that doesn't belong to either
308
+ // child tenant. Fix: bucket by tenantId, run clusterFacts per-tenant,
309
+ // pass tenantId to createMemory.
310
+ function partitionL2sByTenant(unparented) {
311
+ const byTenant = new Map();
312
+ for (const l2 of unparented) {
313
+ const tid = l2.tenantId ?? 'default';
314
+ const key = derivationPartitionKey(tid, l2.scope, l2.origin_project);
315
+ const list = byTenant.get(key) ?? [];
316
+ list.push(l2);
317
+ byTenant.set(key, list);
318
+ }
319
+ return byTenant;
320
+ }
321
+ /** The L3 profile entry for one cluster of L2 summaries. */
322
+ function createProfileEntry(summary, cluster, home) {
323
+ const memberCreatedAts = cluster.members.map((m) => m.created).sort();
324
+ const nowIso = new Date().toISOString();
325
+ const profileEntry = createMemory(summary, {
326
+ layer: Layer.Semantic,
327
+ tags: [...cluster.entityTags, ...neverAutoShareTags(cluster.members), 'dag-entity-profile'],
328
+ confidence: 'inferred',
329
+ dag_level: 3,
330
+ tenantId: home.tenantId, // HIGH #1 fold: thread tenant explicitly
331
+ scope: home.scope,
332
+ baseHalfLifeDays: home.baseHalfLifeDays,
333
+ });
334
+ profileEntry.origin_project = home.originProject;
335
+ profileEntry.descendant_count = cluster.members.length;
336
+ profileEntry.earliest_at = memberCreatedAts[0];
337
+ profileEntry.latest_at = memberCreatedAts[memberCreatedAts.length - 1];
338
+ profileEntry.dag_level_3_built_at = nowIso;
339
+ return profileEntry;
340
+ }
341
+ /** Profile one eligible cluster of L2s, write it, then re-link the L2s under it. */
342
+ async function profileCluster(hippoRoot, cluster, home, opts, result) {
343
+ const summary = await generateDagSummary(cluster.label, cluster.members.map((m) => m.content), opts);
344
+ if (!summary) {
345
+ result.failed++;
346
+ return;
347
+ }
348
+ const profileEntry = createProfileEntry(summary, cluster, home);
349
+ // AT1 (plan §3 containment): per-cluster catch — skip this cluster,
350
+ // count, log once. The member re-linking writes below never run for a
351
+ // skipped cluster (mirrors buildDag above).
352
+ try {
353
+ writeEntry(hippoRoot, profileEntry);
354
+ }
355
+ catch (err) {
356
+ if (err instanceof RejectedValueError) {
357
+ result.rejected++;
358
+ log.warn(`buildEntityProfiles: cluster "${cluster.label}" skipped: profile matches a rejected value`);
359
+ return;
360
+ }
361
+ throw err;
362
+ }
363
+ result.profilesCreated++;
364
+ for (const member of cluster.members) {
365
+ const updated = { ...member, dag_parent_id: profileEntry.id };
366
+ writeEntry(hippoRoot, updated);
367
+ result.l2sLinked++;
368
+ }
369
+ // E3 born-dirty cancellation, same dance as buildDag L161-168 but for
370
+ // L3. Pass source='buildEntityProfiles-clean' to distinguish in audit.
371
+ // Args: (root, id, tenantId, actor, source).
372
+ clearSummaryDirtyAfterBuild(hippoRoot, profileEntry.id, home.tenantId, 'buildEntityProfiles', 'buildEntityProfiles-clean');
373
+ }
283
374
  /**
284
375
  * v0.30 / E5 — build L3 entity profiles by clustering L2 summaries with
285
376
  * shared entity tags. Threshold 2+ L2s per entity. Mirrors buildDag L1->L2
@@ -302,72 +393,18 @@ export async function buildEntityProfiles(hippoRoot, l2Summaries, opts) {
302
393
  const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
303
394
  // Only L2 with no L3 parent yet (avoid re-clustering already-profiled L2s).
304
395
  const unparented = l2Summaries.filter((s) => s.dag_level === 2 && !s.dag_parent_id);
305
- // independent-review HIGH #1 fold: cluster ONLY within-tenant.
306
- // clusterFacts has no tenant awareness; without this partition step a
307
- // multi-tenant host could form a cluster spanning tenants and produce
308
- // a single L3 with tenantId='default' that doesn't belong to either
309
- // child tenant. Fix: bucket by tenantId, run clusterFacts per-tenant,
310
- // pass tenantId to createMemory.
311
- const byTenant = new Map();
312
- for (const l2 of unparented) {
313
- const tid = l2.tenantId ?? 'default';
314
- const key = derivationPartitionKey(tid, l2.scope, l2.origin_project);
315
- const list = byTenant.get(key) ?? [];
316
- list.push(l2);
317
- byTenant.set(key, list);
318
- }
319
- for (const [, tenantL2s] of byTenant) {
320
- const tenantId = tenantL2s[0].tenantId ?? 'default';
321
- const scope = derivationScope(tenantL2s[0].scope);
396
+ for (const [, tenantL2s] of partitionL2sByTenant(unparented)) {
397
+ const home = {
398
+ tenantId: tenantL2s[0].tenantId ?? 'default',
399
+ scope: derivationScope(tenantL2s[0].scope),
400
+ originProject: tenantL2s[0].origin_project,
401
+ baseHalfLifeDays,
402
+ };
322
403
  const clusters = clusterFacts(tenantL2s);
323
404
  const eligible = clusters.filter((c) => c.members.length >= 2);
324
405
  result.candidateClusters += eligible.length;
325
406
  for (const cluster of eligible) {
326
- const summary = await generateDagSummary(cluster.label, cluster.members.map((m) => m.content), opts);
327
- if (!summary) {
328
- result.failed++;
329
- continue;
330
- }
331
- const memberCreatedAts = cluster.members.map((m) => m.created).sort();
332
- const nowIso = new Date().toISOString();
333
- const profileEntry = createMemory(summary, {
334
- layer: Layer.Semantic,
335
- tags: [...cluster.entityTags, ...neverAutoShareTags(cluster.members), 'dag-entity-profile'],
336
- confidence: 'inferred',
337
- dag_level: 3,
338
- tenantId, // HIGH #1 fold: thread tenant explicitly
339
- scope,
340
- baseHalfLifeDays,
341
- });
342
- profileEntry.origin_project = tenantL2s[0].origin_project;
343
- profileEntry.descendant_count = cluster.members.length;
344
- profileEntry.earliest_at = memberCreatedAts[0];
345
- profileEntry.latest_at = memberCreatedAts[memberCreatedAts.length - 1];
346
- profileEntry.dag_level_3_built_at = nowIso;
347
- // AT1 (plan §3 containment): per-cluster catch — skip this cluster,
348
- // count, log once. The member re-linking writes below never run for a
349
- // skipped cluster (mirrors buildDag above).
350
- try {
351
- writeEntry(hippoRoot, profileEntry);
352
- }
353
- catch (err) {
354
- if (err instanceof RejectedValueError) {
355
- result.rejected++;
356
- log.warn(`buildEntityProfiles: cluster "${cluster.label}" skipped: profile matches a rejected value`);
357
- continue;
358
- }
359
- throw err;
360
- }
361
- result.profilesCreated++;
362
- for (const member of cluster.members) {
363
- const updated = { ...member, dag_parent_id: profileEntry.id };
364
- writeEntry(hippoRoot, updated);
365
- result.l2sLinked++;
366
- }
367
- // E3 born-dirty cancellation, same dance as buildDag L161-168 but for
368
- // L3. Pass source='buildEntityProfiles-clean' to distinguish in audit.
369
- // Args: (root, id, tenantId, actor, source).
370
- clearSummaryDirtyAfterBuild(hippoRoot, profileEntry.id, tenantId, 'buildEntityProfiles', 'buildEntityProfiles-clean');
407
+ await profileCluster(hippoRoot, cluster, home, opts, result);
371
408
  }
372
409
  }
373
410
  return result;