hippo-memory 1.44.0 → 1.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (554) hide show
  1. package/README.md +68 -24
  2. package/bin/hippo.js +3 -1
  3. package/dist/ablation.d.ts +10 -1
  4. package/dist/ablation.js +17 -1
  5. package/dist/api.d.ts +81 -22
  6. package/dist/api.js +258 -49
  7. package/dist/audit.d.ts +2 -2
  8. package/dist/audit.js +9 -0
  9. package/dist/autolearn.d.ts +1 -1
  10. package/dist/autolearn.js +2 -1
  11. package/dist/capture-error.d.ts +20 -0
  12. package/dist/capture-error.js +82 -0
  13. package/dist/capture.d.ts +25 -8
  14. package/dist/capture.js +100 -5
  15. package/dist/cli.d.ts +9 -1
  16. package/dist/cli.js +531 -119
  17. package/dist/client.d.ts +4 -21
  18. package/dist/client.js +3 -112
  19. package/dist/config.d.ts +20 -0
  20. package/dist/config.js +37 -1
  21. package/dist/consolidate.d.ts +9 -0
  22. package/dist/consolidate.js +133 -46
  23. package/dist/dag.d.ts +1 -0
  24. package/dist/dag.js +10 -5
  25. package/dist/dashboard.js +7 -3
  26. package/dist/db.js +69 -24
  27. package/dist/dedupe.d.ts +1 -0
  28. package/dist/dedupe.js +4 -7
  29. package/dist/doctor.d.ts +34 -0
  30. package/dist/doctor.js +174 -0
  31. package/dist/dormant.d.ts +91 -0
  32. package/dist/dormant.js +121 -0
  33. package/dist/embedding-provider.js +2 -1
  34. package/dist/embeddings.js +81 -8
  35. package/dist/eval-stats.d.ts +123 -0
  36. package/dist/eval-stats.js +187 -0
  37. package/dist/extract.d.ts +2 -0
  38. package/dist/extract.js +9 -4
  39. package/dist/half-life-migration.d.ts +55 -0
  40. package/dist/half-life-migration.js +111 -0
  41. package/dist/hooks.d.ts +4 -0
  42. package/dist/hooks.js +47 -0
  43. package/dist/mcp/server.d.ts +6 -0
  44. package/dist/mcp/server.js +90 -48
  45. package/dist/memory.d.ts +18 -1
  46. package/dist/memory.js +33 -5
  47. package/dist/physics-config.js +5 -1
  48. package/dist/recall-scope.d.ts +24 -0
  49. package/dist/recall-scope.js +41 -0
  50. package/dist/refine-llm.js +3 -2
  51. package/dist/reject-flow.d.ts +3 -3
  52. package/dist/reject-flow.js +10 -3
  53. package/dist/rerankers/jev.js +3 -2
  54. package/dist/rerankers/llm.js +3 -2
  55. package/dist/search.d.ts +4 -4
  56. package/dist/search.js +26 -21
  57. package/dist/server.d.ts +5 -0
  58. package/dist/server.js +49 -29
  59. package/dist/shared.js +1 -1
  60. package/dist/store.d.ts +29 -11
  61. package/dist/store.js +197 -70
  62. package/dist/token-ledger.d.ts +119 -0
  63. package/dist/token-ledger.js +181 -0
  64. package/dist/version.d.ts +4 -6
  65. package/dist/version.js +4 -7
  66. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  67. package/extensions/openclaw-plugin/package.json +1 -1
  68. package/openclaw.plugin.json +1 -1
  69. package/package.json +7 -3
  70. package/dist/ablation.d.ts.map +0 -1
  71. package/dist/ablation.js.map +0 -1
  72. package/dist/ambient.d.ts.map +0 -1
  73. package/dist/ambient.js.map +0 -1
  74. package/dist/api.d.ts.map +0 -1
  75. package/dist/api.js.map +0 -1
  76. package/dist/audit-prune.d.ts.map +0 -1
  77. package/dist/audit-prune.js.map +0 -1
  78. package/dist/audit.d.ts.map +0 -1
  79. package/dist/audit.js.map +0 -1
  80. package/dist/auth.d.ts.map +0 -1
  81. package/dist/auth.js.map +0 -1
  82. package/dist/autolearn.d.ts.map +0 -1
  83. package/dist/autolearn.js.map +0 -1
  84. package/dist/availability.d.ts.map +0 -1
  85. package/dist/availability.js.map +0 -1
  86. package/dist/benchmarks/e1.3/incident-recall-eval.js +0 -78
  87. package/dist/benchmarks/e1.3/incident-recall-eval.js.map +0 -1
  88. package/dist/benchmarks/e1.3/slack-1000-event-smoke.js +0 -103
  89. package/dist/benchmarks/e1.3/slack-1000-event-smoke.js.map +0 -1
  90. package/dist/capture.d.ts.map +0 -1
  91. package/dist/capture.js.map +0 -1
  92. package/dist/card-detail.d.ts.map +0 -1
  93. package/dist/card-detail.js.map +0 -1
  94. package/dist/card.d.ts.map +0 -1
  95. package/dist/card.js.map +0 -1
  96. package/dist/cli.d.ts.map +0 -1
  97. package/dist/cli.js.map +0 -1
  98. package/dist/client.d.ts.map +0 -1
  99. package/dist/client.js.map +0 -1
  100. package/dist/compare.d.ts.map +0 -1
  101. package/dist/compare.js.map +0 -1
  102. package/dist/config.d.ts.map +0 -1
  103. package/dist/config.js.map +0 -1
  104. package/dist/connectors/github/backfill.d.ts.map +0 -1
  105. package/dist/connectors/github/backfill.js.map +0 -1
  106. package/dist/connectors/github/cli-impl.d.ts.map +0 -1
  107. package/dist/connectors/github/cli-impl.js.map +0 -1
  108. package/dist/connectors/github/deletion.d.ts.map +0 -1
  109. package/dist/connectors/github/deletion.js.map +0 -1
  110. package/dist/connectors/github/dlq.d.ts.map +0 -1
  111. package/dist/connectors/github/dlq.js.map +0 -1
  112. package/dist/connectors/github/idempotency.d.ts.map +0 -1
  113. package/dist/connectors/github/idempotency.js.map +0 -1
  114. package/dist/connectors/github/ingest.d.ts.map +0 -1
  115. package/dist/connectors/github/ingest.js.map +0 -1
  116. package/dist/connectors/github/octokit-client.d.ts.map +0 -1
  117. package/dist/connectors/github/octokit-client.js.map +0 -1
  118. package/dist/connectors/github/ratelimit.d.ts.map +0 -1
  119. package/dist/connectors/github/ratelimit.js.map +0 -1
  120. package/dist/connectors/github/scope.d.ts.map +0 -1
  121. package/dist/connectors/github/scope.js.map +0 -1
  122. package/dist/connectors/github/signature.d.ts.map +0 -1
  123. package/dist/connectors/github/signature.js.map +0 -1
  124. package/dist/connectors/github/tenant-routing.d.ts.map +0 -1
  125. package/dist/connectors/github/tenant-routing.js.map +0 -1
  126. package/dist/connectors/github/transform.d.ts.map +0 -1
  127. package/dist/connectors/github/transform.js.map +0 -1
  128. package/dist/connectors/github/types.d.ts.map +0 -1
  129. package/dist/connectors/github/types.js.map +0 -1
  130. package/dist/connectors/slack/backfill.d.ts.map +0 -1
  131. package/dist/connectors/slack/backfill.js.map +0 -1
  132. package/dist/connectors/slack/deletion.d.ts.map +0 -1
  133. package/dist/connectors/slack/deletion.js.map +0 -1
  134. package/dist/connectors/slack/dlq.d.ts.map +0 -1
  135. package/dist/connectors/slack/dlq.js.map +0 -1
  136. package/dist/connectors/slack/idempotency.d.ts.map +0 -1
  137. package/dist/connectors/slack/idempotency.js.map +0 -1
  138. package/dist/connectors/slack/ingest.d.ts.map +0 -1
  139. package/dist/connectors/slack/ingest.js.map +0 -1
  140. package/dist/connectors/slack/ratelimit.d.ts.map +0 -1
  141. package/dist/connectors/slack/ratelimit.js.map +0 -1
  142. package/dist/connectors/slack/scope.d.ts.map +0 -1
  143. package/dist/connectors/slack/scope.js.map +0 -1
  144. package/dist/connectors/slack/signature.d.ts.map +0 -1
  145. package/dist/connectors/slack/signature.js.map +0 -1
  146. package/dist/connectors/slack/tenant-routing.d.ts.map +0 -1
  147. package/dist/connectors/slack/tenant-routing.js.map +0 -1
  148. package/dist/connectors/slack/transform.d.ts.map +0 -1
  149. package/dist/connectors/slack/transform.js.map +0 -1
  150. package/dist/connectors/slack/types.d.ts.map +0 -1
  151. package/dist/connectors/slack/types.js.map +0 -1
  152. package/dist/connectors/slack/web-client.d.ts.map +0 -1
  153. package/dist/connectors/slack/web-client.js.map +0 -1
  154. package/dist/connectors/slack/workspaces.d.ts.map +0 -1
  155. package/dist/connectors/slack/workspaces.js.map +0 -1
  156. package/dist/consolidate.d.ts.map +0 -1
  157. package/dist/consolidate.js.map +0 -1
  158. package/dist/correction-latency.d.ts.map +0 -1
  159. package/dist/correction-latency.js.map +0 -1
  160. package/dist/customer-notes.d.ts.map +0 -1
  161. package/dist/customer-notes.js.map +0 -1
  162. package/dist/dag.d.ts.map +0 -1
  163. package/dist/dag.js.map +0 -1
  164. package/dist/dashboard.d.ts.map +0 -1
  165. package/dist/dashboard.js.map +0 -1
  166. package/dist/db.d.ts.map +0 -1
  167. package/dist/db.js.map +0 -1
  168. package/dist/decisions.d.ts.map +0 -1
  169. package/dist/decisions.js.map +0 -1
  170. package/dist/dedupe.d.ts.map +0 -1
  171. package/dist/dedupe.js.map +0 -1
  172. package/dist/embedding-provider.d.ts.map +0 -1
  173. package/dist/embedding-provider.js.map +0 -1
  174. package/dist/embeddings.d.ts.map +0 -1
  175. package/dist/embeddings.js.map +0 -1
  176. package/dist/eval-suite.d.ts.map +0 -1
  177. package/dist/eval-suite.js.map +0 -1
  178. package/dist/eval.d.ts.map +0 -1
  179. package/dist/eval.js.map +0 -1
  180. package/dist/extensions/openclaw-plugin/index.js.map +0 -1
  181. package/dist/extract.d.ts.map +0 -1
  182. package/dist/extract.js.map +0 -1
  183. package/dist/forward-claim-detector.d.ts.map +0 -1
  184. package/dist/forward-claim-detector.js.map +0 -1
  185. package/dist/goals.d.ts.map +0 -1
  186. package/dist/goals.js.map +0 -1
  187. package/dist/graph-extract.d.ts.map +0 -1
  188. package/dist/graph-extract.js.map +0 -1
  189. package/dist/graph-recall.d.ts.map +0 -1
  190. package/dist/graph-recall.js.map +0 -1
  191. package/dist/graph-stream.d.ts.map +0 -1
  192. package/dist/graph-stream.js.map +0 -1
  193. package/dist/graph-view.d.ts.map +0 -1
  194. package/dist/graph-view.js.map +0 -1
  195. package/dist/graph.d.ts.map +0 -1
  196. package/dist/graph.js.map +0 -1
  197. package/dist/handoff.d.ts.map +0 -1
  198. package/dist/handoff.js.map +0 -1
  199. package/dist/hooks.d.ts.map +0 -1
  200. package/dist/hooks.js.map +0 -1
  201. package/dist/importers.d.ts.map +0 -1
  202. package/dist/importers.js.map +0 -1
  203. package/dist/incidents.d.ts.map +0 -1
  204. package/dist/incidents.js.map +0 -1
  205. package/dist/index.d.ts.map +0 -1
  206. package/dist/index.js.map +0 -1
  207. package/dist/invalidation.d.ts.map +0 -1
  208. package/dist/invalidation.js.map +0 -1
  209. package/dist/mcp/framing.d.ts.map +0 -1
  210. package/dist/mcp/framing.js.map +0 -1
  211. package/dist/mcp/server.d.ts.map +0 -1
  212. package/dist/mcp/server.js.map +0 -1
  213. package/dist/memory-value-weights.d.ts.map +0 -1
  214. package/dist/memory-value-weights.js.map +0 -1
  215. package/dist/memory-value.d.ts.map +0 -1
  216. package/dist/memory-value.js.map +0 -1
  217. package/dist/memory.d.ts.map +0 -1
  218. package/dist/memory.js.map +0 -1
  219. package/dist/multihop.d.ts.map +0 -1
  220. package/dist/multihop.js.map +0 -1
  221. package/dist/owner-validation.d.ts.map +0 -1
  222. package/dist/owner-validation.js.map +0 -1
  223. package/dist/path-context.d.ts.map +0 -1
  224. package/dist/path-context.js.map +0 -1
  225. package/dist/physics-config.d.ts.map +0 -1
  226. package/dist/physics-config.js.map +0 -1
  227. package/dist/physics-state.d.ts.map +0 -1
  228. package/dist/physics-state.js.map +0 -1
  229. package/dist/physics.d.ts.map +0 -1
  230. package/dist/physics.js.map +0 -1
  231. package/dist/policies.d.ts.map +0 -1
  232. package/dist/policies.js.map +0 -1
  233. package/dist/postinstall.d.ts.map +0 -1
  234. package/dist/postinstall.js.map +0 -1
  235. package/dist/predictions.d.ts.map +0 -1
  236. package/dist/predictions.js.map +0 -1
  237. package/dist/processes.d.ts.map +0 -1
  238. package/dist/processes.js.map +0 -1
  239. package/dist/project-briefs.d.ts.map +0 -1
  240. package/dist/project-briefs.js.map +0 -1
  241. package/dist/project-identity.d.ts.map +0 -1
  242. package/dist/project-identity.js.map +0 -1
  243. package/dist/provenance-coverage.d.ts.map +0 -1
  244. package/dist/provenance-coverage.js.map +0 -1
  245. package/dist/rate-limit.d.ts.map +0 -1
  246. package/dist/rate-limit.js.map +0 -1
  247. package/dist/raw-archive-mirror-cleanup.d.ts.map +0 -1
  248. package/dist/raw-archive-mirror-cleanup.js.map +0 -1
  249. package/dist/raw-archive.d.ts.map +0 -1
  250. package/dist/raw-archive.js.map +0 -1
  251. package/dist/recall-history.d.ts.map +0 -1
  252. package/dist/recall-history.js.map +0 -1
  253. package/dist/recall-scope.d.ts.map +0 -1
  254. package/dist/recall-scope.js.map +0 -1
  255. package/dist/recall-trace.d.ts.map +0 -1
  256. package/dist/recall-trace.js.map +0 -1
  257. package/dist/refine-llm.d.ts.map +0 -1
  258. package/dist/refine-llm.js.map +0 -1
  259. package/dist/reject-flow.d.ts.map +0 -1
  260. package/dist/reject-flow.js.map +0 -1
  261. package/dist/rejection.d.ts.map +0 -1
  262. package/dist/rejection.js.map +0 -1
  263. package/dist/replay.d.ts.map +0 -1
  264. package/dist/replay.js.map +0 -1
  265. package/dist/rerankers/cross-encoder.d.ts.map +0 -1
  266. package/dist/rerankers/cross-encoder.js.map +0 -1
  267. package/dist/rerankers/index.d.ts.map +0 -1
  268. package/dist/rerankers/index.js.map +0 -1
  269. package/dist/rerankers/jev.d.ts.map +0 -1
  270. package/dist/rerankers/jev.js.map +0 -1
  271. package/dist/rerankers/llm.d.ts.map +0 -1
  272. package/dist/rerankers/llm.js.map +0 -1
  273. package/dist/rerankers/types.d.ts.map +0 -1
  274. package/dist/rerankers/types.js.map +0 -1
  275. package/dist/rrf.d.ts.map +0 -1
  276. package/dist/rrf.js.map +0 -1
  277. package/dist/salience.d.ts.map +0 -1
  278. package/dist/salience.js.map +0 -1
  279. package/dist/scheduler.d.ts.map +0 -1
  280. package/dist/scheduler.js.map +0 -1
  281. package/dist/scope.d.ts.map +0 -1
  282. package/dist/scope.js.map +0 -1
  283. package/dist/search.d.ts.map +0 -1
  284. package/dist/search.js.map +0 -1
  285. package/dist/secret-detect.d.ts.map +0 -1
  286. package/dist/secret-detect.js.map +0 -1
  287. package/dist/server-detect.d.ts.map +0 -1
  288. package/dist/server-detect.js.map +0 -1
  289. package/dist/server.d.ts.map +0 -1
  290. package/dist/server.js.map +0 -1
  291. package/dist/shared.d.ts.map +0 -1
  292. package/dist/shared.js.map +0 -1
  293. package/dist/skills.d.ts.map +0 -1
  294. package/dist/skills.js.map +0 -1
  295. package/dist/sleep-redact.d.ts +0 -58
  296. package/dist/sleep-redact.d.ts.map +0 -1
  297. package/dist/sleep-redact.js +0 -80
  298. package/dist/sleep-redact.js.map +0 -1
  299. package/dist/src/ablation.js +0 -138
  300. package/dist/src/ablation.js.map +0 -1
  301. package/dist/src/ambient.js +0 -148
  302. package/dist/src/ambient.js.map +0 -1
  303. package/dist/src/api.js +0 -2109
  304. package/dist/src/api.js.map +0 -1
  305. package/dist/src/audit-prune.js +0 -109
  306. package/dist/src/audit-prune.js.map +0 -1
  307. package/dist/src/audit.js +0 -213
  308. package/dist/src/audit.js.map +0 -1
  309. package/dist/src/auth.js +0 -97
  310. package/dist/src/auth.js.map +0 -1
  311. package/dist/src/autolearn.js +0 -174
  312. package/dist/src/autolearn.js.map +0 -1
  313. package/dist/src/availability.js +0 -94
  314. package/dist/src/availability.js.map +0 -1
  315. package/dist/src/capture.js +0 -1346
  316. package/dist/src/capture.js.map +0 -1
  317. package/dist/src/card-detail.js +0 -15
  318. package/dist/src/card-detail.js.map +0 -1
  319. package/dist/src/card.js +0 -19
  320. package/dist/src/card.js.map +0 -1
  321. package/dist/src/cli.js +0 -9387
  322. package/dist/src/cli.js.map +0 -1
  323. package/dist/src/client.js +0 -237
  324. package/dist/src/client.js.map +0 -1
  325. package/dist/src/compare.js +0 -122
  326. package/dist/src/compare.js.map +0 -1
  327. package/dist/src/config.js +0 -135
  328. package/dist/src/config.js.map +0 -1
  329. package/dist/src/connectors/github/backfill.js +0 -281
  330. package/dist/src/connectors/github/backfill.js.map +0 -1
  331. package/dist/src/connectors/github/cli-impl.js +0 -234
  332. package/dist/src/connectors/github/cli-impl.js.map +0 -1
  333. package/dist/src/connectors/github/deletion.js +0 -85
  334. package/dist/src/connectors/github/deletion.js.map +0 -1
  335. package/dist/src/connectors/github/dlq.js +0 -190
  336. package/dist/src/connectors/github/dlq.js.map +0 -1
  337. package/dist/src/connectors/github/idempotency.js +0 -27
  338. package/dist/src/connectors/github/idempotency.js.map +0 -1
  339. package/dist/src/connectors/github/ingest.js +0 -156
  340. package/dist/src/connectors/github/ingest.js.map +0 -1
  341. package/dist/src/connectors/github/octokit-client.js +0 -70
  342. package/dist/src/connectors/github/octokit-client.js.map +0 -1
  343. package/dist/src/connectors/github/ratelimit.js +0 -31
  344. package/dist/src/connectors/github/ratelimit.js.map +0 -1
  345. package/dist/src/connectors/github/scope.js +0 -13
  346. package/dist/src/connectors/github/scope.js.map +0 -1
  347. package/dist/src/connectors/github/signature.js +0 -81
  348. package/dist/src/connectors/github/signature.js.map +0 -1
  349. package/dist/src/connectors/github/tenant-routing.js +0 -69
  350. package/dist/src/connectors/github/tenant-routing.js.map +0 -1
  351. package/dist/src/connectors/github/transform.js +0 -103
  352. package/dist/src/connectors/github/transform.js.map +0 -1
  353. package/dist/src/connectors/github/types.js +0 -100
  354. package/dist/src/connectors/github/types.js.map +0 -1
  355. package/dist/src/connectors/slack/backfill.js +0 -78
  356. package/dist/src/connectors/slack/backfill.js.map +0 -1
  357. package/dist/src/connectors/slack/deletion.js +0 -59
  358. package/dist/src/connectors/slack/deletion.js.map +0 -1
  359. package/dist/src/connectors/slack/dlq.js +0 -224
  360. package/dist/src/connectors/slack/dlq.js.map +0 -1
  361. package/dist/src/connectors/slack/idempotency.js +0 -31
  362. package/dist/src/connectors/slack/idempotency.js.map +0 -1
  363. package/dist/src/connectors/slack/ingest.js +0 -109
  364. package/dist/src/connectors/slack/ingest.js.map +0 -1
  365. package/dist/src/connectors/slack/ratelimit.js +0 -18
  366. package/dist/src/connectors/slack/ratelimit.js.map +0 -1
  367. package/dist/src/connectors/slack/scope.js +0 -13
  368. package/dist/src/connectors/slack/scope.js.map +0 -1
  369. package/dist/src/connectors/slack/signature.js +0 -27
  370. package/dist/src/connectors/slack/signature.js.map +0 -1
  371. package/dist/src/connectors/slack/tenant-routing.js +0 -41
  372. package/dist/src/connectors/slack/tenant-routing.js.map +0 -1
  373. package/dist/src/connectors/slack/transform.js +0 -47
  374. package/dist/src/connectors/slack/transform.js.map +0 -1
  375. package/dist/src/connectors/slack/types.js +0 -35
  376. package/dist/src/connectors/slack/types.js.map +0 -1
  377. package/dist/src/connectors/slack/web-client.js +0 -43
  378. package/dist/src/connectors/slack/web-client.js.map +0 -1
  379. package/dist/src/connectors/slack/workspaces.js +0 -62
  380. package/dist/src/connectors/slack/workspaces.js.map +0 -1
  381. package/dist/src/consolidate.js +0 -978
  382. package/dist/src/consolidate.js.map +0 -1
  383. package/dist/src/correction-latency.js +0 -74
  384. package/dist/src/correction-latency.js.map +0 -1
  385. package/dist/src/customer-notes.js +0 -325
  386. package/dist/src/customer-notes.js.map +0 -1
  387. package/dist/src/dag.js +0 -352
  388. package/dist/src/dag.js.map +0 -1
  389. package/dist/src/dashboard.js +0 -258
  390. package/dist/src/dashboard.js.map +0 -1
  391. package/dist/src/db.js +0 -2731
  392. package/dist/src/db.js.map +0 -1
  393. package/dist/src/decisions.js +0 -304
  394. package/dist/src/decisions.js.map +0 -1
  395. package/dist/src/dedupe.js +0 -141
  396. package/dist/src/dedupe.js.map +0 -1
  397. package/dist/src/embedding-provider.js +0 -314
  398. package/dist/src/embedding-provider.js.map +0 -1
  399. package/dist/src/embeddings.js +0 -543
  400. package/dist/src/embeddings.js.map +0 -1
  401. package/dist/src/eval-suite.js +0 -294
  402. package/dist/src/eval-suite.js.map +0 -1
  403. package/dist/src/eval.js +0 -187
  404. package/dist/src/eval.js.map +0 -1
  405. package/dist/src/extract.js +0 -117
  406. package/dist/src/extract.js.map +0 -1
  407. package/dist/src/forward-claim-detector.js +0 -117
  408. package/dist/src/forward-claim-detector.js.map +0 -1
  409. package/dist/src/goals.js +0 -399
  410. package/dist/src/goals.js.map +0 -1
  411. package/dist/src/graph-extract.js +0 -314
  412. package/dist/src/graph-extract.js.map +0 -1
  413. package/dist/src/graph-recall.js +0 -277
  414. package/dist/src/graph-recall.js.map +0 -1
  415. package/dist/src/graph-stream.js +0 -176
  416. package/dist/src/graph-stream.js.map +0 -1
  417. package/dist/src/graph-view.js +0 -310
  418. package/dist/src/graph-view.js.map +0 -1
  419. package/dist/src/graph.js +0 -762
  420. package/dist/src/graph.js.map +0 -1
  421. package/dist/src/handoff.js +0 -65
  422. package/dist/src/handoff.js.map +0 -1
  423. package/dist/src/hooks.js +0 -926
  424. package/dist/src/hooks.js.map +0 -1
  425. package/dist/src/importers.js +0 -881
  426. package/dist/src/importers.js.map +0 -1
  427. package/dist/src/incidents.js +0 -336
  428. package/dist/src/incidents.js.map +0 -1
  429. package/dist/src/index.js +0 -32
  430. package/dist/src/index.js.map +0 -1
  431. package/dist/src/invalidation.js +0 -129
  432. package/dist/src/invalidation.js.map +0 -1
  433. package/dist/src/mcp/framing.js +0 -45
  434. package/dist/src/mcp/framing.js.map +0 -1
  435. package/dist/src/mcp/server.js +0 -1298
  436. package/dist/src/mcp/server.js.map +0 -1
  437. package/dist/src/memory-value-weights.js +0 -31
  438. package/dist/src/memory-value-weights.js.map +0 -1
  439. package/dist/src/memory-value.js +0 -261
  440. package/dist/src/memory-value.js.map +0 -1
  441. package/dist/src/memory.js +0 -425
  442. package/dist/src/memory.js.map +0 -1
  443. package/dist/src/multihop.js +0 -35
  444. package/dist/src/multihop.js.map +0 -1
  445. package/dist/src/owner-validation.js +0 -56
  446. package/dist/src/owner-validation.js.map +0 -1
  447. package/dist/src/path-context.js +0 -48
  448. package/dist/src/path-context.js.map +0 -1
  449. package/dist/src/physics-config.js +0 -26
  450. package/dist/src/physics-config.js.map +0 -1
  451. package/dist/src/physics-state.js +0 -172
  452. package/dist/src/physics-state.js.map +0 -1
  453. package/dist/src/physics.js +0 -378
  454. package/dist/src/physics.js.map +0 -1
  455. package/dist/src/policies.js +0 -414
  456. package/dist/src/policies.js.map +0 -1
  457. package/dist/src/postinstall.js +0 -101
  458. package/dist/src/postinstall.js.map +0 -1
  459. package/dist/src/predictions.js +0 -629
  460. package/dist/src/predictions.js.map +0 -1
  461. package/dist/src/processes.js +0 -349
  462. package/dist/src/processes.js.map +0 -1
  463. package/dist/src/project-briefs.js +0 -492
  464. package/dist/src/project-briefs.js.map +0 -1
  465. package/dist/src/project-identity.js +0 -200
  466. package/dist/src/project-identity.js.map +0 -1
  467. package/dist/src/provenance-coverage.js +0 -23
  468. package/dist/src/provenance-coverage.js.map +0 -1
  469. package/dist/src/rate-limit.js +0 -60
  470. package/dist/src/rate-limit.js.map +0 -1
  471. package/dist/src/raw-archive-mirror-cleanup.js +0 -55
  472. package/dist/src/raw-archive-mirror-cleanup.js.map +0 -1
  473. package/dist/src/raw-archive.js +0 -91
  474. package/dist/src/raw-archive.js.map +0 -1
  475. package/dist/src/recall-history.js +0 -235
  476. package/dist/src/recall-history.js.map +0 -1
  477. package/dist/src/recall-scope.js +0 -89
  478. package/dist/src/recall-scope.js.map +0 -1
  479. package/dist/src/recall-trace.js +0 -186
  480. package/dist/src/recall-trace.js.map +0 -1
  481. package/dist/src/refine-llm.js +0 -156
  482. package/dist/src/refine-llm.js.map +0 -1
  483. package/dist/src/reject-flow.js +0 -209
  484. package/dist/src/reject-flow.js.map +0 -1
  485. package/dist/src/rejection.js +0 -160
  486. package/dist/src/rejection.js.map +0 -1
  487. package/dist/src/replay.js +0 -122
  488. package/dist/src/replay.js.map +0 -1
  489. package/dist/src/rerankers/cross-encoder.js +0 -137
  490. package/dist/src/rerankers/cross-encoder.js.map +0 -1
  491. package/dist/src/rerankers/index.js +0 -20
  492. package/dist/src/rerankers/index.js.map +0 -1
  493. package/dist/src/rerankers/jev.js +0 -119
  494. package/dist/src/rerankers/jev.js.map +0 -1
  495. package/dist/src/rerankers/llm.js +0 -80
  496. package/dist/src/rerankers/llm.js.map +0 -1
  497. package/dist/src/rerankers/types.js +0 -2
  498. package/dist/src/rerankers/types.js.map +0 -1
  499. package/dist/src/rrf.js +0 -61
  500. package/dist/src/rrf.js.map +0 -1
  501. package/dist/src/salience.js +0 -74
  502. package/dist/src/salience.js.map +0 -1
  503. package/dist/src/scheduler.js +0 -77
  504. package/dist/src/scheduler.js.map +0 -1
  505. package/dist/src/scope.js +0 -35
  506. package/dist/src/scope.js.map +0 -1
  507. package/dist/src/search.js +0 -1003
  508. package/dist/src/search.js.map +0 -1
  509. package/dist/src/secret-detect.js +0 -95
  510. package/dist/src/secret-detect.js.map +0 -1
  511. package/dist/src/server-detect.js +0 -231
  512. package/dist/src/server-detect.js.map +0 -1
  513. package/dist/src/server.js +0 -3255
  514. package/dist/src/server.js.map +0 -1
  515. package/dist/src/shared.js +0 -502
  516. package/dist/src/shared.js.map +0 -1
  517. package/dist/src/skills.js +0 -346
  518. package/dist/src/skills.js.map +0 -1
  519. package/dist/src/sleep-redact.js +0 -80
  520. package/dist/src/sleep-redact.js.map +0 -1
  521. package/dist/src/sso.js +0 -22
  522. package/dist/src/sso.js.map +0 -1
  523. package/dist/src/stdin.js +0 -41
  524. package/dist/src/stdin.js.map +0 -1
  525. package/dist/src/store.js +0 -3749
  526. package/dist/src/store.js.map +0 -1
  527. package/dist/src/tenant.js +0 -17
  528. package/dist/src/tenant.js.map +0 -1
  529. package/dist/src/trace.js +0 -72
  530. package/dist/src/trace.js.map +0 -1
  531. package/dist/src/version.js +0 -40
  532. package/dist/src/version.js.map +0 -1
  533. package/dist/src/working-memory.js +0 -157
  534. package/dist/src/working-memory.js.map +0 -1
  535. package/dist/src/yaml.js +0 -107
  536. package/dist/src/yaml.js.map +0 -1
  537. package/dist/sso.d.ts +0 -13
  538. package/dist/sso.d.ts.map +0 -1
  539. package/dist/sso.js +0 -22
  540. package/dist/sso.js.map +0 -1
  541. package/dist/stdin.d.ts.map +0 -1
  542. package/dist/stdin.js.map +0 -1
  543. package/dist/store.d.ts.map +0 -1
  544. package/dist/store.js.map +0 -1
  545. package/dist/tenant.d.ts.map +0 -1
  546. package/dist/tenant.js.map +0 -1
  547. package/dist/trace.d.ts.map +0 -1
  548. package/dist/trace.js.map +0 -1
  549. package/dist/version.d.ts.map +0 -1
  550. package/dist/version.js.map +0 -1
  551. package/dist/working-memory.d.ts.map +0 -1
  552. package/dist/working-memory.js.map +0 -1
  553. package/dist/yaml.d.ts.map +0 -1
  554. package/dist/yaml.js.map +0 -1
package/dist/cli.js CHANGED
@@ -19,6 +19,9 @@
19
19
  * hippo reject <id>|--value "<text>" --reason "<why>"
20
20
  * hippo rejections
21
21
  * hippo unreject <digest-prefix>
22
+ * hippo dormant [<query>] [--limit <n>] [--json] | restore <id> | forget <id>
23
+ * hippo tokens [--days <n>] [--json] [--global]
24
+ * hippo doctor [--json]
22
25
  * hippo inspect <id>
23
26
  * hippo embed [--status]
24
27
  * hippo watch "<command>"
@@ -29,7 +32,7 @@
29
32
  * hippo decide "<decision>" [--context "<why>"] [--supersedes <id>]
30
33
  * hippo wm <push|read|clear|flush>
31
34
  */
32
- import { evalNow, isRecallBoostAblated } from './ablation.js';
35
+ import { evalNow } from './ablation.js';
33
36
  import * as path from 'path';
34
37
  import * as fs from 'fs';
35
38
  import * as os from 'os';
@@ -38,14 +41,14 @@ import { execFileSync, execSync, spawn } from 'child_process';
38
41
  import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInstalledTools, defaultSleepLogPath, ensureCodexWrapperInstalled, installCodexWrapper, isCodexWrapperInstalled, repairCodexWrapperIfInstalled, uninstallCodexWrapper, resolveCodexSessionTranscript, resolveCodexWrapperPaths, installOpencodePlugin, uninstallOpencodePlugin, resolveOpencodePluginPath, } from './hooks.js';
39
42
  import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, confidenceFacets, confidenceLabel, computeSchemaFit, Layer, } from './memory.js';
40
43
  import { detectSecret } from './secret-detect.js';
41
- import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOutcome, writeSessionEndHandoff, createCard, loadCard, listCards, loadCardRuns, claimCard, heartbeatCard, blockCard, reviewCard, completeCard, reclaimExpiredCards, addCardComment, } from './store.js';
44
+ import { getHippoRoot, isInitialized, initStore, writeEntry, strengthenRetrieved, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOutcome, writeSessionEndHandoff, createCard, loadCard, listCards, loadCardRuns, claimCard, heartbeatCard, blockCard, reviewCard, completeCard, reclaimExpiredCards, addCardComment, } from './store.js';
42
45
  import { rejectValue, unrejectValue, listRejectionsForTenant } from './reject-flow.js';
43
46
  import { RejectedValueError } from './rejection.js';
44
47
  import { isHandoffOutcome, formatHandoffEvidenceLine } from './handoff.js';
45
48
  import { isCardStatus } from './card.js';
46
49
  import { loadCardDetail } from './card-detail.js';
47
50
  import { passesScopeFilterForRecall } from './recall-scope.js';
48
- import { markRetrieved, hybridSearch, physicsSearch, explainMatch, textOverlap, tokenize as tokenizeQuery } from './search.js';
51
+ import { estimateTokens, hybridSearch, physicsSearch, explainMatch, textOverlap, tokenize as tokenizeQuery } from './search.js';
49
52
  import { compareEntryIdentity } from './compare.js';
50
53
  import { renderTraceContent, parseSteps } from './trace.js';
51
54
  import { writeRecallTraceAtRoot } from './recall-trace.js';
@@ -56,6 +59,9 @@ import { loadPhysicsState, resetAllPhysicsState } from './physics-state.js';
56
59
  import { computeSystemEnergy, vecNorm } from './physics.js';
57
60
  import { loadConfig } from './config.js';
58
61
  import { openHippoDb, closeHippoDb } from './db.js';
62
+ import { runDoctor, formatDoctor } from './doctor.js';
63
+ import { captureToolFailure } from './capture-error.js';
64
+ import { blockHash, hookPayloadSessionId, lastSentState, recordTokenUse, shouldSkipUnchanged } from './token-ledger.js';
59
65
  import { pushGoal, getActiveGoals, completeGoal, suspendGoal, resumeGoal, applyGoalStackBoost } from './goals.js';
60
66
  import { rowToGoal } from './goals.js';
61
67
  import { captureError, extractLessons, partitionLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
@@ -65,7 +71,7 @@ import { detectScope } from './scope.js';
65
71
  import { getGlobalRoot, initGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
66
72
  import { DAILY_TASK_NAME, buildDailyRunnerCommand, listRegisteredWorkspaces, registerWorkspace, runDailyMaintenance, } from './scheduler.js';
67
73
  import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, importVault, } from './importers.js';
68
- import { cmdCapture, cmdPreCompact, resolveLastSessionTranscript, truncateCodePointSafe, sanitizeLogMessage } from './capture.js';
74
+ import { cmdCapture, cmdPreCompact, postCompactMessage, resolveLastSessionTranscript, truncateCodePointSafe, sanitizeLogMessage } from './capture.js';
69
75
  import { readStdinBounded } from './stdin.js';
70
76
  import { auditMemories, appendAuditEvent, } from './audit.js';
71
77
  import { listApiKeys, revokeApiKey } from './auth.js';
@@ -195,8 +201,8 @@ function requireInit(hippoRoot) {
195
201
  * H2: when HIPPO_REQUIRE_SERVER is set, the CLI must not silently fall back to
196
202
  * direct DB mode — a missing server then masks a real misconfiguration (the
197
203
  * configured HIPPO_API_KEY is also silently discarded on fallback). Throws a
198
- * clear, actionable error in that case; a no-op when the knob is unset, so
199
- * default behaviour is unchanged.
204
+ * clear error then. It guards only the routed writes (remember, forget, archive,
205
+ * promote); every other command opens the store directly, knob or not.
200
206
  */
201
207
  function failIfServerRequired(reason) {
202
208
  if (process.env['HIPPO_REQUIRE_SERVER']) {
@@ -263,6 +269,42 @@ export const BOOLEAN_FLAGS = new Set([
263
269
  'reset-physics', 'save-baseline', 'show-cases', 'stats', 'stdin',
264
270
  'strict', 'suite', 'value-aware', 'verified', 'version', 'why',
265
271
  ]);
272
+ // Every flag some command reads. Anything else is a typo that no command would act on.
273
+ export const KNOWN_FLAGS = new Set([
274
+ ...BOOLEAN_FLAGS,
275
+ 'actual', 'artifact', 'artifact-ref', 'as-of', 'author', 'baseline', 'body', 'budget', 'card-id',
276
+ 'change', 'channel', 'chatgpt', 'class', 'claude', 'codex-home', 'compare', 'constraint', 'content',
277
+ 'context', 'contract', 'cursor', 'customer', 'days', 'depends-on', 'depth', 'description',
278
+ 'embedding-weight', 'entity', 'estimate', 'file', 'format', 'framing', 'fresh-tail', 'from', 'goal',
279
+ 'graph-hops', 'graph-seeds', 'history-path', 'hops', 'host', 'id', 'importance', 'include-recent',
280
+ 'instructions', 'keep', 'kind', 'label', 'layer', 'level', 'limit', 'link', 'local-bump', 'log-file',
281
+ 'markdown', 'max', 'max-cases', 'max-neighbors', 'min-mrr', 'min-results', 'min-score', 'mmr-lambda',
282
+ 'model', 'name', 'next', 'next-step', 'note', 'older-than', 'op', 'out', 'outcome', 'owner', 'parent',
283
+ 'path', 'policy', 'port', 'reason', 'repo', 'repos', 'reranker', 'reranker-top-k', 'resolution',
284
+ 'role', 'run', 'runtime', 'salience-threshold', 'scan', 'scope', 'session', 'session-id', 'since',
285
+ 'source', 'start-offset', 'started-at', 'state', 'status', 'step', 'steps', 'success', 'summary',
286
+ 'supersedes', 'tag', 'target', 'target-runtime', 'task', 'team', 'tenant', 'tenant-id', 'tests',
287
+ 'text', 'threshold', 'title', 'to', 'transcript', 'trigger', 'type', 'unit', 'value', 'vault',
288
+ ]);
289
+ // Commands that delete or hide memories: an unknown flag here stops the run instead of being ignored.
290
+ const DESTRUCTIVE_COMMANDS = new Set([
291
+ 'audit', 'dedup', 'forget', 'invalidate', 'reject', 'resolve', 'sleep', 'supersede',
292
+ ]);
293
+ // Commands that honour --dry-run. Any other command would ignore it and run for real.
294
+ const DRY_RUN_COMMANDS = new Set([
295
+ 'audit', 'capture', 'dedup', 'forget', 'import', 'invalidate', 'refine', 'setup', 'sleep',
296
+ ]);
297
+ // share and brief honour --dry-run in one form only; their other forms write for real.
298
+ function dryRunRefusal(command, args, flags) {
299
+ const isBrief = command === 'brief' || command === 'project-brief';
300
+ const onlyForm = command === 'share' ? 'share --auto' : isBrief ? `${command} refresh` : null;
301
+ const honoured = command === 'share' ? args[0] === '--auto' || Boolean(flags['auto'])
302
+ : isBrief ? args[0] === 'refresh' : DRY_RUN_COMMANDS.has(command);
303
+ if (honoured)
304
+ return null;
305
+ const where = onlyForm ? ` outside \`hippo ${onlyForm}\`` : '';
306
+ return `hippo ${command} has no --dry-run${where}, so it would run for real. Nothing was changed.`;
307
+ }
266
308
  // Shared by both the separated and glued (`=`) forms so the list can't drift.
267
309
  function isRepeatableFlag(key) {
268
310
  return key === 'tag' || key === 'artifact' || key === 'link' || key === 'step' || key === 'constraint' || key === 'depends-on';
@@ -506,17 +548,17 @@ function autoInstallHooks(quiet) {
506
548
  // Skip if we already installed a hook into this file
507
549
  if (installed.has(targetPath))
508
550
  continue;
509
- // Skip if hook already present
510
- if (fs.existsSync(targetPath)) {
511
- const content = fs.readFileSync(targetPath, 'utf8');
512
- if (content.includes(HOOK_MARKERS.start))
513
- continue;
514
- }
551
+ // An instruction block already present is not rewritten, but the JSON
552
+ // hooks and plugins below still run: they are idempotent, and skipping
553
+ // them meant a store set up by an older hippo never got hooks added in
554
+ // later releases (PreCompact, PostToolUseFailure) on a re-run of init.
555
+ const blockPresent = fs.existsSync(targetPath)
556
+ && fs.readFileSync(targetPath, 'utf8').includes(HOOK_MARKERS.start);
515
557
  // Only patch the agent-instructions file if it already exists.
516
558
  // Never create a new CLAUDE.md / AGENTS.md / etc. just because a sibling
517
559
  // marker file (.claude/settings.json, .codex, etc.) was detected — that
518
560
  // pollutes dirs the user didn't intend to configure.
519
- if (fs.existsSync(targetPath)) {
561
+ if (!blockPresent && fs.existsSync(targetPath)) {
520
562
  const block = `${HOOK_MARKERS.start}\n${hookDef.content}\n${HOOK_MARKERS.end}`;
521
563
  const existing = fs.readFileSync(targetPath, 'utf8');
522
564
  const sep = existing.endsWith('\n') ? '\n' : '\n\n';
@@ -527,7 +569,7 @@ function autoInstallHooks(quiet) {
527
569
  // The Codex session-capture wrapper swaps the codex launcher binary, so
528
570
  // init never installs it silently — it points at the explicit opt-in
529
571
  // command instead (issue #133).
530
- if (hook === 'codex' && !isCodexWrapperInstalled()) {
572
+ if (hook === 'codex' && !blockPresent && !isCodexWrapperInstalled()) {
531
573
  console.log(' Codex detected. To capture Codex sessions: hippo hook install codex');
532
574
  }
533
575
  // For Claude Code, also install SessionEnd+SessionStart entries in its
@@ -550,6 +592,12 @@ function autoInstallHooks(quiet) {
550
592
  if (result.installedCompactResume) {
551
593
  console.log(` Auto-installed hippo compact-resume SessionStart(compact) hook in ${hook} settings`);
552
594
  }
595
+ if (result.installedPostCompact) {
596
+ console.log(` Auto-installed hippo post-compact PostCompact hook in ${hook} settings`);
597
+ }
598
+ if (result.installedCaptureError) {
599
+ console.log(` Auto-installed hippo capture-error PostToolUseFailure hook in ${hook} settings`);
600
+ }
553
601
  if (result.migratedFromStop) {
554
602
  console.log(` Migrated legacy Stop hook → SessionEnd (no longer runs every turn)`);
555
603
  }
@@ -658,7 +706,7 @@ async function cmdRemember(hippoRoot, text, flags) {
658
706
  if (flags['verified'])
659
707
  confidence = 'verified';
660
708
  // Compute schema fit against existing memories
661
- const existing = loadAllEntries(targetRoot);
709
+ const existing = loadAllEntries(targetRoot, resolveTenantId({}));
662
710
  const schemaFit = computeSchemaFit(text, rawTags, existing);
663
711
  // A3 envelope flags
664
712
  const kindFlagRaw = typeof flags['kind'] === 'string' ? flags['kind'] : undefined;
@@ -688,6 +736,7 @@ async function cmdRemember(hippoRoot, text, flags) {
688
736
  // A5 stub auth: stamp tenant_id from env (HIPPO_TENANT) so recall isolation
689
737
  // can filter on this row. Default tenant 'default' for unauthenticated CLI.
690
738
  const tenantId = resolveTenantId({});
739
+ const rememberConfig = loadConfig(targetRoot);
691
740
  const entry = createMemory(text, {
692
741
  layer: Layer.Episodic,
693
742
  tags: rawTags,
@@ -700,6 +749,7 @@ async function cmdRemember(hippoRoot, text, flags) {
700
749
  owner: ownerFlag,
701
750
  artifact_ref: artifactRefFlag,
702
751
  tenantId,
752
+ baseHalfLifeDays: rememberConfig.defaultHalfLifeDays,
703
753
  });
704
754
  // Auto-tag with path context
705
755
  const pathTags = extractPathTags(process.cwd());
@@ -716,7 +766,6 @@ async function cmdRemember(hippoRoot, text, flags) {
716
766
  entry.tags.push(scopeTag);
717
767
  }
718
768
  // Salience gate: decide if this memory is worth storing
719
- const rememberConfig = loadConfig(targetRoot);
720
769
  if (rememberConfig.salience.enabled && !Boolean(flags['pin']) && !Boolean(flags['force'])) {
721
770
  const salienceResult = computeSalience(text, entry.tags, existing, {
722
771
  recentWindow: rememberConfig.salience.recentWindow,
@@ -758,14 +807,16 @@ async function cmdRemember(hippoRoot, text, flags) {
758
807
  const facts = await extractFacts(entry.content, {
759
808
  apiKey,
760
809
  model: config.extraction.model,
810
+ onError: (msg) => console.error(` (extraction failed: ${msg})`),
761
811
  });
762
812
  if (facts.length > 0) {
763
813
  storeExtractedFacts(targetRoot, entry, facts);
764
814
  console.error(` extracted ${facts.length} fact(s)`);
765
815
  }
766
816
  }
767
- catch {
768
- // Extraction is best-effort — never block remember
817
+ catch (err) {
818
+ // Extraction is best-effort: report it, never block remember.
819
+ console.error(` (extraction failed: ${err instanceof Error ? err.message : String(err)})`);
769
820
  }
770
821
  }
771
822
  else if (shouldExtract && !apiKey) {
@@ -797,6 +848,7 @@ function cmdSupersede(hippoRoot, oldId, newContent, flags) {
797
848
  pinned,
798
849
  source: old.source,
799
850
  confidence: 'verified',
851
+ tenantId: old.tenantId,
800
852
  });
801
853
  // AT1: write the SUCCESSOR first. The rejection guard fires on the new
802
854
  // content — if it refuses, nothing has been mutated yet (the old ordering
@@ -854,8 +906,9 @@ async function cmdRecall(hippoRoot, query, flags) {
854
906
  // detected-project recalls would change shape.
855
907
  const recallExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
856
908
  const requestedScopeForFilter = recallExplicitScope || undefined;
857
- let localEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive');
858
- let globalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive') : [];
909
+ const loadSuperseded = includeSuperseded || Boolean(asOf);
910
+ let localEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive', loadSuperseded);
911
+ let globalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive', loadSuperseded) : [];
859
912
  // v1.12.13 / C5 — WYSIATI counters. Track filter activity per the plan v3
860
913
  // Task 3 mapping table. dropped_pre_rank is the SUM of all non-budget
861
914
  // filter drops (pre-rank AND post-rank); its meaning is unchanged by C5.
@@ -1616,7 +1669,7 @@ async function cmdRecall(hippoRoot, query, flags) {
1616
1669
  sessionHandoff =
1617
1670
  rawHandoff && passesScopeFilterForRecall(rowScope(rawHandoff), effectiveScope) ? rawHandoff : null;
1618
1671
  recentSessionEvents = rawEvents.filter((e) => passesScopeFilterForRecall(rowScope(e), effectiveScope));
1619
- const tokenize = (s) => s ? Math.ceil(s.length / 4) : 0;
1672
+ const tokenize = (s) => s ? estimateTokens(s) : 0;
1620
1673
  continuityTokens =
1621
1674
  tokenize(activeSnapshot?.task) +
1622
1675
  tokenize(activeSnapshot?.summary) +
@@ -1634,6 +1687,16 @@ async function cmdRecall(hippoRoot, query, flags) {
1634
1687
  const hasContinuity = activeSnapshot !== null
1635
1688
  || sessionHandoff !== null
1636
1689
  || recentSessionEvents.length > 0;
1690
+ // TE0 token ledger: the memory text this recall hands back (results plus
1691
+ // any continuity block), recorded once per query.
1692
+ withLedgerDb(hippoRoot, (db) => recordTokenUse(db, {
1693
+ tenantId,
1694
+ sessionId: hostSessionId() ?? null,
1695
+ surface: 'recall',
1696
+ event: 'inject',
1697
+ items: results.length,
1698
+ tokens: results.reduce((acc, r) => acc + (r.tokens || 0), 0) + continuityTokens,
1699
+ }));
1637
1700
  if (results.length === 0) {
1638
1701
  // LC1 F1 structural fix (docs/plans/2026-08-02-lc1-recall-trace-persistence.md):
1639
1702
  // trace the zero-result recall too (result_count 0, no result rows) so
@@ -1725,21 +1788,13 @@ async function cmdRecall(hippoRoot, query, flags) {
1725
1788
  console.log('No memories found for:', query);
1726
1789
  return;
1727
1790
  }
1728
- // Update retrieval metadata and persist
1729
- const updated = markRetrieved(results.map((r) => r.entry));
1791
+ const retrievedIds = results.map((r) => r.entry.id);
1730
1792
  const localIndex = loadIndex(hippoRoot);
1731
- // EVAL-ONLY ablation (see ablation.ts): under the recall flag, markRetrieved
1732
- // returns unmutated entries (ids preserved for outcome attribution below)
1733
- // and persistence is skipped - writeEntry on identical rows still refreshes
1734
- // updated_at, rewrites mirrors, and marks DAG parents dirty.
1735
- if (!isRecallBoostAblated()) {
1736
- for (const u of updated) {
1737
- const targetRoot = localIndex.entries[u.id] ? hippoRoot : (isInitialized(globalRoot) ? globalRoot : hippoRoot);
1738
- writeEntry(targetRoot, u);
1739
- }
1740
- }
1793
+ const strengthenedHere = strengthenRetrieved(hippoRoot, retrievedIds);
1794
+ if (isInitialized(globalRoot))
1795
+ strengthenRetrieved(globalRoot, retrievedIds.filter((id) => !strengthenedHere.has(id)));
1741
1796
  // Track last retrieval IDs for outcome command
1742
- localIndex.last_retrieval_ids = updated.map((u) => u.id);
1797
+ localIndex.last_retrieval_ids = retrievedIds;
1743
1798
  // LC1 F1 structural fix (docs/plans/2026-08-02-lc1-recall-trace-persistence.md):
1744
1799
  // ONE trace at hippoRoot (where last_retrieval_ids and outcome
1745
1800
  // attribution live). Write the trace FIRST, then fold its id into
@@ -1971,8 +2026,9 @@ async function cmdExplain(hippoRoot, query, flags) {
1971
2026
  // command honest for an operator debugging a hidden row.
1972
2027
  const explainExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
1973
2028
  const explainRequestedScope = explainExplicitScope || undefined;
1974
- let explainLocalEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, explainRequestedScope, 'additive');
1975
- let explainGlobalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, explainRequestedScope, 'additive') : [];
2029
+ const explainLoadSuperseded = explainIncludeSuperseded || Boolean(explainAsOf);
2030
+ let explainLocalEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, explainRequestedScope, 'additive', explainLoadSuperseded);
2031
+ let explainGlobalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, explainRequestedScope, 'additive', explainLoadSuperseded) : [];
1976
2032
  const passesExplainScope = (e) => api.passesCliRecallScopeFilter(e.scope ?? null, explainRequestedScope);
1977
2033
  explainLocalEntries = explainLocalEntries.filter(passesExplainScope);
1978
2034
  explainGlobalEntries = explainGlobalEntries.filter(passesExplainScope);
@@ -2376,6 +2432,7 @@ function cmdTraceRecord(hippoRoot, flags) {
2376
2432
  source: String(flags['source'] ?? 'cli'),
2377
2433
  trace_outcome: outcome,
2378
2434
  source_session_id: sessionId,
2435
+ tenantId: resolveTenantId({}),
2379
2436
  });
2380
2437
  writeEntry(hippoRoot, entry);
2381
2438
  console.log(`Recorded trace ${entry.id} (outcome=${outcome}, ${steps.length} steps)`);
@@ -2540,7 +2597,7 @@ export function learnFromMemoryMd(hippoRoot, homeDir = os.homedir()) {
2540
2597
  }
2541
2598
  if (memoryDirs.length === 0)
2542
2599
  return 0;
2543
- const existing = loadAllEntries(hippoRoot);
2600
+ const existing = loadAllEntries(hippoRoot, resolveTenantId({}));
2544
2601
  let imported = 0;
2545
2602
  let skippedSecret = 0;
2546
2603
  // AT1 (plan §3 containment): a rejection guard refusal is per-VALUE — one
@@ -2583,6 +2640,7 @@ export function learnFromMemoryMd(hippoRoot, homeDir = os.homedir()) {
2583
2640
  tags: ['claude-code-memory'],
2584
2641
  source: `claude-memory:${file}`,
2585
2642
  confidence: 'observed',
2643
+ tenantId: resolveTenantId({}),
2586
2644
  });
2587
2645
  try {
2588
2646
  writeEntry(hippoRoot, entry);
@@ -2712,6 +2770,14 @@ export function renderSleepResult(result) {
2712
2770
  console.log(`\nResults:`);
2713
2771
  console.log(` Active memories: ${result.active}`);
2714
2772
  console.log(` Removed (decayed): ${result.removed}`);
2773
+ // Only when dormant.enabled moved something, so every other render stays
2774
+ // byte-identical (tests/cli-context-render-snapshot.test.ts).
2775
+ if (result.dormant !== undefined && result.dormant > 0) {
2776
+ console.log(` Kept dormant: ${result.dormant} (hippo dormant to list)`);
2777
+ }
2778
+ if (result.dormantExpired !== undefined && result.dormantExpired > 0) {
2779
+ console.log(` Expired dormant: ${result.dormantExpired} (past dormant.retentionDays)`);
2780
+ }
2715
2781
  console.log(` Merged episodic: ${result.mergedEpisodic}`);
2716
2782
  console.log(` New semantic: ${result.newSemantic}`);
2717
2783
  if (result.details && result.details.length > 0) {
@@ -2731,11 +2797,11 @@ export function renderSleepResult(result) {
2731
2797
  parts.push(`${epiDups} duplicate episodic lessons`);
2732
2798
  if (crossDups > 0)
2733
2799
  parts.push(`${crossDups} cross-layer duplicates`);
2734
- console.log(`\nDeduped ${removed} duplicates (${parts.join(', ')}). Kept stronger copies.`);
2800
+ console.log(`\n${result.dryRun ? 'Would dedupe' : 'Deduped'} ${removed} duplicates (${parts.join(', ')}). ${result.dryRun ? 'Would keep' : 'Kept'} stronger copies.`);
2735
2801
  }
2736
2802
  if (result.audit) {
2737
2803
  if (result.audit.errorsRemoved > 0) {
2738
- console.log(`\nAudit: removed ${result.audit.errorsRemoved} junk memories (too short/empty).`);
2804
+ console.log(`\nAudit: ${result.dryRun ? 'would remove' : 'removed'} ${result.audit.errorsRemoved} junk memories (too short/empty).`);
2739
2805
  }
2740
2806
  if (result.audit.warningCount > 0) {
2741
2807
  console.log(`Audit: ${result.audit.warningCount} low-quality memories detected (run \`hippo audit\` for details).`);
@@ -2760,7 +2826,10 @@ async function cmdSleepCore(hippoRoot, flags) {
2760
2826
  requireInit(hippoRoot);
2761
2827
  // Phase 1: Auto-learn from git + MEMORY.md (CLI-only, uses process.cwd() / os.homedir()).
2762
2828
  // Stays in cli.ts; api.sleep covers Phase 2-6 only.
2763
- if (!flags['no-learn']) {
2829
+ if (!flags['no-learn'] && flags['dry-run']) {
2830
+ console.log('Dry run: skipped learning from git commits and MEMORY.md files.');
2831
+ }
2832
+ else if (!flags['no-learn']) {
2764
2833
  const config = loadConfig(hippoRoot);
2765
2834
  if (config.autoLearnOnSleep && isGitRepo(process.cwd())) {
2766
2835
  const { added } = learnFromRepo(hippoRoot, process.cwd(), 1);
@@ -3227,13 +3296,13 @@ async function cmdCodexSessionEndWorker(hippoRoot, flags) {
3227
3296
  // capture path logs its own failures
3228
3297
  }
3229
3298
  }
3230
- function shouldAutoRepairCodexWrapper(currentCommand, currentArgs) {
3299
+ export function shouldAutoRepairCodexWrapper(currentCommand, flags) {
3231
3300
  if (process.env.HIPPO_SKIP_AUTO_INTEGRATIONS === '1')
3232
3301
  return false;
3233
3302
  if (!['context', 'remember', 'recall', 'sleep', 'capture', 'outcome', 'status', 'init'].includes(currentCommand)) {
3234
3303
  return false;
3235
3304
  }
3236
- if (currentCommand === 'init' && currentArgs.includes('--no-hooks'))
3305
+ if (currentCommand === 'init' && flags['no-hooks'] === true)
3237
3306
  return false;
3238
3307
  return true;
3239
3308
  }
@@ -3242,8 +3311,8 @@ function shouldAutoRepairCodexWrapper(currentCommand, currentArgs) {
3242
3311
  // shim). Never first-installs — silently swapping the codex binary on routine
3243
3312
  // commands is a consent violation and reads as binary hijacking to
3244
3313
  // supply-chain scanners (issue #133).
3245
- function maybeRepairCodexWrapper(currentCommand, currentArgs) {
3246
- if (!shouldAutoRepairCodexWrapper(currentCommand, currentArgs))
3314
+ function maybeRepairCodexWrapper(currentCommand, flags) {
3315
+ if (!shouldAutoRepairCodexWrapper(currentCommand, flags))
3247
3316
  return;
3248
3317
  try {
3249
3318
  repairCodexWrapperIfInstalled();
@@ -3465,8 +3534,13 @@ function cmdForget(hippoRoot, id, flags) {
3465
3534
  if (/append-only/i.test(msg)) {
3466
3535
  // The delete was refused by the append-only trigger — this is a raw
3467
3536
  // memory, not a missing one. Point the user at the archive path.
3468
- console.error(`Cannot forget ${id}: it is a raw, append-only memory. ` +
3469
- `Archive it instead: hippo forget ${id} --archive --reason "<why>"`);
3537
+ console.error(rawForgetRefusal(id));
3538
+ }
3539
+ else if (api.isDormant(ctx, id)) {
3540
+ // Sleep moved it to the dormant store: it is not in active memory, so
3541
+ // point at the command that owns it.
3542
+ console.error(`${id} is dormant, not in active memory. Delete it for good: hippo dormant forget ${id} ` +
3543
+ `(or bring it back: hippo dormant restore ${id})`);
3470
3544
  }
3471
3545
  else {
3472
3546
  console.error(`Memory not found: ${id}`);
@@ -3474,6 +3548,28 @@ function cmdForget(hippoRoot, id, flags) {
3474
3548
  process.exit(1);
3475
3549
  }
3476
3550
  }
3551
+ function rawForgetRefusal(id) {
3552
+ return `Cannot forget ${id}: it is a raw, append-only memory. Archive it instead: hippo forget ${id} --archive --reason "<why>"`;
3553
+ }
3554
+ // Refuses exactly where the real run would, so "Would forget" is a promise, not a guess.
3555
+ function previewForget(hippoRoot, id, archive) {
3556
+ requireInit(hippoRoot);
3557
+ const entry = readEntry(hippoRoot, id, resolveTenantId({}));
3558
+ if (!entry) {
3559
+ console.error(`Memory not found: ${id}`);
3560
+ process.exit(1);
3561
+ }
3562
+ if (!archive && entry.kind === 'raw') {
3563
+ console.error(rawForgetRefusal(id));
3564
+ process.exit(1);
3565
+ }
3566
+ if (archive && entry.kind !== 'raw') {
3567
+ console.error(`Could not archive ${id}: memory ${id} is not raw (kind=${entry.kind})`);
3568
+ process.exit(1);
3569
+ }
3570
+ const snippet = entry.content.length > 80 ? `${entry.content.slice(0, 80)}...` : entry.content;
3571
+ console.log(`Would ${archive ? 'archive' : 'forget'} ${id} (dry run, nothing changed): "${snippet}"`);
3572
+ }
3477
3573
  function cmdInspect(hippoRoot, id) {
3478
3574
  requireInit(hippoRoot);
3479
3575
  const entry = readEntry(hippoRoot, id, resolveTenantId({}));
@@ -3729,6 +3825,110 @@ function cmdUnreject(hippoRoot, args, flags) {
3729
3825
  }
3730
3826
  console.log(`Unrejected [${outcome.digest.slice(0, 12)}...] (was: ${outcome.reason ?? 'none given'})`);
3731
3827
  }
3828
+ /**
3829
+ * `hippo dormant [list] [<query>...] [--limit <n>] [--json] [--global]`,
3830
+ * `hippo dormant restore <id>`, `hippo dormant forget <id>`.
3831
+ * Dormant memories are what sleep keeps instead of deleting (on by default;
3832
+ * `"dormant": { "enabled": false }` in .hippo/config.json deletes instead).
3833
+ */
3834
+ function cmdDormant(hippoRoot, args, flags) {
3835
+ const root = resolveAuthRoot(hippoRoot, flags);
3836
+ const ctx = {
3837
+ hippoRoot: root,
3838
+ tenantId: resolveTenantId({}),
3839
+ actor: api.adminActor('cli'),
3840
+ };
3841
+ const sub = args[0];
3842
+ if (sub === 'restore' || sub === 'forget') {
3843
+ const id = (args[1] ?? '').trim();
3844
+ if (!id) {
3845
+ console.error(`Usage: hippo dormant ${sub} <id>`);
3846
+ process.exit(1);
3847
+ }
3848
+ try {
3849
+ if (sub === 'restore') {
3850
+ api.restoreDormant(ctx, id);
3851
+ console.log(`Restored ${id} to active memory.`);
3852
+ }
3853
+ else {
3854
+ api.forgetDormant(ctx, id);
3855
+ console.log(`Forgot dormant memory ${id} permanently.`);
3856
+ }
3857
+ }
3858
+ catch (err) {
3859
+ if (err instanceof RejectedValueError) {
3860
+ console.error(`Cannot restore ${id}: its value was rejected (${err.reason ?? 'no reason given'}). Run \`hippo unreject\` first to allow it.`);
3861
+ }
3862
+ else {
3863
+ console.error(`Could not ${sub} ${id}: ${err instanceof Error ? err.message : String(err)}`);
3864
+ }
3865
+ process.exit(1);
3866
+ }
3867
+ return;
3868
+ }
3869
+ const queryArgs = sub === 'list' ? args.slice(1) : args;
3870
+ const limit = parseCountFlag(flags['limit']);
3871
+ const rows = api.listDormant(ctx, {
3872
+ query: queryArgs.join(' '),
3873
+ limit: limit > 0 ? limit : undefined,
3874
+ });
3875
+ if (flags['json']) {
3876
+ console.log(JSON.stringify({ dormant: rows }, null, 2));
3877
+ return;
3878
+ }
3879
+ if (rows.length === 0) {
3880
+ console.log(queryArgs.length > 0 ? 'No dormant memories match.' : 'No dormant memories.');
3881
+ if (!loadConfig(root).dormant.enabled) {
3882
+ console.log(`Sleep deletes faded memories. To keep them dormant instead, set "dormant": { "enabled": true } in ${path.join(root, 'config.json')}.`);
3883
+ }
3884
+ return;
3885
+ }
3886
+ console.log(`${rows.length} dormant memor${rows.length === 1 ? 'y' : 'ies'}${queryArgs.length > 0 ? ' matching' : ''} (newest first):\n`);
3887
+ for (const row of rows) {
3888
+ const preview = row.content.length > 100 ? `${row.content.slice(0, 100)}...` : row.content;
3889
+ console.log(`--- ${row.id}`);
3890
+ console.log(` ${preview}`);
3891
+ console.log(` Dormant since ${row.dormantAt.slice(0, 10)} (${row.reason}, strength ${row.strength.toFixed(3)})${row.tags.length > 0 ? ` tags: ${row.tags.join(', ')}` : ''}`);
3892
+ console.log('');
3893
+ }
3894
+ console.log('Bring one back: hippo dormant restore <id> Delete for good: hippo dormant forget <id>');
3895
+ }
3896
+ /**
3897
+ * `hippo tokens [--days <n>] [--json] [--global]`: the token ledger
3898
+ * (ROADMAP TE0). Tokens of memory text handed to agents per surface, blocks
3899
+ * the per-prompt hook skipped as unchanged (TE2) and the tokens that saved.
3900
+ * Counts are estimates (characters / 4), the same estimate every budget uses.
3901
+ */
3902
+ function cmdTokens(hippoRoot, flags) {
3903
+ const root = resolveAuthRoot(hippoRoot, flags);
3904
+ const ctx = {
3905
+ hippoRoot: root,
3906
+ tenantId: resolveTenantId({}),
3907
+ actor: api.adminActor('cli'),
3908
+ };
3909
+ const days = parseCountFlag(flags['days']);
3910
+ const summary = api.tokenSummary(ctx, { days: days > 0 ? days : undefined });
3911
+ if (flags['json']) {
3912
+ console.log(JSON.stringify(summary, null, 2));
3913
+ return;
3914
+ }
3915
+ const windowDays = days > 0 ? days : 30;
3916
+ if (summary.surfaces.length === 0) {
3917
+ console.log(`No memory text recorded in the last ${windowDays} days.`);
3918
+ return;
3919
+ }
3920
+ console.log(`Memory text handed to agents, last ${windowDays} days (estimated tokens)\n`);
3921
+ console.log(` ${'surface'.padEnd(14)}${'sent'.padStart(8)}${'tokens'.padStart(12)}${'skipped'.padStart(10)}${'saved'.padStart(12)}`);
3922
+ for (const row of summary.surfaces) {
3923
+ console.log(` ${row.surface.padEnd(14)}${String(row.injected).padStart(8)}${String(row.tokens).padStart(12)}`
3924
+ + `${String(row.skipped).padStart(10)}${String(row.tokensAvoided).padStart(12)}`);
3925
+ }
3926
+ console.log('');
3927
+ console.log(` Total sent: ${summary.totalTokens} tokens. Saved by skipping unchanged blocks: ${summary.totalTokensAvoided}.`);
3928
+ if (summary.meanTokensPerSession > 0) {
3929
+ console.log(` Mean per session (rows with a session id): ${summary.meanTokensPerSession} tokens.`);
3930
+ }
3931
+ }
3732
3932
  function cmdSnapshot(hippoRoot, args, flags) {
3733
3933
  requireInit(hippoRoot);
3734
3934
  const subcommand = args[0] ?? 'show';
@@ -6057,22 +6257,24 @@ async function cmdContext(hippoRoot, args, flags, stdinText) {
6057
6257
  origin: r.origin ?? null,
6058
6258
  category: r.category ?? null,
6059
6259
  }));
6060
- console.log(JSON.stringify({
6260
+ const jsonText = JSON.stringify({
6061
6261
  query: query || '*',
6062
6262
  activeSnapshot: result.activeSnapshot ?? null,
6063
6263
  sessionHandoff: result.sessionHandoff ?? null,
6064
6264
  recentSessionEvents: result.recentEvents ?? [],
6065
6265
  memories: output,
6066
6266
  tokens: result.tokens,
6267
+ });
6268
+ console.log(jsonText);
6269
+ withLedgerDb(hippoRoot, (db) => recordTokenUse(db, {
6270
+ tenantId: ctx.tenantId, sessionId: currentSessionId, surface: pinnedOnly ? 'hook' : 'context',
6271
+ event: 'inject', items: output.length, tokens: estimateTokens(jsonText),
6067
6272
  }));
6068
6273
  }
6069
6274
  else if (format === 'additional-context') {
6070
6275
  // Claude Code UserPromptSubmit hook JSON shape. Capture print* helpers'
6071
6276
  // output into a string buffer and wrap as `additionalContext`.
6072
- const lines = [];
6073
- const realLog = console.log;
6074
- console.log = (...parts) => { lines.push(parts.map(String).join(' ')); };
6075
- try {
6277
+ const textBlock = captureConsole(() => {
6076
6278
  if (result.activeSnapshot)
6077
6279
  printActiveTaskSnapshot(result.activeSnapshot);
6078
6280
  if (result.sessionHandoff)
@@ -6081,16 +6283,37 @@ async function cmdContext(hippoRoot, args, flags, stdinText) {
6081
6283
  printSessionEvents(result.recentEvents);
6082
6284
  }
6083
6285
  if (renderItems.length > 0) {
6084
- printContextMarkdown(renderItems, result.tokens, framing);
6286
+ // TE1: no live strength percentage, so an unchanged set of memories
6287
+ // renders byte-identically turn after turn.
6288
+ printContextMarkdown(renderItems, result.tokens, framing, { showStrength: false });
6085
6289
  }
6086
6290
  printCrossProjectSection(crossEntries);
6087
- }
6088
- finally {
6089
- console.log = realLog;
6090
- }
6091
- const textBlock = lines.join('\n');
6291
+ });
6092
6292
  if (!textBlock.trim())
6093
6293
  return;
6294
+ const surface = pinnedOnly ? 'hook' : 'context';
6295
+ const hash = blockHash(textBlock);
6296
+ const tokens = estimateTokens(textBlock);
6297
+ // TE2: the per-prompt hook skips a block identical to the one this
6298
+ // session already has, and resends it every refreshTurns skips. Only
6299
+ // with a session id from the hook payload itself: an inherited env id
6300
+ // (a manual run inside an agent's shell) must never suppress output.
6301
+ if (pinnedOnly && payloadSessionId !== undefined) {
6302
+ const injectCfg = loadConfig(hippoRoot).pinnedInject;
6303
+ if (injectCfg.skipUnchanged !== false) {
6304
+ const refreshTurns = Number.isFinite(injectCfg.refreshTurns) && injectCfg.refreshTurns >= 0
6305
+ ? injectCfg.refreshTurns
6306
+ : 10;
6307
+ const last = withLedgerDb(hippoRoot, (db) => lastSentState(db, ctx.tenantId, payloadSessionId, surface));
6308
+ if (shouldSkipUnchanged(last ?? null, hash, refreshTurns)) {
6309
+ withLedgerDb(hippoRoot, (db) => recordTokenUse(db, {
6310
+ tenantId: ctx.tenantId, sessionId: payloadSessionId, surface, event: 'skip',
6311
+ items: renderItems.length, tokens, hash,
6312
+ }));
6313
+ return;
6314
+ }
6315
+ }
6316
+ }
6094
6317
  const payload = {
6095
6318
  hookSpecificOutput: {
6096
6319
  hookEventName: 'UserPromptSubmit',
@@ -6098,25 +6321,113 @@ async function cmdContext(hippoRoot, args, flags, stdinText) {
6098
6321
  },
6099
6322
  };
6100
6323
  process.stdout.write(JSON.stringify(payload));
6324
+ withLedgerDb(hippoRoot, (db) => recordTokenUse(db, {
6325
+ tenantId: ctx.tenantId, sessionId: currentSessionId, surface, event: 'inject',
6326
+ items: renderItems.length, tokens, hash,
6327
+ }));
6101
6328
  }
6102
6329
  else {
6103
6330
  // markdown (default)
6104
- if (result.activeSnapshot) {
6105
- printActiveTaskSnapshot(result.activeSnapshot);
6106
- }
6107
- if (result.sessionHandoff) {
6108
- printHandoff(result.sessionHandoff);
6109
- }
6110
- if (result.recentEvents && result.recentEvents.length > 0) {
6111
- printSessionEvents(result.recentEvents);
6112
- }
6113
- if (renderItems.length > 0) {
6114
- printContextMarkdown(renderItems, result.tokens, framing);
6115
- }
6116
- printCrossProjectSection(crossEntries);
6117
- if (result.ambientState) {
6118
- console.log(`\n${renderAmbientSummary(result.ambientState)}`);
6119
- }
6331
+ const text = captureConsole(() => {
6332
+ if (result.activeSnapshot) {
6333
+ printActiveTaskSnapshot(result.activeSnapshot);
6334
+ }
6335
+ if (result.sessionHandoff) {
6336
+ printHandoff(result.sessionHandoff);
6337
+ }
6338
+ if (result.recentEvents && result.recentEvents.length > 0) {
6339
+ printSessionEvents(result.recentEvents);
6340
+ }
6341
+ if (renderItems.length > 0) {
6342
+ printContextMarkdown(renderItems, result.tokens, framing);
6343
+ }
6344
+ printCrossProjectSection(crossEntries);
6345
+ if (result.ambientState) {
6346
+ console.log(`\n${renderAmbientSummary(result.ambientState)}`);
6347
+ }
6348
+ });
6349
+ if (text.length > 0)
6350
+ console.log(text);
6351
+ withLedgerDb(hippoRoot, (db) => recordTokenUse(db, {
6352
+ tenantId: ctx.tenantId, sessionId: currentSessionId, surface: pinnedOnly ? 'hook' : 'context',
6353
+ event: 'inject', items: renderItems.length, tokens: estimateTokens(text),
6354
+ }));
6355
+ }
6356
+ }
6357
+ /**
6358
+ * TE2: compaction drops the pinned blocks the per-prompt hook injected
6359
+ * earlier, so record a `reset` for the payload's session and the next prompt
6360
+ * injects again even if nothing changed. `requiredSource` limits it to hook
6361
+ * payloads with that `source` (SessionStart fires for other reasons too).
6362
+ * Best-effort and silent: a malformed payload records nothing.
6363
+ */
6364
+ function resetHookInjection(hippoRoot, stdinText, requiredSource) {
6365
+ const sessionId = hookPayloadSessionId(stdinText, requiredSource);
6366
+ if (sessionId === null)
6367
+ return;
6368
+ withLedgerDb(hippoRoot, (db) => recordTokenUse(db, {
6369
+ tenantId: resolveTenantId({}), sessionId, surface: 'hook', event: 'reset', items: 0, tokens: 0,
6370
+ }));
6371
+ }
6372
+ /**
6373
+ * Run `fn` with console.log captured; returns the captured lines joined by
6374
+ * newlines (what the same calls would have printed, minus the final newline).
6375
+ */
6376
+ function captureConsole(fn) {
6377
+ const lines = [];
6378
+ const realLog = console.log;
6379
+ console.log = (...parts) => { lines.push(parts.map(String).join(' ')); };
6380
+ try {
6381
+ fn();
6382
+ }
6383
+ finally {
6384
+ console.log = realLog;
6385
+ }
6386
+ return lines.join('\n');
6387
+ }
6388
+ /**
6389
+ * The store a Claude Code hook writes to: the project store when there is
6390
+ * one, else an existing global store, else the project path (which the hook
6391
+ * then skips, since hooks fire in every directory and must not create one).
6392
+ * Pre-compact and compact-resume must agree, or a snapshot saved to one store
6393
+ * is looked for in the other.
6394
+ */
6395
+ function hookStoreRoot(hippoRoot) {
6396
+ if (isInitialized(hippoRoot))
6397
+ return hippoRoot;
6398
+ const globalRoot = getGlobalRoot();
6399
+ return isInitialized(globalRoot) ? globalRoot : hippoRoot;
6400
+ }
6401
+ /**
6402
+ * Run `fn` against the token ledger's store: the local store when it is
6403
+ * initialized, else the global one (the per-prompt hook runs in directories
6404
+ * without a local store). Best-effort: returns undefined and never throws,
6405
+ * because a ledger failure must not break context or recall.
6406
+ */
6407
+ function withLedgerDb(hippoRoot, fn) {
6408
+ let root = null;
6409
+ try {
6410
+ if (isInitialized(hippoRoot))
6411
+ root = hippoRoot;
6412
+ else if (isInitialized(getGlobalRoot()))
6413
+ root = getGlobalRoot();
6414
+ }
6415
+ catch {
6416
+ return undefined;
6417
+ }
6418
+ if (root === null)
6419
+ return undefined;
6420
+ let db;
6421
+ try {
6422
+ db = openHippoDb(root);
6423
+ return fn(db);
6424
+ }
6425
+ catch {
6426
+ return undefined;
6427
+ }
6428
+ finally {
6429
+ if (db)
6430
+ closeHippoDb(db);
6120
6431
  }
6121
6432
  }
6122
6433
  /**
@@ -6135,13 +6446,15 @@ function printCrossProjectSection(items) {
6135
6446
  }
6136
6447
  }
6137
6448
  /** @internal — exported for snapshot tests (tests/cli-context-render-snapshot.test.ts). NOT a stable public API. */
6138
- export function printContextMarkdown(items, totalTokens, framing = 'observe') {
6449
+ export function printContextMarkdown(items, totalTokens, framing = 'observe', opts = {}) {
6139
6450
  const now = evalNow();
6451
+ const showStrength = opts.showStrength !== false;
6140
6452
  console.log(`## Project Memory (${items.length} entries, ${totalTokens} tokens)\n`);
6141
6453
  for (const item of items) {
6142
6454
  const e = item.entry;
6143
6455
  const tagStr = e.tags.length > 0 ? ` [${e.tags.join(', ')}]` : '';
6144
6456
  const strengthPct = Math.round(calculateStrength(e) * 100);
6457
+ const strengthStr = showStrength ? ` (${strengthPct}%)` : '';
6145
6458
  const globalPrefix = item.isGlobal ? '[global] ' : '';
6146
6459
  const effectiveConf = confidenceFacets(e, now).tier;
6147
6460
  const label = confidenceLabel(e, now);
@@ -6151,21 +6464,21 @@ export function printContextMarkdown(items, totalTokens, framing = 'observe') {
6151
6464
  const dateStr = new Date(e.created).toISOString().slice(0, 10);
6152
6465
  if (effectiveConf === 'verified') {
6153
6466
  // Verified: no date prefix, just the rule
6154
- console.log(`- **${confTag} ${globalPrefix}${e.content}**${tagStr} (${strengthPct}%)`);
6467
+ console.log(`- **${confTag} ${globalPrefix}${e.content}**${tagStr}${strengthStr}`);
6155
6468
  }
6156
6469
  else if (effectiveConf === 'stale') {
6157
- console.log(`- **${confTag} Previously observed (${dateStr}): ${globalPrefix}${e.content}**${tagStr} (${strengthPct}%)`);
6470
+ console.log(`- **${confTag} Previously observed (${dateStr}): ${globalPrefix}${e.content}**${tagStr}${strengthStr}`);
6158
6471
  }
6159
6472
  else {
6160
- console.log(`- **${confTag} Previously observed (${dateStr}): ${globalPrefix}${e.content}**${tagStr} (${strengthPct}%)`);
6473
+ console.log(`- **${confTag} Previously observed (${dateStr}): ${globalPrefix}${e.content}**${tagStr}${strengthStr}`);
6161
6474
  }
6162
6475
  }
6163
6476
  else if (framing === 'suggest') {
6164
- console.log(`- **${confTag} Consider checking: ${globalPrefix}${e.content}**${tagStr} (${strengthPct}%)`);
6477
+ console.log(`- **${confTag} Consider checking: ${globalPrefix}${e.content}**${tagStr}${strengthStr}`);
6165
6478
  }
6166
6479
  else {
6167
6480
  // framing === 'assert': no prefix (bare facts)
6168
- console.log(`- **${confTag} ${globalPrefix}${e.content}**${tagStr} (${strengthPct}%)`);
6481
+ console.log(`- **${confTag} ${globalPrefix}${e.content}**${tagStr}${strengthStr}`);
6169
6482
  }
6170
6483
  }
6171
6484
  }
@@ -6304,12 +6617,12 @@ async function cmdWatch(command, hippoRoot) {
6304
6617
  console.error('Command failed but .hippo not initialized. Run `hippo init` to enable auto-learn.');
6305
6618
  process.exit(exitCode);
6306
6619
  }
6307
- const entry = captureError(exitCode, stderr, command);
6620
+ const entry = captureError(exitCode, stderr, command, resolveTenantId({}));
6308
6621
  // Compute schema fit against existing memories
6309
- const existingWatch = loadAllEntries(hippoRoot);
6622
+ const existingWatch = loadAllEntries(hippoRoot, entry.tenantId);
6310
6623
  const watchFit = computeSchemaFit(entry.content, entry.tags, existingWatch);
6311
6624
  entry.schema_fit = watchFit;
6312
- entry.half_life_days = deriveHalfLife(7, entry);
6625
+ entry.half_life_days = deriveHalfLife(loadConfig(hippoRoot).defaultHalfLifeDays, entry);
6313
6626
  entry.strength = calculateStrength(entry);
6314
6627
  // AT1 (plan §3 containment): mechanical content from a failed command — a
6315
6628
  // rejection-guard refusal here must not crash the watcher. Skip silently
@@ -6348,7 +6661,9 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
6348
6661
  console.log(`${prefix}No fix/revert/bug commits found in the specified period.`);
6349
6662
  return { added: 0, skipped: 0, lowInfo: 0 };
6350
6663
  }
6351
- const parsedLessons = extractLessons(gitLog);
6664
+ // Same patterns as MCP hippo_learn: config.gitLearnPatterns (whose default
6665
+ // equals extractLessons' built-in list) so a custom list applies everywhere.
6666
+ const parsedLessons = extractLessons(gitLog, loadConfig(hippoRoot).gitLearnPatterns);
6352
6667
  if (parsedLessons.length === 0) {
6353
6668
  console.log(`${prefix}No fix/revert/bug commits found in the specified period.`);
6354
6669
  return { added: 0, skipped: 0, lowInfo: 0 };
@@ -6392,14 +6707,9 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
6392
6707
  // the existing summary line.
6393
6708
  let rejected = 0;
6394
6709
  const gitLearnTags = ['error', 'git-learned'];
6395
- const existingForSchema = loadAllEntries(hippoRoot);
6710
+ const existingForSchema = loadAllEntries(hippoRoot, resolveTenantId({}));
6396
6711
  for (const lesson of lessons) {
6397
- // L9: learnFromRepo's existingForSchema load just above (the
6398
- // `loadAllEntries(hippoRoot)` call a few lines up) is host-wide and
6399
- // intentionally out-of-L9-scope per plan §11 (cli.ts is
6400
- // single-tenant-per-process). The tenantId arg here is a defensive no-op:
6401
- // deduplicateLesson's array overload ignores it; the parameter exists
6402
- // only to mirror the canonical caller pattern used elsewhere in cli.ts.
6712
+ // The array overload ignores the tenant arg; existingForSchema is already scoped.
6403
6713
  if (deduplicateLesson(existingForSchema, lesson, 0.7, resolveTenantId({}))) {
6404
6714
  skipped++;
6405
6715
  continue;
@@ -6418,6 +6728,7 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
6418
6728
  source: 'git-learn',
6419
6729
  confidence: 'observed',
6420
6730
  schema_fit: schemaFitVal,
6731
+ tenantId: resolveTenantId({}),
6421
6732
  });
6422
6733
  // Auto-tag with path context from the repo being learned
6423
6734
  const learnPathTags = extractPathTags(repoPath);
@@ -6912,6 +7223,12 @@ function cmdHook(args, flags) {
6912
7223
  if (result.installedCompactResume) {
6913
7224
  console.log(`Installed hippo compact-resume SessionStart(compact) hook in ${result.target} settings`);
6914
7225
  }
7226
+ if (result.installedPostCompact) {
7227
+ console.log(`Installed hippo post-compact PostCompact hook in ${result.target} settings`);
7228
+ }
7229
+ if (result.installedCaptureError) {
7230
+ console.log(`Installed hippo capture-error PostToolUseFailure hook in ${result.target} settings`);
7231
+ }
6915
7232
  if (result.migratedFromStop) {
6916
7233
  console.log(`Migrated legacy Stop hook → SessionEnd (was running every turn; now fires once on session exit)`);
6917
7234
  }
@@ -7032,6 +7349,10 @@ function cmdSetup(flags) {
7032
7349
  bits.push('PreCompact (pre-compact)');
7033
7350
  if (result.installedCompactResume)
7034
7351
  bits.push('SessionStart(compact) (compact-resume)');
7352
+ if (result.installedPostCompact)
7353
+ bits.push('PostCompact (post-compact)');
7354
+ if (result.installedCaptureError)
7355
+ bits.push('PostToolUseFailure (capture-error)');
7035
7356
  if (result.migratedFromStop)
7036
7357
  bits.push('migrated legacy Stop');
7037
7358
  if (result.migratedSplitSessionEnd)
@@ -7573,6 +7894,8 @@ const VALID_AUDIT_OPS = new Set([
7573
7894
  'reject_refusal', // AT1 — emitted when the rejection guard refuses a write; lockstep
7574
7895
  'unreject_value', // AT1 — emitted by `hippo unreject`; lockstep
7575
7896
  'conflict_resolve', // AT1 — emitted by resolveConflict on every resolution path; lockstep
7897
+ 'half_life_migrate', // Decay default change — emitted by migrateDefaultHalfLife; lockstep with AuditOp union
7898
+ 'dormant_restore', // Dormant memories — emitted by api.restoreDormant; lockstep with AuditOp union + server.ts VALID_AUDIT_OPS
7576
7899
  ]);
7577
7900
  function formatAuditRow(ev) {
7578
7901
  const target = ev.targetId ?? '-';
@@ -8298,6 +8621,23 @@ Commands:
8298
8621
  --global Operate on the global store
8299
8622
  unreject <digest-prefix> Delete a tombstone (the only escape hatch)
8300
8623
  --global Operate on the global store
8624
+ dormant [<query>] List faded memories sleep kept instead of deleting
8625
+ (on by default; "dormant": {"enabled": false} deletes instead)
8626
+ --limit <n> Max rows, newest first (default: 20)
8627
+ --json Output as JSON
8628
+ --global Operate on the global store
8629
+ dormant restore <id> Bring a dormant memory back to active memory
8630
+ dormant forget <id> Delete a dormant memory permanently
8631
+ capture-error Store a failed tool call as an error memory (reads the Claude Code
8632
+ PostToolUseFailure hook payload on stdin; skips routine failures)
8633
+ doctor Check the install: Node, store, schema, sleep, agent hooks
8634
+ --json Machine-readable report (exit code 1 on any failure)
8635
+ tokens Tokens of memory text hippo handed agents, per surface
8636
+ (hook, context, recall, MCP, HTTP), and what skipping
8637
+ unchanged hook blocks saved
8638
+ --days <n> Window in days (default: 30)
8639
+ --json Output as JSON
8640
+ --global Operate on the global store
8301
8641
  snapshot <sub> Persist or inspect the current active task
8302
8642
  snapshot save Save active task state
8303
8643
  --task <task>
@@ -8425,6 +8765,8 @@ Commands:
8425
8765
  pre-compact PreCompact hook: snapshot + capture the tail before compaction
8426
8766
  --log-file <p> Diagnostic log path (default: ~/.hippo/logs/pre-compact.log)
8427
8767
  compact-resume SessionStart(compact) hook: re-print the snapshot + session trail
8768
+ post-compact PostCompact hook: tell the user what pre-compact saved
8769
+ --log-file <p> Same log path as pre-compact (default: ~/.hippo/logs/pre-compact.log)
8428
8770
  codex-run [-- ...args] Launch real Codex behind Hippo's session-end wrapper
8429
8771
  hook <sub> [target] Manage framework integrations
8430
8772
  hook list Show available hooks
@@ -8589,6 +8931,9 @@ Examples:
8589
8931
  hippo reject --value "never store my key again" --reason "secret"
8590
8932
  hippo rejections
8591
8933
  hippo unreject a1b2c3d4e5f6
8934
+ hippo dormant "staging hostname"
8935
+ hippo dormant restore mem_abc123
8936
+ hippo tokens --days 7
8592
8937
  hippo session log --id sess_123 --task "Ship feature" --type progress --content "Build is green, next step is docs"
8593
8938
  hippo session latest --json
8594
8939
  hippo session resume
@@ -8632,25 +8977,16 @@ Examples:
8632
8977
  // ---------------------------------------------------------------------------
8633
8978
  // Entry point
8634
8979
  // ---------------------------------------------------------------------------
8635
- const { command, args, flags } = parseArgs(process.argv);
8636
- const hippoRoot = getHippoRoot(process.cwd());
8637
- async function main() {
8980
+ async function main(command, args, flags, hippoRoot) {
8638
8981
  if (command === '--version' || command === '-v' || flags['version']) {
8639
8982
  const __filename_local = fileURLToPath(import.meta.url);
8640
8983
  const __dirname_local = path.dirname(__filename_local);
8641
- const pkgPath = path.join(__dirname_local, '..', 'package.json');
8642
- let pkgJson;
8643
- try {
8644
- pkgJson = fs.readFileSync(pkgPath, 'utf-8');
8645
- }
8646
- catch {
8647
- pkgJson = fs.readFileSync(path.join(__dirname_local, '..', '..', 'package.json'), 'utf-8');
8648
- }
8984
+ const pkgJson = fs.readFileSync(path.join(__dirname_local, '..', 'package.json'), 'utf-8');
8649
8985
  const { version } = JSON.parse(pkgJson);
8650
8986
  console.log(version);
8651
8987
  process.exit(0);
8652
8988
  }
8653
- maybeRepairCodexWrapper(command, args);
8989
+ maybeRepairCodexWrapper(command, flags);
8654
8990
  /** Global --scope well-formedness guard (v1.26.2). parseArgs stores a value-less
8655
8991
  * flag as boolean true; downstream the 14 consumer sites either coerced that to
8656
8992
  * the literal scope string 'true' (recall filter/unlock input, wm session scope,
@@ -8685,6 +9021,21 @@ async function main() {
8685
9021
  process.exit(1);
8686
9022
  }
8687
9023
  }
9024
+ // card checks its flags per subcommand, with a stricter message.
9025
+ const unknownFlags = command === 'card' ? [] : Object.keys(flags).filter((key) => !KNOWN_FLAGS.has(key));
9026
+ if (unknownFlags.length > 0) {
9027
+ const names = unknownFlags.map((key) => `--${key}`).join(', ');
9028
+ if (DESTRUCTIVE_COMMANDS.has(command)) {
9029
+ console.error(`Unknown flag ${names} for hippo ${command}. Nothing was changed.`);
9030
+ process.exit(2);
9031
+ }
9032
+ console.error(`hippo: ignoring unknown flag ${names}. A later release will reject it.`);
9033
+ }
9034
+ const refusal = Object.hasOwn(flags, 'dry-run') ? dryRunRefusal(command, args, flags) : null;
9035
+ if (refusal) {
9036
+ console.error(refusal);
9037
+ process.exit(2);
9038
+ }
8688
9039
  switch (command) {
8689
9040
  case 'init':
8690
9041
  cmdInit(hippoRoot, flags);
@@ -8831,16 +9182,45 @@ async function main() {
8831
9182
  case 'pre-compact': {
8832
9183
  // Bounded wait, not a TTY guard: an idle non-TTY pipe must not hang.
8833
9184
  const { text: stdinText, timedOut: stdinTimedOut } = await readStdinBounded();
8834
- await cmdPreCompact(hippoRoot, {
9185
+ resetHookInjection(hippoRoot, stdinText, null);
9186
+ await cmdPreCompact(hookStoreRoot(hippoRoot), {
8835
9187
  stdinText,
8836
9188
  stdinTimedOut,
8837
9189
  logFile: typeof flags['log-file'] === 'string' ? flags['log-file'] : undefined,
8838
9190
  });
8839
9191
  break;
8840
9192
  }
9193
+ case 'post-compact': {
9194
+ // PostCompact hook: tells the user what pre-compact saved. Plain text,
9195
+ // because Claude Code shows this hook's stdout as-is. Always exits 0.
9196
+ const { text } = await readStdinBounded();
9197
+ const logFlag = flags['log-file'];
9198
+ const message = postCompactMessage(text, logFlag === true || logFlag === false || Array.isArray(logFlag) ? undefined : logFlag);
9199
+ if (message !== null)
9200
+ console.log(message);
9201
+ break;
9202
+ }
9203
+ case 'capture-error': {
9204
+ // PostToolUseFailure hook: every path exits 0, and nothing is created
9205
+ // when no store exists (the hook fires in every directory).
9206
+ const { text } = await readStdinBounded();
9207
+ try {
9208
+ const root = hookStoreRoot(hippoRoot);
9209
+ const payload = (text ?? '').trim();
9210
+ if (isInitialized(root) && payload) {
9211
+ // SAFETY: JSON.parse returns a JSON value by definition.
9212
+ captureToolFailure(root, resolveTenantId({}), JSON.parse(payload));
9213
+ }
9214
+ }
9215
+ catch {
9216
+ // A malformed payload or store error must never fail the agent's tool call.
9217
+ }
9218
+ break;
9219
+ }
8841
9220
  case 'compact-resume': {
8842
9221
  const { text: stdinText, timedOut: stdinTimedOut } = await readStdinBounded();
8843
- cmdCompactResume(hippoRoot, stdinText, stdinTimedOut);
9222
+ resetHookInjection(hippoRoot, stdinText, 'compact');
9223
+ cmdCompactResume(hookStoreRoot(hippoRoot), stdinText, stdinTimedOut);
8844
9224
  break;
8845
9225
  }
8846
9226
  case 'codex-run':
@@ -8876,7 +9256,7 @@ async function main() {
8876
9256
  break;
8877
9257
  }
8878
9258
  requireInit(hippoRoot);
8879
- const entries = loadAllEntries(hippoRoot);
9259
+ const entries = loadAllEntries(hippoRoot, resolveTenantId({}));
8880
9260
  const result = auditMemories(entries);
8881
9261
  const shouldFix = Boolean(flags['fix']);
8882
9262
  if (result.issues.length === 0) {
@@ -8890,13 +9270,15 @@ async function main() {
8890
9270
  console.log(` "${issue.content.slice(0, 80)}${issue.content.length > 80 ? '...' : ''}"`);
8891
9271
  }
8892
9272
  if (shouldFix) {
8893
- const errorIds = result.issues.filter(i => i.severity === 'error').map(i => i.memoryId);
8894
- if (errorIds.length > 0) {
8895
- for (const id of errorIds) {
8896
- deleteEntry(hippoRoot, id);
8897
- }
8898
- console.log(`\nRemoved ${errorIds.length} error-severity memories.`);
8899
- console.log(`${result.issues.length - errorIds.length} warnings remain (review manually).`);
9273
+ const errors = result.issues.filter(i => i.severity === 'error');
9274
+ if (errors.length > 0 && flags['dry-run'] === true) {
9275
+ console.log(`\nWould remove ${errors.length} error-severity memories (dry run, nothing deleted).`);
9276
+ console.log(`${result.issues.length - errors.length} warnings would remain (review manually).`);
9277
+ }
9278
+ else if (errors.length > 0) {
9279
+ const removedCount = errors.filter((issue) => deleteEntry(hippoRoot, issue.memoryId, { reason: `audit --fix: ${issue.reason}`, automatic: true })).length;
9280
+ console.log(`\nRemoved ${removedCount} error-severity memories.`);
9281
+ console.log(`${result.issues.length - errors.length} warnings remain (review manually).`);
8900
9282
  }
8901
9283
  else {
8902
9284
  console.log(`\nNo error-severity issues. Warnings require manual review.`);
@@ -8985,6 +9367,21 @@ async function main() {
8985
9367
  case 'unreject':
8986
9368
  cmdUnreject(hippoRoot, args, flags);
8987
9369
  break;
9370
+ case 'dormant':
9371
+ cmdDormant(hippoRoot, args, flags);
9372
+ break;
9373
+ case 'tokens':
9374
+ cmdTokens(hippoRoot, flags);
9375
+ break;
9376
+ case 'doctor': {
9377
+ // SAFETY: package.json always carries a string "version" (checked at release by check-manifest-versions).
9378
+ const pkg = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf-8'));
9379
+ const report = runDoctor({ version: pkg.version });
9380
+ console.log(flags['json'] ? JSON.stringify(report, null, 2) : formatDoctor(report));
9381
+ if (!report.ok)
9382
+ process.exit(1);
9383
+ break;
9384
+ }
8988
9385
  case 'snapshot':
8989
9386
  cmdSnapshot(hippoRoot, args, flags);
8990
9387
  break;
@@ -9017,6 +9414,10 @@ async function main() {
9017
9414
  console.error(ARCHIVE_REASON_REQUIRED);
9018
9415
  process.exit(1);
9019
9416
  }
9417
+ if (flags['dry-run'] === true) {
9418
+ previewForget(hippoRoot, id, archive);
9419
+ break;
9420
+ }
9020
9421
  const routed = await runViaServerIfAvailable(hippoRoot, async (info, apiKey) => {
9021
9422
  try {
9022
9423
  if (archive) {
@@ -9182,7 +9583,7 @@ async function main() {
9182
9583
  requireInit(hippoRoot);
9183
9584
  const format = flags['format'] || 'json';
9184
9585
  const outputPath = args[0] || null;
9185
- const entries = loadAllEntries(hippoRoot);
9586
+ const entries = loadAllEntries(hippoRoot, resolveTenantId({}));
9186
9587
  let output;
9187
9588
  if (format === 'markdown' || format === 'md') {
9188
9589
  output = entries.map(e => {
@@ -9282,11 +9683,11 @@ async function main() {
9282
9683
  }
9283
9684
  const host = typeof flags['host'] === 'string' ? flags['host'] : '127.0.0.1';
9284
9685
  const { serve } = await import('./server.js');
9285
- const handle = await serve({ hippoRoot, port, host });
9686
+ const handle = await serve({ hippoRoot, port, host, handleSignals: true });
9286
9687
  console.log(`hippo serve listening on ${handle.url} (pid ${process.pid})`);
9287
9688
  console.log(`pidfile: ${path.join(hippoRoot, 'server.pid')}`);
9288
9689
  console.log('press Ctrl+C to stop');
9289
- // SIGINT/SIGTERM handlers wired in server.ts (skipped under VITEST). Hang.
9690
+ // The SIGINT/SIGTERM handlers stop the server and exit. Hang until then.
9290
9691
  await new Promise(() => { });
9291
9692
  break;
9292
9693
  }
@@ -9380,8 +9781,19 @@ async function main() {
9380
9781
  process.exit(1);
9381
9782
  }
9382
9783
  }
9383
- main().catch((err) => {
9384
- console.error('Error:', err.message ?? err);
9385
- process.exit(1);
9386
- });
9784
+ export async function runCli(argv = process.argv) {
9785
+ const { command, args, flags } = parseArgs(argv);
9786
+ try {
9787
+ await main(command, args, flags, getHippoRoot(process.cwd()));
9788
+ }
9789
+ catch (err) {
9790
+ console.error('Error:', err instanceof Error ? err.message : err);
9791
+ process.exit(1);
9792
+ }
9793
+ }
9794
+ // bin/hippo.js calls runCli(); this keeps `node dist/cli.js` working while an import runs nothing.
9795
+ const entryPath = process.argv[1];
9796
+ if (entryPath && fs.existsSync(entryPath) && fs.realpathSync(entryPath) === fileURLToPath(import.meta.url)) {
9797
+ void runCli();
9798
+ }
9387
9799
  //# sourceMappingURL=cli.js.map