hippo-memory 1.43.3 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (534) hide show
  1. package/README.md +14 -12
  2. package/bin/hippo.js +3 -1
  3. package/dist/api.d.ts +21 -21
  4. package/dist/api.js +69 -42
  5. package/dist/audit-prune.js +4 -1
  6. package/dist/audit.d.ts +1 -1
  7. package/dist/audit.js +9 -0
  8. package/dist/autolearn.d.ts +1 -1
  9. package/dist/autolearn.js +2 -1
  10. package/dist/capture.d.ts +11 -10
  11. package/dist/capture.js +22 -30
  12. package/dist/cli.d.ts +4 -0
  13. package/dist/cli.js +309 -171
  14. package/dist/client.d.ts +4 -21
  15. package/dist/client.js +3 -112
  16. package/dist/config.js +2 -1
  17. package/dist/consolidate.d.ts +3 -0
  18. package/dist/consolidate.js +44 -42
  19. package/dist/dag.d.ts +1 -0
  20. package/dist/dag.js +10 -5
  21. package/dist/dashboard.js +7 -3
  22. package/dist/db.js +16 -24
  23. package/dist/dedupe.d.ts +1 -0
  24. package/dist/dedupe.js +4 -7
  25. package/dist/embedding-provider.d.ts +1 -1
  26. package/dist/embedding-provider.js +3 -2
  27. package/dist/embeddings.js +81 -8
  28. package/dist/extract.d.ts +2 -0
  29. package/dist/extract.js +9 -4
  30. package/dist/goals.js +12 -3
  31. package/dist/mcp/server.js +21 -36
  32. package/dist/memory.d.ts +3 -0
  33. package/dist/memory.js +7 -1
  34. package/dist/physics-state.js +4 -1
  35. package/dist/raw-archive.js +5 -2
  36. package/dist/recall-trace.js +4 -1
  37. package/dist/refine-llm.js +3 -2
  38. package/dist/rerankers/jev.js +3 -2
  39. package/dist/rerankers/llm.js +3 -2
  40. package/dist/search.js +3 -3
  41. package/dist/server.d.ts +5 -0
  42. package/dist/server.js +39 -29
  43. package/dist/shared.js +1 -1
  44. package/dist/stdin.d.ts +12 -0
  45. package/dist/stdin.js +41 -0
  46. package/dist/store.d.ts +18 -11
  47. package/dist/store.js +152 -65
  48. package/dist/version.d.ts +4 -6
  49. package/dist/version.js +4 -7
  50. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  51. package/extensions/openclaw-plugin/package.json +1 -1
  52. package/openclaw.plugin.json +1 -1
  53. package/package.json +8 -5
  54. package/dist/ablation.d.ts.map +0 -1
  55. package/dist/ablation.js.map +0 -1
  56. package/dist/ambient.d.ts.map +0 -1
  57. package/dist/ambient.js.map +0 -1
  58. package/dist/api.d.ts.map +0 -1
  59. package/dist/api.js.map +0 -1
  60. package/dist/audit-prune.d.ts.map +0 -1
  61. package/dist/audit-prune.js.map +0 -1
  62. package/dist/audit.d.ts.map +0 -1
  63. package/dist/audit.js.map +0 -1
  64. package/dist/auth.d.ts.map +0 -1
  65. package/dist/auth.js.map +0 -1
  66. package/dist/autolearn.d.ts.map +0 -1
  67. package/dist/autolearn.js.map +0 -1
  68. package/dist/availability.d.ts.map +0 -1
  69. package/dist/availability.js.map +0 -1
  70. package/dist/benchmarks/e1.3/incident-recall-eval.js +0 -78
  71. package/dist/benchmarks/e1.3/incident-recall-eval.js.map +0 -1
  72. package/dist/benchmarks/e1.3/slack-1000-event-smoke.js +0 -103
  73. package/dist/benchmarks/e1.3/slack-1000-event-smoke.js.map +0 -1
  74. package/dist/capture.d.ts.map +0 -1
  75. package/dist/capture.js.map +0 -1
  76. package/dist/card-detail.d.ts.map +0 -1
  77. package/dist/card-detail.js.map +0 -1
  78. package/dist/card.d.ts.map +0 -1
  79. package/dist/card.js.map +0 -1
  80. package/dist/cli.d.ts.map +0 -1
  81. package/dist/cli.js.map +0 -1
  82. package/dist/client.d.ts.map +0 -1
  83. package/dist/client.js.map +0 -1
  84. package/dist/compare.d.ts.map +0 -1
  85. package/dist/compare.js.map +0 -1
  86. package/dist/config.d.ts.map +0 -1
  87. package/dist/config.js.map +0 -1
  88. package/dist/connectors/github/backfill.d.ts.map +0 -1
  89. package/dist/connectors/github/backfill.js.map +0 -1
  90. package/dist/connectors/github/cli-impl.d.ts.map +0 -1
  91. package/dist/connectors/github/cli-impl.js.map +0 -1
  92. package/dist/connectors/github/deletion.d.ts.map +0 -1
  93. package/dist/connectors/github/deletion.js.map +0 -1
  94. package/dist/connectors/github/dlq.d.ts.map +0 -1
  95. package/dist/connectors/github/dlq.js.map +0 -1
  96. package/dist/connectors/github/idempotency.d.ts.map +0 -1
  97. package/dist/connectors/github/idempotency.js.map +0 -1
  98. package/dist/connectors/github/ingest.d.ts.map +0 -1
  99. package/dist/connectors/github/ingest.js.map +0 -1
  100. package/dist/connectors/github/octokit-client.d.ts.map +0 -1
  101. package/dist/connectors/github/octokit-client.js.map +0 -1
  102. package/dist/connectors/github/ratelimit.d.ts.map +0 -1
  103. package/dist/connectors/github/ratelimit.js.map +0 -1
  104. package/dist/connectors/github/scope.d.ts.map +0 -1
  105. package/dist/connectors/github/scope.js.map +0 -1
  106. package/dist/connectors/github/signature.d.ts.map +0 -1
  107. package/dist/connectors/github/signature.js.map +0 -1
  108. package/dist/connectors/github/tenant-routing.d.ts.map +0 -1
  109. package/dist/connectors/github/tenant-routing.js.map +0 -1
  110. package/dist/connectors/github/transform.d.ts.map +0 -1
  111. package/dist/connectors/github/transform.js.map +0 -1
  112. package/dist/connectors/github/types.d.ts.map +0 -1
  113. package/dist/connectors/github/types.js.map +0 -1
  114. package/dist/connectors/slack/backfill.d.ts.map +0 -1
  115. package/dist/connectors/slack/backfill.js.map +0 -1
  116. package/dist/connectors/slack/deletion.d.ts.map +0 -1
  117. package/dist/connectors/slack/deletion.js.map +0 -1
  118. package/dist/connectors/slack/dlq.d.ts.map +0 -1
  119. package/dist/connectors/slack/dlq.js.map +0 -1
  120. package/dist/connectors/slack/idempotency.d.ts.map +0 -1
  121. package/dist/connectors/slack/idempotency.js.map +0 -1
  122. package/dist/connectors/slack/ingest.d.ts.map +0 -1
  123. package/dist/connectors/slack/ingest.js.map +0 -1
  124. package/dist/connectors/slack/ratelimit.d.ts.map +0 -1
  125. package/dist/connectors/slack/ratelimit.js.map +0 -1
  126. package/dist/connectors/slack/scope.d.ts.map +0 -1
  127. package/dist/connectors/slack/scope.js.map +0 -1
  128. package/dist/connectors/slack/signature.d.ts.map +0 -1
  129. package/dist/connectors/slack/signature.js.map +0 -1
  130. package/dist/connectors/slack/tenant-routing.d.ts.map +0 -1
  131. package/dist/connectors/slack/tenant-routing.js.map +0 -1
  132. package/dist/connectors/slack/transform.d.ts.map +0 -1
  133. package/dist/connectors/slack/transform.js.map +0 -1
  134. package/dist/connectors/slack/types.d.ts.map +0 -1
  135. package/dist/connectors/slack/types.js.map +0 -1
  136. package/dist/connectors/slack/web-client.d.ts.map +0 -1
  137. package/dist/connectors/slack/web-client.js.map +0 -1
  138. package/dist/connectors/slack/workspaces.d.ts.map +0 -1
  139. package/dist/connectors/slack/workspaces.js.map +0 -1
  140. package/dist/consolidate.d.ts.map +0 -1
  141. package/dist/consolidate.js.map +0 -1
  142. package/dist/correction-latency.d.ts.map +0 -1
  143. package/dist/correction-latency.js.map +0 -1
  144. package/dist/customer-notes.d.ts.map +0 -1
  145. package/dist/customer-notes.js.map +0 -1
  146. package/dist/dag.d.ts.map +0 -1
  147. package/dist/dag.js.map +0 -1
  148. package/dist/dashboard.d.ts.map +0 -1
  149. package/dist/dashboard.js.map +0 -1
  150. package/dist/db.d.ts.map +0 -1
  151. package/dist/db.js.map +0 -1
  152. package/dist/decisions.d.ts.map +0 -1
  153. package/dist/decisions.js.map +0 -1
  154. package/dist/dedupe.d.ts.map +0 -1
  155. package/dist/dedupe.js.map +0 -1
  156. package/dist/embedding-provider.d.ts.map +0 -1
  157. package/dist/embedding-provider.js.map +0 -1
  158. package/dist/embeddings.d.ts.map +0 -1
  159. package/dist/embeddings.js.map +0 -1
  160. package/dist/eval-suite.d.ts.map +0 -1
  161. package/dist/eval-suite.js.map +0 -1
  162. package/dist/eval.d.ts.map +0 -1
  163. package/dist/eval.js.map +0 -1
  164. package/dist/extensions/openclaw-plugin/index.js.map +0 -1
  165. package/dist/extract.d.ts.map +0 -1
  166. package/dist/extract.js.map +0 -1
  167. package/dist/forward-claim-detector.d.ts.map +0 -1
  168. package/dist/forward-claim-detector.js.map +0 -1
  169. package/dist/goals.d.ts.map +0 -1
  170. package/dist/goals.js.map +0 -1
  171. package/dist/graph-extract.d.ts.map +0 -1
  172. package/dist/graph-extract.js.map +0 -1
  173. package/dist/graph-recall.d.ts.map +0 -1
  174. package/dist/graph-recall.js.map +0 -1
  175. package/dist/graph-stream.d.ts.map +0 -1
  176. package/dist/graph-stream.js.map +0 -1
  177. package/dist/graph-view.d.ts.map +0 -1
  178. package/dist/graph-view.js.map +0 -1
  179. package/dist/graph.d.ts.map +0 -1
  180. package/dist/graph.js.map +0 -1
  181. package/dist/handoff.d.ts.map +0 -1
  182. package/dist/handoff.js.map +0 -1
  183. package/dist/hooks.d.ts.map +0 -1
  184. package/dist/hooks.js.map +0 -1
  185. package/dist/importers.d.ts.map +0 -1
  186. package/dist/importers.js.map +0 -1
  187. package/dist/incidents.d.ts.map +0 -1
  188. package/dist/incidents.js.map +0 -1
  189. package/dist/index.d.ts.map +0 -1
  190. package/dist/index.js.map +0 -1
  191. package/dist/invalidation.d.ts.map +0 -1
  192. package/dist/invalidation.js.map +0 -1
  193. package/dist/mcp/framing.d.ts.map +0 -1
  194. package/dist/mcp/framing.js.map +0 -1
  195. package/dist/mcp/server.d.ts.map +0 -1
  196. package/dist/mcp/server.js.map +0 -1
  197. package/dist/memory-value-weights.d.ts.map +0 -1
  198. package/dist/memory-value-weights.js.map +0 -1
  199. package/dist/memory-value.d.ts.map +0 -1
  200. package/dist/memory-value.js.map +0 -1
  201. package/dist/memory.d.ts.map +0 -1
  202. package/dist/memory.js.map +0 -1
  203. package/dist/multihop.d.ts.map +0 -1
  204. package/dist/multihop.js.map +0 -1
  205. package/dist/owner-validation.d.ts.map +0 -1
  206. package/dist/owner-validation.js.map +0 -1
  207. package/dist/path-context.d.ts.map +0 -1
  208. package/dist/path-context.js.map +0 -1
  209. package/dist/physics-config.d.ts.map +0 -1
  210. package/dist/physics-config.js.map +0 -1
  211. package/dist/physics-state.d.ts.map +0 -1
  212. package/dist/physics-state.js.map +0 -1
  213. package/dist/physics.d.ts.map +0 -1
  214. package/dist/physics.js.map +0 -1
  215. package/dist/policies.d.ts.map +0 -1
  216. package/dist/policies.js.map +0 -1
  217. package/dist/postinstall.d.ts.map +0 -1
  218. package/dist/postinstall.js.map +0 -1
  219. package/dist/predictions.d.ts.map +0 -1
  220. package/dist/predictions.js.map +0 -1
  221. package/dist/processes.d.ts.map +0 -1
  222. package/dist/processes.js.map +0 -1
  223. package/dist/project-briefs.d.ts.map +0 -1
  224. package/dist/project-briefs.js.map +0 -1
  225. package/dist/project-identity.d.ts.map +0 -1
  226. package/dist/project-identity.js.map +0 -1
  227. package/dist/provenance-coverage.d.ts.map +0 -1
  228. package/dist/provenance-coverage.js.map +0 -1
  229. package/dist/rate-limit.d.ts.map +0 -1
  230. package/dist/rate-limit.js.map +0 -1
  231. package/dist/raw-archive-mirror-cleanup.d.ts.map +0 -1
  232. package/dist/raw-archive-mirror-cleanup.js.map +0 -1
  233. package/dist/raw-archive.d.ts.map +0 -1
  234. package/dist/raw-archive.js.map +0 -1
  235. package/dist/recall-history.d.ts.map +0 -1
  236. package/dist/recall-history.js.map +0 -1
  237. package/dist/recall-scope.d.ts.map +0 -1
  238. package/dist/recall-scope.js.map +0 -1
  239. package/dist/recall-trace.d.ts.map +0 -1
  240. package/dist/recall-trace.js.map +0 -1
  241. package/dist/refine-llm.d.ts.map +0 -1
  242. package/dist/refine-llm.js.map +0 -1
  243. package/dist/reject-flow.d.ts.map +0 -1
  244. package/dist/reject-flow.js.map +0 -1
  245. package/dist/rejection.d.ts.map +0 -1
  246. package/dist/rejection.js.map +0 -1
  247. package/dist/replay.d.ts.map +0 -1
  248. package/dist/replay.js.map +0 -1
  249. package/dist/rerankers/cross-encoder.d.ts.map +0 -1
  250. package/dist/rerankers/cross-encoder.js.map +0 -1
  251. package/dist/rerankers/index.d.ts.map +0 -1
  252. package/dist/rerankers/index.js.map +0 -1
  253. package/dist/rerankers/jev.d.ts.map +0 -1
  254. package/dist/rerankers/jev.js.map +0 -1
  255. package/dist/rerankers/llm.d.ts.map +0 -1
  256. package/dist/rerankers/llm.js.map +0 -1
  257. package/dist/rerankers/types.d.ts.map +0 -1
  258. package/dist/rerankers/types.js.map +0 -1
  259. package/dist/rrf.d.ts.map +0 -1
  260. package/dist/rrf.js.map +0 -1
  261. package/dist/salience.d.ts.map +0 -1
  262. package/dist/salience.js.map +0 -1
  263. package/dist/scheduler.d.ts.map +0 -1
  264. package/dist/scheduler.js.map +0 -1
  265. package/dist/scope.d.ts.map +0 -1
  266. package/dist/scope.js.map +0 -1
  267. package/dist/search.d.ts.map +0 -1
  268. package/dist/search.js.map +0 -1
  269. package/dist/secret-detect.d.ts.map +0 -1
  270. package/dist/secret-detect.js.map +0 -1
  271. package/dist/server-detect.d.ts.map +0 -1
  272. package/dist/server-detect.js.map +0 -1
  273. package/dist/server.d.ts.map +0 -1
  274. package/dist/server.js.map +0 -1
  275. package/dist/shared.d.ts.map +0 -1
  276. package/dist/shared.js.map +0 -1
  277. package/dist/skills.d.ts.map +0 -1
  278. package/dist/skills.js.map +0 -1
  279. package/dist/sleep-redact.d.ts +0 -58
  280. package/dist/sleep-redact.d.ts.map +0 -1
  281. package/dist/sleep-redact.js +0 -80
  282. package/dist/sleep-redact.js.map +0 -1
  283. package/dist/src/ablation.js +0 -138
  284. package/dist/src/ablation.js.map +0 -1
  285. package/dist/src/ambient.js +0 -148
  286. package/dist/src/ambient.js.map +0 -1
  287. package/dist/src/api.js +0 -2109
  288. package/dist/src/api.js.map +0 -1
  289. package/dist/src/audit-prune.js +0 -106
  290. package/dist/src/audit-prune.js.map +0 -1
  291. package/dist/src/audit.js +0 -213
  292. package/dist/src/audit.js.map +0 -1
  293. package/dist/src/auth.js +0 -97
  294. package/dist/src/auth.js.map +0 -1
  295. package/dist/src/autolearn.js +0 -174
  296. package/dist/src/autolearn.js.map +0 -1
  297. package/dist/src/availability.js +0 -94
  298. package/dist/src/availability.js.map +0 -1
  299. package/dist/src/capture.js +0 -1354
  300. package/dist/src/capture.js.map +0 -1
  301. package/dist/src/card-detail.js +0 -15
  302. package/dist/src/card-detail.js.map +0 -1
  303. package/dist/src/card.js +0 -19
  304. package/dist/src/card.js.map +0 -1
  305. package/dist/src/cli.js +0 -9328
  306. package/dist/src/cli.js.map +0 -1
  307. package/dist/src/client.js +0 -237
  308. package/dist/src/client.js.map +0 -1
  309. package/dist/src/compare.js +0 -122
  310. package/dist/src/compare.js.map +0 -1
  311. package/dist/src/config.js +0 -135
  312. package/dist/src/config.js.map +0 -1
  313. package/dist/src/connectors/github/backfill.js +0 -281
  314. package/dist/src/connectors/github/backfill.js.map +0 -1
  315. package/dist/src/connectors/github/cli-impl.js +0 -234
  316. package/dist/src/connectors/github/cli-impl.js.map +0 -1
  317. package/dist/src/connectors/github/deletion.js +0 -85
  318. package/dist/src/connectors/github/deletion.js.map +0 -1
  319. package/dist/src/connectors/github/dlq.js +0 -190
  320. package/dist/src/connectors/github/dlq.js.map +0 -1
  321. package/dist/src/connectors/github/idempotency.js +0 -27
  322. package/dist/src/connectors/github/idempotency.js.map +0 -1
  323. package/dist/src/connectors/github/ingest.js +0 -156
  324. package/dist/src/connectors/github/ingest.js.map +0 -1
  325. package/dist/src/connectors/github/octokit-client.js +0 -70
  326. package/dist/src/connectors/github/octokit-client.js.map +0 -1
  327. package/dist/src/connectors/github/ratelimit.js +0 -31
  328. package/dist/src/connectors/github/ratelimit.js.map +0 -1
  329. package/dist/src/connectors/github/scope.js +0 -13
  330. package/dist/src/connectors/github/scope.js.map +0 -1
  331. package/dist/src/connectors/github/signature.js +0 -81
  332. package/dist/src/connectors/github/signature.js.map +0 -1
  333. package/dist/src/connectors/github/tenant-routing.js +0 -69
  334. package/dist/src/connectors/github/tenant-routing.js.map +0 -1
  335. package/dist/src/connectors/github/transform.js +0 -103
  336. package/dist/src/connectors/github/transform.js.map +0 -1
  337. package/dist/src/connectors/github/types.js +0 -100
  338. package/dist/src/connectors/github/types.js.map +0 -1
  339. package/dist/src/connectors/slack/backfill.js +0 -78
  340. package/dist/src/connectors/slack/backfill.js.map +0 -1
  341. package/dist/src/connectors/slack/deletion.js +0 -59
  342. package/dist/src/connectors/slack/deletion.js.map +0 -1
  343. package/dist/src/connectors/slack/dlq.js +0 -224
  344. package/dist/src/connectors/slack/dlq.js.map +0 -1
  345. package/dist/src/connectors/slack/idempotency.js +0 -31
  346. package/dist/src/connectors/slack/idempotency.js.map +0 -1
  347. package/dist/src/connectors/slack/ingest.js +0 -109
  348. package/dist/src/connectors/slack/ingest.js.map +0 -1
  349. package/dist/src/connectors/slack/ratelimit.js +0 -18
  350. package/dist/src/connectors/slack/ratelimit.js.map +0 -1
  351. package/dist/src/connectors/slack/scope.js +0 -13
  352. package/dist/src/connectors/slack/scope.js.map +0 -1
  353. package/dist/src/connectors/slack/signature.js +0 -27
  354. package/dist/src/connectors/slack/signature.js.map +0 -1
  355. package/dist/src/connectors/slack/tenant-routing.js +0 -41
  356. package/dist/src/connectors/slack/tenant-routing.js.map +0 -1
  357. package/dist/src/connectors/slack/transform.js +0 -47
  358. package/dist/src/connectors/slack/transform.js.map +0 -1
  359. package/dist/src/connectors/slack/types.js +0 -35
  360. package/dist/src/connectors/slack/types.js.map +0 -1
  361. package/dist/src/connectors/slack/web-client.js +0 -43
  362. package/dist/src/connectors/slack/web-client.js.map +0 -1
  363. package/dist/src/connectors/slack/workspaces.js +0 -62
  364. package/dist/src/connectors/slack/workspaces.js.map +0 -1
  365. package/dist/src/consolidate.js +0 -978
  366. package/dist/src/consolidate.js.map +0 -1
  367. package/dist/src/correction-latency.js +0 -74
  368. package/dist/src/correction-latency.js.map +0 -1
  369. package/dist/src/customer-notes.js +0 -325
  370. package/dist/src/customer-notes.js.map +0 -1
  371. package/dist/src/dag.js +0 -352
  372. package/dist/src/dag.js.map +0 -1
  373. package/dist/src/dashboard.js +0 -258
  374. package/dist/src/dashboard.js.map +0 -1
  375. package/dist/src/db.js +0 -2728
  376. package/dist/src/db.js.map +0 -1
  377. package/dist/src/decisions.js +0 -304
  378. package/dist/src/decisions.js.map +0 -1
  379. package/dist/src/dedupe.js +0 -141
  380. package/dist/src/dedupe.js.map +0 -1
  381. package/dist/src/embedding-provider.js +0 -314
  382. package/dist/src/embedding-provider.js.map +0 -1
  383. package/dist/src/embeddings.js +0 -543
  384. package/dist/src/embeddings.js.map +0 -1
  385. package/dist/src/eval-suite.js +0 -294
  386. package/dist/src/eval-suite.js.map +0 -1
  387. package/dist/src/eval.js +0 -187
  388. package/dist/src/eval.js.map +0 -1
  389. package/dist/src/extract.js +0 -117
  390. package/dist/src/extract.js.map +0 -1
  391. package/dist/src/forward-claim-detector.js +0 -117
  392. package/dist/src/forward-claim-detector.js.map +0 -1
  393. package/dist/src/goals.js +0 -390
  394. package/dist/src/goals.js.map +0 -1
  395. package/dist/src/graph-extract.js +0 -314
  396. package/dist/src/graph-extract.js.map +0 -1
  397. package/dist/src/graph-recall.js +0 -277
  398. package/dist/src/graph-recall.js.map +0 -1
  399. package/dist/src/graph-stream.js +0 -176
  400. package/dist/src/graph-stream.js.map +0 -1
  401. package/dist/src/graph-view.js +0 -310
  402. package/dist/src/graph-view.js.map +0 -1
  403. package/dist/src/graph.js +0 -762
  404. package/dist/src/graph.js.map +0 -1
  405. package/dist/src/handoff.js +0 -65
  406. package/dist/src/handoff.js.map +0 -1
  407. package/dist/src/hooks.js +0 -926
  408. package/dist/src/hooks.js.map +0 -1
  409. package/dist/src/importers.js +0 -881
  410. package/dist/src/importers.js.map +0 -1
  411. package/dist/src/incidents.js +0 -336
  412. package/dist/src/incidents.js.map +0 -1
  413. package/dist/src/index.js +0 -32
  414. package/dist/src/index.js.map +0 -1
  415. package/dist/src/invalidation.js +0 -129
  416. package/dist/src/invalidation.js.map +0 -1
  417. package/dist/src/mcp/framing.js +0 -45
  418. package/dist/src/mcp/framing.js.map +0 -1
  419. package/dist/src/mcp/server.js +0 -1298
  420. package/dist/src/mcp/server.js.map +0 -1
  421. package/dist/src/memory-value-weights.js +0 -31
  422. package/dist/src/memory-value-weights.js.map +0 -1
  423. package/dist/src/memory-value.js +0 -261
  424. package/dist/src/memory-value.js.map +0 -1
  425. package/dist/src/memory.js +0 -425
  426. package/dist/src/memory.js.map +0 -1
  427. package/dist/src/multihop.js +0 -35
  428. package/dist/src/multihop.js.map +0 -1
  429. package/dist/src/owner-validation.js +0 -56
  430. package/dist/src/owner-validation.js.map +0 -1
  431. package/dist/src/path-context.js +0 -48
  432. package/dist/src/path-context.js.map +0 -1
  433. package/dist/src/physics-config.js +0 -26
  434. package/dist/src/physics-config.js.map +0 -1
  435. package/dist/src/physics-state.js +0 -169
  436. package/dist/src/physics-state.js.map +0 -1
  437. package/dist/src/physics.js +0 -378
  438. package/dist/src/physics.js.map +0 -1
  439. package/dist/src/policies.js +0 -414
  440. package/dist/src/policies.js.map +0 -1
  441. package/dist/src/postinstall.js +0 -101
  442. package/dist/src/postinstall.js.map +0 -1
  443. package/dist/src/predictions.js +0 -629
  444. package/dist/src/predictions.js.map +0 -1
  445. package/dist/src/processes.js +0 -349
  446. package/dist/src/processes.js.map +0 -1
  447. package/dist/src/project-briefs.js +0 -492
  448. package/dist/src/project-briefs.js.map +0 -1
  449. package/dist/src/project-identity.js +0 -200
  450. package/dist/src/project-identity.js.map +0 -1
  451. package/dist/src/provenance-coverage.js +0 -23
  452. package/dist/src/provenance-coverage.js.map +0 -1
  453. package/dist/src/rate-limit.js +0 -60
  454. package/dist/src/rate-limit.js.map +0 -1
  455. package/dist/src/raw-archive-mirror-cleanup.js +0 -55
  456. package/dist/src/raw-archive-mirror-cleanup.js.map +0 -1
  457. package/dist/src/raw-archive.js +0 -88
  458. package/dist/src/raw-archive.js.map +0 -1
  459. package/dist/src/recall-history.js +0 -235
  460. package/dist/src/recall-history.js.map +0 -1
  461. package/dist/src/recall-scope.js +0 -89
  462. package/dist/src/recall-scope.js.map +0 -1
  463. package/dist/src/recall-trace.js +0 -183
  464. package/dist/src/recall-trace.js.map +0 -1
  465. package/dist/src/refine-llm.js +0 -156
  466. package/dist/src/refine-llm.js.map +0 -1
  467. package/dist/src/reject-flow.js +0 -209
  468. package/dist/src/reject-flow.js.map +0 -1
  469. package/dist/src/rejection.js +0 -160
  470. package/dist/src/rejection.js.map +0 -1
  471. package/dist/src/replay.js +0 -122
  472. package/dist/src/replay.js.map +0 -1
  473. package/dist/src/rerankers/cross-encoder.js +0 -137
  474. package/dist/src/rerankers/cross-encoder.js.map +0 -1
  475. package/dist/src/rerankers/index.js +0 -20
  476. package/dist/src/rerankers/index.js.map +0 -1
  477. package/dist/src/rerankers/jev.js +0 -119
  478. package/dist/src/rerankers/jev.js.map +0 -1
  479. package/dist/src/rerankers/llm.js +0 -80
  480. package/dist/src/rerankers/llm.js.map +0 -1
  481. package/dist/src/rerankers/types.js +0 -2
  482. package/dist/src/rerankers/types.js.map +0 -1
  483. package/dist/src/rrf.js +0 -61
  484. package/dist/src/rrf.js.map +0 -1
  485. package/dist/src/salience.js +0 -74
  486. package/dist/src/salience.js.map +0 -1
  487. package/dist/src/scheduler.js +0 -77
  488. package/dist/src/scheduler.js.map +0 -1
  489. package/dist/src/scope.js +0 -35
  490. package/dist/src/scope.js.map +0 -1
  491. package/dist/src/search.js +0 -1003
  492. package/dist/src/search.js.map +0 -1
  493. package/dist/src/secret-detect.js +0 -95
  494. package/dist/src/secret-detect.js.map +0 -1
  495. package/dist/src/server-detect.js +0 -231
  496. package/dist/src/server-detect.js.map +0 -1
  497. package/dist/src/server.js +0 -3255
  498. package/dist/src/server.js.map +0 -1
  499. package/dist/src/shared.js +0 -502
  500. package/dist/src/shared.js.map +0 -1
  501. package/dist/src/skills.js +0 -346
  502. package/dist/src/skills.js.map +0 -1
  503. package/dist/src/sleep-redact.js +0 -80
  504. package/dist/src/sleep-redact.js.map +0 -1
  505. package/dist/src/sso.js +0 -22
  506. package/dist/src/sso.js.map +0 -1
  507. package/dist/src/store.js +0 -3743
  508. package/dist/src/store.js.map +0 -1
  509. package/dist/src/tenant.js +0 -17
  510. package/dist/src/tenant.js.map +0 -1
  511. package/dist/src/trace.js +0 -72
  512. package/dist/src/trace.js.map +0 -1
  513. package/dist/src/version.js +0 -40
  514. package/dist/src/version.js.map +0 -1
  515. package/dist/src/working-memory.js +0 -157
  516. package/dist/src/working-memory.js.map +0 -1
  517. package/dist/src/yaml.js +0 -107
  518. package/dist/src/yaml.js.map +0 -1
  519. package/dist/sso.d.ts +0 -13
  520. package/dist/sso.d.ts.map +0 -1
  521. package/dist/sso.js +0 -22
  522. package/dist/sso.js.map +0 -1
  523. package/dist/store.d.ts.map +0 -1
  524. package/dist/store.js.map +0 -1
  525. package/dist/tenant.d.ts.map +0 -1
  526. package/dist/tenant.js.map +0 -1
  527. package/dist/trace.d.ts.map +0 -1
  528. package/dist/trace.js.map +0 -1
  529. package/dist/version.d.ts.map +0 -1
  530. package/dist/version.js.map +0 -1
  531. package/dist/working-memory.d.ts.map +0 -1
  532. package/dist/working-memory.js.map +0 -1
  533. package/dist/yaml.d.ts.map +0 -1
  534. package/dist/yaml.js.map +0 -1
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@
29
29
  * hippo decide "<decision>" [--context "<why>"] [--supersedes <id>]
30
30
  * hippo wm <push|read|clear|flush>
31
31
  */
32
- import { evalNow, isRecallBoostAblated } from './ablation.js';
32
+ import { evalNow } from './ablation.js';
33
33
  import * as path from 'path';
34
34
  import * as fs from 'fs';
35
35
  import * as os from 'os';
@@ -38,14 +38,14 @@ import { execFileSync, execSync, spawn } from 'child_process';
38
38
  import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInstalledTools, defaultSleepLogPath, ensureCodexWrapperInstalled, installCodexWrapper, isCodexWrapperInstalled, repairCodexWrapperIfInstalled, uninstallCodexWrapper, resolveCodexSessionTranscript, resolveCodexWrapperPaths, installOpencodePlugin, uninstallOpencodePlugin, resolveOpencodePluginPath, } from './hooks.js';
39
39
  import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, confidenceFacets, confidenceLabel, computeSchemaFit, Layer, } from './memory.js';
40
40
  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';
41
+ 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
42
  import { rejectValue, unrejectValue, listRejectionsForTenant } from './reject-flow.js';
43
43
  import { RejectedValueError } from './rejection.js';
44
44
  import { isHandoffOutcome, formatHandoffEvidenceLine } from './handoff.js';
45
45
  import { isCardStatus } from './card.js';
46
46
  import { loadCardDetail } from './card-detail.js';
47
47
  import { passesScopeFilterForRecall } from './recall-scope.js';
48
- import { markRetrieved, hybridSearch, physicsSearch, explainMatch, textOverlap, tokenize as tokenizeQuery } from './search.js';
48
+ import { hybridSearch, physicsSearch, explainMatch, textOverlap, tokenize as tokenizeQuery } from './search.js';
49
49
  import { compareEntryIdentity } from './compare.js';
50
50
  import { renderTraceContent, parseSteps } from './trace.js';
51
51
  import { writeRecallTraceAtRoot } from './recall-trace.js';
@@ -65,7 +65,8 @@ import { detectScope } from './scope.js';
65
65
  import { getGlobalRoot, initGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
66
66
  import { DAILY_TASK_NAME, buildDailyRunnerCommand, listRegisteredWorkspaces, registerWorkspace, runDailyMaintenance, } from './scheduler.js';
67
67
  import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, importVault, } from './importers.js';
68
- import { cmdCapture, cmdPreCompact, truncateCodePointSafe, sanitizeLogMessage } from './capture.js';
68
+ import { cmdCapture, cmdPreCompact, resolveLastSessionTranscript, truncateCodePointSafe, sanitizeLogMessage } from './capture.js';
69
+ import { readStdinBounded } from './stdin.js';
69
70
  import { auditMemories, appendAuditEvent, } from './audit.js';
70
71
  import { listApiKeys, revokeApiKey } from './auth.js';
71
72
  import { buildProvenanceCoverage } from './provenance-coverage.js';
@@ -143,6 +144,22 @@ function parseCountFlag(value) {
143
144
  const parsed = parseInt(String(value), 10);
144
145
  return Number.isFinite(parsed) && parsed >= 1 ? parsed : 0;
145
146
  }
147
+ function parseBudgetFlag(value, fallback) {
148
+ if (value === undefined)
149
+ return fallback;
150
+ // A value-less flag and a junk value are different typos; the --hops guard already splits them.
151
+ if (typeof value !== 'string') {
152
+ console.error('--budget requires an integer value (e.g. --budget 1500).');
153
+ process.exit(1);
154
+ }
155
+ // Number(), like the --hops guard: parseInt('12abc') is 12, silently accepting what this message rejects.
156
+ const parsed = Number(value);
157
+ if (!Number.isInteger(parsed) || parsed < 0) {
158
+ console.error(`Invalid --budget: "${value}". Must be a non-negative integer.`);
159
+ process.exit(1);
160
+ }
161
+ return parsed;
162
+ }
146
163
  /**
147
164
  * Emit an audit event against `hippoRoot`'s db. Opens its own short-lived
148
165
  * connection so callers don't have to thread a db handle. Swallows all errors
@@ -178,8 +195,8 @@ function requireInit(hippoRoot) {
178
195
  * H2: when HIPPO_REQUIRE_SERVER is set, the CLI must not silently fall back to
179
196
  * direct DB mode — a missing server then masks a real misconfiguration (the
180
197
  * configured HIPPO_API_KEY is also silently discarded on fallback). Throws a
181
- * clear, actionable error in that case; a no-op when the knob is unset, so
182
- * default behaviour is unchanged.
198
+ * clear error then. It guards only the routed writes (remember, forget, archive,
199
+ * promote); every other command opens the store directly, knob or not.
183
200
  */
184
201
  function failIfServerRequired(reason) {
185
202
  if (process.env['HIPPO_REQUIRE_SERVER']) {
@@ -233,10 +250,68 @@ async function runViaServerIfAvailable(hippoRoot, httpFn) {
233
250
  throw err;
234
251
  }
235
252
  }
236
- // Flags that NEVER take a value. Without this, a positional following the
237
- // flag is silently swallowed as its value (`invalidate --dry-run "X"` would
238
- // eat the pattern). Every existing --dry-run consumer reads it as boolean.
239
- const BOOLEAN_FLAGS = new Set(['dry-run']);
253
+ // Every switch the CLI reads. A value on one reads as on under Boolean() (`--fix=false` would fix)
254
+ // and as off under === true (`--pin=true` would not pin), so parseArgs and main() refuse one.
255
+ // tests/cli-parse-flag-equals.test.ts fails when a switch read is missing from this set.
256
+ export const BOOLEAN_FLAGS = new Set([
257
+ 'all', 'all-tenants', 'archive', 'auto', 'bad', 'bootstrap', 'classic', 'continuity',
258
+ 'cross-project', 'dry-run', 'equal-sources', 'error', 'evc-adaptive', 'extract',
259
+ 'filter-conflicts', 'fix', 'force', 'forget', 'git', 'global', 'good', 'graph-stream',
260
+ 'help', 'include-superseded', 'inferred', 'json', 'last-session', 'multihop', 'no-hooks',
261
+ 'no-learn', 'no-mmr', 'no-propagate', 'no-schedule', 'no-share', 'no-summarize-older',
262
+ 'observed', 'open', 'physics', 'pin', 'pinned-only', 'reject-loser', 'rerank-utility',
263
+ 'reset-physics', 'save-baseline', 'show-cases', 'stats', 'stdin',
264
+ 'strict', 'suite', 'value-aware', 'verified', 'version', 'why',
265
+ ]);
266
+ // Every flag some command reads. Anything else is a typo that no command would act on.
267
+ export const KNOWN_FLAGS = new Set([
268
+ ...BOOLEAN_FLAGS,
269
+ 'actual', 'artifact', 'artifact-ref', 'as-of', 'author', 'baseline', 'body', 'budget', 'card-id',
270
+ 'change', 'channel', 'chatgpt', 'class', 'claude', 'codex-home', 'compare', 'constraint', 'content',
271
+ 'context', 'contract', 'cursor', 'customer', 'days', 'depends-on', 'depth', 'description',
272
+ 'embedding-weight', 'entity', 'estimate', 'file', 'format', 'framing', 'fresh-tail', 'from', 'goal',
273
+ 'graph-hops', 'graph-seeds', 'history-path', 'hops', 'host', 'id', 'importance', 'include-recent',
274
+ 'instructions', 'keep', 'kind', 'label', 'layer', 'level', 'limit', 'link', 'local-bump', 'log-file',
275
+ 'markdown', 'max', 'max-cases', 'max-neighbors', 'min-mrr', 'min-results', 'min-score', 'mmr-lambda',
276
+ 'model', 'name', 'next', 'next-step', 'note', 'older-than', 'op', 'out', 'outcome', 'owner', 'parent',
277
+ 'path', 'policy', 'port', 'reason', 'repo', 'repos', 'reranker', 'reranker-top-k', 'resolution',
278
+ 'role', 'run', 'runtime', 'salience-threshold', 'scan', 'scope', 'session', 'session-id', 'since',
279
+ 'source', 'start-offset', 'started-at', 'state', 'status', 'step', 'steps', 'success', 'summary',
280
+ 'supersedes', 'tag', 'target', 'target-runtime', 'task', 'team', 'tenant', 'tenant-id', 'tests',
281
+ 'text', 'threshold', 'title', 'to', 'transcript', 'trigger', 'type', 'unit', 'value', 'vault',
282
+ ]);
283
+ // Commands that delete or hide memories: an unknown flag here stops the run instead of being ignored.
284
+ const DESTRUCTIVE_COMMANDS = new Set([
285
+ 'audit', 'dedup', 'forget', 'invalidate', 'reject', 'resolve', 'sleep', 'supersede',
286
+ ]);
287
+ // Commands that honour --dry-run. Any other command would ignore it and run for real.
288
+ const DRY_RUN_COMMANDS = new Set([
289
+ 'audit', 'capture', 'dedup', 'forget', 'import', 'invalidate', 'refine', 'setup', 'sleep',
290
+ ]);
291
+ // share and brief honour --dry-run in one form only; their other forms write for real.
292
+ function dryRunRefusal(command, args, flags) {
293
+ const isBrief = command === 'brief' || command === 'project-brief';
294
+ const onlyForm = command === 'share' ? 'share --auto' : isBrief ? `${command} refresh` : null;
295
+ const honoured = command === 'share' ? args[0] === '--auto' || Boolean(flags['auto'])
296
+ : isBrief ? args[0] === 'refresh' : DRY_RUN_COMMANDS.has(command);
297
+ if (honoured)
298
+ return null;
299
+ const where = onlyForm ? ` outside \`hippo ${onlyForm}\`` : '';
300
+ return `hippo ${command} has no --dry-run${where}, so it would run for real. Nothing was changed.`;
301
+ }
302
+ // Shared by both the separated and glued (`=`) forms so the list can't drift.
303
+ function isRepeatableFlag(key) {
304
+ return key === 'tag' || key === 'artifact' || key === 'link' || key === 'step' || key === 'constraint' || key === 'depends-on';
305
+ }
306
+ function pushRepeatableFlag(flags, key, value) {
307
+ if (Array.isArray(flags[key])) {
308
+ // SAFETY: Array.isArray just confirmed flags[key] is an array; the union has no other array member.
309
+ flags[key].push(value);
310
+ }
311
+ else {
312
+ flags[key] = [value];
313
+ }
314
+ }
240
315
  export function parseArgs(argv) {
241
316
  const [, , command = '', ...rest] = argv;
242
317
  const args = [];
@@ -249,26 +324,43 @@ export function parseArgs(argv) {
249
324
  break;
250
325
  }
251
326
  if (part.startsWith('--')) {
327
+ const eqIdx = part.indexOf('=');
328
+ if (eqIdx > 2) {
329
+ // Glued form has no following token to swallow, so BOOLEAN_FLAGS gets its
330
+ // own branch here instead of the swallow-avoidance short-circuit below.
331
+ const key = part.slice(2, eqIdx);
332
+ const value = part.slice(eqIdx + 1);
333
+ if (BOOLEAN_FLAGS.has(key)) {
334
+ flags[key] = value;
335
+ }
336
+ else if (isRepeatableFlag(key)) {
337
+ if (value !== '')
338
+ pushRepeatableFlag(flags, key, value);
339
+ }
340
+ else {
341
+ flags[key] = value === '' ? true : value;
342
+ }
343
+ i++;
344
+ continue;
345
+ }
252
346
  const key = part.slice(2);
253
347
  const next = rest[i + 1];
254
- if (!next || next.startsWith('--') || BOOLEAN_FLAGS.has(key)) {
348
+ if (BOOLEAN_FLAGS.has(key) && (next === 'true' || next === 'false')) {
349
+ // Kept as a value so main() rejects it, instead of `--pin true` pinning the text "... true".
350
+ flags[key] = next;
351
+ i += 2;
352
+ }
353
+ else if (!next || next.startsWith('--') || BOOLEAN_FLAGS.has(key)) {
255
354
  // Boolean flag
256
355
  flags[key] = true;
257
356
  i++;
258
357
  }
358
+ else if (isRepeatableFlag(key)) {
359
+ pushRepeatableFlag(flags, key, next);
360
+ i += 2;
361
+ }
259
362
  else {
260
- // Check if it's a repeatable flag (tag, artifact, link, step, constraint, depends-on)
261
- if (key === 'tag' || key === 'artifact' || key === 'link' || key === 'step' || key === 'constraint' || key === 'depends-on') {
262
- if (Array.isArray(flags[key])) {
263
- flags[key].push(next);
264
- }
265
- else {
266
- flags[key] = [next];
267
- }
268
- }
269
- else {
270
- flags[key] = next;
271
- }
363
+ flags[key] = next;
272
364
  i += 2;
273
365
  }
274
366
  }
@@ -602,7 +694,7 @@ async function cmdRemember(hippoRoot, text, flags) {
602
694
  if (flags['verified'])
603
695
  confidence = 'verified';
604
696
  // Compute schema fit against existing memories
605
- const existing = loadAllEntries(targetRoot);
697
+ const existing = loadAllEntries(targetRoot, resolveTenantId({}));
606
698
  const schemaFit = computeSchemaFit(text, rawTags, existing);
607
699
  // A3 envelope flags
608
700
  const kindFlagRaw = typeof flags['kind'] === 'string' ? flags['kind'] : undefined;
@@ -632,6 +724,7 @@ async function cmdRemember(hippoRoot, text, flags) {
632
724
  // A5 stub auth: stamp tenant_id from env (HIPPO_TENANT) so recall isolation
633
725
  // can filter on this row. Default tenant 'default' for unauthenticated CLI.
634
726
  const tenantId = resolveTenantId({});
727
+ const rememberConfig = loadConfig(targetRoot);
635
728
  const entry = createMemory(text, {
636
729
  layer: Layer.Episodic,
637
730
  tags: rawTags,
@@ -644,6 +737,7 @@ async function cmdRemember(hippoRoot, text, flags) {
644
737
  owner: ownerFlag,
645
738
  artifact_ref: artifactRefFlag,
646
739
  tenantId,
740
+ baseHalfLifeDays: rememberConfig.defaultHalfLifeDays,
647
741
  });
648
742
  // Auto-tag with path context
649
743
  const pathTags = extractPathTags(process.cwd());
@@ -660,7 +754,6 @@ async function cmdRemember(hippoRoot, text, flags) {
660
754
  entry.tags.push(scopeTag);
661
755
  }
662
756
  // Salience gate: decide if this memory is worth storing
663
- const rememberConfig = loadConfig(targetRoot);
664
757
  if (rememberConfig.salience.enabled && !Boolean(flags['pin']) && !Boolean(flags['force'])) {
665
758
  const salienceResult = computeSalience(text, entry.tags, existing, {
666
759
  recentWindow: rememberConfig.salience.recentWindow,
@@ -702,14 +795,16 @@ async function cmdRemember(hippoRoot, text, flags) {
702
795
  const facts = await extractFacts(entry.content, {
703
796
  apiKey,
704
797
  model: config.extraction.model,
798
+ onError: (msg) => console.error(` (extraction failed: ${msg})`),
705
799
  });
706
800
  if (facts.length > 0) {
707
801
  storeExtractedFacts(targetRoot, entry, facts);
708
802
  console.error(` extracted ${facts.length} fact(s)`);
709
803
  }
710
804
  }
711
- catch {
712
- // Extraction is best-effort — never block remember
805
+ catch (err) {
806
+ // Extraction is best-effort: report it, never block remember.
807
+ console.error(` (extraction failed: ${err instanceof Error ? err.message : String(err)})`);
713
808
  }
714
809
  }
715
810
  else if (shouldExtract && !apiKey) {
@@ -741,6 +836,7 @@ function cmdSupersede(hippoRoot, oldId, newContent, flags) {
741
836
  pinned,
742
837
  source: old.source,
743
838
  confidence: 'verified',
839
+ tenantId: old.tenantId,
744
840
  });
745
841
  // AT1: write the SUCCESSOR first. The rejection guard fires on the new
746
842
  // content — if it refuses, nothing has been mutated yet (the old ordering
@@ -768,7 +864,7 @@ function cmdSupersede(hippoRoot, oldId, newContent, flags) {
768
864
  }
769
865
  async function cmdRecall(hippoRoot, query, flags) {
770
866
  requireInit(hippoRoot);
771
- const budget = parseInt(String(flags['budget'] ?? '4000'), 10);
867
+ const budget = parseBudgetFlag(flags['budget'], 4000);
772
868
  const limit = parseLimitFlag(flags['limit']);
773
869
  const asJson = Boolean(flags['json']);
774
870
  const showWhy = Boolean(flags['why']);
@@ -798,8 +894,9 @@ async function cmdRecall(hippoRoot, query, flags) {
798
894
  // detected-project recalls would change shape.
799
895
  const recallExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
800
896
  const requestedScopeForFilter = recallExplicitScope || undefined;
801
- let localEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive');
802
- let globalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive') : [];
897
+ const loadSuperseded = includeSuperseded || Boolean(asOf);
898
+ let localEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive', loadSuperseded);
899
+ let globalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, requestedScopeForFilter, 'additive', loadSuperseded) : [];
803
900
  // v1.12.13 / C5 — WYSIATI counters. Track filter activity per the plan v3
804
901
  // Task 3 mapping table. dropped_pre_rank is the SUM of all non-budget
805
902
  // filter drops (pre-rank AND post-rank); its meaning is unchanged by C5.
@@ -1669,21 +1766,13 @@ async function cmdRecall(hippoRoot, query, flags) {
1669
1766
  console.log('No memories found for:', query);
1670
1767
  return;
1671
1768
  }
1672
- // Update retrieval metadata and persist
1673
- const updated = markRetrieved(results.map((r) => r.entry));
1769
+ const retrievedIds = results.map((r) => r.entry.id);
1674
1770
  const localIndex = loadIndex(hippoRoot);
1675
- // EVAL-ONLY ablation (see ablation.ts): under the recall flag, markRetrieved
1676
- // returns unmutated entries (ids preserved for outcome attribution below)
1677
- // and persistence is skipped - writeEntry on identical rows still refreshes
1678
- // updated_at, rewrites mirrors, and marks DAG parents dirty.
1679
- if (!isRecallBoostAblated()) {
1680
- for (const u of updated) {
1681
- const targetRoot = localIndex.entries[u.id] ? hippoRoot : (isInitialized(globalRoot) ? globalRoot : hippoRoot);
1682
- writeEntry(targetRoot, u);
1683
- }
1684
- }
1771
+ const strengthenedHere = strengthenRetrieved(hippoRoot, retrievedIds);
1772
+ if (isInitialized(globalRoot))
1773
+ strengthenRetrieved(globalRoot, retrievedIds.filter((id) => !strengthenedHere.has(id)));
1685
1774
  // Track last retrieval IDs for outcome command
1686
- localIndex.last_retrieval_ids = updated.map((u) => u.id);
1775
+ localIndex.last_retrieval_ids = retrievedIds;
1687
1776
  // LC1 F1 structural fix (docs/plans/2026-08-02-lc1-recall-trace-persistence.md):
1688
1777
  // ONE trace at hippoRoot (where last_retrieval_ids and outcome
1689
1778
  // attribution live). Write the trace FIRST, then fold its id into
@@ -1895,7 +1984,7 @@ async function cmdRecall(hippoRoot, query, flags) {
1895
1984
  }
1896
1985
  async function cmdExplain(hippoRoot, query, flags) {
1897
1986
  requireInit(hippoRoot);
1898
- const budget = parseInt(String(flags['budget'] ?? '4000'), 10);
1987
+ const budget = parseBudgetFlag(flags['budget'], 4000);
1899
1988
  const limit = parseLimitFlag(flags['limit']);
1900
1989
  const asJson = Boolean(flags['json']);
1901
1990
  const forcePhysics = Boolean(flags['physics']);
@@ -1915,8 +2004,9 @@ async function cmdExplain(hippoRoot, query, flags) {
1915
2004
  // command honest for an operator debugging a hidden row.
1916
2005
  const explainExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
1917
2006
  const explainRequestedScope = explainExplicitScope || undefined;
1918
- let explainLocalEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, explainRequestedScope, 'additive');
1919
- let explainGlobalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, explainRequestedScope, 'additive') : [];
2007
+ const explainLoadSuperseded = explainIncludeSuperseded || Boolean(explainAsOf);
2008
+ let explainLocalEntries = loadRecallSearchEntries(hippoRoot, query, undefined, tenantId, explainRequestedScope, 'additive', explainLoadSuperseded);
2009
+ let explainGlobalEntries = isInitialized(globalRoot) ? loadRecallSearchEntries(globalRoot, query, undefined, tenantId, explainRequestedScope, 'additive', explainLoadSuperseded) : [];
1920
2010
  const passesExplainScope = (e) => api.passesCliRecallScopeFilter(e.scope ?? null, explainRequestedScope);
1921
2011
  explainLocalEntries = explainLocalEntries.filter(passesExplainScope);
1922
2012
  explainGlobalEntries = explainGlobalEntries.filter(passesExplainScope);
@@ -2320,6 +2410,7 @@ function cmdTraceRecord(hippoRoot, flags) {
2320
2410
  source: String(flags['source'] ?? 'cli'),
2321
2411
  trace_outcome: outcome,
2322
2412
  source_session_id: sessionId,
2413
+ tenantId: resolveTenantId({}),
2323
2414
  });
2324
2415
  writeEntry(hippoRoot, entry);
2325
2416
  console.log(`Recorded trace ${entry.id} (outcome=${outcome}, ${steps.length} steps)`);
@@ -2484,7 +2575,7 @@ export function learnFromMemoryMd(hippoRoot, homeDir = os.homedir()) {
2484
2575
  }
2485
2576
  if (memoryDirs.length === 0)
2486
2577
  return 0;
2487
- const existing = loadAllEntries(hippoRoot);
2578
+ const existing = loadAllEntries(hippoRoot, resolveTenantId({}));
2488
2579
  let imported = 0;
2489
2580
  let skippedSecret = 0;
2490
2581
  // AT1 (plan §3 containment): a rejection guard refusal is per-VALUE — one
@@ -2527,6 +2618,7 @@ export function learnFromMemoryMd(hippoRoot, homeDir = os.homedir()) {
2527
2618
  tags: ['claude-code-memory'],
2528
2619
  source: `claude-memory:${file}`,
2529
2620
  confidence: 'observed',
2621
+ tenantId: resolveTenantId({}),
2530
2622
  });
2531
2623
  try {
2532
2624
  writeEntry(hippoRoot, entry);
@@ -2675,11 +2767,11 @@ export function renderSleepResult(result) {
2675
2767
  parts.push(`${epiDups} duplicate episodic lessons`);
2676
2768
  if (crossDups > 0)
2677
2769
  parts.push(`${crossDups} cross-layer duplicates`);
2678
- console.log(`\nDeduped ${removed} duplicates (${parts.join(', ')}). Kept stronger copies.`);
2770
+ console.log(`\n${result.dryRun ? 'Would dedupe' : 'Deduped'} ${removed} duplicates (${parts.join(', ')}). ${result.dryRun ? 'Would keep' : 'Kept'} stronger copies.`);
2679
2771
  }
2680
2772
  if (result.audit) {
2681
2773
  if (result.audit.errorsRemoved > 0) {
2682
- console.log(`\nAudit: removed ${result.audit.errorsRemoved} junk memories (too short/empty).`);
2774
+ console.log(`\nAudit: ${result.dryRun ? 'would remove' : 'removed'} ${result.audit.errorsRemoved} junk memories (too short/empty).`);
2683
2775
  }
2684
2776
  if (result.audit.warningCount > 0) {
2685
2777
  console.log(`Audit: ${result.audit.warningCount} low-quality memories detected (run \`hippo audit\` for details).`);
@@ -2704,7 +2796,10 @@ async function cmdSleepCore(hippoRoot, flags) {
2704
2796
  requireInit(hippoRoot);
2705
2797
  // Phase 1: Auto-learn from git + MEMORY.md (CLI-only, uses process.cwd() / os.homedir()).
2706
2798
  // Stays in cli.ts; api.sleep covers Phase 2-6 only.
2707
- if (!flags['no-learn']) {
2799
+ if (!flags['no-learn'] && flags['dry-run']) {
2800
+ console.log('Dry run: skipped learning from git commits and MEMORY.md files.');
2801
+ }
2802
+ else if (!flags['no-learn']) {
2708
2803
  const config = loadConfig(hippoRoot);
2709
2804
  if (config.autoLearnOnSleep && isGitRepo(process.cwd())) {
2710
2805
  const { added } = learnFromRepo(hippoRoot, process.cwd(), 1);
@@ -2776,7 +2871,7 @@ function cmdLastSleep(flags) {
2776
2871
  // X8: session-event content is capped at print time only — the shared
2777
2872
  // printSessionEvents stays untouched for every other caller.
2778
2873
  const COMPACT_RESUME_EVENT_CONTENT_CAP = 400;
2779
- function cmdCompactResume(hippoRoot, stdinText) {
2874
+ function cmdCompactResume(hippoRoot, stdinText, stdinTimedOut) {
2780
2875
  try {
2781
2876
  // X3: gate on the non-exiting isInitialized check before any
2782
2877
  // store-opening call (loadActiveTaskSnapshot/listSessionEvents both
@@ -2791,7 +2886,9 @@ function cmdCompactResume(hippoRoot, stdinText) {
2791
2886
  // carries a different source (e.g. 'startup') means the matcher-based
2792
2887
  // gate failed to apply — stay silent rather than print stale state.
2793
2888
  const nonEmptyStdin = !!stdinText && stdinText.trim() !== '';
2794
- let suppressOutput = false;
2889
+ // Without a payload session_id the X5 cross-restore guard below can
2890
+ // never fire, so a timed-out empty read must not reach the print path.
2891
+ let suppressOutput = stdinTimedOut && !nonEmptyStdin;
2795
2892
  let payloadSessionId = null;
2796
2893
  if (nonEmptyStdin) {
2797
2894
  let payload = null;
@@ -2872,30 +2969,23 @@ function cmdCompactResume(hippoRoot, stdinText) {
2872
2969
  */
2873
2970
  async function cmdSessionEnd(hippoRoot, flags) {
2874
2971
  const logFile = typeof flags['log-file'] === 'string' ? flags['log-file'] : null;
2875
- // Read stdin synchronously. The SessionEnd hook payload carries
2876
- // `transcript_path` as JSON; we extract it here and pass it to the worker
2877
- // via argv so the detached child doesn't need to inherit stdin.
2878
- // DF1 T3 (docs/plans/2026-08-23-df1-snapshot-lifecycle.md): `session_id`
2879
- // is extracted the same way, so the worker can close the ending session's
2880
- // own active task snapshot after sleep+capture finish.
2881
- let transcriptPath = null;
2972
+ // Bounded read (DF1 T3, docs/plans/2026-08-23-df1-snapshot-lifecycle.md):
2973
+ // extracts transcript_path + session_id for the detached worker's argv.
2882
2974
  let sessionId = null;
2975
+ const { text: stdinText, timedOut: stdinTimedOut } = await readStdinBounded();
2883
2976
  try {
2884
- const stdinText = fs.readFileSync(0, 'utf8');
2885
2977
  if (stdinText && stdinText.trim().startsWith('{')) {
2886
2978
  const payload = JSON.parse(stdinText);
2887
- if (typeof payload.transcript_path === 'string') {
2888
- transcriptPath = payload.transcript_path;
2889
- }
2890
2979
  if (typeof payload.session_id === 'string') {
2891
2980
  sessionId = payload.session_id;
2892
2981
  }
2893
2982
  }
2894
2983
  }
2895
2984
  catch {
2896
- // No stdin, not JSON, or read failure — capture will fall back to
2897
- // transcript auto-discovery; the snapshot close below will no-op.
2985
+ // No stdin, not JSON, or read failure: the snapshot close below will no-op.
2898
2986
  }
2987
+ // Resolved here because only this process saw the payload; the worker captures just the path it is handed.
2988
+ const transcriptPath = resolveLastSessionTranscript(undefined, stdinText, stdinTimedOut);
2899
2989
  const workerArgs = [process.argv[1], '__session-end-worker'];
2900
2990
  if (logFile)
2901
2991
  workerArgs.push('--log-file', logFile);
@@ -2912,11 +3002,11 @@ async function cmdSessionEnd(hippoRoot, flags) {
2912
3002
  child.unref();
2913
3003
  }
2914
3004
  catch (err) {
2915
- // If spawn fails, run inline as a last resort — better late output than
2916
- // no consolidation at all. NOTE: `flags` carries neither --transcript nor
2917
- // --session-id (both are stdin-derived, argv-only for the child), so in
2918
- // this fallback capture auto-discovers the transcript and the DF1
2919
- // snapshot close no-ops — the ambient freshness bound is the backstop.
3005
+ // If spawn fails, run inline as a last resort, handed what the child's argv would have carried.
3006
+ if (transcriptPath)
3007
+ flags['transcript'] = transcriptPath;
3008
+ if (sessionId)
3009
+ flags['session-id'] = sessionId;
2920
3010
  await cmdSessionEndWorker(hippoRoot, flags);
2921
3011
  return;
2922
3012
  }
@@ -2959,19 +3049,22 @@ async function cmdSessionEndWorker(hippoRoot, flags) {
2959
3049
  // `[hippo] sleep failed: ...` line. Continue to capture regardless.
2960
3050
  }
2961
3051
  try {
2962
- const captureOpts = {
2963
- source: 'last-session',
2964
- transcriptPath: typeof flags['transcript'] === 'string'
2965
- ? flags['transcript']
2966
- : undefined,
2967
- logFile: typeof flags['log-file'] === 'string'
2968
- ? flags['log-file']
2969
- : undefined,
2970
- dryRun: false,
2971
- global: false,
2972
- tenantId: resolveTenantId({}),
2973
- };
2974
- cmdCapture(hippoRoot, captureOpts);
3052
+ const logFile = typeof flags['log-file'] === 'string' ? flags['log-file'] : undefined;
3053
+ const transcriptPath = typeof flags['transcript'] === 'string' ? flags['transcript'] : undefined;
3054
+ // With no stdin of its own, capture would read this as a manual run and scan every project.
3055
+ if (!transcriptPath) {
3056
+ appendSessionEndCloseLog(logFile ?? null, 'skip capture: no transcript for this session');
3057
+ }
3058
+ else {
3059
+ cmdCapture(hippoRoot, {
3060
+ source: 'last-session',
3061
+ transcriptPath,
3062
+ logFile,
3063
+ dryRun: false,
3064
+ global: false,
3065
+ tenantId: resolveTenantId({}),
3066
+ });
3067
+ }
2975
3068
  }
2976
3069
  catch {
2977
3070
  // Same treatment — the failure line is already in the log.
@@ -3154,6 +3247,11 @@ async function cmdCodexSessionEndWorker(hippoRoot, flags) {
3154
3247
  startOffsetBytes,
3155
3248
  startedAtMs,
3156
3249
  }) ?? undefined;
3250
+ // No Codex transcript must not fall through to the scan of Claude Code projects.
3251
+ if (!transcriptPath) {
3252
+ appendSessionEndCloseLog(logFile ?? null, 'skip capture: no Codex transcript for this session');
3253
+ return;
3254
+ }
3157
3255
  const captureOpts = {
3158
3256
  source: 'last-session',
3159
3257
  transcriptPath,
@@ -3168,13 +3266,13 @@ async function cmdCodexSessionEndWorker(hippoRoot, flags) {
3168
3266
  // capture path logs its own failures
3169
3267
  }
3170
3268
  }
3171
- function shouldAutoRepairCodexWrapper(currentCommand, currentArgs) {
3269
+ export function shouldAutoRepairCodexWrapper(currentCommand, flags) {
3172
3270
  if (process.env.HIPPO_SKIP_AUTO_INTEGRATIONS === '1')
3173
3271
  return false;
3174
3272
  if (!['context', 'remember', 'recall', 'sleep', 'capture', 'outcome', 'status', 'init'].includes(currentCommand)) {
3175
3273
  return false;
3176
3274
  }
3177
- if (currentCommand === 'init' && currentArgs.includes('--no-hooks'))
3275
+ if (currentCommand === 'init' && flags['no-hooks'] === true)
3178
3276
  return false;
3179
3277
  return true;
3180
3278
  }
@@ -3183,8 +3281,8 @@ function shouldAutoRepairCodexWrapper(currentCommand, currentArgs) {
3183
3281
  // shim). Never first-installs — silently swapping the codex binary on routine
3184
3282
  // commands is a consent violation and reads as binary hijacking to
3185
3283
  // supply-chain scanners (issue #133).
3186
- function maybeRepairCodexWrapper(currentCommand, currentArgs) {
3187
- if (!shouldAutoRepairCodexWrapper(currentCommand, currentArgs))
3284
+ function maybeRepairCodexWrapper(currentCommand, flags) {
3285
+ if (!shouldAutoRepairCodexWrapper(currentCommand, flags))
3188
3286
  return;
3189
3287
  try {
3190
3288
  repairCodexWrapperIfInstalled();
@@ -3406,8 +3504,7 @@ function cmdForget(hippoRoot, id, flags) {
3406
3504
  if (/append-only/i.test(msg)) {
3407
3505
  // The delete was refused by the append-only trigger — this is a raw
3408
3506
  // memory, not a missing one. Point the user at the archive path.
3409
- console.error(`Cannot forget ${id}: it is a raw, append-only memory. ` +
3410
- `Archive it instead: hippo forget ${id} --archive --reason "<why>"`);
3507
+ console.error(rawForgetRefusal(id));
3411
3508
  }
3412
3509
  else {
3413
3510
  console.error(`Memory not found: ${id}`);
@@ -3415,6 +3512,28 @@ function cmdForget(hippoRoot, id, flags) {
3415
3512
  process.exit(1);
3416
3513
  }
3417
3514
  }
3515
+ function rawForgetRefusal(id) {
3516
+ return `Cannot forget ${id}: it is a raw, append-only memory. Archive it instead: hippo forget ${id} --archive --reason "<why>"`;
3517
+ }
3518
+ // Refuses exactly where the real run would, so "Would forget" is a promise, not a guess.
3519
+ function previewForget(hippoRoot, id, archive) {
3520
+ requireInit(hippoRoot);
3521
+ const entry = readEntry(hippoRoot, id, resolveTenantId({}));
3522
+ if (!entry) {
3523
+ console.error(`Memory not found: ${id}`);
3524
+ process.exit(1);
3525
+ }
3526
+ if (!archive && entry.kind === 'raw') {
3527
+ console.error(rawForgetRefusal(id));
3528
+ process.exit(1);
3529
+ }
3530
+ if (archive && entry.kind !== 'raw') {
3531
+ console.error(`Could not archive ${id}: memory ${id} is not raw (kind=${entry.kind})`);
3532
+ process.exit(1);
3533
+ }
3534
+ const snippet = entry.content.length > 80 ? `${entry.content.slice(0, 80)}...` : entry.content;
3535
+ console.log(`Would ${archive ? 'archive' : 'forget'} ${id} (dry run, nothing changed): "${snippet}"`);
3536
+ }
3418
3537
  function cmdInspect(hippoRoot, id) {
3419
3538
  requireInit(hippoRoot);
3420
3539
  const entry = readEntry(hippoRoot, id, resolveTenantId({}));
@@ -4122,10 +4241,8 @@ function cmdCard(hippoRoot, args, flags) {
4122
4241
  const allowedFlags = CARD_SUBCOMMAND_FLAGS[subcommand];
4123
4242
  for (const key of Object.keys(flags)) {
4124
4243
  if (!allowedFlags.includes(key)) {
4125
- // --flag=value never splits on '=' (see parseArgs), so it lands here as one long key.
4126
- const hint = key.includes('=') ? ` Use --${key.slice(0, key.indexOf('='))} <value>, not --${key}.` : '';
4127
4244
  const valid = allowedFlags.length > 0 ? allowedFlags.map((f) => `--${f}`).join(', ') : '(none)';
4128
- console.error(`Unknown flag --${key} for hippo card ${subcommand}.${hint} Valid flags: ${valid}`);
4245
+ console.error(`Unknown flag --${key} for hippo card ${subcommand}. Valid flags: ${valid}`);
4129
4246
  process.exit(1);
4130
4247
  }
4131
4248
  }
@@ -5915,7 +6032,7 @@ async function cmdContext(hippoRoot, args, flags, stdinText) {
5915
6032
  if (!pinnedOnly) {
5916
6033
  requireInit(hippoRoot);
5917
6034
  }
5918
- const budget = parseInt(String(flags['budget'] ?? '1500'), 10);
6035
+ const budget = parseBudgetFlag(flags['budget'], 1500);
5919
6036
  if (budget <= 0)
5920
6037
  return;
5921
6038
  // Resolve query: explicit args, --auto (git diff via CLI-side helper), or
@@ -6247,9 +6364,9 @@ async function cmdWatch(command, hippoRoot) {
6247
6364
  console.error('Command failed but .hippo not initialized. Run `hippo init` to enable auto-learn.');
6248
6365
  process.exit(exitCode);
6249
6366
  }
6250
- const entry = captureError(exitCode, stderr, command);
6367
+ const entry = captureError(exitCode, stderr, command, resolveTenantId({}));
6251
6368
  // Compute schema fit against existing memories
6252
- const existingWatch = loadAllEntries(hippoRoot);
6369
+ const existingWatch = loadAllEntries(hippoRoot, entry.tenantId);
6253
6370
  const watchFit = computeSchemaFit(entry.content, entry.tags, existingWatch);
6254
6371
  entry.schema_fit = watchFit;
6255
6372
  entry.half_life_days = deriveHalfLife(7, entry);
@@ -6335,14 +6452,9 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
6335
6452
  // the existing summary line.
6336
6453
  let rejected = 0;
6337
6454
  const gitLearnTags = ['error', 'git-learned'];
6338
- const existingForSchema = loadAllEntries(hippoRoot);
6455
+ const existingForSchema = loadAllEntries(hippoRoot, resolveTenantId({}));
6339
6456
  for (const lesson of lessons) {
6340
- // L9: learnFromRepo's existingForSchema load just above (the
6341
- // `loadAllEntries(hippoRoot)` call a few lines up) is host-wide and
6342
- // intentionally out-of-L9-scope per plan §11 (cli.ts is
6343
- // single-tenant-per-process). The tenantId arg here is a defensive no-op:
6344
- // deduplicateLesson's array overload ignores it; the parameter exists
6345
- // only to mirror the canonical caller pattern used elsewhere in cli.ts.
6457
+ // The array overload ignores the tenant arg; existingForSchema is already scoped.
6346
6458
  if (deduplicateLesson(existingForSchema, lesson, 0.7, resolveTenantId({}))) {
6347
6459
  skipped++;
6348
6460
  continue;
@@ -6361,6 +6473,7 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
6361
6473
  source: 'git-learn',
6362
6474
  confidence: 'observed',
6363
6475
  schema_fit: schemaFitVal,
6476
+ tenantId: resolveTenantId({}),
6364
6477
  });
6365
6478
  // Auto-tag with path context from the repo being learned
6366
6479
  const learnPathTags = extractPathTags(repoPath);
@@ -7242,7 +7355,8 @@ function cmdDag(hippoRoot, flags) {
7242
7355
  }
7243
7356
  function cmdAssemble(hippoRoot, sessionId, flags) {
7244
7357
  requireInit(hippoRoot);
7245
- const budget = typeof flags['budget'] === 'string' ? Number(flags['budget']) : undefined;
7358
+ // Absent stays undefined so the api default applies; the 0 fallback is unreachable.
7359
+ const budget = flags['budget'] === undefined ? undefined : parseBudgetFlag(flags['budget'], 0);
7246
7360
  const freshTailCount = typeof flags['fresh-tail'] === 'string' ? Number(flags['fresh-tail']) : undefined;
7247
7361
  const summarizeOlder = flags['no-summarize-older'] !== true;
7248
7362
  const scope = typeof flags['scope'] === 'string' && flags['scope'].length > 0
@@ -7273,7 +7387,8 @@ function cmdAssemble(hippoRoot, sessionId, flags) {
7273
7387
  function cmdDrillDown(hippoRoot, summaryId, flags) {
7274
7388
  requireInit(hippoRoot);
7275
7389
  const limit = typeof flags['limit'] === 'string' ? Number(flags['limit']) : undefined;
7276
- const budget = typeof flags['budget'] === 'string' ? Number(flags['budget']) : undefined;
7390
+ // Absent stays undefined so the api default applies; the 0 fallback is unreachable.
7391
+ const budget = flags['budget'] === undefined ? undefined : parseBudgetFlag(flags['budget'], 0);
7277
7392
  // v0.30 / E5: --depth N walks N levels down (default 1, hard cap 10).
7278
7393
  // L4 fold: reject out-of-range explicitly (no silent clamp).
7279
7394
  const rawDepth = typeof flags['depth'] === 'string' ? Number(flags['depth']) : undefined;
@@ -8573,25 +8688,16 @@ Examples:
8573
8688
  // ---------------------------------------------------------------------------
8574
8689
  // Entry point
8575
8690
  // ---------------------------------------------------------------------------
8576
- const { command, args, flags } = parseArgs(process.argv);
8577
- const hippoRoot = getHippoRoot(process.cwd());
8578
- async function main() {
8691
+ async function main(command, args, flags, hippoRoot) {
8579
8692
  if (command === '--version' || command === '-v' || flags['version']) {
8580
8693
  const __filename_local = fileURLToPath(import.meta.url);
8581
8694
  const __dirname_local = path.dirname(__filename_local);
8582
- const pkgPath = path.join(__dirname_local, '..', 'package.json');
8583
- let pkgJson;
8584
- try {
8585
- pkgJson = fs.readFileSync(pkgPath, 'utf-8');
8586
- }
8587
- catch {
8588
- pkgJson = fs.readFileSync(path.join(__dirname_local, '..', '..', 'package.json'), 'utf-8');
8589
- }
8695
+ const pkgJson = fs.readFileSync(path.join(__dirname_local, '..', 'package.json'), 'utf-8');
8590
8696
  const { version } = JSON.parse(pkgJson);
8591
8697
  console.log(version);
8592
8698
  process.exit(0);
8593
8699
  }
8594
- maybeRepairCodexWrapper(command, args);
8700
+ maybeRepairCodexWrapper(command, flags);
8595
8701
  /** Global --scope well-formedness guard (v1.26.2). parseArgs stores a value-less
8596
8702
  * flag as boolean true; downstream the 14 consumer sites either coerced that to
8597
8703
  * the literal scope string 'true' (recall filter/unlock input, wm session scope,
@@ -8603,6 +8709,44 @@ async function main() {
8603
8709
  console.error('--scope requires a non-empty value (e.g. --scope slack:private:C1).');
8604
8710
  process.exit(1);
8605
8711
  }
8712
+ // parseArgs stores a value-less flag as boolean true, and NaN then survives every
8713
+ // downstream guard because each comparison against it is false.
8714
+ const NUMERIC_FLAGS = [
8715
+ 'days', 'threshold', 'min-score', 'port', 'limit', 'mmr-lambda', 'local-bump',
8716
+ 'min-results', 'reranker-top-k', 'min-mrr', 'embedding-weight', 'max-cases',
8717
+ ];
8718
+ for (const key of NUMERIC_FLAGS) {
8719
+ const raw = flags[key];
8720
+ if (raw === undefined)
8721
+ continue;
8722
+ if (typeof raw !== 'string' || !raw.trim() || !Number.isFinite(Number(raw))) {
8723
+ console.error(`--${key} requires a numeric value.`);
8724
+ process.exit(1);
8725
+ }
8726
+ }
8727
+ // Reject rather than coerce: consumers read --dry-run both as Boolean() and === true,
8728
+ // so no single coercion of an inline value would be correct for every one of them.
8729
+ for (const key of BOOLEAN_FLAGS) {
8730
+ if (Object.hasOwn(flags, key) && typeof flags[key] !== 'boolean') {
8731
+ console.error(`--${key} takes no value`);
8732
+ process.exit(1);
8733
+ }
8734
+ }
8735
+ // card checks its flags per subcommand, with a stricter message.
8736
+ const unknownFlags = command === 'card' ? [] : Object.keys(flags).filter((key) => !KNOWN_FLAGS.has(key));
8737
+ if (unknownFlags.length > 0) {
8738
+ const names = unknownFlags.map((key) => `--${key}`).join(', ');
8739
+ if (DESTRUCTIVE_COMMANDS.has(command)) {
8740
+ console.error(`Unknown flag ${names} for hippo ${command}. Nothing was changed.`);
8741
+ process.exit(2);
8742
+ }
8743
+ console.error(`hippo: ignoring unknown flag ${names}. A later release will reject it.`);
8744
+ }
8745
+ const refusal = Object.hasOwn(flags, 'dry-run') ? dryRunRefusal(command, args, flags) : null;
8746
+ if (refusal) {
8747
+ console.error(refusal);
8748
+ process.exit(2);
8749
+ }
8606
8750
  switch (command) {
8607
8751
  case 'init':
8608
8752
  cmdInit(hippoRoot, flags);
@@ -8747,34 +8891,18 @@ async function main() {
8747
8891
  await cmdSessionEndWorker(hippoRoot, flags);
8748
8892
  break;
8749
8893
  case 'pre-compact': {
8750
- // Same TTY guard as `capture --last-session`: skip reading stdin when
8751
- // it's an interactive terminal so a manual invocation never hangs.
8752
- let stdinText;
8753
- if (!process.stdin.isTTY) {
8754
- try {
8755
- stdinText = fs.readFileSync(0, 'utf8');
8756
- }
8757
- catch {
8758
- stdinText = undefined;
8759
- }
8760
- }
8894
+ // Bounded wait, not a TTY guard: an idle non-TTY pipe must not hang.
8895
+ const { text: stdinText, timedOut: stdinTimedOut } = await readStdinBounded();
8761
8896
  await cmdPreCompact(hippoRoot, {
8762
8897
  stdinText,
8898
+ stdinTimedOut,
8763
8899
  logFile: typeof flags['log-file'] === 'string' ? flags['log-file'] : undefined,
8764
8900
  });
8765
8901
  break;
8766
8902
  }
8767
8903
  case 'compact-resume': {
8768
- let stdinText;
8769
- if (!process.stdin.isTTY) {
8770
- try {
8771
- stdinText = fs.readFileSync(0, 'utf8');
8772
- }
8773
- catch {
8774
- stdinText = undefined;
8775
- }
8776
- }
8777
- cmdCompactResume(hippoRoot, stdinText);
8904
+ const { text: stdinText, timedOut: stdinTimedOut } = await readStdinBounded();
8905
+ cmdCompactResume(hippoRoot, stdinText, stdinTimedOut);
8778
8906
  break;
8779
8907
  }
8780
8908
  case 'codex-run':
@@ -8810,7 +8938,7 @@ async function main() {
8810
8938
  break;
8811
8939
  }
8812
8940
  requireInit(hippoRoot);
8813
- const entries = loadAllEntries(hippoRoot);
8941
+ const entries = loadAllEntries(hippoRoot, resolveTenantId({}));
8814
8942
  const result = auditMemories(entries);
8815
8943
  const shouldFix = Boolean(flags['fix']);
8816
8944
  if (result.issues.length === 0) {
@@ -8824,13 +8952,15 @@ async function main() {
8824
8952
  console.log(` "${issue.content.slice(0, 80)}${issue.content.length > 80 ? '...' : ''}"`);
8825
8953
  }
8826
8954
  if (shouldFix) {
8827
- const errorIds = result.issues.filter(i => i.severity === 'error').map(i => i.memoryId);
8828
- if (errorIds.length > 0) {
8829
- for (const id of errorIds) {
8830
- deleteEntry(hippoRoot, id);
8831
- }
8832
- console.log(`\nRemoved ${errorIds.length} error-severity memories.`);
8833
- console.log(`${result.issues.length - errorIds.length} warnings remain (review manually).`);
8955
+ const errors = result.issues.filter(i => i.severity === 'error');
8956
+ if (errors.length > 0 && flags['dry-run'] === true) {
8957
+ console.log(`\nWould remove ${errors.length} error-severity memories (dry run, nothing deleted).`);
8958
+ console.log(`${result.issues.length - errors.length} warnings would remain (review manually).`);
8959
+ }
8960
+ else if (errors.length > 0) {
8961
+ const removedCount = errors.filter((issue) => deleteEntry(hippoRoot, issue.memoryId, { reason: `audit --fix: ${issue.reason}`, automatic: true })).length;
8962
+ console.log(`\nRemoved ${removedCount} error-severity memories.`);
8963
+ console.log(`${result.issues.length - errors.length} warnings remain (review manually).`);
8834
8964
  }
8835
8965
  else {
8836
8966
  console.log(`\nNo error-severity issues. Warnings require manual review.`);
@@ -8951,6 +9081,10 @@ async function main() {
8951
9081
  console.error(ARCHIVE_REASON_REQUIRED);
8952
9082
  process.exit(1);
8953
9083
  }
9084
+ if (flags['dry-run'] === true) {
9085
+ previewForget(hippoRoot, id, archive);
9086
+ break;
9087
+ }
8954
9088
  const routed = await runViaServerIfAvailable(hippoRoot, async (info, apiKey) => {
8955
9089
  try {
8956
9090
  if (archive) {
@@ -8987,23 +9121,9 @@ async function main() {
8987
9121
  break;
8988
9122
  }
8989
9123
  case 'context': {
8990
- // DF1 T2 (docs/plans/2026-08-23-df1-snapshot-lifecycle.md): same TTY
8991
- // guard as `pre-compact` / `compact-resume` above — skip reading stdin
8992
- // when it's an interactive terminal so a manual invocation never
8993
- // hangs. `hippo context` is both the hot UserPromptSubmit path
8994
- // (non-TTY, stdin carries the hook JSON with `session_id`) and a
8995
- // manually-invocable command (TTY, no payload) — the guardless read in
8996
- // cmdSessionEnd is the wrong sibling to copy here; it only runs under
8997
- // a hook that always supplies stdin.
8998
- let stdinText;
8999
- if (!process.stdin.isTTY) {
9000
- try {
9001
- stdinText = fs.readFileSync(0, 'utf8');
9002
- }
9003
- catch {
9004
- stdinText = undefined;
9005
- }
9006
- }
9124
+ // Bounded, not a TTY guard (DF1 T2, docs/plans/2026-08-23-df1-snapshot-lifecycle.md):
9125
+ // the hot stdin path and a manual run share this one command.
9126
+ const { text: stdinText } = await readStdinBounded();
9007
9127
  await cmdContext(hippoRoot, args, flags, stdinText);
9008
9128
  break;
9009
9129
  }
@@ -9130,7 +9250,7 @@ async function main() {
9130
9250
  requireInit(hippoRoot);
9131
9251
  const format = flags['format'] || 'json';
9132
9252
  const outputPath = args[0] || null;
9133
- const entries = loadAllEntries(hippoRoot);
9253
+ const entries = loadAllEntries(hippoRoot, resolveTenantId({}));
9134
9254
  let output;
9135
9255
  if (format === 'markdown' || format === 'md') {
9136
9256
  output = entries.map(e => {
@@ -9180,10 +9300,17 @@ async function main() {
9180
9300
  console.error('Usage: hippo capture --stdin|--file <path>|--last-session [--transcript <path>] [--log-file <path>] [--dry-run] [--global]');
9181
9301
  process.exit(1);
9182
9302
  }
9303
+ // Bounded, and only when last-session has no explicit path: the
9304
+ // --stdin source keeps its own blocking read in capture.ts by design.
9305
+ const bounded = captureSource === 'last-session' && !transcriptPath
9306
+ ? await readStdinBounded()
9307
+ : { text: undefined, timedOut: false };
9183
9308
  cmdCapture(hippoRoot, {
9184
9309
  source: captureSource,
9185
9310
  filePath: captureFile,
9186
9311
  transcriptPath,
9312
+ stdinText: bounded.text,
9313
+ stdinTimedOut: bounded.timedOut,
9187
9314
  logFile: typeof flags['log-file'] === 'string' ? flags['log-file'] : undefined,
9188
9315
  dryRun: Boolean(flags['dry-run']),
9189
9316
  global: Boolean(flags['global']),
@@ -9223,11 +9350,11 @@ async function main() {
9223
9350
  }
9224
9351
  const host = typeof flags['host'] === 'string' ? flags['host'] : '127.0.0.1';
9225
9352
  const { serve } = await import('./server.js');
9226
- const handle = await serve({ hippoRoot, port, host });
9353
+ const handle = await serve({ hippoRoot, port, host, handleSignals: true });
9227
9354
  console.log(`hippo serve listening on ${handle.url} (pid ${process.pid})`);
9228
9355
  console.log(`pidfile: ${path.join(hippoRoot, 'server.pid')}`);
9229
9356
  console.log('press Ctrl+C to stop');
9230
- // SIGINT/SIGTERM handlers wired in server.ts (skipped under VITEST). Hang.
9357
+ // The SIGINT/SIGTERM handlers stop the server and exit. Hang until then.
9231
9358
  await new Promise(() => { });
9232
9359
  break;
9233
9360
  }
@@ -9242,8 +9369,8 @@ async function main() {
9242
9369
  }
9243
9370
  const onlyId = typeof flags['id'] === 'string' ? flags['id'] : undefined;
9244
9371
  if (typeof flags['dry-run'] === 'string') {
9245
- // Unreachable via argv (dry-run is in BOOLEAN_FLAGS); guards
9246
- // programmatically-built flags objects.
9372
+ // Dead: the earlier global BOOLEAN_FLAGS guard now exits first on any --dry-run=<v>.
9373
+ // Kept as defence in depth on a destructive command.
9247
9374
  console.error('--dry-run takes no value');
9248
9375
  process.exit(1);
9249
9376
  }
@@ -9321,8 +9448,19 @@ async function main() {
9321
9448
  process.exit(1);
9322
9449
  }
9323
9450
  }
9324
- main().catch((err) => {
9325
- console.error('Error:', err.message ?? err);
9326
- process.exit(1);
9327
- });
9451
+ export async function runCli(argv = process.argv) {
9452
+ const { command, args, flags } = parseArgs(argv);
9453
+ try {
9454
+ await main(command, args, flags, getHippoRoot(process.cwd()));
9455
+ }
9456
+ catch (err) {
9457
+ console.error('Error:', err instanceof Error ? err.message : err);
9458
+ process.exit(1);
9459
+ }
9460
+ }
9461
+ // bin/hippo.js calls runCli(); this keeps `node dist/cli.js` working while an import runs nothing.
9462
+ const entryPath = process.argv[1];
9463
+ if (entryPath && fs.existsSync(entryPath) && fs.realpathSync(entryPath) === fileURLToPath(import.meta.url)) {
9464
+ void runCli();
9465
+ }
9328
9466
  //# sourceMappingURL=cli.js.map