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.
- package/README.md +37 -53
- package/dist/agent-memories/apply.d.ts +3 -1
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +4 -1
- package/dist/agent-memories/claude-code.js +53 -9
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +46 -24
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/ambient-store.d.ts +4 -4
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +7 -10
- package/dist/api/assemble.js +62 -66
- package/dist/api/audit.d.ts +2 -2
- package/dist/api/audit.js +2 -2
- package/dist/api/auth.d.ts +6 -6
- package/dist/api/auth.js +5 -7
- package/dist/api/context-select.d.ts +50 -0
- package/dist/api/context-select.js +342 -0
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +5 -4
- package/dist/api/context.js +208 -535
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +56 -41
- package/dist/api/outcome.d.ts +8 -13
- package/dist/api/outcome.js +13 -17
- package/dist/api/promote.d.ts +5 -8
- package/dist/api/promote.js +55 -66
- package/dist/api/quarantine.js +3 -2
- package/dist/api/recall-types.d.ts +43 -55
- package/dist/api/recall.d.ts +3 -3
- package/dist/api/recall.js +310 -456
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +206 -220
- package/dist/api/tokens.d.ts +2 -2
- package/dist/api/tokens.js +2 -2
- package/dist/api/types.d.ts +6 -10
- package/dist/api/types.js +2 -4
- package/dist/audit-prune.d.ts +4 -6
- package/dist/audit-prune.js +3 -5
- package/dist/audit.js +11 -33
- package/dist/auth.d.ts +8 -9
- package/dist/auth.js +5 -7
- package/dist/autolearn.d.ts +1 -1
- package/dist/autolearn.js +1 -1
- package/dist/availability.js +3 -5
- package/dist/capture/command.d.ts +5 -4
- package/dist/capture/command.js +8 -22
- package/dist/capture/compact.d.ts +2 -2
- package/dist/capture/compact.js +11 -15
- package/dist/capture/extract.js +37 -115
- package/dist/capture-error.d.ts +1 -1
- package/dist/capture-error.js +1 -1
- package/dist/churn-git.d.ts +1 -1
- package/dist/churn-git.js +1 -1
- package/dist/cli/audit.js +3 -4
- package/dist/cli/auth.js +3 -6
- package/dist/cli/briefs.js +324 -306
- package/dist/cli/context.js +44 -34
- package/dist/cli/continuity.js +283 -271
- package/dist/cli/curate.d.ts +1 -1
- package/dist/cli/curate.js +43 -58
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +334 -345
- package/dist/cli/explain.js +68 -61
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/maintenance.js +62 -51
- package/dist/cli/playbooks.js +391 -379
- package/dist/cli/projects.js +11 -6
- package/dist/cli/recall.js +30 -44
- package/dist/cli/remember.js +118 -87
- package/dist/cli/session-hooks.js +106 -115
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +267 -250
- package/dist/cli/shared.js +3 -3
- package/dist/cli/slack.js +1 -1
- package/dist/cli/sleep.js +27 -1
- package/dist/cli/status.d.ts +4 -4
- package/dist/cli/status.js +80 -76
- package/dist/cli/transfer.js +88 -107
- package/dist/cli/usage.js +9 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +4 -9
- package/dist/compaction-record.d.ts +2 -2
- package/dist/compaction-record.js +89 -68
- package/dist/compare.d.ts +11 -16
- package/dist/compare.js +11 -16
- package/dist/config.d.ts +19 -18
- package/dist/config.js +90 -67
- package/dist/connectors/github/backfill.d.ts +2 -2
- package/dist/connectors/github/backfill.js +8 -15
- package/dist/connectors/github/cli-impl.js +3 -8
- package/dist/connectors/github/deletion.d.ts +5 -12
- package/dist/connectors/github/deletion.js +5 -12
- package/dist/connectors/github/dlq.d.ts +6 -9
- package/dist/connectors/github/dlq.js +2 -3
- package/dist/connectors/github/ingest.d.ts +5 -7
- package/dist/connectors/github/ingest.js +8 -12
- package/dist/connectors/github/octokit-client.d.ts +3 -5
- package/dist/connectors/github/octokit-client.js +5 -6
- package/dist/connectors/github/signature.d.ts +9 -39
- package/dist/connectors/github/signature.js +9 -39
- package/dist/connectors/github/tenant-routing.d.ts +1 -1
- package/dist/connectors/github/tenant-routing.js +1 -1
- package/dist/connectors/github/transform.js +2 -2
- package/dist/connectors/github/types.d.ts +2 -10
- package/dist/connectors/github/types.js +1 -3
- package/dist/connectors/slack/deletion.d.ts +3 -8
- package/dist/connectors/slack/deletion.js +3 -8
- package/dist/connectors/slack/dlq.d.ts +1 -1
- package/dist/connectors/slack/ingest.d.ts +1 -1
- package/dist/connectors/slack/ingest.js +7 -16
- package/dist/connectors/slack/signature.d.ts +1 -1
- package/dist/connectors/slack/tenant-routing.d.ts +3 -5
- package/dist/connectors/slack/tenant-routing.js +3 -5
- package/dist/connectors/slack/transform.d.ts +5 -6
- package/dist/connectors/slack/transform.js +5 -6
- package/dist/connectors/slack/types.d.ts +2 -6
- package/dist/connectors/slack/types.js +1 -3
- package/dist/connectors/slack/web-client.js +10 -3
- package/dist/connectors/slack/workspaces.d.ts +3 -5
- package/dist/connectors/slack/workspaces.js +3 -5
- package/dist/consolidate/conflicts.js +3 -14
- package/dist/consolidate/decay.js +9 -29
- package/dist/consolidate/llm-passes.js +4 -5
- package/dist/consolidate/merge.js +8 -23
- package/dist/consolidate/run.d.ts +1 -8
- package/dist/consolidate/run.js +3 -25
- package/dist/consolidate/sleep.js +5 -17
- package/dist/consolidate/traces.js +9 -21
- package/dist/customer-notes.d.ts +5 -7
- package/dist/customer-notes.js +82 -76
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +189 -203
- package/dist/db/continuity.js +2 -2
- package/dist/db/migrations/v14.js +1 -1
- package/dist/db/migrations/v15.js +1 -2
- package/dist/db/migrations/v16.js +3 -4
- package/dist/db/migrations/v17.js +2 -3
- package/dist/db/migrations/v19.js +1 -1
- package/dist/db/migrations/v20.js +1 -1
- package/dist/db/migrations/v21.js +2 -6
- package/dist/db/migrations/v22.js +2 -4
- package/dist/db/migrations/v23.js +1 -1
- package/dist/db/migrations/v24.js +4 -6
- package/dist/db/migrations/v25.js +2 -3
- package/dist/db/migrations/v26.js +3 -3
- package/dist/db/migrations/v27.js +2 -10
- package/dist/db/migrations/v28.js +5 -8
- package/dist/db/migrations/v29.js +3 -4
- package/dist/db/migrations/v30.js +2 -2
- package/dist/db/migrations/v31.js +1 -1
- package/dist/db/migrations/v32.js +1 -1
- package/dist/db/migrations/v33.js +3 -3
- package/dist/db/migrations/v34.js +1 -1
- package/dist/db/migrations/v35.js +3 -4
- package/dist/db/migrations/v36.js +3 -4
- package/dist/db/migrations/v37.js +5 -5
- package/dist/db/migrations/v38.js +7 -8
- package/dist/db/migrations/v39.js +1 -1
- package/dist/db/migrations/v40.js +4 -16
- package/dist/db/migrations/v41.js +3 -4
- package/dist/db/migrations/v42.js +3 -4
- package/dist/db/migrations/v45.js +1 -1
- package/dist/db/migrations/v46.js +1 -1
- package/dist/db/migrations/v47.js +1 -1
- package/dist/db/migrations/v48.js +1 -1
- package/dist/decisions.d.ts +2 -2
- package/dist/decisions.js +97 -80
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +129 -110
- package/dist/dormant.js +1 -4
- package/dist/embedding-provider.d.ts +4 -8
- package/dist/embedding-provider.js +4 -8
- package/dist/embeddings.js +55 -47
- package/dist/env.d.ts +1 -1
- package/dist/env.js +12 -12
- package/dist/escape.d.ts +5 -0
- package/dist/escape.js +10 -0
- package/dist/eval-stats.d.ts +1 -2
- package/dist/eval-stats.js +1 -2
- package/dist/eval-suite.js +27 -21
- package/dist/extract.js +4 -9
- package/dist/failure-log.d.ts +3 -3
- package/dist/failure-log.js +1 -1
- package/dist/forward-claim-detector.d.ts +2 -4
- package/dist/forward-claim-detector.js +6 -11
- package/dist/goals.d.ts +3 -3
- package/dist/goals.js +103 -91
- package/dist/graph/read.d.ts +2 -2
- package/dist/graph/read.js +5 -6
- package/dist/graph/types.d.ts +8 -8
- package/dist/graph/write.d.ts +7 -14
- package/dist/graph/write.js +16 -23
- package/dist/graph-extract.d.ts +7 -8
- package/dist/graph-extract.js +62 -72
- package/dist/graph-recall.d.ts +2 -2
- package/dist/graph-recall.js +55 -49
- package/dist/graph-stream.d.ts +5 -6
- package/dist/graph-stream.js +66 -57
- package/dist/graph-view.d.ts +2 -2
- package/dist/graph-view.js +7 -7
- package/dist/half-life-migration.d.ts +1 -2
- package/dist/half-life-migration.js +2 -3
- package/dist/hooks/codex-session.js +1 -1
- package/dist/hooks/codex-wrapper.d.ts +1 -1
- package/dist/hooks/codex-wrapper.js +3 -2
- package/dist/hooks/json-hooks.d.ts +2 -2
- package/dist/hooks/json-hooks.js +5 -4
- package/dist/hooks/opencode.d.ts +1 -1
- package/dist/hooks/opencode.js +5 -4
- package/dist/hooks/shared.d.ts +3 -7
- package/dist/hooks/shared.js +1 -8
- package/dist/http-util.d.ts +2 -3
- package/dist/http-util.js +3 -0
- package/dist/importers/core.d.ts +2 -9
- package/dist/importers/core.js +15 -30
- package/dist/importers/sources.js +2 -1
- package/dist/importers/vault.js +2 -20
- package/dist/incidents.d.ts +1 -1
- package/dist/incidents.js +46 -39
- package/dist/instruction-detect.d.ts +1 -1
- package/dist/instruction-detect.js +1 -1
- package/dist/invalidation.d.ts +3 -0
- package/dist/invalidation.js +160 -114
- package/dist/json.d.ts +5 -0
- package/dist/json.js +4 -0
- package/dist/judgment.js +1 -2
- package/dist/local-embedding.js +1 -1
- package/dist/mcp/admin-tools.js +7 -17
- package/dist/mcp/format.js +1 -1
- package/dist/mcp/framing.js +3 -6
- package/dist/mcp/protocol.d.ts +2 -5
- package/dist/mcp/protocol.js +1 -3
- package/dist/mcp/recall-tools.js +12 -15
- package/dist/mcp/request.js +4 -3
- package/dist/mcp/session-state.js +2 -3
- package/dist/mcp/stdio.js +2 -1
- package/dist/mcp/tools.js +9 -6
- package/dist/memory-value-weights.d.ts +5 -8
- package/dist/memory-value-weights.js +5 -8
- package/dist/memory-value.d.ts +13 -13
- package/dist/memory-value.js +26 -37
- package/dist/memory.d.ts +20 -22
- package/dist/memory.js +24 -48
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +3 -2
- package/dist/owner-validation.d.ts +4 -5
- package/dist/owner-validation.js +4 -5
- package/dist/physics.d.ts +4 -4
- package/dist/physics.js +7 -9
- package/dist/policies.d.ts +9 -10
- package/dist/policies.js +96 -81
- package/dist/postinstall.js +3 -6
- package/dist/predictions/planning-fallacy.d.ts +9 -14
- package/dist/predictions/planning-fallacy.js +10 -16
- package/dist/predictions/store.d.ts +15 -23
- package/dist/predictions/store.js +36 -33
- package/dist/processes.d.ts +2 -7
- package/dist/processes.js +88 -72
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +141 -118
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +28 -5
- package/dist/project-merge.js +213 -46
- package/dist/project-remote.d.ts +12 -0
- package/dist/project-remote.js +138 -0
- package/dist/prompt-recall.js +1 -2
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.js +1 -1
- package/dist/raw-archive.d.ts +9 -0
- package/dist/raw-archive.js +70 -53
- package/dist/recall-history.d.ts +19 -20
- package/dist/recall-history.js +24 -42
- package/dist/recall-pipeline.js +4 -28
- package/dist/recall-scope.d.ts +7 -8
- package/dist/recall-scope.js +7 -8
- package/dist/recall-trace.d.ts +5 -9
- package/dist/recall-trace.js +6 -10
- package/dist/refine-llm.d.ts +1 -1
- package/dist/refine-llm.js +2 -2
- package/dist/reject-flow.d.ts +3 -4
- package/dist/reject-flow.js +122 -117
- package/dist/rejection.d.ts +5 -6
- package/dist/rejection.js +7 -15
- package/dist/rerankers/clef.d.ts +1 -1
- package/dist/rerankers/jev.d.ts +1 -2
- package/dist/rerankers/jev.js +4 -5
- package/dist/rerankers/llm.d.ts +1 -2
- package/dist/rerankers/llm.js +1 -2
- package/dist/rerankers/types.d.ts +1 -2
- package/dist/rrf.d.ts +2 -2
- package/dist/rrf.js +2 -2
- package/dist/search/bm25-search.d.ts +1 -1
- package/dist/search/bm25-search.js +2 -1
- package/dist/search/boosts.js +2 -1
- package/dist/search/hybrid.d.ts +1 -1
- package/dist/search/hybrid.js +2 -1
- package/dist/search/physics-search.d.ts +1 -1
- package/dist/search/physics-search.js +2 -1
- package/dist/search/types.d.ts +2 -0
- package/dist/search/types.js +3 -1
- package/dist/secret-detect.d.ts +4 -5
- package/dist/secret-detect.js +6 -10
- package/dist/server/auth.js +5 -5
- package/dist/server/client-ip.js +1 -1
- package/dist/server/cursor.js +2 -1
- package/dist/server/mcp-http.js +4 -4
- package/dist/server/request.d.ts +3 -6
- package/dist/server/request.js +6 -7
- package/dist/server/routes/admin.js +5 -4
- package/dist/server/routes/customer-notes.js +6 -5
- package/dist/server/routes/decisions.js +4 -3
- package/dist/server/routes/incidents.js +7 -5
- package/dist/server/routes/memories.js +7 -7
- package/dist/server/routes/policies.js +3 -2
- package/dist/server/routes/predictions.js +12 -15
- package/dist/server/routes/processes.js +3 -2
- package/dist/server/routes/project-briefs.js +8 -7
- package/dist/server/routes/recall.js +95 -93
- package/dist/server/routes/skills.js +6 -5
- package/dist/server/types.d.ts +1 -1
- package/dist/server/validation.d.ts +1 -2
- package/dist/server/validation.js +7 -14
- package/dist/server-detect.js +72 -58
- package/dist/server.d.ts +2 -2
- package/dist/server.js +131 -117
- package/dist/shared.d.ts +26 -17
- package/dist/shared.js +102 -104
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +88 -72
- package/dist/store/audit-event.d.ts +2 -2
- package/dist/store/audit-event.js +1 -1
- package/dist/store/candidates.d.ts +2 -2
- package/dist/store/candidates.js +4 -3
- package/dist/store/conflicts.js +30 -22
- package/dist/store/delete-and-batch.d.ts +11 -14
- package/dist/store/delete-and-batch.js +40 -91
- package/dist/store/entry-reads.d.ts +17 -25
- package/dist/store/entry-reads.js +59 -40
- package/dist/store/entry-row.d.ts +6 -24
- package/dist/store/entry-row.js +6 -24
- package/dist/store/entry-writes.d.ts +6 -7
- package/dist/store/entry-writes.js +13 -11
- package/dist/store/handoffs.d.ts +1 -1
- package/dist/store/handoffs.js +7 -10
- package/dist/store/index-and-stats.d.ts +2 -6
- package/dist/store/index-and-stats.js +4 -10
- package/dist/store/mirrors.d.ts +6 -19
- package/dist/store/mirrors.js +14 -39
- package/dist/store/open.js +9 -31
- package/dist/store/rows.d.ts +5 -11
- package/dist/store/rows.js +6 -11
- package/dist/store/search-rows.d.ts +17 -34
- package/dist/store/search-rows.js +34 -56
- package/dist/store/sessions.d.ts +4 -5
- package/dist/store/sessions.js +5 -6
- package/dist/store/summaries.d.ts +13 -17
- package/dist/store/summaries.js +26 -70
- package/dist/support-bundle.js +4 -8
- package/dist/tenant.d.ts +1 -5
- package/dist/token-ledger.d.ts +1 -1
- package/dist/token-ledger.js +3 -5
- package/dist/trace.js +1 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ npm install -g hippo-memory && hippo init
|
|
|
22
22
|
|
|
23
23
|
Setting up every git repo under a folder in one go is a second step. The [Quick start](#quick-start) says what it changes, then gives the command.
|
|
24
24
|
|
|
25
|
-
Package installation alone does not enable automatic preservation on every agent. Complete the documented setup and required host trust; capture and compaction coverage depend on the integration.
|
|
25
|
+
Package installation alone does not enable automatic preservation on every agent. Complete the documented setup and required host trust; capture and compaction coverage depend on the integration.
|
|
26
26
|
|
|
27
27
|
Having an AI agent install it? Point it at [llms-install.md](llms-install.md): it installs, wires hippo into the agents it finds, and verifies with `hippo doctor`.
|
|
28
28
|
|
|
@@ -544,9 +544,18 @@ database to `.hippo/backups/` first and logs every id it touched in the audit lo
|
|
|
544
544
|
```bash
|
|
545
545
|
hippo projects --global # names, counts, live worktrees of this repo
|
|
546
546
|
hippo projects merge hippo-wt-fix hippo --global # fold an old worktree name into its repo
|
|
547
|
-
hippo projects repair --global # re-tag
|
|
547
|
+
hippo projects repair --global # set aside note copies, fold old worktree names, re-tag merges
|
|
548
548
|
```
|
|
549
549
|
|
|
550
|
+
A project's name is the `id` in a committed `.hippo-project.json`, else its `origin` remote
|
|
551
|
+
(`github.com/acme/api`), else its folder name. So two repos both called `api` no longer share
|
|
552
|
+
memories in the global store. Rows saved under the old folder name stay visible to the
|
|
553
|
+
project, and `hippo projects repair` folds that name into the id unless two projects claim it.
|
|
554
|
+
`hippo sleep` runs that repair once per store after an upgrade, with a backup; in the global store
|
|
555
|
+
it leaves the name folds to you, since only `hippo projects repair --global` lists them for review.
|
|
556
|
+
Set `{"projectIdentity":{"remote":false}}` in the global `config.json` to keep folder names.
|
|
557
|
+
A long-running MCP or HTTP server reads a new project file or remote after a restart.
|
|
558
|
+
|
|
550
559
|
**See what memory costs in tokens.** Every block of memory text hippo hands an agent (the
|
|
551
560
|
per-prompt hook, the block `hippo compact-resume` restores after compaction, `hippo context`,
|
|
552
561
|
`hippo recall`, the MCP tools, the HTTP API) is recorded in a token ledger: counts, surface
|
|
@@ -936,53 +945,36 @@ For how these mechanisms connect to LLM training, continual learning, and open r
|
|
|
936
945
|
|
|
937
946
|
## Comparison
|
|
938
947
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
### What each tool is
|
|
942
|
-
|
|
943
|
-
The rows from where the data lives to the graph were checked against each tool's own pages on 2026-09-28.
|
|
948
|
+
The AI-memory category matured fast in 2026. Hippo's specific take (bio-decay, strengthen-on-use, outcome-weighted half-lives) is one stance among several. The table below is a feature snapshot, not a verdict: graph-first systems ([gbrain](https://hermesatlas.com/projects/garrytan/gbrain), [Zep](https://www.getzep.com/), [Cognee](https://www.cognee.ai/)), agent-managed systems ([Letta](https://github.com/letta-ai/letta-code)), and version-control / skill-distillation takes ([Memoria](https://github.com/matrixorigin/Memoria), [EverMind](https://evermind.ai/)) all solve adjacent problems with different mechanics.
|
|
944
949
|
|
|
945
950
|
| Feature | Hippo | [MemPalace](https://github.com/milla-jovovich/mempalace) | [Mem0](https://github.com/mem0ai/mem0) | [Basic Memory](https://github.com/basicmachines-co/basic-memory) | [gbrain](https://hermesatlas.com/projects/garrytan/gbrain) | [Zep](https://www.getzep.com/) | [Letta](https://github.com/letta-ai/letta-code) | [Cognee](https://www.cognee.ai/) | [Memoria](https://github.com/matrixorigin/Memoria) | [EverMind](https://evermind.ai/) |
|
|
946
951
|
|---------|-------|-----------|------|-------------|--------|-----|-------|--------|---------|----------|
|
|
947
|
-
| Where your data lives | Your machine or your server (SQLite) | Your machine (ChromaDB by default) | Where you run it, or Mem0's cloud | Your machine (Markdown files); cloud optional | Your machine (PGLite) or your Postgres | Zep's cloud (your own cloud on Enterprise) | Your machine; cloud backup with /login | Your machine by default; Cognee Cloud optional | Memoria Cloud, or self-hosted (Docker or embedded) | Your machine by default; EverOS Cloud optional |
|
|
948
|
-
| Managed multi-user service | No (self-hosted, with tenants and API keys; hosted SaaS is planned for the commercial edition) | ? | Yes (hosted platform) | Yes (Teams) | ? (self-hosted server with OAuth) | Yes (Zep Cloud) | ? (cloud backup with /login) | Yes (Cognee Cloud) | ? (Memoria Cloud) | Yes (EverOS Cloud) |
|
|
949
|
-
| Needs an account or model key | No | No (core path) | Yes (model key; account for the platform) | No (account for the cloud) | No (keyless mode) | Yes (Zep account; Graphiti needs a model key) | Yes (your own model keys) | No (local) | ? (account for Memoria Cloud) | Yes (an LLM key, OpenRouter) |
|
|
950
|
-
| License | MIT | MIT | Apache-2.0 | AGPL-3.0 | MIT | Proprietary cloud (Graphiti: Apache-2.0) | Apache-2.0 | Apache-2.0 | Apache-2.0 | Apache-2.0 (EverOS) + cloud |
|
|
951
|
-
| Runtime | Node.js 22.16+, no runtime deps | Python 3.9+ (ChromaDB by default) | Python or Node.js (server: Postgres + pgvector) | Python (SQLite by default) | Bun (PGLite or Postgres + pgvector) | Managed service (Graphiti: Python + a graph database) | Node.js (npm) | Python (graph and vector stores, or Postgres) | A CLI binary + a MatrixOne database | Python (SQLite + LanceDB) |
|
|
952
|
-
| SDKs and APIs | CLI, HTTP API, Python SDK over HTTP | Python library, CLI | Python and Node.js libraries, a self-hosted server | CLI, cloud app | CLI, HTTP API | Python, TypeScript and Go SDKs | TypeScript SDK, CLI | Python and TypeScript SDKs, REST API, CLI | Python client, REST API, CLI | Python library, HTTP API, CLI |
|
|
953
|
-
| Native Python library | No (the Python SDK calls hippo serve over HTTP) | Yes (mempalace) | Yes (mem0ai) | Yes (basic-memory) | No (installs with Bun) | Yes (Graphiti: graphiti-core) | ? (Letta Code ships on npm) | Yes (cognee) | ? (memoria-client) | Yes (everos) |
|
|
954
|
-
| Graph or entity relations | Partial (entities from decisions and policies; recall --hops, off by default) | Yes (temporal entity graph) | Partial (entity linking; graph memory on Pro) | Yes (wikilinks and observations) | Yes (typed knowledge graph) | Yes (temporal knowledge graph) | No | Yes (knowledge graph) | No (typed claims) | ? (graph view in progress) |
|
|
955
|
-
| Hybrid search (BM25 + embeddings) | Yes (BM25 by default; embeddings are an optional install) | Embeddings + spatial | Yes (semantic + BM25 + entity) | No | Yes (vec + rerank + graph) | Yes (graph + vec) | ? | Yes (GraphRAG) | Yes (vector + full-text) | Yes (mRAG, multi-modal) |
|
|
956
|
-
| MCP server | Yes | Yes | Yes (hosted, needs an account) | Yes | Yes (stdio + HTTP/OAuth) | Yes (hosted, needs an account) | Yes (hosted, needs an API key) | Yes (first-party Claude/LangGraph) | Yes | ? |
|
|
957
|
-
| Multi-agent shared memory | Yes | No | No | No | Yes (brain repo, team mounts) | Yes | Yes (shared memory blocks) | Yes | Yes (branch/merge across sessions) | Yes (multi-agent coordination) |
|
|
958
|
-
| Auto-hook install | Yes (Claude Code hooks, OpenCode plugin) | No | No | No | No | No | No | No | No | No |
|
|
959
|
-
| Cross-tool import (ChatGPT/Claude/Cursor) | Yes | No | No | No | Partial (data sources) | ? | No | Partial (28 data sources) | No (Git ops) | Partial (mRAG: PDFs/images/URLs) |
|
|
960
|
-
| Git-friendly | Yes | No | No | Yes | Yes | No | Yes (memory tracked in git) | No | Yes (Git is the model) | ? |
|
|
961
|
-
| Framework agnostic | Yes | Yes | Partial | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
|
962
|
-
| LongMemEval (published) | 85.6% R@5 from hippo recall, 96.8% with the budget lifted (4,000-token default budget; the benchmark scripts' best of five settings: 98.0% local / 99.8% voyage any-evidence, 88.5% local all-evidence; s_cleaned, per-haystack)\* | 96.6% raw / 100% reranked R@5 | 94.4 (hosted platform)\*\* | N/A | 95.53% all-evidence R@5 reranked, 93.19% without (s_cleaned\*) | 90.2% accuracy\*\* (LoCoMo 94.7%) | N/A | N/A | 88.78% overall accuracy w/ reader\*\* | 83.00% overall\*\* (LoCoMo 93.05%, HaluMem 93.04%) |
|
|
963
|
-
|
|
964
|
-
\* Hippo's figures are on `longmemeval_s_cleaned` with a per-question haystack. On a default install, `hippo recall` puts an answer session in its top 5 for 85.6% of the 500 questions inside its default 4,000-token budget. With the budget lifted it scores 96.8%, but then it returns every candidate, so that figure measures the ranking ([result](docs/evals/2026-09-28-recall-cli-longmemeval-result.md)). The 98.0% and 99.8% are the benchmark scripts' retrieval, each the best of five settings, not `hippo recall`. Any-evidence R@5 counts a hit when any answer session is in the top 5, over all 500 questions: 98.0% with the free local MiniLM embedder (an optional install) and 99.8% with voyage-3-large (measured 2026-06-09, not re-run). All-evidence R@5 counts a hit only when every answer session is in the top 5, over the 470 questions that have an answer: 86.8 to 88.5% with MiniLM. gbrain first published 97.6%, an any-evidence score over all 500; its [report](https://github.com/garrytan/gbrain-evals/blob/main/docs/benchmarks/2026-05-07-longmemeval-s.md) now leads with all-evidence, 95.53% (449 of 470) with the paid Voyage rerank-2.5 reranker and 93.19% without it. On all-evidence recall gbrain is ahead. The June 2026 build scored 98.6 any-evidence; [`docs/evals/2026-09-23-longmemeval-reproduction.md`](docs/evals/2026-09-23-longmemeval-reproduction.md) has both runs. An older hippo number, 86.8% R@5 on `longmemeval_oracle` under pooled (non-per-haystack) retrieval, is not comparable to per-haystack figures.
|
|
965
|
-
|
|
966
|
-
\*\* Different metric: these are end-to-end answer scores, not retrieval R@5. Mem0's 94.4 comes from its hosted platform, which its README says includes optimizations the open-source SDK lacks. Zep's 90.2% and 94.7% are accuracy figures from its homepage. Memoria's 88.78% and EverMind's 83% are overall accuracy with a reader LLM. Higher denominator + LLM helps. Not directly comparable to retrieval-only R@5 numbers above. The Mem0, Zep and Letta columns were last checked against each vendor's own pages on 2026-09-28.
|
|
967
|
-
|
|
968
|
-
### Design bets
|
|
969
|
-
|
|
970
|
-
A Yes means the tool made that choice, not that the choice was shown to help. What hippo has measured about its own bets is under [Benchmarks](#benchmarks); decay, for one, tied with decay switched off. Spatial organization and lossless compression are MemPalace's bets.
|
|
971
|
-
|
|
972
|
-
| Design bet | Hippo | [MemPalace](https://github.com/milla-jovovich/mempalace) | [Mem0](https://github.com/mem0ai/mem0) | [Basic Memory](https://github.com/basicmachines-co/basic-memory) | [gbrain](https://hermesatlas.com/projects/garrytan/gbrain) | [Zep](https://www.getzep.com/) | [Letta](https://github.com/letta-ai/letta-code) | [Cognee](https://www.cognee.ai/) | [Memoria](https://github.com/matrixorigin/Memoria) | [EverMind](https://evermind.ai/) |
|
|
973
|
-
|---------|-------|-----------|------|-------------|--------|-----|-------|--------|---------|----------|
|
|
974
952
|
| Decay by default | Yes | No | No | No | No | No | No | No | No | No |
|
|
975
953
|
| Retrieval strengthening | Yes | No | No | No | No | No | No | Partial (recall tuning) | No | Partial (Skill Memory distills patterns) |
|
|
976
954
|
| Reward-proportional decay | Yes | No | No | No | No | No | No | No | No | No |
|
|
977
|
-
|
|
|
955
|
+
| Hybrid search (BM25 + embeddings) | Yes | Embeddings + spatial | Yes (semantic + BM25 + entity) | No | Yes (vec + rerank + graph) | Yes (graph + vec) | ? | Yes (GraphRAG) | Yes (vector + full-text) | Yes (mRAG, multi-modal) |
|
|
956
|
+
| Schema acceleration / knowledge graph | Yes (schema) | No | Partial (entity linking; graph memory on Pro) | No | Yes (typed KG, self-wiring) | Yes (temporal KG) | No | Yes (auto-ontologies) | No (typed claims) | Yes (hierarchical: user/group/agent) |
|
|
978
957
|
| Conflict detection + resolution | Yes | No | Partial (hosted platform marks superseded facts) | No | Yes (eval-surfaced) | Yes (auto-invalidate stale facts) | No | No | Yes (auto-detect + quarantine) | Partial (temporal tracking) |
|
|
958
|
+
| Multi-agent shared memory | Yes | No | No | No | Yes (brain repo, team mounts) | Yes | Yes (shared memory blocks) | Yes | Yes (branch/merge across sessions) | Yes (multi-agent coordination) |
|
|
979
959
|
| Transfer scoring | Yes | No | No | No | No | No | No | No | No | No |
|
|
960
|
+
| Outcome tracking | Yes | No | No | No | No | No | No | No | No | Partial (Cases: agent trajectories) |
|
|
980
961
|
| Confidence tiers | Yes | No | No | No | No (typed facts) | No | No | No | No | No |
|
|
981
|
-
| Schema acceleration | Yes | No | No | No | No | No | No | No | No | No |
|
|
982
962
|
| Spatial organization | No | Yes (wings/halls/rooms) | No | No | No | No | No | No | No | No |
|
|
983
963
|
| Lossless compression | No | Yes (AAAK, 30x) | No | No | No | No | No | No | No | No |
|
|
964
|
+
| Cross-tool import (ChatGPT/Claude/Cursor) | Yes | No | No | No | Partial (data sources) | ? | No | Partial (28 data sources) | No (Git ops) | Partial (mRAG: PDFs/images/URLs) |
|
|
965
|
+
| Auto-hook install | Yes | No | No | No | No | No | No | No | No | No |
|
|
966
|
+
| MCP server | Yes | Yes | Yes (hosted, needs an account) | Yes | Yes (stdio + HTTP/OAuth) | Yes (hosted, needs an account) | Yes (hosted, needs an API key) | Yes (first-party Claude/LangGraph) | Yes | ? |
|
|
967
|
+
| Zero runtime deps | Yes | No (ChromaDB) | No | No | No (PGLite or PG+pgvector) | No (managed service) | No (npm deps) | No (Python deps) | Yes (single Rust binary) | No (managed + OSS) |
|
|
968
|
+
| LongMemEval (best published) | 98.0% local / 99.8% voyage any-evidence R@5; 88.5% local all-evidence R@5 (s_cleaned, per-haystack)\* | 96.6% raw / 100% reranked R@5 | 94.4 (hosted platform)\*\* | N/A | 95.53% all-evidence R@5 reranked, 93.19% without (s_cleaned\*) | 90.2% accuracy\*\* (LoCoMo 94.7%) | N/A | N/A | 88.78% overall accuracy w/ reader\*\* | 83.00% overall\*\* (LoCoMo 93.05%, HaluMem 93.04%) |
|
|
969
|
+
| Git-friendly | Yes | No | No | Yes | Yes | No | Yes (memory tracked in git) | No | Yes (Git is the model) | ? |
|
|
970
|
+
| Framework agnostic | Yes | Yes | Partial | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
|
971
|
+
| License | MIT | (open) | Apache-2.0 | (open) | MIT | Proprietary cloud (Graphiti: Apache-2.0) | Apache-2.0 | MIT (core) | Apache-2.0 | Apache-2.0 (OSS) + cloud |
|
|
984
972
|
|
|
985
|
-
|
|
973
|
+
\* Hippo's figures are on `longmemeval_s_cleaned` with a per-question haystack, each the best of five retrieval settings in the benchmark scripts, not `hippo recall`. Any-evidence R@5 counts a hit when any answer session is in the top 5, over all 500 questions: 98.0% with the free local MiniLM embedder (an optional install) and 99.8% with voyage-3-large (measured 2026-06-09, not re-run). All-evidence R@5 counts a hit only when every answer session is in the top 5, over the 470 questions that have an answer: 86.8 to 88.5% with MiniLM. gbrain first published 97.6%, an any-evidence score over all 500; its [report](https://github.com/garrytan/gbrain-evals/blob/main/docs/benchmarks/2026-05-07-longmemeval-s.md) now leads with all-evidence, 95.53% (449 of 470) with the paid Voyage rerank-2.5 reranker and 93.19% without it. On all-evidence recall gbrain is ahead. The June 2026 build scored 98.6 any-evidence; [`docs/evals/2026-09-23-longmemeval-reproduction.md`](docs/evals/2026-09-23-longmemeval-reproduction.md) has both runs. An older hippo number, 86.8% R@5 on `longmemeval_oracle` under pooled (non-per-haystack) retrieval, is not comparable to per-haystack figures.
|
|
974
|
+
|
|
975
|
+
\*\* Different metric: these are end-to-end answer scores, not retrieval R@5. Mem0's 94.4 comes from its hosted platform, which its README says includes optimizations the open-source SDK lacks. Zep's 90.2% and 94.7% are accuracy figures from its homepage. Memoria's 88.78% and EverMind's 83% are overall accuracy with a reader LLM. Higher denominator + LLM helps. Not directly comparable to retrieval-only R@5 numbers above. The Mem0, Zep and Letta columns were last checked against each vendor's own pages on 2026-09-28.
|
|
976
|
+
|
|
977
|
+
Different tools answer different questions. Mem0 and Basic Memory implement "save everything, search later." MemPalace implements "store everything, organize spatially for retrieval." gbrain, Zep, and Cognee implement "extract typed entities and relationships into a knowledge graph." Letta implements "the agent edits its own memory blocks." Memoria implements "Git-style version control over the memory state itself." EverMind implements "self-evolving Skill Memory + multi-modal retrieval over hierarchical scopes." Hippo implements "learn what is wrong and stop repeating it." These are complementary takes, not a single-axis ranking: bio-lifecycle (Hippo) + GraphRAG (gbrain/Cognee/Zep) + agent-self-edit (Letta) + memory-VCS (Memoria) + skill-distillation (EverMind) cover different parts of the same problem.
|
|
986
978
|
|
|
987
979
|
---
|
|
988
980
|
|
|
@@ -1090,7 +1082,7 @@ node run.mjs --adapter all
|
|
|
1090
1082
|
|
|
1091
1083
|
### How do I give Claude Code memory between sessions?
|
|
1092
1084
|
|
|
1093
|
-
Run `npm install -g hippo-memory`, then `hippo init` in the project. If the project has a `CLAUDE.md`, init adds a short block telling Claude to run `hippo context --auto` when a session starts. It also adds
|
|
1085
|
+
Run `npm install -g hippo-memory`, then `hippo init` in the project. If the project has a `CLAUDE.md`, init adds a short block telling Claude to run `hippo context --auto` when a session starts. It also adds hooks to Claude Code's settings that keep your pinned memories in context, save a task snapshot before compaction, and run `hippo sleep` when the session ends. `hippo init --scan ~` gives every git repo under your home folder a store and installs the same hooks, but adds no block to any `CLAUDE.md`. The [Claude Code plugin](https://github.com/kitfunso/hippo-memory/tree/master/extensions/claude-code-plugin) is the alternative to these hooks; use one, not both.
|
|
1094
1086
|
|
|
1095
1087
|
### How do I give Cursor memory between sessions?
|
|
1096
1088
|
|
|
@@ -1098,19 +1090,11 @@ Run `npm install -g hippo-memory`, then `hippo init` in the project. If the proj
|
|
|
1098
1090
|
|
|
1099
1091
|
### How do I give Codex memory across sessions?
|
|
1100
1092
|
|
|
1101
|
-
`hippo init` adds its instructions to your `AGENTS.md`, which Codex reads before it starts work.
|
|
1093
|
+
`hippo init` adds its instructions to your `AGENTS.md`, which Codex reads before it starts work. Capturing Codex sessions is opt-in: `hippo hook install codex` wraps the Codex launcher, and `hippo hook uninstall codex` removes the wrapper.
|
|
1102
1094
|
|
|
1103
1095
|
### Which agents does hippo work with?
|
|
1104
1096
|
|
|
1105
|
-
`hippo init` detects Claude Code, Codex, Cursor, OpenClaw, OpenCode and Pi
|
|
1106
|
-
|
|
1107
|
-
### Does installation automatically save before compaction?
|
|
1108
|
-
|
|
1109
|
-
Package installation alone does not enable automatic preservation on every agent. Complete the documented setup and required host trust; capture and compaction coverage depend on the integration.
|
|
1110
|
-
|
|
1111
|
-
With the Claude Code hooks or native plugin configured, PreCompact saves a derivable working-state snapshot and a compaction record; PostCompact extracts the lessons listed in the compaction summary. A snapshot, a compaction record and a useful memory are different outputs, and post-compaction extraction is not a guarantee that every earlier lesson was saved before loss. Hippo's Codex hooks currently provide prompt delivery and post-compaction re-injection, with one-time trust in `/hooks`; they install no PreCompact save hook. Codex session-end capture requires the opt-in wrapper. MCP tool access and instruction files alone do not provide automatic lifecycle capture.
|
|
1112
|
-
|
|
1113
|
-
Follow the [integration recipes](https://github.com/kitfunso/hippo-memory/tree/master/integrations) for the exact agent and mode. Automatic pre-loss preservation across all supported agents is planned under AZ4-AZ6 in the [roadmap](https://github.com/kitfunso/hippo-memory/blob/master/ROADMAP.md#current-execution-index); it is not a universal install-only capability today.
|
|
1097
|
+
`hippo init` detects Claude Code, Codex, Cursor, OpenClaw, OpenCode and Pi, and wires itself into each one's instruction file, hooks or plugin. It only patches instruction files that already exist. Any MCP client can use the [MCP server](#mcp-server), and other tools can call the CLI or the HTTP API that `hippo serve` starts.
|
|
1114
1098
|
|
|
1115
1099
|
### Can I use hippo as an MCP memory server?
|
|
1116
1100
|
|
|
@@ -1118,7 +1102,7 @@ Yes. `hippo mcp` runs the server over stdio, and `npx -y hippo-memory mcp` runs
|
|
|
1118
1102
|
|
|
1119
1103
|
### How is hippo different from mem0?
|
|
1120
1104
|
|
|
1121
|
-
mem0 uses a language model to extract memories, OpenAI by default in its open-source library, and memories stored through its hosted MCP server live in your Mem0 account ([mem0 docs](https://docs.mem0.ai/platform/mem0-mcp), checked 2026-09-28). Hippo stores memories in SQLite on your machine, needs no account and no model, and `hippo init` wires it into the coding agents it finds. mem0's platform
|
|
1105
|
+
mem0 uses a language model to extract memories, OpenAI by default in its open-source library, and memories stored through its hosted MCP server live in your Mem0 account ([mem0 docs](https://docs.mem0.ai/platform/mem0-mcp), checked 2026-09-28). Hippo stores memories in SQLite on your machine, needs no account and no model, and `hippo init` wires it into the coding agents it finds. mem0's platform and hippo both mark an older fact superseded when a newer one replaces it. Hippo also lets you mark a recalled memory wrong with `hippo outcome --bad`, and it drops out of the top results.
|
|
1122
1106
|
|
|
1123
1107
|
### Is this just RAG?
|
|
1124
1108
|
|
|
@@ -1126,7 +1110,7 @@ No. RAG searches a fixed corpus. Hippo's store changes as your agent works: a me
|
|
|
1126
1110
|
|
|
1127
1111
|
### Does it need embeddings?
|
|
1128
1112
|
|
|
1129
|
-
No. Recall runs on BM25 out of the box, with no model and no network call, and a default install has no embedder. Embeddings are an optional install for hybrid search. On LongMemEval-S, where each question gets its own haystack,
|
|
1113
|
+
No. Recall runs on BM25 out of the box, with no model and no network call, and a default install has no embedder. Embeddings are an optional install for hybrid search. On LongMemEval-S, where each question gets its own haystack, the benchmark scripts (not `hippo recall`) fuse BM25 with the free local MiniLM embedder and reach 98.0% recall@5, counting a hit when any answer session is in the top five. On LongMemEval's oracle split with one pooled store, BM25 alone scored 74.0% recall@5 in v0.11. The two runs use different setups, so they are not a before and after.
|
|
1130
1114
|
|
|
1131
1115
|
### Do I still need CLAUDE.md?
|
|
1132
1116
|
|
|
@@ -1138,11 +1122,11 @@ Mark it, and it drops out of the top results. `hippo outcome --bad` weakens the
|
|
|
1138
1122
|
|
|
1139
1123
|
### Where does hippo keep my data?
|
|
1140
1124
|
|
|
1141
|
-
On your machine, in SQLite: `.hippo/hippo.db` in each project, plus a global store in `~/.hippo/` for lessons shared across projects, with markdown mirrors you can read and commit. Recall makes no network call by default. Text goes to an outside provider only through features that use one: an API embedder, the Jev
|
|
1125
|
+
On your machine, in SQLite: `.hippo/hippo.db` in each project, plus a global store in `~/.hippo/` for lessons shared across projects, with markdown mirrors you can read and commit. Recall makes no network call by default. Text goes to an outside provider only through features that use one: an API embedder, the Jev or LLM reranker, `hippo refine`, and the fact extraction `hippo sleep` runs through Anthropic's API whenever `ANTHROPIC_API_KEY` is set in its environment. To turn that last one off, set `{"extraction":{"enabled":false}}` in `.hippo/config.json`.
|
|
1142
1126
|
|
|
1143
1127
|
### What does hippo cost?
|
|
1144
1128
|
|
|
1145
|
-
Nothing. Hippo is MIT-licensed and needs no account or API key. Optional features that call an outside provider bill through it: the Jev reranker costs about 0.0004 USD a recall, and API embedders and sleep's fact extraction bill your own keys. Memory text handed to your agent uses context tokens
|
|
1129
|
+
Nothing. Hippo is MIT-licensed and needs no account or API key. Optional features that call an outside provider bill through it: the Jev reranker costs about 0.0004 USD a recall, and API embedders and sleep's fact extraction bill your own keys. Memory text handed to your agent uses context tokens, and `hippo tokens` shows how many.
|
|
1146
1130
|
|
|
1147
1131
|
### Is it production-ready?
|
|
1148
1132
|
|
|
@@ -1150,7 +1134,7 @@ Judge it by what is tested. 3,500+ tests run against a real database, with no mo
|
|
|
1150
1134
|
|
|
1151
1135
|
### Has hippo been shown to make agents better at their work?
|
|
1152
1136
|
|
|
1153
|
-
|
|
1137
|
+
Not yet. The published numbers measure retrieval: whether the right memory comes back, and whether a memory marked wrong stays out of the results. A paired test that runs real agent sessions with and without hippo is under way. Every measurement, including failed runs and one retracted claim, is indexed in [docs/evals](https://github.com/kitfunso/hippo-memory/blob/master/docs/evals/README.md).
|
|
1154
1138
|
|
|
1155
1139
|
---
|
|
1156
1140
|
|
|
@@ -24,6 +24,8 @@ export interface ContainerWork {
|
|
|
24
24
|
readonly adopt: ReadonlyMap<string, readonly MemoryEntry[]>;
|
|
25
25
|
/** Legacy rows a new row of the key supersedes, by key (second round). */
|
|
26
26
|
readonly replace: ReadonlyMap<string, readonly MemoryEntry[]>;
|
|
27
|
+
/** This container's prefixes under the project's earlier names; their rows move here, so a new id imports nothing twice. */
|
|
28
|
+
readonly legacyPrefixes: readonly string[];
|
|
27
29
|
}
|
|
28
30
|
export interface ContainerOutcome {
|
|
29
31
|
readonly tally: Tally;
|
|
@@ -34,7 +36,7 @@ export interface ContainerOutcome {
|
|
|
34
36
|
}
|
|
35
37
|
/** Throws SQLITE_BUSY when another writer holds the store past its busy timeout; nothing is written then. */
|
|
36
38
|
export declare function syncContainer(s: StoreSession, work: ContainerWork): ContainerOutcome;
|
|
37
|
-
export type SetAsideWhy = 'note-gone' | 'note-changed' | 'handover' | 'project-merge';
|
|
39
|
+
export type SetAsideWhy = 'note-gone' | 'note-changed' | 'handover' | 'project-merge' | 'project-repair';
|
|
38
40
|
export type SetAsideResult = {
|
|
39
41
|
readonly kind: 'untagged';
|
|
40
42
|
readonly entry: MemoryEntry;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// One container's sync in one transaction on the caller's handle: lookup, plan, then every write (plan designs 6 to 8).
|
|
2
2
|
import { appendAuditEvent } from '../audit.js';
|
|
3
|
-
import { deleteDormantRow, dormantSnapshotsBySourcePrefix, insertDormantRow, readDormantSnapshot } from '../dormant.js';
|
|
3
|
+
import { deleteDormantRow, dormantSnapshotsBySourcePrefix, insertDormantRow, readDormantSnapshot, replaceDormantEntry } from '../dormant.js';
|
|
4
4
|
import { gatedWrite } from '../gated-write.js';
|
|
5
5
|
import { Layer, calculateStrength, createMemory } from '../memory.js';
|
|
6
6
|
import { findRejectedValue, rejectionDigest } from '../rejection.js';
|
|
@@ -65,6 +65,8 @@ class ContainerRun {
|
|
|
65
65
|
}
|
|
66
66
|
run() {
|
|
67
67
|
this.adoptLegacy();
|
|
68
|
+
for (const old of this.w.legacyPrefixes)
|
|
69
|
+
this.adoptPrefix(old);
|
|
68
70
|
const live = selectLiveEntriesBySourcePrefix(this.s.db, this.s.tenantId, this.w.prefix);
|
|
69
71
|
for (const row of [...live, ...[...this.w.replace.values()].flat()])
|
|
70
72
|
this.rows.set(row.id, row);
|
|
@@ -116,6 +118,26 @@ class ContainerRun {
|
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
}
|
|
121
|
+
/** Rows filed under an earlier project name keep their id and history; dormant ones move too, scanned only until this prefix holds live rows. */
|
|
122
|
+
adoptPrefix(old) {
|
|
123
|
+
const origin = this.s.originProject ?? null;
|
|
124
|
+
let moved = 0;
|
|
125
|
+
for (const row of selectLiveEntriesBySourcePrefix(this.s.db, this.s.tenantId, old)) {
|
|
126
|
+
const source = this.w.prefix + row.source.slice(old.length);
|
|
127
|
+
const done = this.s.db.prepare(`UPDATE memories SET source = ?, origin_project = COALESCE(?, origin_project) WHERE id = ? AND tenant_id = ? AND source = ?`).run(source, origin, row.id, row.tenantId, row.source);
|
|
128
|
+
if (Number(done.changes ?? 0) === 0)
|
|
129
|
+
continue;
|
|
130
|
+
moved++;
|
|
131
|
+
this.mirror.push({ ...row, source, origin_project: origin ?? row.origin_project });
|
|
132
|
+
}
|
|
133
|
+
this.tally.renamed += moved;
|
|
134
|
+
if (moved === 0 && (this.w.container.items.length === 0 || selectLiveEntriesBySourcePrefix(this.s.db, this.s.tenantId, this.w.prefix).length > 0))
|
|
135
|
+
return;
|
|
136
|
+
for (const snap of dormantSnapshotsBySourcePrefix(this.s.db, this.s.tenantId, old)) {
|
|
137
|
+
const source = this.w.prefix + snap.entry.source.slice(old.length);
|
|
138
|
+
replaceDormantEntry(this.s.db, this.s.tenantId, snap.entry.id, { ...snap.entry, source, origin_project: origin ?? snap.entry.origin_project });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
119
141
|
refusals() {
|
|
120
142
|
const out = new Map();
|
|
121
143
|
for (const item of this.w.container.items) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ProjectIdentity } from '../project-identity.js';
|
|
1
2
|
import type { Adapter, AdapterContext, Listing } from './types.js';
|
|
2
3
|
/** Claude Code's auto memory folder names for a project: its checkout, which subfolders share, or the folder itself outside a repository. */
|
|
3
4
|
export declare function claudeMemoryFolderNames(projectRoot: string, platform: NodeJS.Platform): Set<string>;
|
|
@@ -6,6 +7,8 @@ export declare function claudeCheckoutRoot(top: string, gitDir: string, common:
|
|
|
6
7
|
/** Claude Code's folder name for a path: non-alphanumerics made '-', and a name over 200 characters cut to 200 plus a base-36 hash of the whole path. */
|
|
7
8
|
export declare function claudeFolderName(root: string): string;
|
|
8
9
|
export declare const claudeCodeAdapter: Adapter;
|
|
9
|
-
/**
|
|
10
|
+
/** A session's own notes folder and nothing else, with no git call, so post-compact can read it inside the hook's time limit. */
|
|
10
11
|
export declare function claudeTranscriptListing(ctx: AdapterContext, transcriptPath: string): Listing;
|
|
12
|
+
/** The project a session folder's notes belong to: the one Claude named the folder for, the session's start folder, else cwd or a parent; null when none matches. */
|
|
13
|
+
export declare function transcriptNotesProject(transcriptPath: string, cwd: string | null, machine: Pick<AdapterContext, 'platform' | 'env'>): ProjectIdentity | null;
|
|
11
14
|
//# sourceMappingURL=claude-code.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Claude Code's auto memory: frontmatter `.md` notes in a per-project folder, plus the `autoMemoryDirectory` user folder.
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { realpathOrResolve } from '../project-identity.js';
|
|
4
|
+
import { realpathOrResolve, resolveProjectIdentity } from '../project-identity.js';
|
|
5
5
|
import { isStringValue } from '../capture-contract.js';
|
|
6
6
|
import { isJsonObject } from '../hooks/shared.js';
|
|
7
7
|
import { expandHome, frontmatterField, itemTime, readTextFile, splitFrontmatter } from './files.js';
|
|
@@ -45,23 +45,67 @@ export const claudeCodeAdapter = {
|
|
|
45
45
|
return { tool: 'claude-code', home: config, containers: readFolders(folders, scope, ctx.platform), warnings };
|
|
46
46
|
},
|
|
47
47
|
};
|
|
48
|
-
/**
|
|
48
|
+
/** A session's own notes folder and nothing else, with no git call, so post-compact can read it inside the hook's time limit. */
|
|
49
49
|
export function claudeTranscriptListing(ctx, transcriptPath) {
|
|
50
50
|
const config = ctx.env.CLAUDE_CONFIG_DIR || path.join(ctx.home, '.claude');
|
|
51
51
|
const folder = path.join(path.dirname(transcriptPath), 'memory');
|
|
52
52
|
return { tool: 'claude-code', home: config, containers: readFolders([folder], 'project', ctx.platform), warnings: [] };
|
|
53
53
|
}
|
|
54
|
+
/** The project a session folder's notes belong to: the one Claude named the folder for, the session's start folder, else cwd or a parent; null when none matches. */
|
|
55
|
+
export function transcriptNotesProject(transcriptPath, cwd, machine) {
|
|
56
|
+
const fold = (name) => (machine.platform === 'win32' ? name.toLowerCase() : name);
|
|
57
|
+
const folder = fold(path.basename(path.dirname(transcriptPath)));
|
|
58
|
+
const start = transcriptStartCwd(transcriptPath);
|
|
59
|
+
const pinned = pinnedProjectDirName(machine.env);
|
|
60
|
+
if (pinned !== null && fold(pinned) === folder) {
|
|
61
|
+
// A pinned name stands for whatever project the session ran in, so its start folder decides.
|
|
62
|
+
const from = start ?? cwd;
|
|
63
|
+
return from !== null && fs.existsSync(from) ? resolveProjectIdentity(from) : null;
|
|
64
|
+
}
|
|
65
|
+
for (const from of [start, cwd]) {
|
|
66
|
+
for (let dir = from === null ? null : path.resolve(from); dir !== null; dir = path.dirname(dir) === dir ? null : path.dirname(dir)) {
|
|
67
|
+
// A folder gone from disk resolves to its bare name, never the project it was in, so it decides nothing.
|
|
68
|
+
if ([dir, realpathOrResolve(dir)].some((d) => fold(claudeFolderName(d)) === folder))
|
|
69
|
+
return fs.existsSync(dir) ? resolveProjectIdentity(dir) : null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Claude writes the cwd on each message line after a few header lines; 64 KB holds the first with room to spare.
|
|
75
|
+
const START_SCAN_BYTES = 64 * 1024;
|
|
76
|
+
/** The cwd on the transcript's first line that has one: the folder Claude named the session folder for, which a folder name alone cannot give back. */
|
|
77
|
+
function transcriptStartCwd(transcriptPath) {
|
|
78
|
+
if (!fs.existsSync(transcriptPath))
|
|
79
|
+
return null;
|
|
80
|
+
const fd = fs.openSync(transcriptPath, 'r');
|
|
81
|
+
try {
|
|
82
|
+
const buf = Buffer.alloc(START_SCAN_BYTES);
|
|
83
|
+
const lines = buf.subarray(0, fs.readSync(fd, buf, 0, buf.length, 0)).toString('utf8').split('\n');
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
const cwd = /"cwd":"((?:[^"\\]|\\.)*)"/.exec(line)?.[1];
|
|
86
|
+
if (cwd === undefined)
|
|
87
|
+
continue;
|
|
88
|
+
// SAFETY: the match is the body of one JSON string, so parsing it in quotes yields a string.
|
|
89
|
+
return JSON.parse(`"${cwd}"`);
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
fs.closeSync(fd);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
54
97
|
function projectFolders(ctx, config) {
|
|
55
98
|
const projects = path.join(config, 'projects');
|
|
56
99
|
const names = ctx.projectRoot === undefined ? [] : [...claudeMemoryFolderNames(ctx.projectRoot, ctx.platform)];
|
|
57
|
-
const pinned = ctx.env
|
|
58
|
-
|
|
59
|
-
if (ctx.env.CLAUDE_CONFIG_DIR && pinned !== undefined && PROJECT_DIR_NAME.test(pinned))
|
|
100
|
+
const pinned = pinnedProjectDirName(ctx.env);
|
|
101
|
+
if (pinned !== null)
|
|
60
102
|
names.push(pinned);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
103
|
+
return names.map((name) => path.join(projects, name, 'memory'));
|
|
104
|
+
}
|
|
105
|
+
function pinnedProjectDirName(env) {
|
|
106
|
+
const pinned = env.CLAUDE_CODE_PROJECT_DIR_NAME;
|
|
107
|
+
// Claude reads the pinned name only alongside a pinned config folder.
|
|
108
|
+
return env.CLAUDE_CONFIG_DIR && pinned !== undefined && PROJECT_DIR_NAME.test(pinned) ? pinned : null;
|
|
65
109
|
}
|
|
66
110
|
function userFolders(ctx, config, warnings) {
|
|
67
111
|
const dir = autoMemoryDirectory(path.join(config, 'settings.json'), ctx.home, warnings);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { AGENT_MEMORY_TOOLS } from './tools.js';
|
|
3
3
|
export function emptyTally() {
|
|
4
4
|
return {
|
|
5
|
-
imported: 0, replaced: 0, restored: 0, setAside: 0, untagged: 0, unchanged: 0, adopted: 0, collapsed: 0, retagged: 0,
|
|
5
|
+
imported: 0, replaced: 0, restored: 0, setAside: 0, untagged: 0, unchanged: 0, adopted: 0, renamed: 0, collapsed: 0, retagged: 0,
|
|
6
6
|
handedOver: 0, duplicate: 0, secret: 0, short: 0, rejected: 0, unread: 0, unreadable: 0, busy: 0,
|
|
7
7
|
};
|
|
8
8
|
}
|
|
@@ -47,7 +47,7 @@ export function totalTally(report) {
|
|
|
47
47
|
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
48
48
|
const TALLY_WORDS = {
|
|
49
49
|
imported: 'new', replaced: 'replaced', restored: 'brought back', setAside: 'set aside', untagged: 'pinned and kept',
|
|
50
|
-
unchanged: 'unchanged', adopted: 'taken over from the old Claude import', collapsed: 'duplicates folded', retagged: 'retagged',
|
|
50
|
+
unchanged: 'unchanged', adopted: 'taken over from the old Claude import', renamed: 'moved to the project id', collapsed: 'duplicates folded', retagged: 'retagged',
|
|
51
51
|
handedOver: 'handed over from the global store', duplicate: 'already stored', secret: 'skipped for a secret', short: 'too short',
|
|
52
52
|
rejected: 'skipped as rejected', unread: 'files skipped', unreadable: 'unreadable folders', busy: 'busy folders',
|
|
53
53
|
};
|
|
@@ -24,10 +24,10 @@ export declare function importForStore(hippoRoot: string, opts: SyncOptions): Im
|
|
|
24
24
|
export declare function importProjectMemories(hippoRoot: string, opts: SyncOptions): ImportReport;
|
|
25
25
|
/** Every tool's user-level memory into the global store, created on demand, with no origin. */
|
|
26
26
|
export declare function importUserMemories(invokingRoot: string, opts: SyncOptions): ImportReport;
|
|
27
|
-
/** Session end in a folder with no store of its own: the session's project into the global store with its origin, then the user pass. */
|
|
27
|
+
/** Session end in a folder with no store of its own: the session's project into the global store with its origin, the session folder's notes, then the user pass. */
|
|
28
28
|
export declare function importAtSessionEnd(cwd: string, transcriptPath: string | undefined, opts: SyncOptions): ImportReport;
|
|
29
|
-
/**
|
|
30
|
-
export declare function
|
|
29
|
+
/** The session folder's notes under the project Claude filed them for, never cwd's: a session begun at home keeps its home notes user-global wherever it ends. No git call, so post-compact can run it. */
|
|
30
|
+
export declare function importSessionFolder(hippoRoot: string, transcriptPath: string, cwd: string | null, opts: SyncOptions): ImportReport;
|
|
31
31
|
/** The variable when set (empty or `none` is off); else the tools both the invoking and the target store's config allow. */
|
|
32
32
|
export declare function allowedTools(env: Machine['env'], invoking: string, target: string, warnings: string[]): Set<ToolId>;
|
|
33
33
|
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -6,7 +6,8 @@ import path from 'node:path';
|
|
|
6
6
|
import { errorMessage } from '../log.js';
|
|
7
7
|
import { loadConfig } from '../config.js';
|
|
8
8
|
import { closeHippoDb, isSqliteBusy, openHippoDb } from '../db.js';
|
|
9
|
-
import {
|
|
9
|
+
import { namesFoldedInto } from '../project-merge.js';
|
|
10
|
+
import { isGlobalStoreRoot, originInSql, projectNames, resolveGlobalRootDir, resolveProjectIdentity } from '../project-identity.js';
|
|
10
11
|
import { duplicateKey, heldTextKeys } from '../same-text.js';
|
|
11
12
|
import { removeEntryMirrors } from '../store/mirrors.js';
|
|
12
13
|
import { initStore, isInitialized } from '../store/open.js';
|
|
@@ -15,7 +16,7 @@ import { selectLiveEntriesBySourcePrefix } from '../store/entry-reads.js';
|
|
|
15
16
|
import { updateStats } from '../store/index-and-stats.js';
|
|
16
17
|
import { resolveTenantId } from '../tenant.js';
|
|
17
18
|
import { setAsideRow, syncContainer } from './apply.js';
|
|
18
|
-
import { claudeCodeAdapter, claudeTranscriptListing } from './claude-code.js';
|
|
19
|
+
import { claudeCodeAdapter, claudeTranscriptListing, transcriptNotesProject } from './claude-code.js';
|
|
19
20
|
import { codexAdapter } from './codex.js';
|
|
20
21
|
import { copilotAdapter } from './copilot.js';
|
|
21
22
|
import { geminiAdapter } from './gemini.js';
|
|
@@ -43,34 +44,45 @@ export function importForStore(hippoRoot, opts) {
|
|
|
43
44
|
export function importProjectMemories(hippoRoot, opts) {
|
|
44
45
|
const ctx = context(opts.machine, { projectRoot: path.dirname(hippoRoot) });
|
|
45
46
|
return runPass({
|
|
46
|
-
scope: 'project', target: hippoRoot, invoking: hippoRoot, list: (a) => a.list(ctx, 'project'), legacy: true, originProject: undefined, handover: true,
|
|
47
|
+
scope: 'project', target: hippoRoot, invoking: hippoRoot, list: (a) => a.list(ctx, 'project'), legacy: true, originProject: undefined, legacyOrigins: [], handover: true,
|
|
47
48
|
}, opts);
|
|
48
49
|
}
|
|
49
50
|
/** Every tool's user-level memory into the global store, created on demand, with no origin. */
|
|
50
51
|
export function importUserMemories(invokingRoot, opts) {
|
|
51
52
|
const ctx = context(opts.machine, {});
|
|
52
53
|
return runPass({
|
|
53
|
-
scope: 'user', target: resolveGlobalRootDir(), invoking: invokingRoot, list: (a) => a.list(ctx, 'user'), legacy: false, originProject: '', handover: false,
|
|
54
|
+
scope: 'user', target: resolveGlobalRootDir(), invoking: invokingRoot, list: (a) => a.list(ctx, 'user'), legacy: false, originProject: '', legacyOrigins: [], handover: false,
|
|
54
55
|
}, opts);
|
|
55
56
|
}
|
|
56
|
-
/** Session end in a folder with no store of its own: the session's project into the global store with its origin, then the user pass. */
|
|
57
|
+
/** Session end in a folder with no store of its own: the session's project into the global store with its origin, the session folder's notes, then the user pass. */
|
|
57
58
|
export function importAtSessionEnd(cwd, transcriptPath, opts) {
|
|
58
59
|
const globalRoot = resolveGlobalRootDir();
|
|
59
|
-
const ctx = context(opts.machine, { projectRoot: cwd
|
|
60
|
+
const ctx = context(opts.machine, { projectRoot: cwd });
|
|
60
61
|
const report = runPass({
|
|
61
|
-
scope: 'project', target: globalRoot, invoking: globalRoot, list: (a) => a.list(ctx, 'project'), legacy: false,
|
|
62
|
+
scope: 'project', target: globalRoot, invoking: globalRoot, list: (a) => a.list(ctx, 'project'), legacy: false, ...projectOrigins(resolveProjectIdentity(cwd)), handover: false,
|
|
62
63
|
}, opts);
|
|
64
|
+
if (transcriptPath !== undefined)
|
|
65
|
+
mergeReports(report, importSessionFolder(globalRoot, transcriptPath, cwd, opts));
|
|
63
66
|
mergeReports(report, importUserMemories(globalRoot, opts));
|
|
64
67
|
return report;
|
|
65
68
|
}
|
|
66
|
-
/**
|
|
67
|
-
export function
|
|
69
|
+
/** The session folder's notes under the project Claude filed them for, never cwd's: a session begun at home keeps its home notes user-global wherever it ends. No git call, so post-compact can run it. */
|
|
70
|
+
export function importSessionFolder(hippoRoot, transcriptPath, cwd, opts) {
|
|
71
|
+
const project = transcriptNotesProject(transcriptPath, cwd, opts.machine);
|
|
72
|
+
if (project === null)
|
|
73
|
+
return emptyReport();
|
|
74
|
+
// A project store takes its own project's notes; another project's, or home's, go to the global store, which parts them by origin.
|
|
75
|
+
const own = isGlobalStoreRoot(hippoRoot) || project.name === resolveProjectIdentity(path.dirname(hippoRoot)).name;
|
|
76
|
+
const target = own ? hippoRoot : resolveGlobalRootDir();
|
|
68
77
|
const ctx = context(opts.machine, {});
|
|
69
78
|
return runPass({
|
|
70
|
-
scope: 'project', target
|
|
79
|
+
scope: 'project', target, invoking: hippoRoot, legacy: false, ...projectOrigins(project), handover: false,
|
|
71
80
|
list: (a) => (a.tool === 'claude-code' ? claudeTranscriptListing(ctx, transcriptPath) : null),
|
|
72
81
|
}, opts);
|
|
73
82
|
}
|
|
83
|
+
function projectOrigins(project) {
|
|
84
|
+
return { originProject: project.name, legacyOrigins: projectNames(project).slice(1) };
|
|
85
|
+
}
|
|
74
86
|
function context(machine, extra) {
|
|
75
87
|
return { home: machine.home, env: machine.env, platform: machine.platform, ...extra };
|
|
76
88
|
}
|
|
@@ -175,11 +187,12 @@ function syncStore(pass, listings, store, opts, report) {
|
|
|
175
187
|
tenantId,
|
|
176
188
|
baseHalfLifeDays: loadConfig(store.root).defaultHalfLifeDays,
|
|
177
189
|
originProject: pass.originProject,
|
|
178
|
-
isDuplicate: duplicateCheck(store, tenantId, pass.originProject, legacy?.adopted ?? new Set()),
|
|
190
|
+
isDuplicate: duplicateCheck(store, tenantId, [pass.originProject ?? '', ...pass.legacyOrigins], legacy?.adopted ?? new Set()),
|
|
179
191
|
dryRun: opts.dryRun === true,
|
|
180
192
|
};
|
|
181
193
|
// A project's rows in the global store are parted by origin: a worktree and its main checkout share a Claude folder.
|
|
182
194
|
const partition = store.global && pass.scope === 'project' ? pass.originProject ?? '' : null;
|
|
195
|
+
const earlier = partition === null || partition === '' ? [] : earlierNames(store.db, tenantId, partition, pass.legacyOrigins);
|
|
183
196
|
const synced = [];
|
|
184
197
|
let remembered = 0;
|
|
185
198
|
for (const listing of listings) {
|
|
@@ -190,7 +203,7 @@ function syncStore(pass, listings, store, opts, report) {
|
|
|
190
203
|
out.tally.unreadable++;
|
|
191
204
|
continue;
|
|
192
205
|
}
|
|
193
|
-
const work = containerWork(tool, container, opts.machine.platform, legacy, partition ?? '');
|
|
206
|
+
const work = containerWork(tool, container, opts.machine.platform, legacy, partition ?? '', earlier);
|
|
194
207
|
const outcome = syncOne(session, work, out, report);
|
|
195
208
|
if (outcome === null)
|
|
196
209
|
continue;
|
|
@@ -200,21 +213,28 @@ function syncStore(pass, listings, store, opts, report) {
|
|
|
200
213
|
afterCommit(store.root, outcome, report);
|
|
201
214
|
}
|
|
202
215
|
if (session.dryRun)
|
|
203
|
-
out.unlisted += unlistedRows(store.db, tenantId, tool, pass.scope, listing, opts.machine.platform, partition);
|
|
216
|
+
out.unlisted += unlistedRows(store.db, tenantId, tool, pass.scope, listing, opts.machine.platform, partition === null ? null : [partition, ...earlier]);
|
|
204
217
|
}
|
|
205
218
|
if (remembered > 0 && !session.dryRun)
|
|
206
219
|
bumpRemembered(store.root, remembered, report);
|
|
207
220
|
return synced;
|
|
208
221
|
}
|
|
209
|
-
|
|
222
|
+
/** The legacy names plus every name a merge or repair folded into the project; the audit read runs once per pass. */
|
|
223
|
+
function earlierNames(db, tenantId, id, legacyOrigins) {
|
|
224
|
+
const names = [id, ...legacyOrigins];
|
|
225
|
+
return [...legacyOrigins, ...namesFoldedInto(db, tenantId, names)].filter((n) => n !== '' && n !== id);
|
|
226
|
+
}
|
|
227
|
+
function containerWork(tool, container, platform, legacy, origin, earlier) {
|
|
210
228
|
const none = new Map();
|
|
211
229
|
const own = tool.id === 'claude-code' ? legacy?.byContainer.get(container.path) : undefined;
|
|
230
|
+
const prefixOf = (name) => containerPrefix(tool.id, containerId(container.path, container.scope, platform, name));
|
|
212
231
|
return {
|
|
213
232
|
tool,
|
|
214
233
|
container,
|
|
215
|
-
prefix:
|
|
234
|
+
prefix: prefixOf(origin),
|
|
216
235
|
adopt: own?.adopt ?? none,
|
|
217
236
|
replace: own?.replace ?? none,
|
|
237
|
+
legacyPrefixes: earlier.map(prefixOf),
|
|
218
238
|
};
|
|
219
239
|
}
|
|
220
240
|
/** Null when the container was skipped: a busy store waits for the next sync, any other failure is a warning and the sync goes on. */
|
|
@@ -257,27 +277,27 @@ function bumpRemembered(root, remembered, report) {
|
|
|
257
277
|
}
|
|
258
278
|
}
|
|
259
279
|
/** Design 6: only text stored by another path counts, and in the global store only rows visible where the new row goes. */
|
|
260
|
-
function duplicateCheck(store, tenantId,
|
|
280
|
+
function duplicateCheck(store, tenantId, origins, adopted) {
|
|
261
281
|
let keys = null;
|
|
262
282
|
return (text) => {
|
|
263
|
-
keys ??= otherPathKeys(store, tenantId,
|
|
283
|
+
keys ??= otherPathKeys(store, tenantId, origins, adopted);
|
|
264
284
|
return keys.has(duplicateKey(text));
|
|
265
285
|
};
|
|
266
286
|
}
|
|
267
|
-
function otherPathKeys(store, tenantId,
|
|
268
|
-
const visible = store.global ? ` AND (origin_project = '' OR
|
|
269
|
-
const params = store.global ? [tenantId, AGENT_MEMORY_SOURCE_PREFIX,
|
|
287
|
+
function otherPathKeys(store, tenantId, origins, adopted) {
|
|
288
|
+
const visible = store.global ? ` AND (origin_project = '' OR ${originInSql(origins)})` : '';
|
|
289
|
+
const params = store.global ? [tenantId, AGENT_MEMORY_SOURCE_PREFIX, ...origins] : [tenantId, AGENT_MEMORY_SOURCE_PREFIX];
|
|
270
290
|
// SAFETY: the SELECT names the three columns of the row type.
|
|
271
291
|
const rows = store.db.prepare(`SELECT id, content, source FROM memories
|
|
272
292
|
WHERE tenant_id = ? AND superseded_by IS NULL AND substr(source, 1, ${AGENT_MEMORY_SOURCE_PREFIX.length}) != ?${visible}`).all(...params);
|
|
273
293
|
return new Set(rows.filter((r) => !adopted.has(r.id)).flatMap(heldTextKeys));
|
|
274
294
|
}
|
|
275
|
-
/** Dry run only: kept rows of this scope in containers this run did not list (a moved project's old folder); `partition` limits it to one
|
|
295
|
+
/** Dry run only: kept rows of this scope in containers this run did not list (a moved project's old folder); `partition` limits it to one project's names. */
|
|
276
296
|
function unlistedRows(db, tenantId, tool, scope, listing, platform, partition) {
|
|
277
|
-
const listed = listing.containers.
|
|
297
|
+
const listed = listing.containers.flatMap((c) => (partition ?? ['']).map((name) => containerPrefix(tool.id, containerId(c.path, c.scope, platform, name))));
|
|
278
298
|
return selectLiveEntriesBySourcePrefix(db, tenantId, `${toolSourcePrefix(tool.id)}${scope === 'project' ? 'p' : 'u'}-`)
|
|
279
299
|
.filter((row) => row.tags.includes(tool.tag) && !listed.some((p) => row.source.startsWith(p)))
|
|
280
|
-
.filter((row) => partition === null || (row.origin_project ?? '')
|
|
300
|
+
.filter((row) => partition === null || partition.includes(row.origin_project ?? '')).length;
|
|
281
301
|
}
|
|
282
302
|
/** Design 2's handover: rows the store-less hook path left in the global store, under this project's origin, for containers its store now syncs. */
|
|
283
303
|
function handOver(synced, projectRoot, opts, report) {
|
|
@@ -289,7 +309,9 @@ function handOver(synced, projectRoot, opts, report) {
|
|
|
289
309
|
db = openHippoDb(globalRoot, { busyWaitMs: opts.busyWaitMs });
|
|
290
310
|
const tenantId = resolveTenantId({});
|
|
291
311
|
// A folder with no git and no marker wrote as '' before its store existed, and as its own name after.
|
|
292
|
-
|
|
312
|
+
// Names folded into this project's in the global store were its rows too.
|
|
313
|
+
const names = projectNames(resolveProjectIdentity(projectRoot));
|
|
314
|
+
const origins = [...new Set([...names, ...namesFoldedInto(db, tenantId, names), ''])];
|
|
293
315
|
for (const work of synced)
|
|
294
316
|
handOverContainer(db, globalRoot, tenantId, work, origins, opts.machine.platform, report);
|
|
295
317
|
}
|
|
@@ -6,8 +6,6 @@ export interface AdapterContext {
|
|
|
6
6
|
readonly env: Readonly<Record<string, string | undefined>>;
|
|
7
7
|
readonly platform: NodeJS.Platform;
|
|
8
8
|
readonly projectRoot?: string;
|
|
9
|
-
/** Claude Code's transcript at a hook: its folder's `memory/` holds that session's own notes. */
|
|
10
|
-
readonly transcriptPath?: string;
|
|
11
9
|
}
|
|
12
10
|
export interface MemoryItem {
|
|
13
11
|
/** A path inside the container with '/' separators, or `<heading slug>/<text hash>` in a single-file store. */
|