hippo-memory 1.61.0 → 1.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/README.md +37 -53
  2. package/dist/agent-memories/apply.d.ts +3 -1
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +4 -1
  5. package/dist/agent-memories/claude-code.js +53 -9
  6. package/dist/agent-memories/report.d.ts +1 -0
  7. package/dist/agent-memories/report.js +2 -2
  8. package/dist/agent-memories/sync.d.ts +3 -3
  9. package/dist/agent-memories/sync.js +46 -24
  10. package/dist/agent-memories/types.d.ts +0 -2
  11. package/dist/ambient-store.d.ts +4 -4
  12. package/dist/ambient-store.js +4 -3
  13. package/dist/api/assemble.d.ts +7 -10
  14. package/dist/api/assemble.js +62 -66
  15. package/dist/api/audit.d.ts +2 -2
  16. package/dist/api/audit.js +2 -2
  17. package/dist/api/auth.d.ts +6 -6
  18. package/dist/api/auth.js +5 -7
  19. package/dist/api/context-select.d.ts +50 -0
  20. package/dist/api/context-select.js +342 -0
  21. package/dist/api/context-types.d.ts +12 -17
  22. package/dist/api/context.d.ts +5 -4
  23. package/dist/api/context.js +208 -535
  24. package/dist/api/dormant.js +1 -1
  25. package/dist/api/drill-down.d.ts +4 -4
  26. package/dist/api/drill-down.js +56 -41
  27. package/dist/api/outcome.d.ts +8 -13
  28. package/dist/api/outcome.js +13 -17
  29. package/dist/api/promote.d.ts +5 -8
  30. package/dist/api/promote.js +55 -66
  31. package/dist/api/quarantine.js +3 -2
  32. package/dist/api/recall-types.d.ts +43 -55
  33. package/dist/api/recall.d.ts +3 -3
  34. package/dist/api/recall.js +310 -456
  35. package/dist/api/remember.d.ts +2 -2
  36. package/dist/api/sleep.d.ts +7 -35
  37. package/dist/api/sleep.js +206 -220
  38. package/dist/api/tokens.d.ts +2 -2
  39. package/dist/api/tokens.js +2 -2
  40. package/dist/api/types.d.ts +6 -10
  41. package/dist/api/types.js +2 -4
  42. package/dist/audit-prune.d.ts +4 -6
  43. package/dist/audit-prune.js +3 -5
  44. package/dist/audit.js +11 -33
  45. package/dist/auth.d.ts +8 -9
  46. package/dist/auth.js +5 -7
  47. package/dist/autolearn.d.ts +1 -1
  48. package/dist/autolearn.js +1 -1
  49. package/dist/availability.js +3 -5
  50. package/dist/capture/command.d.ts +5 -4
  51. package/dist/capture/command.js +8 -22
  52. package/dist/capture/compact.d.ts +2 -2
  53. package/dist/capture/compact.js +11 -15
  54. package/dist/capture/extract.js +37 -115
  55. package/dist/capture-error.d.ts +1 -1
  56. package/dist/capture-error.js +1 -1
  57. package/dist/churn-git.d.ts +1 -1
  58. package/dist/churn-git.js +1 -1
  59. package/dist/cli/audit.js +3 -4
  60. package/dist/cli/auth.js +3 -6
  61. package/dist/cli/briefs.js +324 -306
  62. package/dist/cli/context.js +44 -34
  63. package/dist/cli/continuity.js +283 -271
  64. package/dist/cli/curate.d.ts +1 -1
  65. package/dist/cli/curate.js +43 -58
  66. package/dist/cli/dag.js +5 -9
  67. package/dist/cli/decisions.js +334 -345
  68. package/dist/cli/explain.js +68 -61
  69. package/dist/cli/goals.js +1 -1
  70. package/dist/cli/init.js +1 -1
  71. package/dist/cli/maintenance.js +62 -51
  72. package/dist/cli/playbooks.js +391 -379
  73. package/dist/cli/projects.js +11 -6
  74. package/dist/cli/recall.js +30 -44
  75. package/dist/cli/remember.js +118 -87
  76. package/dist/cli/session-hooks.js +106 -115
  77. package/dist/cli/setup.d.ts +1 -1
  78. package/dist/cli/setup.js +267 -250
  79. package/dist/cli/shared.js +3 -3
  80. package/dist/cli/slack.js +1 -1
  81. package/dist/cli/sleep.js +27 -1
  82. package/dist/cli/status.d.ts +4 -4
  83. package/dist/cli/status.js +80 -76
  84. package/dist/cli/transfer.js +88 -107
  85. package/dist/cli/usage.js +9 -6
  86. package/dist/cli.d.ts +1 -1
  87. package/dist/cli.js +4 -9
  88. package/dist/compaction-record.d.ts +2 -2
  89. package/dist/compaction-record.js +89 -68
  90. package/dist/compare.d.ts +11 -16
  91. package/dist/compare.js +11 -16
  92. package/dist/config.d.ts +19 -18
  93. package/dist/config.js +90 -67
  94. package/dist/connectors/github/backfill.d.ts +2 -2
  95. package/dist/connectors/github/backfill.js +8 -15
  96. package/dist/connectors/github/cli-impl.js +3 -8
  97. package/dist/connectors/github/deletion.d.ts +5 -12
  98. package/dist/connectors/github/deletion.js +5 -12
  99. package/dist/connectors/github/dlq.d.ts +6 -9
  100. package/dist/connectors/github/dlq.js +2 -3
  101. package/dist/connectors/github/ingest.d.ts +5 -7
  102. package/dist/connectors/github/ingest.js +8 -12
  103. package/dist/connectors/github/octokit-client.d.ts +3 -5
  104. package/dist/connectors/github/octokit-client.js +5 -6
  105. package/dist/connectors/github/signature.d.ts +9 -39
  106. package/dist/connectors/github/signature.js +9 -39
  107. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  108. package/dist/connectors/github/tenant-routing.js +1 -1
  109. package/dist/connectors/github/transform.js +2 -2
  110. package/dist/connectors/github/types.d.ts +2 -10
  111. package/dist/connectors/github/types.js +1 -3
  112. package/dist/connectors/slack/deletion.d.ts +3 -8
  113. package/dist/connectors/slack/deletion.js +3 -8
  114. package/dist/connectors/slack/dlq.d.ts +1 -1
  115. package/dist/connectors/slack/ingest.d.ts +1 -1
  116. package/dist/connectors/slack/ingest.js +7 -16
  117. package/dist/connectors/slack/signature.d.ts +1 -1
  118. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  119. package/dist/connectors/slack/tenant-routing.js +3 -5
  120. package/dist/connectors/slack/transform.d.ts +5 -6
  121. package/dist/connectors/slack/transform.js +5 -6
  122. package/dist/connectors/slack/types.d.ts +2 -6
  123. package/dist/connectors/slack/types.js +1 -3
  124. package/dist/connectors/slack/web-client.js +10 -3
  125. package/dist/connectors/slack/workspaces.d.ts +3 -5
  126. package/dist/connectors/slack/workspaces.js +3 -5
  127. package/dist/consolidate/conflicts.js +3 -14
  128. package/dist/consolidate/decay.js +9 -29
  129. package/dist/consolidate/llm-passes.js +4 -5
  130. package/dist/consolidate/merge.js +8 -23
  131. package/dist/consolidate/run.d.ts +1 -8
  132. package/dist/consolidate/run.js +3 -25
  133. package/dist/consolidate/sleep.js +5 -17
  134. package/dist/consolidate/traces.js +9 -21
  135. package/dist/customer-notes.d.ts +5 -7
  136. package/dist/customer-notes.js +82 -76
  137. package/dist/dag.d.ts +10 -21
  138. package/dist/dag.js +189 -203
  139. package/dist/db/continuity.js +2 -2
  140. package/dist/db/migrations/v14.js +1 -1
  141. package/dist/db/migrations/v15.js +1 -2
  142. package/dist/db/migrations/v16.js +3 -4
  143. package/dist/db/migrations/v17.js +2 -3
  144. package/dist/db/migrations/v19.js +1 -1
  145. package/dist/db/migrations/v20.js +1 -1
  146. package/dist/db/migrations/v21.js +2 -6
  147. package/dist/db/migrations/v22.js +2 -4
  148. package/dist/db/migrations/v23.js +1 -1
  149. package/dist/db/migrations/v24.js +4 -6
  150. package/dist/db/migrations/v25.js +2 -3
  151. package/dist/db/migrations/v26.js +3 -3
  152. package/dist/db/migrations/v27.js +2 -10
  153. package/dist/db/migrations/v28.js +5 -8
  154. package/dist/db/migrations/v29.js +3 -4
  155. package/dist/db/migrations/v30.js +2 -2
  156. package/dist/db/migrations/v31.js +1 -1
  157. package/dist/db/migrations/v32.js +1 -1
  158. package/dist/db/migrations/v33.js +3 -3
  159. package/dist/db/migrations/v34.js +1 -1
  160. package/dist/db/migrations/v35.js +3 -4
  161. package/dist/db/migrations/v36.js +3 -4
  162. package/dist/db/migrations/v37.js +5 -5
  163. package/dist/db/migrations/v38.js +7 -8
  164. package/dist/db/migrations/v39.js +1 -1
  165. package/dist/db/migrations/v40.js +4 -16
  166. package/dist/db/migrations/v41.js +3 -4
  167. package/dist/db/migrations/v42.js +3 -4
  168. package/dist/db/migrations/v45.js +1 -1
  169. package/dist/db/migrations/v46.js +1 -1
  170. package/dist/db/migrations/v47.js +1 -1
  171. package/dist/db/migrations/v48.js +1 -1
  172. package/dist/decisions.d.ts +2 -2
  173. package/dist/decisions.js +97 -80
  174. package/dist/dedupe.js +86 -61
  175. package/dist/delivery-recorder.js +154 -135
  176. package/dist/doctor.js +129 -110
  177. package/dist/dormant.js +1 -4
  178. package/dist/embedding-provider.d.ts +4 -8
  179. package/dist/embedding-provider.js +4 -8
  180. package/dist/embeddings.js +55 -47
  181. package/dist/env.d.ts +1 -1
  182. package/dist/env.js +12 -12
  183. package/dist/escape.d.ts +5 -0
  184. package/dist/escape.js +10 -0
  185. package/dist/eval-stats.d.ts +1 -2
  186. package/dist/eval-stats.js +1 -2
  187. package/dist/eval-suite.js +27 -21
  188. package/dist/extract.js +4 -9
  189. package/dist/failure-log.d.ts +3 -3
  190. package/dist/failure-log.js +1 -1
  191. package/dist/forward-claim-detector.d.ts +2 -4
  192. package/dist/forward-claim-detector.js +6 -11
  193. package/dist/goals.d.ts +3 -3
  194. package/dist/goals.js +103 -91
  195. package/dist/graph/read.d.ts +2 -2
  196. package/dist/graph/read.js +5 -6
  197. package/dist/graph/types.d.ts +8 -8
  198. package/dist/graph/write.d.ts +7 -14
  199. package/dist/graph/write.js +16 -23
  200. package/dist/graph-extract.d.ts +7 -8
  201. package/dist/graph-extract.js +62 -72
  202. package/dist/graph-recall.d.ts +2 -2
  203. package/dist/graph-recall.js +55 -49
  204. package/dist/graph-stream.d.ts +5 -6
  205. package/dist/graph-stream.js +66 -57
  206. package/dist/graph-view.d.ts +2 -2
  207. package/dist/graph-view.js +7 -7
  208. package/dist/half-life-migration.d.ts +1 -2
  209. package/dist/half-life-migration.js +2 -3
  210. package/dist/hooks/codex-session.js +1 -1
  211. package/dist/hooks/codex-wrapper.d.ts +1 -1
  212. package/dist/hooks/codex-wrapper.js +3 -2
  213. package/dist/hooks/json-hooks.d.ts +2 -2
  214. package/dist/hooks/json-hooks.js +5 -4
  215. package/dist/hooks/opencode.d.ts +1 -1
  216. package/dist/hooks/opencode.js +5 -4
  217. package/dist/hooks/shared.d.ts +3 -7
  218. package/dist/hooks/shared.js +1 -8
  219. package/dist/http-util.d.ts +2 -3
  220. package/dist/http-util.js +3 -0
  221. package/dist/importers/core.d.ts +2 -9
  222. package/dist/importers/core.js +15 -30
  223. package/dist/importers/sources.js +2 -1
  224. package/dist/importers/vault.js +2 -20
  225. package/dist/incidents.d.ts +1 -1
  226. package/dist/incidents.js +46 -39
  227. package/dist/instruction-detect.d.ts +1 -1
  228. package/dist/instruction-detect.js +1 -1
  229. package/dist/invalidation.d.ts +3 -0
  230. package/dist/invalidation.js +160 -114
  231. package/dist/json.d.ts +5 -0
  232. package/dist/json.js +4 -0
  233. package/dist/judgment.js +1 -2
  234. package/dist/local-embedding.js +1 -1
  235. package/dist/mcp/admin-tools.js +7 -17
  236. package/dist/mcp/format.js +1 -1
  237. package/dist/mcp/framing.js +3 -6
  238. package/dist/mcp/protocol.d.ts +2 -5
  239. package/dist/mcp/protocol.js +1 -3
  240. package/dist/mcp/recall-tools.js +12 -15
  241. package/dist/mcp/request.js +4 -3
  242. package/dist/mcp/session-state.js +2 -3
  243. package/dist/mcp/stdio.js +2 -1
  244. package/dist/mcp/tools.js +9 -6
  245. package/dist/memory-value-weights.d.ts +5 -8
  246. package/dist/memory-value-weights.js +5 -8
  247. package/dist/memory-value.d.ts +13 -13
  248. package/dist/memory-value.js +26 -37
  249. package/dist/memory.d.ts +20 -22
  250. package/dist/memory.js +24 -48
  251. package/dist/multihop.d.ts +1 -1
  252. package/dist/multihop.js +3 -2
  253. package/dist/owner-validation.d.ts +4 -5
  254. package/dist/owner-validation.js +4 -5
  255. package/dist/physics.d.ts +4 -4
  256. package/dist/physics.js +7 -9
  257. package/dist/policies.d.ts +9 -10
  258. package/dist/policies.js +96 -81
  259. package/dist/postinstall.js +3 -6
  260. package/dist/predictions/planning-fallacy.d.ts +9 -14
  261. package/dist/predictions/planning-fallacy.js +10 -16
  262. package/dist/predictions/store.d.ts +15 -23
  263. package/dist/predictions/store.js +36 -33
  264. package/dist/processes.d.ts +2 -7
  265. package/dist/processes.js +88 -72
  266. package/dist/project-briefs.d.ts +2 -3
  267. package/dist/project-briefs.js +141 -118
  268. package/dist/project-identity.d.ts +22 -9
  269. package/dist/project-identity.js +47 -12
  270. package/dist/project-merge.d.ts +28 -5
  271. package/dist/project-merge.js +213 -46
  272. package/dist/project-remote.d.ts +12 -0
  273. package/dist/project-remote.js +138 -0
  274. package/dist/prompt-recall.js +1 -2
  275. package/dist/rate-limit.d.ts +1 -1
  276. package/dist/rate-limit.js +1 -1
  277. package/dist/raw-archive.d.ts +9 -0
  278. package/dist/raw-archive.js +70 -53
  279. package/dist/recall-history.d.ts +19 -20
  280. package/dist/recall-history.js +24 -42
  281. package/dist/recall-pipeline.js +4 -28
  282. package/dist/recall-scope.d.ts +7 -8
  283. package/dist/recall-scope.js +7 -8
  284. package/dist/recall-trace.d.ts +5 -9
  285. package/dist/recall-trace.js +6 -10
  286. package/dist/refine-llm.d.ts +1 -1
  287. package/dist/refine-llm.js +2 -2
  288. package/dist/reject-flow.d.ts +3 -4
  289. package/dist/reject-flow.js +122 -117
  290. package/dist/rejection.d.ts +5 -6
  291. package/dist/rejection.js +7 -15
  292. package/dist/rerankers/clef.d.ts +1 -1
  293. package/dist/rerankers/jev.d.ts +1 -2
  294. package/dist/rerankers/jev.js +4 -5
  295. package/dist/rerankers/llm.d.ts +1 -2
  296. package/dist/rerankers/llm.js +1 -2
  297. package/dist/rerankers/types.d.ts +1 -2
  298. package/dist/rrf.d.ts +2 -2
  299. package/dist/rrf.js +2 -2
  300. package/dist/search/bm25-search.d.ts +1 -1
  301. package/dist/search/bm25-search.js +2 -1
  302. package/dist/search/boosts.js +2 -1
  303. package/dist/search/hybrid.d.ts +1 -1
  304. package/dist/search/hybrid.js +2 -1
  305. package/dist/search/physics-search.d.ts +1 -1
  306. package/dist/search/physics-search.js +2 -1
  307. package/dist/search/types.d.ts +2 -0
  308. package/dist/search/types.js +3 -1
  309. package/dist/secret-detect.d.ts +4 -5
  310. package/dist/secret-detect.js +6 -10
  311. package/dist/server/auth.js +5 -5
  312. package/dist/server/client-ip.js +1 -1
  313. package/dist/server/cursor.js +2 -1
  314. package/dist/server/mcp-http.js +4 -4
  315. package/dist/server/request.d.ts +3 -6
  316. package/dist/server/request.js +6 -7
  317. package/dist/server/routes/admin.js +5 -4
  318. package/dist/server/routes/customer-notes.js +6 -5
  319. package/dist/server/routes/decisions.js +4 -3
  320. package/dist/server/routes/incidents.js +7 -5
  321. package/dist/server/routes/memories.js +7 -7
  322. package/dist/server/routes/policies.js +3 -2
  323. package/dist/server/routes/predictions.js +12 -15
  324. package/dist/server/routes/processes.js +3 -2
  325. package/dist/server/routes/project-briefs.js +8 -7
  326. package/dist/server/routes/recall.js +95 -93
  327. package/dist/server/routes/skills.js +6 -5
  328. package/dist/server/types.d.ts +1 -1
  329. package/dist/server/validation.d.ts +1 -2
  330. package/dist/server/validation.js +7 -14
  331. package/dist/server-detect.js +72 -58
  332. package/dist/server.d.ts +2 -2
  333. package/dist/server.js +131 -117
  334. package/dist/shared.d.ts +26 -17
  335. package/dist/shared.js +102 -104
  336. package/dist/skills.d.ts +3 -3
  337. package/dist/skills.js +88 -72
  338. package/dist/store/audit-event.d.ts +2 -2
  339. package/dist/store/audit-event.js +1 -1
  340. package/dist/store/candidates.d.ts +2 -2
  341. package/dist/store/candidates.js +4 -3
  342. package/dist/store/conflicts.js +30 -22
  343. package/dist/store/delete-and-batch.d.ts +11 -14
  344. package/dist/store/delete-and-batch.js +40 -91
  345. package/dist/store/entry-reads.d.ts +17 -25
  346. package/dist/store/entry-reads.js +59 -40
  347. package/dist/store/entry-row.d.ts +6 -24
  348. package/dist/store/entry-row.js +6 -24
  349. package/dist/store/entry-writes.d.ts +6 -7
  350. package/dist/store/entry-writes.js +13 -11
  351. package/dist/store/handoffs.d.ts +1 -1
  352. package/dist/store/handoffs.js +7 -10
  353. package/dist/store/index-and-stats.d.ts +2 -6
  354. package/dist/store/index-and-stats.js +4 -10
  355. package/dist/store/mirrors.d.ts +6 -19
  356. package/dist/store/mirrors.js +14 -39
  357. package/dist/store/open.js +9 -31
  358. package/dist/store/rows.d.ts +5 -11
  359. package/dist/store/rows.js +6 -11
  360. package/dist/store/search-rows.d.ts +17 -34
  361. package/dist/store/search-rows.js +34 -56
  362. package/dist/store/sessions.d.ts +4 -5
  363. package/dist/store/sessions.js +5 -6
  364. package/dist/store/summaries.d.ts +13 -17
  365. package/dist/store/summaries.js +26 -70
  366. package/dist/support-bundle.js +4 -8
  367. package/dist/tenant.d.ts +1 -5
  368. package/dist/token-ledger.d.ts +1 -1
  369. package/dist/token-ledger.js +3 -5
  370. package/dist/trace.js +1 -3
  371. package/dist/version.d.ts +1 -1
  372. package/dist/version.js +1 -1
  373. package/dist/working-memory.d.ts +1 -1
  374. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  375. package/extensions/openclaw-plugin/package.json +1 -1
  376. package/openclaw.plugin.json +1 -1
  377. package/package.json +1 -1
@@ -8,14 +8,10 @@
8
8
  * read their key from a conventional env var. The provider is selected by
9
9
  * `config.embeddings.provider` (default `'local'`).
10
10
  *
11
- * Design contract (see docs/plans/2026-06-08-b-pluggable-embedding-provider.md):
12
- * - Local provider `id` is the BARE model string. (Historical note: this
13
- * originally guaranteed NO identity change on upgrade; since the
14
- * embed-text-format versioning in embeddings.ts (`embeddingIndexIdentity`,
15
- * `${id}#t2`, docs/plans/2026-07-09-recall-determinism.md T1), the STORED
16
- * identity carries a `#t<N>` suffix and pre-#t2 stores get exactly one
17
- * forced reindex on their next embed-touching operation — deliberate,
18
- * because their vectors were computed over path-contaminated text.)
11
+ * Design contract:
12
+ * - Local provider `id` is the BARE model string; the STORED identity adds a `#t<N>`
13
+ * embed-text-format suffix (`embeddingIndexIdentity`), so older stores reindex once:
14
+ * their vectors were computed over path-contaminated text.
19
15
  * - API provider `id` is `${kind}:${model}`; switching to/from an API embedder
20
16
  * (or a dimension change) flips the identity and triggers the existing
21
17
  * reindex-on-change path.
@@ -8,14 +8,10 @@
8
8
  * read their key from a conventional env var. The provider is selected by
9
9
  * `config.embeddings.provider` (default `'local'`).
10
10
  *
11
- * Design contract (see docs/plans/2026-06-08-b-pluggable-embedding-provider.md):
12
- * - Local provider `id` is the BARE model string. (Historical note: this
13
- * originally guaranteed NO identity change on upgrade; since the
14
- * embed-text-format versioning in embeddings.ts (`embeddingIndexIdentity`,
15
- * `${id}#t2`, docs/plans/2026-07-09-recall-determinism.md T1), the STORED
16
- * identity carries a `#t<N>` suffix and pre-#t2 stores get exactly one
17
- * forced reindex on their next embed-touching operation — deliberate,
18
- * because their vectors were computed over path-contaminated text.)
11
+ * Design contract:
12
+ * - Local provider `id` is the BARE model string; the STORED identity adds a `#t<N>`
13
+ * embed-text-format suffix (`embeddingIndexIdentity`), so older stores reindex once:
14
+ * their vectors were computed over path-contaminated text.
19
15
  * - API provider `id` is `${kind}:${model}`; switching to/from an API embedder
20
16
  * (or a dimension change) flips the identity and triggers the existing
21
17
  * reindex-on-change path.
@@ -314,7 +314,7 @@ export async function embedMemory(hippoRoot, entry, model) {
314
314
  try {
315
315
  const identity = provider.id;
316
316
  if (embeddingModelRequiresReindex(hippoRoot, identity)) {
317
- // L9: host-wide rebuild. The embedding index is keyed by entry.id
317
+ // Host-wide rebuild. The embedding index is keyed by entry.id
318
318
  // (which is tenant-scoped) but the index itself is one per hippoRoot.
319
319
  // Cross-tenant content equivalence is visible at the vector level.
320
320
  // Per-tenant indices would be a larger architecture change.
@@ -357,6 +357,57 @@ export async function embedMemory(hippoRoot, entry, model) {
357
357
  log.warn(`skipped embedding ${entry.id} (${err instanceof Error ? err.message : String(err)}); run 'hippo embed' to backfill`);
358
358
  });
359
359
  }
360
+ /** Throws when an unavailable provider is a misconfiguration rather than an intentional no-op. */
361
+ function throwIfProviderKeyMissing(hippoRoot, provider) {
362
+ // A configured (non-disabled) API provider with a missing key is a
363
+ // misconfiguration, not a no-op: surface it so programmatic callers of the
364
+ // exported embedAll() learn nothing was written. Local-not-installed and an
365
+ // explicit enabled=false stay silent no-ops (best-effort / intentional).
366
+ const cfg = loadConfig(hippoRoot).embeddings;
367
+ if (provider.kind !== 'local' &&
368
+ cfg.enabled !== false &&
369
+ provider.keyEnv &&
370
+ !envByName(provider.keyEnv)?.trim()) {
371
+ throw new Error(`Embedding provider '${provider.kind}' is configured but ${provider.keyEnv} is not set.`);
372
+ }
373
+ }
374
+ // Embed entries without a cached vector in save-checkpointed chunks.
375
+ // provider.embed batches internally (one HTTP request per batchSize for API
376
+ // providers; sequential for local). A `[]` row means that single item could
377
+ // not be embedded and is left for a later run (resumable). On a hard provider
378
+ // failure mid-backfill we persist the chunks already embedded this run rather
379
+ // than discarding paid progress, then stop and resume on the next run.
380
+ async function backfillPending(hippoRoot, provider, pending, model) {
381
+ let count = 0;
382
+ // Initialized to `undefined` (not a known-evidence literal like `null`) so
383
+ // it stays a plain `unknown` binding for the arbitrary caught value below;
384
+ // falsy either way, so `if (backfillError)` behaves identically.
385
+ let backfillError = undefined;
386
+ const SAVE_CHUNK = 64;
387
+ for (let i = 0; i < pending.length; i += SAVE_CHUNK) {
388
+ const chunk = pending.slice(i, i + SAVE_CHUNK);
389
+ let vectors;
390
+ try {
391
+ vectors = await provider.embed(chunk.map((e) => embeddingInputText(e)), 'passage');
392
+ }
393
+ catch (err) {
394
+ // Preserve the chunks already saved this run, then surface the failure
395
+ // below so the explicit `hippo embed` path never reports a false success.
396
+ backfillError = err;
397
+ break;
398
+ }
399
+ const rows = [];
400
+ for (let j = 0; j < chunk.length; j++) {
401
+ const vec = vectors[j];
402
+ if (vec && vec.length > 0)
403
+ rows.push([chunk[j].id, vec]);
404
+ else
405
+ noteSkippedEmbedding(chunk[j].id);
406
+ }
407
+ count += withVectorDb(hippoRoot, (db) => upsertVectors(db, rows, model));
408
+ }
409
+ return { count, backfillError };
410
+ }
360
411
  /**
361
412
  * Embed all entries in hippoRoot that don't already have cached vectors.
362
413
  * Prunes orphaned embeddings for memories that no longer exist.
@@ -364,22 +415,12 @@ export async function embedMemory(hippoRoot, entry, model) {
364
415
  */
365
416
  export async function embedAll(hippoRoot, model, provider = resolveEmbeddingProvider(hippoRoot, { model })) {
366
417
  if (!provider.isAvailable()) {
367
- // A configured (non-disabled) API provider with a missing key is a
368
- // misconfiguration, not a no-op: surface it so programmatic callers of the
369
- // exported embedAll() learn nothing was written. Local-not-installed and an
370
- // explicit enabled=false stay silent no-ops (best-effort / intentional).
371
- const cfg = loadConfig(hippoRoot).embeddings;
372
- if (provider.kind !== 'local' &&
373
- cfg.enabled !== false &&
374
- provider.keyEnv &&
375
- !envByName(provider.keyEnv)?.trim()) {
376
- throw new Error(`Embedding provider '${provider.kind}' is configured but ${provider.keyEnv} is not set.`);
377
- }
418
+ throwIfProviderKeyMissing(hippoRoot, provider);
378
419
  return 0;
379
420
  }
380
421
  return withEmbedLock(hippoRoot, async () => {
381
422
  const identity = provider.id;
382
- // L9: host-wide by design. embedAll backfills vectors for all tenants'
423
+ // Host-wide by design. embedAll backfills vectors for all tenants'
383
424
  // entries into the per-host embedding index. Per-tenant filtering would
384
425
  // produce partial indices and break recall.
385
426
  const entries = loadAllEntries(hippoRoot);
@@ -395,41 +436,8 @@ export async function embedAll(hippoRoot, model, provider = resolveEmbeddingProv
395
436
  deleteOrphanVectors(db);
396
437
  return storedVectorIds(db);
397
438
  });
398
- // Embed entries without a cached vector in save-checkpointed chunks.
399
- // provider.embed batches internally (one HTTP request per batchSize for API
400
- // providers; sequential for local). A `[]` row means that single item could
401
- // not be embedded and is left for a later run (resumable). On a hard provider
402
- // failure mid-backfill we persist the chunks already embedded this run rather
403
- // than discarding paid progress, then stop and resume on the next run.
404
439
  const pending = entries.filter((e) => !embedded.has(e.id));
405
- let count = 0;
406
- // Initialized to `undefined` (not a known-evidence literal like `null`) so
407
- // it stays a plain `unknown` binding for the arbitrary caught value below;
408
- // falsy either way, so `if (backfillError)` behaves identically.
409
- let backfillError = undefined;
410
- const SAVE_CHUNK = 64;
411
- for (let i = 0; i < pending.length; i += SAVE_CHUNK) {
412
- const chunk = pending.slice(i, i + SAVE_CHUNK);
413
- let vectors;
414
- try {
415
- vectors = await provider.embed(chunk.map((e) => embeddingInputText(e)), 'passage');
416
- }
417
- catch (err) {
418
- // Preserve the chunks already saved this run, then surface the failure
419
- // below so the explicit `hippo embed` path never reports a false success.
420
- backfillError = err;
421
- break;
422
- }
423
- const rows = [];
424
- for (let j = 0; j < chunk.length; j++) {
425
- const vec = vectors[j];
426
- if (vec && vec.length > 0)
427
- rows.push([chunk[j].id, vec]);
428
- else
429
- noteSkippedEmbedding(chunk[j].id);
430
- }
431
- count += withVectorDb(hippoRoot, (db) => upsertVectors(db, rows, model));
432
- }
440
+ const { count, backfillError } = await backfillPending(hippoRoot, provider, pending, model);
433
441
  saveStoredEmbeddingModel(hippoRoot, identity);
434
442
  // Partial progress is now persisted; surface a hard backfill failure so the
435
443
  // explicit embed path reports it (best-effort callers go via embedMemory).
package/dist/env.d.ts CHANGED
@@ -35,7 +35,7 @@ export declare function envTenant(): string;
35
35
  export declare function envSkipAutoIntegrations(): boolean;
36
36
  export declare function envSkipPostinstall(): boolean;
37
37
  export declare function envMcpStdio(): boolean;
38
- /** Any non-empty value counts, unlike the `=1` switches. */
38
+ /** `1` or `true`, so `0` and `false` turn it off; `true` stays on for anyone who set it that way. */
39
39
  export declare function envRequireServer(): boolean;
40
40
  export declare function envRequireSessionScopedFreshTail(): boolean;
41
41
  export declare function envStdinWaitMs(): number | undefined;
package/dist/env.js CHANGED
@@ -15,9 +15,9 @@ function positiveInt(name) {
15
15
  const parsed = Number.parseInt(raw(name) ?? '', 10);
16
16
  return parsed > 0 ? parsed : undefined;
17
17
  }
18
- /** parseInt, kept when nonzero: these sites have always used `|| default`, so a negative value passes through. */
19
- function nonZeroInt(name) {
20
- return Number.parseInt(raw(name) ?? '', 10) || undefined;
18
+ /** Trimmed, with an empty or whitespace-only value read as unset. */
19
+ function trimmed(name) {
20
+ return raw(name)?.trim() || undefined;
21
21
  }
22
22
  /** The whole environment, for code that takes it as an injectable map (agent-memory machines, owner checks, support bundles). */
23
23
  export function processEnv() {
@@ -61,10 +61,10 @@ export function envHippoHome() { return raw('HIPPO_HOME')?.trim(); }
61
61
  export function envXdgDataHome() { return raw('XDG_DATA_HOME')?.trim(); }
62
62
  export function envHomeDir() { return raw('HOME') || raw('USERPROFILE'); }
63
63
  export function envPath() { return raw('PATH'); }
64
- export function envModelCache() { return raw('HIPPO_MODEL_CACHE'); }
64
+ export function envModelCache() { return trimmed('HIPPO_MODEL_CACHE'); }
65
65
  // Session, scope and tenant.
66
- export function envHippoSessionId() { return raw('HIPPO_SESSION_ID'); }
67
- export function envClaudeCodeSessionId() { return raw('CLAUDE_CODE_SESSION_ID'); }
66
+ export function envHippoSessionId() { return trimmed('HIPPO_SESSION_ID'); }
67
+ export function envClaudeCodeSessionId() { return trimmed('CLAUDE_CODE_SESSION_ID'); }
68
68
  export function envScope() { return raw('HIPPO_SCOPE'); }
69
69
  export function envGstackSkill() { return raw('GSTACK_SKILL'); }
70
70
  export function envOpenclawSkill() { return raw('OPENCLAW_SKILL'); }
@@ -76,8 +76,8 @@ export function envTenant() {
76
76
  export function envSkipAutoIntegrations() { return isOne('HIPPO_SKIP_AUTO_INTEGRATIONS'); }
77
77
  export function envSkipPostinstall() { return isOne('HIPPO_SKIP_POSTINSTALL'); }
78
78
  export function envMcpStdio() { return isOne('HIPPO_MCP_STDIO'); }
79
- /** Any non-empty value counts, unlike the `=1` switches. */
80
- export function envRequireServer() { return Boolean(raw('HIPPO_REQUIRE_SERVER')); }
79
+ /** `1` or `true`, so `0` and `false` turn it off; `true` stays on for anyone who set it that way. */
80
+ export function envRequireServer() { return isOneOrTrue('HIPPO_REQUIRE_SERVER'); }
81
81
  export function envRequireSessionScopedFreshTail() { return isOne('HIPPO_REQUIRE_SESSION_SCOPED_FRESH_TAIL'); }
82
82
  export function envStdinWaitMs() { return positiveInt('HIPPO_STDIN_WAIT_MS'); }
83
83
  export function envLogLevel() { return raw('HIPPO_LOG')?.trim().toLowerCase() ?? ''; }
@@ -89,18 +89,18 @@ export function envApiKey() { return raw('HIPPO_API_KEY'); }
89
89
  export function envClientIpHeader() { return raw('HIPPO_CLIENT_IP_HEADER')?.trim().toLowerCase(); }
90
90
  export function envTrustedProxies() { return raw('HIPPO_TRUSTED_PROXIES'); }
91
91
  export function envMcpSseMaxStreams() { return positiveInt('MCP_SSE_MAX_STREAMS'); }
92
- export function envMcpSseHeartbeatMs() { return nonZeroInt('MCP_SSE_HEARTBEAT_MS'); }
93
- export function envMcpSseMaxAgeSec() { return nonZeroInt('MCP_SSE_MAX_AGE_SEC'); }
92
+ export function envMcpSseHeartbeatMs() { return positiveInt('MCP_SSE_HEARTBEAT_MS'); }
93
+ export function envMcpSseMaxAgeSec() { return positiveInt('MCP_SSE_MAX_AGE_SEC'); }
94
94
  // LLM and reranker credentials and knobs.
95
95
  export function envAnthropicApiKey() { return raw('ANTHROPIC_API_KEY'); }
96
96
  export function envLlmTimeoutMs() { return positiveInt('HIPPO_LLM_TIMEOUT_MS'); }
97
- export function envTypesafeApiKey() { return raw('TYPESAFE_API_KEY'); }
97
+ export function envTypesafeApiKey() { return trimmed('TYPESAFE_API_KEY'); }
98
98
  export function envJevTimeoutMs() { return positiveInt('HIPPO_JEV_TIMEOUT_MS'); }
99
99
  export function envJevModel() { return raw('HIPPO_JEV_MODEL'); }
100
100
  export function envLlmRerankerUrl() { return raw('HIPPO_LLM_RERANKER_URL'); }
101
101
  export function envLlmRerankerKey() { return raw('HIPPO_LLM_RERANKER_KEY'); }
102
102
  export function envLlmRerankerModel() { return raw('HIPPO_LLM_RERANKER_MODEL'); }
103
- export function envLlmRerankerTimeoutMs() { return nonZeroInt('HIPPO_LLM_RERANKER_TIMEOUT_MS'); }
103
+ export function envLlmRerankerTimeoutMs() { return positiveInt('HIPPO_LLM_RERANKER_TIMEOUT_MS'); }
104
104
  export function envClefEndpoint() { return raw('HIPPO_CLEF_ENDPOINT')?.trim(); }
105
105
  export function envClefEndpointToken() { return raw('HIPPO_CLEF_ENDPOINT_TOKEN')?.trim() || undefined; }
106
106
  export function envClefTimeoutMs() { return raw('HIPPO_CLEF_TIMEOUT_MS'); }
@@ -0,0 +1,5 @@
1
+ /** Escape LIKE metacharacters for a query that declares `ESCAPE '\'`. */
2
+ export declare function escapeLike(term: string): string;
3
+ /** Escape a string for use as a literal inside a RegExp source. */
4
+ export declare function escapeRegex(s: string): string;
5
+ //# sourceMappingURL=escape.d.ts.map
package/dist/escape.js ADDED
@@ -0,0 +1,10 @@
1
+ // Leaf module: literal-match escapes, kept in one place so every LIKE and RegExp caller escapes the same set.
2
+ /** Escape LIKE metacharacters for a query that declares `ESCAPE '\'`. */
3
+ export function escapeLike(term) {
4
+ return term.replace(/[%_\\]/g, '\\$&');
5
+ }
6
+ /** Escape a string for use as a literal inside a RegExp source. */
7
+ export function escapeRegex(s) {
8
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
9
+ }
10
+ //# sourceMappingURL=escape.js.map
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Statistics and cost accounting for the token-efficiency evals (ROADMAP
3
- * Part IX, TE3-TE5).
2
+ * Statistics and cost accounting for the token-efficiency evals.
4
3
  *
5
4
  * - Cost: price provider usage over four buckets (uncached input, cache
6
5
  * write, cache read, output). Raw token counts overstate savings when most
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Statistics and cost accounting for the token-efficiency evals (ROADMAP
3
- * Part IX, TE3-TE5).
2
+ * Statistics and cost accounting for the token-efficiency evals.
4
3
  *
5
4
  * - Cost: price provider usage over four buckets (uncached input, cache
6
5
  * write, cache read, output). Raw token counts overstate savings when most
@@ -37,17 +37,13 @@ function mem(id, content, opts = {}) {
37
37
  entry.created = opts.created;
38
38
  return entry;
39
39
  }
40
- export function buildSyntheticCorpus() {
41
- const entries = [];
42
- const cases = [];
43
- // =========================================================================
44
- // 1. DIRECT RECALL — basic keyword matching
45
- // =========================================================================
40
+ /** Direct recall: basic keyword matching. */
41
+ function addDirectRecall(entries, cases) {
46
42
  entries.push(mem('dr-1', 'The PostgreSQL database migration failed because the users table had a NOT NULL constraint on the email column', { created: dateOffset(1), tags: ['topic:database'] }), mem('dr-2', 'React component rendering performance improved by 40% after memoizing the expensive computation in useMemo', { created: dateOffset(2), tags: ['topic:frontend'] }), mem('dr-3', 'The API rate limiter should use a sliding window algorithm instead of fixed window to prevent burst traffic', { created: dateOffset(3), tags: ['topic:api'] }), mem('dr-4', 'Docker container memory limits need to be set to 512MB for the worker service to prevent OOM kills', { created: dateOffset(4), tags: ['topic:devops'] }), mem('dr-5', 'The JWT token expiration was set to 24 hours but should be reduced to 1 hour for security compliance', { created: dateOffset(5), tags: ['topic:security'] }), mem('dr-6', 'Webpack bundle size grew to 2.3MB because lodash was imported as a whole instead of cherry-picking', { created: dateOffset(6), tags: ['topic:frontend'] }), mem('dr-7', 'The Redis cache TTL for user sessions should match the JWT expiration to prevent stale sessions', { created: dateOffset(7), tags: ['topic:caching'] }), mem('dr-8', 'GraphQL resolver for nested comments has N+1 query problem solved by DataLoader batching', { created: dateOffset(8), tags: ['topic:api'] }));
47
43
  cases.push({ id: 'dr-q1', category: 'direct-recall', query: 'PostgreSQL migration NOT NULL constraint', expectedIds: ['dr-1'], description: 'exact keyword match on DB migration' }, { id: 'dr-q2', category: 'direct-recall', query: 'React useMemo performance memoizing', expectedIds: ['dr-2'], description: 'React performance optimization' }, { id: 'dr-q3', category: 'direct-recall', query: 'sliding window rate limiter API', expectedIds: ['dr-3'], description: 'API rate limiting approach' }, { id: 'dr-q4', category: 'direct-recall', query: 'Docker OOM memory limits worker', expectedIds: ['dr-4'], description: 'Docker memory config' }, { id: 'dr-q5', category: 'direct-recall', query: 'JWT token expiration security', expectedIds: ['dr-5'], description: 'JWT security setting' }, { id: 'dr-q6', category: 'direct-recall', query: 'webpack bundle size lodash', expectedIds: ['dr-6'], description: 'bundle size issue' }, { id: 'dr-q7', category: 'direct-recall', query: 'Redis cache TTL session expiration', expectedIds: ['dr-7'], description: 'cache TTL config' }, { id: 'dr-q8', category: 'direct-recall', query: 'GraphQL N+1 DataLoader batching', expectedIds: ['dr-8'], description: 'GraphQL N+1 fix' });
48
- // =========================================================================
49
- // 2. EXTRACTION PREFERENCE — extracted facts should rank above raw source
50
- // =========================================================================
44
+ }
45
+ /** Extraction preference: extracted facts should rank above raw source. */
46
+ function addExtractionPreference(entries, cases) {
51
47
  entries.push(mem('ep-src-1', 'speaker:Alice: So we had this big meeting yesterday about the deployment pipeline and Bob mentioned that the staging environment is using Kubernetes 1.28 and we should upgrade to 1.30 before the end of Q2 because of the security patches', {
52
48
  created: dateOffset(10), tags: ['speaker:Alice', 'topic:infrastructure', 'session:meeting-1'],
53
49
  }), mem('ep-ext-1', 'The staging environment runs Kubernetes 1.28 and needs to be upgraded to 1.30 before end of Q2 for security patches', {
@@ -65,9 +61,9 @@ export function buildSyntheticCorpus() {
65
61
  extracted_from: 'ep-src-3',
66
62
  }));
67
63
  cases.push({ id: 'ep-q1', category: 'extraction-preference', query: 'Kubernetes upgrade staging environment', expectedIds: ['ep-ext-1'], description: 'extracted fact about K8s upgrade should rank above raw utterance' }, { id: 'ep-q2', category: 'extraction-preference', query: 'Datadog Grafana alerting migration', expectedIds: ['ep-ext-2'], description: 'extracted fact about monitoring switch' }, { id: 'ep-q3', category: 'extraction-preference', query: 'OAuth2 PKCE login page redesign', expectedIds: ['ep-ext-3'], description: 'extracted fact about auth work' });
68
- // =========================================================================
69
- // 3. DAG DRILL-DOWN — summary nodes should surface children
70
- // =========================================================================
64
+ }
65
+ /** Dag drill-down: summary nodes should surface children. */
66
+ function addDagDrilldown(entries, cases) {
71
67
  entries.push(mem('dag-child-1', 'The API response time for /users endpoint degraded from 50ms to 300ms after adding the permissions check', {
72
68
  created: dateOffset(15), layer: Layer.Semantic, tags: ['topic:api-performance', 'extracted'],
73
69
  dag_level: 1,
@@ -86,14 +82,14 @@ export function buildSyntheticCorpus() {
86
82
  child.dag_parent_id = 'dag-summary-1';
87
83
  }
88
84
  cases.push({ id: 'dag-q1', category: 'dag-drilldown', query: 'API performance problems latency', expectedIds: ['dag-child-1', 'dag-child-2', 'dag-child-3', 'dag-summary-1'], description: 'summary should drill down to all children' }, { id: 'dag-q2', category: 'dag-drilldown', query: 'endpoint response time degradation', expectedIds: ['dag-child-1', 'dag-child-2', 'dag-summary-1'], description: 'query matching summary should surface relevant children' });
89
- // =========================================================================
90
- // 4. TEMPORAL — recency/oldest cues should affect ranking
91
- // =========================================================================
85
+ }
86
+ /** Temporal: recency/oldest cues should affect ranking. */
87
+ function addTemporal(entries, cases) {
92
88
  entries.push(mem('tmp-1', 'The team decided to use TypeScript for the new service', { created: dateOffset(-30), tags: ['topic:architecture'] }), mem('tmp-2', 'The team evaluated Rust as an alternative language for the service', { created: dateOffset(-20), tags: ['topic:architecture'] }), mem('tmp-3', 'The team added Go as a candidate language for the service rewrite', { created: dateOffset(-10), tags: ['topic:architecture'] }), mem('tmp-4', 'The team finalized the language choice as Go for the service rewrite', { created: dateOffset(-1), tags: ['topic:architecture'] }));
93
89
  cases.push({ id: 'tmp-q1', category: 'temporal', query: 'what did the team recently decide about the service language', expectedIds: ['tmp-4'], description: 'recent cue should boost newest entry' }, { id: 'tmp-q2', category: 'temporal', query: 'what was the first language choice for the service', expectedIds: ['tmp-1'], description: 'oldest cue should boost earliest entry' }, { id: 'tmp-q3', category: 'temporal', query: 'latest update on the service rewrite language', expectedIds: ['tmp-4'], description: 'latest should boost most recent' }, { id: 'tmp-q4', category: 'temporal', query: 'original architecture decision for the service', expectedIds: ['tmp-1'], description: 'original should boost earliest' });
94
- // =========================================================================
95
- // 5. NOISE RESISTANCE — relevant memories found despite noise
96
- // =========================================================================
90
+ }
91
+ /** Noise resistance: relevant memories found despite noise. */
92
+ function addNoiseResistance(entries, cases) {
97
93
  // Add 30 noise entries
98
94
  const noiseTopics = [
99
95
  'breakfast meeting catering ordered sandwiches', 'office temperature thermostat adjusted',
@@ -121,9 +117,9 @@ export function buildSyntheticCorpus() {
121
117
  created: dateOffset(21), tags: ['topic:security', 'topic:s3'],
122
118
  }));
123
119
  cases.push({ id: 'nr-q1', category: 'noise-resistance', query: 'database connection pool exhausted max connections', expectedIds: ['nr-1'], description: 'find DB issue despite 30 noise entries' }, { id: 'nr-q2', category: 'noise-resistance', query: 'S3 bucket public access security misconfiguration', expectedIds: ['nr-2'], description: 'find S3 issue despite noise' }, { id: 'nr-q3', category: 'noise-resistance', query: 'PostgreSQL migration constraint', expectedIds: ['dr-1'], description: 'find earlier DB entry despite noise' }, { id: 'nr-q4', category: 'noise-resistance', query: 'JWT token security expiration', expectedIds: ['dr-5'], description: 'find security entry despite noise' });
124
- // =========================================================================
125
- // 6. MULTI-HOP — entity chaining across sessions
126
- // =========================================================================
120
+ }
121
+ /** Multi-hop: entity chaining across sessions. */
122
+ function addMultiHop(entries, cases) {
127
123
  entries.push(mem('mh-1', 'speaker:Alice works on the payment gateway integration with Stripe. She found a webhook signature validation bug.', {
128
124
  created: dateOffset(25), tags: ['speaker:Alice', 'topic:payments'],
129
125
  }), mem('mh-2', 'speaker:Bob reviewed the Stripe webhook code and confirmed the signature validation uses the wrong secret key from the test environment.', {
@@ -134,6 +130,16 @@ export function buildSyntheticCorpus() {
134
130
  created: dateOffset(28), tags: ['speaker:Carol', 'topic:billing'],
135
131
  }));
136
132
  cases.push({ id: 'mh-q1', category: 'multi-hop', query: 'Who fixed the Stripe webhook bug and what was the root cause?', expectedIds: ['mh-1', 'mh-2', 'mh-3'], description: 'chain Alice -> Stripe -> Bob -> fix' }, { id: 'mh-q2', category: 'multi-hop', query: 'What are all the payment-related issues the team discussed?', expectedIds: ['mh-1', 'mh-2', 'mh-3', 'mh-4'], description: 'find all payment topics across speakers' });
133
+ }
134
+ export function buildSyntheticCorpus() {
135
+ const entries = [];
136
+ const cases = [];
137
+ addDirectRecall(entries, cases);
138
+ addExtractionPreference(entries, cases);
139
+ addDagDrilldown(entries, cases);
140
+ addTemporal(entries, cases);
141
+ addNoiseResistance(entries, cases);
142
+ addMultiHop(entries, cases);
137
143
  return { entries, cases };
138
144
  }
139
145
  // ---------------------------------------------------------------------------
package/dist/extract.js CHANGED
@@ -6,9 +6,7 @@ import { redactSecretsStrict } from './secret-detect.js';
6
6
  import { fetchWithRetry, llmTimeoutMs } from './http-retry.js';
7
7
  import { neverAutoShareTags } from './shared.js';
8
8
  import { log } from './log.js';
9
- function isJsonString(value) {
10
- return typeof value === 'string';
11
- }
9
+ import { isJsonString } from './json.js';
12
10
  const EXTRACTION_PROMPT = `You are extracting factual statements from a conversation or memory entry. Extract 1-8 standalone factual statements that would be useful to remember later.
13
11
 
14
12
  Rules:
@@ -101,15 +99,12 @@ export function storeExtractedFacts(hippoRoot, source, facts) {
101
99
  source: source.source,
102
100
  extracted_from: source.id,
103
101
  scope: source.scope,
104
- // T1 executor check (2026-08-15 hardening pass): same defect as the
105
- // consolidate.ts merge/trace passes — createMemory with no tenantId
106
- // option stamps 'default' (memory.ts:535) regardless of the source
107
- // entry's own tenant. Thread it through so extracted facts land in
108
- // the same tenant as the episodic memory they were extracted from.
102
+ // Without it createMemory stamps 'default', and extracted facts leave
103
+ // the tenant of the episodic memory they were extracted from.
109
104
  tenantId: source.tenantId,
110
105
  baseHalfLifeDays,
111
106
  }), origin_project: source.origin_project };
112
- // AT1 containment: a refusal is per-VALUE — one rejected fact must not
107
+ // A refusal is per-VALUE: one rejected fact must not
113
108
  // drop the rest of this batch. writeEntry has already audited the
114
109
  // refusal (reject_refusal) before rethrowing, so skip-and-count here.
115
110
  try {
@@ -1,4 +1,4 @@
1
- /** Failure log (ROADMAP CD13): every failed tool call the capture-error hook sees, stored or not. */
1
+ /** Failure log: every failed tool call the capture-error hook sees, stored or not. */
2
2
  import type { CaptureErrorOutcome, RoutineRule } from './capture-error.js';
3
3
  import type { DatabaseSyncLike } from './db.js';
4
4
  /** Rows older than this are pruned on write, which also bounds how far back a repeat can be found. */
@@ -31,9 +31,9 @@ export interface SessionFailures {
31
31
  /** Of those, failures whose signature another session hit first. */
32
32
  repeats: number;
33
33
  }
34
- /** Rated failures per session since `sinceIso`, the input for repeat-error rate per arm (CD11, CD12). */
34
+ /** Rated failures per session since `sinceIso`, the input for repeat-error rate per arm. */
35
35
  export declare function failuresBySession(db: DatabaseSyncLike, tenantId: string, sinceIso: string): SessionFailures[];
36
- /** Failure log totals over a window, for {@link summarizeFailures}. Counts only: a rate needs a holdout arm (CD11). */
36
+ /** Failure log totals over a window, for {@link summarizeFailures}. Counts only: a rate needs a holdout arm. */
37
37
  export interface FailureSummary {
38
38
  /** ISO start of the window (inclusive). */
39
39
  since: string;
@@ -11,7 +11,7 @@ export function recordFailure(db, event) {
11
11
  const cutoff = new Date(Date.parse(now) - FAILURE_LOG_RETENTION_DAYS * 86_400_000).toISOString();
12
12
  db.prepare(`DELETE FROM failure_log WHERE ts < ?`).run(cutoff);
13
13
  }
14
- /** Rated failures per session since `sinceIso`, the input for repeat-error rate per arm (CD11, CD12). */
14
+ /** Rated failures per session since `sinceIso`, the input for repeat-error rate per arm. */
15
15
  export function failuresBySession(db, tenantId, sinceIso) {
16
16
  // SAFETY: the SELECT names exactly these three TEXT columns.
17
17
  const rows = db.prepare(`SELECT ts, session_id, sig_hash FROM failure_log
@@ -1,5 +1,5 @@
1
1
  /**
2
- * J3.2 forward-claim detector — pure-function regex set + token extraction.
2
+ * Forward-claim detector: pure-function regex set + token extraction.
3
3
  *
4
4
  * No DB. No state. No dependencies beyond stdlib regex + String.
5
5
  *
@@ -13,9 +13,7 @@
13
13
  * Iteration signal: the `recall_autodebias_hint_no_class_match` audit op
14
14
  * (emitted by computePlanningFallacyOutput when a phrase matches but no class
15
15
  * resolves) is the telemetry channel for deciding whether to add an
16
- * embedding-based detector in J3.3.
17
- *
18
- * Plan: docs/plans/2026-05-26-j32-auto-injection.md (Task 1).
16
+ * embedding-based detector.
19
17
  */
20
18
  export interface ForwardClaimMatch {
21
19
  /** The regex match snippet, e.g. "will take" or "~3 days". Surfaced
@@ -1,5 +1,5 @@
1
1
  /**
2
- * J3.2 forward-claim detector — pure-function regex set + token extraction.
2
+ * Forward-claim detector: pure-function regex set + token extraction.
3
3
  *
4
4
  * No DB. No state. No dependencies beyond stdlib regex + String.
5
5
  *
@@ -13,12 +13,10 @@
13
13
  * Iteration signal: the `recall_autodebias_hint_no_class_match` audit op
14
14
  * (emitted by computePlanningFallacyOutput when a phrase matches but no class
15
15
  * resolves) is the telemetry channel for deciding whether to add an
16
- * embedding-based detector in J3.3.
17
- *
18
- * Plan: docs/plans/2026-05-26-j32-auto-injection.md (Task 1).
16
+ * embedding-based detector.
19
17
  */
20
18
  /**
21
- * Patterns ship-locked at v1.13.x. Each pattern is intentionally narrow —
19
+ * Each pattern is intentionally narrow:
22
20
  * adding a pattern should require evidence from the
23
21
  * `recall_autodebias_hint_no_class_match` audit channel that real forward-
24
22
  * claims are slipping through, NOT speculative "this might match more".
@@ -29,8 +27,7 @@
29
27
  */
30
28
  // Reused fragment for ALL duration-suffix patterns. Requires a digit + unit
31
29
  // so 'will take ownership' / 'ship in Docker' (no time component) don't
32
- // match. Codex round 2 P2: patterns that allowed verb-only matches fired
33
- // on every-day non-estimate queries that happened to share a class-token.
30
+ // match: verb-only patterns fire on everyday non-estimate queries that share a class-token.
34
31
  // Accepts `<N> unit` (3 days), `a/an unit` (a week — implicit 1), and
35
32
  // `one unit` so natural English forward-claims aren't lost.
36
33
  const DURATION_TAIL = String.raw `(?:about|around|~|≈)?\s*(?:\d+|a|an|one)\s*(?:day|week|month|hour|hr|min(?:ute)?|sec(?:ond)?)s?\b`;
@@ -51,8 +48,7 @@ const FORWARD_CLAIM_PATTERNS = [
51
48
  // Lookbehind asserts start-of-string OR whitespace before the tilde
52
49
  // because \b before ~ requires a word char immediately preceding (~ is
53
50
  // not a word character), so /\b~/ would only match in 'foo~3 days'
54
- // (malformed) and silently miss the legitimate cases. Codex review
55
- // round 1 catch.
51
+ // (malformed) and silently miss the legitimate cases.
56
52
  /(?<=^|\s)~\s*\d+\s*(?:day|week|month|hour)s?\b/i,
57
53
  // Should + verb + duration tail. Same tightening as the leading 'will
58
54
  // take' rule: 'should ship by EOD' must include a quantifier.
@@ -78,8 +74,7 @@ const STOP_WORDS = new Set([
78
74
  // Duration units appear in every forward-claim phrase by design (the
79
75
  // regex set REQUIRES a `<N> <unit>` quantifier after the verb). Letting
80
76
  // them through to class resolution lets a class tag containing 'days'
81
- // win or tie on the unit token instead of the domain token. Codex round
82
- // 2 P3 catch.
77
+ // win or tie on the unit token instead of the domain token.
83
78
  'day', 'days', 'week', 'weeks', 'month', 'months',
84
79
  'hour', 'hours', 'hr', 'hrs',
85
80
  'min', 'mins', 'minute', 'minutes', 'sec', 'secs', 'second', 'seconds',
package/dist/goals.d.ts CHANGED
@@ -60,7 +60,7 @@ export interface PushGoalOpts {
60
60
  }
61
61
  export declare function pushGoal(hippoRoot: string, opts: PushGoalOpts): Goal;
62
62
  /**
63
- * v1.7.4 — depth-cap enforcer extracted from pushGoalWithDb and resumeGoal.
63
+ * Depth-cap enforcer shared by pushGoalWithDb and resumeGoal.
64
64
  * If the (tenant, session) has >= MAX_ACTIVE_GOAL_DEPTH active goals,
65
65
  * suspend the oldest `overflow` ones.
66
66
  *
@@ -69,7 +69,7 @@ export declare function pushGoal(hippoRoot: string, opts: PushGoalOpts): Goal;
69
69
  * is impossible to misread the contract at a call site. Both existing call
70
70
  * sites (pushGoalWithDb, resumeGoal) wrap in `BEGIN IMMEDIATE` already.
71
71
  *
72
- * @internal v1.7.4 -- internal goal-stack invariant. Subject to change.
72
+ * @internal Internal goal-stack invariant. Subject to change.
73
73
  */
74
74
  export declare function enforceDepthCapWithinTx(db: DatabaseSyncLike, tenantId: string, sessionId: string): void;
75
75
  export declare function pushGoalWithDb(db: DatabaseSyncLike, opts: PushGoalOpts): Goal;
@@ -148,7 +148,7 @@ export declare function applyGoalStackBoost<R extends {
148
148
  export interface CompleteGoalOpts {
149
149
  outcomeScore?: number;
150
150
  /**
151
- * v1.7.4 — when true, skip the strength-multiplier propagation block.
151
+ * When true, skip the strength-multiplier propagation block.
152
152
  * Default false (propagate). The goal's status still transitions to
153
153
  * 'completed' and `outcome_score` is still recorded; only the side-effect
154
154
  * on recalled memories' strength is suppressed.