context-vault 3.4.3 → 3.4.5
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 +50 -0
- package/assets/setup-prompt.md +58 -0
- package/assets/skills/vault-setup/skill.md +81 -0
- package/bin/cli.js +533 -11
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +23 -0
- package/dist/helpers.js.map +1 -1
- package/dist/server.js +52 -12
- package/dist/server.js.map +1 -1
- package/dist/tools/context-status.js +29 -28
- package/dist/tools/context-status.js.map +1 -1
- package/dist/tools/get-context.d.ts +2 -1
- package/dist/tools/get-context.d.ts.map +1 -1
- package/dist/tools/get-context.js +44 -20
- package/dist/tools/get-context.js.map +1 -1
- package/dist/tools/list-context.d.ts.map +1 -1
- package/dist/tools/list-context.js +8 -8
- package/dist/tools/list-context.js.map +1 -1
- package/dist/tools/save-context.d.ts +2 -1
- package/dist/tools/save-context.d.ts.map +1 -1
- package/dist/tools/save-context.js +100 -24
- 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 +39 -5
- package/dist/tools/session-start.js.map +1 -1
- package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/capture.js +11 -0
- package/node_modules/@context-vault/core/dist/capture.js.map +1 -1
- package/node_modules/@context-vault/core/dist/config.d.ts +8 -0
- package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/config.js +20 -1
- package/node_modules/@context-vault/core/dist/config.js.map +1 -1
- package/node_modules/@context-vault/core/dist/context.d.ts +34 -0
- package/node_modules/@context-vault/core/dist/context.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/context.js +55 -0
- package/node_modules/@context-vault/core/dist/context.js.map +1 -0
- package/node_modules/@context-vault/core/dist/db.d.ts +3 -1
- package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/db.js +29 -2
- 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 +2 -0
- package/node_modules/@context-vault/core/dist/frontmatter.js.map +1 -1
- package/node_modules/@context-vault/core/dist/search.d.ts +1 -0
- package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/search.js +57 -3
- package/node_modules/@context-vault/core/dist/search.js.map +1 -1
- package/node_modules/@context-vault/core/dist/types.d.ts +6 -0
- package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -1
- package/node_modules/@context-vault/core/package.json +5 -1
- package/node_modules/@context-vault/core/src/capture.ts +9 -0
- package/node_modules/@context-vault/core/src/config.ts +22 -1
- package/node_modules/@context-vault/core/src/context.ts +65 -0
- package/node_modules/@context-vault/core/src/db.ts +29 -2
- package/node_modules/@context-vault/core/src/frontmatter.ts +2 -0
- package/node_modules/@context-vault/core/src/search.ts +54 -2
- package/node_modules/@context-vault/core/src/types.ts +6 -0
- package/package.json +2 -2
- package/scripts/prepack.js +17 -0
- package/src/helpers.ts +25 -0
- package/src/server.ts +57 -11
- package/src/tools/context-status.ts +30 -30
- package/src/tools/get-context.ts +48 -25
- package/src/tools/list-context.ts +8 -11
- package/src/tools/save-context.ts +101 -26
- package/src/tools/session-start.ts +36 -5
|
@@ -61,6 +61,8 @@ export interface PreparedStatements {
|
|
|
61
61
|
deleteVecStmt: StatementSync;
|
|
62
62
|
updateSupersededBy: StatementSync;
|
|
63
63
|
clearSupersededByRef: StatementSync;
|
|
64
|
+
insertCtxVecStmt: StatementSync;
|
|
65
|
+
deleteCtxVecStmt: StatementSync;
|
|
64
66
|
}
|
|
65
67
|
export interface VaultEntry {
|
|
66
68
|
id: string;
|
|
@@ -161,6 +163,8 @@ export interface BaseCtx {
|
|
|
161
163
|
embed: (text: string) => Promise<Float32Array | null>;
|
|
162
164
|
insertVec: (rowid: number, embedding: Float32Array) => void;
|
|
163
165
|
deleteVec: (rowid: number) => void;
|
|
166
|
+
insertCtxVec: (rowid: number, embedding: Float32Array) => void;
|
|
167
|
+
deleteCtxVec: (rowid: number) => void;
|
|
164
168
|
}
|
|
165
169
|
export interface SearchOptions {
|
|
166
170
|
kindFilter?: string | null;
|
|
@@ -173,5 +177,7 @@ export interface SearchOptions {
|
|
|
173
177
|
decayDays?: number;
|
|
174
178
|
includeSuperseeded?: boolean;
|
|
175
179
|
includeEphemeral?: boolean;
|
|
180
|
+
/** Pre-computed context embedding for contextual reinstatement boosting. */
|
|
181
|
+
contextEmbedding?: Float32Array | null;
|
|
176
182
|
}
|
|
177
183
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,aAAa,EAAE,mBAAmB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,cAAc,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,aAAa,CAAC;IAC3B,WAAW,EAAE,aAAa,CAAC;IAC3B,WAAW,EAAE,aAAa,CAAC;IAC3B,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,aAAa,CAAC;IAC9B,YAAY,EAAE,aAAa,CAAC;IAC5B,gBAAgB,EAAE,aAAa,CAAC;IAChC,mBAAmB,EAAE,aAAa,CAAC;IACnC,iBAAiB,EAAE,aAAa,CAAC;IACjC,eAAe,EAAE,aAAa,CAAC;IAC/B,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,EAAE,aAAa,CAAC;IAC7B,kBAAkB,EAAE,aAAa,CAAC;IAClC,oBAAoB,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@context-vault/core",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pure local engine: capture, index, search, and utilities for context-vault",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
"./types": {
|
|
62
62
|
"types": "./dist/types.d.ts",
|
|
63
63
|
"import": "./dist/types.js"
|
|
64
|
+
},
|
|
65
|
+
"./context": {
|
|
66
|
+
"types": "./dist/context.d.ts",
|
|
67
|
+
"import": "./dist/context.js"
|
|
64
68
|
}
|
|
65
69
|
},
|
|
66
70
|
"files": [
|
|
@@ -36,6 +36,7 @@ function writeEntryFile(
|
|
|
36
36
|
expires_at?: string | null;
|
|
37
37
|
supersedes?: string[] | null;
|
|
38
38
|
related_to?: string[] | null;
|
|
39
|
+
tier?: string | null;
|
|
39
40
|
}
|
|
40
41
|
): string {
|
|
41
42
|
const resolvedFolder = params.folder || (params.meta?.folder as string) || '';
|
|
@@ -50,6 +51,10 @@ function writeEntryFile(
|
|
|
50
51
|
const created = params.createdAt || new Date().toISOString();
|
|
51
52
|
const fmFields: Record<string, unknown> = { id: params.id };
|
|
52
53
|
|
|
54
|
+
if (params.title) fmFields.title = params.title;
|
|
55
|
+
fmFields.kind = kind;
|
|
56
|
+
if (params.tier) fmFields.tier = params.tier;
|
|
57
|
+
|
|
53
58
|
if (params.meta) {
|
|
54
59
|
for (const [k, v] of Object.entries(params.meta)) {
|
|
55
60
|
if (k === 'folder') continue;
|
|
@@ -150,6 +155,7 @@ export function writeEntry(ctx: BaseCtx, data: CaptureInput): CaptureResult {
|
|
|
150
155
|
expires_at: data.expires_at,
|
|
151
156
|
supersedes: data.supersedes,
|
|
152
157
|
related_to: data.related_to,
|
|
158
|
+
tier: data.tier,
|
|
153
159
|
});
|
|
154
160
|
|
|
155
161
|
return {
|
|
@@ -225,6 +231,9 @@ export function updateEntryFile(
|
|
|
225
231
|
|
|
226
232
|
const now = new Date().toISOString();
|
|
227
233
|
const fmFields: Record<string, unknown> = { id: existing.id };
|
|
234
|
+
if (title) fmFields.title = title;
|
|
235
|
+
fmFields.kind = existing.kind;
|
|
236
|
+
if (existing.tier) fmFields.tier = existing.tier;
|
|
228
237
|
for (const [k, v] of Object.entries(mergedMeta)) {
|
|
229
238
|
if (k === 'folder') continue;
|
|
230
239
|
if (v !== null && v !== undefined) fmFields[k] = v;
|
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
3
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
4
|
import { DEFAULT_GROWTH_THRESHOLDS, DEFAULT_LIFECYCLE } from './constants.js';
|
|
5
5
|
import type { VaultConfig } from './types.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Guard against writes to the real config file during test runs.
|
|
9
|
+
* Set CONTEXT_VAULT_TEST=1 in test helpers to activate.
|
|
10
|
+
*
|
|
11
|
+
* Allows writes if the target path is under a temp directory (tests with
|
|
12
|
+
* HOME overridden to a temp dir). Blocks writes to non-temp paths.
|
|
13
|
+
*/
|
|
14
|
+
export function assertNotTestMode(targetPath: string): void {
|
|
15
|
+
if (process.env.CONTEXT_VAULT_TEST !== '1') return;
|
|
16
|
+
const resolved = resolve(targetPath);
|
|
17
|
+
const tmp = tmpdir();
|
|
18
|
+
// Allow writes to temp directories (tests with HOME isolation)
|
|
19
|
+
if (resolved.startsWith(tmp) || resolved.startsWith('/tmp/') || resolved.startsWith('/var/folders/')) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(
|
|
23
|
+
`[context-vault] Refusing to write to real config in test mode (${targetPath}). ` +
|
|
24
|
+
'Set HOME or CONTEXT_VAULT_DATA_DIR to a temp directory.'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
export function parseArgs(argv: string[]): Record<string, string | number> {
|
|
8
29
|
const args: Record<string, string | number> = {};
|
|
9
30
|
for (let i = 2; i < argv.length; i++) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contextual reinstatement: encoding context capture and serialization.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by hippocampal contextual reinstatement in neuroscience:
|
|
5
|
+
* the brain stores the situation (place, task, goal) alongside each memory,
|
|
6
|
+
* and re-entering a similar situation boosts recall. This module provides
|
|
7
|
+
* the same mechanism for vault entries.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface EncodingContext {
|
|
11
|
+
project?: string;
|
|
12
|
+
arc?: string;
|
|
13
|
+
task?: string;
|
|
14
|
+
cwd?: string;
|
|
15
|
+
session_id?: string;
|
|
16
|
+
[key: string]: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Serialize an EncodingContext into a natural-language sentence suitable
|
|
21
|
+
* for embedding with MiniLM. The output is a simple key-value string
|
|
22
|
+
* that produces meaningful sentence embeddings for similarity matching.
|
|
23
|
+
*
|
|
24
|
+
* Example: "project: leadfront, arc: auth-rewrite, task: implementing JWT rotation"
|
|
25
|
+
*/
|
|
26
|
+
export function serializeContext(ctx: EncodingContext): string {
|
|
27
|
+
const parts: string[] = [];
|
|
28
|
+
for (const [key, value] of Object.entries(ctx)) {
|
|
29
|
+
if (value != null && typeof value === 'string' && value.trim()) {
|
|
30
|
+
parts.push(`${key}: ${value.trim()}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return parts.join(', ');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse and validate a context parameter from tool input.
|
|
38
|
+
* Accepts either a string (used as-is for embedding) or a structured object.
|
|
39
|
+
* Returns null if the input is empty or invalid.
|
|
40
|
+
*/
|
|
41
|
+
export function parseContextParam(input: unknown): { text: string; structured: EncodingContext | null } | null {
|
|
42
|
+
if (input == null) return null;
|
|
43
|
+
|
|
44
|
+
if (typeof input === 'string') {
|
|
45
|
+
const trimmed = input.trim();
|
|
46
|
+
if (!trimmed) return null;
|
|
47
|
+
return { text: trimmed, structured: null };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof input === 'object' && !Array.isArray(input)) {
|
|
51
|
+
const obj = input as Record<string, unknown>;
|
|
52
|
+
const ctx: EncodingContext = {};
|
|
53
|
+
let hasValue = false;
|
|
54
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
55
|
+
if (typeof value === 'string' && value.trim()) {
|
|
56
|
+
ctx[key] = value.trim();
|
|
57
|
+
hasValue = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!hasValue) return null;
|
|
61
|
+
return { text: serializeContext(ctx), structured: ctx };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -175,9 +175,11 @@ export const SCHEMA_DDL = `
|
|
|
175
175
|
END;
|
|
176
176
|
|
|
177
177
|
CREATE VIRTUAL TABLE IF NOT EXISTS vault_vec USING vec0(embedding float[384]);
|
|
178
|
+
|
|
179
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vault_ctx_vec USING vec0(embedding float[384]);
|
|
178
180
|
`;
|
|
179
181
|
|
|
180
|
-
const CURRENT_VERSION =
|
|
182
|
+
const CURRENT_VERSION = 16;
|
|
181
183
|
|
|
182
184
|
export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
183
185
|
const sqliteVec = await loadSqliteVec();
|
|
@@ -202,6 +204,17 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
202
204
|
const version = (db.prepare('PRAGMA user_version').get() as { user_version: number })
|
|
203
205
|
.user_version;
|
|
204
206
|
|
|
207
|
+
// v15 -> v16: add vault_ctx_vec table for contextual reinstatement
|
|
208
|
+
if (version === 15) {
|
|
209
|
+
try {
|
|
210
|
+
db.exec('CREATE VIRTUAL TABLE IF NOT EXISTS vault_ctx_vec USING vec0(embedding float[384])');
|
|
211
|
+
db.exec(`PRAGMA user_version = ${CURRENT_VERSION}`);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
console.error(`[context-vault] v15->v16 migration failed: ${(e as Error).message}`);
|
|
214
|
+
}
|
|
215
|
+
return db;
|
|
216
|
+
}
|
|
217
|
+
|
|
205
218
|
if (version > 0 && version < 15) {
|
|
206
219
|
console.error(`[context-vault] Schema v${version} is outdated. Rebuilding database...`);
|
|
207
220
|
|
|
@@ -243,7 +256,7 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
243
256
|
return freshDb;
|
|
244
257
|
}
|
|
245
258
|
|
|
246
|
-
if (version <
|
|
259
|
+
if (version < 16) {
|
|
247
260
|
db.exec(SCHEMA_DDL);
|
|
248
261
|
db.exec(`PRAGMA user_version = ${CURRENT_VERSION}`);
|
|
249
262
|
}
|
|
@@ -279,6 +292,8 @@ export function prepareStatements(db: DatabaseSync): PreparedStatements {
|
|
|
279
292
|
clearSupersededByRef: db.prepare(
|
|
280
293
|
`UPDATE vault SET superseded_by = NULL WHERE superseded_by = ?`
|
|
281
294
|
),
|
|
295
|
+
insertCtxVecStmt: db.prepare(`INSERT INTO vault_ctx_vec (rowid, embedding) VALUES (?, ?)`),
|
|
296
|
+
deleteCtxVecStmt: db.prepare(`DELETE FROM vault_ctx_vec WHERE rowid = ?`),
|
|
282
297
|
};
|
|
283
298
|
} catch (e) {
|
|
284
299
|
throw new Error(
|
|
@@ -301,6 +316,18 @@ export function deleteVec(stmts: PreparedStatements, rowid: number): void {
|
|
|
301
316
|
stmts.deleteVecStmt.run(safeRowid);
|
|
302
317
|
}
|
|
303
318
|
|
|
319
|
+
export function insertCtxVec(stmts: PreparedStatements, rowid: number, embedding: Float32Array): void {
|
|
320
|
+
const safeRowid = BigInt(rowid);
|
|
321
|
+
if (safeRowid < 1n) throw new Error(`Invalid rowid: ${rowid}`);
|
|
322
|
+
stmts.insertCtxVecStmt.run(safeRowid, embedding);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function deleteCtxVec(stmts: PreparedStatements, rowid: number): void {
|
|
326
|
+
const safeRowid = BigInt(rowid);
|
|
327
|
+
if (safeRowid < 1n) throw new Error(`Invalid rowid: ${rowid}`);
|
|
328
|
+
stmts.deleteCtxVecStmt.run(safeRowid);
|
|
329
|
+
}
|
|
330
|
+
|
|
304
331
|
export function testConnection(db: DatabaseSync): boolean {
|
|
305
332
|
try {
|
|
306
333
|
db.prepare('SELECT 1').get();
|
|
@@ -106,6 +106,7 @@ export async function hybridSearch(
|
|
|
106
106
|
decayDays = 30,
|
|
107
107
|
includeSuperseeded = false,
|
|
108
108
|
includeEphemeral = false,
|
|
109
|
+
contextEmbedding = null,
|
|
109
110
|
} = opts;
|
|
110
111
|
|
|
111
112
|
const rowMap = new Map<string, VaultEntry>();
|
|
@@ -209,9 +210,60 @@ export async function hybridSearch(
|
|
|
209
210
|
}
|
|
210
211
|
}
|
|
211
212
|
|
|
213
|
+
// Context vector pass: KNN against vault_ctx_vec for contextual reinstatement
|
|
214
|
+
const ctxRankedIds: string[] = [];
|
|
215
|
+
if (contextEmbedding) {
|
|
216
|
+
try {
|
|
217
|
+
const ctxVecCount = (
|
|
218
|
+
ctx.db.prepare('SELECT COUNT(*) as c FROM vault_ctx_vec').get() as { c: number }
|
|
219
|
+
).c;
|
|
220
|
+
if (ctxVecCount > 0) {
|
|
221
|
+
const ctxRows = ctx.db
|
|
222
|
+
.prepare(
|
|
223
|
+
`SELECT v.rowid, v.distance FROM vault_ctx_vec v WHERE embedding MATCH ? ORDER BY distance LIMIT 15`
|
|
224
|
+
)
|
|
225
|
+
.all(contextEmbedding, 15) as { rowid: number; distance: number }[];
|
|
226
|
+
|
|
227
|
+
if (ctxRows.length) {
|
|
228
|
+
const ctxRowids = ctxRows.map((cr) => cr.rowid);
|
|
229
|
+
const placeholders = ctxRowids.map(() => '?').join(',');
|
|
230
|
+
const ctxHydrated = ctx.db
|
|
231
|
+
.prepare(`SELECT rowid, * FROM vault WHERE rowid IN (${placeholders})`)
|
|
232
|
+
.all(...ctxRowids) as unknown as (VaultEntry & { rowid: number })[];
|
|
233
|
+
|
|
234
|
+
const ctxByRowid = new Map<number, VaultEntry & { rowid: number }>();
|
|
235
|
+
for (const row of ctxHydrated) ctxByRowid.set(row.rowid, row);
|
|
236
|
+
|
|
237
|
+
for (const cr of ctxRows) {
|
|
238
|
+
const row = ctxByRowid.get(cr.rowid);
|
|
239
|
+
if (!row) continue;
|
|
240
|
+
if (kindFilter && row.kind !== kindFilter) continue;
|
|
241
|
+
if (categoryFilter && row.category !== categoryFilter) continue;
|
|
242
|
+
if (excludeEvents && row.category === 'event') continue;
|
|
243
|
+
if (since && row.created_at < since) continue;
|
|
244
|
+
if (until && row.created_at > until) continue;
|
|
245
|
+
if (row.expires_at && new Date(row.expires_at) <= new Date()) continue;
|
|
246
|
+
|
|
247
|
+
const { rowid: _rowid, ...cleanRow } = row;
|
|
248
|
+
ctxRankedIds.push(cleanRow.id);
|
|
249
|
+
if (!rowMap.has(cleanRow.id)) rowMap.set(cleanRow.id, cleanRow);
|
|
250
|
+
if (!idToRowid.has(cleanRow.id)) idToRowid.set(cleanRow.id, Number(row.rowid));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
if (!(err as Error).message?.includes('no such table')) {
|
|
256
|
+
console.error(`[retrieve] Context vector search error: ${(err as Error).message}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
212
261
|
if (rowMap.size === 0) return [];
|
|
213
262
|
|
|
214
|
-
|
|
263
|
+
// Build ranked lists for RRF: content FTS + content vec + optional context vec
|
|
264
|
+
const rankedLists = [ftsRankedIds, vecRankedIds];
|
|
265
|
+
if (ctxRankedIds.length > 0) rankedLists.push(ctxRankedIds);
|
|
266
|
+
const rrfScores = reciprocalRankFusion(rankedLists);
|
|
215
267
|
|
|
216
268
|
for (const [id, entry] of rowMap) {
|
|
217
269
|
const boost = recencyBoost(entry.created_at, entry.category, decayDays);
|
|
@@ -274,7 +326,7 @@ export async function hybridSearch(
|
|
|
274
326
|
return finalPage;
|
|
275
327
|
}
|
|
276
328
|
|
|
277
|
-
function trackAccess(ctx: BaseCtx, entries: SearchResult[]): void {
|
|
329
|
+
export function trackAccess(ctx: BaseCtx, entries: SearchResult[]): void {
|
|
278
330
|
if (!entries.length) return;
|
|
279
331
|
try {
|
|
280
332
|
const placeholders = entries.map(() => '?').join(',');
|
|
@@ -53,6 +53,8 @@ export interface PreparedStatements {
|
|
|
53
53
|
deleteVecStmt: StatementSync;
|
|
54
54
|
updateSupersededBy: StatementSync;
|
|
55
55
|
clearSupersededByRef: StatementSync;
|
|
56
|
+
insertCtxVecStmt: StatementSync;
|
|
57
|
+
deleteCtxVecStmt: StatementSync;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
export interface VaultEntry {
|
|
@@ -151,6 +153,8 @@ export interface BaseCtx {
|
|
|
151
153
|
embed: (text: string) => Promise<Float32Array | null>;
|
|
152
154
|
insertVec: (rowid: number, embedding: Float32Array) => void;
|
|
153
155
|
deleteVec: (rowid: number) => void;
|
|
156
|
+
insertCtxVec: (rowid: number, embedding: Float32Array) => void;
|
|
157
|
+
deleteCtxVec: (rowid: number) => void;
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
export interface SearchOptions {
|
|
@@ -164,4 +168,6 @@ export interface SearchOptions {
|
|
|
164
168
|
decayDays?: number;
|
|
165
169
|
includeSuperseeded?: boolean;
|
|
166
170
|
includeEphemeral?: boolean;
|
|
171
|
+
/** Pre-computed context embedding for contextual reinstatement boosting. */
|
|
172
|
+
contextEmbedding?: Float32Array | null;
|
|
167
173
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
|
|
6
6
|
"bin": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@context-vault/core"
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@context-vault/core": "^3.4.
|
|
66
|
+
"@context-vault/core": "^3.4.5",
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
68
68
|
"adm-zip": "^0.5.16",
|
|
69
69
|
"sqlite-vec": "^0.1.0"
|
package/scripts/prepack.js
CHANGED
|
@@ -35,3 +35,20 @@ delete corePkg.dependencies;
|
|
|
35
35
|
writeFileSync(corePkgPath, JSON.stringify(corePkg, null, 2) + '\n');
|
|
36
36
|
|
|
37
37
|
console.log('[prepack] Bundled @context-vault/core into node_modules');
|
|
38
|
+
|
|
39
|
+
// Copy monorepo-level assets (agent-rules.md, setup-prompt.md) into local assets/
|
|
40
|
+
// so they ship with the npm package.
|
|
41
|
+
const MONOREPO_ASSETS = join(LOCAL_ROOT, '..', '..', 'assets');
|
|
42
|
+
const LOCAL_ASSETS = join(LOCAL_ROOT, 'assets');
|
|
43
|
+
const ASSET_FILES = ['agent-rules.md', 'setup-prompt.md'];
|
|
44
|
+
|
|
45
|
+
for (const file of ASSET_FILES) {
|
|
46
|
+
const src = join(MONOREPO_ASSETS, file);
|
|
47
|
+
const dest = join(LOCAL_ASSETS, file);
|
|
48
|
+
try {
|
|
49
|
+
cpSync(src, dest);
|
|
50
|
+
console.log(`[prepack] Copied ${file} to assets/`);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.warn(`[prepack] Warning: could not copy ${file}: ${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/helpers.ts
CHANGED
|
@@ -58,4 +58,29 @@ export function ensureValidKind(kind: string): ToolResult | null {
|
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
const KIND_ICONS: Record<string, string> = {
|
|
62
|
+
insight: '◆',
|
|
63
|
+
decision: '◇',
|
|
64
|
+
pattern: '◈',
|
|
65
|
+
reference: '▸',
|
|
66
|
+
event: '○',
|
|
67
|
+
session: '◎',
|
|
68
|
+
brief: '▪',
|
|
69
|
+
bucket: '▫',
|
|
70
|
+
contact: '●',
|
|
71
|
+
project: '■',
|
|
72
|
+
tool: '▹',
|
|
73
|
+
source: '►',
|
|
74
|
+
feedback: '◉',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export function kindIcon(kind: string): string {
|
|
78
|
+
return KIND_ICONS[kind] || '·';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function fmtDate(date: string | null | undefined): string {
|
|
82
|
+
if (!date) return '';
|
|
83
|
+
return date.split('T')[0];
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
export { pkg };
|
package/src/server.ts
CHANGED
|
@@ -7,9 +7,9 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
|
|
|
7
7
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
8
8
|
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
|
|
9
9
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
10
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'node:fs';
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync, readdirSync } from 'node:fs';
|
|
11
11
|
import { join, dirname } from 'node:path';
|
|
12
|
-
import { homedir } from 'node:os';
|
|
12
|
+
import { homedir, tmpdir } from 'node:os';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
|
|
15
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
prepareStatements,
|
|
27
27
|
insertVec,
|
|
28
28
|
deleteVec,
|
|
29
|
+
insertCtxVec,
|
|
30
|
+
deleteCtxVec,
|
|
29
31
|
} from '@context-vault/core/db';
|
|
30
32
|
import { registerTools } from './register-tools.js';
|
|
31
33
|
import { pruneExpired } from '@context-vault/core/index';
|
|
@@ -310,6 +312,29 @@ async function main(): Promise<void> {
|
|
|
310
312
|
|
|
311
313
|
config.vaultDirExists = existsSync(config.vaultDir);
|
|
312
314
|
|
|
315
|
+
// Validate vaultDir sanity
|
|
316
|
+
const osTmp = tmpdir();
|
|
317
|
+
const isTempPath = config.vaultDir.startsWith(osTmp) ||
|
|
318
|
+
config.vaultDir.startsWith('/tmp/') ||
|
|
319
|
+
config.vaultDir.startsWith('/var/folders/');
|
|
320
|
+
if (isTempPath) {
|
|
321
|
+
console.error(`[context-vault] WARNING: vaultDir points to a temp directory: ${config.vaultDir}`);
|
|
322
|
+
console.error(`[context-vault] This is likely from a test run that overwrote ~/.context-mcp/config.json`);
|
|
323
|
+
console.error(`[context-vault] Fix: run 'context-vault reconnect' or 'context-vault setup'`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (config.vaultDirExists) {
|
|
327
|
+
try {
|
|
328
|
+
const entries = readdirSync(config.vaultDir);
|
|
329
|
+
const hasMdFiles = entries.some(f => f.endsWith('.md'));
|
|
330
|
+
const hasMarker = entries.includes('.context-mcp');
|
|
331
|
+
if (!hasMdFiles && hasMarker) {
|
|
332
|
+
console.error(`[context-vault] WARNING: vaultDir has no markdown files but has a marker file`);
|
|
333
|
+
console.error(`[context-vault] The vault may be misconfigured. Run 'context-vault reconnect'`);
|
|
334
|
+
}
|
|
335
|
+
} catch {}
|
|
336
|
+
}
|
|
337
|
+
|
|
313
338
|
console.error(`[context-vault] Vault: ${config.vaultDir}`);
|
|
314
339
|
console.error(`[context-vault] Database: ${config.dbPath}`);
|
|
315
340
|
console.error(`[context-vault] Dev dir: ${config.devDir}`);
|
|
@@ -328,6 +353,8 @@ async function main(): Promise<void> {
|
|
|
328
353
|
embed,
|
|
329
354
|
insertVec: (rowid: number, embedding: Float32Array) => insertVec(stmts, rowid, embedding),
|
|
330
355
|
deleteVec: (rowid: number) => deleteVec(stmts, rowid),
|
|
356
|
+
insertCtxVec: (rowid: number, embedding: Float32Array) => insertCtxVec(stmts, rowid, embedding),
|
|
357
|
+
deleteCtxVec: (rowid: number) => deleteCtxVec(stmts, rowid),
|
|
331
358
|
activeOps: { count: 0 },
|
|
332
359
|
toolStats: { ok: 0, errors: 0, lastError: null },
|
|
333
360
|
};
|
|
@@ -480,15 +507,34 @@ async function main(): Promise<void> {
|
|
|
480
507
|
await transport.handleRequest(req, res, (req as any).body);
|
|
481
508
|
return;
|
|
482
509
|
} else if (sessionId) {
|
|
483
|
-
// Stale session (e.g., daemon restarted).
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
510
|
+
// Stale session (e.g., daemon restarted). Claude Code's MCP client
|
|
511
|
+
// does not auto-reinitialize on 404, so we recover transparently:
|
|
512
|
+
// create a new transport reusing the stale session ID, force it into
|
|
513
|
+
// initialized state, and handle the request as if nothing happened.
|
|
514
|
+
console.error(`[context-vault] Recovering stale session ${sessionId.slice(0, 8)}...`);
|
|
515
|
+
|
|
516
|
+
transport = new StreamableHTTPServerTransport({
|
|
517
|
+
sessionIdGenerator: () => sessionId,
|
|
518
|
+
onsessioninitialized: (sid: string) => {
|
|
519
|
+
transports[sid] = transport;
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
transport.onclose = () => {
|
|
523
|
+
if (transports[sessionId]) delete transports[sessionId];
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const sessionServer = createServer();
|
|
527
|
+
await sessionServer.connect(transport);
|
|
528
|
+
|
|
529
|
+
// Force transport into initialized state, bypassing the initialize
|
|
530
|
+
// handshake. The inner WebStandardStreamableHTTPServerTransport holds
|
|
531
|
+
// the _initialized flag and sessionId.
|
|
532
|
+
const inner = (transport as any)._webStandardTransport;
|
|
533
|
+
inner._initialized = true;
|
|
534
|
+
inner.sessionId = sessionId;
|
|
535
|
+
transports[sessionId] = transport;
|
|
536
|
+
|
|
537
|
+
// Fall through to handleRequest below
|
|
492
538
|
} else {
|
|
493
539
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
494
540
|
res.end(JSON.stringify({
|
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { gatherVaultStatus, computeGrowthWarnings } from '../status.js';
|
|
4
4
|
import { errorLogPath, errorLogCount } from '../error-log.js';
|
|
5
|
-
import { ok, err } from '../helpers.js';
|
|
5
|
+
import { ok, err, kindIcon } from '../helpers.js';
|
|
6
6
|
import type { LocalCtx, ToolResult } from '../types.js';
|
|
7
7
|
|
|
8
8
|
function relativeTime(ts: number): string {
|
|
@@ -30,53 +30,53 @@ export function handler(_args: Record<string, any>, ctx: LocalCtx): ToolResult {
|
|
|
30
30
|
const hasIssues = status.stalePaths || (status.embeddingStatus?.missing ?? 0) > 0;
|
|
31
31
|
const healthIcon = hasIssues ? '⚠' : '✓';
|
|
32
32
|
|
|
33
|
+
const schemaVersion = (ctx.db.prepare('PRAGMA user_version').get() as any)?.user_version ?? 'unknown';
|
|
34
|
+
const embedPct = status.embeddingStatus
|
|
35
|
+
? (status.embeddingStatus.total > 0 ? Math.round((status.embeddingStatus.indexed / status.embeddingStatus.total) * 100) : 100)
|
|
36
|
+
: null;
|
|
37
|
+
const embedStr = status.embeddingStatus
|
|
38
|
+
? `${status.embeddingStatus.indexed}/${status.embeddingStatus.total} (${embedPct}%)`
|
|
39
|
+
: 'n/a';
|
|
40
|
+
const modelStr = status.embedModelAvailable === false ? '⚠ unavailable' : status.embedModelAvailable === true ? '✓ loaded' : 'unknown';
|
|
41
|
+
|
|
33
42
|
const lines = [
|
|
34
|
-
`## ${healthIcon} Vault
|
|
43
|
+
`## ${healthIcon} Vault Dashboard`,
|
|
35
44
|
``,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
`| | |`,
|
|
46
|
+
`|---|---|`,
|
|
47
|
+
`| **Vault** | ${config.vaultDir} (${config.vaultDirExists ? status.fileCount + ' files' : '⚠ missing'}) |`,
|
|
48
|
+
`| **Database** | ${config.dbPath} (${status.dbSize}) |`,
|
|
49
|
+
`| **Schema** | v${schemaVersion} |`,
|
|
50
|
+
`| **Embeddings** | ${embedStr} |`,
|
|
51
|
+
`| **Model** | ${modelStr} |`,
|
|
52
|
+
`| **Event decay** | ${config.eventDecayDays} days |`,
|
|
43
53
|
];
|
|
44
|
-
|
|
45
|
-
if (status.embeddingStatus) {
|
|
46
|
-
const { indexed, total, missing } = status.embeddingStatus;
|
|
47
|
-
const pct = total > 0 ? Math.round((indexed / total) * 100) : 100;
|
|
48
|
-
lines.push(`Embeddings: ${indexed}/${total} (${pct}%)`);
|
|
49
|
-
}
|
|
50
|
-
if (status.embedModelAvailable === false) {
|
|
51
|
-
lines.push(`Embed model: unavailable (semantic search disabled, FTS still works)`);
|
|
52
|
-
} else if (status.embedModelAvailable === true) {
|
|
53
|
-
lines.push(`Embed model: loaded`);
|
|
54
|
-
}
|
|
55
|
-
lines.push(`Decay: ${config.eventDecayDays} days (event recency window)`);
|
|
56
54
|
if (status.expiredCount > 0) {
|
|
57
|
-
lines.push(
|
|
58
|
-
`Expired: ${status.expiredCount} entries pending prune (run \`context-vault prune\` to remove now)`
|
|
59
|
-
);
|
|
55
|
+
lines.push(`| **Expired** | ${status.expiredCount} pending prune |`);
|
|
60
56
|
}
|
|
61
57
|
|
|
58
|
+
// Indexed kinds as compact table
|
|
62
59
|
lines.push(``, `### Indexed`);
|
|
63
|
-
|
|
64
60
|
if (status.kindCounts.length) {
|
|
65
|
-
|
|
61
|
+
lines.push('| Kind | Count |');
|
|
62
|
+
lines.push('|---|---|');
|
|
63
|
+
for (const { kind, c } of status.kindCounts) {
|
|
64
|
+
lines.push(`| ${kindIcon(kind)} \`${kind}\` | ${c} |`);
|
|
65
|
+
}
|
|
66
66
|
} else {
|
|
67
|
-
lines.push(
|
|
67
|
+
lines.push(`_(empty vault)_`);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
if (status.categoryCounts.length) {
|
|
71
71
|
lines.push(``);
|
|
72
72
|
lines.push(`### Categories`);
|
|
73
|
-
for (const { category, c } of status.categoryCounts) lines.push(`-
|
|
73
|
+
for (const { category, c } of status.categoryCounts) lines.push(`- **${category}**: ${c}`);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
if (status.subdirs.length) {
|
|
77
77
|
lines.push(``);
|
|
78
|
-
lines.push(`### Disk
|
|
79
|
-
for (const { name, count } of status.subdirs) lines.push(`-
|
|
78
|
+
lines.push(`### Disk`);
|
|
79
|
+
for (const { name, count } of status.subdirs) lines.push(`- \`${name}/\` ${count} files`);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
if (status.stalePaths) {
|