hippo-memory 0.6.3 → 0.8.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 CHANGED
@@ -47,6 +47,7 @@ That's it. You have a memory system.
47
47
 
48
48
  - **SQLite-first storage** with markdown/JSON mirrors for humans and git
49
49
  - **Active task snapshots** for bare `continue` recovery
50
+ - **Session event trails** for short-term continuity across stops and resumes
50
51
  - **Persistent stale-memory lifecycle** during `hippo sleep`
51
52
  - **Conflict tracking** with `hippo conflicts` and `.hippo/conflicts/` mirrors
52
53
 
@@ -126,6 +127,29 @@ hippo snapshot clear
126
127
 
127
128
  `hippo context --auto` includes the active task snapshot before long-term memories, so agents get both the immediate thread and the deeper lessons.
128
129
 
130
+ ### Session event trails
131
+
132
+ Manual snapshots are useful, but real work also needs a breadcrumb trail. Hippo can now store short session events and link them to the active snapshot so context output shows the latest steps, not just the last summary.
133
+
134
+ ```bash
135
+ hippo session log \
136
+ --id sess_20260326 \
137
+ --task "Ship continuity" \
138
+ --type progress \
139
+ --content "Schema migration is done, next step is CLI wiring"
140
+
141
+ hippo snapshot save \
142
+ --task "Ship continuity" \
143
+ --summary "Structured session events are flowing" \
144
+ --next-step "Surface them in framework hooks" \
145
+ --session sess_20260326
146
+
147
+ hippo session show --id sess_20260326
148
+ hippo context --auto --budget 1500
149
+ ```
150
+
151
+ Hippo mirrors the latest trail to `.hippo/buffer/recent-session.md` so you can inspect the short-term thread without opening SQLite.
152
+
129
153
  ---
130
154
 
131
155
  ## How It Works
@@ -555,11 +579,9 @@ Issues and PRs welcome. Before contributing, run `hippo status` in the repo root
555
579
 
556
580
  The interesting problems:
557
581
  - Better consolidation heuristics (what makes a good semantic memory?)
558
- - Embedding-based search (currently BM25 only)
559
- - MCP server wrapper
560
- - Conflict detection between semantic memories
561
582
  - Schema acceleration (fast-track memories that fit existing patterns)
562
583
  - Multi-agent shared memory with attribution
584
+ - Benchmark eval: memory-augmented agent vs static memory vs no memory
563
585
 
564
586
  ## License
565
587
 
package/dist/cli.d.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  * hippo outcome --good | --bad [--id <id>]
12
12
  * hippo conflicts [--status <status>] [--json]
13
13
  * hippo snapshot <save|show|clear>
14
+ * hippo session <log|show>
14
15
  * hippo forget <id>
15
16
  * hippo inspect <id>
16
17
  * hippo embed [--status]
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;GAmBG"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG"}
package/dist/cli.js CHANGED
@@ -11,6 +11,7 @@
11
11
  * hippo outcome --good | --bad [--id <id>]
12
12
  * hippo conflicts [--status <status>] [--json]
13
13
  * hippo snapshot <save|show|clear>
14
+ * hippo session <log|show>
14
15
  * hippo forget <id>
15
16
  * hippo inspect <id>
16
17
  * hippo embed [--status]
@@ -22,13 +23,13 @@
22
23
  import * as path from 'path';
23
24
  import * as fs from 'fs';
24
25
  import { execSync } from 'child_process';
25
- import { createMemory, calculateStrength, resolveConfidence, applyOutcome, Layer, } from './memory.js';
26
- import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, clearActiveTaskSnapshot, listMemoryConflicts, } from './store.js';
27
- import { search, markRetrieved, estimateTokens } from './search.js';
26
+ import { createMemory, calculateStrength, deriveHalfLife, resolveConfidence, applyOutcome, computeSchemaFit, Layer, } from './memory.js';
27
+ import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, } from './store.js';
28
+ import { markRetrieved, estimateTokens, hybridSearch } from './search.js';
28
29
  import { consolidate } from './consolidate.js';
29
30
  import { isEmbeddingAvailable, embedAll, embedMemory, loadEmbeddingIndex, } from './embeddings.js';
30
31
  import { captureError, extractLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
31
- import { getGlobalRoot, initGlobal, promoteToGlobal, searchBoth, syncGlobalToLocal, } from './shared.js';
32
+ import { getGlobalRoot, initGlobal, promoteToGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
32
33
  import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, } from './importers.js';
33
34
  import { cmdCapture } from './capture.js';
34
35
  // ---------------------------------------------------------------------------
@@ -236,12 +237,16 @@ function cmdRemember(hippoRoot, text, flags) {
236
237
  confidence = 'inferred';
237
238
  if (flags['verified'])
238
239
  confidence = 'verified';
240
+ // Compute schema fit against existing memories
241
+ const existing = loadAllEntries(targetRoot);
242
+ const schemaFit = computeSchemaFit(text, rawTags, existing);
239
243
  const entry = createMemory(text, {
240
244
  layer: Layer.Episodic,
241
245
  tags: rawTags,
242
246
  pinned: Boolean(flags['pin']),
243
247
  source: useGlobal ? 'cli-global' : 'cli',
244
248
  confidence,
249
+ schema_fit: schemaFit,
245
250
  });
246
251
  writeEntry(targetRoot, entry);
247
252
  updateStats(targetRoot, { remembered: 1 });
@@ -259,7 +264,7 @@ function cmdRemember(hippoRoot, text, flags) {
259
264
  });
260
265
  }
261
266
  }
262
- function cmdRecall(hippoRoot, query, flags) {
267
+ async function cmdRecall(hippoRoot, query, flags) {
263
268
  requireInit(hippoRoot);
264
269
  const budget = parseInt(String(flags['budget'] ?? '4000'), 10);
265
270
  const asJson = Boolean(flags['json']);
@@ -269,12 +274,11 @@ function cmdRecall(hippoRoot, query, flags) {
269
274
  const hasGlobal = globalEntries.length > 0;
270
275
  let results;
271
276
  if (hasGlobal) {
272
- // Use searchBoth for merged results
273
- const merged = searchBoth(query, hippoRoot, globalRoot, { budget });
274
- results = merged;
277
+ // Use searchBothHybrid for merged results with embedding support
278
+ results = await searchBothHybrid(query, hippoRoot, globalRoot, { budget });
275
279
  }
276
280
  else {
277
- results = search(query, localEntries, { budget });
281
+ results = await hybridSearch(query, localEntries, { budget, hippoRoot });
278
282
  }
279
283
  if (results.length === 0) {
280
284
  if (asJson) {
@@ -287,16 +291,14 @@ function cmdRecall(hippoRoot, query, flags) {
287
291
  }
288
292
  // Update retrieval metadata and persist
289
293
  const updated = markRetrieved(results.map((r) => r.entry));
294
+ const localIndex = loadIndex(hippoRoot);
290
295
  for (const u of updated) {
291
- // Determine which store this entry belongs to
292
- const localIndex = loadIndex(hippoRoot);
293
296
  const targetRoot = localIndex.entries[u.id] ? hippoRoot : (isInitialized(globalRoot) ? globalRoot : hippoRoot);
294
297
  writeEntry(targetRoot, u);
295
298
  }
296
299
  // Track last retrieval IDs for outcome command
297
- const index = loadIndex(hippoRoot);
298
- index.last_retrieval_ids = updated.map((u) => u.id);
299
- saveIndex(hippoRoot, index);
300
+ localIndex.last_retrieval_ids = updated.map((u) => u.id);
301
+ saveIndex(hippoRoot, localIndex);
300
302
  updateStats(hippoRoot, { recalled: results.length });
301
303
  if (asJson) {
302
304
  const output = results.map((r) => ({
@@ -494,6 +496,9 @@ function printActiveTaskSnapshot(snapshot) {
494
496
  console.log(`- Status: ${snapshot.status}`);
495
497
  console.log(`- Updated: ${snapshot.updated_at}`);
496
498
  console.log(`- Source: ${snapshot.source}`);
499
+ if (snapshot.session_id) {
500
+ console.log(`- Session: ${snapshot.session_id}`);
501
+ }
497
502
  console.log('');
498
503
  console.log('### Summary');
499
504
  console.log(snapshot.summary);
@@ -502,6 +507,22 @@ function printActiveTaskSnapshot(snapshot) {
502
507
  console.log(snapshot.next_step);
503
508
  console.log('');
504
509
  }
510
+ function printSessionEvents(events) {
511
+ if (events.length === 0) {
512
+ console.log('No session events found.');
513
+ return;
514
+ }
515
+ const latest = events[events.length - 1];
516
+ console.log('## Recent Session Trail\n');
517
+ console.log(`- Session: ${latest.session_id}`);
518
+ console.log(`- Task: ${latest.task ?? 'n/a'}`);
519
+ console.log(`- Updated: ${latest.created_at}`);
520
+ console.log('');
521
+ for (const event of events) {
522
+ console.log(`- [${event.created_at}] (${event.event_type}) ${event.content}`);
523
+ }
524
+ console.log('');
525
+ }
505
526
  function cmdConflicts(hippoRoot, flags) {
506
527
  requireInit(hippoRoot);
507
528
  const conflicts = listMemoryConflicts(hippoRoot, String(flags['status'] ?? 'open'));
@@ -521,6 +542,51 @@ function cmdConflicts(hippoRoot, flags) {
521
542
  console.log('');
522
543
  }
523
544
  }
545
+ function cmdResolve(hippoRoot, args, flags) {
546
+ requireInit(hippoRoot);
547
+ const rawId = args[0] ?? '';
548
+ // Accept "42" or "conflict_42"
549
+ const conflictId = parseInt(rawId.replace(/^conflict_/, ''), 10);
550
+ if (isNaN(conflictId)) {
551
+ console.error('Usage: hippo resolve <conflict_id> --keep <memory_id> [--forget]');
552
+ process.exit(1);
553
+ }
554
+ const keepId = String(flags['keep'] ?? '').trim();
555
+ if (!keepId) {
556
+ // Show the conflict details to help the user decide
557
+ const conflicts = listMemoryConflicts(hippoRoot, 'open');
558
+ const conflict = conflicts.find((c) => c.id === conflictId);
559
+ if (!conflict) {
560
+ console.error(`Conflict ${conflictId} not found or already resolved.`);
561
+ process.exit(1);
562
+ }
563
+ console.log(`Conflict ${conflictId}:`);
564
+ console.log(` ${conflict.memory_a_id} <-> ${conflict.memory_b_id}`);
565
+ console.log(` Reason: ${conflict.reason}`);
566
+ console.log('');
567
+ const entryA = readEntry(hippoRoot, conflict.memory_a_id);
568
+ const entryB = readEntry(hippoRoot, conflict.memory_b_id);
569
+ if (entryA) {
570
+ console.log(` [A] ${conflict.memory_a_id}:`);
571
+ console.log(` ${entryA.content.slice(0, 120)}${entryA.content.length > 120 ? '...' : ''}`);
572
+ }
573
+ if (entryB) {
574
+ console.log(` [B] ${conflict.memory_b_id}:`);
575
+ console.log(` ${entryB.content.slice(0, 120)}${entryB.content.length > 120 ? '...' : ''}`);
576
+ }
577
+ console.log('');
578
+ console.log(`Resolve with: hippo resolve ${conflictId} --keep <memory_id> [--forget]`);
579
+ return;
580
+ }
581
+ const forgetLoser = Boolean(flags['forget']);
582
+ const result = resolveConflict(hippoRoot, conflictId, keepId, forgetLoser);
583
+ if (!result) {
584
+ console.error(`Could not resolve conflict ${conflictId}. Check the ID and --keep value.`);
585
+ process.exit(1);
586
+ }
587
+ const action = forgetLoser ? 'deleted' : 'weakened (half-life halved)';
588
+ console.log(`Resolved conflict ${conflictId}: kept ${keepId}, ${action} ${result.loserId}`);
589
+ }
524
590
  function cmdSnapshot(hippoRoot, args, flags) {
525
591
  requireInit(hippoRoot);
526
592
  const subcommand = args[0] ?? 'show';
@@ -528,8 +594,9 @@ function cmdSnapshot(hippoRoot, args, flags) {
528
594
  const task = String(flags['task'] ?? '').trim();
529
595
  const summary = String(flags['summary'] ?? '').trim();
530
596
  const nextStep = String(flags['next-step'] ?? '').trim();
597
+ const sessionId = String(flags['session'] ?? flags['id'] ?? '').trim();
531
598
  if (!task || !summary || !nextStep) {
532
- console.error('Usage: hippo snapshot save --task <task> --summary <summary> --next-step <step> [--source <source>]');
599
+ console.error('Usage: hippo snapshot save --task <task> --summary <summary> --next-step <step> [--source <source>] [--session <session-id>]');
533
600
  process.exit(1);
534
601
  }
535
602
  const snapshot = saveActiveTaskSnapshot(hippoRoot, {
@@ -537,10 +604,14 @@ function cmdSnapshot(hippoRoot, args, flags) {
537
604
  summary,
538
605
  next_step: nextStep,
539
606
  source: String(flags['source'] ?? 'cli'),
607
+ session_id: sessionId || null,
540
608
  });
541
609
  console.log(`Saved active task snapshot #${snapshot.id}`);
542
610
  console.log(` Task: ${snapshot.task}`);
543
611
  console.log(` Next: ${snapshot.next_step}`);
612
+ if (snapshot.session_id) {
613
+ console.log(` Session: ${snapshot.session_id}`);
614
+ }
544
615
  return;
545
616
  }
546
617
  if (subcommand === 'clear') {
@@ -573,7 +644,48 @@ function cmdSnapshot(hippoRoot, args, flags) {
573
644
  console.error('Usage: hippo snapshot <save|show|clear>');
574
645
  process.exit(1);
575
646
  }
576
- function cmdContext(hippoRoot, args, flags) {
647
+ function cmdSession(hippoRoot, args, flags) {
648
+ requireInit(hippoRoot);
649
+ const subcommand = args[0] ?? 'show';
650
+ const sessionId = String(flags['id'] ?? flags['session'] ?? '').trim();
651
+ const task = String(flags['task'] ?? '').trim();
652
+ const limit = Math.max(1, parseInt(String(flags['limit'] ?? '8'), 10) || 8);
653
+ if (subcommand === 'log') {
654
+ const eventType = String(flags['type'] ?? 'note').trim();
655
+ const content = String(flags['content'] ?? '').trim();
656
+ if (!sessionId || !content) {
657
+ console.error('Usage: hippo session log --id <session-id> --content <text> [--type <type>] [--task <task>] [--source <source>]');
658
+ process.exit(1);
659
+ }
660
+ const event = appendSessionEvent(hippoRoot, {
661
+ session_id: sessionId,
662
+ task: task || null,
663
+ event_type: eventType || 'note',
664
+ content,
665
+ source: String(flags['source'] ?? 'cli'),
666
+ });
667
+ console.log(`Logged session event #${event.id}`);
668
+ console.log(` Session: ${event.session_id}`);
669
+ console.log(` Type: ${event.event_type}`);
670
+ return;
671
+ }
672
+ if (subcommand === 'show') {
673
+ const events = listSessionEvents(hippoRoot, {
674
+ session_id: sessionId || undefined,
675
+ task: task || undefined,
676
+ limit,
677
+ });
678
+ if (flags['json']) {
679
+ console.log(JSON.stringify({ events }, null, 2));
680
+ return;
681
+ }
682
+ printSessionEvents(events);
683
+ return;
684
+ }
685
+ console.error('Usage: hippo session <log|show>');
686
+ process.exit(1);
687
+ }
688
+ async function cmdContext(hippoRoot, args, flags) {
577
689
  requireInit(hippoRoot);
578
690
  const budget = parseInt(String(flags['budget'] ?? '1500'), 10);
579
691
  // If budget is 0, skip entirely (zero token cost)
@@ -598,6 +710,9 @@ function cmdContext(hippoRoot, args, flags) {
598
710
  let selectedItems = [];
599
711
  let totalTokens = 0;
600
712
  const activeSnapshot = loadActiveTaskSnapshot(hippoRoot);
713
+ const recentSessionEvents = activeSnapshot?.session_id
714
+ ? listSessionEvents(hippoRoot, { session_id: activeSnapshot.session_id, limit: 5 })
715
+ : [];
601
716
  if (query === '*') {
602
717
  // No query: return strongest memories by strength, up to budget
603
718
  const now = new Date();
@@ -630,7 +745,7 @@ function cmdContext(hippoRoot, args, flags) {
630
745
  else {
631
746
  let results;
632
747
  if (hasGlobal) {
633
- const merged = searchBoth(query, hippoRoot, globalRoot, { budget });
748
+ const merged = await searchBothHybrid(query, hippoRoot, globalRoot, { budget });
634
749
  const localIndex = loadIndex(hippoRoot);
635
750
  results = merged.map((r) => ({
636
751
  entry: r.entry,
@@ -640,7 +755,7 @@ function cmdContext(hippoRoot, args, flags) {
640
755
  }));
641
756
  }
642
757
  else {
643
- results = search(query, localEntries, { budget }).map((r) => ({
758
+ results = (await hybridSearch(query, localEntries, { budget, hippoRoot })).map((r) => ({
644
759
  entry: r.entry,
645
760
  score: r.score,
646
761
  tokens: r.tokens,
@@ -650,7 +765,7 @@ function cmdContext(hippoRoot, args, flags) {
650
765
  selectedItems = results;
651
766
  totalTokens = results.reduce((sum, r) => sum + r.tokens, 0);
652
767
  }
653
- if (selectedItems.length === 0 && !activeSnapshot)
768
+ if (selectedItems.length === 0 && !activeSnapshot && recentSessionEvents.length === 0)
654
769
  return;
655
770
  // Mark retrieved and persist
656
771
  const toUpdate = selectedItems.map((s) => s.entry);
@@ -675,12 +790,15 @@ function cmdContext(hippoRoot, args, flags) {
675
790
  content: r.entry.content,
676
791
  global: r.isGlobal ?? false,
677
792
  }));
678
- console.log(JSON.stringify({ query, activeSnapshot, memories: output, tokens: totalTokens }));
793
+ console.log(JSON.stringify({ query, activeSnapshot, recentSessionEvents, memories: output, tokens: totalTokens }));
679
794
  }
680
795
  else {
681
796
  if (activeSnapshot) {
682
797
  printActiveTaskSnapshot(activeSnapshot);
683
798
  }
799
+ if (recentSessionEvents.length > 0) {
800
+ printSessionEvents(recentSessionEvents);
801
+ }
684
802
  printContextMarkdown(selectedItems.map((r) => ({
685
803
  entry: updatedEntries.find((u) => u.id === r.entry.id) ?? r.entry,
686
804
  score: r.score,
@@ -799,6 +917,12 @@ async function cmdWatch(command, hippoRoot) {
799
917
  process.exit(exitCode);
800
918
  }
801
919
  const entry = captureError(exitCode, stderr, command);
920
+ // Compute schema fit against existing memories
921
+ const existingWatch = loadAllEntries(hippoRoot);
922
+ const watchFit = computeSchemaFit(entry.content, entry.tags, existingWatch);
923
+ entry.schema_fit = watchFit;
924
+ entry.half_life_days = deriveHalfLife(7, entry);
925
+ entry.strength = calculateStrength(entry);
802
926
  writeEntry(hippoRoot, entry);
803
927
  updateStats(hippoRoot, { remembered: 1 });
804
928
  if (isEmbeddingAvailable()) {
@@ -829,16 +953,20 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
829
953
  }
830
954
  let added = 0;
831
955
  let skipped = 0;
956
+ const gitLearnTags = ['error', 'git-learned'];
957
+ const existingForSchema = loadAllEntries(hippoRoot);
832
958
  for (const lesson of lessons) {
833
959
  if (deduplicateLesson(hippoRoot, lesson)) {
834
960
  skipped++;
835
961
  continue;
836
962
  }
963
+ const schemaFitVal = computeSchemaFit(lesson, gitLearnTags, existingForSchema);
837
964
  const entry = createMemory(lesson, {
838
965
  layer: Layer.Episodic,
839
- tags: ['error', 'git-learned'],
966
+ tags: gitLearnTags,
840
967
  source: 'git-learn',
841
968
  confidence: 'observed',
969
+ schema_fit: schemaFitVal,
842
970
  });
843
971
  writeEntry(hippoRoot, entry);
844
972
  updateStats(hippoRoot, { remembered: 1 });
@@ -1215,16 +1343,32 @@ Commands:
1215
1343
  conflicts List detected open memory conflicts
1216
1344
  --status <status> Filter by status (default: open)
1217
1345
  --json Output as JSON
1346
+ resolve <conflict_id> Resolve a memory conflict
1347
+ --keep <memory_id> Memory to keep (required)
1348
+ --forget Delete the losing memory (default: halve half-life)
1218
1349
  snapshot <sub> Persist or inspect the current active task
1219
1350
  snapshot save Save active task state
1220
1351
  --task <task>
1221
1352
  --summary <summary>
1222
1353
  --next-step <step>
1223
1354
  --source <source> Optional source label
1355
+ --session <id> Link snapshot to a session trail
1224
1356
  snapshot show Show the active task snapshot
1225
1357
  --json Output as JSON
1226
1358
  snapshot clear Clear the active task snapshot
1227
1359
  --status <status> Mark final status (default: cleared)
1360
+ session <sub> Append or inspect short-term session history
1361
+ session log Append a structured session event
1362
+ --id <session-id>
1363
+ --content <text>
1364
+ --type <type> Event type (default: note)
1365
+ --task <task> Optional task label
1366
+ --source <source> Optional source label
1367
+ session show Show recent events for a session or task
1368
+ --id <session-id>
1369
+ --task <task>
1370
+ --limit <n> Event limit (default: 8)
1371
+ --json Output as JSON
1228
1372
  forget <id> Force remove a memory
1229
1373
  inspect <id> Show full memory detail
1230
1374
  embed Embed all memories for semantic search
@@ -1235,6 +1379,12 @@ Commands:
1235
1379
  --days <n> Scan this many days back (default: 7)
1236
1380
  --repos <paths> Comma-separated repo paths to scan
1237
1381
  promote <id> Copy a local memory to the global store
1382
+ share <id> Share a memory with attribution + transfer scoring
1383
+ --force Share even if transfer score is low
1384
+ --auto Auto-share all high-transfer-score memories
1385
+ --dry-run Preview what would be shared
1386
+ --min-score <n> Minimum transfer score (default: 0.6)
1387
+ peers List projects contributing to global store
1238
1388
  sync Pull global memories into local project
1239
1389
  import Import memories from other AI tools
1240
1390
  --chatgpt <path> Import from ChatGPT memory export (JSON or txt)
@@ -1263,7 +1413,8 @@ Examples:
1263
1413
  hippo recall "data pipeline issues" --budget 2000
1264
1414
  hippo context --auto --budget 1500
1265
1415
  hippo conflicts
1266
- hippo snapshot save --task "Ship feature" --summary "Tests are green" --next-step "Open the PR"
1416
+ hippo session log --id sess_123 --task "Ship feature" --type progress --content "Build is green, next step is docs"
1417
+ hippo snapshot save --task "Ship feature" --summary "Tests are green" --next-step "Open the PR" --session sess_123
1267
1418
  hippo embed --status
1268
1419
  hippo watch "npm run build"
1269
1420
  hippo learn --git --days 30
@@ -1300,7 +1451,7 @@ async function main() {
1300
1451
  console.error('Please provide a search query.');
1301
1452
  process.exit(1);
1302
1453
  }
1303
- cmdRecall(hippoRoot, query, flags);
1454
+ await cmdRecall(hippoRoot, query, flags);
1304
1455
  break;
1305
1456
  }
1306
1457
  case 'sleep':
@@ -1315,9 +1466,15 @@ async function main() {
1315
1466
  case 'conflicts':
1316
1467
  cmdConflicts(hippoRoot, flags);
1317
1468
  break;
1469
+ case 'resolve':
1470
+ cmdResolve(hippoRoot, args, flags);
1471
+ break;
1318
1472
  case 'snapshot':
1319
1473
  cmdSnapshot(hippoRoot, args, flags);
1320
1474
  break;
1475
+ case 'session':
1476
+ cmdSession(hippoRoot, args, flags);
1477
+ break;
1321
1478
  case 'forget': {
1322
1479
  const id = args[0];
1323
1480
  if (!id) {
@@ -1337,7 +1494,7 @@ async function main() {
1337
1494
  break;
1338
1495
  }
1339
1496
  case 'context':
1340
- cmdContext(hippoRoot, args, flags);
1497
+ await cmdContext(hippoRoot, args, flags);
1341
1498
  break;
1342
1499
  case 'hook':
1343
1500
  cmdHook(args, flags);
@@ -1365,6 +1522,71 @@ async function main() {
1365
1522
  case 'sync':
1366
1523
  cmdSync(hippoRoot);
1367
1524
  break;
1525
+ case 'share': {
1526
+ const shareId = args[0];
1527
+ if (shareId === '--auto' || flags['auto']) {
1528
+ // Auto-share mode
1529
+ requireInit(hippoRoot);
1530
+ const minScore = parseFloat(String(flags['min-score'] ?? '0.6'));
1531
+ const dryRun = Boolean(flags['dry-run']);
1532
+ const results = autoShare(hippoRoot, { minScore, dryRun });
1533
+ if (results.length === 0) {
1534
+ console.log('No memories meet the sharing threshold.');
1535
+ }
1536
+ else if (dryRun) {
1537
+ console.log(`Would share ${results.length} memories:\n`);
1538
+ for (const e of results) {
1539
+ const score = transferScore(e);
1540
+ console.log(` ${e.id} (transfer=${fmt(score)}) ${e.content.slice(0, 80)}...`);
1541
+ }
1542
+ }
1543
+ else {
1544
+ console.log(`Shared ${results.length} memories to global store.`);
1545
+ for (const e of results) {
1546
+ console.log(` ${e.id} <- ${e.source}`);
1547
+ }
1548
+ }
1549
+ }
1550
+ else if (shareId) {
1551
+ requireInit(hippoRoot);
1552
+ const force = Boolean(flags['force']);
1553
+ const result = shareMemory(hippoRoot, shareId, { force });
1554
+ if (result) {
1555
+ console.log(`Shared [${result.id}] to global store.`);
1556
+ console.log(` Source: ${result.source}`);
1557
+ }
1558
+ else {
1559
+ const entry = readEntry(hippoRoot, shareId);
1560
+ if (entry) {
1561
+ const score = transferScore(entry);
1562
+ console.log(`Transfer score too low (${fmt(score)}). This memory looks project-specific.`);
1563
+ console.log('Use --force to share anyway.');
1564
+ }
1565
+ else {
1566
+ console.error(`Memory not found: ${shareId}`);
1567
+ process.exit(1);
1568
+ }
1569
+ }
1570
+ }
1571
+ else {
1572
+ console.error('Usage: hippo share <memory_id> [--force] or hippo share --auto [--dry-run]');
1573
+ process.exit(1);
1574
+ }
1575
+ break;
1576
+ }
1577
+ case 'peers': {
1578
+ const peers = listPeers();
1579
+ if (peers.length === 0) {
1580
+ console.log('No peers found. Share memories with: hippo share <id>');
1581
+ }
1582
+ else {
1583
+ console.log(`${peers.length} project${peers.length === 1 ? '' : 's'} contributing to global store:\n`);
1584
+ for (const p of peers) {
1585
+ console.log(` ${p.project.padEnd(25)} ${String(p.count).padStart(4)} memories (latest: ${p.latest.slice(0, 10)})`);
1586
+ }
1587
+ }
1588
+ break;
1589
+ }
1368
1590
  case 'import':
1369
1591
  cmdImport(hippoRoot, args, flags);
1370
1592
  break;