context-vault 3.18.0 → 3.20.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/bin/cli.js +673 -4
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +0 -2
- package/dist/register-tools.js.map +1 -1
- package/dist/server.js +78 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/recall.d.ts +1 -1
- package/dist/tools/recall.d.ts.map +1 -1
- package/dist/tools/recall.js +50 -100
- package/dist/tools/recall.js.map +1 -1
- package/node_modules/@context-vault/core/dist/assemble.d.ts +22 -0
- package/node_modules/@context-vault/core/dist/assemble.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/assemble.js +143 -0
- package/node_modules/@context-vault/core/dist/assemble.js.map +1 -0
- package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/capture.js +10 -5
- package/node_modules/@context-vault/core/dist/capture.js.map +1 -1
- package/node_modules/@context-vault/core/dist/consolidation.d.ts +40 -0
- package/node_modules/@context-vault/core/dist/consolidation.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/consolidation.js +229 -0
- package/node_modules/@context-vault/core/dist/consolidation.js.map +1 -0
- package/node_modules/@context-vault/core/dist/db.d.ts +25 -1
- package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/db.js +92 -4
- package/node_modules/@context-vault/core/dist/db.js.map +1 -1
- package/node_modules/@context-vault/core/dist/frontmatter.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/frontmatter.js +26 -3
- package/node_modules/@context-vault/core/dist/frontmatter.js.map +1 -1
- package/node_modules/@context-vault/core/dist/index.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/index.js +225 -184
- package/node_modules/@context-vault/core/dist/index.js.map +1 -1
- package/node_modules/@context-vault/core/dist/main.d.ts +3 -0
- package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/main.js +4 -0
- package/node_modules/@context-vault/core/dist/main.js.map +1 -1
- package/node_modules/@context-vault/core/dist/search.d.ts +6 -0
- package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/search.js +106 -5
- package/node_modules/@context-vault/core/dist/search.js.map +1 -1
- package/node_modules/@context-vault/core/dist/search.test.d.ts +2 -0
- package/node_modules/@context-vault/core/dist/search.test.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/search.test.js +49 -0
- package/node_modules/@context-vault/core/dist/search.test.js.map +1 -0
- package/node_modules/@context-vault/core/dist/summarize.d.ts +5 -0
- package/node_modules/@context-vault/core/dist/summarize.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/summarize.js +146 -0
- package/node_modules/@context-vault/core/dist/summarize.js.map +1 -0
- package/node_modules/@context-vault/core/dist/types.d.ts +2 -0
- package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -1
- package/node_modules/@context-vault/core/package.json +13 -1
- package/node_modules/@context-vault/core/src/assemble.ts +187 -0
- package/node_modules/@context-vault/core/src/capture.ts +10 -5
- package/node_modules/@context-vault/core/src/consolidation.ts +356 -0
- package/node_modules/@context-vault/core/src/db.ts +95 -4
- package/node_modules/@context-vault/core/src/frontmatter.ts +25 -4
- package/node_modules/@context-vault/core/src/index.ts +127 -88
- package/node_modules/@context-vault/core/src/main.ts +7 -0
- package/node_modules/@context-vault/core/src/search.test.ts +59 -0
- package/node_modules/@context-vault/core/src/search.ts +112 -5
- package/node_modules/@context-vault/core/src/summarize.ts +157 -0
- package/node_modules/@context-vault/core/src/types.ts +2 -0
- package/package.json +2 -2
- package/scripts/validate-epipe-shutdown.mjs +183 -0
- package/scripts/validate-sqlite-busy-retry.mjs +243 -0
- package/src/register-tools.ts +0 -2
- package/src/server.ts +76 -1
- package/src/tools/recall.ts +51 -110
- package/.claude-plugin/README.md +0 -219
- package/.claude-plugin/plugin.json +0 -11
- package/commands/vault-cleanup.md +0 -43
- package/commands/vault-snapshot.md +0 -43
- package/commands/vault-status.md +0 -35
- package/dist/tools/session-start.d.ts +0 -25
- package/dist/tools/session-start.d.ts.map +0 -1
- package/dist/tools/session-start.js +0 -469
- package/dist/tools/session-start.js.map +0 -1
- package/skills/context-assembly/SKILL.md +0 -308
- package/skills/knowledge-capture/SKILL.md +0 -303
- package/skills/memory-management/SKILL.md +0 -237
- package/src/tools/session-start.ts +0 -527
package/src/server.ts
CHANGED
|
@@ -11,6 +11,50 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
13
13
|
|
|
14
|
+
// Module-level shutdown coordination so pipe/uncaught handlers (below main())
|
|
15
|
+
// can route through the graceful shutdown wired up inside main().
|
|
16
|
+
let shutdownHandler: ((signal: string) => void) | null = null;
|
|
17
|
+
let shutdownInProgress = false;
|
|
18
|
+
|
|
19
|
+
function isPipeError(err: unknown): boolean {
|
|
20
|
+
if (!err || typeof err !== 'object') return false;
|
|
21
|
+
const e = err as { code?: string; message?: string };
|
|
22
|
+
if (e.code === 'EPIPE' || e.code === 'ERR_STREAM_DESTROYED' || e.code === 'ERR_STREAM_WRITE_AFTER_END') return true;
|
|
23
|
+
return typeof e.message === 'string' && /\bEPIPE\b/.test(e.message);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handlePipeDisconnect(source: string): void {
|
|
27
|
+
if (shutdownInProgress) return;
|
|
28
|
+
if (shutdownHandler) {
|
|
29
|
+
try {
|
|
30
|
+
shutdownHandler(`EPIPE:${source}`);
|
|
31
|
+
} catch {
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
// Shutdown not wired yet (startup phase). Exit clean so WAL isn't dirtied.
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Catch broken-pipe writes to stdout/stderr so they route through graceful
|
|
41
|
+
// shutdown instead of bubbling up as an `uncaughtException` that skips the
|
|
42
|
+
// WAL checkpoint. Node raises EPIPE (not SIGPIPE) on pipe writes with no reader.
|
|
43
|
+
process.stdout.on('error', (err) => {
|
|
44
|
+
if (isPipeError(err)) {
|
|
45
|
+
handlePipeDisconnect('stdout');
|
|
46
|
+
} else {
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
process.stderr.on('error', (err) => {
|
|
51
|
+
if (isPipeError(err)) {
|
|
52
|
+
handlePipeDisconnect('stderr');
|
|
53
|
+
} else {
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
14
58
|
import { resolveConfig } from '@context-vault/core/config';
|
|
15
59
|
import type { LocalCtx } from './types.js';
|
|
16
60
|
import { appendErrorLog } from './error-log.js';
|
|
@@ -206,7 +250,28 @@ async function main(): Promise<void> {
|
|
|
206
250
|
}
|
|
207
251
|
|
|
208
252
|
function shutdown(signal: string): void {
|
|
209
|
-
|
|
253
|
+
// Idempotent: EPIPE from stdout error + uncaughtException can both fire
|
|
254
|
+
// during a single client disconnect. Second call becomes a no-op.
|
|
255
|
+
if (shutdownInProgress) return;
|
|
256
|
+
shutdownInProgress = true;
|
|
257
|
+
|
|
258
|
+
const isEpipe = signal.startsWith('EPIPE');
|
|
259
|
+
if (isEpipe) {
|
|
260
|
+
// Log a clean shutdown entry in place of the EPIPE uncaughtException
|
|
261
|
+
// that would otherwise have fired. Keeps the audit log readable.
|
|
262
|
+
appendErrorLog(config!.dataDir, {
|
|
263
|
+
timestamp: new Date().toISOString(),
|
|
264
|
+
error_type: 'EPIPE_shutdown',
|
|
265
|
+
message: `client pipe closed (${signal}); graceful shutdown`,
|
|
266
|
+
node_version: process.version,
|
|
267
|
+
platform: process.platform,
|
|
268
|
+
arch: process.arch,
|
|
269
|
+
cv_version: pkg.version,
|
|
270
|
+
});
|
|
271
|
+
console.error(`[context-vault] EPIPE shutdown: client disconnected (${signal})`);
|
|
272
|
+
} else {
|
|
273
|
+
console.error(`[context-vault] Received ${signal}, shutting down...`);
|
|
274
|
+
}
|
|
210
275
|
|
|
211
276
|
if (ctx.activeOps.count > 0) {
|
|
212
277
|
console.error(
|
|
@@ -232,6 +297,9 @@ async function main(): Promise<void> {
|
|
|
232
297
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
233
298
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
234
299
|
|
|
300
|
+
// Expose shutdown to module-level handlers (stdout error, uncaughtException).
|
|
301
|
+
shutdownHandler = shutdown;
|
|
302
|
+
|
|
235
303
|
// RSS watchdog: kill the process if memory usage exceeds the cap.
|
|
236
304
|
// Prevents runaway embedding/reindex operations from frying user systems.
|
|
237
305
|
const MAX_RSS_BYTES = parseInt(process.env.CONTEXT_VAULT_MAX_RSS_MB || '1024', 10) * 1024 * 1024;
|
|
@@ -310,6 +378,13 @@ async function main(): Promise<void> {
|
|
|
310
378
|
}
|
|
311
379
|
|
|
312
380
|
process.on('uncaughtException', (err) => {
|
|
381
|
+
// EPIPE from a dead client pipe is not a crash; it's a disconnect.
|
|
382
|
+
// Route through graceful shutdown so the WAL is checkpointed.
|
|
383
|
+
if (isPipeError(err)) {
|
|
384
|
+
handlePipeDisconnect('uncaught');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
313
388
|
const dataDir = join(homedir(), '.context-mcp');
|
|
314
389
|
const logEntry = {
|
|
315
390
|
timestamp: new Date().toISOString(),
|
package/src/tools/recall.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { ok } from '../helpers.js';
|
|
3
|
-
import {
|
|
3
|
+
import { hybridSearch } from '@context-vault/core/search';
|
|
4
4
|
import { getAutoMemory, findAutoMemoryOverlaps } from '../auto-memory.js';
|
|
5
5
|
import { getRemoteClient, getTeamId, getPublicVaults } from '../remote.js';
|
|
6
6
|
import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
|
|
7
|
+
import type { SearchOptions } from '@context-vault/core/types';
|
|
7
8
|
|
|
8
|
-
const SEMANTIC_SIMILARITY_THRESHOLD = 0.6;
|
|
9
9
|
const CO_RETRIEVAL_WEIGHT_CAP = 50;
|
|
10
10
|
|
|
11
11
|
const STOPWORDS = new Set([
|
|
@@ -31,14 +31,14 @@ const sessionSurfaced = new Map<string, Set<string>>();
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Extract keywords from a signal string.
|
|
34
|
-
* Split on whitespace, filter stopwords and words under
|
|
34
|
+
* Split on whitespace, filter stopwords and words under 2 chars, keep top 10.
|
|
35
35
|
*/
|
|
36
36
|
export function extractKeywords(signal: string): string[] {
|
|
37
37
|
const words = signal
|
|
38
38
|
.toLowerCase()
|
|
39
39
|
.replace(/[^a-z0-9\s_-]/g, ' ')
|
|
40
40
|
.split(/\s+/)
|
|
41
|
-
.filter((w) => w.length >=
|
|
41
|
+
.filter((w) => w.length >= 2 && !STOPWORDS.has(w));
|
|
42
42
|
|
|
43
43
|
const seen = new Set<string>();
|
|
44
44
|
const unique: string[] = [];
|
|
@@ -100,30 +100,48 @@ export async function handler(
|
|
|
100
100
|
return result;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
// Build
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const bucketClause = bucket ? ' AND tags LIKE ?' : '';
|
|
112
|
-
if (bucket) params.push(`%"bucket:${bucket}"%`);
|
|
103
|
+
// Build search query from signal, enriched by signal_type
|
|
104
|
+
let searchQuery = signal || '';
|
|
105
|
+
const searchOpts: SearchOptions = {
|
|
106
|
+
excludeEvents: true,
|
|
107
|
+
limit: limit * 3, // over-fetch to allow for session dedup + bucket filtering
|
|
108
|
+
};
|
|
113
109
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
// Signal-type aware search options
|
|
111
|
+
switch (signal_type) {
|
|
112
|
+
case 'error':
|
|
113
|
+
// Errors should boost recent entries
|
|
114
|
+
searchOpts.decayDays = 7;
|
|
115
|
+
break;
|
|
116
|
+
case 'file':
|
|
117
|
+
// Extract path components and extension as additional search terms
|
|
118
|
+
searchQuery = signal
|
|
119
|
+
.replace(/[/\\]/g, ' ')
|
|
120
|
+
.replace(/\./g, ' ')
|
|
121
|
+
.trim();
|
|
122
|
+
break;
|
|
123
|
+
case 'task':
|
|
124
|
+
// Tasks benefit from wider search
|
|
125
|
+
searchOpts.limit = Math.max(searchOpts.limit!, limit * 5);
|
|
126
|
+
break;
|
|
127
|
+
// 'prompt': standard hybrid search, no modifications
|
|
128
|
+
}
|
|
121
129
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
// Run hybrid search (FTS + vector + tag lanes with RRF fusion)
|
|
131
|
+
let searchResults = await hybridSearch(ctx, searchQuery, searchOpts);
|
|
132
|
+
|
|
133
|
+
// Bucket-aware post-filtering
|
|
134
|
+
if (bucket) {
|
|
135
|
+
const bucketTag = `bucket:${bucket}`;
|
|
136
|
+
searchResults = searchResults.filter((r) => {
|
|
137
|
+
if (!r.tags) return false;
|
|
138
|
+
try {
|
|
139
|
+
const tags: string[] = typeof r.tags === 'string' ? JSON.parse(r.tags) : r.tags;
|
|
140
|
+
return tags.some((t) => t === bucketTag);
|
|
141
|
+
} catch {
|
|
142
|
+
return String(r.tags).includes(bucketTag);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
// Session dedup
|
|
@@ -142,7 +160,7 @@ export async function handler(
|
|
|
142
160
|
tags: string[];
|
|
143
161
|
}> = [];
|
|
144
162
|
|
|
145
|
-
for (const row of
|
|
163
|
+
for (const row of searchResults) {
|
|
146
164
|
if (hints.length >= limit) break;
|
|
147
165
|
|
|
148
166
|
// Dedup check
|
|
@@ -151,21 +169,16 @@ export async function handler(
|
|
|
151
169
|
continue;
|
|
152
170
|
}
|
|
153
171
|
|
|
154
|
-
const entryTags: string[] = row.tags
|
|
172
|
+
const entryTags: string[] = row.tags
|
|
173
|
+
? (typeof row.tags === 'string' ? JSON.parse(row.tags) : row.tags)
|
|
174
|
+
: [];
|
|
155
175
|
|
|
156
|
-
|
|
157
|
-
let matchCount = 0;
|
|
158
|
-
const titleLower = (row.title || '').toLowerCase();
|
|
159
|
-
const tagsLower = (row.tags || '').toLowerCase();
|
|
160
|
-
for (const kw of keywords) {
|
|
161
|
-
if (titleLower.includes(kw) || tagsLower.includes(kw)) matchCount++;
|
|
162
|
-
}
|
|
163
|
-
const relevance: 'high' | 'medium' = matchCount >= 2 ? 'high' : 'medium';
|
|
176
|
+
const relevance: 'high' | 'medium' = row.score >= 0.02 ? 'high' : 'medium';
|
|
164
177
|
|
|
165
178
|
hints.push({
|
|
166
179
|
id: row.id,
|
|
167
180
|
title: row.title || '(untitled)',
|
|
168
|
-
summary: row.
|
|
181
|
+
summary: row.body ? row.body.slice(0, 100) : '',
|
|
169
182
|
relevance,
|
|
170
183
|
kind: row.kind || 'knowledge',
|
|
171
184
|
tags: entryTags,
|
|
@@ -261,79 +274,7 @@ export async function handler(
|
|
|
261
274
|
}
|
|
262
275
|
}
|
|
263
276
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
// Semantic fallback: when fast-path returns 0 results and signal is not file-based
|
|
267
|
-
if (hints.length === 0 && signal_type !== 'file' && isEmbedAvailable()) {
|
|
268
|
-
try {
|
|
269
|
-
const vecCount = (
|
|
270
|
-
ctx.db.prepare('SELECT COUNT(*) as c FROM vault_vec').get() as { c: number }
|
|
271
|
-
).c;
|
|
272
|
-
|
|
273
|
-
if (vecCount > 0) {
|
|
274
|
-
const queryVec = await ctx.embed(signal);
|
|
275
|
-
if (queryVec) {
|
|
276
|
-
const vecRows = ctx.db
|
|
277
|
-
.prepare(
|
|
278
|
-
'SELECT v.rowid, v.distance FROM vault_vec v WHERE embedding MATCH ? ORDER BY distance LIMIT 5'
|
|
279
|
-
)
|
|
280
|
-
.all(queryVec) as { rowid: number; distance: number }[];
|
|
281
|
-
|
|
282
|
-
if (vecRows.length) {
|
|
283
|
-
const rowids = vecRows.map((vr) => vr.rowid);
|
|
284
|
-
const placeholders = rowids.map(() => '?').join(',');
|
|
285
|
-
|
|
286
|
-
let bucketFilter = '';
|
|
287
|
-
const hydrateParams: any[] = [...rowids];
|
|
288
|
-
if (bucket) {
|
|
289
|
-
bucketFilter = ' AND tags LIKE ?';
|
|
290
|
-
hydrateParams.push(`%"bucket:${bucket}"%`);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const hydrated = ctx.db
|
|
294
|
-
.prepare(
|
|
295
|
-
`SELECT rowid, id, title, substr(body, 1, 100) as summary, kind, tags FROM vault WHERE rowid IN (${placeholders}) AND indexed = 1 AND (expires_at IS NULL OR expires_at > datetime('now')) AND superseded_by IS NULL${bucketFilter}`
|
|
296
|
-
)
|
|
297
|
-
.all(...hydrateParams) as any[];
|
|
298
|
-
|
|
299
|
-
const byRowid = new Map<number, any>();
|
|
300
|
-
for (const row of hydrated) byRowid.set(row.rowid, row);
|
|
301
|
-
|
|
302
|
-
for (const vr of vecRows) {
|
|
303
|
-
if (hints.length >= limit) break;
|
|
304
|
-
const row = byRowid.get(vr.rowid);
|
|
305
|
-
if (!row) continue;
|
|
306
|
-
|
|
307
|
-
const similarity = Math.max(0, 1 - vr.distance / 2);
|
|
308
|
-
if (similarity < SEMANTIC_SIMILARITY_THRESHOLD) continue;
|
|
309
|
-
|
|
310
|
-
// Session dedup
|
|
311
|
-
if (sessionSet && !bypassDedup && sessionSet.has(row.id)) {
|
|
312
|
-
suppressed++;
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const entryTags: string[] = row.tags ? JSON.parse(row.tags) : [];
|
|
317
|
-
hints.push({
|
|
318
|
-
id: row.id,
|
|
319
|
-
title: row.title || '(untitled)',
|
|
320
|
-
summary: row.summary || '',
|
|
321
|
-
relevance: similarity >= 0.8 ? 'high' : 'medium',
|
|
322
|
-
kind: row.kind || 'knowledge',
|
|
323
|
-
tags: entryTags,
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
if (sessionSet) sessionSet.add(row.id);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (hints.length > 0) method = 'semantic';
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
} catch {
|
|
334
|
-
// Semantic fallback is best-effort; fast path already ran
|
|
335
|
-
}
|
|
336
|
-
}
|
|
277
|
+
const method: 'hybrid' | 'none' = hints.length > 0 ? 'hybrid' : 'none';
|
|
337
278
|
|
|
338
279
|
const latency = Date.now() - start;
|
|
339
280
|
|
package/.claude-plugin/README.md
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
# context-vault Claude Plugin
|
|
2
|
-
|
|
3
|
-
Persistent memory and knowledge management for AI agents. Save decisions, insights, patterns, and context across sessions using semantic search and project-scoped buckets.
|
|
4
|
-
|
|
5
|
-
## What is context-vault?
|
|
6
|
-
|
|
7
|
-
context-vault is a local-first memory system that lets Claude persistently store and retrieve knowledge. Think of it as a personal knowledge base that follows you across sessions, projects, and contexts.
|
|
8
|
-
|
|
9
|
-
### Key Features
|
|
10
|
-
|
|
11
|
-
- **Persistent Memory** -- Save insights, decisions, patterns, and references across sessions
|
|
12
|
-
- **Semantic Search** -- Find entries by meaning, not just keywords
|
|
13
|
-
- **Project Buckets** -- Organize entries by project for easy scoping
|
|
14
|
-
- **Team Vaults** -- Share knowledge with your team (requires hosted API)
|
|
15
|
-
- **Deduplication** -- Automatic detection of similar entries
|
|
16
|
-
- **Encoding Context** -- Future-proof your saves with metadata for discovery months later
|
|
17
|
-
- **Snapshot Briefs** -- Consolidate scattered entries into a single context brief
|
|
18
|
-
- **Recall Tracking** -- Monitor how often your entries are actually used
|
|
19
|
-
|
|
20
|
-
## Installation
|
|
21
|
-
|
|
22
|
-
Install via Claude Code:
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
/plugin install context-vault
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
For full setup with embedding model download and agent rules:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npx context-vault setup
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Quick Start
|
|
35
|
-
|
|
36
|
-
### Save Your First Insight
|
|
37
|
-
|
|
38
|
-
```javascript
|
|
39
|
-
save_context({
|
|
40
|
-
kind: "insight",
|
|
41
|
-
title: "PostgreSQL connection pooling lesson",
|
|
42
|
-
body: "Connections without pooling exhausted at 100 concurrent users...",
|
|
43
|
-
tags: ["bucket:myproject", "database"],
|
|
44
|
-
encoding_context: { project: "myproject", arc: "scaling", task: "investigation" }
|
|
45
|
-
})
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Load Context for a Task
|
|
49
|
-
|
|
50
|
-
```javascript
|
|
51
|
-
// At the start of a session, scope to your project and get an overview
|
|
52
|
-
clear_context({
|
|
53
|
-
preload_bucket: "myproject",
|
|
54
|
-
max_tokens: 4000
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
// Then search for specific context
|
|
58
|
-
get_context({
|
|
59
|
-
query: "how do we handle database scaling?",
|
|
60
|
-
buckets: ["myproject"]
|
|
61
|
-
})
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Create a Context Brief
|
|
65
|
-
|
|
66
|
-
```javascript
|
|
67
|
-
create_snapshot({
|
|
68
|
-
topic: "API authentication decisions",
|
|
69
|
-
buckets: ["myproject"]
|
|
70
|
-
})
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Commands
|
|
74
|
-
|
|
75
|
-
Three slash commands for common vault workflows:
|
|
76
|
-
|
|
77
|
-
- **/vault-status** -- Check vault health (entry counts, recall ratio, warnings)
|
|
78
|
-
- **/vault-snapshot** -- Create a consolidated context brief for a topic
|
|
79
|
-
- **/vault-cleanup** -- Triage stale entries, consolidate duplicates, manage growth
|
|
80
|
-
|
|
81
|
-
## Skills
|
|
82
|
-
|
|
83
|
-
Three skills teach Claude how to use the vault effectively:
|
|
84
|
-
|
|
85
|
-
### 1. Memory Management
|
|
86
|
-
When to save, what kind to use, how to tag entries, and how to keep your vault healthy.
|
|
87
|
-
|
|
88
|
-
### 2. Knowledge Capture
|
|
89
|
-
Session-end protocol: extract and save the insights and decisions you discovered.
|
|
90
|
-
|
|
91
|
-
### 3. Context Assembly
|
|
92
|
-
Load relevant knowledge at task start. Assemble context efficiently and scope to your project.
|
|
93
|
-
|
|
94
|
-
## Use Cases
|
|
95
|
-
|
|
96
|
-
### Personal Memory
|
|
97
|
-
Save decisions and insights from your work, then access them months later.
|
|
98
|
-
|
|
99
|
-
```javascript
|
|
100
|
-
save_context({
|
|
101
|
-
kind: "decision",
|
|
102
|
-
title: "Chose SQLite + embeddings for local RAG",
|
|
103
|
-
body: "Evaluated: PostgreSQL (setup), Pinecone (cost), local SQLite (simplicity). Chose SQLite + sqlite-vec for full control and no external dependencies.",
|
|
104
|
-
tags: ["bucket:rag-project", "architecture", "database"],
|
|
105
|
-
tier: "durable"
|
|
106
|
-
})
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Team Knowledge Sharing
|
|
110
|
-
Capture lessons learned that the team should know, then publish to a shared vault.
|
|
111
|
-
|
|
112
|
-
```javascript
|
|
113
|
-
save_context({
|
|
114
|
-
kind: "pattern",
|
|
115
|
-
title: "Always test OAuth flow end-to-end on real device",
|
|
116
|
-
body: "Simulator behavior differs from real devices. Always test with physical phone before production.",
|
|
117
|
-
tags: ["bucket:mobile-team", "oauth", "testing"],
|
|
118
|
-
tier: "durable"
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
// Later, share to team vault
|
|
122
|
-
publish_to_team({ id: "entry-id" })
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Project Onboarding
|
|
126
|
-
Create a snapshot of all decisions and patterns for a new team member.
|
|
127
|
-
|
|
128
|
-
```javascript
|
|
129
|
-
create_snapshot({
|
|
130
|
-
topic: "project architecture and key decisions",
|
|
131
|
-
buckets: ["project-x"],
|
|
132
|
-
kinds: ["decision", "pattern"]
|
|
133
|
-
})
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Tools Reference
|
|
137
|
-
|
|
138
|
-
The plugin provides 14 MCP tools:
|
|
139
|
-
|
|
140
|
-
### Read-Only (no side effects)
|
|
141
|
-
- `list_context` -- Browse entries by kind, tags, date range
|
|
142
|
-
- `context_status` -- Vault health dashboard (entry counts, recall stats, warnings)
|
|
143
|
-
- `list_buckets` -- List all project buckets
|
|
144
|
-
- `session_start` -- Load project context at session start
|
|
145
|
-
|
|
146
|
-
### Read with Analytics (update access counters as side effect)
|
|
147
|
-
- `get_context` -- Search vault by query, kind, bucket, or date. Updates `hit_count` and `recall_count` on returned entries.
|
|
148
|
-
- `recall` -- Proactive context surfacing. Takes a `signal` (text) and `signal_type` (prompt/error/file/task), returns relevant hints. Records co-retrieval pairs.
|
|
149
|
-
|
|
150
|
-
### Write (create new entries)
|
|
151
|
-
- `save_context` -- Create or update an entry. Supports `encoding_context` for future discovery and `supersedes` to retire old entries.
|
|
152
|
-
- `create_snapshot` -- Consolidate scattered entries into a single brief. Returns a confirmation with the new entry's ID and identity_key.
|
|
153
|
-
- `ingest_url` -- Fetch a web page and save as a reference entry
|
|
154
|
-
- `ingest_project` -- Scan a project directory and save metadata
|
|
155
|
-
- `session_end` -- Capture session learnings (auto-save insights)
|
|
156
|
-
- `publish_to_team` -- Copy an entry to your team vault
|
|
157
|
-
|
|
158
|
-
### Destructive (modify or delete existing data)
|
|
159
|
-
- `delete_context` -- Permanently remove an entry by ID
|
|
160
|
-
- `clear_context` -- Reset session scope. With `preload_bucket`, returns recent entries from that bucket in the response.
|
|
161
|
-
|
|
162
|
-
## Storage
|
|
163
|
-
|
|
164
|
-
Data is stored in two locations:
|
|
165
|
-
|
|
166
|
-
```
|
|
167
|
-
~/.context-mcp/ # Data directory
|
|
168
|
-
├── vault.db # SQLite database (entries, embeddings, FTS index)
|
|
169
|
-
├── config.json # User configuration
|
|
170
|
-
└── telemetry.jsonl # Local usage telemetry
|
|
171
|
-
|
|
172
|
-
~/vault/ (or configured path) # Vault directory (markdown source of truth)
|
|
173
|
-
├── knowledge/ # Decisions, insights, patterns, references
|
|
174
|
-
├── entity/ # People, projects, tools
|
|
175
|
-
└── event/ # Session logs, activity
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
Markdown files are the source of truth. The SQLite database is a derived index, rebuildable via `context-vault reindex`.
|
|
179
|
-
|
|
180
|
-
## Privacy
|
|
181
|
-
|
|
182
|
-
**Local-first:** All data stored locally on your machine. No network calls unless you explicitly enable hosted sync or team vaults.
|
|
183
|
-
|
|
184
|
-
**Hosted sync (optional):** When configured, entries sync to your personal cloud vault (per-user isolated database). Team vault entries are shared only when you call `publish_to_team`.
|
|
185
|
-
|
|
186
|
-
**Your vault is never accessed or indexed by third parties.** Full privacy policy: https://context-vault.com/privacy
|
|
187
|
-
|
|
188
|
-
## Tips
|
|
189
|
-
|
|
190
|
-
- **Encoding context is key:** Entries without `encoding_context` are rarely discovered later. Always include project, arc, and task.
|
|
191
|
-
- **Check before saving:** Call `get_context` first to avoid duplicates. Duplicates hurt recall ratio.
|
|
192
|
-
- **Use tiers wisely:** `durable` for architecture decisions, `working` (default) for active context, `ephemeral` for temp notes.
|
|
193
|
-
- **Snapshot for scale:** If you have 20+ entries on a topic, create a snapshot instead of loading them individually.
|
|
194
|
-
- **Recall ratio:** 20-30% is healthy. Run `/vault-status` to check yours.
|
|
195
|
-
|
|
196
|
-
## Troubleshooting
|
|
197
|
-
|
|
198
|
-
**"I can't find my entry"**
|
|
199
|
-
- Check the right bucket: `get_context({ query: "...", buckets: ["myproject"] })`
|
|
200
|
-
- Try a broader query: "authentication" instead of "OAuth 2.0 PKCE state handling"
|
|
201
|
-
- Browse by kind: `list_context({ kind: "decision", tags: ["bucket:myproject"] })`
|
|
202
|
-
|
|
203
|
-
**"Vault is growing too fast"**
|
|
204
|
-
- Run `/vault-cleanup` to identify stale or duplicate entries
|
|
205
|
-
- Create snapshots to consolidate large topics
|
|
206
|
-
- Check for session/debug noise mixed with knowledge entries
|
|
207
|
-
|
|
208
|
-
**"Embeddings not working"**
|
|
209
|
-
- Run `context-vault reindex` to regenerate the embedding index
|
|
210
|
-
- Run `context-vault doctor` for a full diagnostic
|
|
211
|
-
|
|
212
|
-
## Support
|
|
213
|
-
|
|
214
|
-
- GitHub: https://github.com/fellanH/context-vault/issues
|
|
215
|
-
- Website: https://context-vault.com
|
|
216
|
-
|
|
217
|
-
## License
|
|
218
|
-
|
|
219
|
-
MIT
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "context-vault",
|
|
3
|
-
"version": "3.10.0",
|
|
4
|
-
"description": "Persistent memory and knowledge management for AI agents. Save decisions, insights, patterns, and context across sessions. Semantic search, project-scoped buckets, team vaults, and recall tracking.",
|
|
5
|
-
"author": "Klarhimmel",
|
|
6
|
-
"homepage": "https://context-vault.com",
|
|
7
|
-
"repository": "https://github.com/fellanH/context-vault",
|
|
8
|
-
"license": "MIT",
|
|
9
|
-
"keywords": ["memory", "knowledge", "vault", "context", "mcp", "persistence", "recall"],
|
|
10
|
-
"categories": ["productivity", "knowledge-management", "developer-tools"]
|
|
11
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# /vault-cleanup — Vault Maintenance
|
|
2
|
-
|
|
3
|
-
Identify stale, duplicate, or low-value entries and help the user clean up their vault.
|
|
4
|
-
|
|
5
|
-
## Steps
|
|
6
|
-
|
|
7
|
-
1. **Gather vault state.** Call `context_status()` to get entry counts, kind breakdown, recall stats, and warnings.
|
|
8
|
-
|
|
9
|
-
2. **Identify cleanup candidates.** Use `list_context` to browse entries that may need attention:
|
|
10
|
-
```
|
|
11
|
-
list_context({ kind: "session", limit: 20 })
|
|
12
|
-
list_context({ kind: "feedback", tags: ["auto-captured"], limit: 20 })
|
|
13
|
-
list_context({ kind: "observation", limit: 20 })
|
|
14
|
-
```
|
|
15
|
-
Review results for entries with `recall_count: 0` (never recalled) and old `created_at` dates.
|
|
16
|
-
|
|
17
|
-
3. **Check for duplicates.** Search for topics the user likely has many entries on:
|
|
18
|
-
```
|
|
19
|
-
get_context({ query: "<suspected duplicate topic>", buckets: ["<project>"], limit: 15 })
|
|
20
|
-
```
|
|
21
|
-
If multiple entries cover the same insight, suggest consolidating via `create_snapshot`.
|
|
22
|
-
|
|
23
|
-
4. **Present a triage report.** For each category, show:
|
|
24
|
-
- Entry title, kind, created date, recall count
|
|
25
|
-
- Recommended action: **delete**, **consolidate**, **expire**, or **keep**
|
|
26
|
-
|
|
27
|
-
5. **Execute only user-approved actions.** Wait for explicit confirmation before any deletion.
|
|
28
|
-
|
|
29
|
-
## Available Actions
|
|
30
|
-
|
|
31
|
-
| Action | How | When |
|
|
32
|
-
|--------|-----|------|
|
|
33
|
-
| **Delete** | `delete_context({ id: "<entry-id>" })` | Duplicates, noise, irrelevant entries |
|
|
34
|
-
| **Consolidate** | `create_snapshot({ topic: "...", buckets: ["..."] })` | Many scattered entries on one topic |
|
|
35
|
-
| **Set to expire** | `save_context({ id: "<entry-id>", tier: "ephemeral" })` | Stale but not worth deleting now |
|
|
36
|
-
| **Supersede** | `save_context({ title: "...", body: "...", supersedes: ["<old-id>"] })` | Replace outdated with current |
|
|
37
|
-
|
|
38
|
-
## Important
|
|
39
|
-
|
|
40
|
-
- `delete_context` is **permanent and irreversible**. Always confirm with the user.
|
|
41
|
-
- There is no bulk delete. Each deletion requires a separate call with a specific entry ID.
|
|
42
|
-
- There is no "archive" or "soft delete." To deprioritize without deleting, set `tier: "ephemeral"`.
|
|
43
|
-
- Do not reference tools or parameters that don't exist. Only use: `context_status`, `list_context`, `get_context`, `delete_context`, `save_context`, `create_snapshot`.
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# /vault-snapshot — Create a Context Brief
|
|
2
|
-
|
|
3
|
-
Consolidate scattered vault entries on a topic into a single brief.
|
|
4
|
-
|
|
5
|
-
## Steps
|
|
6
|
-
|
|
7
|
-
1. Ask the user what topic they want a snapshot of. Also ask which project bucket to scope to (if not obvious from context).
|
|
8
|
-
|
|
9
|
-
2. Call `create_snapshot` with the topic as a **natural language search query** (not a slug):
|
|
10
|
-
```
|
|
11
|
-
create_snapshot({
|
|
12
|
-
topic: "API authentication decisions and patterns",
|
|
13
|
-
buckets: ["project-name"],
|
|
14
|
-
kinds: ["decision", "pattern"],
|
|
15
|
-
tags: ["authentication"]
|
|
16
|
-
})
|
|
17
|
-
```
|
|
18
|
-
All parameters except `topic` are optional. `kinds` filters by entry kind. `tags` adds tag filters. `buckets` scopes to a project.
|
|
19
|
-
|
|
20
|
-
3. The tool returns a short confirmation (not the brief content):
|
|
21
|
-
```
|
|
22
|
-
✓ Snapshot created → id: <entry-id>
|
|
23
|
-
title: <topic> — Context Brief
|
|
24
|
-
identity_key: snapshot-<slugified-topic>
|
|
25
|
-
synthesized from: N entries
|
|
26
|
-
```
|
|
27
|
-
The brief is saved to the vault as a `kind: brief` entry.
|
|
28
|
-
|
|
29
|
-
4. To show the user the brief, make a follow-up call:
|
|
30
|
-
```
|
|
31
|
-
get_context({ kind: "brief", identity_key: "snapshot-<slugified-topic>" })
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
5. Present the brief and offer next steps:
|
|
35
|
-
- "Share this with your team" via `publish_to_team({ id: "<entry-id>" })`
|
|
36
|
-
- "Create another snapshot on a related topic"
|
|
37
|
-
- "Search for more context" via `get_context`
|
|
38
|
-
|
|
39
|
-
## Tips
|
|
40
|
-
|
|
41
|
-
- The `topic` is used as a search query (hybrid FTS + semantic). Use descriptive natural language for best results. "API authentication decisions" works better than "api-auth".
|
|
42
|
-
- If "no entries found," try a broader query or different bucket.
|
|
43
|
-
- Snapshots are idempotent on `identity_key`. Re-running with the same topic updates the existing snapshot.
|
package/commands/vault-status.md
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# /vault-status — Vault Health Check
|
|
2
|
-
|
|
3
|
-
Check the health and metrics of your context vault.
|
|
4
|
-
|
|
5
|
-
## Steps
|
|
6
|
-
|
|
7
|
-
1. Call `context_status()` (no arguments needed).
|
|
8
|
-
2. The tool returns a structured markdown dashboard with sections for entries by kind, recall frequency, learning rate, stale knowledge, growth warnings, and suggested actions. Present it to the user.
|
|
9
|
-
3. After presenting the dashboard, add your own analysis (see below).
|
|
10
|
-
|
|
11
|
-
## Analysis Guidelines
|
|
12
|
-
|
|
13
|
-
**Positive signals to highlight:**
|
|
14
|
-
- High embedding coverage (>90%)
|
|
15
|
-
- Active recall (recall:save ratio above 1:1)
|
|
16
|
-
- Regular saves per session (>1.0)
|
|
17
|
-
- No stale paths or startup errors
|
|
18
|
-
|
|
19
|
-
**Warning signs to flag:**
|
|
20
|
-
- Embedding coverage below 80%: suggest running `context-vault reindex`
|
|
21
|
-
- Zero saves in 30 days: knowledge may be getting lost between sessions
|
|
22
|
-
- Stale paths detected: auto-reindex will fix on next search
|
|
23
|
-
- Growth warnings present: review the kind breakdown for noise (session, feedback entries dominating)
|
|
24
|
-
- Stale knowledge entries: the tool flags entries not updated within their kind-specific staleness window (pattern: 180d, decision: 365d, reference: 90d)
|
|
25
|
-
- Startup errors in the error log: point the user to the log path
|
|
26
|
-
|
|
27
|
-
**Recall ratio (JSON block at the end of the dashboard):**
|
|
28
|
-
- Below 0.10: most entries are never retrieved. Suggest better tagging, encoding_context, and snapshots.
|
|
29
|
-
- Above 0.25: healthy.
|
|
30
|
-
|
|
31
|
-
## Do NOT
|
|
32
|
-
|
|
33
|
-
- Fabricate metrics or example output. Only report what the tool returns.
|
|
34
|
-
- Suggest `npm run build` for any issue. The correct commands are `context-vault reindex`, `context-vault doctor`, or `context-vault setup`.
|
|
35
|
-
- Invent thresholds. Use the warnings the tool itself generates.
|