hippo-memory 1.62.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 (370) hide show
  1. package/README.md +9 -0
  2. package/dist/agent-memories/apply.d.ts +2 -0
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +2 -1
  5. package/dist/agent-memories/claude-code.js +4 -4
  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.js +37 -23
  9. package/dist/ambient-store.d.ts +4 -4
  10. package/dist/ambient-store.js +4 -3
  11. package/dist/api/assemble.d.ts +7 -10
  12. package/dist/api/assemble.js +7 -12
  13. package/dist/api/audit.d.ts +2 -2
  14. package/dist/api/audit.js +2 -2
  15. package/dist/api/auth.d.ts +6 -6
  16. package/dist/api/auth.js +5 -7
  17. package/dist/api/context-select.d.ts +4 -3
  18. package/dist/api/context-select.js +9 -11
  19. package/dist/api/context-types.d.ts +12 -17
  20. package/dist/api/context.d.ts +3 -2
  21. package/dist/api/context.js +20 -20
  22. package/dist/api/dormant.js +1 -1
  23. package/dist/api/drill-down.d.ts +4 -4
  24. package/dist/api/drill-down.js +22 -10
  25. package/dist/api/outcome.d.ts +8 -13
  26. package/dist/api/outcome.js +13 -17
  27. package/dist/api/promote.d.ts +5 -8
  28. package/dist/api/quarantine.js +3 -2
  29. package/dist/api/recall-types.d.ts +43 -55
  30. package/dist/api/recall.d.ts +3 -3
  31. package/dist/api/recall.js +7 -18
  32. package/dist/api/remember.d.ts +2 -2
  33. package/dist/api/sleep.d.ts +7 -35
  34. package/dist/api/sleep.js +3 -2
  35. package/dist/api/tokens.d.ts +2 -2
  36. package/dist/api/tokens.js +2 -2
  37. package/dist/api/types.d.ts +6 -10
  38. package/dist/api/types.js +2 -4
  39. package/dist/audit-prune.d.ts +4 -6
  40. package/dist/audit-prune.js +3 -5
  41. package/dist/audit.js +11 -33
  42. package/dist/auth.d.ts +8 -9
  43. package/dist/auth.js +5 -7
  44. package/dist/autolearn.d.ts +1 -1
  45. package/dist/autolearn.js +1 -1
  46. package/dist/availability.js +3 -5
  47. package/dist/capture/command.d.ts +5 -4
  48. package/dist/capture/command.js +8 -22
  49. package/dist/capture/compact.d.ts +1 -1
  50. package/dist/capture/compact.js +9 -13
  51. package/dist/capture/extract.js +37 -115
  52. package/dist/capture-error.d.ts +1 -1
  53. package/dist/capture-error.js +1 -1
  54. package/dist/churn-git.d.ts +1 -1
  55. package/dist/churn-git.js +1 -1
  56. package/dist/cli/audit.js +3 -4
  57. package/dist/cli/auth.js +3 -6
  58. package/dist/cli/curate.d.ts +1 -1
  59. package/dist/cli/curate.js +8 -24
  60. package/dist/cli/dag.js +5 -9
  61. package/dist/cli/decisions.js +10 -21
  62. package/dist/cli/explain.js +2 -1
  63. package/dist/cli/goals.js +1 -1
  64. package/dist/cli/init.js +1 -1
  65. package/dist/cli/playbooks.js +4 -9
  66. package/dist/cli/projects.js +3 -1
  67. package/dist/cli/recall.js +2 -1
  68. package/dist/cli/remember.js +6 -18
  69. package/dist/cli/session-hooks.js +21 -40
  70. package/dist/cli/setup.d.ts +1 -1
  71. package/dist/cli/setup.js +2 -6
  72. package/dist/cli/shared.js +3 -3
  73. package/dist/cli/slack.js +1 -1
  74. package/dist/cli/sleep.js +27 -1
  75. package/dist/cli/status.d.ts +4 -4
  76. package/dist/cli/status.js +7 -12
  77. package/dist/cli/transfer.js +8 -13
  78. package/dist/cli/usage.js +9 -6
  79. package/dist/cli.d.ts +1 -1
  80. package/dist/cli.js +4 -9
  81. package/dist/compaction-record.d.ts +2 -0
  82. package/dist/compaction-record.js +89 -68
  83. package/dist/compare.d.ts +11 -16
  84. package/dist/compare.js +11 -16
  85. package/dist/config.d.ts +19 -18
  86. package/dist/config.js +90 -67
  87. package/dist/connectors/github/backfill.d.ts +2 -2
  88. package/dist/connectors/github/backfill.js +8 -15
  89. package/dist/connectors/github/cli-impl.js +3 -8
  90. package/dist/connectors/github/deletion.d.ts +5 -12
  91. package/dist/connectors/github/deletion.js +5 -12
  92. package/dist/connectors/github/dlq.d.ts +6 -9
  93. package/dist/connectors/github/dlq.js +2 -3
  94. package/dist/connectors/github/ingest.d.ts +5 -7
  95. package/dist/connectors/github/ingest.js +8 -12
  96. package/dist/connectors/github/octokit-client.d.ts +3 -5
  97. package/dist/connectors/github/octokit-client.js +5 -6
  98. package/dist/connectors/github/signature.d.ts +9 -39
  99. package/dist/connectors/github/signature.js +9 -39
  100. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  101. package/dist/connectors/github/tenant-routing.js +1 -1
  102. package/dist/connectors/github/transform.js +2 -2
  103. package/dist/connectors/github/types.d.ts +2 -10
  104. package/dist/connectors/github/types.js +1 -3
  105. package/dist/connectors/slack/deletion.d.ts +3 -8
  106. package/dist/connectors/slack/deletion.js +3 -8
  107. package/dist/connectors/slack/dlq.d.ts +1 -1
  108. package/dist/connectors/slack/ingest.d.ts +1 -1
  109. package/dist/connectors/slack/ingest.js +7 -16
  110. package/dist/connectors/slack/signature.d.ts +1 -1
  111. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  112. package/dist/connectors/slack/tenant-routing.js +3 -5
  113. package/dist/connectors/slack/transform.d.ts +5 -6
  114. package/dist/connectors/slack/transform.js +5 -6
  115. package/dist/connectors/slack/types.d.ts +2 -6
  116. package/dist/connectors/slack/types.js +1 -3
  117. package/dist/connectors/slack/web-client.js +10 -3
  118. package/dist/connectors/slack/workspaces.d.ts +3 -5
  119. package/dist/connectors/slack/workspaces.js +3 -5
  120. package/dist/consolidate/conflicts.js +3 -14
  121. package/dist/consolidate/decay.js +9 -29
  122. package/dist/consolidate/llm-passes.js +4 -5
  123. package/dist/consolidate/merge.js +8 -23
  124. package/dist/consolidate/run.d.ts +1 -8
  125. package/dist/consolidate/run.js +3 -25
  126. package/dist/consolidate/sleep.js +5 -17
  127. package/dist/consolidate/traces.js +9 -21
  128. package/dist/customer-notes.d.ts +5 -7
  129. package/dist/customer-notes.js +6 -9
  130. package/dist/dag.d.ts +10 -21
  131. package/dist/dag.js +23 -73
  132. package/dist/db/continuity.js +2 -2
  133. package/dist/db/migrations/v14.js +1 -1
  134. package/dist/db/migrations/v15.js +1 -2
  135. package/dist/db/migrations/v16.js +3 -4
  136. package/dist/db/migrations/v17.js +2 -3
  137. package/dist/db/migrations/v19.js +1 -1
  138. package/dist/db/migrations/v20.js +1 -1
  139. package/dist/db/migrations/v21.js +2 -6
  140. package/dist/db/migrations/v22.js +2 -4
  141. package/dist/db/migrations/v23.js +1 -1
  142. package/dist/db/migrations/v24.js +4 -6
  143. package/dist/db/migrations/v25.js +2 -3
  144. package/dist/db/migrations/v26.js +3 -3
  145. package/dist/db/migrations/v27.js +2 -10
  146. package/dist/db/migrations/v28.js +5 -8
  147. package/dist/db/migrations/v29.js +3 -4
  148. package/dist/db/migrations/v30.js +2 -2
  149. package/dist/db/migrations/v31.js +1 -1
  150. package/dist/db/migrations/v32.js +1 -1
  151. package/dist/db/migrations/v33.js +3 -3
  152. package/dist/db/migrations/v34.js +1 -1
  153. package/dist/db/migrations/v35.js +3 -4
  154. package/dist/db/migrations/v36.js +3 -4
  155. package/dist/db/migrations/v37.js +5 -5
  156. package/dist/db/migrations/v38.js +7 -8
  157. package/dist/db/migrations/v39.js +1 -1
  158. package/dist/db/migrations/v40.js +4 -16
  159. package/dist/db/migrations/v41.js +3 -4
  160. package/dist/db/migrations/v42.js +3 -4
  161. package/dist/db/migrations/v45.js +1 -1
  162. package/dist/db/migrations/v46.js +1 -1
  163. package/dist/db/migrations/v47.js +1 -1
  164. package/dist/db/migrations/v48.js +1 -1
  165. package/dist/decisions.d.ts +2 -2
  166. package/dist/decisions.js +6 -6
  167. package/dist/dedupe.js +86 -61
  168. package/dist/delivery-recorder.js +154 -135
  169. package/dist/doctor.js +119 -104
  170. package/dist/dormant.js +1 -4
  171. package/dist/embedding-provider.d.ts +4 -8
  172. package/dist/embedding-provider.js +4 -8
  173. package/dist/embeddings.js +55 -47
  174. package/dist/env.d.ts +1 -1
  175. package/dist/env.js +12 -12
  176. package/dist/escape.d.ts +5 -0
  177. package/dist/escape.js +10 -0
  178. package/dist/eval-stats.d.ts +1 -2
  179. package/dist/eval-stats.js +1 -2
  180. package/dist/eval-suite.js +27 -21
  181. package/dist/extract.js +4 -9
  182. package/dist/failure-log.d.ts +3 -3
  183. package/dist/failure-log.js +1 -1
  184. package/dist/forward-claim-detector.d.ts +2 -4
  185. package/dist/forward-claim-detector.js +6 -11
  186. package/dist/goals.d.ts +3 -3
  187. package/dist/goals.js +5 -6
  188. package/dist/graph/read.d.ts +2 -2
  189. package/dist/graph/read.js +5 -6
  190. package/dist/graph/types.d.ts +8 -8
  191. package/dist/graph/write.d.ts +7 -14
  192. package/dist/graph/write.js +16 -23
  193. package/dist/graph-extract.d.ts +7 -8
  194. package/dist/graph-extract.js +62 -72
  195. package/dist/graph-recall.d.ts +2 -2
  196. package/dist/graph-recall.js +55 -49
  197. package/dist/graph-stream.d.ts +5 -6
  198. package/dist/graph-stream.js +66 -57
  199. package/dist/graph-view.d.ts +2 -2
  200. package/dist/graph-view.js +7 -7
  201. package/dist/half-life-migration.d.ts +1 -2
  202. package/dist/half-life-migration.js +2 -3
  203. package/dist/hooks/codex-session.js +1 -1
  204. package/dist/hooks/codex-wrapper.d.ts +1 -1
  205. package/dist/hooks/codex-wrapper.js +3 -2
  206. package/dist/hooks/json-hooks.d.ts +2 -2
  207. package/dist/hooks/json-hooks.js +5 -4
  208. package/dist/hooks/opencode.d.ts +1 -1
  209. package/dist/hooks/opencode.js +5 -4
  210. package/dist/hooks/shared.d.ts +3 -7
  211. package/dist/hooks/shared.js +1 -8
  212. package/dist/http-util.d.ts +2 -3
  213. package/dist/http-util.js +3 -0
  214. package/dist/importers/core.d.ts +2 -9
  215. package/dist/importers/core.js +15 -30
  216. package/dist/importers/sources.js +2 -1
  217. package/dist/importers/vault.js +2 -20
  218. package/dist/incidents.d.ts +1 -1
  219. package/dist/incidents.js +1 -1
  220. package/dist/instruction-detect.d.ts +1 -1
  221. package/dist/instruction-detect.js +1 -1
  222. package/dist/invalidation.d.ts +3 -0
  223. package/dist/invalidation.js +160 -114
  224. package/dist/json.d.ts +5 -0
  225. package/dist/json.js +4 -0
  226. package/dist/judgment.js +1 -2
  227. package/dist/local-embedding.js +1 -1
  228. package/dist/mcp/admin-tools.js +7 -17
  229. package/dist/mcp/format.js +1 -1
  230. package/dist/mcp/framing.js +3 -6
  231. package/dist/mcp/protocol.d.ts +2 -5
  232. package/dist/mcp/protocol.js +1 -3
  233. package/dist/mcp/recall-tools.js +12 -15
  234. package/dist/mcp/request.js +4 -3
  235. package/dist/mcp/session-state.js +2 -3
  236. package/dist/mcp/stdio.js +2 -1
  237. package/dist/mcp/tools.js +9 -6
  238. package/dist/memory-value-weights.d.ts +5 -8
  239. package/dist/memory-value-weights.js +5 -8
  240. package/dist/memory-value.d.ts +13 -13
  241. package/dist/memory-value.js +26 -37
  242. package/dist/memory.d.ts +20 -22
  243. package/dist/memory.js +24 -48
  244. package/dist/multihop.d.ts +1 -1
  245. package/dist/multihop.js +3 -2
  246. package/dist/owner-validation.d.ts +4 -5
  247. package/dist/owner-validation.js +4 -5
  248. package/dist/physics.d.ts +4 -4
  249. package/dist/physics.js +7 -9
  250. package/dist/policies.d.ts +9 -10
  251. package/dist/policies.js +12 -14
  252. package/dist/postinstall.js +3 -6
  253. package/dist/predictions/planning-fallacy.d.ts +9 -14
  254. package/dist/predictions/planning-fallacy.js +10 -16
  255. package/dist/predictions/store.d.ts +15 -23
  256. package/dist/predictions/store.js +36 -33
  257. package/dist/processes.d.ts +2 -7
  258. package/dist/processes.js +3 -3
  259. package/dist/project-briefs.d.ts +2 -3
  260. package/dist/project-briefs.js +9 -13
  261. package/dist/project-identity.d.ts +22 -9
  262. package/dist/project-identity.js +47 -12
  263. package/dist/project-merge.d.ts +17 -2
  264. package/dist/project-merge.js +109 -27
  265. package/dist/project-remote.d.ts +12 -0
  266. package/dist/project-remote.js +138 -0
  267. package/dist/prompt-recall.js +1 -2
  268. package/dist/rate-limit.d.ts +1 -1
  269. package/dist/rate-limit.js +1 -1
  270. package/dist/raw-archive.d.ts +9 -0
  271. package/dist/raw-archive.js +70 -53
  272. package/dist/recall-history.d.ts +19 -20
  273. package/dist/recall-history.js +24 -42
  274. package/dist/recall-pipeline.js +4 -28
  275. package/dist/recall-scope.d.ts +7 -8
  276. package/dist/recall-scope.js +7 -8
  277. package/dist/recall-trace.d.ts +5 -9
  278. package/dist/recall-trace.js +6 -10
  279. package/dist/refine-llm.d.ts +1 -1
  280. package/dist/refine-llm.js +2 -2
  281. package/dist/reject-flow.d.ts +3 -4
  282. package/dist/reject-flow.js +122 -117
  283. package/dist/rejection.d.ts +5 -6
  284. package/dist/rejection.js +7 -15
  285. package/dist/rerankers/clef.d.ts +1 -1
  286. package/dist/rerankers/jev.d.ts +1 -2
  287. package/dist/rerankers/jev.js +4 -5
  288. package/dist/rerankers/llm.d.ts +1 -2
  289. package/dist/rerankers/llm.js +1 -2
  290. package/dist/rerankers/types.d.ts +1 -2
  291. package/dist/rrf.d.ts +2 -2
  292. package/dist/rrf.js +2 -2
  293. package/dist/search/bm25-search.d.ts +1 -1
  294. package/dist/search/bm25-search.js +2 -1
  295. package/dist/search/boosts.js +2 -1
  296. package/dist/search/hybrid.d.ts +1 -1
  297. package/dist/search/hybrid.js +2 -1
  298. package/dist/search/physics-search.d.ts +1 -1
  299. package/dist/search/physics-search.js +2 -1
  300. package/dist/search/types.d.ts +2 -0
  301. package/dist/search/types.js +3 -1
  302. package/dist/secret-detect.d.ts +4 -5
  303. package/dist/secret-detect.js +6 -10
  304. package/dist/server/auth.js +5 -5
  305. package/dist/server/client-ip.js +1 -1
  306. package/dist/server/cursor.js +2 -1
  307. package/dist/server/mcp-http.js +4 -4
  308. package/dist/server/request.d.ts +3 -6
  309. package/dist/server/request.js +6 -7
  310. package/dist/server/routes/admin.js +5 -4
  311. package/dist/server/routes/customer-notes.js +6 -5
  312. package/dist/server/routes/decisions.js +4 -3
  313. package/dist/server/routes/incidents.js +7 -5
  314. package/dist/server/routes/memories.js +7 -7
  315. package/dist/server/routes/policies.js +3 -2
  316. package/dist/server/routes/predictions.js +12 -15
  317. package/dist/server/routes/processes.js +3 -2
  318. package/dist/server/routes/project-briefs.js +8 -7
  319. package/dist/server/routes/recall.js +95 -93
  320. package/dist/server/routes/skills.js +6 -5
  321. package/dist/server/types.d.ts +1 -1
  322. package/dist/server/validation.d.ts +1 -2
  323. package/dist/server/validation.js +7 -14
  324. package/dist/server-detect.js +72 -58
  325. package/dist/server.d.ts +2 -2
  326. package/dist/server.js +131 -117
  327. package/dist/shared.d.ts +17 -17
  328. package/dist/shared.js +93 -97
  329. package/dist/skills.d.ts +3 -3
  330. package/dist/skills.js +8 -8
  331. package/dist/store/audit-event.d.ts +2 -2
  332. package/dist/store/audit-event.js +1 -1
  333. package/dist/store/candidates.d.ts +2 -2
  334. package/dist/store/candidates.js +4 -3
  335. package/dist/store/conflicts.js +30 -22
  336. package/dist/store/delete-and-batch.d.ts +11 -14
  337. package/dist/store/delete-and-batch.js +40 -91
  338. package/dist/store/entry-reads.d.ts +17 -25
  339. package/dist/store/entry-reads.js +59 -40
  340. package/dist/store/entry-row.d.ts +6 -24
  341. package/dist/store/entry-row.js +6 -24
  342. package/dist/store/entry-writes.d.ts +6 -7
  343. package/dist/store/entry-writes.js +13 -11
  344. package/dist/store/handoffs.d.ts +1 -1
  345. package/dist/store/handoffs.js +7 -10
  346. package/dist/store/index-and-stats.d.ts +2 -6
  347. package/dist/store/index-and-stats.js +4 -10
  348. package/dist/store/mirrors.d.ts +6 -19
  349. package/dist/store/mirrors.js +14 -39
  350. package/dist/store/open.js +9 -31
  351. package/dist/store/rows.d.ts +5 -11
  352. package/dist/store/rows.js +6 -11
  353. package/dist/store/search-rows.d.ts +17 -34
  354. package/dist/store/search-rows.js +31 -59
  355. package/dist/store/sessions.d.ts +4 -5
  356. package/dist/store/sessions.js +5 -6
  357. package/dist/store/summaries.d.ts +13 -17
  358. package/dist/store/summaries.js +26 -70
  359. package/dist/support-bundle.js +4 -8
  360. package/dist/tenant.d.ts +1 -5
  361. package/dist/token-ledger.d.ts +1 -1
  362. package/dist/token-ledger.js +3 -5
  363. package/dist/trace.js +1 -3
  364. package/dist/version.d.ts +1 -1
  365. package/dist/version.js +1 -1
  366. package/dist/working-memory.d.ts +1 -1
  367. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  368. package/extensions/openclaw-plugin/package.json +1 -1
  369. package/openclaw.plugin.json +1 -1
  370. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -185,7 +185,7 @@ export function shouldAutoRepairCodexWrapper(currentCommand, flags) {
185
185
  // hook install codex` (a Codex update can restore the real binary over our
186
186
  // shim). Never first-installs — silently swapping the codex binary on routine
187
187
  // commands is a consent violation and reads as binary hijacking to
188
- // supply-chain scanners (issue #133).
188
+ // supply-chain scanners.
189
189
  function maybeRepairCodexWrapper(currentCommand, flags) {
190
190
  if (!shouldAutoRepairCodexWrapper(currentCommand, flags))
191
191
  return;
@@ -407,7 +407,7 @@ export const COMMANDS = {
407
407
  usage: VERB_USAGE.context,
408
408
  },
409
409
  hook: {
410
- run: async ({ args, flags }) => { (await import('./cli/setup.js')).cmdHook(args, flags); },
410
+ run: async ({ args }) => { (await import('./cli/setup.js')).cmdHook(args); },
411
411
  usage: VERB_USAGE.hook,
412
412
  },
413
413
  setup: {
@@ -579,13 +579,8 @@ async function main(command, args, flags, hippoRoot) {
579
579
  return;
580
580
  }
581
581
  maybeRepairCodexWrapper(command, flags);
582
- /** Global --scope well-formedness guard (v1.26.2). parseArgs stores a value-less
583
- * flag as boolean true; downstream the 14 consumer sites either coerced that to
584
- * the literal scope string 'true' (recall filter/unlock input, wm session scope,
585
- * the remember scope-tag dual-write) or silently dropped the user's scoping
586
- * intent (the remember envelope WRITE). Reject it once here, mirroring the
587
- * --hops value-less guard, so every current and future command - including the
588
- * thin-client dispatch relays - sees --scope only as a non-empty string. */
582
+ /** A value-less --scope parses as boolean true, which consumers coerced to the scope 'true' or dropped;
583
+ * reject it once here so every command, thin-client relays included, sees only a non-empty string. */
589
584
  if ('scope' in flags && (typeof flags['scope'] !== 'string' || !flags['scope'].trim())) {
590
585
  printError('--scope requires a non-empty value (e.g. --scope slack:private:C1).');
591
586
  process.exit(1);
@@ -56,6 +56,8 @@ export interface ItemContext {
56
56
  recordId: string | null;
57
57
  sessionId: string;
58
58
  originProject: string;
59
+ /** Where the session ran, so rows held under the project's older folder name count too. */
60
+ cwd: string | null;
59
61
  items: string[];
60
62
  }
61
63
  /** Items that become rows, then the record `done`, in one transaction so two sessions cannot both insert one text. Mirrors after commit. */
@@ -7,7 +7,7 @@ import { loadConfig } from './config.js';
7
7
  import { closeHippoDb, isSqliteBusy, openHippoDb } from './db.js';
8
8
  import { gatedWrite } from './gated-write.js';
9
9
  import { COMPACTION_MEMORY_TAG, COMPACTION_SOURCE_PREFIX, Layer, createMemory, generateId } from './memory.js';
10
- import { deriveOriginProject, isGlobalStoreRoot } from './project-identity.js';
10
+ import { isGlobalStoreRoot, originInSql, projectId, projectNames, resolveProjectIdentity } from './project-identity.js';
11
11
  import { maskEmails, redactSecretsStrict } from './secret-detect.js';
12
12
  import { strengthenRetrievedOn, writeEntryMirrors } from './store/entry-writes.js';
13
13
  import { isRecallBoostAblated } from './ablation.js';
@@ -30,11 +30,19 @@ const CLAIMED_SUFFIX = '.claimed';
30
30
  /** The marker redactSecretsStrict writes; an item holding it was a secret before it was stored. */
31
31
  const REDACTED = '[REDACTED]';
32
32
  /** Where the session ran: rows written through the global store keep the project the session was in. */
33
- function compactionOrigin(hippoRoot, cwd) {
33
+ function compactionProject(hippoRoot, cwd) {
34
34
  if (!isGlobalStoreRoot(hippoRoot))
35
- return deriveOriginProject(path.dirname(hippoRoot));
35
+ return resolveProjectIdentity(path.dirname(hippoRoot));
36
36
  // No cwd means user-global, as stampOriginProject gives the global store; undefined would fall back to the hook's own cwd.
37
- return cwd === null ? '' : deriveOriginProject(cwd);
37
+ return cwd === null ? '' : resolveProjectIdentity(cwd);
38
+ }
39
+ function compactionOrigin(hippoRoot, cwd) {
40
+ return projectId(compactionProject(hippoRoot, cwd));
41
+ }
42
+ /** The names a record's held rows may carry: its origin, plus the folder name rows saved before project ids used. */
43
+ function heldOrigins(hippoRoot, cwd, originProject) {
44
+ const names = projectNames(compactionProject(hippoRoot, cwd));
45
+ return names.includes(originProject) ? names : [originProject];
38
46
  }
39
47
  function scrub(text) {
40
48
  return maskEmails(redactSecretsStrict(text));
@@ -158,12 +166,76 @@ function restates(item, held) {
158
166
  return next === item.length;
159
167
  }
160
168
  /** Live rows of one tenant and origin that default recall shows: a compaction often restates what an earlier one, or the user, already saved. */
161
- function heldRows(db, tenantId, originProject) {
169
+ function heldRows(db, tenantId, origins) {
162
170
  // SAFETY: the SELECT names the id, source_session_id and content columns.
163
- const rows = db.prepare(`SELECT id, source_session_id, content FROM memories WHERE tenant_id = ? AND origin_project = ? AND superseded_by IS NULL AND kind != 'raw'
164
- AND (scope IS NULL OR (scope != 'unknown:legacy' AND scope NOT LIKE '%:private:%'))`).all(tenantId, originProject);
171
+ const rows = db.prepare(`SELECT id, source_session_id, content FROM memories WHERE tenant_id = ? AND ${originInSql(origins)} AND superseded_by IS NULL AND kind != 'raw'
172
+ AND (scope IS NULL OR (scope != 'unknown:legacy' AND scope NOT LIKE '%:private:%'))`).all(tenantId, ...origins);
165
173
  return rows.map((r) => ({ id: r.id, sessionId: r.source_session_id, words: words(r.content) }));
166
174
  }
175
+ function compactionEntry(text, ctx, baseHalfLifeDays) {
176
+ return {
177
+ ...createMemory(text, {
178
+ layer: Layer.Episodic,
179
+ tags: [COMPACTION_MEMORY_TAG],
180
+ source: `${COMPACTION_SOURCE_PREFIX}${ctx.sessionId}`,
181
+ confidence: 'observed',
182
+ kind: 'distilled',
183
+ source_session_id: ctx.sessionId,
184
+ tenantId: ctx.tenantId,
185
+ baseHalfLifeDays,
186
+ }),
187
+ origin_project: ctx.originProject,
188
+ };
189
+ }
190
+ /** Runs inside saveItems' transaction: restatements of held rows are counted, the rest go through the write gate. */
191
+ function writeItemRows(db, hippoRoot, ctx, rows, baseHalfLifeDays) {
192
+ const out = { written: [], repeats: 0, refused: 0, restated: [] };
193
+ const held = heldRows(db, ctx.tenantId, heldOrigins(hippoRoot, ctx.cwd, ctx.originProject));
194
+ const restated = new Set();
195
+ for (const text of rows) {
196
+ const itemWords = words(text);
197
+ const match = held.find((h) => restates(itemWords, h.words));
198
+ if (match) {
199
+ out.repeats++;
200
+ if (match.id !== null && match.sessionId !== ctx.sessionId)
201
+ restated.add(match.id);
202
+ continue;
203
+ }
204
+ // createMemory throws below 3 chars, which would sink the whole transaction.
205
+ if (text.trim().length < 3) {
206
+ out.refused++;
207
+ continue;
208
+ }
209
+ const entry = compactionEntry(text, ctx, baseHalfLifeDays);
210
+ if (gatedWrite(db, hippoRoot, entry, { actor: 'post-compact' }) === 'written') {
211
+ out.written.push(entry);
212
+ held.push({ id: null, sessionId: ctx.sessionId, words: itemWords });
213
+ }
214
+ else {
215
+ out.refused++;
216
+ }
217
+ }
218
+ out.restated = [...restated];
219
+ return out;
220
+ }
221
+ /** After commit: report skips, mirror the new rows, bump the counter. */
222
+ function finishItemWrites(hippoRoot, writes, log) {
223
+ if (writes.repeats > 0)
224
+ log(`skipped ${writes.repeats} item(s) the store already holds`);
225
+ if (writes.refused > 0)
226
+ log(`skipped ${writes.refused} item(s) the write gate refused`);
227
+ for (const entry of writes.written)
228
+ writeEntryMirrors(hippoRoot, entry);
229
+ if (writes.written.length > 0) {
230
+ try {
231
+ updateStats(hippoRoot, { remembered: writes.written.length });
232
+ }
233
+ catch (err) {
234
+ // The rows are committed; a counter that could not be bumped must not turn that into a failed step.
235
+ log(`remembered counter not updated: ${errorMessage(err)}`);
236
+ }
237
+ }
238
+ }
167
239
  /** Items that become rows, then the record `done`, in one transaction so two sessions cannot both insert one text. Mirrors after commit. */
168
240
  export function saveItems(db, hippoRoot, ctx, log) {
169
241
  const usable = ctx.items.filter((item) => !item.includes(REDACTED));
@@ -175,9 +247,7 @@ export function saveItems(db, hippoRoot, ctx, log) {
175
247
  if (capped > 0)
176
248
  log(`capped: ${capped} more kept in the record only`);
177
249
  const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
178
- const written = [];
179
- let repeats = 0;
180
- let refused = 0;
250
+ let writes;
181
251
  db.exec('BEGIN IMMEDIATE');
182
252
  try {
183
253
  if (ctx.recordId !== null) {
@@ -190,47 +260,11 @@ export function saveItems(db, hippoRoot, ctx, log) {
190
260
  return current?.items_written ?? 0;
191
261
  }
192
262
  }
193
- const held = heldRows(db, ctx.tenantId, ctx.originProject);
194
- const restated = new Set();
195
- for (const text of rows) {
196
- const itemWords = words(text);
197
- const match = held.find((h) => restates(itemWords, h.words));
198
- if (match) {
199
- repeats++;
200
- if (match.id !== null && match.sessionId !== ctx.sessionId)
201
- restated.add(match.id);
202
- continue;
203
- }
204
- // createMemory throws below 3 chars, which would sink the whole transaction.
205
- if (text.trim().length < 3) {
206
- refused++;
207
- continue;
208
- }
209
- const entry = {
210
- ...createMemory(text, {
211
- layer: Layer.Episodic,
212
- tags: [COMPACTION_MEMORY_TAG],
213
- source: `${COMPACTION_SOURCE_PREFIX}${ctx.sessionId}`,
214
- confidence: 'observed',
215
- kind: 'distilled',
216
- source_session_id: ctx.sessionId,
217
- tenantId: ctx.tenantId,
218
- baseHalfLifeDays,
219
- }),
220
- origin_project: ctx.originProject,
221
- };
222
- if (gatedWrite(db, hippoRoot, entry, { actor: 'post-compact' }) === 'written') {
223
- written.push(entry);
224
- held.push({ id: null, sessionId: ctx.sessionId, words: itemWords });
225
- }
226
- else {
227
- refused++;
228
- }
229
- }
263
+ writes = writeItemRows(db, hippoRoot, ctx, rows, baseHalfLifeDays);
230
264
  // Said again by another session is the same signal as being recalled; the same session carrying it forward is not.
231
- strengthenRetrievedOn(db, [...restated], { tenantId: ctx.tenantId, recallBoostAblated: isRecallBoostAblated() });
265
+ strengthenRetrievedOn(db, writes.restated, { tenantId: ctx.tenantId, recallBoostAblated: isRecallBoostAblated() });
232
266
  if (ctx.recordId !== null) {
233
- db.prepare(`UPDATE compactions SET items_written = ?, status = 'done' WHERE tenant_id = ? AND id = ?`).run(written.length, ctx.tenantId, ctx.recordId);
267
+ db.prepare(`UPDATE compactions SET items_written = ?, status = 'done' WHERE tenant_id = ? AND id = ?`).run(writes.written.length, ctx.tenantId, ctx.recordId);
234
268
  }
235
269
  db.exec('COMMIT');
236
270
  }
@@ -241,22 +275,8 @@ export function saveItems(db, hippoRoot, ctx, log) {
241
275
  catch { /* already rolled back; keep the original error */ }
242
276
  throw err;
243
277
  }
244
- if (repeats > 0)
245
- log(`skipped ${repeats} item(s) the store already holds`);
246
- if (refused > 0)
247
- log(`skipped ${refused} item(s) the write gate refused`);
248
- for (const entry of written)
249
- writeEntryMirrors(hippoRoot, entry);
250
- if (written.length > 0) {
251
- try {
252
- updateStats(hippoRoot, { remembered: written.length });
253
- }
254
- catch (err) {
255
- // The rows are committed; a counter that could not be bumped must not turn that into a failed step.
256
- log(`remembered counter not updated: ${errorMessage(err)}`);
257
- }
258
- }
259
- return written.length;
278
+ finishItemWrites(hippoRoot, writes, log);
279
+ return writes.written.length;
260
280
  }
261
281
  /** null when the text is not a PostCompact payload naming a session. */
262
282
  export function parsePostCompactPayload(stdinText) {
@@ -320,6 +340,7 @@ export function saveCompaction(hippoRoot, payload, log) {
320
340
  recordId: record?.id ?? null,
321
341
  sessionId: payload.sessionId,
322
342
  originProject: record?.originProject ?? compactionOrigin(hippoRoot, payload.cwd),
343
+ cwd: payload.cwd,
323
344
  items: text.items,
324
345
  }, log);
325
346
  }
@@ -494,7 +515,7 @@ function importSpool(db, hippoRoot, tenantId, log, deadline) {
494
515
  // The record holds the items now, so the file is done even if the write below fails.
495
516
  fs.rmSync(claimed, { force: true });
496
517
  removed = true;
497
- saveItems(db, hippoRoot, { tenantId: spooled.tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, items: record.items }, log);
518
+ saveItems(db, hippoRoot, { tenantId: spooled.tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, cwd: record.cwd, items: record.items }, log);
498
519
  finished++;
499
520
  }
500
521
  catch (err) {
@@ -515,7 +536,7 @@ export function replayCompactions(db, hippoRoot, log, deadline = Number.POSITIVE
515
536
  if (Date.now() > deadline)
516
537
  break;
517
538
  try {
518
- saveItems(db, hippoRoot, { tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, items: record.items }, log);
539
+ saveItems(db, hippoRoot, { tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, cwd: record.cwd, items: record.items }, log);
519
540
  finished++;
520
541
  }
521
542
  catch (err) {
@@ -549,7 +570,7 @@ export function replayCompactions(db, hippoRoot, log, deadline = Number.POSITIVE
549
570
  log(`${record.id} was filled by another process`);
550
571
  continue;
551
572
  }
552
- saveItems(db, hippoRoot, { tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, items: text.items }, log);
573
+ saveItems(db, hippoRoot, { tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, cwd: record.cwd, items: text.items }, log);
553
574
  finished++;
554
575
  }
555
576
  catch (err) {
package/dist/compare.d.ts CHANGED
@@ -6,8 +6,7 @@
6
6
  * multihop.ts, rerankers/*). Every comparator here takes a structural param
7
7
  * type instead of an imported one, on purpose — a type-only import back to
8
8
  * search.ts would still create the search.ts <-> physics.ts ESM import
9
- * cycle this module exists to avoid (r2 critic HIGH,
10
- * docs/plans/2026-07-09-recall-determinism.md T2).
9
+ * cycle this module exists to avoid.
11
10
  *
12
11
  * Mirrors the deliberate-determinism comment style already established in
13
12
  * graph-stream.ts:88, :165-168, :233-236 — a sort with a documented,
@@ -32,11 +31,9 @@ export interface EntryIdentity {
32
31
  * independently-created stores (different directory name, different insert
33
32
  * order of everything else on disk) sorts identically. The metadata keys make
34
33
  * byte-identical twins order by what they carry instead of by `id`
35
- * (`crypto.randomUUID()`), which is per-instance random: before v1.38.1 the
36
- * dedupe survivor of two twins that differed only in tags or source was
37
- * whichever id sorted first, and a semantic/episodic pair always kept the
38
- * episodic copy because `mem_` sorts before `sem_`. `id` stays the terminal
39
- * key so the order is total within one store.
34
+ * (`crypto.randomUUID()`), which is per-instance random and would make the
35
+ * dedupe survivor arbitrary. `id` stays the terminal key so the order is
36
+ * total within one store.
40
37
  *
41
38
  * Layer rank puts semantic first: semantic rows are consolidation output, so
42
39
  * keeping that copy preserves the promotion instead of demoting the memory.
@@ -47,8 +44,8 @@ export interface EntryIdentity {
47
44
  * Plain `<`/`>`, NOT `localeCompare`: `localeCompare` is locale- and
48
45
  * ICU-version-dependent (a determinism leak in its own right) and needlessly
49
46
  * slow for a tiebreak that only needs a total order. The metadata keys are
50
- * only computed on a content tie, which is rare post-T1 (path-tag embedding
51
- * fix), so the per-compare Set/sort cost never lands on the hot path.
47
+ * only computed on a content tie, which is rare, so the per-compare Set/sort
48
+ * cost never lands on the hot path.
52
49
  */
53
50
  export declare function compareEntryIdentity(a: EntryIdentity, b: EntryIdentity): number;
54
51
  /** Minimal shape for score-primary sort sites (SearchResult and friends
@@ -72,13 +69,11 @@ export declare function compareScoredResults(a: ScoredEntryLike, b: ScoredEntryL
72
69
  *
73
70
  * `ScoredPhysicsResult` (physics.ts) carries `{ memoryId, baseScore,
74
71
  * clusterAmplification, finalScore }` -- NO `entry`/`content` in scope at
75
- * that layer, so `compareEntryIdentity` cannot apply directly (plan T2
76
- * shape (c)). With only the default memoryId key this is PER-INSTANCE-ONLY
77
- * determinism; callers that need CROSS-INGEST stability supply `tieKeyOf`
78
- * mapping the result to its memory CONTENT (codex review finding: the
79
- * baseScore tie order selects the cluster_top_k amplification set, which
80
- * MUTATES scores before the downstream content-aware merge sort runs -- so
81
- * the tie key must be content-stable at THIS layer, not just downstream).
72
+ * that layer, so `compareEntryIdentity` cannot apply directly. With only the
73
+ * default memoryId key this is PER-INSTANCE-ONLY determinism; callers that need
74
+ * CROSS-INGEST stability supply `tieKeyOf` mapping the result to its memory
75
+ * CONTENT, because the baseScore tie order selects the cluster_top_k
76
+ * amplification set, which MUTATES scores before the content-aware merge sort.
82
77
  *
83
78
  * A factory (not a fixed-field comparator) because physics.ts re-sorts the
84
79
  * same result array by two different score fields in sequence (`baseScore`
package/dist/compare.js CHANGED
@@ -6,8 +6,7 @@
6
6
  * multihop.ts, rerankers/*). Every comparator here takes a structural param
7
7
  * type instead of an imported one, on purpose — a type-only import back to
8
8
  * search.ts would still create the search.ts <-> physics.ts ESM import
9
- * cycle this module exists to avoid (r2 critic HIGH,
10
- * docs/plans/2026-07-09-recall-determinism.md T2).
9
+ * cycle this module exists to avoid.
11
10
  *
12
11
  * Mirrors the deliberate-determinism comment style already established in
13
12
  * graph-stream.ts:88, :165-168, :233-236 — a sort with a documented,
@@ -21,11 +20,9 @@
21
20
  * independently-created stores (different directory name, different insert
22
21
  * order of everything else on disk) sorts identically. The metadata keys make
23
22
  * byte-identical twins order by what they carry instead of by `id`
24
- * (`crypto.randomUUID()`), which is per-instance random: before v1.38.1 the
25
- * dedupe survivor of two twins that differed only in tags or source was
26
- * whichever id sorted first, and a semantic/episodic pair always kept the
27
- * episodic copy because `mem_` sorts before `sem_`. `id` stays the terminal
28
- * key so the order is total within one store.
23
+ * (`crypto.randomUUID()`), which is per-instance random and would make the
24
+ * dedupe survivor arbitrary. `id` stays the terminal key so the order is
25
+ * total within one store.
29
26
  *
30
27
  * Layer rank puts semantic first: semantic rows are consolidation output, so
31
28
  * keeping that copy preserves the promotion instead of demoting the memory.
@@ -36,8 +33,8 @@
36
33
  * Plain `<`/`>`, NOT `localeCompare`: `localeCompare` is locale- and
37
34
  * ICU-version-dependent (a determinism leak in its own right) and needlessly
38
35
  * slow for a tiebreak that only needs a total order. The metadata keys are
39
- * only computed on a content tie, which is rare post-T1 (path-tag embedding
40
- * fix), so the per-compare Set/sort cost never lands on the hot path.
36
+ * only computed on a content tie, which is rare, so the per-compare Set/sort
37
+ * cost never lands on the hot path.
41
38
  */
42
39
  export function compareEntryIdentity(a, b) {
43
40
  return (compareStrings(a.content, b.content) ||
@@ -90,13 +87,11 @@ export function compareScoredResults(a, b) {
90
87
  *
91
88
  * `ScoredPhysicsResult` (physics.ts) carries `{ memoryId, baseScore,
92
89
  * clusterAmplification, finalScore }` -- NO `entry`/`content` in scope at
93
- * that layer, so `compareEntryIdentity` cannot apply directly (plan T2
94
- * shape (c)). With only the default memoryId key this is PER-INSTANCE-ONLY
95
- * determinism; callers that need CROSS-INGEST stability supply `tieKeyOf`
96
- * mapping the result to its memory CONTENT (codex review finding: the
97
- * baseScore tie order selects the cluster_top_k amplification set, which
98
- * MUTATES scores before the downstream content-aware merge sort runs -- so
99
- * the tie key must be content-stable at THIS layer, not just downstream).
90
+ * that layer, so `compareEntryIdentity` cannot apply directly. With only the
91
+ * default memoryId key this is PER-INSTANCE-ONLY determinism; callers that need
92
+ * CROSS-INGEST stability supply `tieKeyOf` mapping the result to its memory
93
+ * CONTENT, because the baseScore tie order selects the cluster_top_k
94
+ * amplification set, which MUTATES scores before the content-aware merge sort.
100
95
  *
101
96
  * A factory (not a fixed-field comparator) because physics.ts re-sorts the
102
97
  * same result array by two different score fields in sequence (`baseScore`
package/dist/config.d.ts CHANGED
@@ -64,34 +64,33 @@ export interface HippoConfig {
64
64
  enabled: boolean;
65
65
  budget: number;
66
66
  /** Skip a block identical to the one already injected this session
67
- * (ROADMAP TE2). Default true. Needs a session id from the hook payload. */
67
+ * Default true. Needs a session id from the hook payload. */
68
68
  skipUnchanged: boolean;
69
69
  /** Resend an unchanged block after this many consecutive skips, so long
70
70
  * sessions still see pinned rules near the latest turn. Default 10; 0
71
71
  * never resends an unchanged block. */
72
72
  refreshTurns: number;
73
- /** Z1: gate the hook's backfill on the prompt's own content instead of
74
- * the five newest memories. Default true since 1.55.0: overlap tied but median
75
- * tokens fell 847 to 533 (docs/evals/2026-09-26-z1-prompt-recall-result.md). */
73
+ /** Gate the hook's backfill on the prompt's own content instead of the five
74
+ * newest memories. Default true: the same overlap for fewer tokens. */
76
75
  promptRecall: boolean;
77
- /** Z1: overlap metric for the prompt-recall gate. Default 'jaccard' (tuned, docs/evals/2026-09-26-z1-prompt-recall-result.md). */
76
+ /** Overlap metric for the prompt-recall gate. Default 'jaccard' (tuned). */
78
77
  promptRecallMetric: PromptRecallMetric;
79
- /** Z1: minimum overlap score to admit a candidate. Default 0.04 (tuned). */
78
+ /** Minimum overlap score to admit a candidate. Default 0.04 (tuned). */
80
79
  promptRecallThreshold: number;
81
- /** Z1: minimum shared tokens to admit a candidate. Default 2. */
80
+ /** Minimum shared tokens to admit a candidate. Default 2. */
82
81
  promptRecallMinShared: number;
83
- /** Z1: max prompt-recall entries injected per prompt. Default 5 (tuned). */
82
+ /** Max prompt-recall entries injected per prompt. Default 5 (tuned). */
84
83
  promptRecallMaxItems: number;
85
- /** Z1: FTS candidate pool size per store before gating. Default 100. */
84
+ /** FTS candidate pool size per store before gating. Default 100. */
86
85
  promptRecallCandidates: number;
87
86
  };
88
- /** Memory scope isolation (v39): when true (default), ambient context
87
+ /** Memory scope isolation: when true (default), ambient context
89
88
  * (`hippo context`, the UserPromptSubmit hook, /v1/context, MCP
90
89
  * hippo_context) excludes memories owned by OTHER projects; explicit
91
90
  * recall is unaffected. Set false to disable the ORIGIN PARTITION only -
92
91
  * the secret veto is unconditional for ambient surfaces and no config or
93
92
  * flag re-includes secret-flagged rows (explicit recall still returns
94
- * them). See docs/plans/2026-07-01-memory-scope-isolation.md. */
93
+ * them). */
95
94
  contextProjectIsolation: boolean;
96
95
  extraction: {
97
96
  enabled: boolean | 'auto';
@@ -110,11 +109,8 @@ export interface HippoConfig {
110
109
  ambient: {
111
110
  enabled: boolean;
112
111
  };
113
- /** LC2-E3: opt-in learned memory-value rescue veto on the sleep decay pass
114
- * (docs/plans/2026-08-10-lc2-e3-mv-wiring.md). Default OFF — the frozen
115
- * E2 weights (src/memory-value-weights.ts) only run when explicitly
116
- * enabled; no other knobs in v1 (the rescue budget is a code constant
117
- * tied to E2 evidence, not user-tunable). */
112
+ /** Opt-in learned memory-value rescue veto on the sleep decay pass. Default OFF: the frozen weights
113
+ * (src/memory-value-weights.ts) only run when enabled; the rescue budget is a code constant, not user-tunable. */
118
114
  memoryValue: {
119
115
  enabled: boolean;
120
116
  };
@@ -131,8 +127,8 @@ export interface HippoConfig {
131
127
  * Default 180. 0 keeps dormant memories forever. */
132
128
  retentionDays: number;
133
129
  };
134
- /** FE2: tags a memory `churn-stale` when its named file/symbol/script
135
- * changed since storage. Default OFF - FE3 measures before it flips. */
130
+ /** Tags a memory `churn-stale` when its named file/symbol/script
131
+ * changed since storage. Default OFF until a measurement justifies the flip. */
136
132
  churnStaleness: {
137
133
  enabled: boolean;
138
134
  };
@@ -150,6 +146,11 @@ export interface HippoConfig {
150
146
  pilot: {
151
147
  holdoutRateBp: number;
152
148
  };
149
+ /** How a folder's project is named. `remote: false` drops the origin-remote rule, leaving
150
+ * `.hippo-project.json` then the folder name. Read from the global store's config only, so every store agrees. */
151
+ projectIdentity: {
152
+ remote: boolean;
153
+ };
153
154
  }
154
155
  export declare function loadConfig(hippoRoot: string): HippoConfig;
155
156
  //# sourceMappingURL=config.d.ts.map