hippo-memory 1.23.0 → 1.24.1
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 +16 -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/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +133 -20
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -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/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 +36 -7
- 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/cli.js +133 -20
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +2 -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/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 +36 -7
- 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/cli.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ import { MemoryEntry } from './memory.js';
|
|
|
29
29
|
import * as api from './api.js';
|
|
30
30
|
/** Test-only: reset the module-level recall-history Map. Call from beforeEach. */
|
|
31
31
|
export declare function __resetSessionRecallHistoryCli(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Scan for Claude Code MEMORY.md files and import new entries into hippo.
|
|
34
|
+
* Looks in ~/.claude/projects/<project>/memory/ for .md files with YAML frontmatter.
|
|
35
|
+
*/
|
|
36
|
+
export declare function learnFromMemoryMd(hippoRoot: string, homeDir?: string): number;
|
|
32
37
|
/**
|
|
33
38
|
* Render an api.sleep result as console output, byte-identical to the
|
|
34
39
|
* pre-extraction inline implementation in cmdSleepCore.
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAyBH,OAAO,EASL,WAAW,EAEZ,MAAM,aAAa,CAAC;AAsGrB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AA4ChC,kFAAkF;AAClF,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAguED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,MAAqB,GAAG,MAAM,CAyE3F;AAyGD;;;GAGG;AACH,oHAAoH;AACpH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAkD/D;AA8vFD,oHAAoH;AACpH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,EACtF,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,MAAkB,GAC1B,IAAI,CA6BN"}
|
package/dist/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,6 +33,8 @@ 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';
|
|
37
|
+
import { detectSecret } from './secret-detect.js';
|
|
35
38
|
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
39
|
import { markRetrieved, hybridSearch, physicsSearch, explainMatch, textOverlap } from './search.js';
|
|
37
40
|
import { renderTraceContent, parseSteps } from './trace.js';
|
|
@@ -50,7 +53,7 @@ import { extractPathTags } from './path-context.js';
|
|
|
50
53
|
import { detectScope } from './scope.js';
|
|
51
54
|
import { getGlobalRoot, initGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
|
|
52
55
|
import { DAILY_TASK_NAME, buildDailyRunnerCommand, listRegisteredWorkspaces, registerWorkspace, runDailyMaintenance, } from './scheduler.js';
|
|
53
|
-
import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, } from './importers.js';
|
|
56
|
+
import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, importVault, } from './importers.js';
|
|
54
57
|
import { cmdCapture } from './capture.js';
|
|
55
58
|
import { auditMemories, appendAuditEvent, } from './audit.js';
|
|
56
59
|
import { listApiKeys, revokeApiKey } from './auth.js';
|
|
@@ -1482,9 +1485,15 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
1482
1485
|
// Update retrieval metadata and persist
|
|
1483
1486
|
const updated = markRetrieved(results.map((r) => r.entry));
|
|
1484
1487
|
const localIndex = loadIndex(hippoRoot);
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
+
// EVAL-ONLY ablation (see ablation.ts): under the recall flag, markRetrieved
|
|
1489
|
+
// returns unmutated entries (ids preserved for outcome attribution below)
|
|
1490
|
+
// and persistence is skipped - writeEntry on identical rows still refreshes
|
|
1491
|
+
// updated_at, rewrites mirrors, and marks DAG parents dirty.
|
|
1492
|
+
if (!isRecallBoostAblated()) {
|
|
1493
|
+
for (const u of updated) {
|
|
1494
|
+
const targetRoot = localIndex.entries[u.id] ? hippoRoot : (isInitialized(globalRoot) ? globalRoot : hippoRoot);
|
|
1495
|
+
writeEntry(targetRoot, u);
|
|
1496
|
+
}
|
|
1488
1497
|
}
|
|
1489
1498
|
// Track last retrieval IDs for outcome command
|
|
1490
1499
|
localIndex.last_retrieval_ids = updated.map((u) => u.id);
|
|
@@ -2091,7 +2100,7 @@ function cmdTrace(hippoRoot, id, flags) {
|
|
|
2091
2100
|
console.error(`Memory not found: ${id}`);
|
|
2092
2101
|
process.exit(1);
|
|
2093
2102
|
}
|
|
2094
|
-
const now =
|
|
2103
|
+
const now = evalNow();
|
|
2095
2104
|
const strength = calculateStrength(entry, now);
|
|
2096
2105
|
const halfLife = deriveHalfLife(7, entry);
|
|
2097
2106
|
const rewardFactor = calculateRewardFactor(entry);
|
|
@@ -2216,8 +2225,8 @@ async function cmdRefine(hippoRoot, flags) {
|
|
|
2216
2225
|
* Scan for Claude Code MEMORY.md files and import new entries into hippo.
|
|
2217
2226
|
* Looks in ~/.claude/projects/<project>/memory/ for .md files with YAML frontmatter.
|
|
2218
2227
|
*/
|
|
2219
|
-
function learnFromMemoryMd(hippoRoot) {
|
|
2220
|
-
const home =
|
|
2228
|
+
export function learnFromMemoryMd(hippoRoot, homeDir = os.homedir()) {
|
|
2229
|
+
const home = homeDir;
|
|
2221
2230
|
const memoryDirs = [];
|
|
2222
2231
|
// Claude Code project memories
|
|
2223
2232
|
const claudeProjectsDir = path.join(home, '.claude', 'projects');
|
|
@@ -2235,6 +2244,7 @@ function learnFromMemoryMd(hippoRoot) {
|
|
|
2235
2244
|
return 0;
|
|
2236
2245
|
const existing = loadAllEntries(hippoRoot);
|
|
2237
2246
|
let imported = 0;
|
|
2247
|
+
let skippedSecret = 0;
|
|
2238
2248
|
for (const memDir of memoryDirs) {
|
|
2239
2249
|
try {
|
|
2240
2250
|
const files = fs.readdirSync(memDir).filter(f => f.endsWith('.md') && f !== 'MEMORY.md');
|
|
@@ -2249,6 +2259,15 @@ function learnFromMemoryMd(hippoRoot) {
|
|
|
2249
2259
|
continue;
|
|
2250
2260
|
// Truncate to reasonable size
|
|
2251
2261
|
const content = body.length > 1500 ? body.slice(0, 1500) + ' [truncated]' : body;
|
|
2262
|
+
// Never ingest secret-bearing memory files. Some Claude Code memory
|
|
2263
|
+
// files exist purely to hold a live credential (e.g. an API-key
|
|
2264
|
+
// reference). The scope-isolation secret veto (v1.24.0) gated
|
|
2265
|
+
// share/promote/sync/ambient but missed this import path, so a live
|
|
2266
|
+
// key could land in the store here. Veto it at ingest. (v1.24.1)
|
|
2267
|
+
if (detectSecret({ content, tags: ['claude-code-memory'] }).flagged) {
|
|
2268
|
+
skippedSecret++;
|
|
2269
|
+
continue;
|
|
2270
|
+
}
|
|
2252
2271
|
// Dedup: check if substantially similar content already exists
|
|
2253
2272
|
const isDup = existing.some(e => {
|
|
2254
2273
|
const overlap = textOverlap(content.slice(0, 200), e.content.slice(0, 200));
|
|
@@ -2269,6 +2288,9 @@ function learnFromMemoryMd(hippoRoot) {
|
|
|
2269
2288
|
}
|
|
2270
2289
|
catch { /* skip broken dirs */ }
|
|
2271
2290
|
}
|
|
2291
|
+
if (skippedSecret > 0) {
|
|
2292
|
+
console.log(`Skipped ${skippedSecret} secret-bearing memory file${skippedSecret === 1 ? '' : 's'} (not ingested).`);
|
|
2293
|
+
}
|
|
2272
2294
|
return imported;
|
|
2273
2295
|
}
|
|
2274
2296
|
function cmdDedup(hippoRoot, flags) {
|
|
@@ -2705,7 +2727,7 @@ function cmdStatus(hippoRoot) {
|
|
|
2705
2727
|
requireInit(hippoRoot);
|
|
2706
2728
|
const entries = loadAllEntries(hippoRoot);
|
|
2707
2729
|
const stats = loadStats(hippoRoot);
|
|
2708
|
-
const now =
|
|
2730
|
+
const now = evalNow();
|
|
2709
2731
|
const byLayer = {
|
|
2710
2732
|
[Layer.Buffer]: 0,
|
|
2711
2733
|
[Layer.Episodic]: 0,
|
|
@@ -2926,7 +2948,7 @@ function cmdInspect(hippoRoot, id) {
|
|
|
2926
2948
|
console.error(`Memory not found: ${id}`);
|
|
2927
2949
|
process.exit(1);
|
|
2928
2950
|
}
|
|
2929
|
-
const now =
|
|
2951
|
+
const now = evalNow();
|
|
2930
2952
|
const currentStrength = calculateStrength(entry, now);
|
|
2931
2953
|
const lastRetrieved = new Date(entry.last_retrieved);
|
|
2932
2954
|
const created = new Date(entry.created);
|
|
@@ -4911,6 +4933,9 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4911
4933
|
tenantId: resolveTenantId({}),
|
|
4912
4934
|
actor: api.adminActor('cli'),
|
|
4913
4935
|
};
|
|
4936
|
+
// v39 memory scope isolation: --cross-project re-includes other-project
|
|
4937
|
+
// memories (rendered under a demarcated section below).
|
|
4938
|
+
const crossProject = flags['cross-project'] === true;
|
|
4914
4939
|
const opts = {
|
|
4915
4940
|
q: query,
|
|
4916
4941
|
budget,
|
|
@@ -4918,6 +4943,7 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4918
4943
|
pinnedOnly,
|
|
4919
4944
|
scope: ctxActiveScope ?? undefined,
|
|
4920
4945
|
includeRecent: parseCountFlag(flags['include-recent']),
|
|
4946
|
+
crossProject,
|
|
4921
4947
|
};
|
|
4922
4948
|
const result = await api.getContext(ctx, opts);
|
|
4923
4949
|
// Early exit when there's nothing to render (matches pre-extraction behavior).
|
|
@@ -4930,8 +4956,13 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4930
4956
|
// Format + framing are CLI rendering concerns; api.getContext doesn't see them.
|
|
4931
4957
|
const format = String(flags['format'] ?? 'markdown');
|
|
4932
4958
|
const framing = String(flags['framing'] ?? 'observe');
|
|
4933
|
-
// Adapter: ContextResultEntry -> the print-helper input shape.
|
|
4934
|
-
|
|
4959
|
+
// Adapter: ContextResultEntry -> the print-helper input shape. v39:
|
|
4960
|
+
// cross-project inclusions (only present under --cross-project or with
|
|
4961
|
+
// isolation disabled via crossProject) render in their own demarcated
|
|
4962
|
+
// section so they can never masquerade as project memory.
|
|
4963
|
+
const mainEntries = result.entries.filter((r) => r.category !== 'cross-project');
|
|
4964
|
+
const crossEntries = result.entries.filter((r) => r.category === 'cross-project');
|
|
4965
|
+
const renderItems = mainEntries.map((r) => ({
|
|
4935
4966
|
entry: r.entry,
|
|
4936
4967
|
score: r.score,
|
|
4937
4968
|
tokens: r.tokens,
|
|
@@ -4946,6 +4977,8 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4946
4977
|
confidence: r.entry.confidence,
|
|
4947
4978
|
content: r.entry.content,
|
|
4948
4979
|
global: r.isGlobal ?? false,
|
|
4980
|
+
origin: r.origin ?? null,
|
|
4981
|
+
category: r.category ?? null,
|
|
4949
4982
|
}));
|
|
4950
4983
|
console.log(JSON.stringify({
|
|
4951
4984
|
query: query || '*',
|
|
@@ -4973,6 +5006,7 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
4973
5006
|
if (renderItems.length > 0) {
|
|
4974
5007
|
printContextMarkdown(renderItems, result.tokens, framing);
|
|
4975
5008
|
}
|
|
5009
|
+
printCrossProjectSection(crossEntries);
|
|
4976
5010
|
}
|
|
4977
5011
|
finally {
|
|
4978
5012
|
console.log = realLog;
|
|
@@ -5002,6 +5036,7 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
5002
5036
|
if (renderItems.length > 0) {
|
|
5003
5037
|
printContextMarkdown(renderItems, result.tokens, framing);
|
|
5004
5038
|
}
|
|
5039
|
+
printCrossProjectSection(crossEntries);
|
|
5005
5040
|
// Ambient state summary (CLI-side: api.getContext doesn't load all entries
|
|
5006
5041
|
// post-selection, so we re-load here for the landscape summary).
|
|
5007
5042
|
const ambientConfig = loadConfig(hippoRoot);
|
|
@@ -5009,11 +5044,17 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
5009
5044
|
const tenantId = ctx.tenantId;
|
|
5010
5045
|
const globalRoot = getGlobalRoot();
|
|
5011
5046
|
const hasGlobalForAmbient = isInitialized(globalRoot);
|
|
5047
|
+
// v39: the landscape summary must reflect the same admission policy as
|
|
5048
|
+
// the injected entries - otherwise excluded rows leak back in as
|
|
5049
|
+
// aggregate signal (codex P2-13).
|
|
5050
|
+
const isolationOff = ambientConfig.contextProjectIsolation === false;
|
|
5051
|
+
const ambientCurrentName = resolveProjectIdentity(process.cwd()).name;
|
|
5052
|
+
const ambientAdmit = (e) => api.ambientAdmitEntry(e, ambientCurrentName, crossProject || isolationOff);
|
|
5012
5053
|
const localForAmbient = isInitialized(hippoRoot)
|
|
5013
|
-
? loadAllEntries(hippoRoot, tenantId).filter(e => !e.superseded_by)
|
|
5054
|
+
? loadAllEntries(hippoRoot, tenantId).filter(e => !e.superseded_by).filter(ambientAdmit)
|
|
5014
5055
|
: [];
|
|
5015
5056
|
const globalForAmbient = hasGlobalForAmbient
|
|
5016
|
-
? loadAllEntries(globalRoot, tenantId).filter(e => !e.superseded_by)
|
|
5057
|
+
? loadAllEntries(globalRoot, tenantId).filter(e => !e.superseded_by).filter(ambientAdmit)
|
|
5017
5058
|
: [];
|
|
5018
5059
|
const allForAmbient = [...localForAmbient, ...globalForAmbient];
|
|
5019
5060
|
if (allForAmbient.length > 0) {
|
|
@@ -5023,9 +5064,24 @@ async function cmdContext(hippoRoot, args, flags) {
|
|
|
5023
5064
|
}
|
|
5024
5065
|
}
|
|
5025
5066
|
}
|
|
5067
|
+
/**
|
|
5068
|
+
* v39: render cross-project inclusions under an explicit header so agents
|
|
5069
|
+
* (and humans) can tell borrowed context from project memory. Only ever
|
|
5070
|
+
* non-empty when the caller passed --cross-project or disabled isolation.
|
|
5071
|
+
*/
|
|
5072
|
+
function printCrossProjectSection(items) {
|
|
5073
|
+
if (items.length === 0)
|
|
5074
|
+
return;
|
|
5075
|
+
console.log(`\n## Other-project memory (explicitly requested, ${items.length} entries)\n`);
|
|
5076
|
+
for (const item of items) {
|
|
5077
|
+
const originLabel = item.origin === null || item.origin === '' ? 'unknown-origin' : item.origin;
|
|
5078
|
+
const tagStr = item.entry.tags.length > 0 ? ` [${item.entry.tags.join(', ')}]` : '';
|
|
5079
|
+
console.log(`- **[${originLabel}]** ${item.entry.content}${tagStr}`);
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5026
5082
|
/** @internal — exported for snapshot tests (tests/cli-context-render-snapshot.test.ts). NOT a stable public API. */
|
|
5027
5083
|
export function printContextMarkdown(items, totalTokens, framing = 'observe') {
|
|
5028
|
-
const now =
|
|
5084
|
+
const now = evalNow();
|
|
5029
5085
|
console.log(`## Project Memory (${items.length} entries, ${totalTokens} tokens)\n`);
|
|
5030
5086
|
for (const item of items) {
|
|
5031
5087
|
const e = item.entry;
|
|
@@ -5320,6 +5376,54 @@ function cmdImport(hippoRoot, args, flags) {
|
|
|
5320
5376
|
extraTags,
|
|
5321
5377
|
hippoRoot,
|
|
5322
5378
|
};
|
|
5379
|
+
// K1 vault import: a FOLDER importer that mirrors the connector pattern
|
|
5380
|
+
// (kind='raw' + tag provenance + archiveRaw deletions), so it dispatches
|
|
5381
|
+
// separately from the single-file `importer` function-pointer slot below.
|
|
5382
|
+
// It writes through api.remember/archiveRaw which are tenant-scoped, so we
|
|
5383
|
+
// resolve the tenant and pass it through. --global is not supported for
|
|
5384
|
+
// vault import (the connector raw-archive path is tenant-local).
|
|
5385
|
+
if (flags['vault']) {
|
|
5386
|
+
const folderPath = String(flags['vault']);
|
|
5387
|
+
if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
|
|
5388
|
+
console.error(`Vault folder not found (or not a directory): ${folderPath}`);
|
|
5389
|
+
process.exit(1);
|
|
5390
|
+
}
|
|
5391
|
+
if (useGlobal) {
|
|
5392
|
+
console.error('hippo import --vault does not support --global (raw rows are tenant-local).');
|
|
5393
|
+
process.exit(1);
|
|
5394
|
+
}
|
|
5395
|
+
if (typeof flags['name'] !== 'string' || !flags['name'].trim()) {
|
|
5396
|
+
// --name is the vault identity key for the destructive source-deletion sync;
|
|
5397
|
+
// inferring it from the folder basename let same-basename vaults collide and
|
|
5398
|
+
// clobber each other (codex R10 P2). A valueless `--name` parses as boolean
|
|
5399
|
+
// true, and String(true) === "true" would silently import under vault:true:*
|
|
5400
|
+
// - reject a non-string so it fails fast instead (codex R11 P2).
|
|
5401
|
+
console.error('hippo import --vault requires --name <vault> (a non-empty identity key for source-deletion sync).');
|
|
5402
|
+
process.exit(1);
|
|
5403
|
+
}
|
|
5404
|
+
if (flags['scope'] !== undefined && (typeof flags['scope'] !== 'string' || !flags['scope'].trim())) {
|
|
5405
|
+
// Same valueless-flag trap: a bare `--scope` must not become scope "true".
|
|
5406
|
+
// Example uses the source-prefixed private form, since a bare `private` scope
|
|
5407
|
+
// is NOT treated as private by recall and importVault rejects it (R13 P2).
|
|
5408
|
+
console.error('hippo import --vault: --scope requires a value (e.g. --scope vault:private:notes).');
|
|
5409
|
+
process.exit(1);
|
|
5410
|
+
}
|
|
5411
|
+
const tenantId = resolveTenantId({});
|
|
5412
|
+
const vaultOptions = {
|
|
5413
|
+
...importOptions,
|
|
5414
|
+
tenantId,
|
|
5415
|
+
name: flags['name'] ? String(flags['name']) : undefined,
|
|
5416
|
+
scope: flags['scope'] ? String(flags['scope']) : undefined,
|
|
5417
|
+
};
|
|
5418
|
+
const vaultResult = importVault(folderPath, vaultOptions);
|
|
5419
|
+
console.log(`\nImport Vault: ${folderPath}${dryRun ? ' (dry run - no writes)' : ''}`);
|
|
5420
|
+
console.log(` Notes found: ${vaultResult.total}`);
|
|
5421
|
+
console.log(` ${dryRun ? 'Would import: ' : 'Imported: '}${vaultResult.imported}`);
|
|
5422
|
+
console.log(` Skipped (unchanged): ${vaultResult.skipped}`);
|
|
5423
|
+
console.log(` ${dryRun ? 'Would archive: ' : 'Archived (removed): '}${vaultResult.archived ?? 0}`);
|
|
5424
|
+
console.log(` Store: ${hippoRoot}`);
|
|
5425
|
+
return;
|
|
5426
|
+
}
|
|
5323
5427
|
// Determine which importer to use based on flag
|
|
5324
5428
|
let filePath;
|
|
5325
5429
|
let importer;
|
|
@@ -5356,7 +5460,7 @@ function cmdImport(hippoRoot, args, flags) {
|
|
|
5356
5460
|
importerName = 'File';
|
|
5357
5461
|
}
|
|
5358
5462
|
if (!filePath || !importer) {
|
|
5359
|
-
console.error('Usage: hippo import <--chatgpt|--claude|--cursor|--file|--markdown> <path>');
|
|
5463
|
+
console.error('Usage: hippo import <--chatgpt|--claude|--cursor|--file|--markdown|--vault> <path>');
|
|
5360
5464
|
process.exit(1);
|
|
5361
5465
|
}
|
|
5362
5466
|
if (!fs.existsSync(filePath)) {
|
|
@@ -5412,15 +5516,17 @@ function cmdPromote(hippoRoot, id) {
|
|
|
5412
5516
|
// ---------------------------------------------------------------------------
|
|
5413
5517
|
// Sync command
|
|
5414
5518
|
// ---------------------------------------------------------------------------
|
|
5415
|
-
function cmdSync(hippoRoot) {
|
|
5519
|
+
function cmdSync(hippoRoot, flags = {}) {
|
|
5416
5520
|
requireInit(hippoRoot);
|
|
5417
5521
|
const globalRoot = getGlobalRoot();
|
|
5418
5522
|
if (!isInitialized(globalRoot)) {
|
|
5419
5523
|
console.log('No global store found. Run `hippo init --global` first.');
|
|
5420
5524
|
return;
|
|
5421
5525
|
}
|
|
5422
|
-
|
|
5423
|
-
|
|
5526
|
+
// v39: other-project rows are skipped by default; secrets always are.
|
|
5527
|
+
const includeCrossProject = flags['cross-project'] === true;
|
|
5528
|
+
const count = syncGlobalToLocal(hippoRoot, globalRoot, { includeCrossProject });
|
|
5529
|
+
console.log(`Synced ${count} global memories into local project.${includeCrossProject ? '' : ' (other-project rows skipped; use --cross-project to include them)'}`);
|
|
5424
5530
|
}
|
|
5425
5531
|
// ---------------------------------------------------------------------------
|
|
5426
5532
|
// Hook install/uninstall
|
|
@@ -5436,7 +5542,11 @@ const HOOKS = {
|
|
|
5436
5542
|
content: `
|
|
5437
5543
|
## Project Memory (Hippo)
|
|
5438
5544
|
|
|
5439
|
-
|
|
5545
|
+
Pinned rules and recent writes auto-inject at every prompt via the installed
|
|
5546
|
+
UserPromptSubmit hook; never re-run that part manually. At the START of a
|
|
5547
|
+
task (not per prompt), additionally load task-specific context: git-aware
|
|
5548
|
+
recall over the full store that per-prompt injection does not cover. Also
|
|
5549
|
+
run it if the hook is not installed:
|
|
5440
5550
|
\`\`\`bash
|
|
5441
5551
|
hippo context --auto --budget 1500
|
|
5442
5552
|
\`\`\`
|
|
@@ -7076,6 +7186,9 @@ Commands:
|
|
|
7076
7186
|
--cursor <path> Import from .cursorrules or .cursor/rules
|
|
7077
7187
|
--file <path> Import from any markdown or text file
|
|
7078
7188
|
--markdown <path> Import from structured MEMORY.md / AGENTS.md
|
|
7189
|
+
--vault <path> Import a markdown-vault FOLDER as kind='raw' notes
|
|
7190
|
+
(Obsidian/Foam/Dendron). Requires --name <vault>.
|
|
7191
|
+
[--scope <scope>]
|
|
7079
7192
|
--dry-run Preview without writing
|
|
7080
7193
|
--global Write to global store ($HIPPO_HOME or ~/.hippo/)
|
|
7081
7194
|
--tag <tag> Add extra tag (repeatable)
|
|
@@ -7670,7 +7783,7 @@ async function main() {
|
|
|
7670
7783
|
break;
|
|
7671
7784
|
}
|
|
7672
7785
|
case 'sync':
|
|
7673
|
-
cmdSync(hippoRoot);
|
|
7786
|
+
cmdSync(hippoRoot, flags);
|
|
7674
7787
|
break;
|
|
7675
7788
|
case 'share': {
|
|
7676
7789
|
const shareId = args[0];
|