hippo-memory 1.52.4 → 1.52.6

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 CHANGED
@@ -830,17 +830,17 @@ For how these mechanisms connect to LLM training, continual learning, and open r
830
830
 
831
831
  ## Comparison
832
832
 
833
- 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)), and version-control / skill-distillation takes ([Memoria](https://github.com/matrixorigin/Memoria), [EverMind](https://evermind.ai/)) all solve adjacent problems with different mechanics.
833
+ 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.
834
834
 
835
- | 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) | [Cognee](https://www.cognee.ai/) | [Memoria](https://github.com/matrixorigin/Memoria) | [EverMind](https://evermind.ai/) |
835
+ | 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/) |
836
836
  |---------|-------|-----------|------|-------------|--------|-----|-------|--------|---------|----------|
837
837
  | Decay by default | Yes | No | No | No | No | No | No | No | No | No |
838
838
  | Retrieval strengthening | Yes | No | No | No | No | No | No | Partial (recall tuning) | No | Partial (Skill Memory distills patterns) |
839
839
  | Reward-proportional decay | Yes | No | No | No | No | No | No | No | No | No |
840
- | Hybrid search (BM25 + embeddings) | Yes | Embeddings + spatial | Embeddings only | No | Yes (vec + rerank + graph) | Yes (graph + vec) | ? | Yes (GraphRAG) | Yes (vector + full-text) | Yes (mRAG, multi-modal) |
841
- | Schema acceleration / knowledge graph | Yes (schema) | No | No | No | Yes (typed KG, self-wiring) | Yes (temporal KG) | No | Yes (auto-ontologies) | No (typed claims) | Yes (hierarchical: user/group/agent) |
842
- | Conflict detection + resolution | Yes | No | No | No | Yes (eval-surfaced) | Yes (auto-invalidate stale facts) | No | No | Yes (auto-detect + quarantine) | Partial (temporal tracking) |
843
- | Multi-agent shared memory | Yes | No | No | No | Yes (brain repo, team mounts) | Yes | No (single-agent state) | Yes | Yes (branch/merge across sessions) | Yes (multi-agent coordination) |
840
+ | 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) |
841
+ | 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) |
842
+ | 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) |
843
+ | 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) |
844
844
  | Transfer scoring | Yes | No | No | No | No | No | No | No | No | No |
845
845
  | Outcome tracking | Yes | No | No | No | No | No | No | No | No | Partial (Cases: agent trajectories) |
846
846
  | Confidence tiers | Yes | No | No | No | No (typed facts) | No | No | No | No | No |
@@ -848,16 +848,16 @@ The AI-memory category matured fast in 2026. Hippo's specific take — bio-decay
848
848
  | Lossless compression | No | Yes (AAAK, 30x) | No | No | No | No | No | No | No | No |
849
849
  | 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) |
850
850
  | Auto-hook install | Yes | No | No | No | No | No | No | No | No | No |
851
- | MCP server | Yes | Yes | Yes (hosted, needs an account) | Yes | Yes (stdio + HTTP/OAuth) | Partial (managed) | Yes (via Letta Code) | Yes (first-party Claude/LangGraph) | Yes | ? |
852
- | Zero runtime deps | Yes | No (ChromaDB) | No | No | No (PGLite or PG+pgvector) | No (managed service) | No (Python deps) | No (Python deps) | Yes (single Rust binary) | No (managed + OSS) |
853
- | LongMemEval (best published) | 98.0% local / 99.8% voyage R@5 (s_cleaned, per-haystack)\* | 96.6% raw / 100% reranked R@5 | ~49-85% R@5 | N/A | 97.6-97.9% R@5 (s_cleaned\*) | N/A (LoCoMo 80.3%) | N/A | N/A | 88.78% overall accuracy w/ reader\*\* | 83.00% overall\*\* (LoCoMo 93.05%, HaluMem 93.04%) |
854
- | Git-friendly | Yes | No | No | Yes | Yes | No | No | No | Yes (Git is the model) | ? |
851
+ | 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 | ? |
852
+ | 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) |
853
+ | LongMemEval (best published) | 98.0% local / 99.8% voyage R@5 (s_cleaned, per-haystack)\* | 96.6% raw / 100% reranked R@5 | 94.4 (hosted platform)\*\* | N/A | 97.6-97.9% R@5 (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%) |
854
+ | Git-friendly | Yes | No | No | Yes | Yes | No | Yes (memory tracked in git) | No | Yes (Git is the model) | ? |
855
855
  | Framework agnostic | Yes | Yes | Partial | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
856
- | License | MIT | (open) | Apache-2.0 | (open) | MIT | Apache-2.0 (community) | Apache-2.0 | MIT (core) | Apache-2.0 | Apache-2.0 (OSS) + cloud |
856
+ | 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 |
857
857
 
858
858
  \* Hippo's 98.0% uses the free local MiniLM embedder (an optional install) and 99.8% uses voyage-3-large (measured 2026-06-09, not re-run). Both are on `longmemeval_s_cleaned` with a per-question haystack, the split and metric of gbrain's published 97.6%. Each is the best of five retrieval settings in the benchmark scripts, not `hippo recall`. At 500 questions the 95% interval is about ±1.2 points, so 98.0 and 97.6 are a tie. The June 2026 build scored 98.6; [`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.
859
859
 
860
- \*\* Different metric: Memoria's 88.78% and EverMind's 83% are reported as overall accuracy with a reader LLM, not retrieval R@5. Higher denominator + LLM helps. Not directly comparable to retrieval-only R@5 numbers above.
860
+ \*\* 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.
861
861
 
862
862
  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.
863
863
 
package/dist/cli.d.ts CHANGED
@@ -65,5 +65,7 @@ export declare function printContextMarkdown(items: Array<{
65
65
  showStrength?: boolean;
66
66
  heading?: string;
67
67
  }): void;
68
+ export declare function usageText(): string;
69
+ export declare function verbUsage(verb: string): string | null;
68
70
  export declare function runCli(argv?: string[]): Promise<void>;
69
71
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js CHANGED
@@ -138,7 +138,7 @@ import { listDlq, replayDlqEntry } from './connectors/slack/dlq.js';
138
138
  import { backfillChannel } from './connectors/slack/backfill.js';
139
139
  import { slackHistoryFetcher } from './connectors/slack/web-client.js';
140
140
  import { addWorkspace as addSlackWorkspace, listWorkspaces as listSlackWorkspaces, removeWorkspace as removeSlackWorkspace, } from './connectors/slack/workspaces.js';
141
- import { cmdGithub } from './connectors/github/cli-impl.js';
141
+ import { cmdGithub, printGithubBackfillUsage } from './connectors/github/cli-impl.js';
142
142
  // ---------------------------------------------------------------------------
143
143
  // Helpers
144
144
  // ---------------------------------------------------------------------------
@@ -392,6 +392,11 @@ export function parseArgs(argv) {
392
392
  i += 2;
393
393
  }
394
394
  }
395
+ else if (part === '-h') {
396
+ // Running a verb when help was asked costs more than losing a literal -h; `-- -h` still passes one.
397
+ flags['help'] = true;
398
+ i++;
399
+ }
395
400
  else {
396
401
  args.push(part);
397
402
  i++;
@@ -8168,10 +8173,6 @@ function printAuditPruneUsage() {
8168
8173
  console.log(' --json Output the result as JSON {cutoff, count, dryRun}.');
8169
8174
  }
8170
8175
  function cmdAuditPrune(hippoRoot, flags) {
8171
- if (flags['help']) {
8172
- printAuditPruneUsage();
8173
- return;
8174
- }
8175
8176
  const olderThanRaw = typeof flags['older-than'] === 'string' ? flags['older-than'] : '';
8176
8177
  if (!olderThanRaw) {
8177
8178
  console.error('Usage: hippo audit prune --older-than <Nd> [--dry-run] [--tenant <t>]');
@@ -8474,12 +8475,6 @@ function printSlackBackfillUsage() {
8474
8475
  console.log(' --since backfill from ISO timestamp (default: cursor)');
8475
8476
  }
8476
8477
  function cmdSlackBackfill(hippoRoot, flags) {
8477
- // M3: detect --help BEFORE token check so operators can read usage in
8478
- // environments without SLACK_BOT_TOKEN configured.
8479
- if (flags['help']) {
8480
- printSlackBackfillUsage();
8481
- return;
8482
- }
8483
8478
  const channel = typeof flags['channel'] === 'string' ? flags['channel'] : undefined;
8484
8479
  if (!channel) {
8485
8480
  printSlackBackfillUsage();
@@ -8557,10 +8552,6 @@ function printSlackWorkspacesUsage() {
8557
8552
  console.log(' remove --team <T> Remove a workspace registration');
8558
8553
  }
8559
8554
  function cmdSlackWorkspacesAdd(hippoRoot, flags) {
8560
- if (flags['help']) {
8561
- printSlackWorkspacesUsage();
8562
- return;
8563
- }
8564
8555
  const teamId = typeof flags['team'] === 'string' ? flags['team'].trim() : '';
8565
8556
  const tenantId = typeof flags['tenant'] === 'string' ? flags['tenant'].trim() : '';
8566
8557
  if (!teamId || !tenantId) {
@@ -8593,10 +8584,6 @@ function cmdSlackWorkspacesList(hippoRoot) {
8593
8584
  }
8594
8585
  }
8595
8586
  function cmdSlackWorkspacesRemove(hippoRoot, flags) {
8596
- if (flags['help']) {
8597
- printSlackWorkspacesUsage();
8598
- return;
8599
- }
8600
8587
  const teamId = typeof flags['team'] === 'string' ? flags['team'].trim() : '';
8601
8588
  if (!teamId) {
8602
8589
  console.error('Usage: hippo slack workspaces remove --team <T>');
@@ -8649,9 +8636,9 @@ function cmdSlack(hippoRoot, args, flags) {
8649
8636
  console.error('Usage: hippo slack <backfill|dlq list|dlq replay <id> [--force]|workspaces add|workspaces list|workspaces remove> [...]');
8650
8637
  process.exit(1);
8651
8638
  }
8652
- function printUsage() {
8653
- console.log(`
8654
- Hippo - biologically-inspired memory system for AI agents
8639
+ export function usageText() {
8640
+ return `
8641
+ Hippo - memory for AI agents that learns what is wrong and stops repeating it
8655
8642
 
8656
8643
  Usage: hippo <command> [options]
8657
8644
 
@@ -8671,6 +8658,10 @@ Commands:
8671
8658
  --observed Set confidence: observed
8672
8659
  --inferred Set confidence: inferred
8673
8660
  --global Store in global store ($HIPPO_HOME or ~/.hippo/)
8661
+ supersede <id> "<text>" Replace a memory with a new version; the old one points at it
8662
+ --layer <layer> Layer for the new memory (default: the old memory's layer)
8663
+ --tag <tag> Tag for the new memory (repeatable; default: the old memory's tags)
8664
+ --pin Pin the new memory (default: pinned if the old one was)
8674
8665
  recall <query> Search and retrieve memories (local + global)
8675
8666
  --budget <n> Token budget (default: 4000)
8676
8667
  --min-results <n> Minimum results regardless of budget (default: 1)
@@ -8806,9 +8797,19 @@ Commands:
8806
8797
  dlq list List DLQ entries for the active tenant
8807
8798
  dlq replay <id> [--force]
8808
8799
  Re-ingest a DLQ entry (--force skips sig check)
8800
+ slack Slack connector subcommands (backfill, dlq, workspaces)
8801
+ backfill --channel <id> [--since ISO]
8802
+ Backfill a channel's history (needs SLACK_BOT_TOKEN)
8803
+ dlq list List DLQ entries for the active tenant
8804
+ dlq replay <id> [--force]
8805
+ Re-ingest a DLQ entry (--force skips sig check)
8806
+ workspaces <add|list|remove>
8807
+ Map Slack workspaces (team ids) to tenants
8809
8808
  provenance Provenance coverage gate for kind='raw' rows
8810
8809
  --json Output as JSON
8811
8810
  --strict Exit non-zero when coverage < 100%
8811
+ dag Show the summary tree: entity profiles, topic summaries, facts
8812
+ --stats Count memories per DAG level instead
8812
8813
  drill <summary-id> Walk down a DAG level-2 summary to its children
8813
8814
  --limit N Cap children list (default 50)
8814
8815
  --budget N Cap total child token cost (≈ chars/4)
@@ -8999,6 +9000,8 @@ Commands:
8999
9000
  last-sleep Print the last 'hippo sleep --log-file' output and clear it
9000
9001
  --path <p> Log path (default: ~/.hippo/logs/last-sleep.log)
9001
9002
  --keep Print without clearing
9003
+ session-end SessionEnd hook: run sleep, then capture this session, in a detached worker
9004
+ --log-file <path> Tee the worker's output to a log file (paired with 'hippo last-sleep')
9002
9005
  pre-compact PreCompact hook: save a working-state snapshot before compaction
9003
9006
  --log-file <p> Diagnostic log path (default: ~/.hippo/logs/pre-compact.log)
9004
9007
  compact-resume SessionStart(compact) hook: re-print the snapshot + session trail
@@ -9013,6 +9016,20 @@ Commands:
9013
9016
  SessionStart(compact) for mid-session continuity;
9014
9017
  codex wraps the detected launcher in place
9015
9018
  hook uninstall <target> Remove hook
9019
+ predict "<claim>" Record a prediction to score against the actual outcome later
9020
+ --class <c> Reference class (required)
9021
+ --estimate <v> Numeric estimate
9022
+ --unit <u> Unit of the estimate
9023
+ --target <YYYY-MM-DD> When the outcome is due
9024
+ predict close <id> Close a prediction
9025
+ --state <s> closed | closed-unknown (required)
9026
+ --actual <v> The actual value
9027
+ --note "<text>" Closure note
9028
+ predict list [--class <c>] [--status open|closed|closed-unknown|all] [--limit N]
9029
+ List predictions (closed and closed-unknown need --class)
9030
+ predict show <id> Show one prediction
9031
+ predict baserate --class <c>
9032
+ How past estimates in a class compared with the actuals
9016
9033
  decide "<decision>" Record a decision (first-class object + memory mirror)
9017
9034
  --context "<why>" Why this decision was made
9018
9035
  --supersedes <mem-id> Supersede the decision backed by this memory id
@@ -9124,6 +9141,9 @@ Commands:
9124
9141
  dashboard Open web dashboard for memory health
9125
9142
  --port <n> Port to serve on (default: 3333)
9126
9143
  mcp Start MCP server (stdio transport)
9144
+ serve Start the HTTP API server for this store (Ctrl+C stops it)
9145
+ --port <n> Port to serve on (default: $HIPPO_PORT or 6789)
9146
+ --host <host> Address to bind (default: 127.0.0.1)
9127
9147
  goal <sub> dlPFC goal stack (B3) — scoped per session
9128
9148
  goal push <name> Push a new active goal; prints the new goal id
9129
9149
  --policy <type> schema-fit-biased | error-prioritized |
@@ -9221,7 +9241,40 @@ Examples:
9221
9241
  hippo sleep --dry-run
9222
9242
  hippo outcome --good
9223
9243
  hippo status
9224
- `);
9244
+ `;
9245
+ }
9246
+ function printUsage() {
9247
+ console.log(usageText());
9248
+ }
9249
+ const USAGE_ALIASES = new Map([['project-brief', 'brief'], ['customer-note', 'note']]);
9250
+ // Cut from usageText() so a verb's help can never drift from the full listing.
9251
+ export function verbUsage(verb) {
9252
+ const name = USAGE_ALIASES.get(verb) ?? verb;
9253
+ const block = [];
9254
+ let inBlock = false;
9255
+ for (const line of usageText().split('\n')) {
9256
+ if (/^ {2}\S/.test(line))
9257
+ inBlock = line.trimStart().split(' ', 1)[0] === name;
9258
+ else if (!line.startsWith(' '))
9259
+ inBlock = false;
9260
+ if (inBlock)
9261
+ block.push(line);
9262
+ }
9263
+ return block.length > 0 ? block.join('\n') : null;
9264
+ }
9265
+ // These sub-commands have fuller usage text than their lines in usageText().
9266
+ const SUBCOMMAND_USAGE = new Map([
9267
+ ['audit prune', printAuditPruneUsage],
9268
+ ['slack backfill', printSlackBackfillUsage],
9269
+ ['slack workspaces', printSlackWorkspacesUsage],
9270
+ ['github backfill', printGithubBackfillUsage],
9271
+ ]);
9272
+ function printHelp(command, args) {
9273
+ const printSubcommandUsage = SUBCOMMAND_USAGE.get(`${command} ${args[0] ?? ''}`);
9274
+ if (printSubcommandUsage)
9275
+ printSubcommandUsage();
9276
+ else
9277
+ console.log(verbUsage(command) ?? usageText());
9225
9278
  }
9226
9279
  // ---------------------------------------------------------------------------
9227
9280
  // Entry point
@@ -9235,6 +9288,15 @@ async function main(command, args, flags, hippoRoot) {
9235
9288
  console.log(version);
9236
9289
  process.exit(0);
9237
9290
  }
9291
+ if (command === '' || command === 'help' || command === '--help' || command === '-h') {
9292
+ printUsage();
9293
+ return;
9294
+ }
9295
+ // Before every other step, so help never opens a store, installs a hook or starts a server.
9296
+ if (Object.hasOwn(flags, 'help')) {
9297
+ printHelp(command, args);
9298
+ return;
9299
+ }
9238
9300
  maybeRepairCodexWrapper(command, flags);
9239
9301
  /** Global --scope well-formedness guard (v1.26.2). parseArgs stores a value-less
9240
9302
  * flag as boolean true; downstream the 14 consumer sites either coerced that to
@@ -10092,12 +10154,6 @@ async function main(command, args, flags, hippoRoot) {
10092
10154
  case 'graph':
10093
10155
  cmdGraph(hippoRoot, args, flags);
10094
10156
  break;
10095
- case 'help':
10096
- case '--help':
10097
- case '-h':
10098
- case '':
10099
- printUsage();
10100
- break;
10101
10157
  default:
10102
10158
  console.error(`Unknown command: ${command}`);
10103
10159
  printUsage();
@@ -64,10 +64,6 @@ export function printGithubBackfillUsage() {
64
64
  * code path without hitting the network. Defaults to `realGitHubFetcher`.
65
65
  */
66
66
  export async function cmdGithubBackfill(hippoRoot, flags, fetcher = realGitHubFetcher) {
67
- if (flags['help']) {
68
- printGithubBackfillUsage();
69
- return;
70
- }
71
67
  const repo = flags['repo'];
72
68
  if (!isFlagString(repo) || !repo.includes('/')) {
73
69
  printGithubBackfillUsage();
@@ -224,35 +224,56 @@ const HOME_SPELLINGS = [
224
224
  fileUrlPath,
225
225
  (p) => p.replace(/[^A-Za-z0-9]/g, '-'),
226
226
  ];
227
- // A Windows separator can be written \, / or JSON-escaped (\\ in a log line quoting JSON), so any of them matches.
228
- function spellingPattern(spelling) {
227
+ // Git Bash, Cygwin and WSL mount a drive at /c, /cygdrive/c and /mnt/c; a Claude Code folder name writes each / and : as -.
228
+ function mountedDrive(letter) {
229
+ return `(?:${letter}[:-]|(?:[-/](?:cygdrive|mnt))?[-/]${letter})`;
230
+ }
231
+ /** Regex source for one spelling of the home, or null when too little of it is left to swap safely. */
232
+ function spellingPattern(spelling, deep) {
229
233
  if (process.platform !== 'win32')
230
- return escapeRegExp(spelling);
231
- return spelling.split(/[\\/]+/).map(escapeRegExp).join('(?:\\\\+|/)');
234
+ return spelling.length >= 3 ? escapeRegExp(spelling) : null;
235
+ // Each tool that mounts a drive writes it its own way, so a home two or more folders below its drive (\Users\<name>)
236
+ // matches after any prefix, with \, / or JSON's \\ between folders. A drive written a known way goes into the swap with it.
237
+ const drive = /^([A-Za-z])[:-]/.exec(spelling);
238
+ const below = drive === null ? spelling : spelling.slice(2);
239
+ const body = below.split(/[\\/]+/).map(escapeRegExp).join('[\\\\/]+');
240
+ if (drive === null) {
241
+ if (spelling.length < 3)
242
+ return null;
243
+ return deep ? body : `(?<![\\p{L}\\p{N}_])${body}`;
244
+ }
245
+ if (deep)
246
+ return `${mountedDrive(drive[1])}?${body}`;
247
+ // One folder (C:\x, D:\home) is a name any path may hold, so it swaps only after its drive and as a whole name; a drive
248
+ // root swaps nothing.
249
+ return /[^\\/-]/.test(below) ? `(?<![\\p{L}\\p{N}_])${mountedDrive(drive[1])}${body}` : null;
232
250
  }
233
251
  /** One pattern for every alias of the home in every spelling, or null when none is long enough to swap safely. */
234
252
  function buildHomePattern(home, probes) {
235
- const spellings = new Set();
253
+ const spellings = [];
236
254
  for (const alias of collectHomeAliases(home, probes)) {
237
- for (const spell of HOME_SPELLINGS) {
238
- const s = spell(alias);
239
- if (s.length >= 3)
240
- spellings.add(s);
241
- }
255
+ // Counted on the path, since its folder-name spelling cannot tell C:\a-b from C:\a\b.
256
+ const deep = alias.replace(/^[A-Za-z]:/, '').split(/[\\/]+/).filter(Boolean).length >= 2;
257
+ for (const spell of HOME_SPELLINGS)
258
+ spellings.push([spell(alias), deep]);
242
259
  }
243
260
  // Longest first: where one home nests in another (a test home under the real one), swap the longer.
244
261
  const patterns = new Map();
245
- for (const s of [...spellings].sort((a, b) => b.length - a.length)) {
246
- const pattern = spellingPattern(s);
262
+ for (const [s, deep] of spellings.sort(([a], [b]) => b.length - a.length)) {
263
+ const pattern = spellingPattern(s, deep);
264
+ if (pattern === null)
265
+ continue;
247
266
  const key = process.platform === 'win32' ? pattern.toLowerCase() : pattern;
248
267
  if (!patterns.has(key))
249
268
  patterns.set(key, pattern);
250
269
  }
251
270
  if (patterns.size === 0)
252
271
  return null;
253
- // Whole names only: "<home> now" and "<home>." swap; "<home>ty", or "web-app" for a home of /app, do not.
272
+ // Whole names only: "<home> now" and "<home>." swap; "<home>ty", or "web-app" for a home of /app, do not. On Windows
273
+ // the start is spellingPattern's call, because the path below the drive may follow anything.
274
+ const before = process.platform === 'win32' ? '' : '(?<![\\p{L}\\p{N}_])';
254
275
  const alternatives = [...patterns.values()].join('|');
255
- return new RegExp(`(?<![\\p{L}\\p{N}_])(?:${alternatives})(?![\\p{L}\\p{N}_])`, process.platform === 'win32' ? 'giu' : 'gu');
276
+ return new RegExp(`${before}(?:${alternatives})(?![\\p{L}\\p{N}_])`, process.platform === 'win32' ? 'giu' : 'gu');
256
277
  }
257
278
  const URL_RE = /\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/gi;
258
279
  function redactUrlsInString(text) {
package/dist/version.d.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  * an ESM `import` can resolve cleanly, and a hardcoded constant survives
17
17
  * any packager that drops .json files.
18
18
  */
19
- export declare const PACKAGE_VERSION = "1.52.4";
19
+ export declare const PACKAGE_VERSION = "1.52.6";
20
20
  /** Compares plain x.y.z versions, positive if a > b; tags throw so the rollback guard never misfires silently. */
21
21
  export declare function compareSemver(a: string, b: string): number;
22
22
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * an ESM `import` can resolve cleanly, and a hardcoded constant survives
17
17
  * any packager that drops .json files.
18
18
  */
19
- export const PACKAGE_VERSION = '1.52.4';
19
+ export const PACKAGE_VERSION = '1.52.6';
20
20
  /** Compares plain x.y.z versions, positive if a > b; tags throw so the rollback guard never misfires silently. */
21
21
  export function compareSemver(a, b) {
22
22
  const parse = (v) => {
@@ -2,7 +2,7 @@
2
2
  "id": "hippo-memory",
3
3
  "name": "Hippo Memory",
4
4
  "description": "Memory for AI agents that learns what is wrong and stops repeating it. Injects context at session start and captures errors.",
5
- "version": "1.52.4",
5
+ "version": "1.52.6",
6
6
 
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hippo-memory",
3
- "version": "1.52.4",
3
+ "version": "1.52.6",
4
4
  "type": "module",
5
5
  "description": "Hippo Memory plugin for OpenClaw - biologically-inspired agent memory",
6
6
  "main": "index.ts",
@@ -2,7 +2,7 @@
2
2
  "id": "hippo-memory",
3
3
  "name": "Hippo Memory",
4
4
  "description": "Memory for AI agents that learns what is wrong and stops repeating it. Injects context at session start and captures errors.",
5
- "version": "1.52.4",
5
+ "version": "1.52.6",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hippo-memory",
3
- "version": "1.52.4",
3
+ "version": "1.52.6",
4
4
  "description": "Memory for AI agents that learns what is wrong and stops repeating it. MCP server and hooks for Claude Code, Codex and Cursor. SQLite, zero runtime deps.",
5
5
  "mcpName": "io.github.kitfunso/hippo-memory",
6
6
  "type": "module",