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
@@ -1,6 +1,6 @@
1
1
  /**
2
- * E3 graph observability + visualization — READ-ONLY over the entity/relation
3
- * graph (docs/plans/2026-06-02-graph-observability.md).
2
+ * Graph observability and visualization, READ-ONLY over the entity/relation
3
+ * graph.
4
4
  *
5
5
  * This module only READS the graph (`loadEntities` / `loadRelations`) and renders
6
6
  * view-models; it issues no INSERT/UPDATE/DELETE, so `scripts/check-graph-writes.mjs`
@@ -1,6 +1,6 @@
1
1
  /**
2
- * E3 graph observability + visualization — READ-ONLY over the entity/relation
3
- * graph (docs/plans/2026-06-02-graph-observability.md).
2
+ * Graph observability and visualization, READ-ONLY over the entity/relation
3
+ * graph.
4
4
  *
5
5
  * This module only READS the graph (`loadEntities` / `loadRelations`) and renders
6
6
  * view-models; it issues no INSERT/UPDATE/DELETE, so `scripts/check-graph-writes.mjs`
@@ -20,7 +20,7 @@ export function buildGraphModel(hippoRoot, tenantId, opts = {}) {
20
20
  const limit = opts.limit ?? DEFAULT_VIEW_LIMIT;
21
21
  // All reads run inside ONE read snapshot so a concurrent `graph extract` /
22
22
  // sleep-drain rebuild can't make the model mix old entity ids with new relation
23
- // ids (codex P2). Every load* call below is passed the snapshot connection `db`.
23
+ // ids. Every load* call below is passed the snapshot connection `db`.
24
24
  return withGraphReadSnapshot(hippoRoot, (db) => {
25
25
  let nodeEntities;
26
26
  let relations;
@@ -31,7 +31,7 @@ export function buildGraphModel(hippoRoot, tenantId, opts = {}) {
31
31
  // (2) A name can map to MANY entities (e.g. many notes for one customer), so
32
32
  // cap the focus matches. (3) Discover 1-hop neighbours and cap the UNION to
33
33
  // `limit` nodes. (4) Load ALL edges AMONG the union so neighbour-to-neighbour
34
- // edges that don't touch the focus are included too. (codex P2s.)
34
+ // edges that don't touch the focus are included too.
35
35
  const focus = loadEntitiesByName(hippoRoot, tenantId, opts.entity, { limit }, db);
36
36
  if (focus.length === 0)
37
37
  return { nodes: [], edges: [], truncated: false };
@@ -51,12 +51,12 @@ export function buildGraphModel(hippoRoot, tenantId, opts = {}) {
51
51
  nodeEntities = loadEntitiesByIds(hippoRoot, tenantId, unionIds, db);
52
52
  // Edges AMONG the union (BOTH endpoints in the set): includes
53
53
  // neighbour-to-neighbour edges, and the LIMIT can never drop a valid in-union
54
- // edge in favour of out-of-union rows (codex P2).
54
+ // edge in favour of out-of-union rows.
55
55
  relations = loadRelationsAmong(hippoRoot, tenantId, unionIds, { limit }, db);
56
56
  truncated =
57
57
  focus.length >= limit ||
58
- hop.length >= limit || // neighbour scan capped -> a 1-hop neighbour may be omitted (codex P2)
59
- neighboursCapped || // node cap filled before all neighbours were consumed (codex P2)
58
+ hop.length >= limit || // neighbour scan capped -> a 1-hop neighbour may be omitted
59
+ neighboursCapped || // node cap filled before all neighbours were consumed
60
60
  union.size > unionIds.length ||
61
61
  relations.length >= limit;
62
62
  }
@@ -5,8 +5,7 @@
5
5
  * default base and a few write-time multipliers (`deriveHalfLife`). Changing
6
6
  * the default therefore reaches only new memories; without this migration a
7
7
  * store would mix old-base and new-base memories, a state the decay
8
- * evaluation never tested (docs/evals/2026-09-24-decay-default-prereg.md,
9
- * Migration). The rule, declared there before any run:
8
+ * evaluation never tested. The rule, declared before any run:
10
9
  *
11
10
  * - only a memory still on the old base is rescaled: its half-life is
12
11
  * `deriveHalfLife(from, entry)` plus its recall bonus. A memory hippo shortened since
@@ -5,8 +5,7 @@
5
5
  * default base and a few write-time multipliers (`deriveHalfLife`). Changing
6
6
  * the default therefore reaches only new memories; without this migration a
7
7
  * store would mix old-base and new-base memories, a state the decay
8
- * evaluation never tested (docs/evals/2026-09-24-decay-default-prereg.md,
9
- * Migration). The rule, declared there before any run:
8
+ * evaluation never tested. The rule, declared before any run:
10
9
  *
11
10
  * - only a memory still on the old base is rescaled: its half-life is
12
11
  * `deriveHalfLife(from, entry)` plus its recall bonus. A memory hippo shortened since
@@ -151,7 +150,7 @@ function objectMemoryIds(db) {
151
150
  }
152
151
  return { all, retired };
153
152
  }
154
- /** Memories that lost a conflict, which resolveConflict halved untagged. A resolved conflict with no audit row (before v1.31.0, or found stale) names no winner, so both sides count. */
153
+ /** Memories that lost a conflict, which resolveConflict halved untagged. A resolved conflict with no audit row (resolved before resolves were audited, or found stale) names no winner, so both sides count. */
155
154
  function conflictLosers(db) {
156
155
  // SAFETY: SELECT of two fields every conflict_resolve audit row carries (ConflictResolveMeta in store.ts).
157
156
  const audited = db.prepare(`SELECT json_extract(metadata_json, '$.conflictId') AS conflictId, json_extract(metadata_json, '$.loserId') AS loserId FROM audit_log WHERE op = 'conflict_resolve'`).all();
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import { isJsonString } from './shared.js';
3
+ import { isJsonString } from '../json.js';
4
4
  function collectFiles(dir) {
5
5
  if (!fs.existsSync(dir))
6
6
  return [];
@@ -49,7 +49,7 @@ export declare function isCodexWrapperInstalled(): boolean;
49
49
  * opt-in record. Never performs a first install. Replacing another vendor's
50
50
  * binary must stay behind the explicit `hippo hook install codex` command;
51
51
  * doing it from postinstall or routine commands is a consent violation and
52
- * reads as binary hijacking to security scanners (issue #133).
52
+ * reads as binary hijacking to security scanners.
53
53
  */
54
54
  export declare function repairCodexWrapperIfInstalled(hippoCliPath?: string): EnsureCodexWrapperResult;
55
55
  //# sourceMappingURL=codex-wrapper.d.ts.map
@@ -2,7 +2,8 @@ import { envPath } from '../env.js';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
- import { isJsonString, HIPPO_CODEX_WRAPPER_MARKER, homeDir, codexHomeDir, ensureDir } from './shared.js';
5
+ import { HIPPO_CODEX_WRAPPER_MARKER, homeDir, codexHomeDir, ensureDir } from './shared.js';
6
+ import { isJsonString } from '../json.js';
6
7
  export function resolveCodexWrapperPaths() {
7
8
  const home = homeDir();
8
9
  const codexHome = codexHomeDir(home);
@@ -271,7 +272,7 @@ export function isCodexWrapperInstalled() {
271
272
  * opt-in record. Never performs a first install. Replacing another vendor's
272
273
  * binary must stay behind the explicit `hippo hook install codex` command;
273
274
  * doing it from postinstall or routine commands is a consent violation and
274
- * reads as binary hijacking to security scanners (issue #133).
275
+ * reads as binary hijacking to security scanners.
275
276
  */
276
277
  export function repairCodexWrapperIfInstalled(hippoCliPath = resolveHippoCliPath()) {
277
278
  if (readCodexWrapperMetadata() === null) {
@@ -23,8 +23,8 @@
23
23
  *
24
24
  * 2. Plugin install (OpenCode only). OpenCode does NOT share Claude Code's
25
25
  * JSON-hook schema — its config has `additionalProperties: false` and no
26
- * `hooks` key, so v1.10.x-v1.11.1's JSON-hook installer broke opencode
27
- * launch (issue #24). Hippo now installs a TypeScript plugin at
26
+ * `hooks` key, so a JSON-hook install breaks opencode launch. Hippo
27
+ * installs a TypeScript plugin at
28
28
  * `~/.config/opencode/plugins/hippo.ts` subscribing to opencode's
29
29
  * `session.idle` (→ `hippo session-end`) and `session.created` (→
30
30
  * `hippo last-sleep`) events. See OPENCODE_PLUGIN_SOURCE below for the
@@ -23,8 +23,8 @@
23
23
  *
24
24
  * 2. Plugin install (OpenCode only). OpenCode does NOT share Claude Code's
25
25
  * JSON-hook schema — its config has `additionalProperties: false` and no
26
- * `hooks` key, so v1.10.x-v1.11.1's JSON-hook installer broke opencode
27
- * launch (issue #24). Hippo now installs a TypeScript plugin at
26
+ * `hooks` key, so a JSON-hook install breaks opencode launch. Hippo
27
+ * installs a TypeScript plugin at
28
28
  * `~/.config/opencode/plugins/hippo.ts` subscribing to opencode's
29
29
  * `session.idle` (→ `hippo session-end`) and `session.created` (→
30
30
  * `hippo last-sleep`) events. See OPENCODE_PLUGIN_SOURCE below for the
@@ -34,7 +34,8 @@
34
34
  */
35
35
  import * as fs from 'fs';
36
36
  import * as path from 'path';
37
- import { isJsonString, isJsonObject, HIPPO_SLEEP_MARKER, HIPPO_LAST_SLEEP_MARKER, HIPPO_CAPTURE_MARKER, HIPPO_SESSION_END_MARKER, HIPPO_PINNED_INJECT_MARKER, HIPPO_PINNED_INJECT_COMMAND, HIPPO_PRE_COMPACT_MARKER, HIPPO_COMPACT_RESUME_MARKER, HIPPO_CAPTURE_ERROR_MARKER, HIPPO_POST_COMPACT_MARKER, homeDir, codexHomeDir, defaultPreCompactLogPath } from './shared.js';
37
+ import { isJsonObject, HIPPO_SLEEP_MARKER, HIPPO_LAST_SLEEP_MARKER, HIPPO_CAPTURE_MARKER, HIPPO_SESSION_END_MARKER, HIPPO_PINNED_INJECT_MARKER, HIPPO_PINNED_INJECT_COMMAND, HIPPO_PRE_COMPACT_MARKER, HIPPO_COMPACT_RESUME_MARKER, HIPPO_CAPTURE_ERROR_MARKER, HIPPO_POST_COMPACT_MARKER, homeDir, codexHomeDir, defaultPreCompactLogPath } from './shared.js';
38
+ import { isJsonString } from '../json.js';
38
39
  export function resolveJsonHookPaths(target) {
39
40
  const home = homeDir();
40
41
  const logsDir = path.join(home, '.hippo', 'logs');
@@ -91,7 +92,7 @@ function migratePinnedInjectRecentCommands(hookArray) {
91
92
  }
92
93
  /**
93
94
  * Returns true when `hooks.SessionEnd` still contains either of the legacy
94
- * v0.22.x split entries (bare `hippo sleep` / `hippo capture --last-session`)
95
+ * split entries (bare `hippo sleep` / `hippo capture --last-session`)
95
96
  * without the current consolidated `hippo session-end` entry.
96
97
  */
97
98
  function hasLegacySplitSessionEnd(hookArray) {
@@ -7,7 +7,7 @@ declare const HIPPO_OPENCODE_PLUGIN_MARKER = "HIPPO_OPENCODE_PLUGIN_V1";
7
7
  * session.idle → `hippo session-end` (Claude Code's SessionEnd equiv)
8
8
  * session.created → `hippo last-sleep` (Claude Code's SessionStart equiv)
9
9
  *
10
- * Design choices forced by plan-eng-critic Rev 0 review (2026-05-23):
10
+ * Design choices:
11
11
  *
12
12
  * 1. No `import type { Plugin } from "@opencode-ai/plugin"`. The package's npm
13
13
  * publication status was unverifiable from the build sandbox (npmjs.com
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import { isJsonString, isJsonObject, homeDir } from './shared.js';
3
+ import { isJsonObject, homeDir } from './shared.js';
4
+ import { isJsonString } from '../json.js';
4
5
  const HIPPO_OPENCODE_PLUGIN_MARKER = 'HIPPO_OPENCODE_PLUGIN_V1';
5
6
  /**
6
7
  * The opencode plugin file we install at ~/.config/opencode/plugins/hippo.ts.
@@ -10,7 +11,7 @@ const HIPPO_OPENCODE_PLUGIN_MARKER = 'HIPPO_OPENCODE_PLUGIN_V1';
10
11
  * session.idle → `hippo session-end` (Claude Code's SessionEnd equiv)
11
12
  * session.created → `hippo last-sleep` (Claude Code's SessionStart equiv)
12
13
  *
13
- * Design choices forced by plan-eng-critic Rev 0 review (2026-05-23):
14
+ * Design choices:
14
15
  *
15
16
  * 1. No `import type { Plugin } from "@opencode-ai/plugin"`. The package's npm
16
17
  * publication status was unverifiable from the build sandbox (npmjs.com
@@ -79,11 +80,11 @@ function resolveOpencodeConfigPath() {
79
80
  * hippo itself installs (session-end, last-sleep, sleep, capture, context),
80
81
  * not any third-party tool that happens to be named `hippo`.
81
82
  *
82
- * Critic-mandated structural check (Rev 0): substring matching against
83
+ * Structural check: substring matching against
83
84
  * arbitrary user content is unsafe — a user's
84
85
  * `echo "remember to hippo sleep your laptop"` is not a hippo-owned hook.
85
86
  *
86
- * Per-hook (not per-entry) granularity (Rev 1 review): an entry whose inner
87
+ * Per-hook (not per-entry) granularity: an entry whose inner
87
88
  * hooks array mixes hippo-installed commands with user-authored commands
88
89
  * must NOT lose the user-authored commands. The migration filters the inner
89
90
  * array per-hook, then drops the entry only when its inner array is empty.
@@ -1,10 +1,6 @@
1
- import type { JsonValue, JsonObject } from '../working-memory.js';
2
- /** JSON-value string check. `constructor` narrows a primitive via its boxed wrapper
3
- * instead of inspecting the `typeof` tag (anti-slop/no-runtime-typeof); equivalent to
4
- * `typeof value === 'string'` for every value JSON.parse can ever produce. */
5
- export declare function isJsonString(value: JsonValue | undefined): value is string;
6
- /** JSON-value plain-object check (excludes arrays and null), typeof-free for the same
7
- * reason as isJsonString above. */
1
+ import type { JsonObject } from '../working-memory.js';
2
+ import type { JsonValue } from '../json.js';
3
+ /** JSON-value plain-object check (excludes arrays and null); `constructor` avoids the `typeof` tag banned by anti-slop/no-runtime-typeof. */
8
4
  export declare function isJsonObject(value: JsonValue | undefined): value is JsonObject;
9
5
  export type JsonHookTarget = 'claude-code' | 'codex';
10
6
  export interface ToolDetection {
@@ -2,14 +2,7 @@ import { envHomeDir, processEnv } from '../env.js';
2
2
  import * as fs from 'fs';
3
3
  import * as os from 'os';
4
4
  import * as path from 'path';
5
- /** JSON-value string check. `constructor` narrows a primitive via its boxed wrapper
6
- * instead of inspecting the `typeof` tag (anti-slop/no-runtime-typeof); equivalent to
7
- * `typeof value === 'string'` for every value JSON.parse can ever produce. */
8
- export function isJsonString(value) {
9
- return value !== undefined && value !== null && value.constructor === String;
10
- }
11
- /** JSON-value plain-object check (excludes arrays and null), typeof-free for the same
12
- * reason as isJsonString above. */
5
+ /** JSON-value plain-object check (excludes arrays and null); `constructor` avoids the `typeof` tag banned by anti-slop/no-runtime-typeof. */
13
6
  export function isJsonObject(value) {
14
7
  return value !== undefined && value !== null && !Array.isArray(value) && value.constructor === Object;
15
8
  }
@@ -1,9 +1,8 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
- export type JsonValue = string | number | boolean | null | JsonValue[] | {
3
- [key: string]: JsonValue;
4
- };
2
+ import type { JsonValue } from './json.js';
5
3
  export declare function isJsonObjectRecord(value: JsonValue | undefined): value is Record<string, JsonValue>;
6
4
  export declare function isHeaderString(value: string | string[] | undefined): value is string;
5
+ export declare const MAX_ID_LEN = 256;
7
6
  export declare const JSON_HEADERS: {
8
7
  readonly 'content-type': "application/json";
9
8
  };
package/dist/http-util.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ApiError } from './api-errors.js';
2
+ // Leaf module shared by server.ts and the connector webhook receivers; it must not import either.
2
3
  export function isJsonObjectRecord(value) {
3
4
  return value !== undefined && value !== null && typeof value === 'object' && !Array.isArray(value);
4
5
  }
@@ -10,6 +11,8 @@ export function isHeaderString(value) {
10
11
  // 1 MB body cap. The CLI never sends payloads near this; anything bigger is
11
12
  // almost certainly a misconfigured client or a deliberate memory-blowup attempt.
12
13
  const MAX_BODY_BYTES = 1024 * 1024;
14
+ // Cap for id-shaped request fields (ids, tenant, session, scope, class): far above real values, small enough to bound logs and indexes.
15
+ export const MAX_ID_LEN = 256;
13
16
  export const JSON_HEADERS = { 'content-type': 'application/json' };
14
17
  export class HttpError extends Error {
15
18
  status;
@@ -3,6 +3,7 @@
3
3
  * Imports memories from ChatGPT, Claude, Cursor, generic files, and structured markdown.
4
4
  */
5
5
  import { MemoryEntry } from '../memory.js';
6
+ import type { JsonValue } from '../json.js';
6
7
  export interface ImportResult {
7
8
  total: number;
8
9
  imported: number;
@@ -44,7 +45,7 @@ export interface ImportOptions {
44
45
  * sharing a basename would collide and clobber each other). importVault throws
45
46
  * if it is missing or blank. Optional in this shared type only because the
46
47
  * other importers ignore it. Operator-supplied, so the loader query LIKE-escapes
47
- * it (see `escapeLike` below).
48
+ * it (`escapeLike` in src/escape.ts).
48
49
  */
49
50
  name?: string;
50
51
  /**
@@ -58,14 +59,6 @@ export interface ImportOptions {
58
59
  * create MemoryEntry objects, write them (unless dry-run), and return a result.
59
60
  */
60
61
  export declare function importEntries(chunks: string[], source: string, tags: string[], options: ImportOptions): ImportResult;
61
- /** The full value space `JSON.parse` can produce. Boundary-guard functions in
62
- * this file accept `JsonValue` (never `unknown`) so a value's origin as
63
- * unparsed external JSON stays visible in its type, then narrow it via the
64
- * `isJson*` predicates below. */
65
- export type JsonValue = string | number | boolean | null | JsonValue[] | {
66
- [key: string]: JsonValue;
67
- };
68
- export declare function isJsonString(x: JsonValue): x is string;
69
62
  export declare function isJsonPlainObject(x: JsonValue): x is {
70
63
  [key: string]: JsonValue;
71
64
  };
@@ -3,7 +3,8 @@
3
3
  * Imports memories from ChatGPT, Claude, Cursor, generic files, and structured markdown.
4
4
  */
5
5
  import { createMemory, Layer } from '../memory.js';
6
- import { writeEntry } from '../store/entry-writes.js';
6
+ import { writeEntryOn } from '../store/entry-writes.js';
7
+ import { openStore } from '../store/open.js';
7
8
  import { loadAllEntries } from '../store/entry-reads.js';
8
9
  import { duplicateKey, storedTextKeys } from '../same-text.js';
9
10
  import { getGlobalRoot, initGlobal } from '../shared.js';
@@ -34,12 +35,8 @@ export function importEntries(chunks, source, tags, options) {
34
35
  let rejected = 0;
35
36
  let redacted = 0;
36
37
  const entries = [];
37
- // AT1 P2 fix: a dry-run preview never called writeEntry, so it never
38
- // checked tombstones either — every non-duplicate chunk counted as
39
- // `imported` even when a real run would refuse it, making the preview's
40
- // `rejected` count silently 0. Probe (read-only) via the same guard
41
- // writeEntry uses internally, without ever writing.
42
- const dryRunDb = options.dryRun ? openHippoDb(targetRoot) : null;
38
+ // A dry run probes the rejection guard read-only; a real run writes every chunk on this one handle.
39
+ const db = options.dryRun ? openHippoDb(targetRoot) : openStore(targetRoot);
43
40
  try {
44
41
  for (const raw of chunks) {
45
42
  const { chunk, wasRedacted } = prepareImportChunk(raw, allTags);
@@ -55,7 +52,7 @@ export function importEntries(chunks, source, tags, options) {
55
52
  continue;
56
53
  }
57
54
  const entry = createImportEntry(chunk, source, allTags, options, baseHalfLifeDays);
58
- if (!writeOrProbeImport(targetRoot, entry, options, dryRunDb)) {
55
+ if (!writeOrProbeImport(db, targetRoot, entry, options)) {
59
56
  rejected++;
60
57
  continue;
61
58
  }
@@ -70,8 +67,7 @@ export function importEntries(chunks, source, tags, options) {
70
67
  return { total, imported, skipped, rejected, redacted, entries };
71
68
  }
72
69
  finally {
73
- if (dryRunDb)
74
- closeHippoDb(dryRunDb);
70
+ closeHippoDb(db);
75
71
  }
76
72
  }
77
73
  /** The secret-vetted chunk capped at 1000 chars, and whether vetting changed it. */
@@ -106,24 +102,13 @@ function createImportEntry(chunk, source, allTags, options, baseHalfLifeDays) {
106
102
  });
107
103
  }
108
104
  /** Writes the entry, or on a dry run only probes the guard; false when a rejected value refuses it. */
109
- function writeOrProbeImport(targetRoot, entry, options, dryRunDb) {
110
- if (options.dryRun) {
111
- if (dryRunDb) {
112
- try {
113
- checkRejectionGuard(dryRunDb, entry.tenantId ?? 'default', entry.id, entry.content);
114
- }
115
- catch (err) {
116
- if (err instanceof RejectedValueError)
117
- return false;
118
- throw err;
119
- }
120
- }
121
- return true;
122
- }
123
- // AT1 (plan §3 containment): a rejection refuses one CHUNK, not the
124
- // whole import batch. Caught per-item so siblings still land.
105
+ function writeOrProbeImport(db, targetRoot, entry, options) {
106
+ // A rejection refuses one chunk, not the whole import, so siblings still land.
125
107
  try {
126
- writeEntry(targetRoot, entry);
108
+ if (options.dryRun)
109
+ checkRejectionGuard(db, entry.tenantId ?? 'default', entry.id, entry.content);
110
+ else
111
+ writeEntryOn(db, targetRoot, entry);
127
112
  }
128
113
  catch (err) {
129
114
  if (err instanceof RejectedValueError)
@@ -132,9 +117,9 @@ function writeOrProbeImport(targetRoot, entry, options, dryRunDb) {
132
117
  }
133
118
  return true;
134
119
  }
135
- export function isJsonString(x) {
136
- return typeof x === 'string';
137
- }
120
+ // ---------------------------------------------------------------------------
121
+ // ChatGPT importer
122
+ // ---------------------------------------------------------------------------
138
123
  export function isJsonPlainObject(x) {
139
124
  return x !== null && !Array.isArray(x) && typeof x === 'object';
140
125
  }
@@ -1,7 +1,8 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import { importEntries, isJsonString, isJsonPlainObject } from './core.js';
3
+ import { importEntries, isJsonPlainObject } from './core.js';
4
4
  import { parseFrontmatter, collectMarkdownFiles } from './markdown-parse.js';
5
+ import { isJsonString } from '../json.js';
5
6
  /** Coerce one imported record (string, `{content|text: ...}` object, or
6
7
  * anything else) into the plain-text memory chunk it represents. */
7
8
  function extractMemoryText(candidate) {
@@ -9,27 +9,9 @@ import { RejectedValueError, checkRejectionGuard } from '../rejection.js';
9
9
  import { loadConfig } from '../config.js';
10
10
  import { vetSecrets } from '../secret-detect.js';
11
11
  import { log } from '../log.js';
12
- import { isJsonString } from './core.js';
13
12
  import { parseFrontmatter, frontmatterList, parseWikilinks, collectMarkdownFiles, realpathOrResolve } from './markdown-parse.js';
14
- // ---------------------------------------------------------------------------
15
- // K1 vault importer (markdown-vault FOLDER → kind='raw' memories)
16
- //
17
- // MIRRORS THE CONNECTOR PATTERN (src/connectors/slack|github), NOT the
18
- // single-file importers above. Each note becomes a single kind='raw' row with
19
- // provenance in TAGS (`source:vault` + `vault:<name>`), an artifactRef cursor
20
- // key, and a content-hash tag. Changes APPEND a new raw row after archiveRaw of
21
- // the old one; deletions archiveRaw the orphaned rows. We NEVER `supersede` a
22
- // raw row (supersede yields kind='distilled', losing raw-append-only protection
23
- // and escaping the kind='raw' deletion rescan) — all raw deletions route through
24
- // `archiveRaw` (the only trigger-legit raw delete).
25
- // ---------------------------------------------------------------------------
26
- /** Escape LIKE wildcards in operator-supplied text (mirror of
27
- * src/project-briefs.ts:477 / src/store/search-rows.ts, kept local since neither is
28
- * exported). Used so a `%`/`_`/`\` in the vault name cannot over-match the
29
- * loader prefix and archive another vault's rows. */
30
- function escapeLike(term) {
31
- return term.replace(/[%_\\]/g, '\\$&');
32
- }
13
+ import { isJsonString } from '../json.js';
14
+ import { escapeLike } from '../escape.js';
33
15
  /**
34
16
  * Import a markdown vault FOLDER as `kind='raw'` memories.
35
17
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * E2 incident first-class object (docs/plans/2026-05-29-e2-incident-object.md).
2
+ * Incident first-class object.
3
3
  *
4
4
  * An incident is a postmortem capsule: a recorded operational event with a
5
5
  * lifecycle and optional linked receipts (the memories that are its evidence).
package/dist/incidents.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * E2 incident first-class object (docs/plans/2026-05-29-e2-incident-object.md).
2
+ * Incident first-class object.
3
3
  *
4
4
  * An incident is a postmortem capsule: a recorded operational event with a
5
5
  * lifecycle and optional linked receipts (the memories that are its evidence).
@@ -1,4 +1,4 @@
1
- /** Prompt-injection detection for untrusted memory content (CD5). Same shape as secret-detect.ts; leaf module, no store/api/shared imports. */
1
+ /** Prompt-injection detection for untrusted memory content. Same shape as secret-detect.ts; leaf module, no store/api/shared imports. */
2
2
  export interface InstructionDetection {
3
3
  flagged: boolean;
4
4
  reason: string | null;
@@ -1,4 +1,4 @@
1
- /** Prompt-injection detection for untrusted memory content (CD5). Same shape as secret-detect.ts; leaf module, no store/api/shared imports. */
1
+ /** Prompt-injection detection for untrusted memory content. Same shape as secret-detect.ts; leaf module, no store/api/shared imports. */
2
2
  const INSTRUCTION_PATTERNS = [
3
3
  { name: 'override-instructions', re: /\b(?:ignore|disregard|forget)\b[^.\n]{0,40}\b(?:previous|prior|above)\b[^.\n]{0,40}\binstructions?\b/i },
4
4
  { name: 'role-reassignment', re: /\b(?:you are now|act as|pretend to be)\b[^.\n]{0,40}\b(?:an?\s+)?(?:ai|assistant|agent|system)\b/i },
@@ -47,7 +47,10 @@ export interface ChurnRefs {
47
47
  export declare function extractChurnRefs(content: string): ChurnRefs;
48
48
  export interface DetectChurnStaleOptions {
49
49
  tenantId: string;
50
+ /** The project id; rows under it or legacyName are checked. */
50
51
  projectName: string;
52
+ /** The folder name older rows carry, and the repo folder a memory's paths may start with. */
53
+ legacyName?: string;
51
54
  /** Evaluate matches but write nothing. */
52
55
  dryRun?: boolean;
53
56
  }