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.
Files changed (68) hide show
  1. package/assets/agent-rules.md +28 -1
  2. package/assets/setup-prompt.md +16 -1
  3. package/bin/cli.js +1187 -4
  4. package/dist/auto-memory.d.ts +52 -0
  5. package/dist/auto-memory.d.ts.map +1 -0
  6. package/dist/auto-memory.js +142 -0
  7. package/dist/auto-memory.js.map +1 -0
  8. package/dist/register-tools.d.ts.map +1 -1
  9. package/dist/register-tools.js +2 -0
  10. package/dist/register-tools.js.map +1 -1
  11. package/dist/remote.d.ts +186 -0
  12. package/dist/remote.d.ts.map +1 -0
  13. package/dist/remote.js +372 -0
  14. package/dist/remote.js.map +1 -0
  15. package/dist/remote.test.d.ts +2 -0
  16. package/dist/remote.test.d.ts.map +1 -0
  17. package/dist/remote.test.js +107 -0
  18. package/dist/remote.test.js.map +1 -0
  19. package/dist/tools/context-status.d.ts.map +1 -1
  20. package/dist/tools/context-status.js +19 -0
  21. package/dist/tools/context-status.js.map +1 -1
  22. package/dist/tools/get-context.d.ts.map +1 -1
  23. package/dist/tools/get-context.js +70 -0
  24. package/dist/tools/get-context.js.map +1 -1
  25. package/dist/tools/publish-to-team.d.ts +11 -0
  26. package/dist/tools/publish-to-team.d.ts.map +1 -0
  27. package/dist/tools/publish-to-team.js +91 -0
  28. package/dist/tools/publish-to-team.js.map +1 -0
  29. package/dist/tools/publish-to-team.test.d.ts +2 -0
  30. package/dist/tools/publish-to-team.test.d.ts.map +1 -0
  31. package/dist/tools/publish-to-team.test.js +95 -0
  32. package/dist/tools/publish-to-team.test.js.map +1 -0
  33. package/dist/tools/recall.d.ts +1 -1
  34. package/dist/tools/recall.d.ts.map +1 -1
  35. package/dist/tools/recall.js +120 -1
  36. package/dist/tools/recall.js.map +1 -1
  37. package/dist/tools/save-context.d.ts +5 -1
  38. package/dist/tools/save-context.d.ts.map +1 -1
  39. package/dist/tools/save-context.js +163 -2
  40. package/dist/tools/save-context.js.map +1 -1
  41. package/dist/tools/session-start.d.ts.map +1 -1
  42. package/dist/tools/session-start.js +134 -86
  43. package/dist/tools/session-start.js.map +1 -1
  44. package/node_modules/@context-vault/core/dist/config.d.ts +3 -1
  45. package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -1
  46. package/node_modules/@context-vault/core/dist/config.js +48 -2
  47. package/node_modules/@context-vault/core/dist/config.js.map +1 -1
  48. package/node_modules/@context-vault/core/dist/main.d.ts +1 -1
  49. package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -1
  50. package/node_modules/@context-vault/core/dist/main.js.map +1 -1
  51. package/node_modules/@context-vault/core/dist/types.d.ts +7 -0
  52. package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -1
  53. package/node_modules/@context-vault/core/package.json +1 -1
  54. package/node_modules/@context-vault/core/src/config.ts +50 -3
  55. package/node_modules/@context-vault/core/src/main.ts +1 -0
  56. package/node_modules/@context-vault/core/src/types.ts +8 -0
  57. package/package.json +2 -2
  58. package/src/auto-memory.ts +169 -0
  59. package/src/register-tools.ts +2 -0
  60. package/src/remote.test.ts +123 -0
  61. package/src/remote.ts +470 -0
  62. package/src/tools/context-status.ts +19 -0
  63. package/src/tools/get-context.ts +72 -0
  64. package/src/tools/publish-to-team.test.ts +115 -0
  65. package/src/tools/publish-to-team.ts +112 -0
  66. package/src/tools/recall.ts +113 -1
  67. package/src/tools/save-context.ts +167 -1
  68. 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 resolvedMemoryPath = auto_memory_path?.trim()
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);