hippo-memory 0.30.1 → 0.32.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 +14 -0
- package/dist/cli.js +166 -24
- package/dist/cli.js.map +1 -1
- package/dist/consolidate.js +2 -0
- package/dist/consolidate.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +14 -1
- package/dist/db.js.map +1 -1
- package/dist/memory.d.ts +3 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +2 -0
- package/dist/memory.js.map +1 -1
- package/dist/scope.d.ts +14 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +35 -0
- package/dist/scope.js.map +1 -0
- package/dist/search.d.ts +12 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +61 -0
- package/dist/search.js.map +1 -1
- package/dist/shared.d.ts +6 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +3 -3
- package/dist/shared.js.map +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +12 -3
- 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
|
@@ -60,6 +60,20 @@ hippo recall "data pipeline issues" --budget 2000
|
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
63
|
+
### What's new in v0.32.0
|
|
64
|
+
|
|
65
|
+
- **Correction without deletion.** `hippo supersede <old-id> "<new content>"` links the old memory as historical truth and creates a successor. Default recall shows only current beliefs; the old one stays in the store so you can audit what changed and when.
|
|
66
|
+
- **`--include-superseded`** on `recall` and `explain` surfaces historical memories with a `[superseded]` marker. Useful for "what did I used to think about X?"
|
|
67
|
+
- **`--as-of <ISO-date>`** returns the set of beliefs that were current at a past moment. Invalid dates exit with a clear format hint.
|
|
68
|
+
- **Schema v11, zero breaking changes.** Adds `valid_from` + `superseded_by` columns. Existing v10 stores upgrade on first open, no data loss, no manual migration.
|
|
69
|
+
- **Physics search ablation: CUT.** Benchmarked over 60 LongMemEval-oracle questions: physics-on is statistically worse than plain BM25 + embeddings on MRR, Recall@5, and NDCG@5 (paired bootstrap, 5000 iters, 95% CI excludes zero). Full results in `benchmarks/physics-ablation/`. Physics stays in the codebase this release; removal is a separate decision.
|
|
70
|
+
|
|
71
|
+
### What's new in v0.31.0
|
|
72
|
+
|
|
73
|
+
- **Scope-aware corrections.** Tag a memory with `hippo remember --scope plan-eng-review` and it only surfaces strongly when that scope is active again. Matching scope gets 1.5x boost, mismatching scope is suppressed 0.5x, unscoped memories stay neutral. Corrections said during one skill stop polluting unrelated contexts.
|
|
74
|
+
- **Auto-detect from env.** `HIPPO_SCOPE`, `GSTACK_SKILL`, `OPENCLAW_SKILL` populate the scope automatically. Explicit `--scope` on any command overrides.
|
|
75
|
+
- **`hippo explain --why`** now shows the `scope:` multiplier when it fires, so you can see why a memory got ranked up or down.
|
|
76
|
+
|
|
63
77
|
### What's new in v0.30.1
|
|
64
78
|
|
|
65
79
|
- **`hippo recall --layer <L>` is now a strict filter.** Previously the flag was accepted but silently dropped; other layers leaked into results. The RSI demo's `recall --layer trace` now does what it says.
|
package/dist/cli.js
CHANGED
|
@@ -44,6 +44,7 @@ import { openHippoDb, closeHippoDb } from './db.js';
|
|
|
44
44
|
import { captureError, extractLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
|
|
45
45
|
import { extractInvalidationTarget, invalidateMatching } from './invalidation.js';
|
|
46
46
|
import { extractPathTags } from './path-context.js';
|
|
47
|
+
import { detectScope, scopeMatch } from './scope.js';
|
|
47
48
|
import { getGlobalRoot, initGlobal, promoteToGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
|
|
48
49
|
import { DAILY_TASK_NAME, buildDailyRunnerCommand, listRegisteredWorkspaces, registerWorkspace, runDailyMaintenance, } from './scheduler.js';
|
|
49
50
|
import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, } from './importers.js';
|
|
@@ -413,6 +414,14 @@ function cmdRemember(hippoRoot, text, flags) {
|
|
|
413
414
|
if (!entry.tags.includes(pt))
|
|
414
415
|
entry.tags.push(pt);
|
|
415
416
|
}
|
|
417
|
+
// Scope tagging: explicit --scope or auto-detected
|
|
418
|
+
const explicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
|
|
419
|
+
const activeScope = explicitScope || detectScope();
|
|
420
|
+
if (activeScope) {
|
|
421
|
+
const scopeTag = `scope:${activeScope}`;
|
|
422
|
+
if (!entry.tags.includes(scopeTag))
|
|
423
|
+
entry.tags.push(scopeTag);
|
|
424
|
+
}
|
|
416
425
|
writeEntry(targetRoot, entry);
|
|
417
426
|
updateStats(targetRoot, { remembered: 1 });
|
|
418
427
|
const prefix = useGlobal ? '[global] ' : '';
|
|
@@ -429,6 +438,37 @@ function cmdRemember(hippoRoot, text, flags) {
|
|
|
429
438
|
});
|
|
430
439
|
}
|
|
431
440
|
}
|
|
441
|
+
function cmdSupersede(hippoRoot, oldId, newContent, flags) {
|
|
442
|
+
requireInit(hippoRoot);
|
|
443
|
+
const old = readEntry(hippoRoot, oldId);
|
|
444
|
+
if (!old) {
|
|
445
|
+
console.error(`Error: memory ${oldId} not found.`);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
if (old.superseded_by) {
|
|
449
|
+
console.error(`Error: memory ${oldId} is already superseded by ${old.superseded_by}. Supersede that one instead.`);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
const layer = (typeof flags['layer'] === 'string' ? flags['layer'] : old.layer);
|
|
453
|
+
const rawTags = flags['tag'];
|
|
454
|
+
const tags = Array.isArray(rawTags)
|
|
455
|
+
? rawTags.map((t) => String(t))
|
|
456
|
+
: typeof rawTags === 'string'
|
|
457
|
+
? rawTags.split(',').map((t) => t.trim()).filter(Boolean)
|
|
458
|
+
: [...old.tags];
|
|
459
|
+
const pinned = flags['pin'] === true || old.pinned;
|
|
460
|
+
const newEntry = createMemory(newContent, {
|
|
461
|
+
layer,
|
|
462
|
+
tags,
|
|
463
|
+
pinned,
|
|
464
|
+
source: old.source,
|
|
465
|
+
confidence: 'verified',
|
|
466
|
+
});
|
|
467
|
+
old.superseded_by = newEntry.id;
|
|
468
|
+
writeEntry(hippoRoot, old);
|
|
469
|
+
writeEntry(hippoRoot, newEntry);
|
|
470
|
+
console.log(`Superseded ${oldId} → ${newEntry.id}`);
|
|
471
|
+
}
|
|
432
472
|
async function cmdRecall(hippoRoot, query, flags) {
|
|
433
473
|
requireInit(hippoRoot);
|
|
434
474
|
const budget = parseInt(String(flags['budget'] ?? '4000'), 10);
|
|
@@ -437,9 +477,43 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
437
477
|
const showWhy = Boolean(flags['why']);
|
|
438
478
|
const forcePhysics = Boolean(flags['physics']);
|
|
439
479
|
const forceClassic = Boolean(flags['classic']);
|
|
480
|
+
const includeSuperseded = Boolean(flags['include-superseded']);
|
|
481
|
+
const asOf = typeof flags['as-of'] === 'string' ? flags['as-of'] : undefined;
|
|
482
|
+
if (asOf !== undefined && Number.isNaN(new Date(asOf).getTime())) {
|
|
483
|
+
console.error(`Error: --as-of value "${asOf}" is not a valid ISO date (e.g. 2026-04-22 or 2026-04-22T12:00:00Z).`);
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
440
486
|
const globalRoot = getGlobalRoot();
|
|
441
|
-
|
|
442
|
-
|
|
487
|
+
let localEntries = loadSearchEntries(hippoRoot, query);
|
|
488
|
+
let globalEntries = isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query) : [];
|
|
489
|
+
// Bi-temporal filtering for physics path (hybridSearch handles it internally)
|
|
490
|
+
if (asOf) {
|
|
491
|
+
const filterAsOf = (entries) => {
|
|
492
|
+
const asOfDate = new Date(asOf);
|
|
493
|
+
const successorValidFrom = new Map();
|
|
494
|
+
for (const e of entries) {
|
|
495
|
+
if (e.superseded_by) {
|
|
496
|
+
const successor = entries.find(s => s.id === e.superseded_by);
|
|
497
|
+
if (successor)
|
|
498
|
+
successorValidFrom.set(e.id, successor.valid_from);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return entries.filter(e => {
|
|
502
|
+
if (new Date(e.valid_from) > asOfDate)
|
|
503
|
+
return false;
|
|
504
|
+
if (!e.superseded_by)
|
|
505
|
+
return true;
|
|
506
|
+
const succVf = successorValidFrom.get(e.id);
|
|
507
|
+
return succVf ? new Date(succVf) > asOfDate : true;
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
localEntries = filterAsOf(localEntries);
|
|
511
|
+
globalEntries = filterAsOf(globalEntries);
|
|
512
|
+
}
|
|
513
|
+
else if (!includeSuperseded) {
|
|
514
|
+
localEntries = localEntries.filter(e => !e.superseded_by);
|
|
515
|
+
globalEntries = globalEntries.filter(e => !e.superseded_by);
|
|
516
|
+
}
|
|
443
517
|
const hasGlobal = globalEntries.length > 0;
|
|
444
518
|
// Determine search mode: --physics forces physics, --classic forces BM25+cosine,
|
|
445
519
|
// default uses physics if config.physics.enabled is not false
|
|
@@ -459,6 +533,8 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
459
533
|
const minResults = flags['min-results'] !== undefined
|
|
460
534
|
? parseInt(String(flags['min-results']), 10)
|
|
461
535
|
: undefined;
|
|
536
|
+
const recallExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
|
|
537
|
+
const recallActiveScope = recallExplicitScope || detectScope();
|
|
462
538
|
let results;
|
|
463
539
|
if (usePhysics && !hasGlobal) {
|
|
464
540
|
results = await physicsSearch(query, localEntries, {
|
|
@@ -466,17 +542,18 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
466
542
|
hippoRoot,
|
|
467
543
|
physicsConfig: config.physics,
|
|
468
544
|
minResults,
|
|
545
|
+
scope: recallActiveScope,
|
|
469
546
|
});
|
|
470
547
|
}
|
|
471
548
|
else if (hasGlobal) {
|
|
472
549
|
// Use searchBothHybrid for merged results with embedding support
|
|
473
550
|
results = await searchBothHybrid(query, hippoRoot, globalRoot, {
|
|
474
|
-
budget, mmr: mmrEnabled, mmrLambda, localBump, minResults,
|
|
551
|
+
budget, mmr: mmrEnabled, mmrLambda, localBump, minResults, scope: recallActiveScope,
|
|
475
552
|
});
|
|
476
553
|
}
|
|
477
554
|
else {
|
|
478
555
|
results = await hybridSearch(query, localEntries, {
|
|
479
|
-
budget, hippoRoot, mmr: mmrEnabled, mmrLambda, minResults,
|
|
556
|
+
budget, hippoRoot, mmr: mmrEnabled, mmrLambda, minResults, scope: recallActiveScope,
|
|
480
557
|
});
|
|
481
558
|
}
|
|
482
559
|
// --outcome filter: drop trace entries whose trace_outcome !== target.
|
|
@@ -543,6 +620,10 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
543
620
|
if (r.entry.layer === Layer.Trace) {
|
|
544
621
|
base.trace_outcome = r.entry.trace_outcome;
|
|
545
622
|
}
|
|
623
|
+
if (r.entry.superseded_by) {
|
|
624
|
+
base.superseded = true;
|
|
625
|
+
base.superseded_by = r.entry.superseded_by;
|
|
626
|
+
}
|
|
546
627
|
if (showWhy) {
|
|
547
628
|
const explanation = explainMatch(query, r);
|
|
548
629
|
base.confidence = resolveConfidence(r.entry);
|
|
@@ -565,8 +646,9 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
565
646
|
const strengthBar = '\u2588'.repeat(Math.round(e.strength * 10)) + '\u2591'.repeat(10 - Math.round(e.strength * 10));
|
|
566
647
|
const isGlobal = isInitialized(globalRoot) && !localIndex.entries[e.id];
|
|
567
648
|
const globalMark = isGlobal ? ' [global]' : '';
|
|
649
|
+
const supersededMark = e.superseded_by ? ' [superseded]' : '';
|
|
568
650
|
const sourceMark = isGlobal ? ' [global]' : ' [local]';
|
|
569
|
-
console.log(`--- ${e.id} [${e.layer}] ${confLabel}${globalMark} score=${fmt(r.score, 3)} strength=${fmt(e.strength)}`);
|
|
651
|
+
console.log(`--- ${e.id} [${e.layer}] ${confLabel}${globalMark}${supersededMark} score=${fmt(r.score, 3)} strength=${fmt(e.strength)}`);
|
|
570
652
|
console.log(` [${strengthBar}] tags: ${e.tags.join(', ') || 'none'} | retrieved: ${e.retrieval_count}x`);
|
|
571
653
|
if (showWhy) {
|
|
572
654
|
const explanation = explainMatch(query, r);
|
|
@@ -585,10 +667,44 @@ async function cmdExplain(hippoRoot, query, flags) {
|
|
|
585
667
|
const asJson = Boolean(flags['json']);
|
|
586
668
|
const forcePhysics = Boolean(flags['physics']);
|
|
587
669
|
const forceClassic = Boolean(flags['classic']);
|
|
670
|
+
const explainIncludeSuperseded = Boolean(flags['include-superseded']);
|
|
671
|
+
const explainAsOf = typeof flags['as-of'] === 'string' ? flags['as-of'] : undefined;
|
|
672
|
+
if (explainAsOf !== undefined && Number.isNaN(new Date(explainAsOf).getTime())) {
|
|
673
|
+
console.error(`Error: --as-of value "${explainAsOf}" is not a valid ISO date (e.g. 2026-04-22 or 2026-04-22T12:00:00Z).`);
|
|
674
|
+
process.exit(1);
|
|
675
|
+
}
|
|
588
676
|
const globalRoot = getGlobalRoot();
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
677
|
+
let explainLocalEntries = loadSearchEntries(hippoRoot, query);
|
|
678
|
+
let explainGlobalEntries = isInitialized(globalRoot) ? loadSearchEntries(globalRoot, query) : [];
|
|
679
|
+
// Bi-temporal filtering
|
|
680
|
+
if (explainAsOf) {
|
|
681
|
+
const filterAsOfExplain = (entries) => {
|
|
682
|
+
const asOfDate = new Date(explainAsOf);
|
|
683
|
+
const successorValidFrom = new Map();
|
|
684
|
+
for (const e of entries) {
|
|
685
|
+
if (e.superseded_by) {
|
|
686
|
+
const successor = entries.find(s => s.id === e.superseded_by);
|
|
687
|
+
if (successor)
|
|
688
|
+
successorValidFrom.set(e.id, successor.valid_from);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return entries.filter(e => {
|
|
692
|
+
if (new Date(e.valid_from) > asOfDate)
|
|
693
|
+
return false;
|
|
694
|
+
if (!e.superseded_by)
|
|
695
|
+
return true;
|
|
696
|
+
const succVf = successorValidFrom.get(e.id);
|
|
697
|
+
return succVf ? new Date(succVf) > asOfDate : true;
|
|
698
|
+
});
|
|
699
|
+
};
|
|
700
|
+
explainLocalEntries = filterAsOfExplain(explainLocalEntries);
|
|
701
|
+
explainGlobalEntries = filterAsOfExplain(explainGlobalEntries);
|
|
702
|
+
}
|
|
703
|
+
else if (!explainIncludeSuperseded) {
|
|
704
|
+
explainLocalEntries = explainLocalEntries.filter(e => !e.superseded_by);
|
|
705
|
+
explainGlobalEntries = explainGlobalEntries.filter(e => !e.superseded_by);
|
|
706
|
+
}
|
|
707
|
+
const hasGlobal = explainGlobalEntries.length > 0;
|
|
592
708
|
const config = loadConfig(hippoRoot);
|
|
593
709
|
const usePhysics = forcePhysics
|
|
594
710
|
|| (!forceClassic && config.physics.enabled !== false);
|
|
@@ -602,33 +718,38 @@ async function cmdExplain(hippoRoot, query, flags) {
|
|
|
602
718
|
: flags['local-bump'] !== undefined
|
|
603
719
|
? parseFloat(String(flags['local-bump']))
|
|
604
720
|
: config.search.localBump;
|
|
721
|
+
const explainExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
|
|
722
|
+
const explainActiveScope = explainExplicitScope || detectScope();
|
|
605
723
|
let results;
|
|
606
724
|
let modeUsed;
|
|
607
725
|
if (usePhysics && !hasGlobal) {
|
|
608
|
-
results = await physicsSearch(query,
|
|
726
|
+
results = await physicsSearch(query, explainLocalEntries, {
|
|
609
727
|
budget,
|
|
610
728
|
hippoRoot,
|
|
611
729
|
physicsConfig: config.physics,
|
|
612
730
|
explain: true,
|
|
731
|
+
scope: explainActiveScope,
|
|
613
732
|
});
|
|
614
733
|
modeUsed = 'physics';
|
|
615
734
|
}
|
|
616
735
|
else if (hasGlobal) {
|
|
617
736
|
results = await searchBothHybrid(query, hippoRoot, globalRoot, {
|
|
618
|
-
budget, explain: true, mmr: mmrEnabled, mmrLambda, localBump,
|
|
737
|
+
budget, explain: true, mmr: mmrEnabled, mmrLambda, localBump, scope: explainActiveScope,
|
|
738
|
+
includeSuperseded: explainIncludeSuperseded, asOf: explainAsOf,
|
|
619
739
|
});
|
|
620
740
|
modeUsed = 'searchBothHybrid';
|
|
621
741
|
}
|
|
622
742
|
else {
|
|
623
|
-
results = await hybridSearch(query,
|
|
624
|
-
budget, hippoRoot, explain: true, mmr: mmrEnabled, mmrLambda,
|
|
743
|
+
results = await hybridSearch(query, explainLocalEntries, {
|
|
744
|
+
budget, hippoRoot, explain: true, mmr: mmrEnabled, mmrLambda, scope: explainActiveScope,
|
|
745
|
+
includeSuperseded: explainIncludeSuperseded, asOf: explainAsOf,
|
|
625
746
|
});
|
|
626
747
|
modeUsed = 'hybrid';
|
|
627
748
|
}
|
|
628
749
|
if (limit < results.length) {
|
|
629
750
|
results = results.slice(0, limit);
|
|
630
751
|
}
|
|
631
|
-
const candidates =
|
|
752
|
+
const candidates = explainLocalEntries.length + explainGlobalEntries.length;
|
|
632
753
|
if (asJson) {
|
|
633
754
|
const output = results.map((r, rank) => ({
|
|
634
755
|
rank: rank + 1,
|
|
@@ -691,6 +812,8 @@ async function cmdExplain(hippoRoot, query, flags) {
|
|
|
691
812
|
console.log(` recency: x${fmt(b.recencyMultiplier, 3)} (age=${b.ageDays}d)`);
|
|
692
813
|
if (b.decisionBoost !== 1)
|
|
693
814
|
console.log(` decision: x${fmt(b.decisionBoost, 2)} (tagged 'decision')`);
|
|
815
|
+
if (b.scopeBoost !== 1)
|
|
816
|
+
console.log(` scope: x${fmt(b.scopeBoost, 2)} (scope tag ${b.scopeBoost > 1 ? 'match' : 'mismatch'})`);
|
|
694
817
|
if (b.pathBoost !== 1)
|
|
695
818
|
console.log(` path: x${fmt(b.pathBoost, 3)} (cwd path tag overlap)`);
|
|
696
819
|
if (b.sourceBump !== 1)
|
|
@@ -2190,6 +2313,8 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2190
2313
|
}
|
|
2191
2314
|
const budget = parseInt(String(flags['budget'] ?? '1500'), 10);
|
|
2192
2315
|
const limit = parseLimitFlag(flags['limit']);
|
|
2316
|
+
const ctxExplicitScope = flags['scope'] !== undefined ? String(flags['scope']).trim() : null;
|
|
2317
|
+
const ctxActiveScope = ctxExplicitScope || detectScope();
|
|
2193
2318
|
// If budget is 0, skip entirely (zero token cost)
|
|
2194
2319
|
if (budget <= 0)
|
|
2195
2320
|
return;
|
|
@@ -2207,8 +2332,11 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2207
2332
|
// When the local store isn't initialized (pinned-only path in a fresh dir),
|
|
2208
2333
|
// skip the local load — loadAllEntries would auto-create .hippo here and
|
|
2209
2334
|
// we don't want to pollute arbitrary cwds.
|
|
2210
|
-
|
|
2211
|
-
|
|
2335
|
+
let localEntries = hasLocal ? loadAllEntries(hippoRoot) : [];
|
|
2336
|
+
let globalEntries = hasGlobal ? loadAllEntries(globalRoot) : [];
|
|
2337
|
+
// Default context always filters superseded (no --include-superseded / --as-of for context)
|
|
2338
|
+
localEntries = localEntries.filter(e => !e.superseded_by);
|
|
2339
|
+
globalEntries = globalEntries.filter(e => !e.superseded_by);
|
|
2212
2340
|
const allEntries = [...localEntries];
|
|
2213
2341
|
if (allEntries.length === 0 && globalEntries.length === 0)
|
|
2214
2342
|
return; // no memories, zero output
|
|
@@ -2239,12 +2367,16 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2239
2367
|
...pinnedLocal.map((e) => ({ entry: e, isGlobal: false })),
|
|
2240
2368
|
...pinnedGlobal.map((e) => ({ entry: e, isGlobal: true })),
|
|
2241
2369
|
]
|
|
2242
|
-
.map(({ entry, isGlobal }) =>
|
|
2243
|
-
entry,
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2370
|
+
.map(({ entry, isGlobal }) => {
|
|
2371
|
+
const scopeSig = scopeMatch(entry.tags, ctxActiveScope);
|
|
2372
|
+
const sBst = scopeSig === 1 ? 1.5 : scopeSig === -1 ? 0.5 : 1.0;
|
|
2373
|
+
return {
|
|
2374
|
+
entry,
|
|
2375
|
+
score: calculateStrength(entry, nowP) * (isGlobal ? 1 / 1.2 : 1) * sBst,
|
|
2376
|
+
tokens: estimateTokens(entry.content),
|
|
2377
|
+
isGlobal,
|
|
2378
|
+
};
|
|
2379
|
+
})
|
|
2248
2380
|
.sort((a, b) => b.score - a.score);
|
|
2249
2381
|
let usedP = 0;
|
|
2250
2382
|
for (const r of rankedPinned) {
|
|
@@ -2287,7 +2419,7 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2287
2419
|
else {
|
|
2288
2420
|
let results;
|
|
2289
2421
|
if (hasGlobal) {
|
|
2290
|
-
const merged = await searchBothHybrid(query, hippoRoot, globalRoot, { budget });
|
|
2422
|
+
const merged = await searchBothHybrid(query, hippoRoot, globalRoot, { budget, scope: ctxActiveScope });
|
|
2291
2423
|
const localIndex = loadIndex(hippoRoot);
|
|
2292
2424
|
results = merged.map((r) => ({
|
|
2293
2425
|
entry: r.entry,
|
|
@@ -2300,8 +2432,8 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
2300
2432
|
const ctxConfig = loadConfig(hippoRoot);
|
|
2301
2433
|
const usePhysicsCtx = ctxConfig.physics?.enabled !== false;
|
|
2302
2434
|
const ctxResults = usePhysicsCtx
|
|
2303
|
-
? await physicsSearch(query, localEntries, { budget, hippoRoot, physicsConfig: ctxConfig.physics })
|
|
2304
|
-
: await hybridSearch(query, localEntries, { budget, hippoRoot });
|
|
2435
|
+
? await physicsSearch(query, localEntries, { budget, hippoRoot, physicsConfig: ctxConfig.physics, scope: ctxActiveScope })
|
|
2436
|
+
: await hybridSearch(query, localEntries, { budget, hippoRoot, scope: ctxActiveScope });
|
|
2305
2437
|
results = ctxResults.map((r) => ({
|
|
2306
2438
|
entry: r.entry,
|
|
2307
2439
|
score: r.score,
|
|
@@ -3518,6 +3650,16 @@ async function main() {
|
|
|
3518
3650
|
await cmdRecall(hippoRoot, query, flags);
|
|
3519
3651
|
break;
|
|
3520
3652
|
}
|
|
3653
|
+
case 'supersede': {
|
|
3654
|
+
const oldId = args[0];
|
|
3655
|
+
const newContent = args.slice(1).join(' ').trim();
|
|
3656
|
+
if (!oldId || !newContent) {
|
|
3657
|
+
console.error('Usage: hippo supersede <old-id> "<new content>" [--layer L] [--tag T] [--pin]');
|
|
3658
|
+
process.exit(1);
|
|
3659
|
+
}
|
|
3660
|
+
cmdSupersede(hippoRoot, oldId, newContent, flags);
|
|
3661
|
+
break;
|
|
3662
|
+
}
|
|
3521
3663
|
case 'explain': {
|
|
3522
3664
|
const query = args.join(' ').trim();
|
|
3523
3665
|
if (!query) {
|