hippo-memory 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +135 -7
  2. package/dist/cli.d.ts +6 -2
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +853 -213
  5. package/dist/cli.js.map +1 -1
  6. package/dist/consolidate.d.ts.map +1 -1
  7. package/dist/consolidate.js +12 -5
  8. package/dist/consolidate.js.map +1 -1
  9. package/dist/db.d.ts.map +1 -1
  10. package/dist/db.js +149 -100
  11. package/dist/db.js.map +1 -1
  12. package/dist/handoff.d.ts +29 -0
  13. package/dist/handoff.d.ts.map +1 -0
  14. package/dist/handoff.js +30 -0
  15. package/dist/handoff.js.map +1 -0
  16. package/dist/index.d.ts +4 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +4 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/invalidation.d.ts +23 -0
  21. package/dist/invalidation.d.ts.map +1 -0
  22. package/dist/invalidation.js +94 -0
  23. package/dist/invalidation.js.map +1 -0
  24. package/dist/memory.d.ts +1 -0
  25. package/dist/memory.d.ts.map +1 -1
  26. package/dist/memory.js +1 -0
  27. package/dist/memory.js.map +1 -1
  28. package/dist/path-context.d.ts +12 -0
  29. package/dist/path-context.d.ts.map +1 -0
  30. package/dist/path-context.js +32 -0
  31. package/dist/path-context.js.map +1 -0
  32. package/dist/search.d.ts +19 -0
  33. package/dist/search.d.ts.map +1 -1
  34. package/dist/search.js +55 -2
  35. package/dist/search.js.map +1 -1
  36. package/dist/store.d.ts +18 -0
  37. package/dist/store.d.ts.map +1 -1
  38. package/dist/store.js +209 -99
  39. package/dist/store.js.map +1 -1
  40. package/dist/working-memory.d.ts +59 -0
  41. package/dist/working-memory.d.ts.map +1 -0
  42. package/dist/working-memory.js +149 -0
  43. package/dist/working-memory.js.map +1 -0
  44. package/extensions/openclaw-plugin/index.ts +569 -495
  45. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  46. package/extensions/openclaw-plugin/package.json +1 -1
  47. package/openclaw.plugin.json +1 -1
  48. package/package.json +1 -1
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,34 @@
19
21
  * hippo learn --git [--days <n>] [--repos <paths>]
20
22
  * hippo promote <id>
21
23
  * hippo sync
24
+ * hippo decide "<decision>" [--context "<why>"] [--supersedes <id>]
25
+ * hippo wm <push|read|clear|flush>
22
26
  */
23
27
  import * as path from 'path';
24
28
  import * as fs from 'fs';
29
+ import * as os from 'os';
25
30
  import { execSync } from 'child_process';
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';
31
+ import { createMemory, calculateStrength, deriveHalfLife, resolveConfidence, applyOutcome, computeSchemaFit, Layer, DECISION_HALF_LIFE_DAYS, } from './memory.js';
32
+ 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';
33
+ import { markRetrieved, estimateTokens, hybridSearch, explainMatch } from './search.js';
29
34
  import { consolidate } from './consolidate.js';
30
35
  import { isEmbeddingAvailable, embedAll, embedMemory, loadEmbeddingIndex, } from './embeddings.js';
31
36
  import { captureError, extractLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
37
+ import { extractInvalidationTarget, invalidateMatching } from './invalidation.js';
38
+ import { extractPathTags } from './path-context.js';
32
39
  import { getGlobalRoot, initGlobal, promoteToGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
33
40
  import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, } from './importers.js';
34
41
  import { cmdCapture } from './capture.js';
42
+ import { wmPush, wmRead, wmClear, wmFlush } from './working-memory.js';
35
43
  // ---------------------------------------------------------------------------
36
44
  // Helpers
37
45
  // ---------------------------------------------------------------------------
46
+ function parseLimitFlag(value) {
47
+ if (!value)
48
+ return Infinity;
49
+ const parsed = parseInt(String(value), 10);
50
+ return Number.isFinite(parsed) && parsed >= 1 ? parsed : Infinity;
51
+ }
38
52
  function requireInit(hippoRoot) {
39
53
  if (!isInitialized(hippoRoot)) {
40
54
  console.error('No .hippo directory found. Run `hippo init` first.');
@@ -57,8 +71,8 @@ function parseArgs(argv) {
57
71
  i++;
58
72
  }
59
73
  else {
60
- // Check if it's a repeatable flag (tag)
61
- if (key === 'tag') {
74
+ // Check if it's a repeatable flag (tag, artifact)
75
+ if (key === 'tag' || key === 'artifact') {
62
76
  if (Array.isArray(flags[key])) {
63
77
  flags[key].push(next);
64
78
  }
@@ -127,6 +141,7 @@ function autoInstallHooks(quiet) {
127
141
  { files: ['AGENTS.md', '.codex'], hook: 'codex' },
128
142
  { files: ['.cursorrules', '.cursor/rules'], hook: 'cursor' },
129
143
  { files: ['.openclaw', 'AGENTS.md'], hook: 'openclaw' },
144
+ { files: ['.opencode', 'opencode.json'], hook: 'opencode' },
130
145
  ];
131
146
  // Track which hook files we've already touched to avoid double-patching AGENTS.md
132
147
  const installed = new Set();
@@ -163,6 +178,12 @@ function autoInstallHooks(quiet) {
163
178
  }
164
179
  installed.add(targetPath);
165
180
  console.log(` Auto-installed ${hook} hook in ${hookDef.file}`);
181
+ // For claude-code, also install the Stop hook in settings.json
182
+ if (hook === 'claude-code') {
183
+ if (installClaudeCodeStopHook()) {
184
+ console.log(` Auto-installed hippo sleep Stop hook in Claude Code settings.json`);
185
+ }
186
+ }
166
187
  }
167
188
  }
168
189
  /**
@@ -248,6 +269,12 @@ function cmdRemember(hippoRoot, text, flags) {
248
269
  confidence,
249
270
  schema_fit: schemaFit,
250
271
  });
272
+ // Auto-tag with path context
273
+ const pathTags = extractPathTags(process.cwd());
274
+ for (const pt of pathTags) {
275
+ if (!entry.tags.includes(pt))
276
+ entry.tags.push(pt);
277
+ }
251
278
  writeEntry(targetRoot, entry);
252
279
  updateStats(targetRoot, { remembered: 1 });
253
280
  const prefix = useGlobal ? '[global] ' : '';
@@ -267,7 +294,9 @@ function cmdRemember(hippoRoot, text, flags) {
267
294
  async function cmdRecall(hippoRoot, query, flags) {
268
295
  requireInit(hippoRoot);
269
296
  const budget = parseInt(String(flags['budget'] ?? '4000'), 10);
297
+ const limit = parseLimitFlag(flags['limit']);
270
298
  const asJson = Boolean(flags['json']);
299
+ const showWhy = Boolean(flags['why']);
271
300
  const globalRoot = getGlobalRoot();
272
301
  const localEntries = loadSearchEntries(hippoRoot, query);
273
302
  const globalEntries = isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query) : [];
@@ -280,6 +309,9 @@ async function cmdRecall(hippoRoot, query, flags) {
280
309
  else {
281
310
  results = await hybridSearch(query, localEntries, { budget, hippoRoot });
282
311
  }
312
+ if (limit < results.length) {
313
+ results = results.slice(0, limit);
314
+ }
283
315
  if (results.length === 0) {
284
316
  if (asJson) {
285
317
  console.log(JSON.stringify({ query, results: [], total: 0 }));
@@ -301,14 +333,27 @@ async function cmdRecall(hippoRoot, query, flags) {
301
333
  saveIndex(hippoRoot, localIndex);
302
334
  updateStats(hippoRoot, { recalled: results.length });
303
335
  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
- }));
336
+ const output = results.map((r) => {
337
+ const isGlobal = isInitialized(globalRoot) && !localIndex.entries[r.entry.id];
338
+ const base = {
339
+ id: r.entry.id,
340
+ score: r.score,
341
+ strength: r.entry.strength,
342
+ tokens: r.tokens,
343
+ tags: r.entry.tags,
344
+ content: r.entry.content,
345
+ };
346
+ if (showWhy) {
347
+ const explanation = explainMatch(query, r);
348
+ base.layer = r.entry.layer;
349
+ base.confidence = resolveConfidence(r.entry);
350
+ base.source = isGlobal ? 'global' : 'local';
351
+ base.reason = explanation.reason;
352
+ base.bm25 = r.bm25;
353
+ base.cosine = r.cosine;
354
+ }
355
+ return base;
356
+ });
312
357
  console.log(JSON.stringify({ query, budget, results: output, total: output.length }));
313
358
  return;
314
359
  }
@@ -319,9 +364,16 @@ async function cmdRecall(hippoRoot, query, flags) {
319
364
  const conf = resolveConfidence(e);
320
365
  const confLabel = conf === 'stale' || conf === 'inferred' ? `[${conf}] \u26A0\uFE0F` : `[${conf}]`;
321
366
  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]' : '';
367
+ const isGlobal = isInitialized(globalRoot) && !localIndex.entries[e.id];
368
+ const globalMark = isGlobal ? ' [global]' : '';
369
+ const sourceMark = isGlobal ? ' [global]' : ' [local]';
323
370
  console.log(`--- ${e.id} [${e.layer}] ${confLabel}${globalMark} score=${fmt(r.score, 3)} strength=${fmt(e.strength)}`);
324
371
  console.log(` [${strengthBar}] tags: ${e.tags.join(', ') || 'none'} | retrieved: ${e.retrieval_count}x`);
372
+ if (showWhy) {
373
+ const explanation = explainMatch(query, r);
374
+ console.log(` source:${sourceMark} | layer: [${e.layer}] | confidence: [${conf}]`);
375
+ console.log(` reason: ${explanation.reason}`);
376
+ }
325
377
  console.log();
326
378
  console.log(e.content);
327
379
  console.log();
@@ -682,12 +734,226 @@ function cmdSession(hippoRoot, args, flags) {
682
734
  printSessionEvents(events);
683
735
  return;
684
736
  }
685
- console.error('Usage: hippo session <log|show>');
737
+ if (subcommand === 'latest') {
738
+ const snapshot = loadActiveTaskSnapshot(hippoRoot);
739
+ const events = listSessionEvents(hippoRoot, {
740
+ session_id: sessionId || snapshot?.session_id || undefined,
741
+ limit,
742
+ });
743
+ if (flags['json']) {
744
+ console.log(JSON.stringify({ snapshot: snapshot ?? null, events }, null, 2));
745
+ return;
746
+ }
747
+ if (snapshot) {
748
+ printActiveTaskSnapshot(snapshot);
749
+ }
750
+ else {
751
+ console.log('No active task snapshot.');
752
+ console.log('');
753
+ }
754
+ printSessionEvents(events);
755
+ return;
756
+ }
757
+ if (subcommand === 'resume') {
758
+ const handoff = loadLatestHandoff(hippoRoot, sessionId || undefined);
759
+ if (!handoff) {
760
+ console.log('No handoff to resume from.');
761
+ return;
762
+ }
763
+ const lines = [
764
+ '## Session Handoff (resumed)',
765
+ '',
766
+ `- Session: ${handoff.sessionId}`,
767
+ `- Updated: ${handoff.updatedAt}`,
768
+ ];
769
+ if (handoff.taskId)
770
+ lines.push(`- Task: ${handoff.taskId}`);
771
+ if (handoff.repoRoot)
772
+ lines.push(`- Repo: ${handoff.repoRoot}`);
773
+ lines.push('', '### Summary', handoff.summary);
774
+ if (handoff.nextAction) {
775
+ lines.push('', '### Next action', handoff.nextAction);
776
+ }
777
+ if (handoff.artifacts && handoff.artifacts.length > 0) {
778
+ lines.push('', '### Artifacts');
779
+ for (const artifact of handoff.artifacts) {
780
+ lines.push(`- ${artifact}`);
781
+ }
782
+ }
783
+ lines.push('');
784
+ console.log(lines.join('\n'));
785
+ return;
786
+ }
787
+ console.error('Usage: hippo session <log|show|latest|resume>');
788
+ process.exit(1);
789
+ }
790
+ function printHandoff(handoff) {
791
+ console.log('## Session Handoff\n');
792
+ console.log(`- Session: ${handoff.sessionId}`);
793
+ console.log(`- Updated: ${handoff.updatedAt}`);
794
+ if (handoff.taskId)
795
+ console.log(`- Task: ${handoff.taskId}`);
796
+ if (handoff.repoRoot)
797
+ console.log(`- Repo: ${handoff.repoRoot}`);
798
+ console.log('');
799
+ console.log('### Summary');
800
+ console.log(handoff.summary);
801
+ if (handoff.nextAction) {
802
+ console.log('');
803
+ console.log('### Next action');
804
+ console.log(handoff.nextAction);
805
+ }
806
+ if (handoff.artifacts && handoff.artifacts.length > 0) {
807
+ console.log('');
808
+ console.log('### Artifacts');
809
+ for (const artifact of handoff.artifacts) {
810
+ console.log(`- ${artifact}`);
811
+ }
812
+ }
813
+ console.log('');
814
+ }
815
+ function cmdHandoff(hippoRoot, args, flags) {
816
+ requireInit(hippoRoot);
817
+ const subcommand = args[0] ?? 'latest';
818
+ if (subcommand === 'create') {
819
+ const summary = String(flags['summary'] ?? '').trim();
820
+ if (!summary) {
821
+ console.error('Usage: hippo handoff create --summary "..." [--next "..."] [--session <id>] [--task <id>] [--artifact <path>...]');
822
+ process.exit(1);
823
+ }
824
+ const sessionId = String(flags['session'] ?? flags['id'] ?? '').trim() || `fallback-${Date.now()}-${process.pid}`;
825
+ const nextAction = String(flags['next'] ?? '').trim() || undefined;
826
+ const taskId = String(flags['task'] ?? '').trim() || undefined;
827
+ const artifactFlag = flags['artifact'];
828
+ const artifacts = Array.isArray(artifactFlag)
829
+ ? artifactFlag
830
+ : (typeof artifactFlag === 'string' ? [artifactFlag] : []);
831
+ const handoff = saveSessionHandoff(hippoRoot, {
832
+ version: 1,
833
+ sessionId,
834
+ repoRoot: process.cwd(),
835
+ taskId,
836
+ summary,
837
+ nextAction,
838
+ artifacts,
839
+ });
840
+ console.log(`Created session handoff for session ${handoff.sessionId}`);
841
+ console.log(` Summary: ${handoff.summary}`);
842
+ if (handoff.nextAction)
843
+ console.log(` Next: ${handoff.nextAction}`);
844
+ if (handoff.artifacts && handoff.artifacts.length > 0) {
845
+ console.log(` Artifacts: ${handoff.artifacts.join(', ')}`);
846
+ }
847
+ return;
848
+ }
849
+ if (subcommand === 'latest') {
850
+ const sessionId = String(flags['session'] ?? flags['id'] ?? '').trim() || undefined;
851
+ const handoff = loadLatestHandoff(hippoRoot, sessionId);
852
+ if (!handoff) {
853
+ if (flags['json']) {
854
+ console.log(JSON.stringify({ handoff: null }));
855
+ }
856
+ else {
857
+ console.log('No session handoff found.');
858
+ }
859
+ return;
860
+ }
861
+ if (flags['json']) {
862
+ console.log(JSON.stringify({ handoff }, null, 2));
863
+ return;
864
+ }
865
+ printHandoff(handoff);
866
+ return;
867
+ }
868
+ if (subcommand === 'show') {
869
+ const idArg = args[1];
870
+ if (!idArg) {
871
+ console.error('Usage: hippo handoff show <id> [--json]');
872
+ process.exit(1);
873
+ }
874
+ const handoffId = parseInt(idArg, 10);
875
+ if (!Number.isFinite(handoffId) || handoffId <= 0) {
876
+ console.error(`Invalid handoff ID: ${idArg}`);
877
+ process.exit(1);
878
+ }
879
+ const handoff = loadHandoffById(hippoRoot, handoffId);
880
+ if (!handoff) {
881
+ if (flags['json']) {
882
+ console.log(JSON.stringify({ handoff: null }));
883
+ }
884
+ else {
885
+ console.log(`No handoff found with ID ${handoffId}.`);
886
+ }
887
+ return;
888
+ }
889
+ if (flags['json']) {
890
+ console.log(JSON.stringify({ handoff }, null, 2));
891
+ return;
892
+ }
893
+ printHandoff(handoff);
894
+ return;
895
+ }
896
+ console.error('Usage: hippo handoff <create|latest|show>');
897
+ process.exit(1);
898
+ }
899
+ function cmdCurrent(hippoRoot, args, flags) {
900
+ requireInit(hippoRoot);
901
+ const subcommand = args[0] ?? 'show';
902
+ if (subcommand === 'show') {
903
+ const asJson = Boolean(flags['json']);
904
+ const snapshot = loadActiveTaskSnapshot(hippoRoot);
905
+ const sessionId = snapshot?.session_id ?? undefined;
906
+ const events = listSessionEvents(hippoRoot, {
907
+ session_id: sessionId,
908
+ limit: 5,
909
+ });
910
+ if (asJson) {
911
+ console.log(JSON.stringify({
912
+ snapshot: snapshot ?? null,
913
+ events: events.map((ev) => ({
914
+ id: ev.id,
915
+ session_id: ev.session_id,
916
+ event_type: ev.event_type,
917
+ content: ev.content,
918
+ created_at: ev.created_at,
919
+ })),
920
+ }));
921
+ return;
922
+ }
923
+ if (!snapshot && events.length === 0) {
924
+ console.log('No active task or recent session events.');
925
+ return;
926
+ }
927
+ console.log('# Current State\n');
928
+ if (snapshot) {
929
+ console.log(`Task: ${snapshot.task}`);
930
+ console.log(`Status: ${snapshot.status} | Source: ${snapshot.source} | Updated: ${snapshot.updated_at}`);
931
+ if (snapshot.session_id) {
932
+ console.log(`Session: ${snapshot.session_id}`);
933
+ }
934
+ console.log(`Summary: ${snapshot.summary}`);
935
+ console.log(`Next: ${snapshot.next_step}`);
936
+ }
937
+ else {
938
+ console.log('No active task snapshot.');
939
+ }
940
+ if (events.length > 0) {
941
+ console.log('');
942
+ console.log('Recent events:');
943
+ for (const ev of events) {
944
+ const ts = ev.created_at.slice(0, 19).replace('T', ' ');
945
+ console.log(` [${ts}] (${ev.event_type}) ${ev.content}`);
946
+ }
947
+ }
948
+ return;
949
+ }
950
+ console.error('Usage: hippo current <show>');
686
951
  process.exit(1);
687
952
  }
688
953
  async function cmdContext(hippoRoot, args, flags) {
689
954
  requireInit(hippoRoot);
690
955
  const budget = parseInt(String(flags['budget'] ?? '1500'), 10);
956
+ const limit = parseLimitFlag(flags['limit']);
691
957
  // If budget is 0, skip entirely (zero token cost)
692
958
  if (budget <= 0)
693
959
  return;
@@ -765,6 +1031,10 @@ async function cmdContext(hippoRoot, args, flags) {
765
1031
  selectedItems = results;
766
1032
  totalTokens = results.reduce((sum, r) => sum + r.tokens, 0);
767
1033
  }
1034
+ if (limit < selectedItems.length) {
1035
+ selectedItems = selectedItems.slice(0, limit);
1036
+ totalTokens = selectedItems.reduce((sum, r) => sum + r.tokens, 0);
1037
+ }
768
1038
  if (selectedItems.length === 0 && !activeSnapshot && recentSessionEvents.length === 0)
769
1039
  return;
770
1040
  // Mark retrieved and persist
@@ -960,14 +1230,27 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
960
1230
  skipped++;
961
1231
  continue;
962
1232
  }
1233
+ const target = extractInvalidationTarget(lesson);
1234
+ if (target) {
1235
+ const invResult = invalidateMatching(hippoRoot, target);
1236
+ if (invResult.invalidated > 0) {
1237
+ console.log(`${prefix} Invalidated ${invResult.invalidated} memories referencing "${target.from}"`);
1238
+ }
1239
+ }
963
1240
  const schemaFitVal = computeSchemaFit(lesson, gitLearnTags, existingForSchema);
964
1241
  const entry = createMemory(lesson, {
965
1242
  layer: Layer.Episodic,
966
- tags: gitLearnTags,
1243
+ tags: [...gitLearnTags],
967
1244
  source: 'git-learn',
968
1245
  confidence: 'observed',
969
1246
  schema_fit: schemaFitVal,
970
1247
  });
1248
+ // Auto-tag with path context from the repo being learned
1249
+ const learnPathTags = extractPathTags(repoPath);
1250
+ for (const pt of learnPathTags) {
1251
+ if (!entry.tags.includes(pt))
1252
+ entry.tags.push(pt);
1253
+ }
971
1254
  writeEntry(hippoRoot, entry);
972
1255
  updateStats(hippoRoot, { remembered: 1 });
973
1256
  if (isEmbeddingAvailable()) {
@@ -1136,97 +1419,125 @@ const HOOKS = {
1136
1419
  'claude-code': {
1137
1420
  file: 'CLAUDE.md',
1138
1421
  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
- \`\`\`
1422
+ content: `
1423
+ ## Project Memory (Hippo)
1424
+
1425
+ Before starting work, load relevant context:
1426
+ \`\`\`bash
1427
+ hippo context --auto --budget 1500
1428
+ \`\`\`
1429
+
1430
+ When you learn something important:
1431
+ \`\`\`bash
1432
+ hippo remember "<lesson>"
1433
+ \`\`\`
1434
+
1435
+ When you hit an error or discover a gotcha:
1436
+ \`\`\`bash
1437
+ hippo remember "<what went wrong and why>" --error
1438
+ \`\`\`
1439
+
1440
+ After significant discussions or decisions, capture context:
1441
+ \`\`\`bash
1442
+ hippo capture --stdin <<< 'summary of what was decided'
1443
+ \`\`\`
1444
+
1445
+ After completing work successfully:
1446
+ \`\`\`bash
1447
+ hippo outcome --good
1448
+ \`\`\`
1166
1449
  `.trim(),
1167
1450
  },
1168
1451
  'codex': {
1169
1452
  file: 'AGENTS.md',
1170
1453
  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
- \`\`\`
1454
+ content: `
1455
+ ## Project Memory (Hippo)
1456
+
1457
+ At the start of every task, run:
1458
+ \`\`\`bash
1459
+ hippo context --auto --budget 1500
1460
+ \`\`\`
1461
+ Read the output before writing any code.
1462
+
1463
+ On errors or unexpected behaviour:
1464
+ \`\`\`bash
1465
+ hippo remember "<description of what went wrong>" --error
1466
+ \`\`\`
1467
+
1468
+ On task completion:
1469
+ \`\`\`bash
1470
+ hippo outcome --good
1471
+ \`\`\`
1189
1472
  `.trim(),
1190
1473
  },
1191
1474
  'cursor': {
1192
1475
  file: '.cursorrules',
1193
1476
  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
1477
+ content: `
1478
+ # Project Memory (Hippo)
1479
+ # Before each task, load context:
1480
+ # hippo context --auto --budget 1500
1481
+ # After errors:
1482
+ # hippo remember "<error description>" --error
1483
+ # After completing:
1484
+ # hippo outcome --good
1202
1485
  `.trim(),
1203
1486
  },
1204
1487
  'openclaw': {
1205
1488
  file: 'AGENTS.md',
1206
1489
  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
- \`\`\`
1490
+ content: `
1491
+ ## Project Memory (Hippo)
1492
+
1493
+ At the start of every session, run:
1494
+ \`\`\`bash
1495
+ hippo context --auto --budget 1500
1496
+ \`\`\`
1497
+ Read the output before writing any code.
1498
+
1499
+ On errors or unexpected behaviour:
1500
+ \`\`\`bash
1501
+ hippo remember "<description of what went wrong>" --error
1502
+ \`\`\`
1503
+
1504
+ On task completion:
1505
+ \`\`\`bash
1506
+ hippo outcome --good
1507
+ \`\`\`
1508
+
1509
+ After significant coding sessions:
1510
+ \`\`\`bash
1511
+ hippo learn --git
1512
+ \`\`\`
1513
+ `.trim(),
1514
+ },
1515
+ 'opencode': {
1516
+ file: 'AGENTS.md',
1517
+ description: 'OpenCode',
1518
+ content: `
1519
+ ## Project Memory (Hippo)
1520
+
1521
+ At the start of every task, run:
1522
+ \`\`\`bash
1523
+ hippo context --auto --budget 1500
1524
+ \`\`\`
1525
+ Read the output before writing any code.
1526
+
1527
+ When you learn something important or hit an error:
1528
+ \`\`\`bash
1529
+ hippo remember "<lesson>" --error
1530
+ \`\`\`
1531
+
1532
+ When stuck or repeating yourself, check if this happened before:
1533
+ \`\`\`bash
1534
+ hippo recall "<what's going wrong>" --budget 2000
1535
+ \`\`\`
1536
+
1537
+ On task completion:
1538
+ \`\`\`bash
1539
+ hippo outcome --good
1540
+ \`\`\`
1230
1541
  `.trim(),
1231
1542
  },
1232
1543
  };
@@ -1276,6 +1587,12 @@ function cmdHook(args, flags) {
1276
1587
  fs.writeFileSync(filepath, block + '\n', 'utf8');
1277
1588
  console.log(`Created ${hook.file} with Hippo hook`);
1278
1589
  }
1590
+ // For claude-code, also install the Stop hook in settings.json
1591
+ if (target === 'claude-code') {
1592
+ if (installClaudeCodeStopHook()) {
1593
+ console.log(`Installed hippo sleep Stop hook in Claude Code settings.json`);
1594
+ }
1595
+ }
1279
1596
  return;
1280
1597
  }
1281
1598
  if (subcommand === 'uninstall') {
@@ -1298,6 +1615,12 @@ function cmdHook(args, flags) {
1298
1615
  const cleaned = existing.replace(re, '\n').replace(/\n{3,}/g, '\n\n').trim();
1299
1616
  fs.writeFileSync(filepath, cleaned + '\n', 'utf8');
1300
1617
  console.log(`Removed Hippo hook from ${hook.file}`);
1618
+ // For claude-code, also remove the Stop hook from settings.json
1619
+ if (target === 'claude-code') {
1620
+ if (uninstallClaudeCodeStopHook()) {
1621
+ console.log(`Removed hippo sleep Stop hook from Claude Code settings.json`);
1622
+ }
1623
+ }
1301
1624
  return;
1302
1625
  }
1303
1626
  console.error('Usage: hippo hook <install|uninstall|list> [target]');
@@ -1306,126 +1629,334 @@ function cmdHook(args, flags) {
1306
1629
  function escapeRegex(s) {
1307
1630
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1308
1631
  }
1632
+ // ---------------------------------------------------------------------------
1633
+ // Claude Code settings.json Stop hook (hippo sleep on session end)
1634
+ // ---------------------------------------------------------------------------
1635
+ const HIPPO_STOP_HOOK_MARKER = 'hippo sleep';
1636
+ /**
1637
+ * Resolve the Claude Code user-level settings.json path (~/.claude/settings.json).
1638
+ * Always targets the global config so the Stop hook runs for all sessions.
1639
+ */
1640
+ function resolveClaudeSettingsPath() {
1641
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
1642
+ return path.join(home, '.claude', 'settings.json');
1643
+ }
1644
+ /**
1645
+ * Check if hippo sleep Stop hook is already installed in Claude Code settings.
1646
+ */
1647
+ function hasClaudeCodeStopHook(settings) {
1648
+ const hooks = settings.hooks;
1649
+ if (!hooks?.Stop)
1650
+ return false;
1651
+ return JSON.stringify(hooks.Stop).includes(HIPPO_STOP_HOOK_MARKER);
1652
+ }
1653
+ /**
1654
+ * Install a Claude Code Stop hook that runs `hippo sleep` at session end.
1655
+ * Merges into existing settings.json without clobbering other hooks.
1656
+ */
1657
+ function installClaudeCodeStopHook() {
1658
+ const settingsPath = resolveClaudeSettingsPath();
1659
+ const dir = path.dirname(settingsPath);
1660
+ if (!fs.existsSync(dir))
1661
+ fs.mkdirSync(dir, { recursive: true });
1662
+ let settings = {};
1663
+ if (fs.existsSync(settingsPath)) {
1664
+ try {
1665
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
1666
+ }
1667
+ catch {
1668
+ console.error(` Warning: could not parse ${settingsPath}, skipping Stop hook install`);
1669
+ return false;
1670
+ }
1671
+ }
1672
+ if (hasClaudeCodeStopHook(settings))
1673
+ return false;
1674
+ // Ensure hooks.Stop array exists
1675
+ if (!settings.hooks)
1676
+ settings.hooks = {};
1677
+ const hooks = settings.hooks;
1678
+ if (!Array.isArray(hooks.Stop))
1679
+ hooks.Stop = [];
1680
+ // Append hippo sleep hook entry (silent: runs every turn, must not produce errors)
1681
+ hooks.Stop.push({
1682
+ hooks: [
1683
+ {
1684
+ type: 'command',
1685
+ command: 'hippo sleep 2>/dev/null || true',
1686
+ timeout: 30,
1687
+ },
1688
+ ],
1689
+ });
1690
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
1691
+ return true;
1692
+ }
1693
+ /**
1694
+ * Remove the hippo sleep Stop hook from Claude Code settings.json.
1695
+ */
1696
+ function uninstallClaudeCodeStopHook() {
1697
+ const settingsPath = resolveClaudeSettingsPath();
1698
+ if (!fs.existsSync(settingsPath))
1699
+ return false;
1700
+ let settings;
1701
+ try {
1702
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
1703
+ }
1704
+ catch {
1705
+ return false;
1706
+ }
1707
+ if (!hasClaudeCodeStopHook(settings))
1708
+ return false;
1709
+ const hooks = settings.hooks;
1710
+ hooks.Stop = hooks.Stop.filter((entry) => !JSON.stringify(entry).includes(HIPPO_STOP_HOOK_MARKER));
1711
+ // Clean up empty Stop array
1712
+ if (hooks.Stop.length === 0)
1713
+ delete hooks.Stop;
1714
+ // Clean up empty hooks object
1715
+ if (Object.keys(hooks).length === 0)
1716
+ delete settings.hooks;
1717
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
1718
+ return true;
1719
+ }
1720
+ // ---------------------------------------------------------------------------
1721
+ // Working Memory
1722
+ // ---------------------------------------------------------------------------
1723
+ function cmdWm(hippoRoot, args, flags) {
1724
+ requireInit(hippoRoot);
1725
+ const subcommand = args[0] ?? '';
1726
+ if (subcommand === 'push') {
1727
+ const scope = String(flags['scope'] ?? 'default').trim();
1728
+ const content = String(flags['content'] ?? '').trim();
1729
+ const importance = parseFloat(String(flags['importance'] ?? '0.5'));
1730
+ const sessionId = flags['session'] ? String(flags['session']).trim() : undefined;
1731
+ const taskId = flags['task'] ? String(flags['task']).trim() : undefined;
1732
+ if (!content) {
1733
+ console.error('Usage: hippo wm push --scope <scope> --content "..." [--importance 0.8] [--session <id>] [--task <id>]');
1734
+ process.exit(1);
1735
+ }
1736
+ const id = wmPush(hippoRoot, {
1737
+ scope,
1738
+ content,
1739
+ importance: Number.isFinite(importance) ? importance : 0.5,
1740
+ sessionId,
1741
+ taskId,
1742
+ });
1743
+ console.log(`Pushed working memory #${id} (scope=${scope}, importance=${Number.isFinite(importance) ? importance : 0.5})`);
1744
+ return;
1745
+ }
1746
+ if (subcommand === 'read') {
1747
+ const scope = flags['scope'] ? String(flags['scope']).trim() : undefined;
1748
+ const sessionId = flags['session'] ? String(flags['session']).trim() : undefined;
1749
+ const limit = parseInt(String(flags['limit'] ?? '20'), 10) || 20;
1750
+ const items = wmRead(hippoRoot, { scope, sessionId, limit });
1751
+ if (flags['json']) {
1752
+ console.log(JSON.stringify({ items }, null, 2));
1753
+ return;
1754
+ }
1755
+ if (items.length === 0) {
1756
+ console.log('No working memory entries.');
1757
+ return;
1758
+ }
1759
+ console.log(`Working memory (${items.length} entries):\n`);
1760
+ for (const item of items) {
1761
+ const sessionLabel = item.sessionId ? ` session=${item.sessionId}` : '';
1762
+ const taskLabel = item.taskId ? ` task=${item.taskId}` : '';
1763
+ console.log(` #${item.id} [${item.scope}] importance=${item.importance}${sessionLabel}${taskLabel}`);
1764
+ console.log(` ${item.content}`);
1765
+ console.log(` created=${item.createdAt}`);
1766
+ console.log('');
1767
+ }
1768
+ return;
1769
+ }
1770
+ if (subcommand === 'clear') {
1771
+ const scope = flags['scope'] ? String(flags['scope']).trim() : undefined;
1772
+ const sessionId = flags['session'] ? String(flags['session']).trim() : undefined;
1773
+ const count = wmClear(hippoRoot, { scope, sessionId });
1774
+ console.log(`Cleared ${count} working memory entries.`);
1775
+ return;
1776
+ }
1777
+ if (subcommand === 'flush') {
1778
+ const scope = flags['scope'] ? String(flags['scope']).trim() : undefined;
1779
+ const sessionId = flags['session'] ? String(flags['session']).trim() : undefined;
1780
+ const count = wmFlush(hippoRoot, { scope, sessionId });
1781
+ console.log(`Flushed ${count} working memory entries.`);
1782
+ return;
1783
+ }
1784
+ console.error('Usage: hippo wm <push|read|clear|flush>');
1785
+ process.exit(1);
1786
+ }
1309
1787
  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
1788
+ console.log(`
1789
+ Hippo - biologically-inspired memory system for AI agents
1790
+
1791
+ Usage: hippo <command> [options]
1792
+
1793
+ Commands:
1794
+ init Create .hippo/ structure in current directory
1795
+ --global Init the global store at ~/.hippo/
1796
+ --no-hooks Skip auto-detecting and installing agent hooks
1797
+ --no-schedule Skip auto-creating daily learn+sleep cron job
1798
+ remember <text> Store a memory
1799
+ --tag <tag> Add a tag (repeatable)
1800
+ --error Tag as error (boosts retention)
1801
+ --pin Pin memory (never decays)
1802
+ --verified Set confidence: verified (default)
1803
+ --observed Set confidence: observed
1804
+ --inferred Set confidence: inferred
1805
+ --global Store in global ~/.hippo/ store
1806
+ recall <query> Search and retrieve memories (local + global)
1807
+ --budget <n> Token budget (default: 4000)
1808
+ --json Output as JSON
1809
+ --why Show match reasons and source annotations
1810
+ context Smart context injection for AI agents
1811
+ --auto Auto-detect task from git state
1812
+ --budget <n> Token budget (default: 1500)
1813
+ --format <fmt> Output format: markdown (default) or json
1814
+ --framing <mode> Framing: observe (default), suggest, assert
1815
+ sleep Run consolidation pass
1816
+ --dry-run Preview without writing
1817
+ status Show memory health stats
1818
+ outcome Apply feedback to last recall
1819
+ --good Memories were helpful
1820
+ --bad Memories were irrelevant
1821
+ --id <id> Target a specific memory
1822
+ conflicts List detected open memory conflicts
1823
+ --status <status> Filter by status (default: open)
1824
+ --json Output as JSON
1825
+ resolve <conflict_id> Resolve a memory conflict
1826
+ --keep <memory_id> Memory to keep (required)
1827
+ --forget Delete the losing memory (default: halve half-life)
1828
+ snapshot <sub> Persist or inspect the current active task
1829
+ snapshot save Save active task state
1830
+ --task <task>
1831
+ --summary <summary>
1832
+ --next-step <step>
1833
+ --source <source> Optional source label
1834
+ --session <id> Link snapshot to a session trail
1835
+ snapshot show Show the active task snapshot
1836
+ --json Output as JSON
1837
+ snapshot clear Clear the active task snapshot
1838
+ --status <status> Mark final status (default: cleared)
1839
+ session <sub> Append or inspect short-term session history
1840
+ session log Append a structured session event
1841
+ --id <session-id>
1842
+ --content <text>
1843
+ --type <type> Event type (default: note)
1844
+ --task <task> Optional task label
1845
+ --source <source> Optional source label
1846
+ session show Show recent events for a session or task
1847
+ --id <session-id>
1848
+ --task <task>
1849
+ --limit <n> Event limit (default: 8)
1850
+ --json Output as JSON
1851
+ session latest Show latest task snapshot + events
1852
+ --id <session-id> Filter by session
1853
+ --json Output as JSON
1854
+ session resume Re-inject latest handoff as context output
1855
+ --id <session-id> Filter by session
1856
+ handoff <sub> Manage session handoffs for continuity
1857
+ handoff create Create a new session handoff
1858
+ --summary <text> Handoff summary (required)
1859
+ --next <text> Next action for successor
1860
+ --session <id> Session ID (auto-generated if omitted)
1861
+ --task <id> Associated task ID
1862
+ --artifact <path> Related file path (repeatable)
1863
+ handoff latest Show the most recent handoff
1864
+ --session <id> Filter by session
1865
+ --json Output as JSON
1866
+ handoff show <id> Show a specific handoff by ID
1867
+ current <sub> Show compact current state for agent injection
1868
+ current show Active task + recent session events (default)
1869
+ --json Output as JSON
1870
+ forget <id> Force remove a memory
1871
+ inspect <id> Show full memory detail
1872
+ embed Embed all memories for semantic search
1873
+ --status Show embedding coverage
1874
+ watch "<command>" Run command, auto-learn from failures
1875
+ learn Learn lessons from repository history
1876
+ --git Scan recent git commits for lessons
1877
+ --days <n> Scan this many days back (default: 7)
1878
+ --repos <paths> Comma-separated repo paths to scan
1879
+ promote <id> Copy a local memory to the global store
1880
+ share <id> Share a memory with attribution + transfer scoring
1881
+ --force Share even if transfer score is low
1882
+ --auto Auto-share all high-transfer-score memories
1883
+ --dry-run Preview what would be shared
1884
+ --min-score <n> Minimum transfer score (default: 0.6)
1885
+ peers List projects contributing to global store
1886
+ sync Pull global memories into local project
1887
+ import Import memories from other AI tools
1888
+ --chatgpt <path> Import from ChatGPT memory export (JSON or txt)
1889
+ --claude <path> Import from CLAUDE.md or Claude memory.json
1890
+ --cursor <path> Import from .cursorrules or .cursor/rules
1891
+ --file <path> Import from any markdown or text file
1892
+ --markdown <path> Import from structured MEMORY.md / AGENTS.md
1893
+ --dry-run Preview without writing
1894
+ --global Write to global store (~/.hippo/)
1895
+ --tag <tag> Add extra tag (repeatable)
1896
+ export [file] Export all memories (default: stdout)
1897
+ --format <fmt> Output format: json (default) or markdown
1898
+ capture Extract memories from conversation text
1899
+ --stdin Read from piped input
1900
+ --file <path> Read from a file
1901
+ --last-session (placeholder) Read from agent session logs
1902
+ --dry-run Preview without writing
1903
+ --global Write to global store (~/.hippo/)
1904
+ hook <sub> [target] Manage framework integrations
1905
+ hook list Show available hooks
1906
+ hook install <target> Install hook (claude-code|codex|cursor|openclaw|opencode)
1907
+ claude-code also installs Stop hook (hippo sleep on exit)
1908
+ hook uninstall <target> Remove hook
1909
+ decide "<decision>" Record an architectural decision (90-day half-life)
1910
+ --context "<why>" Why this decision was made
1911
+ --supersedes <id> Supersede a previous decision (weakens it)
1912
+ invalidate "<pattern>" Actively weaken memories matching an old pattern
1913
+ --reason "<why>" Optional: what replaced it
1914
+ wm <sub> Working memory — bounded buffer for current state
1915
+ wm push Push a working memory entry
1916
+ --scope <scope> Scope name (default: default)
1917
+ --content <text> Content to store (required)
1918
+ --importance <n> Priority 0-1 (default: 0.5)
1919
+ --session <id> Session ID
1920
+ --task <id> Task ID
1921
+ wm read Read working memory entries
1922
+ --scope <scope> Filter by scope
1923
+ --session <id> Filter by session
1924
+ --limit <n> Max entries (default: 20)
1925
+ --json Output as JSON
1926
+ wm clear Clear working memory entries
1927
+ --scope <scope> Filter by scope
1928
+ --session <id> Filter by session
1929
+ wm flush Flush working memory (session end)
1930
+ --scope <scope> Filter by scope
1931
+ --session <id> Filter by session
1932
+ dashboard Open web dashboard for memory health
1933
+ --port <n> Port to serve on (default: 3333)
1934
+ mcp Start MCP server (stdio transport)
1935
+
1936
+ Examples:
1937
+ hippo init
1938
+ hippo remember "FRED cache can silently drop series" --tag error
1939
+ hippo recall "data pipeline issues" --budget 2000
1940
+ hippo context --auto --budget 1500
1941
+ hippo conflicts
1942
+ hippo session log --id sess_123 --task "Ship feature" --type progress --content "Build is green, next step is docs"
1943
+ hippo session latest --json
1944
+ hippo session resume
1945
+ hippo snapshot save --task "Ship feature" --summary "Tests are green" --next-step "Open the PR" --session sess_123
1946
+ hippo handoff create --summary "PR is open, tests green" --next "Merge after review" --session sess_123 --artifact src/foo.ts
1947
+ hippo embed --status
1948
+ hippo watch "npm run build"
1949
+ hippo learn --git --days 30
1950
+ hippo promote mem_abc123
1951
+ hippo sync
1952
+ hippo hook install claude-code
1953
+ hippo decide "Use PostgreSQL for new services" --context "JSONB support"
1954
+ hippo invalidate "REST API" --reason "migrated to GraphQL"
1955
+ hippo export memories.json
1956
+ hippo export --format markdown memories.md
1957
+ hippo sleep --dry-run
1958
+ hippo outcome --good
1959
+ hippo status
1429
1960
  `);
1430
1961
  }
1431
1962
  // ---------------------------------------------------------------------------
@@ -1477,6 +2008,12 @@ async function main() {
1477
2008
  case 'session':
1478
2009
  cmdSession(hippoRoot, args, flags);
1479
2010
  break;
2011
+ case 'handoff':
2012
+ cmdHandoff(hippoRoot, args, flags);
2013
+ break;
2014
+ case 'current':
2015
+ cmdCurrent(hippoRoot, args, flags);
2016
+ break;
1480
2017
  case 'forget': {
1481
2018
  const id = args[0];
1482
2019
  if (!id) {
@@ -1592,6 +2129,37 @@ async function main() {
1592
2129
  case 'import':
1593
2130
  cmdImport(hippoRoot, args, flags);
1594
2131
  break;
2132
+ case 'export': {
2133
+ requireInit(hippoRoot);
2134
+ const format = flags['format'] || 'json';
2135
+ const outputPath = args[0] || null;
2136
+ const entries = loadAllEntries(hippoRoot);
2137
+ let output;
2138
+ if (format === 'markdown' || format === 'md') {
2139
+ output = entries.map(e => {
2140
+ const meta = [
2141
+ `id: ${e.id}`,
2142
+ `created: ${e.created}`,
2143
+ `tags: ${e.tags.join(', ')}`,
2144
+ `confidence: ${e.confidence}`,
2145
+ `half_life: ${e.half_life_days}d`,
2146
+ `strength: ${e.strength.toFixed(2)}`,
2147
+ ].join(' | ');
2148
+ return `### ${e.id}\n\n${e.content}\n\n_${meta}_`;
2149
+ }).join('\n\n---\n\n');
2150
+ }
2151
+ else {
2152
+ output = JSON.stringify(entries, null, 2);
2153
+ }
2154
+ if (outputPath) {
2155
+ fs.writeFileSync(outputPath, output, 'utf8');
2156
+ console.log(`Exported ${entries.length} memories to ${outputPath}`);
2157
+ }
2158
+ else {
2159
+ console.log(output);
2160
+ }
2161
+ break;
2162
+ }
1595
2163
  case 'capture': {
1596
2164
  let captureSource = null;
1597
2165
  let captureFile;
@@ -1625,12 +2193,84 @@ async function main() {
1625
2193
  await new Promise(() => { }); // run until Ctrl+C
1626
2194
  break;
1627
2195
  }
2196
+ case 'wm':
2197
+ cmdWm(hippoRoot, args, flags);
2198
+ break;
1628
2199
  case 'mcp':
1629
2200
  // Start MCP server over stdio - dynamically import to keep main CLI lean
1630
2201
  await import('./mcp/server.js');
1631
2202
  // Server runs until stdin closes, so we never reach here
1632
2203
  await new Promise(() => { }); // hang forever
1633
2204
  break;
2205
+ case 'invalidate': {
2206
+ requireInit(hippoRoot);
2207
+ const target = args[0];
2208
+ if (!target) {
2209
+ console.error('Usage: hippo invalidate "<old pattern>" [--reason "<why>"]');
2210
+ process.exit(1);
2211
+ }
2212
+ const reason = flags['reason'] || null;
2213
+ const invTarget = {
2214
+ from: target,
2215
+ to: reason,
2216
+ type: 'migration',
2217
+ };
2218
+ const result = invalidateMatching(hippoRoot, invTarget);
2219
+ if (result.invalidated === 0) {
2220
+ console.log(`No memories matched "${target}".`);
2221
+ }
2222
+ else {
2223
+ console.log(`Invalidated ${result.invalidated} memories referencing "${target}".`);
2224
+ result.targets.forEach(id => console.log(` ${id}`));
2225
+ }
2226
+ break;
2227
+ }
2228
+ case 'decide': {
2229
+ requireInit(hippoRoot);
2230
+ const text = args[0];
2231
+ if (!text) {
2232
+ console.error('Usage: hippo decide "<decision>" [--context "<why>"] [--supersedes <id>]');
2233
+ process.exit(1);
2234
+ }
2235
+ const context = flags['context'] || '';
2236
+ const supersedesId = flags['supersedes'] || null;
2237
+ // Build content with context
2238
+ const decisionContent = context ? `${text}\n\nContext: ${context}` : text;
2239
+ // Handle supersession
2240
+ if (supersedesId) {
2241
+ const oldEntry = readEntry(hippoRoot, supersedesId);
2242
+ if (!oldEntry) {
2243
+ console.error(`Memory ${supersedesId} not found.`);
2244
+ process.exit(1);
2245
+ }
2246
+ oldEntry.half_life_days = Math.max(1, Math.floor(oldEntry.half_life_days / 2));
2247
+ oldEntry.confidence = 'stale';
2248
+ if (!oldEntry.tags.includes('superseded'))
2249
+ oldEntry.tags.push('superseded');
2250
+ writeEntry(hippoRoot, oldEntry);
2251
+ console.log(`Superseded ${supersedesId} (half-life halved, marked stale)`);
2252
+ }
2253
+ // Create decision memory
2254
+ const mem = createMemory(decisionContent, {
2255
+ tags: ['decision'],
2256
+ layer: Layer.Semantic,
2257
+ confidence: 'verified',
2258
+ source: 'decision',
2259
+ });
2260
+ mem.half_life_days = DECISION_HALF_LIFE_DAYS;
2261
+ // Auto-tag with path context
2262
+ const decisionPathTags = extractPathTags(process.cwd());
2263
+ for (const pt of decisionPathTags) {
2264
+ if (!mem.tags.includes(pt))
2265
+ mem.tags.push(pt);
2266
+ }
2267
+ writeEntry(hippoRoot, mem);
2268
+ console.log(`Decision recorded: ${mem.id}`);
2269
+ if (supersedesId) {
2270
+ console.log(` Supersedes: ${supersedesId}`);
2271
+ }
2272
+ break;
2273
+ }
1634
2274
  case 'help':
1635
2275
  case '--help':
1636
2276
  case '-h':