hippo-memory 1.61.0 → 1.63.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 (377) hide show
  1. package/README.md +37 -53
  2. package/dist/agent-memories/apply.d.ts +3 -1
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +4 -1
  5. package/dist/agent-memories/claude-code.js +53 -9
  6. package/dist/agent-memories/report.d.ts +1 -0
  7. package/dist/agent-memories/report.js +2 -2
  8. package/dist/agent-memories/sync.d.ts +3 -3
  9. package/dist/agent-memories/sync.js +46 -24
  10. package/dist/agent-memories/types.d.ts +0 -2
  11. package/dist/ambient-store.d.ts +4 -4
  12. package/dist/ambient-store.js +4 -3
  13. package/dist/api/assemble.d.ts +7 -10
  14. package/dist/api/assemble.js +62 -66
  15. package/dist/api/audit.d.ts +2 -2
  16. package/dist/api/audit.js +2 -2
  17. package/dist/api/auth.d.ts +6 -6
  18. package/dist/api/auth.js +5 -7
  19. package/dist/api/context-select.d.ts +50 -0
  20. package/dist/api/context-select.js +342 -0
  21. package/dist/api/context-types.d.ts +12 -17
  22. package/dist/api/context.d.ts +5 -4
  23. package/dist/api/context.js +208 -535
  24. package/dist/api/dormant.js +1 -1
  25. package/dist/api/drill-down.d.ts +4 -4
  26. package/dist/api/drill-down.js +56 -41
  27. package/dist/api/outcome.d.ts +8 -13
  28. package/dist/api/outcome.js +13 -17
  29. package/dist/api/promote.d.ts +5 -8
  30. package/dist/api/promote.js +55 -66
  31. package/dist/api/quarantine.js +3 -2
  32. package/dist/api/recall-types.d.ts +43 -55
  33. package/dist/api/recall.d.ts +3 -3
  34. package/dist/api/recall.js +310 -456
  35. package/dist/api/remember.d.ts +2 -2
  36. package/dist/api/sleep.d.ts +7 -35
  37. package/dist/api/sleep.js +206 -220
  38. package/dist/api/tokens.d.ts +2 -2
  39. package/dist/api/tokens.js +2 -2
  40. package/dist/api/types.d.ts +6 -10
  41. package/dist/api/types.js +2 -4
  42. package/dist/audit-prune.d.ts +4 -6
  43. package/dist/audit-prune.js +3 -5
  44. package/dist/audit.js +11 -33
  45. package/dist/auth.d.ts +8 -9
  46. package/dist/auth.js +5 -7
  47. package/dist/autolearn.d.ts +1 -1
  48. package/dist/autolearn.js +1 -1
  49. package/dist/availability.js +3 -5
  50. package/dist/capture/command.d.ts +5 -4
  51. package/dist/capture/command.js +8 -22
  52. package/dist/capture/compact.d.ts +2 -2
  53. package/dist/capture/compact.js +11 -15
  54. package/dist/capture/extract.js +37 -115
  55. package/dist/capture-error.d.ts +1 -1
  56. package/dist/capture-error.js +1 -1
  57. package/dist/churn-git.d.ts +1 -1
  58. package/dist/churn-git.js +1 -1
  59. package/dist/cli/audit.js +3 -4
  60. package/dist/cli/auth.js +3 -6
  61. package/dist/cli/briefs.js +324 -306
  62. package/dist/cli/context.js +44 -34
  63. package/dist/cli/continuity.js +283 -271
  64. package/dist/cli/curate.d.ts +1 -1
  65. package/dist/cli/curate.js +43 -58
  66. package/dist/cli/dag.js +5 -9
  67. package/dist/cli/decisions.js +334 -345
  68. package/dist/cli/explain.js +68 -61
  69. package/dist/cli/goals.js +1 -1
  70. package/dist/cli/init.js +1 -1
  71. package/dist/cli/maintenance.js +62 -51
  72. package/dist/cli/playbooks.js +391 -379
  73. package/dist/cli/projects.js +11 -6
  74. package/dist/cli/recall.js +30 -44
  75. package/dist/cli/remember.js +118 -87
  76. package/dist/cli/session-hooks.js +106 -115
  77. package/dist/cli/setup.d.ts +1 -1
  78. package/dist/cli/setup.js +267 -250
  79. package/dist/cli/shared.js +3 -3
  80. package/dist/cli/slack.js +1 -1
  81. package/dist/cli/sleep.js +27 -1
  82. package/dist/cli/status.d.ts +4 -4
  83. package/dist/cli/status.js +80 -76
  84. package/dist/cli/transfer.js +88 -107
  85. package/dist/cli/usage.js +9 -6
  86. package/dist/cli.d.ts +1 -1
  87. package/dist/cli.js +4 -9
  88. package/dist/compaction-record.d.ts +2 -2
  89. package/dist/compaction-record.js +89 -68
  90. package/dist/compare.d.ts +11 -16
  91. package/dist/compare.js +11 -16
  92. package/dist/config.d.ts +19 -18
  93. package/dist/config.js +90 -67
  94. package/dist/connectors/github/backfill.d.ts +2 -2
  95. package/dist/connectors/github/backfill.js +8 -15
  96. package/dist/connectors/github/cli-impl.js +3 -8
  97. package/dist/connectors/github/deletion.d.ts +5 -12
  98. package/dist/connectors/github/deletion.js +5 -12
  99. package/dist/connectors/github/dlq.d.ts +6 -9
  100. package/dist/connectors/github/dlq.js +2 -3
  101. package/dist/connectors/github/ingest.d.ts +5 -7
  102. package/dist/connectors/github/ingest.js +8 -12
  103. package/dist/connectors/github/octokit-client.d.ts +3 -5
  104. package/dist/connectors/github/octokit-client.js +5 -6
  105. package/dist/connectors/github/signature.d.ts +9 -39
  106. package/dist/connectors/github/signature.js +9 -39
  107. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  108. package/dist/connectors/github/tenant-routing.js +1 -1
  109. package/dist/connectors/github/transform.js +2 -2
  110. package/dist/connectors/github/types.d.ts +2 -10
  111. package/dist/connectors/github/types.js +1 -3
  112. package/dist/connectors/slack/deletion.d.ts +3 -8
  113. package/dist/connectors/slack/deletion.js +3 -8
  114. package/dist/connectors/slack/dlq.d.ts +1 -1
  115. package/dist/connectors/slack/ingest.d.ts +1 -1
  116. package/dist/connectors/slack/ingest.js +7 -16
  117. package/dist/connectors/slack/signature.d.ts +1 -1
  118. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  119. package/dist/connectors/slack/tenant-routing.js +3 -5
  120. package/dist/connectors/slack/transform.d.ts +5 -6
  121. package/dist/connectors/slack/transform.js +5 -6
  122. package/dist/connectors/slack/types.d.ts +2 -6
  123. package/dist/connectors/slack/types.js +1 -3
  124. package/dist/connectors/slack/web-client.js +10 -3
  125. package/dist/connectors/slack/workspaces.d.ts +3 -5
  126. package/dist/connectors/slack/workspaces.js +3 -5
  127. package/dist/consolidate/conflicts.js +3 -14
  128. package/dist/consolidate/decay.js +9 -29
  129. package/dist/consolidate/llm-passes.js +4 -5
  130. package/dist/consolidate/merge.js +8 -23
  131. package/dist/consolidate/run.d.ts +1 -8
  132. package/dist/consolidate/run.js +3 -25
  133. package/dist/consolidate/sleep.js +5 -17
  134. package/dist/consolidate/traces.js +9 -21
  135. package/dist/customer-notes.d.ts +5 -7
  136. package/dist/customer-notes.js +82 -76
  137. package/dist/dag.d.ts +10 -21
  138. package/dist/dag.js +189 -203
  139. package/dist/db/continuity.js +2 -2
  140. package/dist/db/migrations/v14.js +1 -1
  141. package/dist/db/migrations/v15.js +1 -2
  142. package/dist/db/migrations/v16.js +3 -4
  143. package/dist/db/migrations/v17.js +2 -3
  144. package/dist/db/migrations/v19.js +1 -1
  145. package/dist/db/migrations/v20.js +1 -1
  146. package/dist/db/migrations/v21.js +2 -6
  147. package/dist/db/migrations/v22.js +2 -4
  148. package/dist/db/migrations/v23.js +1 -1
  149. package/dist/db/migrations/v24.js +4 -6
  150. package/dist/db/migrations/v25.js +2 -3
  151. package/dist/db/migrations/v26.js +3 -3
  152. package/dist/db/migrations/v27.js +2 -10
  153. package/dist/db/migrations/v28.js +5 -8
  154. package/dist/db/migrations/v29.js +3 -4
  155. package/dist/db/migrations/v30.js +2 -2
  156. package/dist/db/migrations/v31.js +1 -1
  157. package/dist/db/migrations/v32.js +1 -1
  158. package/dist/db/migrations/v33.js +3 -3
  159. package/dist/db/migrations/v34.js +1 -1
  160. package/dist/db/migrations/v35.js +3 -4
  161. package/dist/db/migrations/v36.js +3 -4
  162. package/dist/db/migrations/v37.js +5 -5
  163. package/dist/db/migrations/v38.js +7 -8
  164. package/dist/db/migrations/v39.js +1 -1
  165. package/dist/db/migrations/v40.js +4 -16
  166. package/dist/db/migrations/v41.js +3 -4
  167. package/dist/db/migrations/v42.js +3 -4
  168. package/dist/db/migrations/v45.js +1 -1
  169. package/dist/db/migrations/v46.js +1 -1
  170. package/dist/db/migrations/v47.js +1 -1
  171. package/dist/db/migrations/v48.js +1 -1
  172. package/dist/decisions.d.ts +2 -2
  173. package/dist/decisions.js +97 -80
  174. package/dist/dedupe.js +86 -61
  175. package/dist/delivery-recorder.js +154 -135
  176. package/dist/doctor.js +129 -110
  177. package/dist/dormant.js +1 -4
  178. package/dist/embedding-provider.d.ts +4 -8
  179. package/dist/embedding-provider.js +4 -8
  180. package/dist/embeddings.js +55 -47
  181. package/dist/env.d.ts +1 -1
  182. package/dist/env.js +12 -12
  183. package/dist/escape.d.ts +5 -0
  184. package/dist/escape.js +10 -0
  185. package/dist/eval-stats.d.ts +1 -2
  186. package/dist/eval-stats.js +1 -2
  187. package/dist/eval-suite.js +27 -21
  188. package/dist/extract.js +4 -9
  189. package/dist/failure-log.d.ts +3 -3
  190. package/dist/failure-log.js +1 -1
  191. package/dist/forward-claim-detector.d.ts +2 -4
  192. package/dist/forward-claim-detector.js +6 -11
  193. package/dist/goals.d.ts +3 -3
  194. package/dist/goals.js +103 -91
  195. package/dist/graph/read.d.ts +2 -2
  196. package/dist/graph/read.js +5 -6
  197. package/dist/graph/types.d.ts +8 -8
  198. package/dist/graph/write.d.ts +7 -14
  199. package/dist/graph/write.js +16 -23
  200. package/dist/graph-extract.d.ts +7 -8
  201. package/dist/graph-extract.js +62 -72
  202. package/dist/graph-recall.d.ts +2 -2
  203. package/dist/graph-recall.js +55 -49
  204. package/dist/graph-stream.d.ts +5 -6
  205. package/dist/graph-stream.js +66 -57
  206. package/dist/graph-view.d.ts +2 -2
  207. package/dist/graph-view.js +7 -7
  208. package/dist/half-life-migration.d.ts +1 -2
  209. package/dist/half-life-migration.js +2 -3
  210. package/dist/hooks/codex-session.js +1 -1
  211. package/dist/hooks/codex-wrapper.d.ts +1 -1
  212. package/dist/hooks/codex-wrapper.js +3 -2
  213. package/dist/hooks/json-hooks.d.ts +2 -2
  214. package/dist/hooks/json-hooks.js +5 -4
  215. package/dist/hooks/opencode.d.ts +1 -1
  216. package/dist/hooks/opencode.js +5 -4
  217. package/dist/hooks/shared.d.ts +3 -7
  218. package/dist/hooks/shared.js +1 -8
  219. package/dist/http-util.d.ts +2 -3
  220. package/dist/http-util.js +3 -0
  221. package/dist/importers/core.d.ts +2 -9
  222. package/dist/importers/core.js +15 -30
  223. package/dist/importers/sources.js +2 -1
  224. package/dist/importers/vault.js +2 -20
  225. package/dist/incidents.d.ts +1 -1
  226. package/dist/incidents.js +46 -39
  227. package/dist/instruction-detect.d.ts +1 -1
  228. package/dist/instruction-detect.js +1 -1
  229. package/dist/invalidation.d.ts +3 -0
  230. package/dist/invalidation.js +160 -114
  231. package/dist/json.d.ts +5 -0
  232. package/dist/json.js +4 -0
  233. package/dist/judgment.js +1 -2
  234. package/dist/local-embedding.js +1 -1
  235. package/dist/mcp/admin-tools.js +7 -17
  236. package/dist/mcp/format.js +1 -1
  237. package/dist/mcp/framing.js +3 -6
  238. package/dist/mcp/protocol.d.ts +2 -5
  239. package/dist/mcp/protocol.js +1 -3
  240. package/dist/mcp/recall-tools.js +12 -15
  241. package/dist/mcp/request.js +4 -3
  242. package/dist/mcp/session-state.js +2 -3
  243. package/dist/mcp/stdio.js +2 -1
  244. package/dist/mcp/tools.js +9 -6
  245. package/dist/memory-value-weights.d.ts +5 -8
  246. package/dist/memory-value-weights.js +5 -8
  247. package/dist/memory-value.d.ts +13 -13
  248. package/dist/memory-value.js +26 -37
  249. package/dist/memory.d.ts +20 -22
  250. package/dist/memory.js +24 -48
  251. package/dist/multihop.d.ts +1 -1
  252. package/dist/multihop.js +3 -2
  253. package/dist/owner-validation.d.ts +4 -5
  254. package/dist/owner-validation.js +4 -5
  255. package/dist/physics.d.ts +4 -4
  256. package/dist/physics.js +7 -9
  257. package/dist/policies.d.ts +9 -10
  258. package/dist/policies.js +96 -81
  259. package/dist/postinstall.js +3 -6
  260. package/dist/predictions/planning-fallacy.d.ts +9 -14
  261. package/dist/predictions/planning-fallacy.js +10 -16
  262. package/dist/predictions/store.d.ts +15 -23
  263. package/dist/predictions/store.js +36 -33
  264. package/dist/processes.d.ts +2 -7
  265. package/dist/processes.js +88 -72
  266. package/dist/project-briefs.d.ts +2 -3
  267. package/dist/project-briefs.js +141 -118
  268. package/dist/project-identity.d.ts +22 -9
  269. package/dist/project-identity.js +47 -12
  270. package/dist/project-merge.d.ts +28 -5
  271. package/dist/project-merge.js +213 -46
  272. package/dist/project-remote.d.ts +12 -0
  273. package/dist/project-remote.js +138 -0
  274. package/dist/prompt-recall.js +1 -2
  275. package/dist/rate-limit.d.ts +1 -1
  276. package/dist/rate-limit.js +1 -1
  277. package/dist/raw-archive.d.ts +9 -0
  278. package/dist/raw-archive.js +70 -53
  279. package/dist/recall-history.d.ts +19 -20
  280. package/dist/recall-history.js +24 -42
  281. package/dist/recall-pipeline.js +4 -28
  282. package/dist/recall-scope.d.ts +7 -8
  283. package/dist/recall-scope.js +7 -8
  284. package/dist/recall-trace.d.ts +5 -9
  285. package/dist/recall-trace.js +6 -10
  286. package/dist/refine-llm.d.ts +1 -1
  287. package/dist/refine-llm.js +2 -2
  288. package/dist/reject-flow.d.ts +3 -4
  289. package/dist/reject-flow.js +122 -117
  290. package/dist/rejection.d.ts +5 -6
  291. package/dist/rejection.js +7 -15
  292. package/dist/rerankers/clef.d.ts +1 -1
  293. package/dist/rerankers/jev.d.ts +1 -2
  294. package/dist/rerankers/jev.js +4 -5
  295. package/dist/rerankers/llm.d.ts +1 -2
  296. package/dist/rerankers/llm.js +1 -2
  297. package/dist/rerankers/types.d.ts +1 -2
  298. package/dist/rrf.d.ts +2 -2
  299. package/dist/rrf.js +2 -2
  300. package/dist/search/bm25-search.d.ts +1 -1
  301. package/dist/search/bm25-search.js +2 -1
  302. package/dist/search/boosts.js +2 -1
  303. package/dist/search/hybrid.d.ts +1 -1
  304. package/dist/search/hybrid.js +2 -1
  305. package/dist/search/physics-search.d.ts +1 -1
  306. package/dist/search/physics-search.js +2 -1
  307. package/dist/search/types.d.ts +2 -0
  308. package/dist/search/types.js +3 -1
  309. package/dist/secret-detect.d.ts +4 -5
  310. package/dist/secret-detect.js +6 -10
  311. package/dist/server/auth.js +5 -5
  312. package/dist/server/client-ip.js +1 -1
  313. package/dist/server/cursor.js +2 -1
  314. package/dist/server/mcp-http.js +4 -4
  315. package/dist/server/request.d.ts +3 -6
  316. package/dist/server/request.js +6 -7
  317. package/dist/server/routes/admin.js +5 -4
  318. package/dist/server/routes/customer-notes.js +6 -5
  319. package/dist/server/routes/decisions.js +4 -3
  320. package/dist/server/routes/incidents.js +7 -5
  321. package/dist/server/routes/memories.js +7 -7
  322. package/dist/server/routes/policies.js +3 -2
  323. package/dist/server/routes/predictions.js +12 -15
  324. package/dist/server/routes/processes.js +3 -2
  325. package/dist/server/routes/project-briefs.js +8 -7
  326. package/dist/server/routes/recall.js +95 -93
  327. package/dist/server/routes/skills.js +6 -5
  328. package/dist/server/types.d.ts +1 -1
  329. package/dist/server/validation.d.ts +1 -2
  330. package/dist/server/validation.js +7 -14
  331. package/dist/server-detect.js +72 -58
  332. package/dist/server.d.ts +2 -2
  333. package/dist/server.js +131 -117
  334. package/dist/shared.d.ts +26 -17
  335. package/dist/shared.js +102 -104
  336. package/dist/skills.d.ts +3 -3
  337. package/dist/skills.js +88 -72
  338. package/dist/store/audit-event.d.ts +2 -2
  339. package/dist/store/audit-event.js +1 -1
  340. package/dist/store/candidates.d.ts +2 -2
  341. package/dist/store/candidates.js +4 -3
  342. package/dist/store/conflicts.js +30 -22
  343. package/dist/store/delete-and-batch.d.ts +11 -14
  344. package/dist/store/delete-and-batch.js +40 -91
  345. package/dist/store/entry-reads.d.ts +17 -25
  346. package/dist/store/entry-reads.js +59 -40
  347. package/dist/store/entry-row.d.ts +6 -24
  348. package/dist/store/entry-row.js +6 -24
  349. package/dist/store/entry-writes.d.ts +6 -7
  350. package/dist/store/entry-writes.js +13 -11
  351. package/dist/store/handoffs.d.ts +1 -1
  352. package/dist/store/handoffs.js +7 -10
  353. package/dist/store/index-and-stats.d.ts +2 -6
  354. package/dist/store/index-and-stats.js +4 -10
  355. package/dist/store/mirrors.d.ts +6 -19
  356. package/dist/store/mirrors.js +14 -39
  357. package/dist/store/open.js +9 -31
  358. package/dist/store/rows.d.ts +5 -11
  359. package/dist/store/rows.js +6 -11
  360. package/dist/store/search-rows.d.ts +17 -34
  361. package/dist/store/search-rows.js +34 -56
  362. package/dist/store/sessions.d.ts +4 -5
  363. package/dist/store/sessions.js +5 -6
  364. package/dist/store/summaries.d.ts +13 -17
  365. package/dist/store/summaries.js +26 -70
  366. package/dist/support-bundle.js +4 -8
  367. package/dist/tenant.d.ts +1 -5
  368. package/dist/token-ledger.d.ts +1 -1
  369. package/dist/token-ledger.js +3 -5
  370. package/dist/trace.js +1 -3
  371. package/dist/version.d.ts +1 -1
  372. package/dist/version.js +1 -1
  373. package/dist/working-memory.d.ts +1 -1
  374. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  375. package/extensions/openclaw-plugin/package.json +1 -1
  376. package/openclaw.plugin.json +1 -1
  377. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
- // The `hippo projects` verb: list a store's project names, fold an old worktree name into its repo, repair sleep's user-global merges.
1
+ // The `hippo projects` verb: list a store's project names, fold an old worktree name into its repo, repair old tags in one pass.
2
2
  import * as path from 'path';
3
3
  import { execFileSync } from 'child_process';
4
4
  import { closeHippoDb, openHippoDb } from '../db.js';
5
- import { listProjects, mergeProjects, repairUserGlobalMerges } from '../project-merge.js';
5
+ import { listProjects, mergeProjects, repairProjects } from '../project-merge.js';
6
6
  import { resolveTenantId } from '../tenant.js';
7
7
  import { resolveAuthRoot } from './shared.js';
8
8
  import { printError } from './output.js';
@@ -58,19 +58,24 @@ export function cmdProjects(hippoRoot, args, flags) {
58
58
  return;
59
59
  }
60
60
  console.log(`${apply ? 'Merged' : 'Dry run: would merge'} ${from} into ${into}:`);
61
- console.log(` ${count(r.setAside.length, 'imported note copy', 'imported note copies')} set aside (dormant; the next sync under ${into} imports the notes still on disk)`);
61
+ console.log(` ${count(r.setAside.length, 'imported note copy', 'imported note copies')} set aside (${into} already holds the same note; the rest move under ${into} on the next sync)`);
62
62
  console.log(` ${count(r.restamped.length, 'memory', 'memories')} re-tagged, plus ${r.dormantRestamped.length} dormant and ${count(r.compactions, 'compaction record')}`);
63
63
  console.log(apply ? `Backup: ${r.backup}\nEvery id is in the audit log: hippo audit list --op project_merge` : 'Nothing written. Add --apply to run it.');
64
64
  return;
65
65
  }
66
66
  if (sub === 'repair') {
67
- const r = repairUserGlobalMerges(db, root, { tenantId, dryRun: !apply });
67
+ const r = repairProjects(db, root, { tenantId, dryRun: !apply });
68
68
  if (flags['json']) {
69
69
  console.log(JSON.stringify(r, null, 2));
70
70
  return;
71
71
  }
72
- console.log(`${apply ? 'Repaired' : 'Dry run: would repair'} sleep's user-global merged rows:`);
73
- console.log(` ${r.toProject.length} re-tagged to their parents' project`);
72
+ console.log(`${apply ? 'Repaired' : 'Dry run: would repair'} ${root}:`);
73
+ console.log(` ${count(r.copies.length, 'imported note copy', 'imported note copies')} under the wrong project set aside (the note stays under its own, or user-global)`);
74
+ for (const f of r.folds)
75
+ console.log(` ${f.from} folded into ${f.into} (its sessions' folders resolve there now)`);
76
+ for (const c of r.collisions)
77
+ console.log(` ${c.name} left as it is: its folders now resolve to ${c.ids.join(', ')}; fold it by hand with hippo projects merge`);
78
+ console.log(` sleep's user-global merged rows: ${r.toProject.length} re-tagged to their parents' project`);
74
79
  console.log(` ${r.setAside.length} set aside (parents in two projects; sleep re-merges them per project)`);
75
80
  console.log(` ${r.untraced.length} left as they are (no parent left to show which project; check them with hippo inspect <id>)`);
76
81
  console.log(apply ? `Backup: ${r.backup}\nEvery id is in the audit log: hippo audit list --op project_repair` : 'Nothing written. Add --apply to run it.');
@@ -10,6 +10,7 @@ import { loadLatestHandoff } from '../store/handoffs.js';
10
10
  import { passesScopeFilterForRecall } from '../recall-scope.js';
11
11
  import { fitBudget } from '../search/finalize.js';
12
12
  import { explainMatch } from '../search/explain.js';
13
+ import { DEFAULT_RECALL_BUDGET } from '../search/types.js';
13
14
  import { writeRecallTraceAtRoot } from '../recall-trace.js';
14
15
  import { loadConfig } from '../config.js';
15
16
  import { openHippoDb, closeHippoDb } from '../db.js';
@@ -188,7 +189,7 @@ export async function cmdRecall(hippoRoot, query, flags) {
188
189
  }
189
190
  /** Every flag recall reads, parsed in the order the single-body command checked them. */
190
191
  function parseRecallOptions(hippoRoot, flags) {
191
- const budget = parseBudgetFlag(flags['budget'], 4000);
192
+ const budget = parseBudgetFlag(flags['budget'], DEFAULT_RECALL_BUDGET);
192
193
  const limit = parseLimitFlag(flags['limit']);
193
194
  const asJson = Boolean(flags['json']);
194
195
  const showWhy = Boolean(flags['why']);
@@ -441,9 +442,7 @@ function auditRecall(hippoRoot, globalRoot, query, fit) {
441
442
  /** Traces the recall, books retrieval for a non-empty list, then prints JSON or the fitted block. */
442
443
  function writeRecallResult(hippoRoot, query, o, fit, localIndex) {
443
444
  const { tenantId, sessionId, showWhy, asJson, includeContinuity } = o;
444
- const { results, recallText, cmdPlanningFallacyHint, cmdPlanningFallacyWatching, continuityTokens } = fit;
445
- const { activeSnapshot, sessionHandoff, recentSessionEvents } = fit.continuity;
446
- const { anchoring: cmdAnchoringHint, availability: cmdAvailabilityHint, summary: cmdSuppressionSummary } = fit.hints;
445
+ const { results, recallText } = fit;
447
446
  // The token ledger books the block this recall prints, on whichever exit it takes.
448
447
  const emit = (text) => {
449
448
  withLedgerDb(hippoRoot, (db) => recordTokenUse(db, {
@@ -466,59 +465,46 @@ function writeRecallResult(hippoRoot, query, o, fit, localIndex) {
466
465
  explainMode: showWhy,
467
466
  results: [],
468
467
  });
469
- if (asJson) {
470
- const out = {
471
- query,
472
- results: [],
473
- total: 0,
474
- suppressionSummary: cmdSuppressionSummary,
475
- // HTTP and MCP surface the hint whatever matched, so the zero-result JSON keeps it for parity.
476
- ...(cmdPlanningFallacyHint ? { planningFallacyHint: cmdPlanningFallacyHint } : {}),
477
- ...(cmdPlanningFallacyWatching ? { planningFallacyWatching: cmdPlanningFallacyWatching } : {}),
478
- ...(cmdAnchoringHint ? { anchoringHint: cmdAnchoringHint } : {}),
479
- ...(cmdAvailabilityHint ? { availabilityHint: cmdAvailabilityHint } : {}),
480
- };
481
- if (includeContinuity) {
482
- out.continuity = {
483
- activeSnapshot,
484
- sessionHandoff,
485
- recentSessionEvents,
486
- };
487
- out.continuityTokens = continuityTokens;
488
- }
489
- emit(JSON.stringify(out));
490
- return;
491
- }
492
- emit(recallText);
468
+ // HTTP and MCP surface the hint whatever matched, so the zero-result JSON keeps it for parity.
469
+ emit(asJson ? JSON.stringify({ query, results: [], total: 0, ...recallJsonTail(fit, includeContinuity) }) : recallText);
493
470
  return;
494
471
  }
495
472
  recordRetrieval(hippoRoot, query, o, results, localIndex);
496
473
  if (asJson) {
497
474
  const output = results.map((r) => recallJsonRow(r, query, showWhy, o.primaryIsGlobal || (isInitialized(o.globalRoot) && !localIndex.entries[r.entry.id])));
498
- const jsonOut = {
475
+ emit(JSON.stringify({
499
476
  query,
500
477
  budget: o.budget,
501
478
  results: output,
502
479
  total: output.length,
503
- suppressionSummary: cmdSuppressionSummary,
504
- ...(cmdPlanningFallacyHint ? { planningFallacyHint: cmdPlanningFallacyHint } : {}),
505
- ...(cmdPlanningFallacyWatching ? { planningFallacyWatching: cmdPlanningFallacyWatching } : {}),
506
- ...(cmdAnchoringHint ? { anchoringHint: cmdAnchoringHint } : {}),
507
- ...(cmdAvailabilityHint ? { availabilityHint: cmdAvailabilityHint } : {}),
508
- };
509
- if (includeContinuity) {
510
- jsonOut.continuity = {
511
- activeSnapshot,
512
- sessionHandoff,
513
- recentSessionEvents,
514
- };
515
- jsonOut.continuityTokens = continuityTokens;
516
- }
517
- emit(JSON.stringify(jsonOut));
480
+ ...recallJsonTail(fit, includeContinuity),
481
+ }));
518
482
  return;
519
483
  }
520
484
  emit(recallText);
521
485
  }
486
+ /** The JSON keys after the result list: suppression summary, any bias hints, then continuity when asked for. */
487
+ function recallJsonTail(fit, includeContinuity) {
488
+ const { cmdPlanningFallacyHint, cmdPlanningFallacyWatching, continuityTokens } = fit;
489
+ const { activeSnapshot, sessionHandoff, recentSessionEvents } = fit.continuity;
490
+ const { anchoring: cmdAnchoringHint, availability: cmdAvailabilityHint, summary: cmdSuppressionSummary } = fit.hints;
491
+ const tail = {
492
+ suppressionSummary: cmdSuppressionSummary,
493
+ ...(cmdPlanningFallacyHint ? { planningFallacyHint: cmdPlanningFallacyHint } : {}),
494
+ ...(cmdPlanningFallacyWatching ? { planningFallacyWatching: cmdPlanningFallacyWatching } : {}),
495
+ ...(cmdAnchoringHint ? { anchoringHint: cmdAnchoringHint } : {}),
496
+ ...(cmdAvailabilityHint ? { availabilityHint: cmdAvailabilityHint } : {}),
497
+ };
498
+ if (includeContinuity) {
499
+ tail.continuity = {
500
+ activeSnapshot,
501
+ sessionHandoff,
502
+ recentSessionEvents,
503
+ };
504
+ tail.continuityTokens = continuityTokens;
505
+ }
506
+ return tail;
507
+ }
522
508
  /** Strengthens the returned rows and persists last_retrieval_ids and last_trace_id in one saveIndex call. */
523
509
  function recordRetrieval(hippoRoot, query, o, results, localIndex) {
524
510
  const { globalRoot, tenantId, sessionId, showWhy } = o;
@@ -41,17 +41,8 @@ function rememberTags(flags, cwd) {
41
41
  }
42
42
  return { requested, all };
43
43
  }
44
- async function cmdRemember(hippoRoot, text, flags) {
45
- const useGlobal = Boolean(flags['global']);
46
- const targetRoot = useGlobal ? getGlobalRoot() : hippoRoot;
47
- if (useGlobal) {
48
- initGlobal();
49
- }
50
- else {
51
- requireInit(hippoRoot);
52
- }
53
- const { requested: requestedTags, all: allTags } = rememberTags(flags, process.cwd());
54
- // Resolve explicit confidence flag (default: 'verified' for manual remember)
44
+ // Resolve explicit confidence flag (default: 'verified' for manual remember)
45
+ function rememberConfidence(flags) {
55
46
  let confidence = 'verified';
56
47
  if (flags['observed'])
57
48
  confidence = 'observed';
@@ -59,14 +50,13 @@ async function cmdRemember(hippoRoot, text, flags) {
59
50
  confidence = 'inferred';
60
51
  if (flags['verified'])
61
52
  confidence = 'verified';
62
- // Schema fit needs the store, which the routed request has no access to, so it stays here.
63
- const existing = loadAllEntries(targetRoot, resolveTenantId({}));
64
- const schemaFit = computeSchemaFit(text, requestedTags, existing);
65
- // A3 envelope flags
53
+ return confidence;
54
+ }
55
+ function parseKindFlag(flags) {
66
56
  const kindFlagRaw = typeof flags['kind'] === 'string' ? flags['kind'] : undefined;
67
57
  const kindFlag = kindFlagRaw === undefined ? undefined : kindFlagRaw.toLowerCase();
68
58
  // CLI surface intentionally restricted: 'raw' is reserved for ingestion connectors
69
- // (E1.x: Slack/Jira/Gmail) that route deletions through archiveRawMemory. Existing
59
+ // that route deletions through archiveRawMemory. Existing
70
60
  // forget/consolidate/conflict-resolve paths abort on kind='raw' via the append-only
71
61
  // trigger, so exposing --kind raw here would create unforgettable memories.
72
62
  // 'archived' is an internal sentinel set only inside archiveRawMemory's transaction.
@@ -76,6 +66,10 @@ async function cmdRemember(hippoRoot, text, flags) {
76
66
  printError(`(kind='raw' is reserved for ingestion connectors; kind='archived' is internal.)`);
77
67
  process.exit(1);
78
68
  }
69
+ return kindFlag;
70
+ }
71
+ function parseRememberEnvelope(flags) {
72
+ const kind = parseKindFlag(flags);
79
73
  const ownerRaw = typeof flags['owner'] === 'string' ? flags['owner'] : null;
80
74
  const ownerCheck = validateOwner(ownerRaw, { strict: isStrictOwnerEnv() });
81
75
  if (!ownerCheck.ok) {
@@ -84,11 +78,27 @@ async function cmdRemember(hippoRoot, text, flags) {
84
78
  }
85
79
  if (ownerCheck.message)
86
80
  printError(ownerCheck.message);
87
- const ownerFlag = ownerCheck.value ?? null;
88
- const artifactRefFlag = typeof flags['artifact-ref'] === 'string' ? flags['artifact-ref'] : null;
89
- const scopeForEnvelope = typeof flags['scope'] === 'string' ? flags['scope'].trim() || null : null;
90
- // A5 stub auth: stamp tenant_id from env (HIPPO_TENANT) so recall isolation
91
- // can filter on this row. Default tenant 'default' for unauthenticated CLI.
81
+ const owner = ownerCheck.value ?? null;
82
+ const artifactRef = typeof flags['artifact-ref'] === 'string' ? flags['artifact-ref'] : null;
83
+ const scope = typeof flags['scope'] === 'string' ? flags['scope'].trim() || null : null;
84
+ return { kind, owner, artifactRef, scope };
85
+ }
86
+ async function cmdRemember(hippoRoot, text, flags) {
87
+ const useGlobal = Boolean(flags['global']);
88
+ const targetRoot = useGlobal ? getGlobalRoot() : hippoRoot;
89
+ if (useGlobal) {
90
+ initGlobal();
91
+ }
92
+ else {
93
+ requireInit(hippoRoot);
94
+ }
95
+ const { requested: requestedTags, all: allTags } = rememberTags(flags, process.cwd());
96
+ const confidence = rememberConfidence(flags);
97
+ // Schema fit needs the store, which the routed request has no access to, so it stays here.
98
+ const existing = loadAllEntries(targetRoot, resolveTenantId({}));
99
+ const schemaFit = computeSchemaFit(text, requestedTags, existing);
100
+ const envelope = parseRememberEnvelope(flags);
101
+ // Stamp tenant_id from env (HIPPO_TENANT) so recall isolation can filter on this row; unauthenticated CLI gets 'default'.
92
102
  const tenantId = resolveTenantId({});
93
103
  const rememberConfig = loadConfig(targetRoot);
94
104
  const entry = createMemory(text, {
@@ -98,33 +108,43 @@ async function cmdRemember(hippoRoot, text, flags) {
98
108
  source: useGlobal ? 'cli-global' : 'cli',
99
109
  confidence,
100
110
  schema_fit: schemaFit,
101
- kind: kindFlag,
102
- scope: scopeForEnvelope,
103
- owner: ownerFlag,
104
- artifact_ref: artifactRefFlag,
111
+ kind: envelope.kind,
112
+ scope: envelope.scope,
113
+ owner: envelope.owner,
114
+ artifact_ref: envelope.artifactRef,
105
115
  tenantId,
106
116
  baseHalfLifeDays: rememberConfig.defaultHalfLifeDays,
107
117
  });
108
- // Salience gate: decide if this memory is worth storing
109
- if (rememberConfig.salience.enabled && !Boolean(flags['pin']) && !Boolean(flags['force'])) {
110
- const salienceResult = computeSalience(text, entry.tags, existing, {
111
- recentWindow: rememberConfig.salience.recentWindow,
112
- overlapThreshold: rememberConfig.salience.overlapThreshold,
113
- minContentLength: rememberConfig.salience.minContentLength,
114
- maxRepeatErrors: rememberConfig.salience.maxRepeatErrors,
115
- });
116
- if (salienceResult.decision === 'skip') {
117
- console.log(`Skipped (salience: ${salienceResult.reason}, score ${salienceResult.score.toFixed(2)})`);
118
- return;
119
- }
120
- if (salienceResult.decision === 'start_weak') {
121
- entry.strength = salienceResult.score;
122
- entry.half_life_days = Math.max(1, entry.half_life_days * 0.5);
123
- console.log(`Weakened (salience: ${salienceResult.reason}, strength ${salienceResult.score.toFixed(2)})`);
124
- }
125
- }
118
+ if (!passesSalienceGate(entry, text, existing, rememberConfig, flags))
119
+ return;
126
120
  writeEntry(targetRoot, entry);
127
121
  updateStats(targetRoot, { remembered: 1 });
122
+ printRemembered(entry, useGlobal);
123
+ void embedMemory(targetRoot, entry);
124
+ await extractRememberFacts(targetRoot, entry, flags);
125
+ }
126
+ /** False when the gate skips the write; a start_weak verdict weakens `entry` in place. */
127
+ function passesSalienceGate(entry, text, existing, rememberConfig, flags) {
128
+ if (!rememberConfig.salience.enabled || Boolean(flags['pin']) || Boolean(flags['force']))
129
+ return true;
130
+ const salienceResult = computeSalience(text, entry.tags, existing, {
131
+ recentWindow: rememberConfig.salience.recentWindow,
132
+ overlapThreshold: rememberConfig.salience.overlapThreshold,
133
+ minContentLength: rememberConfig.salience.minContentLength,
134
+ maxRepeatErrors: rememberConfig.salience.maxRepeatErrors,
135
+ });
136
+ if (salienceResult.decision === 'skip') {
137
+ console.log(`Skipped (salience: ${salienceResult.reason}, score ${salienceResult.score.toFixed(2)})`);
138
+ return false;
139
+ }
140
+ if (salienceResult.decision === 'start_weak') {
141
+ entry.strength = salienceResult.score;
142
+ entry.half_life_days = Math.max(1, entry.half_life_days * 0.5);
143
+ console.log(`Weakened (salience: ${salienceResult.reason}, strength ${salienceResult.score.toFixed(2)})`);
144
+ }
145
+ return true;
146
+ }
147
+ function printRemembered(entry, useGlobal) {
128
148
  const prefix = useGlobal ? '[global] ' : '';
129
149
  console.log(`${prefix}Remembered [${entry.id}]`);
130
150
  console.log(` Layer: ${entry.layer} | Strength: ${fmt(entry.strength)} | Half-life: ${entry.half_life_days}d | Confidence: ${entry.confidence}`);
@@ -134,7 +154,8 @@ async function cmdRemember(hippoRoot, text, flags) {
134
154
  console.log(' Pinned (no decay)');
135
155
  for (const w of vetSecrets(entry.content, entry.tags, false).warnings)
136
156
  printError(`Warning: ${w}`);
137
- void embedMemory(targetRoot, entry);
157
+ }
158
+ async function extractRememberFacts(targetRoot, entry, flags) {
138
159
  const config = loadConfig(targetRoot);
139
160
  const shouldExtract = flags['extract'] || config.extraction.enabled === true;
140
161
  const apiKey = envAnthropicApiKey() ?? '';
@@ -186,15 +207,8 @@ function cmdSupersede(hippoRoot, oldId, newContent, flags) {
186
207
  tags,
187
208
  pinned,
188
209
  });
189
- // AT1: write the SUCCESSOR first. The rejection guard fires on the new
190
- // content — if it refuses, nothing has been mutated yet (the old ordering
191
- // committed old.superseded_by before the guarded new write, leaving a
192
- // dangling pointer to an id that was never created). If the old-row write
193
- // below fails instead, the new row exists unpointered — an orphan
194
- // successor, strictly less harmful than a dangling pointer. NOTE: unlike
195
- // api.supersede (whose CAS + insert commit in ONE transaction), this CLI
196
- // path is two independent writes and stays non-atomic; write order is its
197
- // only ordering guarantee.
210
+ // Write the SUCCESSOR first: a rejection-guard refusal then mutates nothing, and an old-row failure leaves an
211
+ // orphan successor rather than a dangling pointer. Unlike api.supersede this path is two non-atomic writes.
198
212
  try {
199
213
  writeEntry(hippoRoot, newEntry);
200
214
  }
@@ -272,6 +286,18 @@ function cmdTrace(hippoRoot, id, flags) {
272
286
  printError(`Memory not found: ${id}`);
273
287
  process.exit(1);
274
288
  }
289
+ const t = {
290
+ entry, id, sourceLabel,
291
+ ...traceStats(entry),
292
+ ...traceLineage(hippoRoot, globalRoot, entry, id, tenantId),
293
+ };
294
+ if (asJson) {
295
+ printTraceJson(t);
296
+ return;
297
+ }
298
+ printTraceText(t);
299
+ }
300
+ function traceStats(entry) {
275
301
  const now = evalNow();
276
302
  const strength = calculateStrength(entry, now);
277
303
  const halfLife = entry.half_life_days;
@@ -285,6 +311,9 @@ function cmdTrace(hippoRoot, id, flags) {
285
311
  const conf = confidenceLabel(entry, now).text;
286
312
  // Projected strength: same decay curve, just push `now` out.
287
313
  const projectedAt = (days) => calculateStrength(entry, new Date(now.getTime() + days * 86_400_000));
314
+ return { strength, halfLife, rewardFactor, effHalfLife, ageDays, sinceLast, facets, conf, projectedAt };
315
+ }
316
+ function traceLineage(hippoRoot, globalRoot, entry, id, tenantId) {
288
317
  // Parents (consolidation lineage) — schema v9 field.
289
318
  const parents = Array.isArray(entry.parents) ? entry.parents : [];
290
319
  const parentPreviews = parents.map((pid) => {
@@ -297,35 +326,40 @@ function cmdTrace(hippoRoot, id, flags) {
297
326
  ...(isInitialized(globalRoot) ? listMemoryConflicts(globalRoot, 'open', tenantId) : []),
298
327
  ];
299
328
  const myConflicts = allConflicts.filter((c) => c.memory_a_id === id || c.memory_b_id === id);
300
- if (asJson) {
301
- console.log(JSON.stringify({
302
- id: entry.id,
303
- source: sourceLabel,
304
- layer: entry.layer,
305
- confidence: facets.tier,
306
- aged_out: facets.agedOut,
307
- pinned: entry.pinned,
308
- starred: entry.starred,
309
- tags: entry.tags,
310
- content: entry.content,
311
- created: entry.created,
312
- age_days: ageDays,
313
- last_retrieved: entry.last_retrieved,
314
- days_since_last_retrieval: sinceLast,
315
- retrieval_count: entry.retrieval_count,
316
- strength_now: strength,
317
- half_life_days: halfLife,
318
- reward_factor: rewardFactor,
319
- effective_half_life_days: effHalfLife,
320
- projected_strength_30d: projectedAt(30),
321
- projected_strength_90d: projectedAt(90),
322
- outcome_positive: entry.outcome_positive,
323
- outcome_negative: entry.outcome_negative,
324
- parents: parentPreviews,
325
- open_conflicts: myConflicts,
326
- }, null, 2));
327
- return;
328
- }
329
+ return { parentPreviews, myConflicts };
330
+ }
331
+ function printTraceJson(t) {
332
+ const { entry, facets } = t;
333
+ console.log(JSON.stringify({
334
+ id: entry.id,
335
+ source: t.sourceLabel,
336
+ layer: entry.layer,
337
+ confidence: facets.tier,
338
+ aged_out: facets.agedOut,
339
+ pinned: entry.pinned,
340
+ starred: entry.starred,
341
+ tags: entry.tags,
342
+ content: entry.content,
343
+ created: entry.created,
344
+ age_days: t.ageDays,
345
+ last_retrieved: entry.last_retrieved,
346
+ days_since_last_retrieval: t.sinceLast,
347
+ retrieval_count: entry.retrieval_count,
348
+ strength_now: t.strength,
349
+ half_life_days: t.halfLife,
350
+ reward_factor: t.rewardFactor,
351
+ effective_half_life_days: t.effHalfLife,
352
+ projected_strength_30d: t.projectedAt(30),
353
+ projected_strength_90d: t.projectedAt(90),
354
+ outcome_positive: entry.outcome_positive,
355
+ outcome_negative: entry.outcome_negative,
356
+ parents: t.parentPreviews,
357
+ open_conflicts: t.myConflicts,
358
+ }, null, 2));
359
+ }
360
+ function printTraceText(t) {
361
+ const { entry, id, sourceLabel, conf, ageDays, strength, projectedAt, halfLife, rewardFactor, effHalfLife, sinceLast } = t;
362
+ const { parentPreviews, myConflicts } = t;
329
363
  console.log(`Memory: ${entry.id} [${sourceLabel}]`);
330
364
  console.log('='.repeat(50));
331
365
  console.log(`Content: ${entry.content.replace(/\s+/g, ' ').slice(0, 160)}${entry.content.length > 160 ? '...' : ''}`);
@@ -377,8 +411,7 @@ export async function handleRemember({ hippoRoot, args, flags }) {
377
411
  // flags (--pin, --layer, --extract, --global) still need the direct
378
412
  // path; we only intercept the minimal envelope. The salience gate is
379
413
  // NOT in richFlag and the route does not apply it, so a routed remember
380
- // stores what a direct one would skip. Measured 2026-09-07, tracked in
381
- // TODOS.md; do not read this list as covering salience.
414
+ // stores what a direct one would skip; do not read this list as covering salience.
382
415
  const richFlag = flags['pin'] || flags['global'] || flags['extract'] || flags['force'] ||
383
416
  flags['observed'] || flags['inferred'] || flags['verified'] ||
384
417
  flags['layer'] !== undefined;
@@ -387,9 +420,7 @@ export async function handleRemember({ hippoRoot, args, flags }) {
387
420
  const rememberKindAllowed = ['distilled', 'superseded'];
388
421
  if (rememberKindRaw === undefined || rememberKindAllowed.includes(rememberKindRaw)) {
389
422
  const tags = rememberTags(flags, process.cwd()).all;
390
- // B2 v1.12.6 — validate --owner on the thin-client path too.
391
- // Failure on this path exits early so the user gets the same
392
- // validation experience whether or not a server is up.
423
+ // Validate --owner on the thin-client path too, so validation is the same whether or not a server is up.
393
424
  const thinOwnerRaw = typeof flags['owner'] === 'string' ? flags['owner'] : undefined;
394
425
  const thinOwnerCheck = validateOwner(thinOwnerRaw, { strict: isStrictOwnerEnv() });
395
426
  if (!thinOwnerCheck.ok) {