context-vault 3.0.3 → 3.1.1
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 +156 -80
- package/node_modules/@context-vault/core/package.json +1 -1
- package/node_modules/@context-vault/core/src/db.ts +12 -0
- package/node_modules/@context-vault/core/src/index.ts +10 -3
- package/package.json +2 -2
- package/scripts/postinstall.js +5 -14
- package/src/helpers.js +10 -2
- package/src/register-tools.js +1 -1
- package/src/server.js +7 -0
- package/src/tools/context-status.js +19 -1
- package/src/tools/create-snapshot.js +44 -35
- package/src/tools/delete-context.js +23 -27
- package/src/tools/get-context.js +16 -15
- package/src/tools/list-context.js +26 -13
- package/src/tools/save-context.js +49 -45
package/bin/cli.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// Node.js version guard — must run before any ESM imports
|
|
4
4
|
const nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
|
|
5
|
-
if (nodeVersion <
|
|
5
|
+
if (nodeVersion < 22) {
|
|
6
6
|
process.stderr.write(
|
|
7
|
-
`\ncontext-vault requires Node.js >=
|
|
7
|
+
`\ncontext-vault requires Node.js >= 22 (you have ${process.versions.node}).\n` +
|
|
8
8
|
`Install a newer version: https://nodejs.org/\n\n`,
|
|
9
9
|
);
|
|
10
10
|
process.exit(1);
|
|
@@ -303,6 +303,7 @@ ${bold("Commands:")}
|
|
|
303
303
|
${cyan("health")} Quick health check — vault, DB, entry count
|
|
304
304
|
${cyan("status")} Show vault diagnostics
|
|
305
305
|
${cyan("doctor")} Diagnose and repair common issues
|
|
306
|
+
${cyan("debug")} Generate AI-pasteable debug report
|
|
306
307
|
${cyan("restart")} Stop running MCP server processes (client auto-restarts)
|
|
307
308
|
${cyan("search")} Search vault entries from CLI
|
|
308
309
|
${cyan("save")} Save an entry to the vault from CLI
|
|
@@ -374,26 +375,6 @@ async function runSetup() {
|
|
|
374
375
|
} catch {}
|
|
375
376
|
|
|
376
377
|
if (latestVersion === VERSION) {
|
|
377
|
-
// Even when "up to date", ensure the launcher points to a valid server
|
|
378
|
-
const dataDir = join(HOME, ".context-mcp");
|
|
379
|
-
const launcherPath = join(dataDir, "server.mjs");
|
|
380
|
-
let launcherOk = false;
|
|
381
|
-
if (existsSync(launcherPath)) {
|
|
382
|
-
const content = readFileSync(launcherPath, "utf-8");
|
|
383
|
-
const m = content.match(/import "(.+?)"/);
|
|
384
|
-
if (m && existsSync(m[1])) launcherOk = true;
|
|
385
|
-
}
|
|
386
|
-
if (!launcherOk && !isNpx()) {
|
|
387
|
-
mkdirSync(dataDir, { recursive: true });
|
|
388
|
-
writeFileSync(launcherPath, `import "${SERVER_PATH}";\n`);
|
|
389
|
-
console.log(
|
|
390
|
-
green(` ✓ context-vault v${VERSION} is up to date`) +
|
|
391
|
-
dim(` (vault: ${existingVault})`),
|
|
392
|
-
);
|
|
393
|
-
console.log(dim(` ↳ Repaired server launcher → ${SERVER_PATH}`));
|
|
394
|
-
console.log();
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
378
|
console.log(
|
|
398
379
|
green(` ✓ context-vault v${VERSION} is up to date`) +
|
|
399
380
|
dim(` (vault: ${existingVault})`),
|
|
@@ -512,12 +493,12 @@ async function runSetup() {
|
|
|
512
493
|
if (detected.length === 0) {
|
|
513
494
|
console.log(yellow(" No supported tools detected.\n"));
|
|
514
495
|
console.log(" To manually configure, add to your tool's MCP config:\n");
|
|
515
|
-
if (isInstalledPackage()) {
|
|
496
|
+
if (isInstalledPackage() || isNpx()) {
|
|
516
497
|
console.log(` ${dim("{")}
|
|
517
498
|
${dim('"mcpServers": {')}
|
|
518
499
|
${dim('"context-vault": {')}
|
|
519
|
-
${dim('"command": "
|
|
520
|
-
${dim(`"args": ["
|
|
500
|
+
${dim('"command": "context-vault",')}
|
|
501
|
+
${dim(`"args": ["serve", "--vault-dir", "/path/to/vault"]`)}
|
|
521
502
|
${dim("}")}
|
|
522
503
|
${dim("}")}
|
|
523
504
|
${dim("}")}\n`);
|
|
@@ -664,11 +645,6 @@ async function runSetup() {
|
|
|
664
645
|
const dataDir = join(HOME, ".context-mcp");
|
|
665
646
|
mkdirSync(dataDir, { recursive: true });
|
|
666
647
|
|
|
667
|
-
// Keep server.mjs launcher up to date so it always resolves to the current installation
|
|
668
|
-
if (isInstalledPackage()) {
|
|
669
|
-
writeFileSync(join(dataDir, "server.mjs"), `import "${SERVER_PATH}";\n`);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
648
|
// Write config.json to data dir (persistent, survives reinstalls)
|
|
673
649
|
const configPath = join(dataDir, "config.json");
|
|
674
650
|
const vaultConfig = {};
|
|
@@ -1136,11 +1112,25 @@ async function configureClaude(tool, vaultDir) {
|
|
|
1136
1112
|
],
|
|
1137
1113
|
{ stdio: "pipe", env },
|
|
1138
1114
|
);
|
|
1115
|
+
} else if (isInstalledPackage()) {
|
|
1116
|
+
const serverArgs = ["serve"];
|
|
1117
|
+
if (vaultDir) serverArgs.push("--vault-dir", vaultDir);
|
|
1118
|
+
execFileSync(
|
|
1119
|
+
"claude",
|
|
1120
|
+
[
|
|
1121
|
+
"mcp",
|
|
1122
|
+
"add",
|
|
1123
|
+
"-s",
|
|
1124
|
+
"user",
|
|
1125
|
+
"context-vault",
|
|
1126
|
+
"--",
|
|
1127
|
+
"context-vault",
|
|
1128
|
+
...serverArgs,
|
|
1129
|
+
],
|
|
1130
|
+
{ stdio: "pipe", env },
|
|
1131
|
+
);
|
|
1139
1132
|
} else {
|
|
1140
|
-
const
|
|
1141
|
-
? join(HOME, ".context-mcp", "server.mjs")
|
|
1142
|
-
: SERVER_PATH;
|
|
1143
|
-
const nodeArgs = [serverPath];
|
|
1133
|
+
const nodeArgs = [SERVER_PATH];
|
|
1144
1134
|
if (vaultDir) nodeArgs.push("--vault-dir", vaultDir);
|
|
1145
1135
|
execFileSync(
|
|
1146
1136
|
"claude",
|
|
@@ -1182,11 +1172,16 @@ async function configureCodex(tool, vaultDir) {
|
|
|
1182
1172
|
["mcp", "add", "context-vault", "--", "npx", ...serverArgs],
|
|
1183
1173
|
{ stdio: "pipe" },
|
|
1184
1174
|
);
|
|
1175
|
+
} else if (isInstalledPackage()) {
|
|
1176
|
+
const serverArgs = ["serve"];
|
|
1177
|
+
if (vaultDir) serverArgs.push("--vault-dir", vaultDir);
|
|
1178
|
+
execFileSync(
|
|
1179
|
+
"codex",
|
|
1180
|
+
["mcp", "add", "context-vault", "--", "context-vault", ...serverArgs],
|
|
1181
|
+
{ stdio: "pipe" },
|
|
1182
|
+
);
|
|
1185
1183
|
} else {
|
|
1186
|
-
const
|
|
1187
|
-
? join(HOME, ".context-mcp", "server.mjs")
|
|
1188
|
-
: SERVER_PATH;
|
|
1189
|
-
const nodeArgs = [serverPath];
|
|
1184
|
+
const nodeArgs = [SERVER_PATH];
|
|
1190
1185
|
if (vaultDir) nodeArgs.push("--vault-dir", vaultDir);
|
|
1191
1186
|
execFileSync(
|
|
1192
1187
|
"codex",
|
|
@@ -1236,13 +1231,11 @@ function configureJsonTool(tool, vaultDir) {
|
|
|
1236
1231
|
env: { NODE_OPTIONS: "--no-warnings=ExperimentalWarning" },
|
|
1237
1232
|
};
|
|
1238
1233
|
} else if (isInstalledPackage()) {
|
|
1239
|
-
const
|
|
1240
|
-
const serverArgs = [];
|
|
1234
|
+
const serverArgs = ["serve"];
|
|
1241
1235
|
if (vaultDir) serverArgs.push("--vault-dir", vaultDir);
|
|
1242
1236
|
config[tool.configKey]["context-vault"] = {
|
|
1243
|
-
command:
|
|
1244
|
-
args:
|
|
1245
|
-
env: { NODE_OPTIONS: "--no-warnings=ExperimentalWarning" },
|
|
1237
|
+
command: "context-vault",
|
|
1238
|
+
args: serverArgs,
|
|
1246
1239
|
};
|
|
1247
1240
|
} else {
|
|
1248
1241
|
const serverArgs = [SERVER_PATH];
|
|
@@ -1597,13 +1590,6 @@ async function runSwitch() {
|
|
|
1597
1590
|
const { detected } = await detectAllTools();
|
|
1598
1591
|
|
|
1599
1592
|
if (target === "local") {
|
|
1600
|
-
const launcherPath = join(dataDir, "server.mjs");
|
|
1601
|
-
if (!existsSync(launcherPath)) {
|
|
1602
|
-
const serverAbs = resolve(ROOT, "src", "server.js");
|
|
1603
|
-
mkdirSync(dataDir, { recursive: true });
|
|
1604
|
-
writeFileSync(launcherPath, `import "${serverAbs}";\n`);
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
1593
|
vaultConfig.mode = "local";
|
|
1608
1594
|
mkdirSync(dataDir, { recursive: true });
|
|
1609
1595
|
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + "\n");
|
|
@@ -1635,7 +1621,7 @@ async function runSwitch() {
|
|
|
1635
1621
|
}
|
|
1636
1622
|
console.log();
|
|
1637
1623
|
console.log(green(" ✓ Switched to local mode."));
|
|
1638
|
-
console.log(dim(` Server:
|
|
1624
|
+
console.log(dim(` Server: context-vault serve`));
|
|
1639
1625
|
console.log();
|
|
1640
1626
|
} else {
|
|
1641
1627
|
const hostedUrl = getFlag("--url") || vaultConfig.hostedUrl || API_URL;
|
|
@@ -2043,8 +2029,7 @@ async function runStatus() {
|
|
|
2043
2029
|
const email = raw.email ? ` · ${raw.email}` : "";
|
|
2044
2030
|
modeDetail = ` (${raw.hostedUrl}${email})`;
|
|
2045
2031
|
} else {
|
|
2046
|
-
|
|
2047
|
-
modeDetail = ` (node ${launcherPath})`;
|
|
2032
|
+
modeDetail = ` (context-vault serve)`;
|
|
2048
2033
|
}
|
|
2049
2034
|
} catch {}
|
|
2050
2035
|
}
|
|
@@ -4686,35 +4671,34 @@ async function runDoctor() {
|
|
|
4686
4671
|
db?.close();
|
|
4687
4672
|
} catch {}
|
|
4688
4673
|
|
|
4689
|
-
// ──
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
}
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
` ${dim("Fix: run context-vault setup to reinstall")}`,
|
|
4706
|
-
);
|
|
4707
|
-
allOk = false;
|
|
4708
|
-
}
|
|
4709
|
-
} else {
|
|
4710
|
-
console.log(` ${green("✓")} Launcher exists ${dim(launcherPath)}`);
|
|
4711
|
-
}
|
|
4712
|
-
} else {
|
|
4713
|
-
console.log(` ${yellow("!")} Launcher not found at ${launcherPath}`);
|
|
4714
|
-
console.log(` ${dim("Fix: run context-vault setup")}`);
|
|
4674
|
+
// ── CLI binary ──────────────────────────────────────────────────────
|
|
4675
|
+
try {
|
|
4676
|
+
const binVersion = execSync("context-vault --version", {
|
|
4677
|
+
encoding: "utf-8",
|
|
4678
|
+
timeout: 5000,
|
|
4679
|
+
}).trim();
|
|
4680
|
+
console.log(
|
|
4681
|
+
` ${green("✓")} CLI binary ${dim(`(${binVersion})`)}`,
|
|
4682
|
+
);
|
|
4683
|
+
} catch {
|
|
4684
|
+
console.log(
|
|
4685
|
+
` ${red("✘")} CLI binary not found in PATH`,
|
|
4686
|
+
);
|
|
4687
|
+
console.log(
|
|
4688
|
+
` ${dim("Fix: npm install -g context-vault")}`,
|
|
4689
|
+
);
|
|
4715
4690
|
allOk = false;
|
|
4716
4691
|
}
|
|
4717
4692
|
|
|
4693
|
+
// Clean up legacy launcher if it exists
|
|
4694
|
+
const legacyLauncher = join(HOME, ".context-mcp", "server.mjs");
|
|
4695
|
+
if (existsSync(legacyLauncher)) {
|
|
4696
|
+
try {
|
|
4697
|
+
unlinkSync(legacyLauncher);
|
|
4698
|
+
console.log(` ${green("✓")} Removed legacy launcher ${dim(legacyLauncher)}`);
|
|
4699
|
+
} catch {}
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4718
4702
|
// ── Error log ─────────────────────────────────────────────────────────
|
|
4719
4703
|
const logPath = errorLogPath(config.dataDir);
|
|
4720
4704
|
const logCount = errorLogCount(config.dataDir);
|
|
@@ -5279,6 +5263,86 @@ async function runConsolidate() {
|
|
|
5279
5263
|
console.log();
|
|
5280
5264
|
}
|
|
5281
5265
|
|
|
5266
|
+
async function runDebug() {
|
|
5267
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
5268
|
+
const { errorLogPath, errorLogCount } = await import("../src/error-log.js");
|
|
5269
|
+
|
|
5270
|
+
let config;
|
|
5271
|
+
try {
|
|
5272
|
+
config = resolveConfig();
|
|
5273
|
+
} catch {
|
|
5274
|
+
config = null;
|
|
5275
|
+
}
|
|
5276
|
+
|
|
5277
|
+
const dataDir = config?.dataDir || join(HOME, ".context-mcp");
|
|
5278
|
+
const vaultDir = config?.vaultDir || join(HOME, "vault");
|
|
5279
|
+
const dbPath = config?.dbPath || join(dataDir, "vault.db");
|
|
5280
|
+
const configPath = config?.configPath || join(dataDir, "config.json");
|
|
5281
|
+
|
|
5282
|
+
const vaultExists = existsSync(vaultDir);
|
|
5283
|
+
let vaultWritable = false;
|
|
5284
|
+
if (vaultExists) {
|
|
5285
|
+
try {
|
|
5286
|
+
const probe = join(vaultDir, ".write-probe");
|
|
5287
|
+
writeFileSync(probe, "");
|
|
5288
|
+
unlinkSync(probe);
|
|
5289
|
+
vaultWritable = true;
|
|
5290
|
+
} catch {}
|
|
5291
|
+
}
|
|
5292
|
+
|
|
5293
|
+
let dbAccessible = false;
|
|
5294
|
+
let dbEntryCount = "n/a";
|
|
5295
|
+
try {
|
|
5296
|
+
const { initDatabase } = await import("@context-vault/core/db");
|
|
5297
|
+
const db = await initDatabase(dbPath);
|
|
5298
|
+
dbEntryCount = db.prepare("SELECT COUNT(*) as c FROM vault").get().c;
|
|
5299
|
+
db.close();
|
|
5300
|
+
dbAccessible = true;
|
|
5301
|
+
} catch {}
|
|
5302
|
+
|
|
5303
|
+
const logCount = errorLogCount(dataDir);
|
|
5304
|
+
const logPath = errorLogPath(dataDir);
|
|
5305
|
+
let lastLogLines = [];
|
|
5306
|
+
if (logCount > 0) {
|
|
5307
|
+
try {
|
|
5308
|
+
const lines = readFileSync(logPath, "utf-8")
|
|
5309
|
+
.split("\n")
|
|
5310
|
+
.filter((l) => l.trim());
|
|
5311
|
+
lastLogLines = lines.slice(-5);
|
|
5312
|
+
} catch {}
|
|
5313
|
+
}
|
|
5314
|
+
|
|
5315
|
+
const lines = [
|
|
5316
|
+
"```",
|
|
5317
|
+
`context-vault debug report`,
|
|
5318
|
+
`Generated: ${new Date().toISOString()}`,
|
|
5319
|
+
``,
|
|
5320
|
+
`Node.js: ${process.versions.node} (${process.execPath})`,
|
|
5321
|
+
`Platform: ${process.platform} ${process.arch}`,
|
|
5322
|
+
`cv version: ${VERSION}`,
|
|
5323
|
+
``,
|
|
5324
|
+
`Config: ${configPath} (${existsSync(configPath) ? "found" : "missing"})`,
|
|
5325
|
+
`Vault dir: ${vaultDir} (${vaultExists ? "exists" : "missing"}${vaultExists ? `, writable: ${vaultWritable}` : ""})`,
|
|
5326
|
+
`DB path: ${dbPath} (${existsSync(dbPath) ? "exists" : "missing"})`,
|
|
5327
|
+
`DB access: ${dbAccessible ? `ok (${dbEntryCount} entries)` : "failed"}`,
|
|
5328
|
+
``,
|
|
5329
|
+
`Error log: ${logPath} (${logCount} entries)`,
|
|
5330
|
+
];
|
|
5331
|
+
|
|
5332
|
+
if (lastLogLines.length) {
|
|
5333
|
+
lines.push(``, `Last 5 error log entries:`);
|
|
5334
|
+
for (const l of lastLogLines) lines.push(` ${l}`);
|
|
5335
|
+
}
|
|
5336
|
+
|
|
5337
|
+
lines.push("```");
|
|
5338
|
+
lines.push(
|
|
5339
|
+
``,
|
|
5340
|
+
`Paste the above into Claude Code or your AI assistant to diagnose issues.`,
|
|
5341
|
+
);
|
|
5342
|
+
|
|
5343
|
+
console.log(lines.join("\n"));
|
|
5344
|
+
}
|
|
5345
|
+
|
|
5282
5346
|
async function runServe() {
|
|
5283
5347
|
await import("../src/server.js");
|
|
5284
5348
|
}
|
|
@@ -5398,6 +5462,9 @@ async function main() {
|
|
|
5398
5462
|
case "consolidate":
|
|
5399
5463
|
await runConsolidate();
|
|
5400
5464
|
break;
|
|
5465
|
+
case "debug":
|
|
5466
|
+
await runDebug();
|
|
5467
|
+
break;
|
|
5401
5468
|
default:
|
|
5402
5469
|
console.error(red(`Unknown command: ${command}`));
|
|
5403
5470
|
console.error(`Run ${cyan("context-vault --help")} for usage.`);
|
|
@@ -5406,6 +5473,15 @@ async function main() {
|
|
|
5406
5473
|
}
|
|
5407
5474
|
|
|
5408
5475
|
main().catch((e) => {
|
|
5409
|
-
|
|
5476
|
+
const dataDir = join(HOME, ".context-mcp");
|
|
5477
|
+
const logPath = join(dataDir, "error.log");
|
|
5478
|
+
console.error(red(`Error: ${e.message}`));
|
|
5479
|
+
console.error(dim(`Error log: ${logPath}`));
|
|
5480
|
+
console.error(dim(`Run: context-vault doctor`));
|
|
5481
|
+
console.error(
|
|
5482
|
+
dim(
|
|
5483
|
+
`Debug with AI: "context-vault exited with: ${e.message} — how do I fix this?"`,
|
|
5484
|
+
),
|
|
5485
|
+
);
|
|
5410
5486
|
process.exit(1);
|
|
5411
5487
|
});
|
|
@@ -107,6 +107,7 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
107
107
|
const db = new DatabaseSync(path, { allowExtension: true });
|
|
108
108
|
db.exec("PRAGMA journal_mode = WAL");
|
|
109
109
|
db.exec("PRAGMA foreign_keys = ON");
|
|
110
|
+
db.exec("PRAGMA busy_timeout = 3000");
|
|
110
111
|
try {
|
|
111
112
|
sqliteVec.load(db);
|
|
112
113
|
} catch (e) {
|
|
@@ -124,6 +125,7 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
124
125
|
);
|
|
125
126
|
|
|
126
127
|
const backupPath = `${dbPath}.v${version}.backup`;
|
|
128
|
+
let backupSucceeded = false;
|
|
127
129
|
try {
|
|
128
130
|
db.close();
|
|
129
131
|
if (existsSync(dbPath)) {
|
|
@@ -131,6 +133,9 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
131
133
|
console.error(
|
|
132
134
|
`[context-vault] Backed up old database to: ${backupPath}`,
|
|
133
135
|
);
|
|
136
|
+
backupSucceeded = true;
|
|
137
|
+
} else {
|
|
138
|
+
backupSucceeded = true;
|
|
134
139
|
}
|
|
135
140
|
} catch (backupErr) {
|
|
136
141
|
console.error(
|
|
@@ -138,6 +143,13 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
138
143
|
);
|
|
139
144
|
}
|
|
140
145
|
|
|
146
|
+
if (!backupSucceeded) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`[context-vault] Aborting schema migration: backup failed for ${dbPath}. ` +
|
|
149
|
+
`Fix the backup issue or manually back up the file before upgrading.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
141
153
|
unlinkSync(dbPath);
|
|
142
154
|
try { unlinkSync(dbPath + "-wal"); } catch {}
|
|
143
155
|
try { unlinkSync(dbPath + "-shm"); } catch {}
|
|
@@ -93,9 +93,16 @@ export async function indexEntry(
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
if (cat !== "event") {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
let embedding: Float32Array | null = null;
|
|
97
|
+
if (precomputedEmbedding !== undefined) {
|
|
98
|
+
embedding = precomputedEmbedding;
|
|
99
|
+
} else {
|
|
100
|
+
try {
|
|
101
|
+
embedding = await ctx.embed([title, body].filter(Boolean).join(" "));
|
|
102
|
+
} catch (embedErr) {
|
|
103
|
+
console.warn(`[context-vault] embed() failed for entry ${id} — skipping vec insert: ${(embedErr as Error).message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
99
106
|
|
|
100
107
|
if (embedding) {
|
|
101
108
|
try { ctx.deleteVec(rowid); } catch { /* no-op */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
|
|
6
6
|
"bin": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@context-vault/core"
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@context-vault/core": "^3.
|
|
60
|
+
"@context-vault/core": "^3.1.1",
|
|
61
61
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
62
62
|
"adm-zip": "^0.5.16",
|
|
63
63
|
"sqlite-vec": "^0.1.0"
|
package/scripts/postinstall.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* 1. Installs @huggingface/transformers with --ignore-scripts to avoid sharp's
|
|
7
7
|
* broken install lifecycle in global contexts. Semantic search degrades
|
|
8
8
|
* gracefully if this step fails.
|
|
9
|
-
* 2.
|
|
9
|
+
* 2. Ensures data directory exists.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { execSync } from "node:child_process";
|
|
13
|
-
import { existsSync,
|
|
13
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
14
14
|
import { join, dirname } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { homedir } from "node:os";
|
|
@@ -51,18 +51,9 @@ async function main() {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
// ── 2.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const isNpx = PKG_ROOT.includes("/_npx/") || PKG_ROOT.includes("\\_npx\\");
|
|
58
|
-
if (!isNpx) {
|
|
59
|
-
const SERVER_ABS = join(PKG_ROOT, "src", "server.js");
|
|
60
|
-
const DATA_DIR = join(homedir(), ".context-mcp");
|
|
61
|
-
const LAUNCHER = join(DATA_DIR, "server.mjs");
|
|
62
|
-
mkdirSync(DATA_DIR, { recursive: true });
|
|
63
|
-
writeFileSync(LAUNCHER, `import "${SERVER_ABS}";\n`);
|
|
64
|
-
console.log("[context-vault] Local server launcher written to " + LAUNCHER);
|
|
65
|
-
}
|
|
54
|
+
// ── 2. Ensure data dir exists ────────────────────────────────────────
|
|
55
|
+
const DATA_DIR = join(homedir(), ".context-mcp");
|
|
56
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
66
57
|
}
|
|
67
58
|
|
|
68
59
|
main().catch(() => {});
|
package/src/helpers.js
CHANGED
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
|
|
5
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const pkg = JSON.parse(
|
|
7
|
-
readFileSync(join(__dirname, "..", "
|
|
7
|
+
readFileSync(join(__dirname, "..", "package.json"), "utf-8"),
|
|
8
8
|
);
|
|
9
9
|
|
|
10
10
|
export function ok(text) {
|
|
@@ -26,11 +26,19 @@ export function err(text, code = "UNKNOWN", meta = {}) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export function errWithHint(text, code, hint) {
|
|
30
|
+
const prompt = hint
|
|
31
|
+
? `\n\n**Debug with AI:** Paste this into Claude Code or your AI assistant:\n> "${hint}"`
|
|
32
|
+
: "";
|
|
33
|
+
return err(text + prompt, code);
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
export function ensureVaultExists(config) {
|
|
30
37
|
if (!config.vaultDirExists) {
|
|
31
|
-
return
|
|
38
|
+
return errWithHint(
|
|
32
39
|
`Vault directory not found: ${config.vaultDir}. Run context-status for diagnostics.`,
|
|
33
40
|
"VAULT_NOT_FOUND",
|
|
41
|
+
"My context-vault can't find the vault directory. Run `context-vault doctor` and help me fix it.",
|
|
34
42
|
);
|
|
35
43
|
}
|
|
36
44
|
return null;
|
package/src/register-tools.js
CHANGED
package/src/server.js
CHANGED
|
@@ -221,6 +221,13 @@ async function main() {
|
|
|
221
221
|
phase,
|
|
222
222
|
};
|
|
223
223
|
appendErrorLog(dataDir, logEntry);
|
|
224
|
+
try {
|
|
225
|
+
mkdirSync(dataDir, { recursive: true });
|
|
226
|
+
writeFileSync(
|
|
227
|
+
join(dataDir, ".last-error"),
|
|
228
|
+
`${logEntry.timestamp} [${phase}] ${err.message}`,
|
|
229
|
+
);
|
|
230
|
+
} catch {}
|
|
224
231
|
|
|
225
232
|
sendTelemetryEvent(config, {
|
|
226
233
|
event: "startup_error",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { gatherVaultStatus, computeGrowthWarnings } from "../status.js";
|
|
2
4
|
import { errorLogPath, errorLogCount } from "../error-log.js";
|
|
3
|
-
import { ok } from "../helpers.js";
|
|
5
|
+
import { ok, err } from "../helpers.js";
|
|
4
6
|
|
|
5
7
|
function relativeTime(ts) {
|
|
6
8
|
const secs = Math.floor((Date.now() - ts) / 1000);
|
|
@@ -23,6 +25,7 @@ export const inputSchema = {};
|
|
|
23
25
|
* @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
|
|
24
26
|
*/
|
|
25
27
|
export function handler(_args, ctx) {
|
|
28
|
+
try {
|
|
26
29
|
const { config } = ctx;
|
|
27
30
|
|
|
28
31
|
const status = gatherVaultStatus(ctx);
|
|
@@ -120,6 +123,18 @@ export function handler(_args, ctx) {
|
|
|
120
123
|
lines.push(`- Entries: ${logCount} (share this file for support)`);
|
|
121
124
|
}
|
|
122
125
|
|
|
126
|
+
// Last startup error
|
|
127
|
+
const lastErrorPath = join(config.dataDir, ".last-error");
|
|
128
|
+
if (existsSync(lastErrorPath)) {
|
|
129
|
+
try {
|
|
130
|
+
const lastError = readFileSync(lastErrorPath, "utf-8").trim();
|
|
131
|
+
lines.push(``, `### Last Startup Error`);
|
|
132
|
+
lines.push(`\`\`\``);
|
|
133
|
+
lines.push(lastError);
|
|
134
|
+
lines.push(`\`\`\``);
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
137
|
+
|
|
123
138
|
// Health: session-level tool call stats
|
|
124
139
|
const ts = ctx.toolStats;
|
|
125
140
|
if (ts) {
|
|
@@ -178,4 +193,7 @@ export function handler(_args, ctx) {
|
|
|
178
193
|
}
|
|
179
194
|
|
|
180
195
|
return ok(lines.join("\n"));
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return err(e.message, "STATUS_FAILED");
|
|
198
|
+
}
|
|
181
199
|
}
|
|
@@ -105,28 +105,32 @@ export async function handler(
|
|
|
105
105
|
|
|
106
106
|
let candidates = [];
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
try {
|
|
109
|
+
if (normalizedKinds.length > 0) {
|
|
110
|
+
for (const kindFilter of normalizedKinds) {
|
|
111
|
+
const rows = await hybridSearch(ctx, topic, {
|
|
112
|
+
kindFilter,
|
|
113
|
+
limit: Math.ceil(MAX_ENTRIES_FOR_GATHER / normalizedKinds.length),
|
|
114
|
+
|
|
115
|
+
includeSuperseeded: false,
|
|
116
|
+
});
|
|
117
|
+
candidates.push(...rows);
|
|
118
|
+
}
|
|
119
|
+
const seen = new Set();
|
|
120
|
+
candidates = candidates.filter((r) => {
|
|
121
|
+
if (seen.has(r.id)) return false;
|
|
122
|
+
seen.add(r.id);
|
|
123
|
+
return true;
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
candidates = await hybridSearch(ctx, topic, {
|
|
127
|
+
limit: MAX_ENTRIES_FOR_GATHER,
|
|
128
|
+
|
|
114
129
|
includeSuperseeded: false,
|
|
115
130
|
});
|
|
116
|
-
candidates.push(...rows);
|
|
117
131
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (seen.has(r.id)) return false;
|
|
121
|
-
seen.add(r.id);
|
|
122
|
-
return true;
|
|
123
|
-
});
|
|
124
|
-
} else {
|
|
125
|
-
candidates = await hybridSearch(ctx, topic, {
|
|
126
|
-
limit: MAX_ENTRIES_FOR_GATHER,
|
|
127
|
-
|
|
128
|
-
includeSuperseeded: false,
|
|
129
|
-
});
|
|
132
|
+
} catch (e) {
|
|
133
|
+
return err(e.message, "SEARCH_FAILED");
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
if (effectiveTags.length) {
|
|
@@ -162,22 +166,27 @@ export async function handler(
|
|
|
162
166
|
|
|
163
167
|
const supersedes = noiseIds.length > 0 ? noiseIds : undefined;
|
|
164
168
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
169
|
+
let entry;
|
|
170
|
+
try {
|
|
171
|
+
entry = await captureAndIndex(ctx, {
|
|
172
|
+
kind: "brief",
|
|
173
|
+
title: `${topic} — Context Brief`,
|
|
174
|
+
body: briefBody,
|
|
175
|
+
tags: briefTags,
|
|
176
|
+
source: "create_snapshot",
|
|
177
|
+
identity_key: effectiveIdentityKey,
|
|
178
|
+
supersedes,
|
|
179
|
+
|
|
180
|
+
meta: {
|
|
181
|
+
topic,
|
|
182
|
+
entry_count: gatherEntries.length,
|
|
183
|
+
noise_superseded: noiseIds.length,
|
|
184
|
+
synthesized_from: gatherEntries.map((e) => e.id),
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
} catch (e) {
|
|
188
|
+
return err(e.message, "SAVE_FAILED");
|
|
189
|
+
}
|
|
181
190
|
|
|
182
191
|
const parts = [
|
|
183
192
|
`✓ Snapshot created → id: ${entry.id}`,
|
|
@@ -17,7 +17,6 @@ export const inputSchema = {
|
|
|
17
17
|
* @param {import('../types.js').ToolShared} shared
|
|
18
18
|
*/
|
|
19
19
|
export async function handler({ id }, ctx, { ensureIndexed }) {
|
|
20
|
-
|
|
21
20
|
if (!id?.trim())
|
|
22
21
|
return err("Required: id (non-empty string)", "INVALID_INPUT");
|
|
23
22
|
await ensureIndexed();
|
|
@@ -25,34 +24,31 @@ export async function handler({ id }, ctx, { ensureIndexed }) {
|
|
|
25
24
|
const entry = ctx.stmts.getEntryById.get(id);
|
|
26
25
|
if (!entry) return err(`Entry not found: ${id}`, "NOT_FOUND");
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
try {
|
|
28
|
+
// Delete DB record first — if this fails, the file stays and no orphan is created
|
|
29
|
+
const rowidResult = ctx.stmts.getRowid.get(id);
|
|
30
|
+
if (rowidResult?.rowid) {
|
|
31
|
+
try {
|
|
32
|
+
ctx.deleteVec(Number(rowidResult.rowid));
|
|
33
|
+
} catch {}
|
|
34
|
+
}
|
|
35
|
+
ctx.stmts.deleteEntry.run(id);
|
|
36
|
+
|
|
37
|
+
// Delete file from disk after successful DB delete
|
|
38
|
+
let fileWarning = null;
|
|
39
|
+
if (entry.file_path) {
|
|
40
|
+
try {
|
|
41
|
+
unlinkSync(entry.file_path);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
if (e.code !== "ENOENT") {
|
|
44
|
+
fileWarning = `file could not be removed from disk (${e.code}): ${entry.file_path}`;
|
|
45
|
+
}
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
|
-
}
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
ctx.deleteVec(Number(rowidResult.rowid));
|
|
50
|
-
} catch {}
|
|
49
|
+
const msg = `Deleted ${entry.kind}: ${entry.title || "(untitled)"} [${id}]`;
|
|
50
|
+
return ok(fileWarning ? `${msg}\nWarning: ${fileWarning}` : msg);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return err(e.message, "DELETE_FAILED");
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
// Delete DB row (FTS trigger handles FTS cleanup)
|
|
54
|
-
ctx.stmts.deleteEntry.run(id);
|
|
55
|
-
|
|
56
|
-
const msg = `Deleted ${entry.kind}: ${entry.title || "(untitled)"} [${id}]`;
|
|
57
|
-
return ok(fileWarning ? `${msg}\nWarning: ${fileWarning}` : msg);
|
|
58
54
|
}
|
package/src/tools/get-context.js
CHANGED
|
@@ -7,7 +7,7 @@ import { categoryFor } from "@context-vault/core/categories";
|
|
|
7
7
|
import { normalizeKind } from "@context-vault/core/files";
|
|
8
8
|
import { resolveTemporalParams } from "../temporal.js";
|
|
9
9
|
import { collectLinkedEntries } from "../linking.js";
|
|
10
|
-
import { ok, err } from "../helpers.js";
|
|
10
|
+
import { ok, err, errWithHint } from "../helpers.js";
|
|
11
11
|
import { isEmbedAvailable } from "@context-vault/core/embed";
|
|
12
12
|
|
|
13
13
|
const STALE_DUPLICATE_DAYS = 7;
|
|
@@ -290,7 +290,7 @@ export const inputSchema = {
|
|
|
290
290
|
.describe(
|
|
291
291
|
"Return entries created before this date. Accepts ISO date strings or the same natural shortcuts as `since`. When `since` is 'yesterday' and `until` is omitted, `until` is automatically set to the end of yesterday.",
|
|
292
292
|
),
|
|
293
|
-
limit: z.number().optional().describe("Max results to return (default 10)"),
|
|
293
|
+
limit: z.number().max(500).optional().describe("Max results to return (default 10)"),
|
|
294
294
|
include_superseded: z
|
|
295
295
|
.boolean()
|
|
296
296
|
.optional()
|
|
@@ -305,6 +305,7 @@ export const inputSchema = {
|
|
|
305
305
|
),
|
|
306
306
|
max_tokens: z
|
|
307
307
|
.number()
|
|
308
|
+
.max(100000)
|
|
308
309
|
.optional()
|
|
309
310
|
.describe(
|
|
310
311
|
"Limit output to entries that fit within this token budget (rough estimate: 1 token ≈ 4 chars). Entries are packed greedily by relevance rank. At least 1 result is always returned. Response metadata includes tokens_used and tokens_budget.",
|
|
@@ -415,16 +416,7 @@ export async function handler(
|
|
|
415
416
|
if (identity_key) {
|
|
416
417
|
if (!kindFilter)
|
|
417
418
|
return err("identity_key requires kind to be specified", "INVALID_INPUT");
|
|
418
|
-
|
|
419
|
-
// Hosted mode: 3 params — (kind, identity_key).
|
|
420
|
-
const match =
|
|
421
|
-
ctx.stmts._mode === "local"
|
|
422
|
-
? ctx.stmts.getByIdentityKey.get(kindFilter, identity_key)
|
|
423
|
-
: ctx.stmts.getByIdentityKey.get(
|
|
424
|
-
kindFilter,
|
|
425
|
-
identity_key,
|
|
426
|
-
null,
|
|
427
|
-
);
|
|
419
|
+
const match = ctx.stmts.getByIdentityKey.get(kindFilter, identity_key);
|
|
428
420
|
if (match) {
|
|
429
421
|
const entryTags = match.tags ? JSON.parse(match.tags) : [];
|
|
430
422
|
const tagStr = entryTags.length ? entryTags.join(", ") : "none";
|
|
@@ -514,9 +506,18 @@ export async function handler(
|
|
|
514
506
|
}
|
|
515
507
|
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
516
508
|
params.push(fetchLimit);
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
.
|
|
509
|
+
let rows;
|
|
510
|
+
try {
|
|
511
|
+
rows = ctx.db
|
|
512
|
+
.prepare(`SELECT * FROM vault ${where} ORDER BY created_at DESC LIMIT ?`)
|
|
513
|
+
.all(...params);
|
|
514
|
+
} catch (e) {
|
|
515
|
+
return errWithHint(
|
|
516
|
+
e.message,
|
|
517
|
+
"DB_ERROR",
|
|
518
|
+
"context-vault get_context DB_ERROR. Check `cat ~/.context-mcp/error.log | tail -5` and help me debug.",
|
|
519
|
+
);
|
|
520
|
+
}
|
|
520
521
|
|
|
521
522
|
// Post-filter by tags if provided, then apply requested limit
|
|
522
523
|
filtered = effectiveTags.length
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { normalizeKind } from "@context-vault/core/files";
|
|
3
3
|
import { categoryFor } from "@context-vault/core/categories";
|
|
4
|
-
import { ok } from "../helpers.js";
|
|
4
|
+
import { ok, err, errWithHint } from "../helpers.js";
|
|
5
|
+
import { resolveTemporalParams } from "../temporal.js";
|
|
5
6
|
|
|
6
7
|
export const name = "list_context";
|
|
7
8
|
|
|
@@ -50,6 +51,10 @@ export async function handler(
|
|
|
50
51
|
|
|
51
52
|
await ensureIndexed();
|
|
52
53
|
|
|
54
|
+
const resolved = resolveTemporalParams({ since, until });
|
|
55
|
+
since = resolved.since;
|
|
56
|
+
until = resolved.until;
|
|
57
|
+
|
|
53
58
|
const kindFilter = kind ? normalizeKind(kind) : null;
|
|
54
59
|
const effectiveCategory =
|
|
55
60
|
category || (kindFilter ? categoryFor(kindFilter) : null);
|
|
@@ -64,8 +69,6 @@ export async function handler(
|
|
|
64
69
|
const clauses = [];
|
|
65
70
|
const params = [];
|
|
66
71
|
|
|
67
|
-
if (false) {
|
|
68
|
-
}
|
|
69
72
|
if (kindFilter) {
|
|
70
73
|
clauses.push("kind = ?");
|
|
71
74
|
params.push(kindFilter);
|
|
@@ -91,16 +94,26 @@ export async function handler(
|
|
|
91
94
|
const fetchLimit = tags?.length ? effectiveLimit * 10 : effectiveLimit;
|
|
92
95
|
|
|
93
96
|
const countParams = [...params];
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
let total;
|
|
98
|
+
let rows;
|
|
99
|
+
try {
|
|
100
|
+
total = ctx.db
|
|
101
|
+
.prepare(`SELECT COUNT(*) as c FROM vault ${where}`)
|
|
102
|
+
.get(...countParams).c;
|
|
103
|
+
|
|
104
|
+
params.push(fetchLimit, effectiveOffset);
|
|
105
|
+
rows = ctx.db
|
|
106
|
+
.prepare(
|
|
107
|
+
`SELECT id, title, kind, category, tags, created_at, updated_at, SUBSTR(body, 1, 120) as preview FROM vault ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
|
108
|
+
)
|
|
109
|
+
.all(...params);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
return errWithHint(
|
|
112
|
+
e.message,
|
|
113
|
+
"DB_ERROR",
|
|
114
|
+
"context-vault list_context DB_ERROR. Check `cat ~/.context-mcp/error.log | tail -5` and help me debug.",
|
|
115
|
+
);
|
|
116
|
+
}
|
|
104
117
|
|
|
105
118
|
// Post-filter by tags if provided, then apply requested limit
|
|
106
119
|
const filtered = tags?.length
|
|
@@ -3,7 +3,7 @@ import { captureAndIndex, updateEntryFile } from "@context-vault/core/capture";
|
|
|
3
3
|
import { indexEntry } from "@context-vault/core/index";
|
|
4
4
|
import { categoryFor, defaultTierFor } from "@context-vault/core/categories";
|
|
5
5
|
import { normalizeKind } from "@context-vault/core/files";
|
|
6
|
-
import { ok, err, ensureVaultExists, ensureValidKind } from "../helpers.js";
|
|
6
|
+
import { ok, err, errWithHint, ensureVaultExists, ensureValidKind } from "../helpers.js";
|
|
7
7
|
import { maybeShowFeedbackPrompt } from "../telemetry.js";
|
|
8
8
|
import { validateRelatedTo } from "../linking.js";
|
|
9
9
|
import {
|
|
@@ -319,12 +319,6 @@ export const inputSchema = {
|
|
|
319
319
|
.describe(
|
|
320
320
|
"Source code files this entry is derived from. When these files change (hash mismatch), the entry will be flagged as stale in get_context results.",
|
|
321
321
|
),
|
|
322
|
-
tier: z
|
|
323
|
-
.enum(["ephemeral", "working", "durable"])
|
|
324
|
-
.optional()
|
|
325
|
-
.describe(
|
|
326
|
-
"Memory tier for lifecycle management. 'ephemeral': short-lived session data. 'working': active context (default). 'durable': long-term reference material. Defaults based on kind when not specified.",
|
|
327
|
-
),
|
|
328
322
|
dry_run: z
|
|
329
323
|
.boolean()
|
|
330
324
|
.optional()
|
|
@@ -409,10 +403,6 @@ export async function handler(
|
|
|
409
403
|
const existing = ctx.stmts.getEntryById.get(id);
|
|
410
404
|
if (!existing) return err(`Entry not found: ${id}`, "NOT_FOUND");
|
|
411
405
|
|
|
412
|
-
// Ownership check: don't leak existence across users
|
|
413
|
-
return err(`Entry not found: ${id}`, "NOT_FOUND");
|
|
414
|
-
}
|
|
415
|
-
|
|
416
406
|
if (kind && normalizeKind(kind) !== existing.kind) {
|
|
417
407
|
return err(
|
|
418
408
|
`Cannot change kind (current: "${existing.kind}"). Delete and re-create instead.`,
|
|
@@ -434,18 +424,27 @@ export async function handler(
|
|
|
434
424
|
if (decrypted.meta) existing.meta = JSON.stringify(decrypted.meta);
|
|
435
425
|
}
|
|
436
426
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
427
|
+
let entry;
|
|
428
|
+
try {
|
|
429
|
+
entry = updateEntryFile(ctx, existing, {
|
|
430
|
+
title,
|
|
431
|
+
body,
|
|
432
|
+
tags,
|
|
433
|
+
meta,
|
|
434
|
+
source,
|
|
435
|
+
expires_at,
|
|
436
|
+
supersedes,
|
|
437
|
+
related_to,
|
|
438
|
+
source_files,
|
|
439
|
+
});
|
|
440
|
+
await indexEntry(ctx, entry);
|
|
441
|
+
} catch (e) {
|
|
442
|
+
return errWithHint(
|
|
443
|
+
e.message,
|
|
444
|
+
"UPDATE_FAILED",
|
|
445
|
+
"context-vault save_context update is failing. Check `cat ~/.context-mcp/error.log | tail -5` and help me debug.",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
449
448
|
if (entry.related_to?.length && ctx.stmts.updateRelatedTo) {
|
|
450
449
|
ctx.stmts.updateRelatedTo.run(JSON.stringify(entry.related_to), entry.id);
|
|
451
450
|
} else if (entry.related_to === null && ctx.stmts.updateRelatedTo) {
|
|
@@ -489,7 +488,11 @@ export async function handler(
|
|
|
489
488
|
if (category === "knowledge" || category === "event") {
|
|
490
489
|
const threshold = similarity_threshold ?? DEFAULT_SIMILARITY_THRESHOLD;
|
|
491
490
|
const embeddingText = [title, body].filter(Boolean).join(" ");
|
|
492
|
-
|
|
491
|
+
try {
|
|
492
|
+
queryEmbedding = await ctx.embed(embeddingText);
|
|
493
|
+
} catch {
|
|
494
|
+
queryEmbedding = null;
|
|
495
|
+
}
|
|
493
496
|
if (queryEmbedding) {
|
|
494
497
|
similarEntries = await findSimilar(
|
|
495
498
|
ctx,
|
|
@@ -543,22 +546,27 @@ export async function handler(
|
|
|
543
546
|
|
|
544
547
|
const embeddingToReuse = category === "knowledge" ? queryEmbedding : null;
|
|
545
548
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
549
|
+
let entry;
|
|
550
|
+
try {
|
|
551
|
+
entry = await captureAndIndex(ctx, {
|
|
552
|
+
kind: normalizedKind,
|
|
553
|
+
title,
|
|
554
|
+
body,
|
|
555
|
+
meta: finalMeta,
|
|
556
|
+
tags,
|
|
557
|
+
source,
|
|
558
|
+
folder,
|
|
559
|
+
identity_key,
|
|
560
|
+
expires_at,
|
|
561
|
+
supersedes,
|
|
562
|
+
related_to,
|
|
563
|
+
source_files,
|
|
559
564
|
|
|
560
|
-
|
|
561
|
-
|
|
565
|
+
tier: effectiveTier,
|
|
566
|
+
}, embeddingToReuse);
|
|
567
|
+
} catch (e) {
|
|
568
|
+
return errWithHint(e.message, "SAVE_FAILED", "context-vault save_context is failing. Check `cat ~/.context-mcp/error.log | tail -5` and help me debug.");
|
|
569
|
+
}
|
|
562
570
|
|
|
563
571
|
if (ctx.config?.dataDir) {
|
|
564
572
|
maybeShowFeedbackPrompt(ctx.config.dataDir);
|
|
@@ -613,12 +621,8 @@ export async function handler(
|
|
|
613
621
|
if (criticalLimit != null) {
|
|
614
622
|
try {
|
|
615
623
|
const countRow = ctx.db
|
|
616
|
-
.prepare(
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
: "SELECT COUNT(*) as c FROM vault",
|
|
620
|
-
)
|
|
621
|
-
.get(...([]));
|
|
624
|
+
.prepare("SELECT COUNT(*) as c FROM vault")
|
|
625
|
+
.get();
|
|
622
626
|
if (countRow.c >= criticalLimit) {
|
|
623
627
|
parts.push(
|
|
624
628
|
``,
|