hippo-memory 1.61.0 → 1.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/README.md +37 -53
  2. package/dist/agent-memories/apply.d.ts +3 -1
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +4 -1
  5. package/dist/agent-memories/claude-code.js +53 -9
  6. package/dist/agent-memories/report.d.ts +1 -0
  7. package/dist/agent-memories/report.js +2 -2
  8. package/dist/agent-memories/sync.d.ts +3 -3
  9. package/dist/agent-memories/sync.js +46 -24
  10. package/dist/agent-memories/types.d.ts +0 -2
  11. package/dist/ambient-store.d.ts +4 -4
  12. package/dist/ambient-store.js +4 -3
  13. package/dist/api/assemble.d.ts +7 -10
  14. package/dist/api/assemble.js +62 -66
  15. package/dist/api/audit.d.ts +2 -2
  16. package/dist/api/audit.js +2 -2
  17. package/dist/api/auth.d.ts +6 -6
  18. package/dist/api/auth.js +5 -7
  19. package/dist/api/context-select.d.ts +50 -0
  20. package/dist/api/context-select.js +342 -0
  21. package/dist/api/context-types.d.ts +12 -17
  22. package/dist/api/context.d.ts +5 -4
  23. package/dist/api/context.js +208 -535
  24. package/dist/api/dormant.js +1 -1
  25. package/dist/api/drill-down.d.ts +4 -4
  26. package/dist/api/drill-down.js +56 -41
  27. package/dist/api/outcome.d.ts +8 -13
  28. package/dist/api/outcome.js +13 -17
  29. package/dist/api/promote.d.ts +5 -8
  30. package/dist/api/promote.js +55 -66
  31. package/dist/api/quarantine.js +3 -2
  32. package/dist/api/recall-types.d.ts +43 -55
  33. package/dist/api/recall.d.ts +3 -3
  34. package/dist/api/recall.js +310 -456
  35. package/dist/api/remember.d.ts +2 -2
  36. package/dist/api/sleep.d.ts +7 -35
  37. package/dist/api/sleep.js +206 -220
  38. package/dist/api/tokens.d.ts +2 -2
  39. package/dist/api/tokens.js +2 -2
  40. package/dist/api/types.d.ts +6 -10
  41. package/dist/api/types.js +2 -4
  42. package/dist/audit-prune.d.ts +4 -6
  43. package/dist/audit-prune.js +3 -5
  44. package/dist/audit.js +11 -33
  45. package/dist/auth.d.ts +8 -9
  46. package/dist/auth.js +5 -7
  47. package/dist/autolearn.d.ts +1 -1
  48. package/dist/autolearn.js +1 -1
  49. package/dist/availability.js +3 -5
  50. package/dist/capture/command.d.ts +5 -4
  51. package/dist/capture/command.js +8 -22
  52. package/dist/capture/compact.d.ts +2 -2
  53. package/dist/capture/compact.js +11 -15
  54. package/dist/capture/extract.js +37 -115
  55. package/dist/capture-error.d.ts +1 -1
  56. package/dist/capture-error.js +1 -1
  57. package/dist/churn-git.d.ts +1 -1
  58. package/dist/churn-git.js +1 -1
  59. package/dist/cli/audit.js +3 -4
  60. package/dist/cli/auth.js +3 -6
  61. package/dist/cli/briefs.js +324 -306
  62. package/dist/cli/context.js +44 -34
  63. package/dist/cli/continuity.js +283 -271
  64. package/dist/cli/curate.d.ts +1 -1
  65. package/dist/cli/curate.js +43 -58
  66. package/dist/cli/dag.js +5 -9
  67. package/dist/cli/decisions.js +334 -345
  68. package/dist/cli/explain.js +68 -61
  69. package/dist/cli/goals.js +1 -1
  70. package/dist/cli/init.js +1 -1
  71. package/dist/cli/maintenance.js +62 -51
  72. package/dist/cli/playbooks.js +391 -379
  73. package/dist/cli/projects.js +11 -6
  74. package/dist/cli/recall.js +30 -44
  75. package/dist/cli/remember.js +118 -87
  76. package/dist/cli/session-hooks.js +106 -115
  77. package/dist/cli/setup.d.ts +1 -1
  78. package/dist/cli/setup.js +267 -250
  79. package/dist/cli/shared.js +3 -3
  80. package/dist/cli/slack.js +1 -1
  81. package/dist/cli/sleep.js +27 -1
  82. package/dist/cli/status.d.ts +4 -4
  83. package/dist/cli/status.js +80 -76
  84. package/dist/cli/transfer.js +88 -107
  85. package/dist/cli/usage.js +9 -6
  86. package/dist/cli.d.ts +1 -1
  87. package/dist/cli.js +4 -9
  88. package/dist/compaction-record.d.ts +2 -2
  89. package/dist/compaction-record.js +89 -68
  90. package/dist/compare.d.ts +11 -16
  91. package/dist/compare.js +11 -16
  92. package/dist/config.d.ts +19 -18
  93. package/dist/config.js +90 -67
  94. package/dist/connectors/github/backfill.d.ts +2 -2
  95. package/dist/connectors/github/backfill.js +8 -15
  96. package/dist/connectors/github/cli-impl.js +3 -8
  97. package/dist/connectors/github/deletion.d.ts +5 -12
  98. package/dist/connectors/github/deletion.js +5 -12
  99. package/dist/connectors/github/dlq.d.ts +6 -9
  100. package/dist/connectors/github/dlq.js +2 -3
  101. package/dist/connectors/github/ingest.d.ts +5 -7
  102. package/dist/connectors/github/ingest.js +8 -12
  103. package/dist/connectors/github/octokit-client.d.ts +3 -5
  104. package/dist/connectors/github/octokit-client.js +5 -6
  105. package/dist/connectors/github/signature.d.ts +9 -39
  106. package/dist/connectors/github/signature.js +9 -39
  107. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  108. package/dist/connectors/github/tenant-routing.js +1 -1
  109. package/dist/connectors/github/transform.js +2 -2
  110. package/dist/connectors/github/types.d.ts +2 -10
  111. package/dist/connectors/github/types.js +1 -3
  112. package/dist/connectors/slack/deletion.d.ts +3 -8
  113. package/dist/connectors/slack/deletion.js +3 -8
  114. package/dist/connectors/slack/dlq.d.ts +1 -1
  115. package/dist/connectors/slack/ingest.d.ts +1 -1
  116. package/dist/connectors/slack/ingest.js +7 -16
  117. package/dist/connectors/slack/signature.d.ts +1 -1
  118. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  119. package/dist/connectors/slack/tenant-routing.js +3 -5
  120. package/dist/connectors/slack/transform.d.ts +5 -6
  121. package/dist/connectors/slack/transform.js +5 -6
  122. package/dist/connectors/slack/types.d.ts +2 -6
  123. package/dist/connectors/slack/types.js +1 -3
  124. package/dist/connectors/slack/web-client.js +10 -3
  125. package/dist/connectors/slack/workspaces.d.ts +3 -5
  126. package/dist/connectors/slack/workspaces.js +3 -5
  127. package/dist/consolidate/conflicts.js +3 -14
  128. package/dist/consolidate/decay.js +9 -29
  129. package/dist/consolidate/llm-passes.js +4 -5
  130. package/dist/consolidate/merge.js +8 -23
  131. package/dist/consolidate/run.d.ts +1 -8
  132. package/dist/consolidate/run.js +3 -25
  133. package/dist/consolidate/sleep.js +5 -17
  134. package/dist/consolidate/traces.js +9 -21
  135. package/dist/customer-notes.d.ts +5 -7
  136. package/dist/customer-notes.js +82 -76
  137. package/dist/dag.d.ts +10 -21
  138. package/dist/dag.js +189 -203
  139. package/dist/db/continuity.js +2 -2
  140. package/dist/db/migrations/v14.js +1 -1
  141. package/dist/db/migrations/v15.js +1 -2
  142. package/dist/db/migrations/v16.js +3 -4
  143. package/dist/db/migrations/v17.js +2 -3
  144. package/dist/db/migrations/v19.js +1 -1
  145. package/dist/db/migrations/v20.js +1 -1
  146. package/dist/db/migrations/v21.js +2 -6
  147. package/dist/db/migrations/v22.js +2 -4
  148. package/dist/db/migrations/v23.js +1 -1
  149. package/dist/db/migrations/v24.js +4 -6
  150. package/dist/db/migrations/v25.js +2 -3
  151. package/dist/db/migrations/v26.js +3 -3
  152. package/dist/db/migrations/v27.js +2 -10
  153. package/dist/db/migrations/v28.js +5 -8
  154. package/dist/db/migrations/v29.js +3 -4
  155. package/dist/db/migrations/v30.js +2 -2
  156. package/dist/db/migrations/v31.js +1 -1
  157. package/dist/db/migrations/v32.js +1 -1
  158. package/dist/db/migrations/v33.js +3 -3
  159. package/dist/db/migrations/v34.js +1 -1
  160. package/dist/db/migrations/v35.js +3 -4
  161. package/dist/db/migrations/v36.js +3 -4
  162. package/dist/db/migrations/v37.js +5 -5
  163. package/dist/db/migrations/v38.js +7 -8
  164. package/dist/db/migrations/v39.js +1 -1
  165. package/dist/db/migrations/v40.js +4 -16
  166. package/dist/db/migrations/v41.js +3 -4
  167. package/dist/db/migrations/v42.js +3 -4
  168. package/dist/db/migrations/v45.js +1 -1
  169. package/dist/db/migrations/v46.js +1 -1
  170. package/dist/db/migrations/v47.js +1 -1
  171. package/dist/db/migrations/v48.js +1 -1
  172. package/dist/decisions.d.ts +2 -2
  173. package/dist/decisions.js +97 -80
  174. package/dist/dedupe.js +86 -61
  175. package/dist/delivery-recorder.js +154 -135
  176. package/dist/doctor.js +129 -110
  177. package/dist/dormant.js +1 -4
  178. package/dist/embedding-provider.d.ts +4 -8
  179. package/dist/embedding-provider.js +4 -8
  180. package/dist/embeddings.js +55 -47
  181. package/dist/env.d.ts +1 -1
  182. package/dist/env.js +12 -12
  183. package/dist/escape.d.ts +5 -0
  184. package/dist/escape.js +10 -0
  185. package/dist/eval-stats.d.ts +1 -2
  186. package/dist/eval-stats.js +1 -2
  187. package/dist/eval-suite.js +27 -21
  188. package/dist/extract.js +4 -9
  189. package/dist/failure-log.d.ts +3 -3
  190. package/dist/failure-log.js +1 -1
  191. package/dist/forward-claim-detector.d.ts +2 -4
  192. package/dist/forward-claim-detector.js +6 -11
  193. package/dist/goals.d.ts +3 -3
  194. package/dist/goals.js +103 -91
  195. package/dist/graph/read.d.ts +2 -2
  196. package/dist/graph/read.js +5 -6
  197. package/dist/graph/types.d.ts +8 -8
  198. package/dist/graph/write.d.ts +7 -14
  199. package/dist/graph/write.js +16 -23
  200. package/dist/graph-extract.d.ts +7 -8
  201. package/dist/graph-extract.js +62 -72
  202. package/dist/graph-recall.d.ts +2 -2
  203. package/dist/graph-recall.js +55 -49
  204. package/dist/graph-stream.d.ts +5 -6
  205. package/dist/graph-stream.js +66 -57
  206. package/dist/graph-view.d.ts +2 -2
  207. package/dist/graph-view.js +7 -7
  208. package/dist/half-life-migration.d.ts +1 -2
  209. package/dist/half-life-migration.js +2 -3
  210. package/dist/hooks/codex-session.js +1 -1
  211. package/dist/hooks/codex-wrapper.d.ts +1 -1
  212. package/dist/hooks/codex-wrapper.js +3 -2
  213. package/dist/hooks/json-hooks.d.ts +2 -2
  214. package/dist/hooks/json-hooks.js +5 -4
  215. package/dist/hooks/opencode.d.ts +1 -1
  216. package/dist/hooks/opencode.js +5 -4
  217. package/dist/hooks/shared.d.ts +3 -7
  218. package/dist/hooks/shared.js +1 -8
  219. package/dist/http-util.d.ts +2 -3
  220. package/dist/http-util.js +3 -0
  221. package/dist/importers/core.d.ts +2 -9
  222. package/dist/importers/core.js +15 -30
  223. package/dist/importers/sources.js +2 -1
  224. package/dist/importers/vault.js +2 -20
  225. package/dist/incidents.d.ts +1 -1
  226. package/dist/incidents.js +46 -39
  227. package/dist/instruction-detect.d.ts +1 -1
  228. package/dist/instruction-detect.js +1 -1
  229. package/dist/invalidation.d.ts +3 -0
  230. package/dist/invalidation.js +160 -114
  231. package/dist/json.d.ts +5 -0
  232. package/dist/json.js +4 -0
  233. package/dist/judgment.js +1 -2
  234. package/dist/local-embedding.js +1 -1
  235. package/dist/mcp/admin-tools.js +7 -17
  236. package/dist/mcp/format.js +1 -1
  237. package/dist/mcp/framing.js +3 -6
  238. package/dist/mcp/protocol.d.ts +2 -5
  239. package/dist/mcp/protocol.js +1 -3
  240. package/dist/mcp/recall-tools.js +12 -15
  241. package/dist/mcp/request.js +4 -3
  242. package/dist/mcp/session-state.js +2 -3
  243. package/dist/mcp/stdio.js +2 -1
  244. package/dist/mcp/tools.js +9 -6
  245. package/dist/memory-value-weights.d.ts +5 -8
  246. package/dist/memory-value-weights.js +5 -8
  247. package/dist/memory-value.d.ts +13 -13
  248. package/dist/memory-value.js +26 -37
  249. package/dist/memory.d.ts +20 -22
  250. package/dist/memory.js +24 -48
  251. package/dist/multihop.d.ts +1 -1
  252. package/dist/multihop.js +3 -2
  253. package/dist/owner-validation.d.ts +4 -5
  254. package/dist/owner-validation.js +4 -5
  255. package/dist/physics.d.ts +4 -4
  256. package/dist/physics.js +7 -9
  257. package/dist/policies.d.ts +9 -10
  258. package/dist/policies.js +96 -81
  259. package/dist/postinstall.js +3 -6
  260. package/dist/predictions/planning-fallacy.d.ts +9 -14
  261. package/dist/predictions/planning-fallacy.js +10 -16
  262. package/dist/predictions/store.d.ts +15 -23
  263. package/dist/predictions/store.js +36 -33
  264. package/dist/processes.d.ts +2 -7
  265. package/dist/processes.js +88 -72
  266. package/dist/project-briefs.d.ts +2 -3
  267. package/dist/project-briefs.js +141 -118
  268. package/dist/project-identity.d.ts +22 -9
  269. package/dist/project-identity.js +47 -12
  270. package/dist/project-merge.d.ts +28 -5
  271. package/dist/project-merge.js +213 -46
  272. package/dist/project-remote.d.ts +12 -0
  273. package/dist/project-remote.js +138 -0
  274. package/dist/prompt-recall.js +1 -2
  275. package/dist/rate-limit.d.ts +1 -1
  276. package/dist/rate-limit.js +1 -1
  277. package/dist/raw-archive.d.ts +9 -0
  278. package/dist/raw-archive.js +70 -53
  279. package/dist/recall-history.d.ts +19 -20
  280. package/dist/recall-history.js +24 -42
  281. package/dist/recall-pipeline.js +4 -28
  282. package/dist/recall-scope.d.ts +7 -8
  283. package/dist/recall-scope.js +7 -8
  284. package/dist/recall-trace.d.ts +5 -9
  285. package/dist/recall-trace.js +6 -10
  286. package/dist/refine-llm.d.ts +1 -1
  287. package/dist/refine-llm.js +2 -2
  288. package/dist/reject-flow.d.ts +3 -4
  289. package/dist/reject-flow.js +122 -117
  290. package/dist/rejection.d.ts +5 -6
  291. package/dist/rejection.js +7 -15
  292. package/dist/rerankers/clef.d.ts +1 -1
  293. package/dist/rerankers/jev.d.ts +1 -2
  294. package/dist/rerankers/jev.js +4 -5
  295. package/dist/rerankers/llm.d.ts +1 -2
  296. package/dist/rerankers/llm.js +1 -2
  297. package/dist/rerankers/types.d.ts +1 -2
  298. package/dist/rrf.d.ts +2 -2
  299. package/dist/rrf.js +2 -2
  300. package/dist/search/bm25-search.d.ts +1 -1
  301. package/dist/search/bm25-search.js +2 -1
  302. package/dist/search/boosts.js +2 -1
  303. package/dist/search/hybrid.d.ts +1 -1
  304. package/dist/search/hybrid.js +2 -1
  305. package/dist/search/physics-search.d.ts +1 -1
  306. package/dist/search/physics-search.js +2 -1
  307. package/dist/search/types.d.ts +2 -0
  308. package/dist/search/types.js +3 -1
  309. package/dist/secret-detect.d.ts +4 -5
  310. package/dist/secret-detect.js +6 -10
  311. package/dist/server/auth.js +5 -5
  312. package/dist/server/client-ip.js +1 -1
  313. package/dist/server/cursor.js +2 -1
  314. package/dist/server/mcp-http.js +4 -4
  315. package/dist/server/request.d.ts +3 -6
  316. package/dist/server/request.js +6 -7
  317. package/dist/server/routes/admin.js +5 -4
  318. package/dist/server/routes/customer-notes.js +6 -5
  319. package/dist/server/routes/decisions.js +4 -3
  320. package/dist/server/routes/incidents.js +7 -5
  321. package/dist/server/routes/memories.js +7 -7
  322. package/dist/server/routes/policies.js +3 -2
  323. package/dist/server/routes/predictions.js +12 -15
  324. package/dist/server/routes/processes.js +3 -2
  325. package/dist/server/routes/project-briefs.js +8 -7
  326. package/dist/server/routes/recall.js +95 -93
  327. package/dist/server/routes/skills.js +6 -5
  328. package/dist/server/types.d.ts +1 -1
  329. package/dist/server/validation.d.ts +1 -2
  330. package/dist/server/validation.js +7 -14
  331. package/dist/server-detect.js +72 -58
  332. package/dist/server.d.ts +2 -2
  333. package/dist/server.js +131 -117
  334. package/dist/shared.d.ts +26 -17
  335. package/dist/shared.js +102 -104
  336. package/dist/skills.d.ts +3 -3
  337. package/dist/skills.js +88 -72
  338. package/dist/store/audit-event.d.ts +2 -2
  339. package/dist/store/audit-event.js +1 -1
  340. package/dist/store/candidates.d.ts +2 -2
  341. package/dist/store/candidates.js +4 -3
  342. package/dist/store/conflicts.js +30 -22
  343. package/dist/store/delete-and-batch.d.ts +11 -14
  344. package/dist/store/delete-and-batch.js +40 -91
  345. package/dist/store/entry-reads.d.ts +17 -25
  346. package/dist/store/entry-reads.js +59 -40
  347. package/dist/store/entry-row.d.ts +6 -24
  348. package/dist/store/entry-row.js +6 -24
  349. package/dist/store/entry-writes.d.ts +6 -7
  350. package/dist/store/entry-writes.js +13 -11
  351. package/dist/store/handoffs.d.ts +1 -1
  352. package/dist/store/handoffs.js +7 -10
  353. package/dist/store/index-and-stats.d.ts +2 -6
  354. package/dist/store/index-and-stats.js +4 -10
  355. package/dist/store/mirrors.d.ts +6 -19
  356. package/dist/store/mirrors.js +14 -39
  357. package/dist/store/open.js +9 -31
  358. package/dist/store/rows.d.ts +5 -11
  359. package/dist/store/rows.js +6 -11
  360. package/dist/store/search-rows.d.ts +17 -34
  361. package/dist/store/search-rows.js +34 -56
  362. package/dist/store/sessions.d.ts +4 -5
  363. package/dist/store/sessions.js +5 -6
  364. package/dist/store/summaries.d.ts +13 -17
  365. package/dist/store/summaries.js +26 -70
  366. package/dist/support-bundle.js +4 -8
  367. package/dist/tenant.d.ts +1 -5
  368. package/dist/token-ledger.d.ts +1 -1
  369. package/dist/token-ledger.js +3 -5
  370. package/dist/trace.js +1 -3
  371. package/dist/version.d.ts +1 -1
  372. package/dist/version.js +1 -1
  373. package/dist/working-memory.d.ts +1 -1
  374. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  375. package/extensions/openclaw-plugin/package.json +1 -1
  376. package/openclaw.plugin.json +1 -1
  377. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Audit log retention pruning (v1.12.9).
2
+ * Audit log retention pruning.
3
3
  *
4
4
  * The `audit_log` table grows unbounded by default — every recall, write,
5
5
  * outcome, sleep, supersede, promote, forget, archive_raw, auth_revoke,
@@ -7,10 +7,8 @@
7
7
  * accumulate to millions of rows and slow down both audit queries and
8
8
  * incremental SQLite VACUUMs.
9
9
  *
10
- * Closes TODOS A5 v2 M6: "Audit log unbounded growth. Add a daily `audit
11
- * prune` cron + `hippo audit prune --older-than 90d` CLI in v2. Mind
12
- * regulatory retention floors (HIPAA, SOX, GDPR) — the prune should be
13
- * opt-in per tenant and emit its own audit trail event."
10
+ * Regulatory retention floors (HIPAA, SOX, GDPR) are why the prune is opt-in
11
+ * per tenant and emits its own audit trail event.
14
12
  *
15
13
  * Design notes:
16
14
  * - Per-tenant by default (matches existing audit CLI conventions).
@@ -29,7 +27,7 @@ import type { DatabaseSyncLike } from './db.js';
29
27
  export interface PruneAuditOpts {
30
28
  /** Cutoff in days. Rows with `ts < (now - N days)` are deleted. */
31
29
  olderThanDays: number;
32
- /** Tenant scope. Required — prune is always tenant-scoped per the A5 v2 design. */
30
+ /** Tenant scope. Required: prune is always tenant-scoped. */
33
31
  tenantId: string;
34
32
  /** When true, count matching rows but do NOT delete. Default false. */
35
33
  dryRun?: boolean;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Audit log retention pruning (v1.12.9).
2
+ * Audit log retention pruning.
3
3
  *
4
4
  * The `audit_log` table grows unbounded by default — every recall, write,
5
5
  * outcome, sleep, supersede, promote, forget, archive_raw, auth_revoke,
@@ -7,10 +7,8 @@
7
7
  * accumulate to millions of rows and slow down both audit queries and
8
8
  * incremental SQLite VACUUMs.
9
9
  *
10
- * Closes TODOS A5 v2 M6: "Audit log unbounded growth. Add a daily `audit
11
- * prune` cron + `hippo audit prune --older-than 90d` CLI in v2. Mind
12
- * regulatory retention floors (HIPAA, SOX, GDPR) — the prune should be
13
- * opt-in per tenant and emit its own audit trail event."
10
+ * Regulatory retention floors (HIPAA, SOX, GDPR) are why the prune is opt-in
11
+ * per tenant and emits its own audit trail event.
14
12
  *
15
13
  * Design notes:
16
14
  * - Per-tenant by default (matches existing audit CLI conventions).
package/dist/audit.js CHANGED
@@ -18,23 +18,16 @@ const VAGUE_ONLY = /^[\w\s,.'"-]+$/;
18
18
  // CJK words average ~2 characters, so approximate substantive units as one per
19
19
  // 2 CJK LETTERS.
20
20
  //
21
- // Two properties this must hold, both learned from codex review findings:
22
- // 1. Letters only, enforced by construction. Two separate wrong guesses were
23
- // caught here: a Katakana BLOCK range counts the middle dot and prolonged
24
- // sound mark, and `\p{Script=Han}` alone still counts Han-script
25
- // NON-letters (Kangxi radicals, the old Chinese hook mark) because Script
26
- // properties are not restricted to letters. The `(?=\p{L})` lookahead
27
- // makes "letters only" true by definition rather than by assertion, and
28
- // the category sweep in tests/df3-cjk-quality-floor.test.ts pins it so the
29
- // next wrong guess fails locally instead of in review.
21
+ // Two properties this must hold:
22
+ // 1. Letters only, enforced by construction: a Katakana BLOCK range counts the
23
+ // middle dot and prolonged sound mark, and `\p{Script=Han}` alone counts
24
+ // Han NON-letters (Kangxi radicals), so the `(?=\p{L})` lookahead makes
25
+ // "letters only" true by definition; tests/df3-cjk-quality-floor.test.ts sweeps it.
30
26
  //
31
27
  // SCOPE, stated precisely because the constant name says "CJK": this covers
32
28
  // Han, Hiragana and Katakana only. Hangul is absent (Korean largely survives
33
29
  // the whitespace split already) and other spaceless scripts - Thai, Khmer,
34
- // Burmese, Lao - still hit the original one-word failure. Their behavior is
35
- // byte-identical to before this change, so nothing regressed; widening the
36
- // script set is a separate, deliberately-scoped follow-up rather than another
37
- // mid-episode guess at this predicate.
30
+ // Burmese, Lao - still hit the original one-word failure.
38
31
  // 2. ADD to the latin count, never strip before it. Stripping CJK first can
39
32
  // REDUCE the count for short mixed tokens (`UI<han> DB<han> QA<han>` leaves
40
33
  // three 2-char latin fragments that fail the `> 2` filter), which would
@@ -84,25 +77,10 @@ function hasNoSpecificity(text) {
84
77
  const words = text.toLowerCase().split(/\s+/);
85
78
  const hasNumber = /\d/.test(text);
86
79
  const hasProperNoun = /[A-Z][a-z]{2,}/.test(text);
87
- // An ACRONYM is specificity too, and this test could not see one: the
88
- // proper-noun pattern needs lowercase after the capital, so "PR", "CI",
89
- // "DB", "API", "S3" - the densest domain tokens in a technical memory -
90
- // all read as vague. Surfaced by DF2: clause-bounding correctly shortened
91
- // "The rule is every PR needs two approvals, no exceptions." to "every PR
92
- // needs two approvals", which then fell under the 40-char vagueness gate
93
- // and was silently DROPPED - a rule that stored before this branch.
94
- // ...but an acronym only signals specificity when it stands out AGAINST
95
- // ordinary prose. Without the lowercase requirement, any shouted phrase
96
- // qualifies: "FIXED SIGNALS" passed the gate while the identical
97
- // "fixed signals" was correctly rejected, so capitalization alone bought a
98
- // bypass - into auditMemory and includeRecent as well, where junk would
99
- // then occupy recent-context slots. Codex P2, r8.
100
- // CHAT acronyms are not domain signal. Admitting any all-caps token let
101
- // "LGTM ship it", "TODO fix this thing", "FYI all done here" through a gate
102
- // that correctly rejected them before - and this gate is shared, so the
103
- // effect is retroactive: junk rows already in a user's store were filtered
104
- // out of recent-context slots and would have started occupying them, and
105
- // `hippo audit` would have stopped flagging them. Found at the ship gate.
80
+ // An ACRONYM is specificity too: the proper-noun pattern needs lowercase after the capital, so
81
+ // "PR", "CI", "DB", "API", "S3" (the densest domain tokens in a technical memory) all read as vague.
82
+ // It counts only against ordinary prose (the lowercase test), else a shouted phrase bypasses the gate,
83
+ // and CHAT acronyms never count: the gate is shared, so "LGTM ship it" would reach recent-context slots.
106
84
  const CHAT_ACRONYMS = /^(?:TODO|FYI|LGTM|IIRC|IMO|IMHO|FWIW|TBD|BTW|ASAP|AFAIK|WIP|NB|PS)$/;
107
85
  const domainAcronyms = (text.match(/\b[A-Z]{2,6}\b/g) ?? []).filter(a => !CHAT_ACRONYMS.test(a));
108
86
  const hasAcronym = domainAcronyms.length > 0 && /[a-z]/.test(text);
@@ -173,7 +151,7 @@ export function isContentWorthStoring(content) {
173
151
  return true;
174
152
  }
175
153
  // ---------------------------------------------------------------------------
176
- // A5 audit log primitives (append-only mutation trail)
154
+ // Audit log primitives (append-only mutation trail)
177
155
  // ---------------------------------------------------------------------------
178
156
  // The one list of audit ops: the AuditOp type, `hippo audit list --op` and GET /v1/audit?op= all read it.
179
157
  export const AUDIT_OPS = [
package/dist/auth.d.ts CHANGED
@@ -11,7 +11,7 @@ export declare function apiKeyVerifyStats(): ApiKeyVerifyStats;
11
11
  export interface CreateApiKeyOpts {
12
12
  tenantId: string;
13
13
  label?: string;
14
- /** v1.12.0 A5 v2 sub-1: 'admin' | 'member'. Defaults to 'admin' (backward-compat for callers that don't specify). */
14
+ /** 'admin' | 'member'. Defaults to 'admin' (backward-compat for callers that don't specify). */
15
15
  role?: 'admin' | 'member';
16
16
  }
17
17
  export interface CreateApiKeyResult {
@@ -23,9 +23,9 @@ export interface ValidateResult {
23
23
  valid: boolean;
24
24
  tenantId?: string;
25
25
  keyId?: string;
26
- /** v1.12.0 A5 v2 sub-1: 'admin' | 'member'. Present only when valid=true. */
26
+ /** 'admin' | 'member'. Present only when valid=true. */
27
27
  role?: 'admin' | 'member';
28
- /** EI2: scope grants for this key. Present only when valid=true. */
28
+ /** Scope grants for this key. Present only when valid=true. */
29
29
  scopes?: string[];
30
30
  }
31
31
  /** The identity a verified API key carries. */
@@ -52,11 +52,11 @@ export declare class VerifiedKeyCache {
52
52
  /** Verify a bearer API key for `hippoRoot`; a cache hit skips both scrypt and the DB open. Null when invalid. */
53
53
  export declare function verifyApiKeyCached(hippoRoot: string, plaintext: string): VerifiedApiKey | null;
54
54
  export declare function revokeApiKey(db: DatabaseSyncLike, keyId: string): void;
55
- /** EI2: grant `keyId` read access to one restricted `scope`. Idempotent. */
55
+ /** Grant `keyId` read access to one restricted `scope`. Idempotent. */
56
56
  export declare function grantScope(db: DatabaseSyncLike, keyId: string, scope: string): void;
57
- /** EI2: revoke `keyId`'s grant on `scope`. Not an error when no such grant exists. */
57
+ /** Revoke `keyId`'s grant on `scope`. Not an error when no such grant exists. */
58
58
  export declare function ungrantScope(db: DatabaseSyncLike, keyId: string, scope: string): void;
59
- /** EI2: every restricted scope `keyId` may read. */
59
+ /** Every restricted scope `keyId` may read. */
60
60
  export declare function listScopeGrants(db: DatabaseSyncLike, keyId: string): string[];
61
61
  export interface ApiKeyListItem {
62
62
  keyId: string;
@@ -65,12 +65,11 @@ export interface ApiKeyListItem {
65
65
  createdAt: string;
66
66
  revokedAt: string | null;
67
67
  /**
68
- * v1.12.3: authorization role bound to the key. SELECT extended to read
69
- * the `role` column (added in schema migration v26 by v1.12.0 sub-1).
68
+ * Authorization role bound to the key, from the `role` column (schema migration v26).
70
69
  * Fail-safe-to-member cast: any non-'admin' value reads as 'member'.
71
70
  */
72
71
  role: 'admin' | 'member';
73
- /** EI2: restricted scopes this key may read. */
72
+ /** Restricted scopes this key may read. */
74
73
  scopes: string[];
75
74
  }
76
75
  export interface ListApiKeysOpts {
package/dist/auth.js CHANGED
@@ -40,10 +40,8 @@ export function createApiKey(db, opts) {
40
40
  const secret = randBase32(SECRET_LEN);
41
41
  const plaintext = `${keyId}.${secret}`;
42
42
  const hash = hashKey(plaintext);
43
- // v1.12.0: 6-column INSERT including role. Boot-order guarantee:
44
- // openHippoDb runs runMigrations synchronously before returning the db
45
- // handle, so migration v26 (adds role column) is in place before this
46
- // INSERT runs.
43
+ // openHippoDb runs runMigrations synchronously before returning the db handle,
44
+ // so migration v26 (adds role column) is in place before this INSERT runs.
47
45
  db.prepare(`INSERT INTO api_keys (key_id, key_hash, tenant_id, label, created_at, role) VALUES (?, ?, ?, ?, ?, ?)`).run(keyId, hash, opts.tenantId, opts.label ?? null, new Date().toISOString(), opts.role ?? 'admin');
48
46
  return { keyId, plaintext };
49
47
  }
@@ -146,18 +144,18 @@ export function revokeApiKey(db, keyId) {
146
144
  .run(new Date().toISOString(), keyId);
147
145
  verifiedKeys.delete(keyId);
148
146
  }
149
- /** EI2: grant `keyId` read access to one restricted `scope`. Idempotent. */
147
+ /** Grant `keyId` read access to one restricted `scope`. Idempotent. */
150
148
  export function grantScope(db, keyId, scope) {
151
149
  db.prepare(`INSERT INTO api_key_scope_grants (key_id, scope, granted_at) VALUES (?, ?, ?)
152
150
  ON CONFLICT(key_id, scope) DO NOTHING`).run(keyId, scope, new Date().toISOString());
153
151
  verifiedKeys.delete(keyId);
154
152
  }
155
- /** EI2: revoke `keyId`'s grant on `scope`. Not an error when no such grant exists. */
153
+ /** Revoke `keyId`'s grant on `scope`. Not an error when no such grant exists. */
156
154
  export function ungrantScope(db, keyId, scope) {
157
155
  db.prepare(`DELETE FROM api_key_scope_grants WHERE key_id = ? AND scope = ?`).run(keyId, scope);
158
156
  verifiedKeys.delete(keyId);
159
157
  }
160
- /** EI2: every restricted scope `keyId` may read. */
158
+ /** Every restricted scope `keyId` may read. */
161
159
  export function listScopeGrants(db, keyId) {
162
160
  // SAFETY: rows' shape matches the single `scope` column named in the SELECT above.
163
161
  const rows = db
@@ -30,7 +30,7 @@ export declare function partitionLessons(lessons: string[]): {
30
30
  * Check if a substantially similar memory already exists.
31
31
  * Returns true if overlap > threshold (default 0.7).
32
32
  *
33
- * L9: `tenantId` is opt-in. Only takes effect when the first argument is a
33
+ * `tenantId` is opt-in. Only takes effect when the first argument is a
34
34
  * root string (string-overload path). When the first argument is a
35
35
  * pre-loaded MemoryEntry[], the caller has already scoped — tenantId is
36
36
  * ignored on that path.
package/dist/autolearn.js CHANGED
@@ -100,7 +100,7 @@ export function partitionLessons(lessons) {
100
100
  * Check if a substantially similar memory already exists.
101
101
  * Returns true if overlap > threshold (default 0.7).
102
102
  *
103
- * L9: `tenantId` is opt-in. Only takes effect when the first argument is a
103
+ * `tenantId` is opt-in. Only takes effect when the first argument is a
104
104
  * root string (string-overload path). When the first argument is a
105
105
  * pre-loaded MemoryEntry[], the caller has already scoped — tenantId is
106
106
  * ignored on that path.
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // J2 — Availability-bias detector (Track J: biases-over-memory-state)
2
+ // Availability-bias detector (biases over memory state)
3
3
  // ---------------------------------------------------------------------------
4
4
  //
5
5
  // Flags when a recall's returned top-K is dominated by recent entries while
@@ -10,10 +10,8 @@
10
10
  // creation timestamp, so we can compare the age distribution of what was
11
11
  // RETURNED against the age distribution of the pool it was drawn from.
12
12
  //
13
- // Soft warning ONLY (ROADMAP-RESEARCH.md Track J discipline note): this never
14
- // filters, reorders, or suppresses a result. It surfaces a hint the calling
15
- // agent may choose to act on, exactly like J1 anchoringHint / J3
16
- // planningFallacyHint / C5 suppressionSummary.
13
+ // Soft warning ONLY: this never filters, reorders, or suppresses a result. It surfaces
14
+ // a hint the calling agent may act on, like anchoringHint / planningFallacyHint / suppressionSummary.
17
15
  //
18
16
  // PURE: no I/O, no env reads. The env gate (HIPPO_AVAILABILITY=off) and the
19
17
  // audit emission live in the callers (api.recall, cmdRecall, MCP), mirroring
@@ -1,3 +1,4 @@
1
+ import { type ProjectRef } from '../project-identity.js';
1
2
  import { type SessionTurn } from './transcript.js';
2
3
  export interface CaptureOptions {
3
4
  source: 'stdin' | 'file' | 'last-session';
@@ -21,13 +22,13 @@ export interface CaptureOptions {
21
22
  dryRun: boolean;
22
23
  global: boolean;
23
24
  /**
24
- * L9: tenant scope for the dedup read in `cmdCaptureCore`. When provided
25
+ * Tenant scope for the dedup read in `cmdCaptureCore`. When provided
25
26
  * AND `global` is false, the dedup check only considers this tenant's
26
- * existing memories. Undefined preserves pre-1.12.1 host-wide dedup
27
- * behaviour. Ignored when `global: true` (global captures are host-wide).
27
+ * existing memories. Undefined means host-wide dedup. Ignored when `global: true` (global captures are host-wide).
28
28
  */
29
29
  tenantId?: string;
30
- originProject?: string;
30
+ /** The session's project: rows are stamped with its id, and dedup reads its rows under either name. */
31
+ originProject?: ProjectRef;
31
32
  }
32
33
  export declare function cmdCapture(hippoRoot: string, options: CaptureOptions): void;
33
34
  //# sourceMappingURL=command.d.ts.map
@@ -14,7 +14,7 @@ import { maskEmails, redactSecretsStrict } from '../secret-detect.js';
14
14
  import { RejectedValueError, checkRejectionGuard } from '../rejection.js';
15
15
  import { openHippoDb, closeHippoDb } from '../db.js';
16
16
  import { loadConfig } from '../config.js';
17
- import { classifyOriginProject } from '../project-identity.js';
17
+ import { classifyOriginProject, projectId } from '../project-identity.js';
18
18
  import { isStringValue } from '../capture-contract.js';
19
19
  import { errorMessage, log } from '../log.js';
20
20
  import { extractFromText } from './extract.js';
@@ -116,7 +116,7 @@ function cmdCaptureCore(hippoRoot, options) {
116
116
  console.log('No actionable items found in the input.');
117
117
  return;
118
118
  }
119
- // Dedup only against rows this capture's reader sees: another tenant's rows (L9), or another
119
+ // Dedup only against rows this capture's reader sees: another tenant's rows, or another
120
120
  // project's, are hidden from it, so they must not stop its own copy.
121
121
  const stored = loadAllEntries(targetRoot, useGlobal ? undefined : options.tenantId);
122
122
  const origin = options.originProject;
@@ -175,13 +175,8 @@ function readCaptureText(options) {
175
175
  function captureExtractedItems(targetRoot, options, extracted, keys) {
176
176
  const tally = { captured: 0, skipped: 0, rejected: 0 };
177
177
  const baseHalfLifeDays = loadConfig(targetRoot).defaultHalfLifeDays;
178
- // AT1 P2 fix (dry-run parity, docs/plans/2026-08-15-at1-rejected-value-tombstone.md):
179
- // dry-run used to skip the guarded write branch ENTIRELY, so a tombstoned
180
- // extraction printed as `[capture]` and counted toward `captured` — the
181
- // preview lied about what a real run would do. Mirrors importers.ts's
182
- // importEntries dry-run probe (commit 6146e82): open a read-only handle
183
- // once, run the same checkRejectionGuard the real write path uses via
184
- // writeEntry, never write anything.
178
+ // Dry run probes the same checkRejectionGuard the real write uses, on a read-only handle,
179
+ // so a tombstoned item previews as rejected rather than captured.
185
180
  const dryRunDb = options.dryRun ? openHippoDb(targetRoot) : null;
186
181
  const writeDb = options.dryRun ? null : openStore(targetRoot);
187
182
  try {
@@ -207,16 +202,8 @@ function captureExtractedItems(targetRoot, options, extracted, keys) {
207
202
  }
208
203
  function captureEntry(item, options, baseHalfLifeDays) {
209
204
  const useGlobal = options.global;
210
- // A3: kind defaults to 'distilled'. capture.ts extracts curated items from
211
- // session output (not raw transcript chunks), so distilled is correct. If a
212
- // future variant captures full raw session text, it MUST set kind: 'raw'
213
- // and route deletions through archiveRawMemory(). See MEMORY_ENVELOPE.md.
214
- // L9: the dedup read above is scoped by options.tenantId — the WRITE
215
- // must match, or scoped-dedup-passes-then-default-tenant-write breaks
216
- // the per-tenant contract. Mirror the dedup-read guard: when
217
- // global: true, the global store is host-wide and tenant is irrelevant
218
- // (createMemory's default 'default' applies). When global: false,
219
- // options.tenantId scopes the write to the same tenant as the dedup.
205
+ // kind stays 'distilled': these are curated items, not raw transcript (see MEMORY_ENVELOPE.md).
206
+ // The write tenant must match the dedup read's, or scoped dedup passes and the row lands in 'default'.
220
207
  const created = createMemory(item.content, {
221
208
  layer: Layer.Episodic,
222
209
  tags: item.tags,
@@ -225,7 +212,7 @@ function captureEntry(item, options, baseHalfLifeDays) {
225
212
  tenantId: useGlobal ? undefined : options.tenantId,
226
213
  baseHalfLifeDays,
227
214
  });
228
- return options.originProject === undefined ? created : { ...created, origin_project: options.originProject };
215
+ return options.originProject === undefined ? created : { ...created, origin_project: projectId(options.originProject) };
229
216
  }
230
217
  /** Previews or writes one non-duplicate item and names the tally it counts toward. */
231
218
  function captureOne(ctx, item, entry) {
@@ -246,8 +233,7 @@ function captureOne(ctx, item, entry) {
246
233
  console.log(` [capture] (${item.category}) ${item.content}`);
247
234
  }
248
235
  else if (writeDb !== null) {
249
- // AT1 (plan §3 containment): one rejected item must not abort the
250
- // rest of this capture's items.
236
+ // One rejected item must not abort the rest of this capture's items.
251
237
  const stamped = stampOriginProject(targetRoot, entry);
252
238
  const outcome = gatedWrite(writeDb, targetRoot, stamped);
253
239
  if (outcome === 'skipped:rejected')
@@ -7,7 +7,7 @@ export declare const PRE_COMPACT_NEXT_STEP_CAP = 500;
7
7
  * newest with assistant responses after them, so a head-first cap keeps
8
8
  * stale context and drops exactly the newest working state this feature
9
9
  * exists to preserve. Keep the LAST maxChars instead, aligned forward to a
10
- * nearby line start, with a trim marker (codex round 3).
10
+ * nearby line start, with a trim marker.
11
11
  */
12
12
  export declare function truncateKeepNewest(text: string, maxChars: number): string;
13
13
  /**
@@ -37,7 +37,7 @@ export declare function cmdPreCompact(hippoRoot: string, options: PreCompactOpti
37
37
  export interface PostCompactOptions {
38
38
  stdinText?: string;
39
39
  logFile?: string;
40
- afterSave?: (transcriptPath: string, originProject: string, log: (message: string) => void) => void;
40
+ afterSave?: (transcriptPath: string, cwd: string | null, log: (message: string) => void) => void;
41
41
  }
42
42
  /** PostCompact entry point: saves the summary and its items, replays earlier leftovers, returns the one line to show. Never throws, so the hook exits 0. */
43
43
  export declare function cmdPostCompact(hippoRoot: string, options: PostCompactOptions): string | null;
@@ -2,7 +2,7 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { isInitialized } from '../store/open.js';
4
4
  import { saveActiveTaskSnapshot, loadActiveTaskSnapshot } from '../store/sessions.js';
5
- import { PRE_COMPACT_INSTRUCTION, compactionOrigin, parsePostCompactPayload, postCompactLine, recordCompactionStart, recordSnapshotSaved, saveCompaction, replayCompactionsAt, COMPACTION_DB_WAIT_MS, } from '../compaction-record.js';
5
+ import { PRE_COMPACT_INSTRUCTION, parsePostCompactPayload, postCompactLine, recordCompactionStart, recordSnapshotSaved, saveCompaction, replayCompactionsAt, COMPACTION_DB_WAIT_MS, } from '../compaction-record.js';
6
6
  import { resolveTenantId } from '../tenant.js';
7
7
  import { defaultPreCompactLogPath } from '../hooks/shared.js';
8
8
  import { maskEmails, redactSecretsStrict } from '../secret-detect.js';
@@ -21,7 +21,7 @@ export const PRE_COMPACT_NEXT_STEP_CAP = 500;
21
21
  * newest with assistant responses after them, so a head-first cap keeps
22
22
  * stale context and drops exactly the newest working state this feature
23
23
  * exists to preserve. Keep the LAST maxChars instead, aligned forward to a
24
- * nearby line start, with a trim marker (codex round 3).
24
+ * nearby line start, with a trim marker.
25
25
  */
26
26
  export function truncateKeepNewest(text, maxChars) {
27
27
  if (text.length <= maxChars)
@@ -154,7 +154,7 @@ export function transcriptWorkingState(transcriptPath, log) {
154
154
  let rawTask = '';
155
155
  let rawNextStep = '';
156
156
  try {
157
- // Compaction fires when a big tool_result lands (CX7), so the last human and assistant turns can sit
157
+ // Compaction fires when a big tool_result lands, so the last human and assistant turns can sit
158
158
  // megabytes back: grow the window a bounded number of times until both turns are in it.
159
159
  const size = fs.statSync(transcriptPath).size;
160
160
  for (const cap of [PRE_COMPACT_TAIL_BYTES, PRE_COMPACT_TAIL_BYTES * 4, PRE_COMPACT_TAIL_BYTES * 16]) {
@@ -176,8 +176,8 @@ export function transcriptWorkingState(transcriptPath, log) {
176
176
  log('skip: empty summary');
177
177
  return null;
178
178
  }
179
- // X9: these fields skip the capture content gate and reach a prompt, so the strict scrub runs. The caps protect the
180
- // re-injection token budget and never split a surrogate pair (X2); `hippo snapshot save` stays uncapped.
179
+ // These fields skip the capture content gate and reach a prompt, so the strict scrub runs. The caps protect the
180
+ // re-injection token budget and never split a surrogate pair; `hippo snapshot save` stays uncapped.
181
181
  const task = maskEmails(redactSecretsStrict(rawTask));
182
182
  const summary = maskEmails(redactSecretsStrict(rawSummary));
183
183
  const nextStep = maskEmails(redactSecretsStrict(rawNextStep));
@@ -189,7 +189,7 @@ export function transcriptWorkingState(transcriptPath, log) {
189
189
  }
190
190
  /** Runs the PreCompact producer: records the compaction, asks the summariser for memories, saves a working-state snapshot. Never extracts memories itself; SessionEnd capture owns that. */
191
191
  function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile) {
192
- // X3: the PreCompact hook fires in every Claude Code project, including
192
+ // The PreCompact hook fires in every Claude Code project, including
193
193
  // ones that never ran `hippo init`, so gate before any store-opening call
194
194
  // (saveActiveTaskSnapshot etc. call initStore, which would create one).
195
195
  if (!isInitialized(hippoRoot)) {
@@ -219,12 +219,8 @@ function runPreCompact(hippoRoot, stdinText, stdinTimedOut, logFile) {
219
219
  }
220
220
  /** The transcript to snapshot, or null after logging why there is none. */
221
221
  function resolvePreCompactTranscript(payloadTranscriptPath, stdinText, logFile) {
222
- // A payload transcript_path is EXCLUSIVE: never fall back to
223
- // newest-transcript auto-discovery when it's missing/unreadable. That
224
- // fallback would snapshot a DIFFERENT session's transcript under THIS
225
- // payload's session_id — cross-session contamination with wrong linkage
226
- // (verify-stage E2E finding, 2026-08-03). Auto-discovery only applies
227
- // on a true manual invocation (no payload at all).
222
+ // A payload transcript_path is EXCLUSIVE: auto-discovery would snapshot a DIFFERENT session's
223
+ // transcript under THIS payload's session_id, so it runs only on a manual invocation (no payload).
228
224
  let transcriptPath;
229
225
  if (payloadTranscriptPath !== null) {
230
226
  if (isReadableFile(payloadTranscriptPath)) {
@@ -246,7 +242,7 @@ function resolvePreCompactTranscript(payloadTranscriptPath, stdinText, logFile)
246
242
  }
247
243
  function saveDerivedSnapshot(hippoRoot, logFile, sessionId, recordId, derived) {
248
244
  const tenantId = resolveTenantId({});
249
- // Per-field merge (X1): a tool-heavy tail whose only user turns are
245
+ // Per-field merge: a tool-heavy tail whose only user turns are
250
246
  // tool_result arrays derives an empty task even though the summary is
251
247
  // non-empty. Loading the existing snapshot first lets each field fall
252
248
  // back independently instead of the whole write clobbering a
@@ -258,7 +254,7 @@ function saveDerivedSnapshot(hippoRoot, logFile, sessionId, recordId, derived) {
258
254
  catch {
259
255
  // No existing snapshot to merge against — proceed with derived-only.
260
256
  }
261
- // CX6 (codex round 2): field fallback must never move content across
257
+ // Field fallback must never move content across
262
258
  // sessions — session A's task carried into a snapshot saved under session
263
259
  // B's id would pass compact-resume's session gate wearing the wrong
264
260
  // badge. Fall back only when the existing snapshot has no session, this
@@ -335,7 +331,7 @@ export function cmdPostCompact(hippoRoot, options) {
335
331
  storeBusy = saved.deferred;
336
332
  if (!storeBusy && payload.transcriptPath !== null && options.afterSave) {
337
333
  try {
338
- options.afterSave(payload.transcriptPath, compactionOrigin(hippoRoot, payload.cwd), log);
334
+ options.afterSave(payload.transcriptPath, payload.cwd, log);
339
335
  }
340
336
  catch (err) {
341
337
  log(`agent memory import failed: ${errorMessage(err)}`);