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,6 +21,7 @@
21
21
  * Lifecycle: active -> superseded (a newer version replaces it) or active ->
22
22
  * closed (retired).
23
23
  */
24
+ import { type KeysetPosition } from './keyset.js';
24
25
  export type BriefStatus = 'active' | 'superseded' | 'closed';
25
26
  export declare const VALID_BRIEF_STATES: ReadonlySet<BriefStatus>;
26
27
  /** Field caps (untrusted at the HTTP/SDK boundary). summary is a body, so a larger
@@ -72,6 +73,8 @@ export interface ListProjectBriefsOpts {
72
73
  /** Filter to a single repo. */
73
74
  repo?: string;
74
75
  limit?: number;
76
+ /** Resume after this row: the position the previous page ended on. */
77
+ after?: KeysetPosition;
75
78
  }
76
79
  /**
77
80
  * Create a project_brief (or a new version that supersedes an existing one). Writes
@@ -23,13 +23,14 @@
23
23
  */
24
24
  import { BadRequestError, ConflictError, NotFoundError } from './api-errors.js';
25
25
  import { openHippoDb, closeHippoDb } from './db.js';
26
- import { writeEntry } from './store.js';
26
+ import { writeEntry } from './store/entry-writes.js';
27
27
  import { assertTenantId } from './tenant.js';
28
28
  import { RECALL_DEFAULT_DENY_SCOPES } from './recall-scope.js';
29
- import { markGraphDirty, removeGraphEntitiesForObject } from './graph.js';
29
+ import { markGraphDirty, removeGraphEntitiesForObject } from './graph/write.js';
30
30
  import { createMemory, Layer } from './memory.js';
31
31
  import { appendAuditEvent } from './audit.js';
32
32
  import { objectHalfLifeDays } from './half-life-migration.js';
33
+ import { keysetAfter } from './keyset.js';
33
34
  export const VALID_BRIEF_STATES = new Set([
34
35
  'active',
35
36
  'superseded',
@@ -102,9 +103,83 @@ const BRIEF_COLS = `
102
103
  function buildBriefContent(repo, summary) {
103
104
  return `${repo}\n\n${summary}`;
104
105
  }
105
- // ---------------------------------------------------------------------------
106
- // Public API
107
- // ---------------------------------------------------------------------------
106
+ // Preflight the supersede target BEFORE inserting the new row (so the new
107
+ // autoincrement id can never be its own supersede target); read the
108
+ // predecessor version in the same SELECT for server-derived versioning.
109
+ // Mirrors saveSkill / saveProcess (codex P1 2026-05-28).
110
+ function preflightBriefSupersede(db, tenantId, supersedesId) {
111
+ // SAFETY: SELECT projects exactly status, version; .get() returns that
112
+ // shape for the matching row, or undefined when no brief/tenant pair matches.
113
+ const pred = db.prepare(`SELECT status, version FROM project_briefs WHERE id = ? AND tenant_id = ?`).get(supersedesId, tenantId);
114
+ if (!pred) {
115
+ throw new NotFoundError(`saveProjectBrief: brief ${supersedesId} to supersede not found for tenant ${tenantId}`);
116
+ }
117
+ if (pred.status !== 'active') {
118
+ throw new ConflictError(`saveProjectBrief: brief ${supersedesId} is not active (status='${pred.status}'); only active briefs can be superseded.`);
119
+ }
120
+ return pred.version + 1;
121
+ }
122
+ function insertBriefRow(db, memoryId, w, version) {
123
+ const result = db.prepare(`
124
+ INSERT INTO project_briefs(
125
+ memory_id, tenant_id, repo, summary, version,
126
+ status, superseded_by, superseded_at, change_summary, closed_at, created_at
127
+ ) VALUES (?, ?, ?, ?, ?, 'active', NULL, NULL, ?, NULL, ?)
128
+ `).run(memoryId, w.tenantId, w.repo, w.summary, version, w.changeSummary, w.now);
129
+ return Number(result.lastInsertRowid ?? 0);
130
+ }
131
+ function supersedeBriefRow(db, w, supersedesId, briefId, version) {
132
+ const sup = db.prepare(`
133
+ UPDATE project_briefs
134
+ SET status = 'superseded', superseded_by = ?, superseded_at = ?
135
+ WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
136
+ `).run(briefId, w.now, supersedesId, w.tenantId, briefId);
137
+ if (sup.changes === 0) {
138
+ throw new ConflictError(`saveProjectBrief: brief ${supersedesId} could not be superseded (no longer active or self-reference).`);
139
+ }
140
+ appendAuditEvent(db, {
141
+ tenantId: w.tenantId,
142
+ actor: w.actor,
143
+ op: 'project_brief_supersede',
144
+ targetId: String(supersedesId),
145
+ metadata: {
146
+ brief_id: supersedesId,
147
+ superseded_by: briefId,
148
+ new_version: version,
149
+ refreshed: w.isRefresh,
150
+ ...w.refreshAuditExtra,
151
+ },
152
+ });
153
+ }
154
+ /** The afterWrite body: preflight, INSERT, supersede, reload, create audit, all in one SAVEPOINT. */
155
+ function writeBriefRow(db, memoryId, w) {
156
+ const version = w.supersedesId !== undefined ? preflightBriefSupersede(db, w.tenantId, w.supersedesId) : 1;
157
+ const briefId = insertBriefRow(db, memoryId, w, version);
158
+ if (w.supersedesId !== undefined)
159
+ supersedeBriefRow(db, w, w.supersedesId, briefId, version);
160
+ // SAFETY: SELECT ${BRIEF_COLS} projects exactly the ProjectBriefRow
161
+ // columns; .get() returns that row, or undefined only if the
162
+ // just-inserted id can't be found.
163
+ const row = db.prepare(`SELECT ${BRIEF_COLS} FROM project_briefs WHERE id = ?`)
164
+ .get(briefId);
165
+ if (!row)
166
+ throw new Error('saveProjectBrief: failed to reload saved brief row');
167
+ // GDPR-light metadata: ids + flags only, no brief text.
168
+ appendAuditEvent(db, {
169
+ tenantId: w.tenantId,
170
+ actor: w.actor,
171
+ op: 'project_brief_create',
172
+ targetId: String(briefId),
173
+ metadata: {
174
+ brief_id: briefId,
175
+ repo: w.repo,
176
+ version,
177
+ refreshed: w.isRefresh,
178
+ ...w.refreshAuditExtra,
179
+ },
180
+ });
181
+ return row;
182
+ }
108
183
  /**
109
184
  * Create a project_brief (or a new version that supersedes an existing one). Writes
110
185
  * the memory mirror + the project_briefs row atomically inside writeEntry's
@@ -132,79 +207,22 @@ export function saveProjectBrief(hippoRoot, tenantId, opts, actor = 'cli') {
132
207
  baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
133
208
  tenantId,
134
209
  });
210
+ const w = {
211
+ tenantId,
212
+ actor,
213
+ repo,
214
+ summary: opts.summary,
215
+ changeSummary,
216
+ supersedesId: opts.supersedesBriefId,
217
+ isRefresh,
218
+ refreshAuditExtra,
219
+ now,
220
+ };
135
221
  let savedRow;
136
222
  writeEntry(hippoRoot, mem, {
137
223
  actor,
138
224
  afterWrite: (db, memoryId) => {
139
- // Preflight the supersede target BEFORE inserting the new row (so the new
140
- // autoincrement id can never be its own supersede target); read the
141
- // predecessor version in the same SELECT for server-derived versioning.
142
- // Mirrors saveSkill / saveProcess (codex P1 2026-05-28).
143
- let version = 1;
144
- if (opts.supersedesBriefId !== undefined) {
145
- // SAFETY: SELECT projects exactly status, version; .get() returns that
146
- // shape for the matching row, or undefined when no brief/tenant pair matches.
147
- const pred = db.prepare(`SELECT status, version FROM project_briefs WHERE id = ? AND tenant_id = ?`).get(opts.supersedesBriefId, tenantId);
148
- if (!pred) {
149
- throw new NotFoundError(`saveProjectBrief: brief ${opts.supersedesBriefId} to supersede not found for tenant ${tenantId}`);
150
- }
151
- if (pred.status !== 'active') {
152
- throw new ConflictError(`saveProjectBrief: brief ${opts.supersedesBriefId} is not active (status='${pred.status}'); only active briefs can be superseded.`);
153
- }
154
- version = pred.version + 1;
155
- }
156
- const result = db.prepare(`
157
- INSERT INTO project_briefs(
158
- memory_id, tenant_id, repo, summary, version,
159
- status, superseded_by, superseded_at, change_summary, closed_at, created_at
160
- ) VALUES (?, ?, ?, ?, ?, 'active', NULL, NULL, ?, NULL, ?)
161
- `).run(memoryId, tenantId, repo, opts.summary, version, changeSummary, now);
162
- const briefId = Number(result.lastInsertRowid ?? 0);
163
- if (opts.supersedesBriefId !== undefined) {
164
- const sup = db.prepare(`
165
- UPDATE project_briefs
166
- SET status = 'superseded', superseded_by = ?, superseded_at = ?
167
- WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
168
- `).run(briefId, now, opts.supersedesBriefId, tenantId, briefId);
169
- if (sup.changes === 0) {
170
- throw new ConflictError(`saveProjectBrief: brief ${opts.supersedesBriefId} could not be superseded (no longer active or self-reference).`);
171
- }
172
- appendAuditEvent(db, {
173
- tenantId,
174
- actor,
175
- op: 'project_brief_supersede',
176
- targetId: String(opts.supersedesBriefId),
177
- metadata: {
178
- brief_id: opts.supersedesBriefId,
179
- superseded_by: briefId,
180
- new_version: version,
181
- refreshed: isRefresh,
182
- ...refreshAuditExtra,
183
- },
184
- });
185
- }
186
- // SAFETY: SELECT ${BRIEF_COLS} projects exactly the ProjectBriefRow
187
- // columns; .get() returns that row, or undefined only if the
188
- // just-inserted id can't be found.
189
- const row = db.prepare(`SELECT ${BRIEF_COLS} FROM project_briefs WHERE id = ?`)
190
- .get(briefId);
191
- if (!row)
192
- throw new Error('saveProjectBrief: failed to reload saved brief row');
193
- savedRow = row;
194
- // GDPR-light metadata: ids + flags only, no brief text.
195
- appendAuditEvent(db, {
196
- tenantId,
197
- actor,
198
- op: 'project_brief_create',
199
- targetId: String(briefId),
200
- metadata: {
201
- brief_id: briefId,
202
- repo,
203
- version,
204
- refreshed: isRefresh,
205
- ...refreshAuditExtra,
206
- },
207
- });
225
+ savedRow = writeBriefRow(db, memoryId, w);
208
226
  },
209
227
  // afterCommit covers refreshBrief (delegates here) + brief new/supersede.
210
228
  afterCommit: () => markGraphDirty(hippoRoot, tenantId, mem.id),
@@ -298,6 +316,7 @@ export function loadProjectBriefById(hippoRoot, tenantId, id) {
298
316
  export function loadProjectBriefs(hippoRoot, tenantId, opts = {}) {
299
317
  assertTenantId('loadProjectBriefs', tenantId);
300
318
  const limit = opts.limit ?? 100;
319
+ const after = keysetAfter('created_at', 'id', opts.after);
301
320
  if (opts.status && !VALID_BRIEF_STATES.has(opts.status)) {
302
321
  throw new BadRequestError(`loadProjectBriefs: status must be one of ${Array.from(VALID_BRIEF_STATES).join('|')}; got ${opts.status}`);
303
322
  }
@@ -313,13 +332,13 @@ export function loadProjectBriefs(hippoRoot, tenantId, opts = {}) {
313
332
  clauses.push('repo = ?');
314
333
  params.push(opts.repo);
315
334
  }
316
- params.push(limit);
335
+ params.push(...after.params, limit);
317
336
  // SAFETY: SELECT ${BRIEF_COLS} projects exactly the ProjectBriefRow columns
318
337
  // regardless of the dynamic WHERE clause built above; .all() returns rows
319
338
  // in that shape.
320
339
  const rows = db.prepare(`
321
340
  SELECT ${BRIEF_COLS} FROM project_briefs
322
- WHERE ${clauses.join(' AND ')}
341
+ WHERE ${clauses.join(' AND ')}${after.sql}
323
342
  ORDER BY created_at DESC, id DESC
324
343
  LIMIT ?
325
344
  `).all(...params);
@@ -356,7 +375,7 @@ export function loadActiveBriefForRepo(hippoRoot, tenantId, repo) {
356
375
  // ---------------------------------------------------------------------------
357
376
  // Refresh assembler (the distinguishing deliverable)
358
377
  // ---------------------------------------------------------------------------
359
- /** Escape LIKE wildcards in operator-supplied text (mirror of store.ts:782). */
378
+ /** Escape LIKE wildcards in operator-supplied text (mirror of store/search-rows.ts). */
360
379
  function escapeLike(term) {
361
380
  return term.replace(/[%_\\]/g, '\\$&');
362
381
  }
@@ -369,34 +388,16 @@ function receiptHeadline(content) {
369
388
  ? `${trimmed.slice(0, MAX_RECEIPT_HEADLINE_LEN)}...`
370
389
  : trimmed;
371
390
  }
372
- /**
373
- * Assemble the repo's recent receipts into a deterministic markdown digest, and
374
- * return it WITH the receipt count (the count feeds refreshBrief's change_summary +
375
- * audit metadata). NO LLM. Always returns a non-empty, valid summary (a brief
376
- * `summary` is NOT NULL), including the zero-receipts case.
377
- *
378
- * A "receipt" = a tenant memory row carrying the repo's `path:<repo>` tag. The
379
- * brief's OWN memory mirror (source='project_brief') is excluded so a brief never
380
- * becomes its own receipt on the next refresh. The match is against the JSON-array
381
- * serialization (each element is a double-quoted string `"path:hippo"`); the
382
- * surrounding quotes are load-bearing — they stop `hip` matching `path:hippo`.
383
- * `repo` is LIKE-escaped + parameterized (operator-supplied; security.md).
384
- */
385
- export function assembleBriefFromReceipts(hippoRoot, tenantId, repo) {
386
- assertTenantId('assembleBriefFromReceipts', tenantId);
387
- const normalizedRepo = (repo ?? '').trim();
388
- if (normalizedRepo.length === 0) {
389
- throw new BadRequestError('assembleBriefFromReceipts: repo is required');
390
- }
391
+ /** The repo's receipt rows, newest first, capped at MAX_BRIEF_RECEIPTS. */
392
+ function loadBriefReceipts(hippoRoot, tenantId, normalizedRepo) {
391
393
  const tag = `path:${normalizedRepo.toLowerCase()}`;
392
394
  const likeParam = `%"${escapeLike(tag)}"%`;
393
395
  const denyPlaceholders = RECALL_DEFAULT_DENY_SCOPES.map(() => '?').join(', ');
394
396
  const db = openHippoDb(hippoRoot);
395
- let receipts;
396
397
  try {
397
398
  // SAFETY: SELECT projects exactly id, created, source, content (the
398
399
  // ReceiptRow columns); .all() returns rows in that shape.
399
- receipts = db.prepare(`
400
+ return db.prepare(`
400
401
  SELECT id, created, source, content FROM memories
401
402
  WHERE tenant_id = ?
402
403
  AND source != 'project_brief'
@@ -409,17 +410,19 @@ export function assembleBriefFromReceipts(hippoRoot, tenantId, repo) {
409
410
  finally {
410
411
  closeHippoDb(db);
411
412
  }
412
- // NOTE on ordering: the `id DESC` tiebreak is lexical on a random-ish memory id
413
- // (e.g. `sem_<hex>`), NOT chronological — within the same `created` timestamp the
414
- // order is stable-but-arbitrary, not insertion order. `created DESC` is the real
415
- // recency ordering. (plan-eng-critic 2026-05-30, med.)
416
- //
417
- // Budget-aware assembly (codex-review-critic 2026-05-30, P2): the digest is the
418
- // brief `summary`, which saveProjectBrief caps at MAX_BRIEF_SUMMARY_LEN. The
419
- // receipt/headline caps (50 x ~200) could otherwise build an ~11KB body that the
420
- // store then REJECTS, breaking refresh for inputs within the advertised caps. So
421
- // include receipt lines newest-first only while they fit under the cap (reserving
422
- // slack for the header + an omission footer), and note the omitted remainder.
413
+ }
414
+ // NOTE on ordering: the `id DESC` tiebreak is lexical on a random-ish memory id
415
+ // (e.g. `sem_<hex>`), NOT chronological — within the same `created` timestamp the
416
+ // order is stable-but-arbitrary, not insertion order. `created DESC` is the real
417
+ // recency ordering. (plan-eng-critic 2026-05-30, med.)
418
+ //
419
+ // Budget-aware assembly (codex-review-critic 2026-05-30, P2): the digest is the
420
+ // brief `summary`, which saveProjectBrief caps at MAX_BRIEF_SUMMARY_LEN. The
421
+ // receipt/headline caps (50 x ~200) could otherwise build an ~11KB body that the
422
+ // store then REJECTS, breaking refresh for inputs within the advertised caps. So
423
+ // include receipt lines newest-first only while they fit under the cap (reserving
424
+ // slack for the header + an omission footer), and note the omitted remainder.
425
+ function fitReceiptLines(receipts) {
423
426
  const buildReceiptLine = (r) => `- ${(r.created ?? '').slice(0, 10)} [${r.source}] ${receiptHeadline(r.content)}`;
424
427
  const receiptLines = [];
425
428
  if (receipts.length > 0) {
@@ -435,17 +438,20 @@ export function assembleBriefFromReceipts(hippoRoot, tenantId, repo) {
435
438
  bodyBudget -= line.length + 1;
436
439
  }
437
440
  }
438
- const omitted = receipts.length - receiptLines.length;
441
+ return receiptLines;
442
+ }
443
+ function renderBriefDigest(normalizedRepo, receiptCount, receiptLines) {
444
+ const omitted = receiptCount - receiptLines.length;
439
445
  const lines = [];
440
446
  lines.push(`# Project Brief: ${normalizedRepo}`);
441
447
  lines.push('');
442
448
  lines.push(omitted > 0
443
- ? `_Auto-assembled from ${receiptLines.length} of ${receipts.length} receipt(s)._`
444
- : `_Auto-assembled from ${receipts.length} receipt(s)._`);
449
+ ? `_Auto-assembled from ${receiptLines.length} of ${receiptCount} receipt(s)._`
450
+ : `_Auto-assembled from ${receiptCount} receipt(s)._`);
445
451
  lines.push('');
446
452
  lines.push('## Recent receipts');
447
453
  lines.push('');
448
- if (receipts.length === 0) {
454
+ if (receiptCount === 0) {
449
455
  lines.push(`_No receipts found for ${normalizedRepo}._`);
450
456
  }
451
457
  else {
@@ -462,6 +468,29 @@ export function assembleBriefFromReceipts(hippoRoot, tenantId, repo) {
462
468
  if (markdown.length > MAX_BRIEF_SUMMARY_LEN) {
463
469
  markdown = markdown.slice(0, MAX_BRIEF_SUMMARY_LEN);
464
470
  }
471
+ return markdown;
472
+ }
473
+ /**
474
+ * Assemble the repo's recent receipts into a deterministic markdown digest, and
475
+ * return it WITH the receipt count (the count feeds refreshBrief's change_summary +
476
+ * audit metadata). NO LLM. Always returns a non-empty, valid summary (a brief
477
+ * `summary` is NOT NULL), including the zero-receipts case.
478
+ *
479
+ * A "receipt" = a tenant memory row carrying the repo's `path:<repo>` tag. The
480
+ * brief's OWN memory mirror (source='project_brief') is excluded so a brief never
481
+ * becomes its own receipt on the next refresh. The match is against the JSON-array
482
+ * serialization (each element is a double-quoted string `"path:hippo"`); the
483
+ * surrounding quotes are load-bearing — they stop `hip` matching `path:hippo`.
484
+ * `repo` is LIKE-escaped + parameterized (operator-supplied; security.md).
485
+ */
486
+ export function assembleBriefFromReceipts(hippoRoot, tenantId, repo) {
487
+ assertTenantId('assembleBriefFromReceipts', tenantId);
488
+ const normalizedRepo = (repo ?? '').trim();
489
+ if (normalizedRepo.length === 0) {
490
+ throw new BadRequestError('assembleBriefFromReceipts: repo is required');
491
+ }
492
+ const receipts = loadBriefReceipts(hippoRoot, tenantId, normalizedRepo);
493
+ const markdown = renderBriefDigest(normalizedRepo, receipts.length, fitReceiptLines(receipts));
465
494
  return { markdown, receiptCount: receipts.length };
466
495
  }
467
496
  /**
@@ -1,3 +1,4 @@
1
+ import { envHippoHome, envXdgDataHome } from './env.js';
1
2
  import * as fs from 'fs';
2
3
  import * as os from 'os';
3
4
  import * as path from 'path';
@@ -170,10 +171,10 @@ export function classifyOriginProject(origin, currentName) {
170
171
  * shared.getGlobalRoot delegates to this.
171
172
  */
172
173
  export function resolveGlobalRootDir() {
173
- const hippoHome = process.env.HIPPO_HOME?.trim();
174
+ const hippoHome = envHippoHome();
174
175
  if (hippoHome)
175
176
  return hippoHome;
176
- const xdgData = process.env.XDG_DATA_HOME?.trim();
177
+ const xdgData = envXdgDataHome();
177
178
  if (xdgData)
178
179
  return path.join(xdgData, 'hippo');
179
180
  return path.join(os.homedir(), '.hippo');
@@ -18,7 +18,15 @@ export interface MergeResult {
18
18
  readonly compactions: number;
19
19
  readonly backup: string | null;
20
20
  }
21
+ export interface ProjectFold {
22
+ readonly from: string;
23
+ readonly into: string;
24
+ }
21
25
  export interface RepairResult {
26
+ /** Imported notes filed under the wrong project, or under a project name when a user-global import holds the same text: set aside. */
27
+ readonly copies: readonly string[];
28
+ /** Names whose recorded session folders all resolve to one other project today, folded as `merge` would. */
29
+ readonly folds: readonly ProjectFold[];
22
30
  readonly toProject: ReadonlyArray<{
23
31
  readonly id: string;
24
32
  readonly origin: string;
@@ -42,10 +50,10 @@ export declare function mergeProjects(db: DatabaseSyncLike, hippoRoot: string, o
42
50
  into: string;
43
51
  dryRun: boolean;
44
52
  }): MergeResult;
45
- /** Reads only, so doctor can call it on a read-only handle: what the repair would do to each user-global merged row. */
46
- export declare function planUserGlobalRepair(db: DatabaseSyncLike, tenantId: string): Omit<RepairResult, 'backup'>;
47
- /** Re-tags sleep's merged rows saved as user-global before the fix, by the projects of their parents. */
48
- export declare function repairUserGlobalMerges(db: DatabaseSyncLike, hippoRoot: string, opts: {
53
+ /** Reads only, so doctor and a dry run take no write lock; merged rows are planned before any fold, so a few may re-tag differently once folds apply. */
54
+ export declare function planProjectRepair(db: DatabaseSyncLike, hippoRoot: string, tenantId: string): Omit<RepairResult, 'backup'>;
55
+ /** Sets aside stray imports, folds the names the resolver now maps elsewhere, then re-tags sleep's user-global merges by their parents; a dry run only plans. */
56
+ export declare function repairProjects(db: DatabaseSyncLike, hippoRoot: string, opts: {
49
57
  tenantId: string;
50
58
  dryRun: boolean;
51
59
  }): RepairResult;
@@ -1,12 +1,19 @@
1
- // `hippo projects`: list the project names a store holds, fold one into another, and re-tag sleep's old user-global merges.
1
+ // `hippo projects`: list the project names a store holds, fold one into another, and repair old tags in one reversible pass.
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import { setAsideRow } from './agent-memories/apply.js';
5
- import { AGENT_MEMORY_SOURCE_PREFIX, AGENT_MEMORY_TOOLS } from './agent-memories/tools.js';
6
- import { appendAuditEvent } from './audit.js';
5
+ import { transcriptNotesOrigin } from './agent-memories/claude-code.js';
6
+ import { containerId, containerPrefix } from './agent-memories/source.js';
7
+ import { AGENT_MEMORY_SOURCE_PREFIX, AGENT_MEMORY_TOOLS, toolSourcePrefix } from './agent-memories/tools.js';
8
+ import { appendAuditEvent, queryAuditEvents } from './audit.js';
7
9
  import { insertDormantRow, listDormantSnapshots, replaceDormantEntry } from './dormant.js';
10
+ import { processEnv } from './env.js';
8
11
  import { calculateStrength } from './memory.js';
9
- import { deleteEntryRowInTx, removeEntryMirrors, selectAllEntries, writeEntryMirrors } from './store.js';
12
+ import { deriveOriginProject, isGlobalStoreRoot } from './project-identity.js';
13
+ import { duplicateKey } from './same-text.js';
14
+ import { removeEntryMirrors } from './store/mirrors.js';
15
+ import { deleteEntryRowInTx, writeEntryMirrors } from './store/entry-writes.js';
16
+ import { selectAllEntries, selectLiveEntriesBySourcePrefix } from './store/entry-reads.js';
10
17
  const ACTOR = 'cli';
11
18
  function isImport(entry) {
12
19
  return entry.source.startsWith(AGENT_MEMORY_SOURCE_PREFIX);
@@ -23,8 +30,9 @@ export function listProjects(db, tenantId) {
23
30
  for (const e of live) {
24
31
  const origin = e.origin_project ?? null;
25
32
  byOrigin.set(origin, [...(byOrigin.get(origin) ?? []), e]);
33
+ const key = duplicateKey(e.content);
26
34
  if (isImport(e))
27
- holders.set(e.content, (holders.get(e.content) ?? new Set()).add(origin));
35
+ holders.set(key, (holders.get(key) ?? new Set()).add(origin));
28
36
  }
29
37
  return [...byOrigin].map(([origin, rows]) => {
30
38
  const imports = rows.filter(isImport);
@@ -33,7 +41,7 @@ export function listProjects(db, tenantId) {
33
41
  live: rows.length,
34
42
  imported: imports.length,
35
43
  newest: rows.reduce((max, e) => (e.created > max ? e.created : max), ''),
36
- copiesElsewhere: imports.filter((e) => (holders.get(e.content)?.size ?? 0) > 1).length,
44
+ copiesElsewhere: imports.filter((e) => (holders.get(duplicateKey(e.content))?.size ?? 0) > 1).length,
37
45
  };
38
46
  }).sort((a, b) => b.newest.localeCompare(a.newest));
39
47
  }
@@ -76,44 +84,111 @@ export function mergeProjects(db, hippoRoot, opts) {
76
84
  const { tenantId, from, into, dryRun } = opts;
77
85
  const backup = dryRun ? null : backupStore(db, hippoRoot, 'before-merge');
78
86
  const result = inTransaction(db, dryRun, () => {
79
- const rows = selectAllEntries(db, tenantId).filter((e) => e.origin_project === from);
80
- const setAside = [];
81
- for (const row of rows) {
82
- const tag = toolTag(row.source);
83
- if (!isImport(row) || row.superseded_by || row.kind === 'raw' || tag === null)
84
- continue;
85
- if (setAsideRow(db, tag, { ...row, origin_project: into }, 'project-merge').kind === 'dormant')
86
- setAside.push(row.id);
87
- }
88
- const gone = new Set(setAside);
89
- const restamped = rows.filter((e) => !gone.has(e.id)).map((e) => e.id);
90
- db.prepare(`UPDATE memories SET origin_project = ?, updated_at = datetime('now') WHERE tenant_id = ? AND origin_project = ?`)
91
- .run(into, tenantId, from);
92
- const dormantRestamped = [];
93
- for (const snap of listDormantSnapshots(db, tenantId)) {
94
- if (snap.entry.origin_project !== from || gone.has(snap.entry.id))
95
- continue;
96
- replaceDormantEntry(db, tenantId, snap.entry.id, { ...snap.entry, origin_project: into });
97
- dormantRestamped.push(snap.entry.id);
98
- }
99
- const compactions = Number(db.prepare(`UPDATE compactions SET origin_project = ? WHERE tenant_id = ? AND origin_project = ?`)
100
- .run(into, tenantId, from).changes ?? 0);
101
- appendAuditEvent(db, {
102
- tenantId, actor: ACTOR, op: 'project_merge',
103
- metadata: { from, into, backup, setAside, restamped, dormantRestamped, compactions },
104
- });
105
- return { from, into, setAside, restamped, dormantRestamped, compactions, backup };
87
+ const folded = foldInTx(db, tenantId, from, into);
88
+ appendAuditEvent(db, { tenantId, actor: ACTOR, op: 'project_merge', metadata: { from, into, backup, ...folded } });
89
+ return { from, into, ...folded, backup };
106
90
  });
107
91
  if (!dryRun)
108
92
  refreshMirrors(db, hippoRoot, tenantId, result.restamped, result.setAside);
109
93
  return result;
110
94
  }
111
- /** Reads only, so doctor can call it on a read-only handle: what the repair would do to each user-global merged row. */
112
- export function planUserGlobalRepair(db, tenantId) {
95
+ function foldInTx(db, tenantId, from, into) {
96
+ const rows = selectAllEntries(db, tenantId).filter((e) => e.origin_project === from);
97
+ const setAside = [];
98
+ for (const row of rows) {
99
+ const tag = toolTag(row.source);
100
+ if (!isImport(row) || row.superseded_by || row.kind === 'raw' || tag === null)
101
+ continue;
102
+ if (setAsideRow(db, tag, { ...row, origin_project: into }, 'project-merge').kind === 'dormant')
103
+ setAside.push(row.id);
104
+ }
105
+ const gone = new Set(setAside);
106
+ const restamped = rows.filter((e) => !gone.has(e.id)).map((e) => e.id);
107
+ db.prepare(`UPDATE memories SET origin_project = ?, updated_at = datetime('now') WHERE tenant_id = ? AND origin_project = ?`)
108
+ .run(into, tenantId, from);
109
+ const dormantRestamped = [];
110
+ for (const snap of listDormantSnapshots(db, tenantId)) {
111
+ if (snap.entry.origin_project !== from || gone.has(snap.entry.id))
112
+ continue;
113
+ replaceDormantEntry(db, tenantId, snap.entry.id, { ...snap.entry, origin_project: into });
114
+ dormantRestamped.push(snap.entry.id);
115
+ }
116
+ const compactions = Number(db.prepare(`UPDATE compactions SET origin_project = ? WHERE tenant_id = ? AND origin_project = ?`)
117
+ .run(into, tenantId, from).changes ?? 0);
118
+ return { setAside, restamped, dormantRestamped, compactions };
119
+ }
120
+ /** Live imports under a project name whose exact text a user-global import holds: a session folder's notes stamped with the folder it ended in. */
121
+ function importCopies(db, tenantId) {
122
+ const live = selectAllEntries(db, tenantId).filter((e) => !e.superseded_by && isImport(e));
123
+ const userGlobal = new Set(live.filter((e) => e.origin_project === '').map((e) => duplicateKey(e.content)));
124
+ return live.filter((e) => e.origin_project && e.kind !== 'raw' && !e.pinned && toolTag(e.source) !== null && userGlobal.has(duplicateKey(e.content)));
125
+ }
126
+ /** Imports to set aside. The global store also checks each Claude session folder a compaction recorded: its notes under any other project are misfiled, edited or not, and a text copy in the right folder is kept. */
127
+ function strayImports(db, hippoRoot, tenantId) {
128
+ const copies = importCopies(db, tenantId);
129
+ if (!isGlobalStoreRoot(hippoRoot))
130
+ return copies;
131
+ const machine = { platform: process.platform, env: processEnv() };
132
+ const { platform } = machine;
133
+ // SAFETY: the SELECT names the two columns of the row type.
134
+ const sessions = db.prepare(`SELECT DISTINCT transcript_path AS transcript, cwd FROM compactions WHERE tenant_id = ? AND transcript_path IS NOT NULL`)
135
+ .all(tenantId);
136
+ const owners = new Map();
137
+ for (const { transcript, cwd } of sessions) {
138
+ const origin = transcriptNotesOrigin(transcript, cwd, machine);
139
+ const dir = path.join(path.dirname(transcript), 'memory');
140
+ if (origin !== null)
141
+ owners.set(dir, (owners.get(dir) ?? new Set()).add(origin));
142
+ }
143
+ const tool = toolSourcePrefix('claude-code');
144
+ const live = selectLiveEntriesBySourcePrefix(db, tenantId, tool).filter((e) => e.kind !== 'raw' && !e.pinned);
145
+ const origins = new Set(live.map((e) => e.origin_project ?? ''));
146
+ const right = new Set();
147
+ const wrong = new Set();
148
+ for (const [dir, owner] of owners) {
149
+ if (owner.size !== 1)
150
+ continue;
151
+ for (const origin of origins)
152
+ (owner.has(origin) ? right : wrong).add(containerPrefix('claude-code', containerId(dir, 'project', platform, origin)));
153
+ }
154
+ const prefix = (e) => e.source.slice(0, e.source.indexOf('/', tool.length) + 1);
155
+ const misfiled = live.filter((e) => wrong.has(prefix(e)));
156
+ const seen = new Set(misfiled.map((e) => e.id));
157
+ return [...misfiled, ...copies.filter((e) => !seen.has(e.id) && !right.has(prefix(e)))];
158
+ }
159
+ /** Global store only, where a compaction's name came from its cwd: a name whose every cwd still on disk resolves to one other project today. */
160
+ function planFolds(db, tenantId) {
161
+ // SAFETY: the SELECT names the two columns of the row type.
162
+ const rows = db.prepare(`SELECT DISTINCT origin_project AS origin, cwd FROM compactions WHERE tenant_id = ? AND origin_project <> '' AND cwd IS NOT NULL`)
163
+ .all(tenantId);
164
+ const today = new Map();
165
+ for (const { origin, cwd } of rows) {
166
+ if (fs.existsSync(cwd))
167
+ today.set(origin, (today.get(origin) ?? new Set()).add(deriveOriginProject(cwd)));
168
+ }
169
+ // A name someone merged into by hand stays: undoing their choice would rest on the resolver alone.
170
+ const chosen = new Set(queryAuditEvents(db, { tenantId, op: 'project_merge', limit: 10000 }).map((e) => e.metadata.into));
171
+ const folds = [...today].flatMap(([from, names]) => {
172
+ const [into] = names;
173
+ return names.size === 1 && into !== '' && into !== from && !chosen.has(from) ? [{ from, into }] : [];
174
+ });
175
+ // A fold into a name that itself folds would land rows by run order; the next repair takes the rest of the chain.
176
+ const sources = new Set(folds.map((f) => f.from));
177
+ return folds.filter((f) => !sources.has(f.into));
178
+ }
179
+ /** Reads only, so doctor and a dry run take no write lock; merged rows are planned before any fold, so a few may re-tag differently once folds apply. */
180
+ export function planProjectRepair(db, hippoRoot, tenantId) {
181
+ const folds = isGlobalStoreRoot(hippoRoot) ? planFolds(db, tenantId) : [];
182
+ return { copies: strayImports(db, hippoRoot, tenantId).map((e) => e.id), folds, ...planUserGlobalRepair(db, tenantId, folds) };
183
+ }
184
+ /** Parents read with `folds` already applied, so a plan matches what apply does after folding. */
185
+ function planUserGlobalRepair(db, tenantId, folds) {
113
186
  const all = selectAllEntries(db, tenantId);
114
- const origins = new Map(listDormantSnapshots(db, tenantId).map((s) => [s.entry.id, s.entry.origin_project ?? null]));
187
+ const renamed = new Map(folds.map((f) => [f.from, f.into]));
188
+ const after = (origin) => (origin ? renamed.get(origin) ?? origin : origin ?? null);
189
+ const origins = new Map(listDormantSnapshots(db, tenantId).map((s) => [s.entry.id, after(s.entry.origin_project)]));
115
190
  for (const e of all)
116
- origins.set(e.id, e.origin_project ?? null);
191
+ origins.set(e.id, after(e.origin_project));
117
192
  const toProject = [];
118
193
  const setAside = [];
119
194
  const untraced = [];
@@ -133,14 +208,22 @@ export function planUserGlobalRepair(db, tenantId) {
133
208
  }
134
209
  return { toProject, setAside, untraced };
135
210
  }
136
- /** Re-tags sleep's merged rows saved as user-global before the fix, by the projects of their parents. */
137
- export function repairUserGlobalMerges(db, hippoRoot, opts) {
211
+ /** Sets aside stray imports, folds the names the resolver now maps elsewhere, then re-tags sleep's user-global merges by their parents; a dry run only plans. */
212
+ export function repairProjects(db, hippoRoot, opts) {
138
213
  const { tenantId, dryRun } = opts;
139
214
  if (dryRun)
140
- return { ...planUserGlobalRepair(db, tenantId), backup: null };
215
+ return { ...planProjectRepair(db, hippoRoot, tenantId), backup: null };
141
216
  const backup = backupStore(db, hippoRoot, 'before-repair');
142
- const result = inTransaction(db, false, () => {
143
- const plan = planUserGlobalRepair(db, tenantId);
217
+ const { result, rewrite, purge } = inTransaction(db, false, () => {
218
+ const copies = [];
219
+ for (const row of strayImports(db, hippoRoot, tenantId)) {
220
+ const tag = toolTag(row.source);
221
+ if (tag !== null && setAsideRow(db, tag, row, 'project-repair').kind === 'dormant')
222
+ copies.push(row.id);
223
+ }
224
+ const folds = isGlobalStoreRoot(hippoRoot) ? planFolds(db, tenantId) : [];
225
+ const folded = folds.map((f) => foldInTx(db, tenantId, f.from, f.into));
226
+ const plan = planUserGlobalRepair(db, tenantId, []);
144
227
  const stamp = db.prepare(`UPDATE memories SET origin_project = ?, updated_at = datetime('now') WHERE tenant_id = ? AND id = ?`);
145
228
  for (const { id, origin } of plan.toProject)
146
229
  stamp.run(origin, tenantId, id);
@@ -150,10 +233,14 @@ export function repairUserGlobalMerges(db, hippoRoot, opts) {
150
233
  insertDormantRow(db, { entry: row, strength: calculateStrength(row, now), reason: 'project-repair', dormantAt: now.toISOString() });
151
234
  deleteEntryRowInTx(db, row, ACTOR);
152
235
  }
153
- appendAuditEvent(db, { tenantId, actor: ACTOR, op: 'project_repair', metadata: { backup, ...plan } });
154
- return { ...plan, backup };
236
+ appendAuditEvent(db, { tenantId, actor: ACTOR, op: 'project_repair', metadata: { backup, copies, folds, folded, ...plan } });
237
+ return {
238
+ result: { copies, folds, ...plan, backup },
239
+ rewrite: [...plan.toProject.map((r) => r.id), ...folded.flatMap((f) => f.restamped)],
240
+ purge: [...copies, ...plan.setAside, ...folded.flatMap((f) => f.setAside)],
241
+ };
155
242
  });
156
- refreshMirrors(db, hippoRoot, tenantId, result.toProject.map((r) => r.id), result.setAside);
243
+ refreshMirrors(db, hippoRoot, tenantId, rewrite, purge);
157
244
  return result;
158
245
  }
159
246
  /** After commit, as the agent memory sync does: a stale mirror would bring the old tag back on the next rebuild. */