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 +25 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +246 -24
- package/dist/cli.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +31 -1
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +13 -11
- package/dist/mcp/server.js.map +1 -1
- package/dist/memory.d.ts +11 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +60 -0
- package/dist/memory.js.map +1 -1
- package/dist/search.d.ts +1 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +7 -6
- package/dist/search.js.map +1 -1
- package/dist/shared.d.ts +38 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +172 -1
- package/dist/shared.js.map +1 -1
- package/dist/store.d.ts +36 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +199 -7
- package/dist/store.js.map +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA
|
|
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 {
|
|
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,
|
|
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
|
|
273
|
-
|
|
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 =
|
|
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
|
-
|
|
298
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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;
|