hippo-memory 1.22.0 → 1.24.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 +17 -5
- package/bin/hippo.js +2 -2
- package/dist/ablation.d.ts +93 -0
- package/dist/ablation.d.ts.map +1 -0
- package/dist/ablation.js +138 -0
- package/dist/ablation.js.map +1 -0
- package/dist/ambient.d.ts.map +1 -1
- package/dist/ambient.js +2 -1
- package/dist/ambient.js.map +1 -1
- package/dist/api.d.ts +37 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +88 -11
- package/dist/api.js.map +1 -1
- package/dist/benchmarks/e1.3/scenarios.json +2587 -0
- package/dist/capture.d.ts.map +1 -1
- package/dist/capture.js +3 -2
- package/dist/capture.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +211 -40
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +18 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/connectors/github/backfill.js +4 -4
- package/dist/connectors/github/cli-impl.js +6 -6
- package/dist/connectors/github/dlq.js +14 -14
- package/dist/connectors/slack/backfill.js +1 -1
- package/dist/connectors/slack/dlq.js +10 -10
- package/dist/connectors/slack/workspaces.js +4 -4
- package/dist/consolidate.d.ts.map +1 -1
- package/dist/consolidate.js +27 -5
- package/dist/consolidate.js.map +1 -1
- package/dist/dag.js +6 -6
- package/dist/dashboard.js +7 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +55 -4
- package/dist/db.js.map +1 -1
- package/dist/embedding-provider.d.ts +76 -0
- package/dist/embedding-provider.d.ts.map +1 -0
- package/dist/embedding-provider.js +301 -0
- package/dist/embedding-provider.js.map +1 -0
- package/dist/embeddings.d.ts +1 -0
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +112 -55
- package/dist/embeddings.js.map +1 -1
- package/dist/hooks.js +24 -24
- package/dist/importers.d.ts +28 -0
- package/dist/importers.d.ts.map +1 -1
- package/dist/importers.js +412 -1
- package/dist/importers.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +44 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/memory.d.ts +9 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +29 -6
- package/dist/memory.js.map +1 -1
- package/dist/physics-state.d.ts.map +1 -1
- package/dist/physics-state.js +4 -3
- package/dist/physics-state.js.map +1 -1
- package/dist/physics.d.ts.map +1 -1
- package/dist/physics.js +8 -1
- package/dist/physics.js.map +1 -1
- package/dist/predictions.js +67 -67
- package/dist/project-identity.d.ts +84 -0
- package/dist/project-identity.d.ts.map +1 -0
- package/dist/project-identity.js +184 -0
- package/dist/project-identity.js.map +1 -0
- package/dist/refine-llm.js +13 -13
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +4 -1
- package/dist/replay.js.map +1 -1
- package/dist/search.d.ts +14 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +67 -20
- package/dist/search.js.map +1 -1
- package/dist/secret-detect.d.ts +33 -0
- package/dist/secret-detect.d.ts.map +1 -0
- package/dist/secret-detect.js +70 -0
- package/dist/secret-detect.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +11 -0
- package/dist/server.js.map +1 -1
- package/dist/shared.d.ts +9 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +67 -16
- package/dist/shared.js.map +1 -1
- package/dist/src/ablation.js +138 -0
- package/dist/src/ablation.js.map +1 -0
- package/dist/src/ambient.js +2 -1
- package/dist/src/ambient.js.map +1 -1
- package/dist/src/api.js +88 -11
- package/dist/src/api.js.map +1 -1
- package/dist/src/capture.js +3 -2
- package/dist/src/capture.js.map +1 -1
- package/dist/src/cli.js +211 -40
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +3 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/connectors/github/backfill.js +4 -4
- package/dist/src/connectors/github/cli-impl.js +6 -6
- package/dist/src/connectors/github/dlq.js +14 -14
- package/dist/src/connectors/slack/backfill.js +1 -1
- package/dist/src/connectors/slack/dlq.js +10 -10
- package/dist/src/connectors/slack/workspaces.js +4 -4
- package/dist/src/consolidate.js +27 -5
- package/dist/src/consolidate.js.map +1 -1
- package/dist/src/dag.js +6 -6
- package/dist/src/dashboard.js +7 -7
- package/dist/src/db.js +55 -4
- package/dist/src/db.js.map +1 -1
- package/dist/src/embedding-provider.js +301 -0
- package/dist/src/embedding-provider.js.map +1 -0
- package/dist/src/embeddings.js +112 -55
- package/dist/src/embeddings.js.map +1 -1
- package/dist/src/hooks.js +24 -24
- package/dist/src/importers.js +412 -1
- package/dist/src/importers.js.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/server.js +44 -7
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/memory.js +29 -6
- package/dist/src/memory.js.map +1 -1
- package/dist/src/physics-state.js +4 -3
- package/dist/src/physics-state.js.map +1 -1
- package/dist/src/physics.js +8 -1
- package/dist/src/physics.js.map +1 -1
- package/dist/src/predictions.js +67 -67
- package/dist/src/project-identity.js +184 -0
- package/dist/src/project-identity.js.map +1 -0
- package/dist/src/refine-llm.js +13 -13
- package/dist/src/replay.js +4 -1
- package/dist/src/replay.js.map +1 -1
- package/dist/src/search.js +67 -20
- package/dist/src/search.js.map +1 -1
- package/dist/src/secret-detect.js +70 -0
- package/dist/src/secret-detect.js.map +1 -0
- package/dist/src/server.js +11 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/shared.js +67 -16
- package/dist/src/shared.js.map +1 -1
- package/dist/src/store.js +66 -11
- package/dist/src/store.js.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/working-memory.js +19 -19
- package/dist/store.d.ts +15 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +66 -11
- package/dist/store.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.js +19 -19
- package/dist-ui/index.html +12 -12
- package/extensions/openclaw-plugin/index.ts +650 -650
- 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 +3 -1
package/dist/src/cli.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* hippo decide "<decision>" [--context "<why>"] [--supersedes <id>]
|
|
26
26
|
* hippo wm <push|read|clear|flush>
|
|
27
27
|
*/
|
|
28
|
+
import { evalNow, isRecallBoostAblated } from './ablation.js';
|
|
28
29
|
import * as path from 'path';
|
|
29
30
|
import * as fs from 'fs';
|
|
30
31
|
import * as os from 'os';
|
|
@@ -32,11 +33,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
32
33
|
import { execFileSync, execSync, spawn } from 'child_process';
|
|
33
34
|
import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInstalledTools, defaultSleepLogPath, ensureCodexWrapperInstalled, installCodexWrapper, uninstallCodexWrapper, resolveCodexSessionTranscript, resolveCodexWrapperPaths, installOpencodePlugin, uninstallOpencodePlugin, resolveOpencodePluginPath, } from './hooks.js';
|
|
34
35
|
import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, computeSchemaFit, Layer, } from './memory.js';
|
|
36
|
+
import { resolveProjectIdentity } from './project-identity.js';
|
|
35
37
|
import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, RECALL_DEFAULT_DENY_SCOPES, } from './store.js';
|
|
36
38
|
import { markRetrieved, hybridSearch, physicsSearch, explainMatch, textOverlap } from './search.js';
|
|
37
39
|
import { renderTraceContent, parseSteps } from './trace.js';
|
|
38
40
|
import { deduplicateStore } from './dedupe.js';
|
|
39
|
-
import { isEmbeddingAvailable, embedAll, embedMemory, loadEmbeddingIndex, resolveEmbeddingModel, } from './embeddings.js';
|
|
41
|
+
import { isEmbeddingAvailable, embedAll, embedMemory, loadEmbeddingIndex, resolveEmbeddingModel, embeddingModelRequiresReindex, } from './embeddings.js';
|
|
42
|
+
import { isEmbeddingConfigured, resolveEmbeddingProvider } from './embedding-provider.js';
|
|
40
43
|
import { loadPhysicsState, resetAllPhysicsState } from './physics-state.js';
|
|
41
44
|
import { computeSystemEnergy, vecNorm } from './physics.js';
|
|
42
45
|
import { loadConfig } from './config.js';
|
|
@@ -49,7 +52,7 @@ import { extractPathTags } from './path-context.js';
|
|
|
49
52
|
import { detectScope } from './scope.js';
|
|
50
53
|
import { getGlobalRoot, initGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
|
|
51
54
|
import { DAILY_TASK_NAME, buildDailyRunnerCommand, listRegisteredWorkspaces, registerWorkspace, runDailyMaintenance, } from './scheduler.js';
|
|
52
|
-
import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, } from './importers.js';
|
|
55
|
+
import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, importVault, } from './importers.js';
|
|
53
56
|
import { cmdCapture } from './capture.js';
|
|
54
57
|
import { auditMemories, appendAuditEvent, } from './audit.js';
|
|
55
58
|
import { listApiKeys, revokeApiKey } from './auth.js';
|
|
@@ -639,8 +642,8 @@ async function cmdRemember(hippoRoot, text, flags) {
|
|
|
639
642
|
console.log(` Tags: ${entry.tags.join(', ')}`);
|
|
640
643
|
if (entry.pinned)
|
|
641
644
|
console.log(' Pinned (no decay)');
|
|
642
|
-
// Auto-embed if available
|
|
643
|
-
if (
|
|
645
|
+
// Auto-embed if available (provider-aware: local dep installed, or API key present)
|
|
646
|
+
if (isEmbeddingConfigured(targetRoot)) {
|
|
644
647
|
embedMemory(targetRoot, entry).catch(() => {
|
|
645
648
|
// Silently ignore embedding errors
|
|
646
649
|
});
|
|
@@ -1481,9 +1484,15 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1481
1484
|
// Update retrieval metadata and persist
|
|
1482
1485
|
const updated = markRetrieved(results.map((r) => r.entry));
|
|
1483
1486
|
const localIndex = loadIndex(hippoRoot);
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
+
// EVAL-ONLY ablation (see ablation.ts): under the recall flag, markRetrieved
|
|
1488
|
+
// returns unmutated entries (ids preserved for outcome attribution below)
|
|
1489
|
+
// and persistence is skipped - writeEntry on identical rows still refreshes
|
|
1490
|
+
// updated_at, rewrites mirrors, and marks DAG parents dirty.
|
|
1491
|
+
if (!isRecallBoostAblated()) {
|
|
1492
|
+
for (const u of updated) {
|
|
1493
|
+
const targetRoot = localIndex.entries[u.id] ? hippoRoot : (isInitialized(globalRoot) ? globalRoot : hippoRoot);
|
|
1494
|
+
writeEntry(targetRoot, u);
|
|
1495
|
+
}
|
|
1487
1496
|
}
|
|
1488
1497
|
// Track last retrieval IDs for outcome command
|
|
1489
1498
|
localIndex.last_retrieval_ids = updated.map((u) => u.id);
|
|
@@ -2090,7 +2099,7 @@ function cmdTrace(hippoRoot, id, flags) {
|
|
|
2090
2099
|
console.error(`Memory not found: ${id}`);
|
|
2091
2100
|
process.exit(1);
|
|
2092
2101
|
}
|
|
2093
|
-
const now =
|
|
2102
|
+
const now = evalNow();
|
|
2094
2103
|
const strength = calculateStrength(entry, now);
|
|
2095
2104
|
const halfLife = deriveHalfLife(7, entry);
|
|
2096
2105
|
const rewardFactor = calculateRewardFactor(entry);
|
|
@@ -2704,7 +2713,7 @@ function cmdStatus(hippoRoot) {
|
|
|
2704
2713
|
requireInit(hippoRoot);
|
|
2705
2714
|
const entries = loadAllEntries(hippoRoot);
|
|
2706
2715
|
const stats = loadStats(hippoRoot);
|
|
2707
|
-
const now =
|
|
2716
|
+
const now = evalNow();
|
|
2708
2717
|
const byLayer = {
|
|
2709
2718
|
[Layer.Buffer]: 0,
|
|
2710
2719
|
[Layer.Episodic]: 0,
|
|
@@ -2762,19 +2771,52 @@ function cmdStatus(hippoRoot) {
|
|
|
2762
2771
|
else {
|
|
2763
2772
|
console.log(`Last sleep: never`);
|
|
2764
2773
|
}
|
|
2765
|
-
// Embedding status
|
|
2766
|
-
const
|
|
2774
|
+
// Embedding status (provider-aware)
|
|
2775
|
+
const embedProvider = (() => {
|
|
2776
|
+
try {
|
|
2777
|
+
return resolveEmbeddingProvider(hippoRoot);
|
|
2778
|
+
}
|
|
2779
|
+
catch {
|
|
2780
|
+
return null;
|
|
2781
|
+
}
|
|
2782
|
+
})();
|
|
2767
2783
|
console.log('');
|
|
2768
|
-
|
|
2769
|
-
|
|
2784
|
+
if (!embedProvider) {
|
|
2785
|
+
console.log(`Embeddings: misconfigured (check embeddings.provider / apiBaseUrl), BM25 only`);
|
|
2786
|
+
}
|
|
2787
|
+
else {
|
|
2788
|
+
const embeddingsDisabled = loadConfig(hippoRoot).embeddings.enabled === false;
|
|
2789
|
+
const embAvail = embedProvider.isAvailable();
|
|
2790
|
+
if (embeddingsDisabled) {
|
|
2791
|
+
console.log(`Embeddings: disabled in config (embeddings.enabled = false), BM25 only`);
|
|
2792
|
+
}
|
|
2793
|
+
else if (embedProvider.kind === 'local') {
|
|
2794
|
+
console.log(`Embeddings: ${embAvail ? `available [${embedProvider.id}]` : 'not installed (BM25 only)'}`);
|
|
2795
|
+
}
|
|
2796
|
+
else if (embAvail) {
|
|
2797
|
+
console.log(`Embeddings: ${embedProvider.kind} api [${embedProvider.id}]`);
|
|
2798
|
+
}
|
|
2799
|
+
else {
|
|
2800
|
+
console.log(`Embeddings: ${embedProvider.kind} configured but ${embedProvider.keyEnv} not set (BM25 only)`);
|
|
2801
|
+
}
|
|
2802
|
+
// Show cached counts whenever vectors exist on disk (even when disabled or
|
|
2803
|
+
// the key was removed), so the user still sees what is already indexed.
|
|
2770
2804
|
const embIndex = loadEmbeddingIndex(hippoRoot);
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
line
|
|
2777
|
-
|
|
2805
|
+
if (embAvail || Object.keys(embIndex).length > 0) {
|
|
2806
|
+
const activeIds = new Set(entries.map((e) => e.id));
|
|
2807
|
+
const activeEmbedded = Object.keys(embIndex).filter((id) => activeIds.has(id)).length;
|
|
2808
|
+
const orphaned = Object.keys(embIndex).length - activeEmbedded;
|
|
2809
|
+
const dims = Object.values(embIndex)[0]?.length;
|
|
2810
|
+
let line = `Embedded: ${activeEmbedded}/${entries.length} memories`;
|
|
2811
|
+
if (dims)
|
|
2812
|
+
line += ` (${dims}-dim)`;
|
|
2813
|
+
if (orphaned > 0)
|
|
2814
|
+
line += ` (${orphaned} orphaned, run \`hippo embed\` to prune)`;
|
|
2815
|
+
console.log(line);
|
|
2816
|
+
if (embeddingModelRequiresReindex(hippoRoot, embedProvider.id, embIndex)) {
|
|
2817
|
+
console.log(` model changed, run \`hippo embed\` to reindex`);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2778
2820
|
}
|
|
2779
2821
|
// Physics status
|
|
2780
2822
|
try {
|
|
@@ -2892,7 +2934,7 @@ function cmdInspect(hippoRoot, id) {
|
|
|
2892
2934
|
console.error(`Memory not found: ${id}`);
|
|
2893
2935
|
process.exit(1);
|
|
2894
2936
|
}
|
|
2895
|
-
const now =
|
|
2937
|
+
const now = evalNow();
|
|
2896
2938
|
const currentStrength = calculateStrength(entry, now);
|
|
2897
2939
|
const lastRetrieved = new Date(entry.last_retrieved);
|
|
2898
2940
|
const created = new Date(entry.created);
|
|
@@ -4877,6 +4919,9 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4877
4919
|
tenantId: resolveTenantId({}),
|
|
4878
4920
|
actor: api.adminActor('cli'),
|
|
4879
4921
|
};
|
|
4922
|
+
// v39 memory scope isolation: --cross-project re-includes other-project
|
|
4923
|
+
// memories (rendered under a demarcated section below).
|
|
4924
|
+
const crossProject = flags['cross-project'] === true;
|
|
4880
4925
|
const opts = {
|
|
4881
4926
|
q: query,
|
|
4882
4927
|
budget,
|
|
@@ -4884,6 +4929,7 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4884
4929
|
pinnedOnly,
|
|
4885
4930
|
scope: ctxActiveScope ?? undefined,
|
|
4886
4931
|
includeRecent: parseCountFlag(flags['include-recent']),
|
|
4932
|
+
crossProject,
|
|
4887
4933
|
};
|
|
4888
4934
|
const result = await api.getContext(ctx, opts);
|
|
4889
4935
|
// Early exit when there's nothing to render (matches pre-extraction behavior).
|
|
@@ -4896,8 +4942,13 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4896
4942
|
// Format + framing are CLI rendering concerns; api.getContext doesn't see them.
|
|
4897
4943
|
const format = String(flags['format'] ?? 'markdown');
|
|
4898
4944
|
const framing = String(flags['framing'] ?? 'observe');
|
|
4899
|
-
// Adapter: ContextResultEntry -> the print-helper input shape.
|
|
4900
|
-
|
|
4945
|
+
// Adapter: ContextResultEntry -> the print-helper input shape. v39:
|
|
4946
|
+
// cross-project inclusions (only present under --cross-project or with
|
|
4947
|
+
// isolation disabled via crossProject) render in their own demarcated
|
|
4948
|
+
// section so they can never masquerade as project memory.
|
|
4949
|
+
const mainEntries = result.entries.filter((r) => r.category !== 'cross-project');
|
|
4950
|
+
const crossEntries = result.entries.filter((r) => r.category === 'cross-project');
|
|
4951
|
+
const renderItems = mainEntries.map((r) => ({
|
|
4901
4952
|
entry: r.entry,
|
|
4902
4953
|
score: r.score,
|
|
4903
4954
|
tokens: r.tokens,
|
|
@@ -4912,6 +4963,8 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4912
4963
|
confidence: r.entry.confidence,
|
|
4913
4964
|
content: r.entry.content,
|
|
4914
4965
|
global: r.isGlobal ?? false,
|
|
4966
|
+
origin: r.origin ?? null,
|
|
4967
|
+
category: r.category ?? null,
|
|
4915
4968
|
}));
|
|
4916
4969
|
console.log(JSON.stringify({
|
|
4917
4970
|
query: query || '*',
|
|
@@ -4939,6 +4992,7 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4939
4992
|
if (renderItems.length > 0) {
|
|
4940
4993
|
printContextMarkdown(renderItems, result.tokens, framing);
|
|
4941
4994
|
}
|
|
4995
|
+
printCrossProjectSection(crossEntries);
|
|
4942
4996
|
}
|
|
4943
4997
|
finally {
|
|
4944
4998
|
console.log = realLog;
|
|
@@ -4968,6 +5022,7 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4968
5022
|
if (renderItems.length > 0) {
|
|
4969
5023
|
printContextMarkdown(renderItems, result.tokens, framing);
|
|
4970
5024
|
}
|
|
5025
|
+
printCrossProjectSection(crossEntries);
|
|
4971
5026
|
// Ambient state summary (CLI-side: api.getContext doesn't load all entries
|
|
4972
5027
|
// post-selection, so we re-load here for the landscape summary).
|
|
4973
5028
|
const ambientConfig = loadConfig(hippoRoot);
|
|
@@ -4975,11 +5030,17 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4975
5030
|
const tenantId = ctx.tenantId;
|
|
4976
5031
|
const globalRoot = getGlobalRoot();
|
|
4977
5032
|
const hasGlobalForAmbient = isInitialized(globalRoot);
|
|
5033
|
+
// v39: the landscape summary must reflect the same admission policy as
|
|
5034
|
+
// the injected entries - otherwise excluded rows leak back in as
|
|
5035
|
+
// aggregate signal (codex P2-13).
|
|
5036
|
+
const isolationOff = ambientConfig.contextProjectIsolation === false;
|
|
5037
|
+
const ambientCurrentName = resolveProjectIdentity(process.cwd()).name;
|
|
5038
|
+
const ambientAdmit = (e) => api.ambientAdmitEntry(e, ambientCurrentName, crossProject || isolationOff);
|
|
4978
5039
|
const localForAmbient = isInitialized(hippoRoot)
|
|
4979
|
-
? loadAllEntries(hippoRoot, tenantId).filter(e => !e.superseded_by)
|
|
5040
|
+
? loadAllEntries(hippoRoot, tenantId).filter(e => !e.superseded_by).filter(ambientAdmit)
|
|
4980
5041
|
: [];
|
|
4981
5042
|
const globalForAmbient = hasGlobalForAmbient
|
|
4982
|
-
? loadAllEntries(globalRoot, tenantId).filter(e => !e.superseded_by)
|
|
5043
|
+
? loadAllEntries(globalRoot, tenantId).filter(e => !e.superseded_by).filter(ambientAdmit)
|
|
4983
5044
|
: [];
|
|
4984
5045
|
const allForAmbient = [...localForAmbient, ...globalForAmbient];
|
|
4985
5046
|
if (allForAmbient.length > 0) {
|
|
@@ -4989,9 +5050,24 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4989
5050
|
}
|
|
4990
5051
|
}
|
|
4991
5052
|
}
|
|
5053
|
+
/**
|
|
5054
|
+
* v39: render cross-project inclusions under an explicit header so agents
|
|
5055
|
+
* (and humans) can tell borrowed context from project memory. Only ever
|
|
5056
|
+
* non-empty when the caller passed --cross-project or disabled isolation.
|
|
5057
|
+
*/
|
|
5058
|
+
function printCrossProjectSection(items) {
|
|
5059
|
+
if (items.length === 0)
|
|
5060
|
+
return;
|
|
5061
|
+
console.log(`\n## Other-project memory (explicitly requested, ${items.length} entries)\n`);
|
|
5062
|
+
for (const item of items) {
|
|
5063
|
+
const originLabel = item.origin === null || item.origin === '' ? 'unknown-origin' : item.origin;
|
|
5064
|
+
const tagStr = item.entry.tags.length > 0 ? ` [${item.entry.tags.join(', ')}]` : '';
|
|
5065
|
+
console.log(`- **[${originLabel}]** ${item.entry.content}${tagStr}`);
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
4992
5068
|
/** @internal — exported for snapshot tests (tests/cli-context-render-snapshot.test.ts). NOT a stable public API. */
|
|
4993
5069
|
export function printContextMarkdown(items, totalTokens, framing = 'observe') {
|
|
4994
|
-
const now =
|
|
5070
|
+
const now = evalNow();
|
|
4995
5071
|
console.log(`## Project Memory (${items.length} entries, ${totalTokens} tokens)\n`);
|
|
4996
5072
|
for (const item of items) {
|
|
4997
5073
|
const e = item.entry;
|
|
@@ -5059,11 +5135,9 @@ function autoDetectContext() {
|
|
|
5059
5135
|
// ---------------------------------------------------------------------------
|
|
5060
5136
|
async function cmdEmbed(hippoRoot, flags) {
|
|
5061
5137
|
requireInit(hippoRoot);
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
return;
|
|
5066
|
-
}
|
|
5138
|
+
// --status and --reset-physics only read cached state, so they must work even
|
|
5139
|
+
// when no provider key is present (e.g. embedded earlier with a key that was
|
|
5140
|
+
// later removed). The provider-availability gate is deferred to the embed path.
|
|
5067
5141
|
if (flags['reset-physics']) {
|
|
5068
5142
|
const entries = loadAllEntries(hippoRoot);
|
|
5069
5143
|
const embIndex = loadEmbeddingIndex(hippoRoot);
|
|
@@ -5093,8 +5167,48 @@ async function cmdEmbed(hippoRoot, flags) {
|
|
|
5093
5167
|
}
|
|
5094
5168
|
return;
|
|
5095
5169
|
}
|
|
5170
|
+
// Embedding (unlike status/reset) needs an available provider.
|
|
5171
|
+
const embedProvider = (() => {
|
|
5172
|
+
try {
|
|
5173
|
+
return resolveEmbeddingProvider(hippoRoot);
|
|
5174
|
+
}
|
|
5175
|
+
catch (err) {
|
|
5176
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
5177
|
+
return null;
|
|
5178
|
+
}
|
|
5179
|
+
})();
|
|
5180
|
+
if (!embedProvider) {
|
|
5181
|
+
process.exitCode = 1;
|
|
5182
|
+
return;
|
|
5183
|
+
}
|
|
5184
|
+
if (!embedProvider.isAvailable()) {
|
|
5185
|
+
if (loadConfig(hippoRoot).embeddings.enabled === false) {
|
|
5186
|
+
console.log('Embeddings are disabled in config (embeddings.enabled = false). Set it to true or "auto" to enable.');
|
|
5187
|
+
return;
|
|
5188
|
+
}
|
|
5189
|
+
if (embedProvider.kind === 'local') {
|
|
5190
|
+
console.log('Embeddings not available. Install @xenova/transformers to enable:');
|
|
5191
|
+
console.log(' npm install @xenova/transformers');
|
|
5192
|
+
}
|
|
5193
|
+
else {
|
|
5194
|
+
console.error(`Embedding provider '${embedProvider.kind}' is configured but ${embedProvider.keyEnv} is not set.`);
|
|
5195
|
+
console.error(`Export ${embedProvider.keyEnv}, or set config.embeddings.provider back to 'local'.`);
|
|
5196
|
+
process.exitCode = 1;
|
|
5197
|
+
}
|
|
5198
|
+
return;
|
|
5199
|
+
}
|
|
5096
5200
|
console.log('Embedding all memories (this may take a moment on first run to download model)...');
|
|
5097
|
-
|
|
5201
|
+
let count;
|
|
5202
|
+
try {
|
|
5203
|
+
count = await embedAll(hippoRoot, resolveEmbeddingModel(hippoRoot));
|
|
5204
|
+
}
|
|
5205
|
+
catch (err) {
|
|
5206
|
+
console.error(`Embedding failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5207
|
+
const partial = loadEmbeddingIndex(hippoRoot);
|
|
5208
|
+
console.error(`Partial progress saved: ${Object.keys(partial).length} embeddings on disk. Re-run \`hippo embed\` to resume.`);
|
|
5209
|
+
process.exitCode = 1;
|
|
5210
|
+
return;
|
|
5211
|
+
}
|
|
5098
5212
|
const entriesAfter = loadAllEntries(hippoRoot);
|
|
5099
5213
|
const embIndexAfter = loadEmbeddingIndex(hippoRoot);
|
|
5100
5214
|
console.log(`Done. ${count} new embeddings created. ${Object.keys(embIndexAfter).length}/${entriesAfter.length} total.`);
|
|
@@ -5126,7 +5240,7 @@ async function cmdWatch(command, hippoRoot) {
|
|
|
5126
5240
|
entry.strength = calculateStrength(entry);
|
|
5127
5241
|
writeEntry(hippoRoot, entry);
|
|
5128
5242
|
updateStats(hippoRoot, { remembered: 1 });
|
|
5129
|
-
if (
|
|
5243
|
+
if (isEmbeddingConfigured(hippoRoot)) {
|
|
5130
5244
|
embedMemory(hippoRoot, entry).catch(() => { });
|
|
5131
5245
|
}
|
|
5132
5246
|
const preview = stderr.trim().slice(0, 80);
|
|
@@ -5190,7 +5304,7 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
|
|
|
5190
5304
|
}
|
|
5191
5305
|
writeEntry(hippoRoot, entry);
|
|
5192
5306
|
updateStats(hippoRoot, { remembered: 1 });
|
|
5193
|
-
if (
|
|
5307
|
+
if (isEmbeddingConfigured(hippoRoot)) {
|
|
5194
5308
|
embedMemory(hippoRoot, entry).catch(() => { });
|
|
5195
5309
|
}
|
|
5196
5310
|
added++;
|
|
@@ -5248,6 +5362,54 @@ function cmdImport(hippoRoot, args, flags) {
|
|
|
5248
5362
|
extraTags,
|
|
5249
5363
|
hippoRoot,
|
|
5250
5364
|
};
|
|
5365
|
+
// K1 vault import: a FOLDER importer that mirrors the connector pattern
|
|
5366
|
+
// (kind='raw' + tag provenance + archiveRaw deletions), so it dispatches
|
|
5367
|
+
// separately from the single-file `importer` function-pointer slot below.
|
|
5368
|
+
// It writes through api.remember/archiveRaw which are tenant-scoped, so we
|
|
5369
|
+
// resolve the tenant and pass it through. --global is not supported for
|
|
5370
|
+
// vault import (the connector raw-archive path is tenant-local).
|
|
5371
|
+
if (flags['vault']) {
|
|
5372
|
+
const folderPath = String(flags['vault']);
|
|
5373
|
+
if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
|
|
5374
|
+
console.error(`Vault folder not found (or not a directory): ${folderPath}`);
|
|
5375
|
+
process.exit(1);
|
|
5376
|
+
}
|
|
5377
|
+
if (useGlobal) {
|
|
5378
|
+
console.error('hippo import --vault does not support --global (raw rows are tenant-local).');
|
|
5379
|
+
process.exit(1);
|
|
5380
|
+
}
|
|
5381
|
+
if (typeof flags['name'] !== 'string' || !flags['name'].trim()) {
|
|
5382
|
+
// --name is the vault identity key for the destructive source-deletion sync;
|
|
5383
|
+
// inferring it from the folder basename let same-basename vaults collide and
|
|
5384
|
+
// clobber each other (codex R10 P2). A valueless `--name` parses as boolean
|
|
5385
|
+
// true, and String(true) === "true" would silently import under vault:true:*
|
|
5386
|
+
// - reject a non-string so it fails fast instead (codex R11 P2).
|
|
5387
|
+
console.error('hippo import --vault requires --name <vault> (a non-empty identity key for source-deletion sync).');
|
|
5388
|
+
process.exit(1);
|
|
5389
|
+
}
|
|
5390
|
+
if (flags['scope'] !== undefined && (typeof flags['scope'] !== 'string' || !flags['scope'].trim())) {
|
|
5391
|
+
// Same valueless-flag trap: a bare `--scope` must not become scope "true".
|
|
5392
|
+
// Example uses the source-prefixed private form, since a bare `private` scope
|
|
5393
|
+
// is NOT treated as private by recall and importVault rejects it (R13 P2).
|
|
5394
|
+
console.error('hippo import --vault: --scope requires a value (e.g. --scope vault:private:notes).');
|
|
5395
|
+
process.exit(1);
|
|
5396
|
+
}
|
|
5397
|
+
const tenantId = resolveTenantId({});
|
|
5398
|
+
const vaultOptions = {
|
|
5399
|
+
...importOptions,
|
|
5400
|
+
tenantId,
|
|
5401
|
+
name: flags['name'] ? String(flags['name']) : undefined,
|
|
5402
|
+
scope: flags['scope'] ? String(flags['scope']) : undefined,
|
|
5403
|
+
};
|
|
5404
|
+
const vaultResult = importVault(folderPath, vaultOptions);
|
|
5405
|
+
console.log(`\nImport Vault: ${folderPath}${dryRun ? ' (dry run - no writes)' : ''}`);
|
|
5406
|
+
console.log(` Notes found: ${vaultResult.total}`);
|
|
5407
|
+
console.log(` ${dryRun ? 'Would import: ' : 'Imported: '}${vaultResult.imported}`);
|
|
5408
|
+
console.log(` Skipped (unchanged): ${vaultResult.skipped}`);
|
|
5409
|
+
console.log(` ${dryRun ? 'Would archive: ' : 'Archived (removed): '}${vaultResult.archived ?? 0}`);
|
|
5410
|
+
console.log(` Store: ${hippoRoot}`);
|
|
5411
|
+
return;
|
|
5412
|
+
}
|
|
5251
5413
|
// Determine which importer to use based on flag
|
|
5252
5414
|
let filePath;
|
|
5253
5415
|
let importer;
|
|
@@ -5284,7 +5446,7 @@ function cmdImport(hippoRoot, args, flags) {
|
|
|
5284
5446
|
importerName = 'File';
|
|
5285
5447
|
}
|
|
5286
5448
|
if (!filePath || !importer) {
|
|
5287
|
-
console.error('Usage: hippo import <--chatgpt|--claude|--cursor|--file|--markdown> <path>');
|
|
5449
|
+
console.error('Usage: hippo import <--chatgpt|--claude|--cursor|--file|--markdown|--vault> <path>');
|
|
5288
5450
|
process.exit(1);
|
|
5289
5451
|
}
|
|
5290
5452
|
if (!fs.existsSync(filePath)) {
|
|
@@ -5340,15 +5502,17 @@ function cmdPromote(hippoRoot, id) {
|
|
|
5340
5502
|
// ---------------------------------------------------------------------------
|
|
5341
5503
|
// Sync command
|
|
5342
5504
|
// ---------------------------------------------------------------------------
|
|
5343
|
-
function cmdSync(hippoRoot) {
|
|
5505
|
+
function cmdSync(hippoRoot, flags = {}) {
|
|
5344
5506
|
requireInit(hippoRoot);
|
|
5345
5507
|
const globalRoot = getGlobalRoot();
|
|
5346
5508
|
if (!isInitialized(globalRoot)) {
|
|
5347
5509
|
console.log('No global store found. Run `hippo init --global` first.');
|
|
5348
5510
|
return;
|
|
5349
5511
|
}
|
|
5350
|
-
|
|
5351
|
-
|
|
5512
|
+
// v39: other-project rows are skipped by default; secrets always are.
|
|
5513
|
+
const includeCrossProject = flags['cross-project'] === true;
|
|
5514
|
+
const count = syncGlobalToLocal(hippoRoot, globalRoot, { includeCrossProject });
|
|
5515
|
+
console.log(`Synced ${count} global memories into local project.${includeCrossProject ? '' : ' (other-project rows skipped; use --cross-project to include them)'}`);
|
|
5352
5516
|
}
|
|
5353
5517
|
// ---------------------------------------------------------------------------
|
|
5354
5518
|
// Hook install/uninstall
|
|
@@ -5364,7 +5528,11 @@ const HOOKS = {
|
|
|
5364
5528
|
content: `
|
|
5365
5529
|
## Project Memory (Hippo)
|
|
5366
5530
|
|
|
5367
|
-
|
|
5531
|
+
Pinned rules and recent writes auto-inject at every prompt via the installed
|
|
5532
|
+
UserPromptSubmit hook; never re-run that part manually. At the START of a
|
|
5533
|
+
task (not per prompt), additionally load task-specific context: git-aware
|
|
5534
|
+
recall over the full store that per-prompt injection does not cover. Also
|
|
5535
|
+
run it if the hook is not installed:
|
|
5368
5536
|
\`\`\`bash
|
|
5369
5537
|
hippo context --auto --budget 1500
|
|
5370
5538
|
\`\`\`
|
|
@@ -7004,6 +7172,9 @@ Commands:
|
|
|
7004
7172
|
--cursor <path> Import from .cursorrules or .cursor/rules
|
|
7005
7173
|
--file <path> Import from any markdown or text file
|
|
7006
7174
|
--markdown <path> Import from structured MEMORY.md / AGENTS.md
|
|
7175
|
+
--vault <path> Import a markdown-vault FOLDER as kind='raw' notes
|
|
7176
|
+
(Obsidian/Foam/Dendron). Requires --name <vault>.
|
|
7177
|
+
[--scope <scope>]
|
|
7007
7178
|
--dry-run Preview without writing
|
|
7008
7179
|
--global Write to global store ($HIPPO_HOME or ~/.hippo/)
|
|
7009
7180
|
--tag <tag> Add extra tag (repeatable)
|
|
@@ -7598,7 +7769,7 @@ async function main() {
|
|
|
7598
7769
|
break;
|
|
7599
7770
|
}
|
|
7600
7771
|
case 'sync':
|
|
7601
|
-
cmdSync(hippoRoot);
|
|
7772
|
+
cmdSync(hippoRoot, flags);
|
|
7602
7773
|
break;
|
|
7603
7774
|
case 'share': {
|
|
7604
7775
|
const shareId = args[0];
|