@andespindola/brainlink 1.0.7 → 1.2.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 (40) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +63 -2
  3. package/dist/application/analyze-vault.js +1 -1
  4. package/dist/application/build-context.js +14 -7
  5. package/dist/application/get-graph-node.js +2 -2
  6. package/dist/application/get-graph-summary.js +2 -2
  7. package/dist/application/get-graph.js +2 -2
  8. package/dist/application/index-vault-phases.js +1 -0
  9. package/dist/application/index-vault.js +16 -2
  10. package/dist/application/list-agents.js +2 -2
  11. package/dist/application/list-links.js +3 -3
  12. package/dist/application/ports/knowledge-store.js +1 -0
  13. package/dist/application/provision-vault.js +74 -0
  14. package/dist/application/ranking/run-search.js +212 -0
  15. package/dist/application/search-graph-node-ids.js +3 -2
  16. package/dist/application/search-knowledge.js +9 -17
  17. package/dist/application/server/routes.js +6 -1
  18. package/dist/application/vault-encryption.js +64 -0
  19. package/dist/application/vault-git-core.js +168 -0
  20. package/dist/application/vault-git.js +75 -0
  21. package/dist/application/vault-snapshot.js +87 -0
  22. package/dist/cli/commands/read-commands.js +9 -5
  23. package/dist/cli/commands/vault-sync-commands.js +137 -0
  24. package/dist/cli/commands/write/vault-lifecycle-commands.js +1 -1
  25. package/dist/cli/main.js +2 -0
  26. package/dist/infrastructure/config.js +16 -1
  27. package/dist/infrastructure/context-packs.js +57 -18
  28. package/dist/infrastructure/file-index.js +22 -409
  29. package/dist/infrastructure/index-signature.js +27 -0
  30. package/dist/infrastructure/index-state.js +6 -0
  31. package/dist/infrastructure/knowledge-store/binary-store.js +333 -0
  32. package/dist/infrastructure/knowledge-store/index.js +37 -0
  33. package/dist/infrastructure/knowledge-store/stored-index.js +216 -0
  34. package/dist/infrastructure/vault-crypto.js +78 -0
  35. package/dist/mcp/server.js +46 -1
  36. package/dist/mcp/tools/maintenance-tools.js +2 -2
  37. package/dist/mcp/tools/read-tools.js +8 -4
  38. package/dist/mcp/tools/vault-tools.js +163 -0
  39. package/dist/mcp/tools.js +1 -0
  40. package/package.json +3 -2
@@ -0,0 +1,137 @@
1
+ import { cloneVault, commitVault, initVaultGit, pullVault, pushVault, restoreVault, vaultGitStatus, vaultHistory } from '../../application/vault-git.js';
2
+ import { provisionVaultRepo } from '../../application/provision-vault.js';
3
+ import { exportVaultSnapshot, importVaultSnapshot } from '../../application/vault-snapshot.js';
4
+ import { print, resolveOptions } from '../runtime.js';
5
+ export const registerVaultSyncCommands = (program) => {
6
+ program
7
+ .command('vault-init-git')
8
+ .option('--vault <vault>', 'vault directory')
9
+ .option('--remote <url>', 'git remote URL (SSH or credential-helper backed; inline tokens are rejected)')
10
+ .option('--json', 'print machine-readable JSON')
11
+ .description('initialize git versioning for the vault (ignores the derived .brainlink/)')
12
+ .action(async (options) => {
13
+ const { vault } = await resolveOptions(options);
14
+ const result = await initVaultGit(vault, { remote: options.remote });
15
+ print(options.json, result, () => `${result.initialized ? 'Initialized' : 'Reused'} git repo at ${result.root}${options.remote ? ` (origin: ${options.remote})` : ''}.`);
16
+ });
17
+ program
18
+ .command('vault-provision')
19
+ .option('--vault <vault>', 'vault directory')
20
+ .option('--name <name>', 'repository name to create', 'brainlink-vault')
21
+ .option('--public', 'create a public repository instead of private')
22
+ .option('--no-auto-version', 'do not enable autoVersion after provisioning')
23
+ .option('--no-encrypt', 'commit plaintext Markdown instead of encrypted ciphertext')
24
+ .option('-m, --message <message>', 'initial commit message')
25
+ .option('--json', 'print machine-readable JSON')
26
+ .description('create a private GitHub repo for the vault via gh, push it, and enable encrypted auto-versioning')
27
+ .action(async (options) => {
28
+ const { vault } = await resolveOptions(options);
29
+ const result = await provisionVaultRepo(vault, {
30
+ name: options.name,
31
+ private: options.public !== true,
32
+ enableAutoVersion: options.autoVersion !== false,
33
+ encrypt: options.encrypt !== false,
34
+ message: options.message
35
+ });
36
+ print(options.json, result, () => `${result.created ? 'Created' : 'Reused'} ${result.repo} and pushed the vault${result.encrypted ? ' (encrypted)' : ''}${result.autoVersionEnabled ? '; autoVersion enabled' : ''}.`);
37
+ });
38
+ program
39
+ .command('vault-commit')
40
+ .requiredOption('-m, --message <message>', 'commit message')
41
+ .option('--vault <vault>', 'vault directory')
42
+ .option('--json', 'print machine-readable JSON')
43
+ .description('commit the vault Markdown')
44
+ .action(async (options) => {
45
+ const { vault } = await resolveOptions(options);
46
+ const hash = await commitVault(vault, options.message ?? 'Update vault');
47
+ print(options.json, { committed: hash != null, hash }, () => hash ? `Committed ${hash.slice(0, 10)}.` : 'Nothing to commit.');
48
+ });
49
+ program
50
+ .command('vault-status')
51
+ .option('--vault <vault>', 'vault directory')
52
+ .option('--json', 'print machine-readable JSON')
53
+ .description('show git status of the vault Markdown')
54
+ .action(async (options) => {
55
+ const { vault } = await resolveOptions(options);
56
+ const status = await vaultGitStatus(vault);
57
+ print(options.json, status, () => `Branch ${status.branch}; ${status.dirty ? `${status.changedMarkdown.length} changed markdown file(s)` : 'clean'}.`);
58
+ });
59
+ program
60
+ .command('vault-history')
61
+ .option('--vault <vault>', 'vault directory')
62
+ .option('--limit <n>', 'number of entries', '20')
63
+ .option('--json', 'print machine-readable JSON')
64
+ .description('show vault commit history')
65
+ .action(async (options) => {
66
+ const { vault } = await resolveOptions(options);
67
+ const entries = await vaultHistory(vault, Number.parseInt(options.limit ?? '20', 10));
68
+ print(options.json, entries, () => entries.map((entry) => `${entry.hash.slice(0, 10)} ${entry.date} ${entry.subject}`).join('\n') || 'No history.');
69
+ });
70
+ program
71
+ .command('vault-push')
72
+ .option('--vault <vault>', 'vault directory')
73
+ .option('--json', 'print machine-readable JSON')
74
+ .description('push the vault to its git remote')
75
+ .action(async (options) => {
76
+ const { vault } = await resolveOptions(options);
77
+ await pushVault(vault);
78
+ print(options.json, { pushed: true }, () => 'Pushed to origin.');
79
+ });
80
+ program
81
+ .command('vault-pull')
82
+ .option('--vault <vault>', 'vault directory')
83
+ .option('--no-index', 'skip reindexing after pull')
84
+ .option('--json', 'print machine-readable JSON')
85
+ .description('pull the vault from git and reindex changed notes')
86
+ .action(async (options) => {
87
+ const { vault } = await resolveOptions(options);
88
+ const result = await pullVault(vault, { skipIndex: options.index === false });
89
+ print(options.json, result, () => `Pulled. ${result.changedMarkdown.length} markdown file(s) changed${result.index ? `; reindexed ${result.index.documentCount} docs` : ''}.`);
90
+ });
91
+ program
92
+ .command('vault-restore')
93
+ .argument('<ref>', 'git ref (commit, tag, branch) to restore Markdown from')
94
+ .option('--vault <vault>', 'vault directory')
95
+ .option('--no-index', 'skip reindexing after restore')
96
+ .option('--json', 'print machine-readable JSON')
97
+ .description('restore vault Markdown from a git ref and reindex')
98
+ .action(async (ref, options) => {
99
+ const { vault } = await resolveOptions(options);
100
+ const result = await restoreVault(vault, ref, { skipIndex: options.index === false });
101
+ print(options.json, result, () => `Restored from ${ref}. ${result.changedMarkdown.length} markdown file(s) changed${result.index ? `; reindexed ${result.index.documentCount} docs` : ''}.`);
102
+ });
103
+ program
104
+ .command('vault-clone')
105
+ .argument('<remote>', 'git remote URL')
106
+ .argument('<dir>', 'target directory')
107
+ .option('--no-index', 'skip building the index after clone')
108
+ .option('--json', 'print machine-readable JSON')
109
+ .description('clone a remote vault and build its index')
110
+ .action(async (remote, dir, options) => {
111
+ const result = await cloneVault(remote, dir, { skipIndex: options.index === false });
112
+ print(options.json, result, () => `Cloned to ${result.root}${result.index ? `; indexed ${result.index.documentCount} docs` : ''}.`);
113
+ });
114
+ program
115
+ .command('vault-export')
116
+ .requiredOption('-o, --output <file>', 'output .blvault file')
117
+ .option('--vault <vault>', 'vault directory')
118
+ .option('--json', 'print machine-readable JSON')
119
+ .description('export the vault Markdown to a portable .blvault snapshot')
120
+ .action(async (options) => {
121
+ const { vault } = await resolveOptions(options);
122
+ const result = await exportVaultSnapshot(vault, options.output ?? 'vault.blvault');
123
+ print(options.json, result, () => `Exported ${result.fileCount} files to ${result.file} (${result.bytes} bytes).`);
124
+ });
125
+ program
126
+ .command('vault-import')
127
+ .argument('<file>', 'input .blvault file')
128
+ .option('--vault <vault>', 'vault directory')
129
+ .option('--no-index', 'skip reindexing after import')
130
+ .option('--json', 'print machine-readable JSON')
131
+ .description('import a .blvault snapshot into the vault and reindex')
132
+ .action(async (file, options) => {
133
+ const { vault } = await resolveOptions(options);
134
+ const result = await importVaultSnapshot(file, vault, { skipIndex: options.index === false });
135
+ print(options.json, result, () => `Imported ${result.imported} files (${result.conflicted} conflicts)${result.index ? `; reindexed ${result.index.documentCount} docs` : ''}.`);
136
+ });
137
+ };
@@ -204,7 +204,7 @@ export const registerVaultLifecycleCommands = (program) => {
204
204
  const policy = await getBootstrapPolicy();
205
205
  const bootstrapStatus = await getBootstrapSessionStatus(resolved.vault, resolved.agent);
206
206
  const context = options.query
207
- ? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode, strategy, resolved.defaults.defaultContextCacheTtlMs)
207
+ ? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode, strategy, resolved.defaults.defaultContextCacheTtlMs, resolved.defaults.cagPackTtlMs)
208
208
  : null;
209
209
  const agentIntegration = options.installAgent === false
210
210
  ? null
package/dist/cli/main.js CHANGED
@@ -10,6 +10,7 @@ import { registerConfigCommands } from './commands/config-commands.js';
10
10
  import { registerPracticalCommands } from './commands/practical-commands.js';
11
11
  import { registerReadCommands } from './commands/read-commands.js';
12
12
  import { registerVaultCommands } from './commands/vault-commands.js';
13
+ import { registerVaultSyncCommands } from './commands/vault-sync-commands.js';
13
14
  import { registerWriteCommands } from './commands/write-commands.js';
14
15
  const readPackageMetadata = () => {
15
16
  const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
@@ -63,6 +64,7 @@ registerReadCommands(program);
63
64
  registerPracticalCommands(program);
64
65
  registerConfigCommands(program);
65
66
  registerVaultCommands(program);
67
+ registerVaultSyncCommands(program);
66
68
  registerAgentCommands(program);
67
69
  await maybePrintUpdateNotice(packageMetadata);
68
70
  program.parseAsync().catch((error) => {
@@ -11,16 +11,21 @@ export const defaultBrainlinkConfig = {
11
11
  defaultAgent: undefined,
12
12
  autoIndexOnWrite: true,
13
13
  autoCanonicalContextLinks: true,
14
+ autoVersion: false,
15
+ vaultEncryption: false,
14
16
  autoUpdateCheck: true,
15
17
  updateCheckIntervalMs: 86_400_000,
16
18
  defaultSearchLimit: 8,
17
19
  defaultContextTokens: 1500,
18
20
  defaultContextStrategy: 'auto',
19
21
  defaultContextCacheTtlMs: 120_000,
22
+ cagPackTtlMs: 0,
20
23
  embeddingProvider: 'local',
21
24
  embedding: {},
22
25
  defaultSearchMode: 'hybrid',
23
26
  chunkSize: 1200,
27
+ storageBackend: 'binary',
28
+ hybridFusion: 'rrf',
24
29
  searchPack: {
25
30
  rowChunkSize: 5_000,
26
31
  compressionLevel: 5,
@@ -47,6 +52,10 @@ const isRecord = (value) => typeof value === 'object' && value !== null && !Arra
47
52
  const embeddingProviders = new Set(['none', 'local', 'ollama', 'openai']);
48
53
  const searchModes = new Set(['fts', 'semantic', 'hybrid']);
49
54
  const contextStrategies = new Set(['rag', 'cag', 'auto']);
55
+ const storageBackends = new Set(['json', 'binary', 'lmdb']);
56
+ const hybridFusions = new Set(['rrf', 'linear']);
57
+ const sanitizeStorageBackend = (value) => typeof value === 'string' && storageBackends.has(value) ? value : defaultBrainlinkConfig.storageBackend;
58
+ const sanitizeHybridFusion = (value) => typeof value === 'string' && hybridFusions.has(value) ? value : defaultBrainlinkConfig.hybridFusion;
50
59
  const sanitizeEmbeddingProvider = (value) => typeof value === 'string' && embeddingProviders.has(value) ? value : defaultBrainlinkConfig.embeddingProvider;
51
60
  const sanitizeOptionalString = (value) => typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
52
61
  const sanitizeEmbeddingConfig = (value) => {
@@ -204,6 +213,8 @@ const sanitizeConfig = (value) => ({
204
213
  autoCanonicalContextLinks: typeof value.autoCanonicalContextLinks === 'boolean'
205
214
  ? value.autoCanonicalContextLinks
206
215
  : defaultBrainlinkConfig.autoCanonicalContextLinks,
216
+ autoVersion: typeof value.autoVersion === 'boolean' ? value.autoVersion : defaultBrainlinkConfig.autoVersion,
217
+ vaultEncryption: typeof value.vaultEncryption === 'boolean' ? value.vaultEncryption : defaultBrainlinkConfig.vaultEncryption,
207
218
  autoUpdateCheck: typeof value.autoUpdateCheck === 'boolean' ? value.autoUpdateCheck : defaultBrainlinkConfig.autoUpdateCheck,
208
219
  updateCheckIntervalMs: typeof value.updateCheckIntervalMs === 'number' && Number.isFinite(value.updateCheckIntervalMs) && value.updateCheckIntervalMs > 0
209
220
  ? Math.floor(value.updateCheckIntervalMs)
@@ -218,8 +229,11 @@ const sanitizeConfig = (value) => ({
218
229
  defaultContextCacheTtlMs: typeof value.defaultContextCacheTtlMs === 'number' && Number.isFinite(value.defaultContextCacheTtlMs) && value.defaultContextCacheTtlMs > 0
219
230
  ? Math.floor(value.defaultContextCacheTtlMs)
220
231
  : defaultBrainlinkConfig.defaultContextCacheTtlMs,
232
+ cagPackTtlMs: sanitizeIntegerInRange(value.cagPackTtlMs, defaultBrainlinkConfig.cagPackTtlMs, 0, 2_147_483_647),
221
233
  allowedVaults: [...sanitizeAllowedVaults(value.allowedVaults), ...readAllowedVaultsFromEnv()],
222
234
  chunkSize: typeof value.chunkSize === 'number' && value.chunkSize > 0 ? value.chunkSize : defaultBrainlinkConfig.chunkSize,
235
+ storageBackend: sanitizeStorageBackend(value.storageBackend),
236
+ hybridFusion: sanitizeHybridFusion(value.hybridFusion),
223
237
  searchPack: sanitizeSearchPackConfig(value.searchPack),
224
238
  embeddingProvider: sanitizeEmbeddingProvider(value.embeddingProvider),
225
239
  embedding: sanitizeEmbeddingConfig(value.embedding),
@@ -234,7 +248,8 @@ export const resolveAgentRuntimeDefaults = (config, agent) => {
234
248
  defaultContextTokens: profile?.defaultContextTokens ?? config.defaultContextTokens,
235
249
  defaultSearchMode: profile?.defaultSearchMode ?? config.defaultSearchMode,
236
250
  defaultContextStrategy: profile?.defaultContextStrategy ?? config.defaultContextStrategy,
237
- defaultContextCacheTtlMs: profile?.defaultContextCacheTtlMs ?? config.defaultContextCacheTtlMs
251
+ defaultContextCacheTtlMs: profile?.defaultContextCacheTtlMs ?? config.defaultContextCacheTtlMs,
252
+ cagPackTtlMs: config.cagPackTtlMs
238
253
  };
239
254
  };
240
255
  const mergeConfigLayers = (layers) => layers.reduce((state, config) => ({
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
3
3
  import { basename, join } from 'node:path';
4
+ const storedPackVersion = 2;
4
5
  const normalizePackKey = (key) => ({
5
6
  query: key.query.trim().toLowerCase(),
6
7
  limit: key.limit,
@@ -13,12 +14,35 @@ export const contextPackPath = (vaultPath, key) => {
13
14
  const digest = createHash('sha256').update(JSON.stringify(normalizePackKey(key))).digest('hex');
14
15
  return join(contextPacksDirectory(vaultPath), `${digest}.json`);
15
16
  };
17
+ // Signature of a single file, matching the volatile/index signatures used
18
+ // elsewhere (floored mtime + size). A missing file collapses to a stable
19
+ // sentinel so a deleted dependency reliably differs from its recorded value.
20
+ const fileSignature = async (path) => {
21
+ try {
22
+ const info = await stat(path);
23
+ return `${Math.floor(info.mtimeMs)}:${info.size}`;
24
+ }
25
+ catch {
26
+ return '0:0';
27
+ }
28
+ };
29
+ // Durable (non-volatile) section source paths, deduplicated. Volatile sections
30
+ // come from turn memory rather than vault files, so they are tracked through the
31
+ // separate volatile signature instead of per-document dependencies.
32
+ const dependencyPaths = (context) => {
33
+ const paths = context.sections.filter((section) => !section.volatile).map((section) => section.path);
34
+ return Array.from(new Set(paths));
35
+ };
36
+ const computeDependencies = async (vaultPath, context) => Promise.all(dependencyPaths(context).map(async (path) => ({ path, signature: await fileSignature(join(vaultPath, path)) })));
16
37
  const isStoredContextPack = (value) => {
17
38
  if (!value || typeof value !== 'object') {
18
39
  return false;
19
40
  }
20
41
  const candidate = value;
21
- return candidate.version === 1 && typeof candidate.dataSignature === 'string' && Boolean(candidate.context);
42
+ return (candidate.version === storedPackVersion &&
43
+ Array.isArray(candidate.dependencies) &&
44
+ typeof candidate.volatileSignature === 'string' &&
45
+ Boolean(candidate.context));
22
46
  };
23
47
  const readStoredContextPack = async (path) => {
24
48
  try {
@@ -29,30 +53,43 @@ const readStoredContextPack = async (path) => {
29
53
  return null;
30
54
  }
31
55
  };
32
- const toContextPackSummary = async (path, dataSignature) => {
33
- const [info, stored] = await Promise.all([
34
- stat(path),
35
- readStoredContextPack(path)
36
- ]);
37
- const stale = !stored || (typeof dataSignature === 'string' && stored.dataSignature !== dataSignature);
56
+ // A pack is fresh while it is within its optional TTL, its volatile signature
57
+ // matches, and every cited document is unchanged on disk. Reads no vault index,
58
+ // so the CAG fast path stays cheap: a few stats over the cited files.
59
+ const isPackFresh = async (vaultPath, stored, freshness, now) => {
60
+ if (freshness.ttlMs > 0) {
61
+ const age = now - Date.parse(stored.createdAt);
62
+ if (!Number.isFinite(age) || age > freshness.ttlMs) {
63
+ return false;
64
+ }
65
+ }
66
+ if (stored.volatileSignature !== freshness.volatileSignature) {
67
+ return false;
68
+ }
69
+ const current = await Promise.all(stored.dependencies.map((dependency) => fileSignature(join(vaultPath, dependency.path))));
70
+ return stored.dependencies.every((dependency, index) => dependency.signature === current[index]);
71
+ };
72
+ const toContextPackSummary = async (path, vaultPath, freshness) => {
73
+ const [info, stored] = await Promise.all([stat(path), readStoredContextPack(path)]);
74
+ const stale = !stored || (freshness ? !(await isPackFresh(vaultPath, stored, freshness, Date.now())) : false);
38
75
  return {
39
76
  path,
40
77
  filename: basename(path),
41
78
  createdAt: stored?.createdAt ?? null,
42
- dataSignature: stored?.dataSignature ?? null,
79
+ dependencyCount: stored?.dependencies.length ?? null,
43
80
  key: stored?.key ?? null,
44
81
  sizeBytes: info.size,
45
82
  stale
46
83
  };
47
84
  };
48
- export const listContextPacks = async (vaultPath, dataSignature) => {
85
+ export const listContextPacks = async (vaultPath, freshness) => {
49
86
  const directory = contextPacksDirectory(vaultPath);
50
87
  try {
51
88
  const entries = await readdir(directory, { withFileTypes: true });
52
89
  const paths = entries
53
90
  .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
54
91
  .map((entry) => join(directory, entry.name));
55
- const summaries = await Promise.all(paths.map((path) => toContextPackSummary(path, dataSignature)));
92
+ const summaries = await Promise.all(paths.map((path) => toContextPackSummary(path, vaultPath, freshness)));
56
93
  return summaries.sort((left, right) => (right.createdAt ?? '').localeCompare(left.createdAt ?? '') || left.filename.localeCompare(right.filename));
57
94
  }
58
95
  catch (error) {
@@ -63,20 +100,20 @@ export const listContextPacks = async (vaultPath, dataSignature) => {
63
100
  }
64
101
  };
65
102
  export const clearContextPacks = async (vaultPath, options = {}) => {
66
- const packs = await listContextPacks(vaultPath, options.dataSignature);
103
+ const packs = await listContextPacks(vaultPath, options.freshness);
67
104
  const removed = options.staleOnly ? packs.filter((pack) => pack.stale) : packs;
68
105
  const kept = options.staleOnly ? packs.filter((pack) => !pack.stale) : [];
69
106
  await Promise.all(removed.map((pack) => rm(pack.path, { force: true })));
70
107
  return { removed, kept };
71
108
  };
72
- export const readContextPack = async (vaultPath, key, dataSignature) => {
109
+ export const readContextPack = async (vaultPath, key, freshness) => {
73
110
  const path = contextPackPath(vaultPath, key);
74
111
  try {
75
112
  const parsed = await readStoredContextPack(path);
76
113
  if (!parsed) {
77
114
  return { status: 'stale', path };
78
115
  }
79
- if (parsed.dataSignature !== dataSignature) {
116
+ if (!(await isPackFresh(vaultPath, parsed, freshness, Date.now()))) {
80
117
  return { status: 'stale', path };
81
118
  }
82
119
  return {
@@ -88,7 +125,7 @@ export const readContextPack = async (vaultPath, key, dataSignature) => {
88
125
  cache: {
89
126
  storage: 'context-pack',
90
127
  status: 'hit',
91
- dataSignature,
128
+ dataSignature: parsed.volatileSignature,
92
129
  path
93
130
  }
94
131
  }
@@ -98,12 +135,14 @@ export const readContextPack = async (vaultPath, key, dataSignature) => {
98
135
  return { status: 'miss', path };
99
136
  }
100
137
  };
101
- export const writeContextPack = async (vaultPath, key, dataSignature, context) => {
138
+ export const writeContextPack = async (vaultPath, key, context, meta) => {
102
139
  const path = contextPackPath(vaultPath, key);
140
+ const dependencies = await computeDependencies(vaultPath, context);
103
141
  const stored = {
104
- version: 1,
142
+ version: storedPackVersion,
105
143
  createdAt: new Date().toISOString(),
106
- dataSignature,
144
+ dependencies,
145
+ volatileSignature: meta.volatileSignature,
107
146
  key: normalizePackKey(key),
108
147
  context: {
109
148
  ...context,
@@ -111,7 +150,7 @@ export const writeContextPack = async (vaultPath, key, dataSignature, context) =
111
150
  cache: {
112
151
  storage: 'context-pack',
113
152
  status: 'refresh',
114
- dataSignature,
153
+ dataSignature: meta.volatileSignature,
115
154
  path
116
155
  }
117
156
  }