hippo-memory 0.8.1 → 0.9.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/dist/cli.js CHANGED
@@ -5,13 +5,15 @@
5
5
  * Commands:
6
6
  * hippo init [--global]
7
7
  * hippo remember <text> [--tag <t>] [--error] [--pin] [--global]
8
- * hippo recall <query> [--budget <n>] [--json]
8
+ * hippo recall <query> [--budget <n>] [--json] [--why]
9
9
  * hippo sleep [--dry-run]
10
10
  * hippo status
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
+ * hippo session <log|show|latest|resume>
15
+ * hippo handoff <create|latest|show>
16
+ * hippo current <show>
15
17
  * hippo forget <id>
16
18
  * hippo inspect <id>
17
19
  * hippo embed [--status]
@@ -19,22 +21,30 @@
19
21
  * hippo learn --git [--days <n>] [--repos <paths>]
20
22
  * hippo promote <id>
21
23
  * hippo sync
24
+ * hippo wm <push|read|clear|flush>
22
25
  */
23
26
  import * as path from 'path';
24
27
  import * as fs from 'fs';
25
28
  import { execSync } from 'child_process';
26
29
  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';
30
+ import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, } from './store.js';
31
+ import { markRetrieved, estimateTokens, hybridSearch, explainMatch } from './search.js';
29
32
  import { consolidate } from './consolidate.js';
30
33
  import { isEmbeddingAvailable, embedAll, embedMemory, loadEmbeddingIndex, } from './embeddings.js';
31
34
  import { captureError, extractLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
32
35
  import { getGlobalRoot, initGlobal, promoteToGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
33
36
  import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, } from './importers.js';
34
37
  import { cmdCapture } from './capture.js';
38
+ import { wmPush, wmRead, wmClear, wmFlush } from './working-memory.js';
35
39
  // ---------------------------------------------------------------------------
36
40
  // Helpers
37
41
  // ---------------------------------------------------------------------------
42
+ function parseLimitFlag(value) {
43
+ if (!value)
44
+ return Infinity;
45
+ const parsed = parseInt(String(value), 10);
46
+ return Number.isFinite(parsed) && parsed >= 1 ? parsed : Infinity;
47
+ }
38
48
  function requireInit(hippoRoot) {
39
49
  if (!isInitialized(hippoRoot)) {
40
50
  console.error('No .hippo directory found. Run `hippo init` first.');
@@ -57,8 +67,8 @@ function parseArgs(argv) {
57
67
  i++;
58
68
  }
59
69
  else {
60
- // Check if it's a repeatable flag (tag)
61
- if (key === 'tag') {
70
+ // Check if it's a repeatable flag (tag, artifact)
71
+ if (key === 'tag' || key === 'artifact') {
62
72
  if (Array.isArray(flags[key])) {
63
73
  flags[key].push(next);
64
74
  }
@@ -267,7 +277,9 @@ function cmdRemember(hippoRoot, text, flags) {
267
277
  async function cmdRecall(hippoRoot, query, flags) {
268
278
  requireInit(hippoRoot);
269
279
  const budget = parseInt(String(flags['budget'] ?? '4000'), 10);
280
+ const limit = parseLimitFlag(flags['limit']);
270
281
  const asJson = Boolean(flags['json']);
282
+ const showWhy = Boolean(flags['why']);
271
283
  const globalRoot = getGlobalRoot();
272
284
  const localEntries = loadSearchEntries(hippoRoot, query);
273
285
  const globalEntries = isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query) : [];
@@ -280,6 +292,9 @@ async function cmdRecall(hippoRoot, query, flags) {
280
292
  else {
281
293
  results = await hybridSearch(query, localEntries, { budget, hippoRoot });
282
294
  }
295
+ if (limit < results.length) {
296
+ results = results.slice(0, limit);
297
+ }
283
298
  if (results.length === 0) {
284
299
  if (asJson) {
285
300
  console.log(JSON.stringify({ query, results: [], total: 0 }));
@@ -301,14 +316,27 @@ async function cmdRecall(hippoRoot, query, flags) {
301
316
  saveIndex(hippoRoot, localIndex);
302
317
  updateStats(hippoRoot, { recalled: results.length });
303
318
  if (asJson) {
304
- const output = results.map((r) => ({
305
- id: r.entry.id,
306
- score: r.score,
307
- strength: r.entry.strength,
308
- tokens: r.tokens,
309
- tags: r.entry.tags,
310
- content: r.entry.content,
311
- }));
319
+ const output = results.map((r) => {
320
+ const isGlobal = isInitialized(globalRoot) && !localIndex.entries[r.entry.id];
321
+ const base = {
322
+ id: r.entry.id,
323
+ score: r.score,
324
+ strength: r.entry.strength,
325
+ tokens: r.tokens,
326
+ tags: r.entry.tags,
327
+ content: r.entry.content,
328
+ };
329
+ if (showWhy) {
330
+ const explanation = explainMatch(query, r);
331
+ base.layer = r.entry.layer;
332
+ base.confidence = resolveConfidence(r.entry);
333
+ base.source = isGlobal ? 'global' : 'local';
334
+ base.reason = explanation.reason;
335
+ base.bm25 = r.bm25;
336
+ base.cosine = r.cosine;
337
+ }
338
+ return base;
339
+ });
312
340
  console.log(JSON.stringify({ query, budget, results: output, total: output.length }));
313
341
  return;
314
342
  }
@@ -319,9 +347,16 @@ async function cmdRecall(hippoRoot, query, flags) {
319
347
  const conf = resolveConfidence(e);
320
348
  const confLabel = conf === 'stale' || conf === 'inferred' ? `[${conf}] \u26A0\uFE0F` : `[${conf}]`;
321
349
  const strengthBar = '\u2588'.repeat(Math.round(e.strength * 10)) + '\u2591'.repeat(10 - Math.round(e.strength * 10));
322
- const globalMark = (isInitialized(globalRoot) && !loadIndex(hippoRoot).entries[e.id]) ? ' [global]' : '';
350
+ const isGlobal = isInitialized(globalRoot) && !localIndex.entries[e.id];
351
+ const globalMark = isGlobal ? ' [global]' : '';
352
+ const sourceMark = isGlobal ? ' [global]' : ' [local]';
323
353
  console.log(`--- ${e.id} [${e.layer}] ${confLabel}${globalMark} score=${fmt(r.score, 3)} strength=${fmt(e.strength)}`);
324
354
  console.log(` [${strengthBar}] tags: ${e.tags.join(', ') || 'none'} | retrieved: ${e.retrieval_count}x`);
355
+ if (showWhy) {
356
+ const explanation = explainMatch(query, r);
357
+ console.log(` source:${sourceMark} | layer: [${e.layer}] | confidence: [${conf}]`);
358
+ console.log(` reason: ${explanation.reason}`);
359
+ }
325
360
  console.log();
326
361
  console.log(e.content);
327
362
  console.log();
@@ -682,12 +717,226 @@ function cmdSession(hippoRoot, args, flags) {
682
717
  printSessionEvents(events);
683
718
  return;
684
719
  }
685
- console.error('Usage: hippo session <log|show>');
720
+ if (subcommand === 'latest') {
721
+ const snapshot = loadActiveTaskSnapshot(hippoRoot);
722
+ const events = listSessionEvents(hippoRoot, {
723
+ session_id: sessionId || snapshot?.session_id || undefined,
724
+ limit,
725
+ });
726
+ if (flags['json']) {
727
+ console.log(JSON.stringify({ snapshot: snapshot ?? null, events }, null, 2));
728
+ return;
729
+ }
730
+ if (snapshot) {
731
+ printActiveTaskSnapshot(snapshot);
732
+ }
733
+ else {
734
+ console.log('No active task snapshot.');
735
+ console.log('');
736
+ }
737
+ printSessionEvents(events);
738
+ return;
739
+ }
740
+ if (subcommand === 'resume') {
741
+ const handoff = loadLatestHandoff(hippoRoot, sessionId || undefined);
742
+ if (!handoff) {
743
+ console.log('No handoff to resume from.');
744
+ return;
745
+ }
746
+ const lines = [
747
+ '## Session Handoff (resumed)',
748
+ '',
749
+ `- Session: ${handoff.sessionId}`,
750
+ `- Updated: ${handoff.updatedAt}`,
751
+ ];
752
+ if (handoff.taskId)
753
+ lines.push(`- Task: ${handoff.taskId}`);
754
+ if (handoff.repoRoot)
755
+ lines.push(`- Repo: ${handoff.repoRoot}`);
756
+ lines.push('', '### Summary', handoff.summary);
757
+ if (handoff.nextAction) {
758
+ lines.push('', '### Next action', handoff.nextAction);
759
+ }
760
+ if (handoff.artifacts && handoff.artifacts.length > 0) {
761
+ lines.push('', '### Artifacts');
762
+ for (const artifact of handoff.artifacts) {
763
+ lines.push(`- ${artifact}`);
764
+ }
765
+ }
766
+ lines.push('');
767
+ console.log(lines.join('\n'));
768
+ return;
769
+ }
770
+ console.error('Usage: hippo session <log|show|latest|resume>');
771
+ process.exit(1);
772
+ }
773
+ function printHandoff(handoff) {
774
+ console.log('## Session Handoff\n');
775
+ console.log(`- Session: ${handoff.sessionId}`);
776
+ console.log(`- Updated: ${handoff.updatedAt}`);
777
+ if (handoff.taskId)
778
+ console.log(`- Task: ${handoff.taskId}`);
779
+ if (handoff.repoRoot)
780
+ console.log(`- Repo: ${handoff.repoRoot}`);
781
+ console.log('');
782
+ console.log('### Summary');
783
+ console.log(handoff.summary);
784
+ if (handoff.nextAction) {
785
+ console.log('');
786
+ console.log('### Next action');
787
+ console.log(handoff.nextAction);
788
+ }
789
+ if (handoff.artifacts && handoff.artifacts.length > 0) {
790
+ console.log('');
791
+ console.log('### Artifacts');
792
+ for (const artifact of handoff.artifacts) {
793
+ console.log(`- ${artifact}`);
794
+ }
795
+ }
796
+ console.log('');
797
+ }
798
+ function cmdHandoff(hippoRoot, args, flags) {
799
+ requireInit(hippoRoot);
800
+ const subcommand = args[0] ?? 'latest';
801
+ if (subcommand === 'create') {
802
+ const summary = String(flags['summary'] ?? '').trim();
803
+ if (!summary) {
804
+ console.error('Usage: hippo handoff create --summary "..." [--next "..."] [--session <id>] [--task <id>] [--artifact <path>...]');
805
+ process.exit(1);
806
+ }
807
+ const sessionId = String(flags['session'] ?? flags['id'] ?? '').trim() || `fallback-${Date.now()}-${process.pid}`;
808
+ const nextAction = String(flags['next'] ?? '').trim() || undefined;
809
+ const taskId = String(flags['task'] ?? '').trim() || undefined;
810
+ const artifactFlag = flags['artifact'];
811
+ const artifacts = Array.isArray(artifactFlag)
812
+ ? artifactFlag
813
+ : (typeof artifactFlag === 'string' ? [artifactFlag] : []);
814
+ const handoff = saveSessionHandoff(hippoRoot, {
815
+ version: 1,
816
+ sessionId,
817
+ repoRoot: process.cwd(),
818
+ taskId,
819
+ summary,
820
+ nextAction,
821
+ artifacts,
822
+ });
823
+ console.log(`Created session handoff for session ${handoff.sessionId}`);
824
+ console.log(` Summary: ${handoff.summary}`);
825
+ if (handoff.nextAction)
826
+ console.log(` Next: ${handoff.nextAction}`);
827
+ if (handoff.artifacts && handoff.artifacts.length > 0) {
828
+ console.log(` Artifacts: ${handoff.artifacts.join(', ')}`);
829
+ }
830
+ return;
831
+ }
832
+ if (subcommand === 'latest') {
833
+ const sessionId = String(flags['session'] ?? flags['id'] ?? '').trim() || undefined;
834
+ const handoff = loadLatestHandoff(hippoRoot, sessionId);
835
+ if (!handoff) {
836
+ if (flags['json']) {
837
+ console.log(JSON.stringify({ handoff: null }));
838
+ }
839
+ else {
840
+ console.log('No session handoff found.');
841
+ }
842
+ return;
843
+ }
844
+ if (flags['json']) {
845
+ console.log(JSON.stringify({ handoff }, null, 2));
846
+ return;
847
+ }
848
+ printHandoff(handoff);
849
+ return;
850
+ }
851
+ if (subcommand === 'show') {
852
+ const idArg = args[1];
853
+ if (!idArg) {
854
+ console.error('Usage: hippo handoff show <id> [--json]');
855
+ process.exit(1);
856
+ }
857
+ const handoffId = parseInt(idArg, 10);
858
+ if (!Number.isFinite(handoffId) || handoffId <= 0) {
859
+ console.error(`Invalid handoff ID: ${idArg}`);
860
+ process.exit(1);
861
+ }
862
+ const handoff = loadHandoffById(hippoRoot, handoffId);
863
+ if (!handoff) {
864
+ if (flags['json']) {
865
+ console.log(JSON.stringify({ handoff: null }));
866
+ }
867
+ else {
868
+ console.log(`No handoff found with ID ${handoffId}.`);
869
+ }
870
+ return;
871
+ }
872
+ if (flags['json']) {
873
+ console.log(JSON.stringify({ handoff }, null, 2));
874
+ return;
875
+ }
876
+ printHandoff(handoff);
877
+ return;
878
+ }
879
+ console.error('Usage: hippo handoff <create|latest|show>');
880
+ process.exit(1);
881
+ }
882
+ function cmdCurrent(hippoRoot, args, flags) {
883
+ requireInit(hippoRoot);
884
+ const subcommand = args[0] ?? 'show';
885
+ if (subcommand === 'show') {
886
+ const asJson = Boolean(flags['json']);
887
+ const snapshot = loadActiveTaskSnapshot(hippoRoot);
888
+ const sessionId = snapshot?.session_id ?? undefined;
889
+ const events = listSessionEvents(hippoRoot, {
890
+ session_id: sessionId,
891
+ limit: 5,
892
+ });
893
+ if (asJson) {
894
+ console.log(JSON.stringify({
895
+ snapshot: snapshot ?? null,
896
+ events: events.map((ev) => ({
897
+ id: ev.id,
898
+ session_id: ev.session_id,
899
+ event_type: ev.event_type,
900
+ content: ev.content,
901
+ created_at: ev.created_at,
902
+ })),
903
+ }));
904
+ return;
905
+ }
906
+ if (!snapshot && events.length === 0) {
907
+ console.log('No active task or recent session events.');
908
+ return;
909
+ }
910
+ console.log('# Current State\n');
911
+ if (snapshot) {
912
+ console.log(`Task: ${snapshot.task}`);
913
+ console.log(`Status: ${snapshot.status} | Source: ${snapshot.source} | Updated: ${snapshot.updated_at}`);
914
+ if (snapshot.session_id) {
915
+ console.log(`Session: ${snapshot.session_id}`);
916
+ }
917
+ console.log(`Summary: ${snapshot.summary}`);
918
+ console.log(`Next: ${snapshot.next_step}`);
919
+ }
920
+ else {
921
+ console.log('No active task snapshot.');
922
+ }
923
+ if (events.length > 0) {
924
+ console.log('');
925
+ console.log('Recent events:');
926
+ for (const ev of events) {
927
+ const ts = ev.created_at.slice(0, 19).replace('T', ' ');
928
+ console.log(` [${ts}] (${ev.event_type}) ${ev.content}`);
929
+ }
930
+ }
931
+ return;
932
+ }
933
+ console.error('Usage: hippo current <show>');
686
934
  process.exit(1);
687
935
  }
688
936
  async function cmdContext(hippoRoot, args, flags) {
689
937
  requireInit(hippoRoot);
690
938
  const budget = parseInt(String(flags['budget'] ?? '1500'), 10);
939
+ const limit = parseLimitFlag(flags['limit']);
691
940
  // If budget is 0, skip entirely (zero token cost)
692
941
  if (budget <= 0)
693
942
  return;
@@ -765,6 +1014,10 @@ async function cmdContext(hippoRoot, args, flags) {
765
1014
  selectedItems = results;
766
1015
  totalTokens = results.reduce((sum, r) => sum + r.tokens, 0);
767
1016
  }
1017
+ if (limit < selectedItems.length) {
1018
+ selectedItems = selectedItems.slice(0, limit);
1019
+ totalTokens = selectedItems.reduce((sum, r) => sum + r.tokens, 0);
1020
+ }
768
1021
  if (selectedItems.length === 0 && !activeSnapshot && recentSessionEvents.length === 0)
769
1022
  return;
770
1023
  // Mark retrieved and persist
@@ -1136,97 +1389,97 @@ const HOOKS = {
1136
1389
  'claude-code': {
1137
1390
  file: 'CLAUDE.md',
1138
1391
  description: 'Claude Code',
1139
- content: `
1140
- ## Project Memory (Hippo)
1141
-
1142
- Before starting work, load relevant context:
1143
- \`\`\`bash
1144
- hippo context --auto --budget 1500
1145
- \`\`\`
1146
-
1147
- When you learn something important:
1148
- \`\`\`bash
1149
- hippo remember "<lesson>"
1150
- \`\`\`
1151
-
1152
- When you hit an error or discover a gotcha:
1153
- \`\`\`bash
1154
- hippo remember "<what went wrong and why>" --error
1155
- \`\`\`
1156
-
1157
- After significant discussions or decisions, capture context:
1158
- \`\`\`bash
1159
- hippo capture --stdin <<< 'summary of what was decided'
1160
- \`\`\`
1161
-
1162
- After completing work successfully:
1163
- \`\`\`bash
1164
- hippo outcome --good
1165
- \`\`\`
1392
+ content: `
1393
+ ## Project Memory (Hippo)
1394
+
1395
+ Before starting work, load relevant context:
1396
+ \`\`\`bash
1397
+ hippo context --auto --budget 1500
1398
+ \`\`\`
1399
+
1400
+ When you learn something important:
1401
+ \`\`\`bash
1402
+ hippo remember "<lesson>"
1403
+ \`\`\`
1404
+
1405
+ When you hit an error or discover a gotcha:
1406
+ \`\`\`bash
1407
+ hippo remember "<what went wrong and why>" --error
1408
+ \`\`\`
1409
+
1410
+ After significant discussions or decisions, capture context:
1411
+ \`\`\`bash
1412
+ hippo capture --stdin <<< 'summary of what was decided'
1413
+ \`\`\`
1414
+
1415
+ After completing work successfully:
1416
+ \`\`\`bash
1417
+ hippo outcome --good
1418
+ \`\`\`
1166
1419
  `.trim(),
1167
1420
  },
1168
1421
  'codex': {
1169
1422
  file: 'AGENTS.md',
1170
1423
  description: 'OpenAI Codex',
1171
- content: `
1172
- ## Project Memory (Hippo)
1173
-
1174
- At the start of every task, run:
1175
- \`\`\`bash
1176
- hippo context --auto --budget 1500
1177
- \`\`\`
1178
- Read the output before writing any code.
1179
-
1180
- On errors or unexpected behaviour:
1181
- \`\`\`bash
1182
- hippo remember "<description of what went wrong>" --error
1183
- \`\`\`
1184
-
1185
- On task completion:
1186
- \`\`\`bash
1187
- hippo outcome --good
1188
- \`\`\`
1424
+ content: `
1425
+ ## Project Memory (Hippo)
1426
+
1427
+ At the start of every task, run:
1428
+ \`\`\`bash
1429
+ hippo context --auto --budget 1500
1430
+ \`\`\`
1431
+ Read the output before writing any code.
1432
+
1433
+ On errors or unexpected behaviour:
1434
+ \`\`\`bash
1435
+ hippo remember "<description of what went wrong>" --error
1436
+ \`\`\`
1437
+
1438
+ On task completion:
1439
+ \`\`\`bash
1440
+ hippo outcome --good
1441
+ \`\`\`
1189
1442
  `.trim(),
1190
1443
  },
1191
1444
  'cursor': {
1192
1445
  file: '.cursorrules',
1193
1446
  description: 'Cursor',
1194
- content: `
1195
- # Project Memory (Hippo)
1196
- # Before each task, load context:
1197
- # hippo context --auto --budget 1500
1198
- # After errors:
1199
- # hippo remember "<error description>" --error
1200
- # After completing:
1201
- # hippo outcome --good
1447
+ content: `
1448
+ # Project Memory (Hippo)
1449
+ # Before each task, load context:
1450
+ # hippo context --auto --budget 1500
1451
+ # After errors:
1452
+ # hippo remember "<error description>" --error
1453
+ # After completing:
1454
+ # hippo outcome --good
1202
1455
  `.trim(),
1203
1456
  },
1204
1457
  'openclaw': {
1205
1458
  file: 'AGENTS.md',
1206
1459
  description: 'OpenClaw',
1207
- content: `
1208
- ## Project Memory (Hippo)
1209
-
1210
- At the start of every session, run:
1211
- \`\`\`bash
1212
- hippo context --auto --budget 1500
1213
- \`\`\`
1214
- Read the output before writing any code.
1215
-
1216
- On errors or unexpected behaviour:
1217
- \`\`\`bash
1218
- hippo remember "<description of what went wrong>" --error
1219
- \`\`\`
1220
-
1221
- On task completion:
1222
- \`\`\`bash
1223
- hippo outcome --good
1224
- \`\`\`
1225
-
1226
- After significant coding sessions:
1227
- \`\`\`bash
1228
- hippo learn --git
1229
- \`\`\`
1460
+ content: `
1461
+ ## Project Memory (Hippo)
1462
+
1463
+ At the start of every session, run:
1464
+ \`\`\`bash
1465
+ hippo context --auto --budget 1500
1466
+ \`\`\`
1467
+ Read the output before writing any code.
1468
+
1469
+ On errors or unexpected behaviour:
1470
+ \`\`\`bash
1471
+ hippo remember "<description of what went wrong>" --error
1472
+ \`\`\`
1473
+
1474
+ On task completion:
1475
+ \`\`\`bash
1476
+ hippo outcome --good
1477
+ \`\`\`
1478
+
1479
+ After significant coding sessions:
1480
+ \`\`\`bash
1481
+ hippo learn --git
1482
+ \`\`\`
1230
1483
  `.trim(),
1231
1484
  },
1232
1485
  };
@@ -1306,126 +1559,234 @@ function cmdHook(args, flags) {
1306
1559
  function escapeRegex(s) {
1307
1560
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1308
1561
  }
1562
+ // ---------------------------------------------------------------------------
1563
+ // Working Memory
1564
+ // ---------------------------------------------------------------------------
1565
+ function cmdWm(hippoRoot, args, flags) {
1566
+ requireInit(hippoRoot);
1567
+ const subcommand = args[0] ?? '';
1568
+ if (subcommand === 'push') {
1569
+ const scope = String(flags['scope'] ?? 'default').trim();
1570
+ const content = String(flags['content'] ?? '').trim();
1571
+ const importance = parseFloat(String(flags['importance'] ?? '0.5'));
1572
+ const sessionId = flags['session'] ? String(flags['session']).trim() : undefined;
1573
+ const taskId = flags['task'] ? String(flags['task']).trim() : undefined;
1574
+ if (!content) {
1575
+ console.error('Usage: hippo wm push --scope <scope> --content "..." [--importance 0.8] [--session <id>] [--task <id>]');
1576
+ process.exit(1);
1577
+ }
1578
+ const id = wmPush(hippoRoot, {
1579
+ scope,
1580
+ content,
1581
+ importance: Number.isFinite(importance) ? importance : 0.5,
1582
+ sessionId,
1583
+ taskId,
1584
+ });
1585
+ console.log(`Pushed working memory #${id} (scope=${scope}, importance=${Number.isFinite(importance) ? importance : 0.5})`);
1586
+ return;
1587
+ }
1588
+ if (subcommand === 'read') {
1589
+ const scope = flags['scope'] ? String(flags['scope']).trim() : undefined;
1590
+ const sessionId = flags['session'] ? String(flags['session']).trim() : undefined;
1591
+ const limit = parseInt(String(flags['limit'] ?? '20'), 10) || 20;
1592
+ const items = wmRead(hippoRoot, { scope, sessionId, limit });
1593
+ if (flags['json']) {
1594
+ console.log(JSON.stringify({ items }, null, 2));
1595
+ return;
1596
+ }
1597
+ if (items.length === 0) {
1598
+ console.log('No working memory entries.');
1599
+ return;
1600
+ }
1601
+ console.log(`Working memory (${items.length} entries):\n`);
1602
+ for (const item of items) {
1603
+ const sessionLabel = item.sessionId ? ` session=${item.sessionId}` : '';
1604
+ const taskLabel = item.taskId ? ` task=${item.taskId}` : '';
1605
+ console.log(` #${item.id} [${item.scope}] importance=${item.importance}${sessionLabel}${taskLabel}`);
1606
+ console.log(` ${item.content}`);
1607
+ console.log(` created=${item.createdAt}`);
1608
+ console.log('');
1609
+ }
1610
+ return;
1611
+ }
1612
+ if (subcommand === 'clear') {
1613
+ const scope = flags['scope'] ? String(flags['scope']).trim() : undefined;
1614
+ const sessionId = flags['session'] ? String(flags['session']).trim() : undefined;
1615
+ const count = wmClear(hippoRoot, { scope, sessionId });
1616
+ console.log(`Cleared ${count} working memory entries.`);
1617
+ return;
1618
+ }
1619
+ if (subcommand === 'flush') {
1620
+ const scope = flags['scope'] ? String(flags['scope']).trim() : undefined;
1621
+ const sessionId = flags['session'] ? String(flags['session']).trim() : undefined;
1622
+ const count = wmFlush(hippoRoot, { scope, sessionId });
1623
+ console.log(`Flushed ${count} working memory entries.`);
1624
+ return;
1625
+ }
1626
+ console.error('Usage: hippo wm <push|read|clear|flush>');
1627
+ process.exit(1);
1628
+ }
1309
1629
  function printUsage() {
1310
- console.log(`
1311
- Hippo - biologically-inspired memory system for AI agents
1312
-
1313
- Usage: hippo <command> [options]
1314
-
1315
- Commands:
1316
- init Create .hippo/ structure in current directory
1317
- --global Init the global store at ~/.hippo/
1318
- --no-hooks Skip auto-detecting and installing agent hooks
1319
- --no-schedule Skip auto-creating daily learn+sleep cron job
1320
- remember <text> Store a memory
1321
- --tag <tag> Add a tag (repeatable)
1322
- --error Tag as error (boosts retention)
1323
- --pin Pin memory (never decays)
1324
- --verified Set confidence: verified (default)
1325
- --observed Set confidence: observed
1326
- --inferred Set confidence: inferred
1327
- --global Store in global ~/.hippo/ store
1328
- recall <query> Search and retrieve memories (local + global)
1329
- --budget <n> Token budget (default: 4000)
1330
- --json Output as JSON
1331
- context Smart context injection for AI agents
1332
- --auto Auto-detect task from git state
1333
- --budget <n> Token budget (default: 1500)
1334
- --format <fmt> Output format: markdown (default) or json
1335
- --framing <mode> Framing: observe (default), suggest, assert
1336
- sleep Run consolidation pass
1337
- --dry-run Preview without writing
1338
- status Show memory health stats
1339
- outcome Apply feedback to last recall
1340
- --good Memories were helpful
1341
- --bad Memories were irrelevant
1342
- --id <id> Target a specific memory
1343
- conflicts List detected open memory conflicts
1344
- --status <status> Filter by status (default: open)
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)
1349
- snapshot <sub> Persist or inspect the current active task
1350
- snapshot save Save active task state
1351
- --task <task>
1352
- --summary <summary>
1353
- --next-step <step>
1354
- --source <source> Optional source label
1355
- --session <id> Link snapshot to a session trail
1356
- snapshot show Show the active task snapshot
1357
- --json Output as JSON
1358
- snapshot clear Clear the active task snapshot
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
1372
- forget <id> Force remove a memory
1373
- inspect <id> Show full memory detail
1374
- embed Embed all memories for semantic search
1375
- --status Show embedding coverage
1376
- watch "<command>" Run command, auto-learn from failures
1377
- learn Learn lessons from repository history
1378
- --git Scan recent git commits for lessons
1379
- --days <n> Scan this many days back (default: 7)
1380
- --repos <paths> Comma-separated repo paths to scan
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
1388
- sync Pull global memories into local project
1389
- import Import memories from other AI tools
1390
- --chatgpt <path> Import from ChatGPT memory export (JSON or txt)
1391
- --claude <path> Import from CLAUDE.md or Claude memory.json
1392
- --cursor <path> Import from .cursorrules or .cursor/rules
1393
- --file <path> Import from any markdown or text file
1394
- --markdown <path> Import from structured MEMORY.md / AGENTS.md
1395
- --dry-run Preview without writing
1396
- --global Write to global store (~/.hippo/)
1397
- --tag <tag> Add extra tag (repeatable)
1398
- capture Extract memories from conversation text
1399
- --stdin Read from piped input
1400
- --file <path> Read from a file
1401
- --last-session (placeholder) Read from agent session logs
1402
- --dry-run Preview without writing
1403
- --global Write to global store (~/.hippo/)
1404
- hook <sub> [target] Manage framework integrations
1405
- hook list Show available hooks
1406
- hook install <target> Install hook (claude-code|codex|cursor|openclaw)
1407
- hook uninstall <target> Remove hook
1408
- dashboard Open web dashboard for memory health
1409
- --port <n> Port to serve on (default: 3333)
1410
- mcp Start MCP server (stdio transport)
1411
-
1412
- Examples:
1413
- hippo init
1414
- hippo remember "FRED cache can silently drop series" --tag error
1415
- hippo recall "data pipeline issues" --budget 2000
1416
- hippo context --auto --budget 1500
1417
- hippo conflicts
1418
- hippo session log --id sess_123 --task "Ship feature" --type progress --content "Build is green, next step is docs"
1419
- hippo snapshot save --task "Ship feature" --summary "Tests are green" --next-step "Open the PR" --session sess_123
1420
- hippo embed --status
1421
- hippo watch "npm run build"
1422
- hippo learn --git --days 30
1423
- hippo promote mem_abc123
1424
- hippo sync
1425
- hippo hook install claude-code
1426
- hippo sleep --dry-run
1427
- hippo outcome --good
1428
- hippo status
1630
+ console.log(`
1631
+ Hippo - biologically-inspired memory system for AI agents
1632
+
1633
+ Usage: hippo <command> [options]
1634
+
1635
+ Commands:
1636
+ init Create .hippo/ structure in current directory
1637
+ --global Init the global store at ~/.hippo/
1638
+ --no-hooks Skip auto-detecting and installing agent hooks
1639
+ --no-schedule Skip auto-creating daily learn+sleep cron job
1640
+ remember <text> Store a memory
1641
+ --tag <tag> Add a tag (repeatable)
1642
+ --error Tag as error (boosts retention)
1643
+ --pin Pin memory (never decays)
1644
+ --verified Set confidence: verified (default)
1645
+ --observed Set confidence: observed
1646
+ --inferred Set confidence: inferred
1647
+ --global Store in global ~/.hippo/ store
1648
+ recall <query> Search and retrieve memories (local + global)
1649
+ --budget <n> Token budget (default: 4000)
1650
+ --json Output as JSON
1651
+ --why Show match reasons and source annotations
1652
+ context Smart context injection for AI agents
1653
+ --auto Auto-detect task from git state
1654
+ --budget <n> Token budget (default: 1500)
1655
+ --format <fmt> Output format: markdown (default) or json
1656
+ --framing <mode> Framing: observe (default), suggest, assert
1657
+ sleep Run consolidation pass
1658
+ --dry-run Preview without writing
1659
+ status Show memory health stats
1660
+ outcome Apply feedback to last recall
1661
+ --good Memories were helpful
1662
+ --bad Memories were irrelevant
1663
+ --id <id> Target a specific memory
1664
+ conflicts List detected open memory conflicts
1665
+ --status <status> Filter by status (default: open)
1666
+ --json Output as JSON
1667
+ resolve <conflict_id> Resolve a memory conflict
1668
+ --keep <memory_id> Memory to keep (required)
1669
+ --forget Delete the losing memory (default: halve half-life)
1670
+ snapshot <sub> Persist or inspect the current active task
1671
+ snapshot save Save active task state
1672
+ --task <task>
1673
+ --summary <summary>
1674
+ --next-step <step>
1675
+ --source <source> Optional source label
1676
+ --session <id> Link snapshot to a session trail
1677
+ snapshot show Show the active task snapshot
1678
+ --json Output as JSON
1679
+ snapshot clear Clear the active task snapshot
1680
+ --status <status> Mark final status (default: cleared)
1681
+ session <sub> Append or inspect short-term session history
1682
+ session log Append a structured session event
1683
+ --id <session-id>
1684
+ --content <text>
1685
+ --type <type> Event type (default: note)
1686
+ --task <task> Optional task label
1687
+ --source <source> Optional source label
1688
+ session show Show recent events for a session or task
1689
+ --id <session-id>
1690
+ --task <task>
1691
+ --limit <n> Event limit (default: 8)
1692
+ --json Output as JSON
1693
+ session latest Show latest task snapshot + events
1694
+ --id <session-id> Filter by session
1695
+ --json Output as JSON
1696
+ session resume Re-inject latest handoff as context output
1697
+ --id <session-id> Filter by session
1698
+ handoff <sub> Manage session handoffs for continuity
1699
+ handoff create Create a new session handoff
1700
+ --summary <text> Handoff summary (required)
1701
+ --next <text> Next action for successor
1702
+ --session <id> Session ID (auto-generated if omitted)
1703
+ --task <id> Associated task ID
1704
+ --artifact <path> Related file path (repeatable)
1705
+ handoff latest Show the most recent handoff
1706
+ --session <id> Filter by session
1707
+ --json Output as JSON
1708
+ handoff show <id> Show a specific handoff by ID
1709
+ current <sub> Show compact current state for agent injection
1710
+ current show Active task + recent session events (default)
1711
+ --json Output as JSON
1712
+ forget <id> Force remove a memory
1713
+ inspect <id> Show full memory detail
1714
+ embed Embed all memories for semantic search
1715
+ --status Show embedding coverage
1716
+ watch "<command>" Run command, auto-learn from failures
1717
+ learn Learn lessons from repository history
1718
+ --git Scan recent git commits for lessons
1719
+ --days <n> Scan this many days back (default: 7)
1720
+ --repos <paths> Comma-separated repo paths to scan
1721
+ promote <id> Copy a local memory to the global store
1722
+ share <id> Share a memory with attribution + transfer scoring
1723
+ --force Share even if transfer score is low
1724
+ --auto Auto-share all high-transfer-score memories
1725
+ --dry-run Preview what would be shared
1726
+ --min-score <n> Minimum transfer score (default: 0.6)
1727
+ peers List projects contributing to global store
1728
+ sync Pull global memories into local project
1729
+ import Import memories from other AI tools
1730
+ --chatgpt <path> Import from ChatGPT memory export (JSON or txt)
1731
+ --claude <path> Import from CLAUDE.md or Claude memory.json
1732
+ --cursor <path> Import from .cursorrules or .cursor/rules
1733
+ --file <path> Import from any markdown or text file
1734
+ --markdown <path> Import from structured MEMORY.md / AGENTS.md
1735
+ --dry-run Preview without writing
1736
+ --global Write to global store (~/.hippo/)
1737
+ --tag <tag> Add extra tag (repeatable)
1738
+ capture Extract memories from conversation text
1739
+ --stdin Read from piped input
1740
+ --file <path> Read from a file
1741
+ --last-session (placeholder) Read from agent session logs
1742
+ --dry-run Preview without writing
1743
+ --global Write to global store (~/.hippo/)
1744
+ hook <sub> [target] Manage framework integrations
1745
+ hook list Show available hooks
1746
+ hook install <target> Install hook (claude-code|codex|cursor|openclaw)
1747
+ hook uninstall <target> Remove hook
1748
+ wm <sub> Working memory — bounded buffer for current state
1749
+ wm push Push a working memory entry
1750
+ --scope <scope> Scope name (default: default)
1751
+ --content <text> Content to store (required)
1752
+ --importance <n> Priority 0-1 (default: 0.5)
1753
+ --session <id> Session ID
1754
+ --task <id> Task ID
1755
+ wm read Read working memory entries
1756
+ --scope <scope> Filter by scope
1757
+ --session <id> Filter by session
1758
+ --limit <n> Max entries (default: 20)
1759
+ --json Output as JSON
1760
+ wm clear Clear working memory entries
1761
+ --scope <scope> Filter by scope
1762
+ --session <id> Filter by session
1763
+ wm flush Flush working memory (session end)
1764
+ --scope <scope> Filter by scope
1765
+ --session <id> Filter by session
1766
+ dashboard Open web dashboard for memory health
1767
+ --port <n> Port to serve on (default: 3333)
1768
+ mcp Start MCP server (stdio transport)
1769
+
1770
+ Examples:
1771
+ hippo init
1772
+ hippo remember "FRED cache can silently drop series" --tag error
1773
+ hippo recall "data pipeline issues" --budget 2000
1774
+ hippo context --auto --budget 1500
1775
+ hippo conflicts
1776
+ hippo session log --id sess_123 --task "Ship feature" --type progress --content "Build is green, next step is docs"
1777
+ hippo session latest --json
1778
+ hippo session resume
1779
+ hippo snapshot save --task "Ship feature" --summary "Tests are green" --next-step "Open the PR" --session sess_123
1780
+ hippo handoff create --summary "PR is open, tests green" --next "Merge after review" --session sess_123 --artifact src/foo.ts
1781
+ hippo embed --status
1782
+ hippo watch "npm run build"
1783
+ hippo learn --git --days 30
1784
+ hippo promote mem_abc123
1785
+ hippo sync
1786
+ hippo hook install claude-code
1787
+ hippo sleep --dry-run
1788
+ hippo outcome --good
1789
+ hippo status
1429
1790
  `);
1430
1791
  }
1431
1792
  // ---------------------------------------------------------------------------
@@ -1477,6 +1838,12 @@ async function main() {
1477
1838
  case 'session':
1478
1839
  cmdSession(hippoRoot, args, flags);
1479
1840
  break;
1841
+ case 'handoff':
1842
+ cmdHandoff(hippoRoot, args, flags);
1843
+ break;
1844
+ case 'current':
1845
+ cmdCurrent(hippoRoot, args, flags);
1846
+ break;
1480
1847
  case 'forget': {
1481
1848
  const id = args[0];
1482
1849
  if (!id) {
@@ -1625,6 +1992,9 @@ async function main() {
1625
1992
  await new Promise(() => { }); // run until Ctrl+C
1626
1993
  break;
1627
1994
  }
1995
+ case 'wm':
1996
+ cmdWm(hippoRoot, args, flags);
1997
+ break;
1628
1998
  case 'mcp':
1629
1999
  // Start MCP server over stdio - dynamically import to keep main CLI lean
1630
2000
  await import('./mcp/server.js');