context-vault 3.8.0 → 3.10.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/assets/agent-rules.md +28 -1
- package/assets/setup-prompt.md +16 -1
- package/bin/cli.js +1187 -4
- package/dist/auto-memory.d.ts +52 -0
- package/dist/auto-memory.d.ts.map +1 -0
- package/dist/auto-memory.js +142 -0
- package/dist/auto-memory.js.map +1 -0
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +2 -0
- package/dist/register-tools.js.map +1 -1
- package/dist/remote.d.ts +186 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +372 -0
- package/dist/remote.js.map +1 -0
- package/dist/remote.test.d.ts +2 -0
- package/dist/remote.test.d.ts.map +1 -0
- package/dist/remote.test.js +107 -0
- package/dist/remote.test.js.map +1 -0
- package/dist/tools/context-status.d.ts.map +1 -1
- package/dist/tools/context-status.js +19 -0
- package/dist/tools/context-status.js.map +1 -1
- package/dist/tools/get-context.d.ts.map +1 -1
- package/dist/tools/get-context.js +70 -0
- package/dist/tools/get-context.js.map +1 -1
- package/dist/tools/publish-to-team.d.ts +11 -0
- package/dist/tools/publish-to-team.d.ts.map +1 -0
- package/dist/tools/publish-to-team.js +91 -0
- package/dist/tools/publish-to-team.js.map +1 -0
- package/dist/tools/publish-to-team.test.d.ts +2 -0
- package/dist/tools/publish-to-team.test.d.ts.map +1 -0
- package/dist/tools/publish-to-team.test.js +95 -0
- package/dist/tools/publish-to-team.test.js.map +1 -0
- package/dist/tools/recall.d.ts +1 -1
- package/dist/tools/recall.d.ts.map +1 -1
- package/dist/tools/recall.js +120 -1
- package/dist/tools/recall.js.map +1 -1
- package/dist/tools/save-context.d.ts +5 -1
- package/dist/tools/save-context.d.ts.map +1 -1
- package/dist/tools/save-context.js +163 -2
- package/dist/tools/save-context.js.map +1 -1
- package/dist/tools/session-start.d.ts.map +1 -1
- package/dist/tools/session-start.js +134 -86
- package/dist/tools/session-start.js.map +1 -1
- package/node_modules/@context-vault/core/dist/config.d.ts +3 -1
- package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/config.js +48 -2
- package/node_modules/@context-vault/core/dist/config.js.map +1 -1
- package/node_modules/@context-vault/core/dist/main.d.ts +1 -1
- package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/main.js.map +1 -1
- package/node_modules/@context-vault/core/dist/types.d.ts +7 -0
- package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -1
- package/node_modules/@context-vault/core/package.json +1 -1
- package/node_modules/@context-vault/core/src/config.ts +50 -3
- package/node_modules/@context-vault/core/src/main.ts +1 -0
- package/node_modules/@context-vault/core/src/types.ts +8 -0
- package/package.json +2 -2
- package/src/auto-memory.ts +169 -0
- package/src/register-tools.ts +2 -0
- package/src/remote.test.ts +123 -0
- package/src/remote.ts +470 -0
- package/src/tools/context-status.ts +19 -0
- package/src/tools/get-context.ts +72 -0
- package/src/tools/publish-to-team.test.ts +115 -0
- package/src/tools/publish-to-team.ts +112 -0
- package/src/tools/recall.ts +113 -1
- package/src/tools/save-context.ts +167 -1
- package/src/tools/session-start.ts +133 -100
|
@@ -4,6 +4,9 @@ import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { ok, err, ensureVaultExists, kindIcon, fmtDate } from '../helpers.js';
|
|
7
|
+
import { getAutoMemory } from '../auto-memory.js';
|
|
8
|
+
import { getRemoteClient, getTeamId, getPublicVaults } from '../remote.js';
|
|
9
|
+
import type { AutoMemoryEntry, AutoMemoryResult } from '../auto-memory.js';
|
|
7
10
|
import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
|
|
8
11
|
|
|
9
12
|
const DEFAULT_MAX_TOKENS = 4000;
|
|
@@ -12,99 +15,6 @@ const MAX_BODY_PER_ENTRY = 400;
|
|
|
12
15
|
const PRIORITY_KINDS = ['decision', 'insight', 'pattern'];
|
|
13
16
|
const SESSION_SUMMARY_KIND = 'session';
|
|
14
17
|
|
|
15
|
-
interface AutoMemoryEntry {
|
|
16
|
-
file: string;
|
|
17
|
-
name: string;
|
|
18
|
-
description: string;
|
|
19
|
-
type: string;
|
|
20
|
-
body: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface AutoMemoryResult {
|
|
24
|
-
detected: boolean;
|
|
25
|
-
path: string | null;
|
|
26
|
-
entries: AutoMemoryEntry[];
|
|
27
|
-
linesUsed: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Detect the Claude Code auto-memory directory for the current project.
|
|
32
|
-
* Convention: ~/.claude/projects/-<cwd-with-slashes-replaced-by-dashes>/memory/
|
|
33
|
-
*/
|
|
34
|
-
function detectAutoMemoryPath(): string | null {
|
|
35
|
-
try {
|
|
36
|
-
const cwd = process.cwd();
|
|
37
|
-
// Claude Code project key: absolute path with / replaced by -, leading - kept
|
|
38
|
-
const projectKey = cwd.replace(/\//g, '-');
|
|
39
|
-
const memoryDir = join(homedir(), '.claude', 'projects', projectKey, 'memory');
|
|
40
|
-
const memoryIndex = join(memoryDir, 'MEMORY.md');
|
|
41
|
-
if (existsSync(memoryIndex)) return memoryDir;
|
|
42
|
-
} catch {}
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Parse YAML-ish frontmatter from a memory file.
|
|
48
|
-
* Returns { name, description, type } and the body after frontmatter.
|
|
49
|
-
*/
|
|
50
|
-
function parseMemoryFile(content: string): { name: string; description: string; type: string; body: string } {
|
|
51
|
-
const result = { name: '', description: '', type: '', body: content };
|
|
52
|
-
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
53
|
-
if (!fmMatch) return result;
|
|
54
|
-
|
|
55
|
-
const frontmatter = fmMatch[1];
|
|
56
|
-
result.body = fmMatch[2].trim();
|
|
57
|
-
|
|
58
|
-
for (const line of frontmatter.split('\n')) {
|
|
59
|
-
const kv = line.match(/^(\w+)\s*:\s*(.+)$/);
|
|
60
|
-
if (!kv) continue;
|
|
61
|
-
const [, key, val] = kv;
|
|
62
|
-
if (key === 'name') result.name = val.trim();
|
|
63
|
-
else if (key === 'description') result.description = val.trim();
|
|
64
|
-
else if (key === 'type') result.type = val.trim();
|
|
65
|
-
}
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Read and parse all auto-memory entries from a memory directory.
|
|
71
|
-
*/
|
|
72
|
-
function readAutoMemory(memoryDir: string): AutoMemoryResult {
|
|
73
|
-
const indexPath = join(memoryDir, 'MEMORY.md');
|
|
74
|
-
let linesUsed = 0;
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
const indexContent = readFileSync(indexPath, 'utf-8');
|
|
78
|
-
linesUsed = indexContent.split('\n').length;
|
|
79
|
-
} catch {
|
|
80
|
-
return { detected: true, path: memoryDir, entries: [], linesUsed: 0 };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const entries: AutoMemoryEntry[] = [];
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const files = readdirSync(memoryDir).filter(
|
|
87
|
-
(f) => f.endsWith('.md') && f !== 'MEMORY.md'
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
for (const file of files) {
|
|
91
|
-
try {
|
|
92
|
-
const content = readFileSync(join(memoryDir, file), 'utf-8');
|
|
93
|
-
const parsed = parseMemoryFile(content);
|
|
94
|
-
entries.push({
|
|
95
|
-
file,
|
|
96
|
-
name: parsed.name || file.replace('.md', ''),
|
|
97
|
-
description: parsed.description,
|
|
98
|
-
type: parsed.type,
|
|
99
|
-
body: parsed.body,
|
|
100
|
-
});
|
|
101
|
-
} catch {}
|
|
102
|
-
}
|
|
103
|
-
} catch {}
|
|
104
|
-
|
|
105
|
-
return { detected: true, path: memoryDir, entries, linesUsed };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
18
|
/**
|
|
109
19
|
* Build a search context string from auto-memory entries.
|
|
110
20
|
* Used to boost vault retrieval relevance.
|
|
@@ -241,12 +151,7 @@ export async function handler(
|
|
|
241
151
|
const sinceDate = new Date(Date.now() - RECENT_DAYS * 86400000).toISOString();
|
|
242
152
|
|
|
243
153
|
// Auto-detect Claude Code auto-memory (explicit path overrides auto-detection)
|
|
244
|
-
const
|
|
245
|
-
? (existsSync(join(auto_memory_path.trim(), 'MEMORY.md')) ? auto_memory_path.trim() : null)
|
|
246
|
-
: detectAutoMemoryPath();
|
|
247
|
-
const autoMemory: AutoMemoryResult = resolvedMemoryPath
|
|
248
|
-
? readAutoMemory(resolvedMemoryPath)
|
|
249
|
-
: { detected: false, path: null, entries: [], linesUsed: 0 };
|
|
154
|
+
const autoMemory: AutoMemoryResult = getAutoMemory(auto_memory_path);
|
|
250
155
|
const autoMemoryContext = buildAutoMemoryContext(autoMemory.entries);
|
|
251
156
|
const topicsExtracted = autoMemory.entries.length > 0
|
|
252
157
|
? extractKeywords(autoMemoryContext).slice(0, 20)
|
|
@@ -328,12 +233,140 @@ export async function handler(
|
|
|
328
233
|
}
|
|
329
234
|
}
|
|
330
235
|
|
|
236
|
+
// Remote entries: pull recent from hosted API if configured
|
|
237
|
+
const remoteClient = getRemoteClient(ctx.config);
|
|
238
|
+
let remoteCount = 0;
|
|
239
|
+
if (remoteClient && tokensUsed < tokenBudget) {
|
|
240
|
+
try {
|
|
241
|
+
const seenIds = new Set([
|
|
242
|
+
...decisions.map((d: any) => d.id),
|
|
243
|
+
...deduped.map((d: any) => d.id),
|
|
244
|
+
...(lastSession ? [lastSession.id] : []),
|
|
245
|
+
]);
|
|
246
|
+
const remoteTags = effectiveTags.length ? effectiveTags : undefined;
|
|
247
|
+
const remoteResults = await remoteClient.search({
|
|
248
|
+
tags: remoteTags,
|
|
249
|
+
limit: 10,
|
|
250
|
+
since: sinceDate,
|
|
251
|
+
});
|
|
252
|
+
const uniqueRemote = remoteResults.filter((r: any) => !seenIds.has(r.id));
|
|
253
|
+
if (uniqueRemote.length > 0) {
|
|
254
|
+
const header = '## Remote Entries\n';
|
|
255
|
+
const headerTokens = estimateTokens(header);
|
|
256
|
+
if (tokensUsed + headerTokens <= tokenBudget) {
|
|
257
|
+
const entryLines: string[] = [];
|
|
258
|
+
tokensUsed += headerTokens;
|
|
259
|
+
for (const entry of uniqueRemote) {
|
|
260
|
+
const line = formatEntry(entry);
|
|
261
|
+
const lineTokens = estimateTokens(line);
|
|
262
|
+
if (tokensUsed + lineTokens > tokenBudget) break;
|
|
263
|
+
entryLines.push(line);
|
|
264
|
+
tokensUsed += lineTokens;
|
|
265
|
+
remoteCount++;
|
|
266
|
+
}
|
|
267
|
+
if (entryLines.length > 0) {
|
|
268
|
+
sections.push(header + entryLines.join('\n') + '\n');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} catch (e) {
|
|
273
|
+
console.warn(`[context-vault] Remote session_start failed: ${(e as Error).message}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Team vault entries: include team knowledge in brief if teamId is configured
|
|
278
|
+
let teamCount = 0;
|
|
279
|
+
const teamId = getTeamId(ctx.config);
|
|
280
|
+
if (remoteClient && teamId && tokensUsed < tokenBudget) {
|
|
281
|
+
try {
|
|
282
|
+
const allSeenIds = new Set([
|
|
283
|
+
...decisions.map((d: any) => d.id),
|
|
284
|
+
...deduped.map((d: any) => d.id),
|
|
285
|
+
...(lastSession ? [lastSession.id] : []),
|
|
286
|
+
]);
|
|
287
|
+
const teamResults = await remoteClient.teamSearch(teamId, {
|
|
288
|
+
tags: effectiveTags.length ? effectiveTags : undefined,
|
|
289
|
+
limit: 10,
|
|
290
|
+
since: sinceDate,
|
|
291
|
+
});
|
|
292
|
+
const uniqueTeam = teamResults.filter((r: any) => !allSeenIds.has(r.id));
|
|
293
|
+
if (uniqueTeam.length > 0) {
|
|
294
|
+
const header = '## Team Knowledge\n';
|
|
295
|
+
const headerTokens = estimateTokens(header);
|
|
296
|
+
if (tokensUsed + headerTokens <= tokenBudget) {
|
|
297
|
+
const entryLines: string[] = [];
|
|
298
|
+
tokensUsed += headerTokens;
|
|
299
|
+
for (const entry of uniqueTeam) {
|
|
300
|
+
const line = formatEntry(entry) + ' `[team]`';
|
|
301
|
+
const lineTokens = estimateTokens(line);
|
|
302
|
+
if (tokensUsed + lineTokens > tokenBudget) break;
|
|
303
|
+
entryLines.push(line);
|
|
304
|
+
tokensUsed += lineTokens;
|
|
305
|
+
teamCount++;
|
|
306
|
+
}
|
|
307
|
+
if (entryLines.length > 0) {
|
|
308
|
+
sections.push(header + entryLines.join('\n') + '\n');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} catch (e) {
|
|
313
|
+
console.warn(`[context-vault] Team session_start failed: ${(e as Error).message}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Public vault entries: include public knowledge if publicVaults are configured
|
|
318
|
+
let publicCount = 0;
|
|
319
|
+
const publicVaultSlugs = getPublicVaults(ctx.config);
|
|
320
|
+
if (remoteClient && publicVaultSlugs.length > 0 && tokensUsed < tokenBudget) {
|
|
321
|
+
try {
|
|
322
|
+
const allPublicSeenIds = new Set([
|
|
323
|
+
...decisions.map((d: any) => d.id),
|
|
324
|
+
...deduped.map((d: any) => d.id),
|
|
325
|
+
...(lastSession ? [lastSession.id] : []),
|
|
326
|
+
]);
|
|
327
|
+
const publicSearches = publicVaultSlugs.map(slug =>
|
|
328
|
+
remoteClient.publicSearch(slug, {
|
|
329
|
+
tags: effectiveTags.length ? effectiveTags : undefined,
|
|
330
|
+
limit: 5,
|
|
331
|
+
since: sinceDate,
|
|
332
|
+
}).catch(() => [])
|
|
333
|
+
);
|
|
334
|
+
const allPublicResults = await Promise.all(publicSearches);
|
|
335
|
+
const flatPublic = allPublicResults.flat().filter((r: any) => !allPublicSeenIds.has(r.id));
|
|
336
|
+
if (flatPublic.length > 0) {
|
|
337
|
+
const header = '## Public Knowledge\n';
|
|
338
|
+
const headerTokens = estimateTokens(header);
|
|
339
|
+
if (tokensUsed + headerTokens <= tokenBudget) {
|
|
340
|
+
const entryLines: string[] = [];
|
|
341
|
+
tokensUsed += headerTokens;
|
|
342
|
+
for (const entry of flatPublic) {
|
|
343
|
+
const slug = (entry as any).vault_slug || 'public';
|
|
344
|
+
const line = formatEntry(entry) + ` \`[public:${slug}]\``;
|
|
345
|
+
const lineTokens = estimateTokens(line);
|
|
346
|
+
if (tokensUsed + lineTokens > tokenBudget) break;
|
|
347
|
+
entryLines.push(line);
|
|
348
|
+
tokensUsed += lineTokens;
|
|
349
|
+
publicCount++;
|
|
350
|
+
}
|
|
351
|
+
if (entryLines.length > 0) {
|
|
352
|
+
sections.push(header + entryLines.join('\n') + '\n');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
} catch (e) {
|
|
357
|
+
console.warn(`[context-vault] Public vault session_start failed: ${(e as Error).message}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
331
361
|
const totalEntries =
|
|
332
362
|
(lastSession ? 1 : 0) +
|
|
333
363
|
decisions.length +
|
|
334
364
|
deduped.filter((_d: any) => {
|
|
335
365
|
return true;
|
|
336
|
-
}).length
|
|
366
|
+
}).length +
|
|
367
|
+
remoteCount +
|
|
368
|
+
teamCount +
|
|
369
|
+
publicCount;
|
|
337
370
|
|
|
338
371
|
if (indexWarning) {
|
|
339
372
|
sections.push(indexWarning);
|