hippo-memory 1.23.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.
Files changed (147) hide show
  1. package/README.md +16 -5
  2. package/bin/hippo.js +2 -2
  3. package/dist/ablation.d.ts +93 -0
  4. package/dist/ablation.d.ts.map +1 -0
  5. package/dist/ablation.js +138 -0
  6. package/dist/ablation.js.map +1 -0
  7. package/dist/ambient.d.ts.map +1 -1
  8. package/dist/ambient.js +2 -1
  9. package/dist/ambient.js.map +1 -1
  10. package/dist/api.d.ts +37 -0
  11. package/dist/api.d.ts.map +1 -1
  12. package/dist/api.js +88 -11
  13. package/dist/api.js.map +1 -1
  14. package/dist/benchmarks/e1.3/scenarios.json +2587 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +117 -18
  17. package/dist/cli.js.map +1 -1
  18. package/dist/config.d.ts +8 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +2 -0
  21. package/dist/config.js.map +1 -1
  22. package/dist/connectors/github/backfill.js +4 -4
  23. package/dist/connectors/github/cli-impl.js +6 -6
  24. package/dist/connectors/github/dlq.js +14 -14
  25. package/dist/connectors/slack/backfill.js +1 -1
  26. package/dist/connectors/slack/dlq.js +10 -10
  27. package/dist/connectors/slack/workspaces.js +4 -4
  28. package/dist/consolidate.d.ts.map +1 -1
  29. package/dist/consolidate.js +27 -5
  30. package/dist/consolidate.js.map +1 -1
  31. package/dist/dag.js +6 -6
  32. package/dist/dashboard.js +7 -7
  33. package/dist/db.d.ts.map +1 -1
  34. package/dist/db.js +55 -4
  35. package/dist/db.js.map +1 -1
  36. package/dist/hooks.js +24 -24
  37. package/dist/importers.d.ts +28 -0
  38. package/dist/importers.d.ts.map +1 -1
  39. package/dist/importers.js +412 -1
  40. package/dist/importers.js.map +1 -1
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +1 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/mcp/server.d.ts.map +1 -1
  46. package/dist/mcp/server.js +44 -7
  47. package/dist/mcp/server.js.map +1 -1
  48. package/dist/memory.d.ts +9 -0
  49. package/dist/memory.d.ts.map +1 -1
  50. package/dist/memory.js +29 -6
  51. package/dist/memory.js.map +1 -1
  52. package/dist/physics-state.d.ts.map +1 -1
  53. package/dist/physics-state.js +4 -3
  54. package/dist/physics-state.js.map +1 -1
  55. package/dist/physics.d.ts.map +1 -1
  56. package/dist/physics.js +8 -1
  57. package/dist/physics.js.map +1 -1
  58. package/dist/predictions.js +67 -67
  59. package/dist/project-identity.d.ts +84 -0
  60. package/dist/project-identity.d.ts.map +1 -0
  61. package/dist/project-identity.js +184 -0
  62. package/dist/project-identity.js.map +1 -0
  63. package/dist/refine-llm.js +13 -13
  64. package/dist/replay.d.ts.map +1 -1
  65. package/dist/replay.js +4 -1
  66. package/dist/replay.js.map +1 -1
  67. package/dist/search.d.ts +14 -0
  68. package/dist/search.d.ts.map +1 -1
  69. package/dist/search.js +36 -7
  70. package/dist/search.js.map +1 -1
  71. package/dist/secret-detect.d.ts +33 -0
  72. package/dist/secret-detect.d.ts.map +1 -0
  73. package/dist/secret-detect.js +70 -0
  74. package/dist/secret-detect.js.map +1 -0
  75. package/dist/server.d.ts.map +1 -1
  76. package/dist/server.js +11 -0
  77. package/dist/server.js.map +1 -1
  78. package/dist/shared.d.ts +9 -1
  79. package/dist/shared.d.ts.map +1 -1
  80. package/dist/shared.js +67 -16
  81. package/dist/shared.js.map +1 -1
  82. package/dist/src/ablation.js +138 -0
  83. package/dist/src/ablation.js.map +1 -0
  84. package/dist/src/ambient.js +2 -1
  85. package/dist/src/ambient.js.map +1 -1
  86. package/dist/src/api.js +88 -11
  87. package/dist/src/api.js.map +1 -1
  88. package/dist/src/cli.js +117 -18
  89. package/dist/src/cli.js.map +1 -1
  90. package/dist/src/config.js +2 -0
  91. package/dist/src/config.js.map +1 -1
  92. package/dist/src/connectors/github/backfill.js +4 -4
  93. package/dist/src/connectors/github/cli-impl.js +6 -6
  94. package/dist/src/connectors/github/dlq.js +14 -14
  95. package/dist/src/connectors/slack/backfill.js +1 -1
  96. package/dist/src/connectors/slack/dlq.js +10 -10
  97. package/dist/src/connectors/slack/workspaces.js +4 -4
  98. package/dist/src/consolidate.js +27 -5
  99. package/dist/src/consolidate.js.map +1 -1
  100. package/dist/src/dag.js +6 -6
  101. package/dist/src/dashboard.js +7 -7
  102. package/dist/src/db.js +55 -4
  103. package/dist/src/db.js.map +1 -1
  104. package/dist/src/hooks.js +24 -24
  105. package/dist/src/importers.js +412 -1
  106. package/dist/src/importers.js.map +1 -1
  107. package/dist/src/index.js +1 -1
  108. package/dist/src/index.js.map +1 -1
  109. package/dist/src/mcp/server.js +44 -7
  110. package/dist/src/mcp/server.js.map +1 -1
  111. package/dist/src/memory.js +29 -6
  112. package/dist/src/memory.js.map +1 -1
  113. package/dist/src/physics-state.js +4 -3
  114. package/dist/src/physics-state.js.map +1 -1
  115. package/dist/src/physics.js +8 -1
  116. package/dist/src/physics.js.map +1 -1
  117. package/dist/src/predictions.js +67 -67
  118. package/dist/src/project-identity.js +184 -0
  119. package/dist/src/project-identity.js.map +1 -0
  120. package/dist/src/refine-llm.js +13 -13
  121. package/dist/src/replay.js +4 -1
  122. package/dist/src/replay.js.map +1 -1
  123. package/dist/src/search.js +36 -7
  124. package/dist/src/search.js.map +1 -1
  125. package/dist/src/secret-detect.js +70 -0
  126. package/dist/src/secret-detect.js.map +1 -0
  127. package/dist/src/server.js +11 -0
  128. package/dist/src/server.js.map +1 -1
  129. package/dist/src/shared.js +67 -16
  130. package/dist/src/shared.js.map +1 -1
  131. package/dist/src/store.js +66 -11
  132. package/dist/src/store.js.map +1 -1
  133. package/dist/src/version.js +1 -1
  134. package/dist/src/working-memory.js +19 -19
  135. package/dist/store.d.ts +15 -0
  136. package/dist/store.d.ts.map +1 -1
  137. package/dist/store.js +66 -11
  138. package/dist/store.js.map +1 -1
  139. package/dist/version.d.ts +1 -1
  140. package/dist/version.js +1 -1
  141. package/dist/working-memory.js +19 -19
  142. package/dist-ui/index.html +12 -12
  143. package/extensions/openclaw-plugin/index.ts +650 -650
  144. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  145. package/extensions/openclaw-plugin/package.json +1 -1
  146. package/openclaw.plugin.json +1 -1
  147. package/package.json +3 -1
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;AAwBH,OAAO,EASL,WAAW,EAEZ,MAAM,aAAa,CAAC;AAmGrB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AA4ChC,kFAAkF;AAClF,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAi4ED;;;GAGG;AACH,oHAAoH;AACpH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAkD/D;AA2tFD,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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAyBH,OAAO,EASL,WAAW,EAEZ,MAAM,aAAa,CAAC;AAqGrB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AA4ChC,kFAAkF;AAClF,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAu4ED;;;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,7 @@ 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';
@@ -50,7 +52,7 @@ import { extractPathTags } from './path-context.js';
50
52
  import { detectScope } from './scope.js';
51
53
  import { getGlobalRoot, initGlobal, shareMemory, listPeers, autoShare, transferScore, searchBothHybrid, syncGlobalToLocal, } from './shared.js';
52
54
  import { DAILY_TASK_NAME, buildDailyRunnerCommand, listRegisteredWorkspaces, registerWorkspace, runDailyMaintenance, } from './scheduler.js';
53
- import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, } from './importers.js';
55
+ import { importChatGPT, importClaude, importCursor, importGenericFile, importMarkdown, importVault, } from './importers.js';
54
56
  import { cmdCapture } from './capture.js';
55
57
  import { auditMemories, appendAuditEvent, } from './audit.js';
56
58
  import { listApiKeys, revokeApiKey } from './auth.js';
@@ -1482,9 +1484,15 @@ async function cmdRecall(hippoRoot, query, flags) {
1482
1484
  // Update retrieval metadata and persist
1483
1485
  const updated = markRetrieved(results.map((r) => r.entry));
1484
1486
  const localIndex = loadIndex(hippoRoot);
1485
- for (const u of updated) {
1486
- const targetRoot = localIndex.entries[u.id] ? hippoRoot : (isInitialized(globalRoot) ? globalRoot : hippoRoot);
1487
- writeEntry(targetRoot, u);
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
+ }
1488
1496
  }
1489
1497
  // Track last retrieval IDs for outcome command
1490
1498
  localIndex.last_retrieval_ids = updated.map((u) => u.id);
@@ -2091,7 +2099,7 @@ function cmdTrace(hippoRoot, id, flags) {
2091
2099
  console.error(`Memory not found: ${id}`);
2092
2100
  process.exit(1);
2093
2101
  }
2094
- const now = new Date();
2102
+ const now = evalNow();
2095
2103
  const strength = calculateStrength(entry, now);
2096
2104
  const halfLife = deriveHalfLife(7, entry);
2097
2105
  const rewardFactor = calculateRewardFactor(entry);
@@ -2705,7 +2713,7 @@ function cmdStatus(hippoRoot) {
2705
2713
  requireInit(hippoRoot);
2706
2714
  const entries = loadAllEntries(hippoRoot);
2707
2715
  const stats = loadStats(hippoRoot);
2708
- const now = new Date();
2716
+ const now = evalNow();
2709
2717
  const byLayer = {
2710
2718
  [Layer.Buffer]: 0,
2711
2719
  [Layer.Episodic]: 0,
@@ -2926,7 +2934,7 @@ function cmdInspect(hippoRoot, id) {
2926
2934
  console.error(`Memory not found: ${id}`);
2927
2935
  process.exit(1);
2928
2936
  }
2929
- const now = new Date();
2937
+ const now = evalNow();
2930
2938
  const currentStrength = calculateStrength(entry, now);
2931
2939
  const lastRetrieved = new Date(entry.last_retrieved);
2932
2940
  const created = new Date(entry.created);
@@ -4911,6 +4919,9 @@ async function cmdContext(hippoRoot, args, flags) {
4911
4919
  tenantId: resolveTenantId({}),
4912
4920
  actor: api.adminActor('cli'),
4913
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;
4914
4925
  const opts = {
4915
4926
  q: query,
4916
4927
  budget,
@@ -4918,6 +4929,7 @@ async function cmdContext(hippoRoot, args, flags) {
4918
4929
  pinnedOnly,
4919
4930
  scope: ctxActiveScope ?? undefined,
4920
4931
  includeRecent: parseCountFlag(flags['include-recent']),
4932
+ crossProject,
4921
4933
  };
4922
4934
  const result = await api.getContext(ctx, opts);
4923
4935
  // Early exit when there's nothing to render (matches pre-extraction behavior).
@@ -4930,8 +4942,13 @@ async function cmdContext(hippoRoot, args, flags) {
4930
4942
  // Format + framing are CLI rendering concerns; api.getContext doesn't see them.
4931
4943
  const format = String(flags['format'] ?? 'markdown');
4932
4944
  const framing = String(flags['framing'] ?? 'observe');
4933
- // Adapter: ContextResultEntry -> the print-helper input shape.
4934
- const renderItems = result.entries.map((r) => ({
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) => ({
4935
4952
  entry: r.entry,
4936
4953
  score: r.score,
4937
4954
  tokens: r.tokens,
@@ -4946,6 +4963,8 @@ async function cmdContext(hippoRoot, args, flags) {
4946
4963
  confidence: r.entry.confidence,
4947
4964
  content: r.entry.content,
4948
4965
  global: r.isGlobal ?? false,
4966
+ origin: r.origin ?? null,
4967
+ category: r.category ?? null,
4949
4968
  }));
4950
4969
  console.log(JSON.stringify({
4951
4970
  query: query || '*',
@@ -4973,6 +4992,7 @@ async function cmdContext(hippoRoot, args, flags) {
4973
4992
  if (renderItems.length > 0) {
4974
4993
  printContextMarkdown(renderItems, result.tokens, framing);
4975
4994
  }
4995
+ printCrossProjectSection(crossEntries);
4976
4996
  }
4977
4997
  finally {
4978
4998
  console.log = realLog;
@@ -5002,6 +5022,7 @@ async function cmdContext(hippoRoot, args, flags) {
5002
5022
  if (renderItems.length > 0) {
5003
5023
  printContextMarkdown(renderItems, result.tokens, framing);
5004
5024
  }
5025
+ printCrossProjectSection(crossEntries);
5005
5026
  // Ambient state summary (CLI-side: api.getContext doesn't load all entries
5006
5027
  // post-selection, so we re-load here for the landscape summary).
5007
5028
  const ambientConfig = loadConfig(hippoRoot);
@@ -5009,11 +5030,17 @@ async function cmdContext(hippoRoot, args, flags) {
5009
5030
  const tenantId = ctx.tenantId;
5010
5031
  const globalRoot = getGlobalRoot();
5011
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);
5012
5039
  const localForAmbient = isInitialized(hippoRoot)
5013
- ? loadAllEntries(hippoRoot, tenantId).filter(e => !e.superseded_by)
5040
+ ? loadAllEntries(hippoRoot, tenantId).filter(e => !e.superseded_by).filter(ambientAdmit)
5014
5041
  : [];
5015
5042
  const globalForAmbient = hasGlobalForAmbient
5016
- ? loadAllEntries(globalRoot, tenantId).filter(e => !e.superseded_by)
5043
+ ? loadAllEntries(globalRoot, tenantId).filter(e => !e.superseded_by).filter(ambientAdmit)
5017
5044
  : [];
5018
5045
  const allForAmbient = [...localForAmbient, ...globalForAmbient];
5019
5046
  if (allForAmbient.length > 0) {
@@ -5023,9 +5050,24 @@ async function cmdContext(hippoRoot, args, flags) {
5023
5050
  }
5024
5051
  }
5025
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
+ }
5026
5068
  /** @internal — exported for snapshot tests (tests/cli-context-render-snapshot.test.ts). NOT a stable public API. */
5027
5069
  export function printContextMarkdown(items, totalTokens, framing = 'observe') {
5028
- const now = new Date();
5070
+ const now = evalNow();
5029
5071
  console.log(`## Project Memory (${items.length} entries, ${totalTokens} tokens)\n`);
5030
5072
  for (const item of items) {
5031
5073
  const e = item.entry;
@@ -5320,6 +5362,54 @@ function cmdImport(hippoRoot, args, flags) {
5320
5362
  extraTags,
5321
5363
  hippoRoot,
5322
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
+ }
5323
5413
  // Determine which importer to use based on flag
5324
5414
  let filePath;
5325
5415
  let importer;
@@ -5356,7 +5446,7 @@ function cmdImport(hippoRoot, args, flags) {
5356
5446
  importerName = 'File';
5357
5447
  }
5358
5448
  if (!filePath || !importer) {
5359
- console.error('Usage: hippo import <--chatgpt|--claude|--cursor|--file|--markdown> <path>');
5449
+ console.error('Usage: hippo import <--chatgpt|--claude|--cursor|--file|--markdown|--vault> <path>');
5360
5450
  process.exit(1);
5361
5451
  }
5362
5452
  if (!fs.existsSync(filePath)) {
@@ -5412,15 +5502,17 @@ function cmdPromote(hippoRoot, id) {
5412
5502
  // ---------------------------------------------------------------------------
5413
5503
  // Sync command
5414
5504
  // ---------------------------------------------------------------------------
5415
- function cmdSync(hippoRoot) {
5505
+ function cmdSync(hippoRoot, flags = {}) {
5416
5506
  requireInit(hippoRoot);
5417
5507
  const globalRoot = getGlobalRoot();
5418
5508
  if (!isInitialized(globalRoot)) {
5419
5509
  console.log('No global store found. Run `hippo init --global` first.');
5420
5510
  return;
5421
5511
  }
5422
- const count = syncGlobalToLocal(hippoRoot, globalRoot);
5423
- console.log(`Synced ${count} global memories into local project.`);
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)'}`);
5424
5516
  }
5425
5517
  // ---------------------------------------------------------------------------
5426
5518
  // Hook install/uninstall
@@ -5436,7 +5528,11 @@ const HOOKS = {
5436
5528
  content: `
5437
5529
  ## Project Memory (Hippo)
5438
5530
 
5439
- Before starting work, load relevant context:
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:
5440
5536
  \`\`\`bash
5441
5537
  hippo context --auto --budget 1500
5442
5538
  \`\`\`
@@ -7076,6 +7172,9 @@ Commands:
7076
7172
  --cursor <path> Import from .cursorrules or .cursor/rules
7077
7173
  --file <path> Import from any markdown or text file
7078
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>]
7079
7178
  --dry-run Preview without writing
7080
7179
  --global Write to global store ($HIPPO_HOME or ~/.hippo/)
7081
7180
  --tag <tag> Add extra tag (repeatable)
@@ -7670,7 +7769,7 @@ async function main() {
7670
7769
  break;
7671
7770
  }
7672
7771
  case 'sync':
7673
- cmdSync(hippoRoot);
7772
+ cmdSync(hippoRoot, flags);
7674
7773
  break;
7675
7774
  case 'share': {
7676
7775
  const shareId = args[0];