context-vault 2.16.0 → 2.17.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 +513 -63
- package/node_modules/@context-vault/core/package.json +2 -2
- package/node_modules/@context-vault/core/src/capture/file-ops.js +2 -0
- package/node_modules/@context-vault/core/src/capture/index.js +14 -0
- package/node_modules/@context-vault/core/src/constants.js +7 -2
- package/node_modules/@context-vault/core/src/core/categories.js +1 -0
- package/node_modules/@context-vault/core/src/core/config.js +8 -1
- package/node_modules/@context-vault/core/src/core/files.js +6 -29
- package/node_modules/@context-vault/core/src/core/frontmatter.js +1 -0
- package/node_modules/@context-vault/core/src/core/linking.js +161 -0
- package/node_modules/@context-vault/core/src/core/migrate-dirs.js +196 -0
- package/node_modules/@context-vault/core/src/core/status.js +28 -2
- package/node_modules/@context-vault/core/src/core/temporal.js +146 -0
- package/node_modules/@context-vault/core/src/index/db.js +178 -8
- package/node_modules/@context-vault/core/src/index/index.js +113 -48
- package/node_modules/@context-vault/core/src/index.js +5 -0
- package/node_modules/@context-vault/core/src/retrieve/index.js +16 -136
- package/node_modules/@context-vault/core/src/server/tools/context-status.js +7 -0
- package/node_modules/@context-vault/core/src/server/tools/create-snapshot.js +37 -68
- package/node_modules/@context-vault/core/src/server/tools/get-context.js +120 -19
- package/node_modules/@context-vault/core/src/server/tools/save-context.js +29 -6
- package/node_modules/@context-vault/core/src/server/tools.js +0 -2
- package/package.json +3 -3
- package/src/hooks/post-tool-call.mjs +1 -1
- package/src/hooks/session-end.mjs +1 -1
- package/src/server/index.js +4 -2
- package/node_modules/@context-vault/core/src/server/tools/submit-feedback.js +0 -55
package/bin/cli.js
CHANGED
|
@@ -282,7 +282,7 @@ function printDetectionResults(results) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
function showHelp() {
|
|
285
|
+
function showHelp(showAll = false) {
|
|
286
286
|
console.log(`
|
|
287
287
|
${bold("◇ context-vault")} ${dim(`v${VERSION}`)}
|
|
288
288
|
${dim("Persistent memory for AI agents")}
|
|
@@ -293,35 +293,48 @@ ${bold("Usage:")}
|
|
|
293
293
|
${dim("No command → runs setup (first time) or shows status (existing vault)")}
|
|
294
294
|
|
|
295
295
|
${bold("Commands:")}
|
|
296
|
-
${cyan("setup")}
|
|
297
|
-
${cyan("connect")} --key cv_...
|
|
298
|
-
${cyan("switch")} local|hosted
|
|
299
|
-
${cyan("serve")}
|
|
300
|
-
${cyan("hooks")} install|uninstall
|
|
301
|
-
${cyan("claude")} install|uninstall
|
|
302
|
-
${cyan("skills")} install
|
|
303
|
-
${cyan("health")}
|
|
304
|
-
${cyan("
|
|
305
|
-
${cyan("
|
|
306
|
-
${cyan("
|
|
307
|
-
${cyan("
|
|
308
|
-
${cyan("save")}
|
|
309
|
-
${cyan("
|
|
310
|
-
${cyan("
|
|
311
|
-
${cyan("
|
|
312
|
-
${cyan("
|
|
313
|
-
${cyan("
|
|
314
|
-
${cyan("
|
|
315
|
-
${cyan("
|
|
316
|
-
${cyan("
|
|
317
|
-
${cyan("
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
${
|
|
296
|
+
${cyan("setup")} Interactive MCP server installer
|
|
297
|
+
${cyan("connect")} --key cv_... Connect AI tools to hosted vault
|
|
298
|
+
${cyan("switch")} local|hosted Switch between local and hosted MCP modes
|
|
299
|
+
${cyan("serve")} Start the MCP server (used by AI clients)
|
|
300
|
+
${cyan("hooks")} install|uninstall Install or remove Claude Code memory hook
|
|
301
|
+
${cyan("claude")} install|uninstall Alias for hooks install|uninstall
|
|
302
|
+
${cyan("skills")} install Install bundled Claude Code skills
|
|
303
|
+
${cyan("health")} Quick health check — vault, DB, entry count
|
|
304
|
+
${cyan("status")} Show vault diagnostics
|
|
305
|
+
${cyan("doctor")} Diagnose and repair common issues
|
|
306
|
+
${cyan("restart")} Stop running MCP server processes (client auto-restarts)
|
|
307
|
+
${cyan("search")} Search vault entries from CLI
|
|
308
|
+
${cyan("save")} Save an entry to the vault from CLI
|
|
309
|
+
${cyan("import")} <path> Import entries from file or directory
|
|
310
|
+
${cyan("export")} Export vault to JSON or CSV
|
|
311
|
+
${cyan("ingest")} <url> Fetch URL and save as vault entry
|
|
312
|
+
${cyan("ingest-project")} <path> Scan project directory and register as project entity
|
|
313
|
+
${cyan("reindex")} Rebuild search index from knowledge files
|
|
314
|
+
${cyan("migrate-dirs")} [--dry-run] Rename plural vault dirs to singular (post-2.18.0)
|
|
315
|
+
${cyan("prune")} Remove expired entries (use --dry-run to preview)
|
|
316
|
+
${cyan("update")} Check for and install updates
|
|
317
|
+
${cyan("uninstall")} Remove MCP configs and optionally data
|
|
318
|
+
`);
|
|
319
|
+
|
|
320
|
+
if (showAll) {
|
|
321
|
+
console.log(`${bold("Plumbing")} ${dim("(internal — hook implementations and maintenance utilities):")}
|
|
322
|
+
${cyan("recall")} Search vault from a Claude Code hook (reads stdin)
|
|
323
|
+
${cyan("session-capture")} Save a session summary entry (reads JSON from stdin)
|
|
324
|
+
${cyan("session-end")} Run session-end hook (parse transcript + capture)
|
|
325
|
+
${cyan("post-tool-call")} Run post-tool-call hook (log tool usage)
|
|
326
|
+
${cyan("flush")} Check vault health and confirm DB is accessible
|
|
327
|
+
${cyan("consolidate")} Find hot tags and cold entries for maintenance
|
|
328
|
+
${cyan("migrate")} Migrate vault between local and hosted
|
|
329
|
+
`);
|
|
330
|
+
} else {
|
|
331
|
+
console.log(` ${dim("Run")} ${dim("context-vault --help --all")} ${dim("to show internal plumbing commands.")}
|
|
332
|
+
`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log(`${bold("Options:")}
|
|
324
336
|
--help Show this help
|
|
337
|
+
--help --all Show all commands including internal plumbing
|
|
325
338
|
--version Show version
|
|
326
339
|
--vault-dir <path> Set vault directory (setup/serve)
|
|
327
340
|
--yes Non-interactive mode (accept all defaults)
|
|
@@ -1694,6 +1707,80 @@ async function runReindex() {
|
|
|
1694
1707
|
console.log(` ${dim("·")} ${stats.unchanged} unchanged`);
|
|
1695
1708
|
}
|
|
1696
1709
|
|
|
1710
|
+
async function runMigrateDirs() {
|
|
1711
|
+
const dryRun = flags.has("--dry-run");
|
|
1712
|
+
|
|
1713
|
+
// Vault dir: positional arg (skip --flags), or fall back to configured vault
|
|
1714
|
+
const positional = args.slice(1).find((a) => !a.startsWith("--"));
|
|
1715
|
+
let vaultDir = positional;
|
|
1716
|
+
|
|
1717
|
+
if (!vaultDir) {
|
|
1718
|
+
const { resolveConfig } = await import("@context-vault/core/core/config");
|
|
1719
|
+
const config = resolveConfig();
|
|
1720
|
+
if (!config.vaultDirExists) {
|
|
1721
|
+
console.error(red(`Vault directory not found: ${config.vaultDir}`));
|
|
1722
|
+
console.error("Run " + cyan("context-vault setup") + " to configure.");
|
|
1723
|
+
process.exit(1);
|
|
1724
|
+
}
|
|
1725
|
+
vaultDir = config.vaultDir;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
if (!existsSync(vaultDir) || !statSync(vaultDir).isDirectory()) {
|
|
1729
|
+
console.error(red(`Error: ${vaultDir} is not a directory`));
|
|
1730
|
+
process.exit(1);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
const { planMigration, executeMigration } =
|
|
1734
|
+
await import("@context-vault/core/core/migrate-dirs");
|
|
1735
|
+
|
|
1736
|
+
const ops = planMigration(vaultDir);
|
|
1737
|
+
|
|
1738
|
+
if (ops.length === 0) {
|
|
1739
|
+
console.log(green("✓ No plural directories found — vault is up to date."));
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
if (dryRun) {
|
|
1744
|
+
console.log(dim("Dry run — no files will be moved.\n"));
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
for (const op of ops) {
|
|
1748
|
+
const fileLabel = `${op.fileCount} ${op.fileCount === 1 ? "file" : "files"}`;
|
|
1749
|
+
const actionLabel = op.action === "rename" ? "RENAME" : "MERGE";
|
|
1750
|
+
const suffix = dryRun ? dim(" [dry-run]") : "";
|
|
1751
|
+
console.log(
|
|
1752
|
+
` ${cyan(actionLabel)}: ${op.pluralName}/ → ${op.singularName}/ (${fileLabel})${suffix}`,
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
if (dryRun) {
|
|
1757
|
+
console.log();
|
|
1758
|
+
console.log(
|
|
1759
|
+
dim(
|
|
1760
|
+
` ${ops.length} ${ops.length === 1 ? "directory" : "directories"} would be renamed/merged.`,
|
|
1761
|
+
),
|
|
1762
|
+
);
|
|
1763
|
+
console.log(dim(" Remove --dry-run to apply."));
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
const { renamed, merged, errors } = executeMigration(ops);
|
|
1768
|
+
|
|
1769
|
+
console.log();
|
|
1770
|
+
if (renamed > 0) console.log(green(`✓ Renamed: ${renamed}`));
|
|
1771
|
+
if (merged > 0) console.log(green(`✓ Merged: ${merged}`));
|
|
1772
|
+
if (errors.length > 0) {
|
|
1773
|
+
for (const e of errors) console.log(red(` ✗ ${e}`));
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
if (renamed + merged > 0) {
|
|
1777
|
+
console.log();
|
|
1778
|
+
console.log(
|
|
1779
|
+
dim("Run `context-vault reindex` to rebuild the search index."),
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1697
1784
|
async function runPrune() {
|
|
1698
1785
|
const dryRun = flags.has("--dry-run");
|
|
1699
1786
|
|
|
@@ -1783,7 +1870,18 @@ async function runStatus() {
|
|
|
1783
1870
|
} catch {}
|
|
1784
1871
|
}
|
|
1785
1872
|
|
|
1786
|
-
|
|
1873
|
+
let db;
|
|
1874
|
+
try {
|
|
1875
|
+
db = await initDatabase(config.dbPath);
|
|
1876
|
+
} catch (e) {
|
|
1877
|
+
console.log();
|
|
1878
|
+
console.log(` ${bold("◇ context-vault")} ${dim(`v${VERSION}`)}`);
|
|
1879
|
+
console.log();
|
|
1880
|
+
console.log(` ${red("✘")} Database not accessible: ${e.message}`);
|
|
1881
|
+
console.log(dim(` Run ${cyan("context-vault doctor")} for diagnostics`));
|
|
1882
|
+
console.log();
|
|
1883
|
+
process.exit(1);
|
|
1884
|
+
}
|
|
1787
1885
|
|
|
1788
1886
|
const status = gatherVaultStatus({ db, config });
|
|
1789
1887
|
|
|
@@ -1814,7 +1912,7 @@ async function runStatus() {
|
|
|
1814
1912
|
const filled = maxCount > 0 ? Math.round((c / maxCount) * BAR_WIDTH) : 0;
|
|
1815
1913
|
const bar = "█".repeat(filled) + "░".repeat(BAR_WIDTH - filled);
|
|
1816
1914
|
const countStr = String(c).padStart(4);
|
|
1817
|
-
console.log(`
|
|
1915
|
+
console.log(` ${dim(bar)} ${countStr} ${kind}s`);
|
|
1818
1916
|
}
|
|
1819
1917
|
} else {
|
|
1820
1918
|
console.log(`\n ${dim("(empty — no entries indexed)")}`);
|
|
@@ -1956,6 +2054,25 @@ async function runUninstall() {
|
|
|
1956
2054
|
}
|
|
1957
2055
|
}
|
|
1958
2056
|
|
|
2057
|
+
// Remove Claude Code hooks
|
|
2058
|
+
const recallRemoved = removeClaudeHook();
|
|
2059
|
+
const captureRemoved = removeSessionCaptureHook();
|
|
2060
|
+
const flushRemoved = removeSessionEndHook();
|
|
2061
|
+
const autoCaptureRemoved = removePostToolCallHook();
|
|
2062
|
+
if (recallRemoved || captureRemoved || flushRemoved || autoCaptureRemoved) {
|
|
2063
|
+
console.log(` ${green("+")} Removed Claude Code hooks`);
|
|
2064
|
+
} else {
|
|
2065
|
+
console.log(` ${dim("-")} No Claude Code hooks to remove`);
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// Remove installed skills
|
|
2069
|
+
const skillsDir = join(HOME, ".claude", "skills", "compile-context");
|
|
2070
|
+
if (existsSync(skillsDir)) {
|
|
2071
|
+
const { rmSync } = await import("node:fs");
|
|
2072
|
+
rmSync(skillsDir, { recursive: true, force: true });
|
|
2073
|
+
console.log(` ${green("+")} Removed installed skills`);
|
|
2074
|
+
}
|
|
2075
|
+
|
|
1959
2076
|
// Optionally remove data directory
|
|
1960
2077
|
const dataDir = join(HOME, ".context-mcp");
|
|
1961
2078
|
if (existsSync(dataDir)) {
|
|
@@ -2745,6 +2862,16 @@ async function runSessionCapture() {
|
|
|
2745
2862
|
}
|
|
2746
2863
|
}
|
|
2747
2864
|
|
|
2865
|
+
async function runSessionEnd() {
|
|
2866
|
+
const { main } = await import("../src/hooks/session-end.mjs");
|
|
2867
|
+
await main();
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
async function runPostToolCall() {
|
|
2871
|
+
const { main } = await import("../src/hooks/post-tool-call.mjs");
|
|
2872
|
+
await main();
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2748
2875
|
async function runSave() {
|
|
2749
2876
|
const kind = getFlag("--kind");
|
|
2750
2877
|
const title = getFlag("--title");
|
|
@@ -2839,6 +2966,7 @@ async function runSearch() {
|
|
|
2839
2966
|
const sort = getFlag("--sort") || "relevance";
|
|
2840
2967
|
const format = getFlag("--format") || "plain";
|
|
2841
2968
|
const showFull = flags.has("--full");
|
|
2969
|
+
const scopeArg = getFlag("--scope"); // "hot" | "events" | "all"
|
|
2842
2970
|
|
|
2843
2971
|
const valuedFlags = new Set([
|
|
2844
2972
|
"--kind",
|
|
@@ -2846,6 +2974,7 @@ async function runSearch() {
|
|
|
2846
2974
|
"--limit",
|
|
2847
2975
|
"--sort",
|
|
2848
2976
|
"--format",
|
|
2977
|
+
"--scope",
|
|
2849
2978
|
]);
|
|
2850
2979
|
|
|
2851
2980
|
const queryParts = [];
|
|
@@ -2885,8 +3014,18 @@ async function runSearch() {
|
|
|
2885
3014
|
|
|
2886
3015
|
let results;
|
|
2887
3016
|
|
|
3017
|
+
// Resolve scope → category/exclude filter
|
|
3018
|
+
const validScopes = new Set(["hot", "events", "all"]);
|
|
3019
|
+
const resolvedScope = validScopes.has(scopeArg) ? scopeArg : "hot";
|
|
3020
|
+
const scopeCategoryFilter = resolvedScope === "events" ? "event" : null;
|
|
3021
|
+
const scopeExcludeEvents = resolvedScope === "hot";
|
|
3022
|
+
|
|
2888
3023
|
if (query) {
|
|
2889
|
-
results = await hybridSearch(ctx, query, {
|
|
3024
|
+
results = await hybridSearch(ctx, query, {
|
|
3025
|
+
limit: limit * 2,
|
|
3026
|
+
categoryFilter: scopeCategoryFilter,
|
|
3027
|
+
excludeEvents: scopeExcludeEvents,
|
|
3028
|
+
});
|
|
2890
3029
|
|
|
2891
3030
|
if (kind) {
|
|
2892
3031
|
results = results.filter((r) => r.kind === kind);
|
|
@@ -2899,6 +3038,10 @@ async function runSearch() {
|
|
|
2899
3038
|
sql += " AND kind = ?";
|
|
2900
3039
|
params.push(kind);
|
|
2901
3040
|
}
|
|
3041
|
+
if (scopeCategoryFilter) {
|
|
3042
|
+
sql += " AND category = ?";
|
|
3043
|
+
params.push(scopeCategoryFilter);
|
|
3044
|
+
}
|
|
2902
3045
|
sql += " ORDER BY COALESCE(updated_at, created_at) DESC LIMIT ?";
|
|
2903
3046
|
params.push(limit);
|
|
2904
3047
|
results = db.prepare(sql).all(...params);
|
|
@@ -3160,14 +3303,6 @@ function removeClaudeHook() {
|
|
|
3160
3303
|
return true;
|
|
3161
3304
|
}
|
|
3162
3305
|
|
|
3163
|
-
function sessionEndHookPath() {
|
|
3164
|
-
return resolve(ROOT, "src", "hooks", "session-end.mjs");
|
|
3165
|
-
}
|
|
3166
|
-
|
|
3167
|
-
function postToolCallHookPath() {
|
|
3168
|
-
return resolve(ROOT, "src", "hooks", "post-tool-call.mjs");
|
|
3169
|
-
}
|
|
3170
|
-
|
|
3171
3306
|
/**
|
|
3172
3307
|
* Writes a SessionEnd hook entry for session capture to ~/.claude/settings.json.
|
|
3173
3308
|
* Returns true if installed, false if already present.
|
|
@@ -3190,17 +3325,29 @@ function installSessionCaptureHook() {
|
|
|
3190
3325
|
if (!settings.hooks) settings.hooks = {};
|
|
3191
3326
|
if (!settings.hooks.SessionEnd) settings.hooks.SessionEnd = [];
|
|
3192
3327
|
|
|
3328
|
+
const newCommand = "context-vault session-end";
|
|
3329
|
+
|
|
3330
|
+
// Check if already installed with new CLI-based command
|
|
3193
3331
|
const alreadyInstalled = settings.hooks.SessionEnd.some((h) =>
|
|
3194
|
-
h.hooks?.some((hh) => hh.command?.includes(
|
|
3332
|
+
h.hooks?.some((hh) => hh.command?.includes(newCommand)),
|
|
3195
3333
|
);
|
|
3196
3334
|
if (alreadyInstalled) return false;
|
|
3197
3335
|
|
|
3198
|
-
|
|
3336
|
+
// Migrate: remove stale absolute-path hooks (node <path>/session-end.mjs)
|
|
3337
|
+
const hadStale = settings.hooks.SessionEnd.some((h) =>
|
|
3338
|
+
h.hooks?.some((hh) => hh.command?.includes("session-end.mjs")),
|
|
3339
|
+
);
|
|
3340
|
+
if (hadStale) {
|
|
3341
|
+
settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter(
|
|
3342
|
+
(h) => !h.hooks?.some((hh) => hh.command?.includes("session-end.mjs")),
|
|
3343
|
+
);
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3199
3346
|
settings.hooks.SessionEnd.push({
|
|
3200
3347
|
hooks: [
|
|
3201
3348
|
{
|
|
3202
3349
|
type: "command",
|
|
3203
|
-
command:
|
|
3350
|
+
command: newCommand,
|
|
3204
3351
|
timeout: 30,
|
|
3205
3352
|
},
|
|
3206
3353
|
],
|
|
@@ -3230,7 +3377,12 @@ function removeSessionCaptureHook() {
|
|
|
3230
3377
|
|
|
3231
3378
|
const before = settings.hooks.SessionEnd.length;
|
|
3232
3379
|
settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter(
|
|
3233
|
-
(h) =>
|
|
3380
|
+
(h) =>
|
|
3381
|
+
!h.hooks?.some(
|
|
3382
|
+
(hh) =>
|
|
3383
|
+
hh.command?.includes("session-end.mjs") ||
|
|
3384
|
+
hh.command?.includes("context-vault session-end"),
|
|
3385
|
+
),
|
|
3234
3386
|
);
|
|
3235
3387
|
|
|
3236
3388
|
if (settings.hooks.SessionEnd.length === before) return false;
|
|
@@ -3261,17 +3413,29 @@ function installPostToolCallHook() {
|
|
|
3261
3413
|
if (!settings.hooks) settings.hooks = {};
|
|
3262
3414
|
if (!settings.hooks.PostToolCall) settings.hooks.PostToolCall = [];
|
|
3263
3415
|
|
|
3416
|
+
const newCommand = "context-vault post-tool-call";
|
|
3417
|
+
|
|
3418
|
+
// Check if already installed with new CLI-based command
|
|
3264
3419
|
const alreadyInstalled = settings.hooks.PostToolCall.some((h) =>
|
|
3265
|
-
h.hooks?.some((hh) => hh.command?.includes(
|
|
3420
|
+
h.hooks?.some((hh) => hh.command?.includes(newCommand)),
|
|
3266
3421
|
);
|
|
3267
3422
|
if (alreadyInstalled) return false;
|
|
3268
3423
|
|
|
3269
|
-
|
|
3424
|
+
// Migrate: remove stale absolute-path hooks (node <path>/post-tool-call.mjs)
|
|
3425
|
+
const hadStale = settings.hooks.PostToolCall.some((h) =>
|
|
3426
|
+
h.hooks?.some((hh) => hh.command?.includes("post-tool-call.mjs")),
|
|
3427
|
+
);
|
|
3428
|
+
if (hadStale) {
|
|
3429
|
+
settings.hooks.PostToolCall = settings.hooks.PostToolCall.filter(
|
|
3430
|
+
(h) => !h.hooks?.some((hh) => hh.command?.includes("post-tool-call.mjs")),
|
|
3431
|
+
);
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3270
3434
|
settings.hooks.PostToolCall.push({
|
|
3271
3435
|
hooks: [
|
|
3272
3436
|
{
|
|
3273
3437
|
type: "command",
|
|
3274
|
-
command:
|
|
3438
|
+
command: newCommand,
|
|
3275
3439
|
timeout: 5,
|
|
3276
3440
|
},
|
|
3277
3441
|
],
|
|
@@ -3301,7 +3465,12 @@ function removePostToolCallHook() {
|
|
|
3301
3465
|
|
|
3302
3466
|
const before = settings.hooks.PostToolCall.length;
|
|
3303
3467
|
settings.hooks.PostToolCall = settings.hooks.PostToolCall.filter(
|
|
3304
|
-
(h) =>
|
|
3468
|
+
(h) =>
|
|
3469
|
+
!h.hooks?.some(
|
|
3470
|
+
(hh) =>
|
|
3471
|
+
hh.command?.includes("post-tool-call.mjs") ||
|
|
3472
|
+
hh.command?.includes("context-vault post-tool-call"),
|
|
3473
|
+
),
|
|
3305
3474
|
);
|
|
3306
3475
|
|
|
3307
3476
|
if (settings.hooks.PostToolCall.length === before) return false;
|
|
@@ -3667,16 +3836,20 @@ async function runDoctor() {
|
|
|
3667
3836
|
}
|
|
3668
3837
|
|
|
3669
3838
|
// ── Database ──────────────────────────────────────────────────────────
|
|
3839
|
+
let db;
|
|
3670
3840
|
if (existsSync(config.dbPath)) {
|
|
3671
3841
|
try {
|
|
3672
3842
|
const { initDatabase } = await import("@context-vault/core/index/db");
|
|
3673
|
-
|
|
3674
|
-
db.
|
|
3675
|
-
|
|
3843
|
+
db = await initDatabase(config.dbPath);
|
|
3844
|
+
const schemaRow = db.prepare("PRAGMA user_version").get();
|
|
3845
|
+
const schemaVersion = schemaRow?.user_version ?? "unknown";
|
|
3846
|
+
console.log(
|
|
3847
|
+
` ${green("✓")} Database ${dim(`${config.dbPath} (schema v${schemaVersion})`)}`,
|
|
3848
|
+
);
|
|
3676
3849
|
} catch (e) {
|
|
3677
3850
|
console.log(` ${red("✘")} Database error: ${e.message}`);
|
|
3678
3851
|
console.log(
|
|
3679
|
-
` ${dim(`Fix: rm "${config.dbPath}" (
|
|
3852
|
+
` ${dim(`Fix: rm "${config.dbPath}" and restart (will rebuild from vault files)`)}`,
|
|
3680
3853
|
);
|
|
3681
3854
|
allOk = false;
|
|
3682
3855
|
}
|
|
@@ -3686,6 +3859,101 @@ async function runDoctor() {
|
|
|
3686
3859
|
);
|
|
3687
3860
|
}
|
|
3688
3861
|
|
|
3862
|
+
// ── Embedding model ──────────────────────────────────────────────────
|
|
3863
|
+
try {
|
|
3864
|
+
const { embed } = await import("@context-vault/core/index/embed");
|
|
3865
|
+
const vec = await embed("doctor check");
|
|
3866
|
+
if (vec && vec.length > 0) {
|
|
3867
|
+
console.log(
|
|
3868
|
+
` ${green("✓")} Embedding model ${dim(`(${vec.length} dimensions)`)}`,
|
|
3869
|
+
);
|
|
3870
|
+
} else {
|
|
3871
|
+
console.log(
|
|
3872
|
+
` ${yellow("!")} Embedding model unavailable — semantic search disabled (FTS-only)`,
|
|
3873
|
+
);
|
|
3874
|
+
console.log(
|
|
3875
|
+
` ${dim("Fix: run context-vault setup to download the model")}`,
|
|
3876
|
+
);
|
|
3877
|
+
}
|
|
3878
|
+
} catch {
|
|
3879
|
+
console.log(
|
|
3880
|
+
` ${yellow("!")} Embedding model unavailable — semantic search disabled (FTS-only)`,
|
|
3881
|
+
);
|
|
3882
|
+
console.log(
|
|
3883
|
+
` ${dim("Fix: run context-vault setup to download the model")}`,
|
|
3884
|
+
);
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
// ── DB/filesystem consistency ─────────────────────────────────────────
|
|
3888
|
+
if (db && existsSync(config.vaultDir)) {
|
|
3889
|
+
try {
|
|
3890
|
+
const totalRow = db.prepare("SELECT COUNT(*) as c FROM vault").get();
|
|
3891
|
+
const total = totalRow?.c ?? 0;
|
|
3892
|
+
if (total > 0) {
|
|
3893
|
+
const sampleRows = db
|
|
3894
|
+
.prepare("SELECT file_path FROM vault LIMIT 50")
|
|
3895
|
+
.all();
|
|
3896
|
+
let staleCount = 0;
|
|
3897
|
+
for (const row of sampleRows) {
|
|
3898
|
+
if (row.file_path && !existsSync(row.file_path)) {
|
|
3899
|
+
staleCount++;
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
if (staleCount > 0) {
|
|
3903
|
+
const pct = Math.round((staleCount / sampleRows.length) * 100);
|
|
3904
|
+
console.log(
|
|
3905
|
+
` ${yellow("!")} ${staleCount}/${sampleRows.length} sampled DB entries point to missing files (${pct}%)`,
|
|
3906
|
+
);
|
|
3907
|
+
console.log(
|
|
3908
|
+
` ${dim("Fix: run context-vault reindex to rebuild from vault files")}`,
|
|
3909
|
+
);
|
|
3910
|
+
allOk = false;
|
|
3911
|
+
} else {
|
|
3912
|
+
console.log(
|
|
3913
|
+
` ${green("✓")} DB/filesystem consistency ${dim(`(${total} entries, sample OK)`)}`,
|
|
3914
|
+
);
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
} catch {
|
|
3918
|
+
// non-critical — skip silently
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
// ── Auto-captured feedback entries ─────────────────────────────────────
|
|
3923
|
+
if (db) {
|
|
3924
|
+
try {
|
|
3925
|
+
const feedbackRow = db
|
|
3926
|
+
.prepare(
|
|
3927
|
+
`SELECT COUNT(*) as c FROM vault WHERE kind = 'feedback' AND tags LIKE '%"auto-captured"%'`,
|
|
3928
|
+
)
|
|
3929
|
+
.get();
|
|
3930
|
+
const feedbackCount = feedbackRow?.c ?? 0;
|
|
3931
|
+
if (feedbackCount > 0) {
|
|
3932
|
+
const recentRows = db
|
|
3933
|
+
.prepare(
|
|
3934
|
+
`SELECT title, created_at FROM vault WHERE kind = 'feedback' AND tags LIKE '%"auto-captured"%' ORDER BY created_at DESC LIMIT 3`,
|
|
3935
|
+
)
|
|
3936
|
+
.all();
|
|
3937
|
+
console.log(
|
|
3938
|
+
` ${yellow("!")} ${feedbackCount} auto-captured error${feedbackCount === 1 ? "" : "s"} in vault`,
|
|
3939
|
+
);
|
|
3940
|
+
for (const row of recentRows) {
|
|
3941
|
+
console.log(` ${dim(`${row.created_at} — ${row.title}`)}`);
|
|
3942
|
+
}
|
|
3943
|
+
console.log(
|
|
3944
|
+
` ${dim("Review: context-vault search --kind feedback --tag auto-captured")}`,
|
|
3945
|
+
);
|
|
3946
|
+
}
|
|
3947
|
+
} catch {
|
|
3948
|
+
// non-critical — skip silently
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
// Close DB if opened
|
|
3953
|
+
try {
|
|
3954
|
+
db?.close();
|
|
3955
|
+
} catch {}
|
|
3956
|
+
|
|
3689
3957
|
// ── Launcher (server.mjs) ─────────────────────────────────────────────
|
|
3690
3958
|
const launcherPath = join(HOME, ".context-mcp", "server.mjs");
|
|
3691
3959
|
if (existsSync(launcherPath)) {
|
|
@@ -3743,6 +4011,9 @@ async function runDoctor() {
|
|
|
3743
4011
|
// ── MCP tool configs ──────────────────────────────────────────────────────
|
|
3744
4012
|
console.log();
|
|
3745
4013
|
console.log(bold(" Tool Configurations"));
|
|
4014
|
+
let anyToolConfigured = false;
|
|
4015
|
+
|
|
4016
|
+
// Check Claude Code
|
|
3746
4017
|
const claudeConfigPath = join(HOME, ".claude.json");
|
|
3747
4018
|
if (existsSync(claudeConfigPath)) {
|
|
3748
4019
|
try {
|
|
@@ -3752,9 +4023,9 @@ async function runDoctor() {
|
|
|
3752
4023
|
const srv = servers["context-vault"];
|
|
3753
4024
|
const cmd = [srv.command, ...(srv.args || [])].join(" ");
|
|
3754
4025
|
console.log(` ${green("+")} Claude Code: ${dim(cmd)}`);
|
|
4026
|
+
anyToolConfigured = true;
|
|
3755
4027
|
} else {
|
|
3756
|
-
console.log(` ${dim("-")} Claude Code:
|
|
3757
|
-
console.log(` ${dim("Fix: run context-vault setup")}`);
|
|
4028
|
+
console.log(` ${dim("-")} Claude Code: not configured`);
|
|
3758
4029
|
}
|
|
3759
4030
|
} catch {
|
|
3760
4031
|
console.log(
|
|
@@ -3765,21 +4036,191 @@ async function runDoctor() {
|
|
|
3765
4036
|
console.log(` ${dim("-")} Claude Code: ~/.claude.json not found`);
|
|
3766
4037
|
}
|
|
3767
4038
|
|
|
4039
|
+
// Check all JSON-configured tools
|
|
4040
|
+
for (const tool of TOOLS.filter((t) => t.configType === "json")) {
|
|
4041
|
+
const cfgPath = tool.configPath;
|
|
4042
|
+
if (!cfgPath || !existsSync(cfgPath)) {
|
|
4043
|
+
continue; // tool not installed — skip silently
|
|
4044
|
+
}
|
|
4045
|
+
try {
|
|
4046
|
+
const toolConfig = JSON.parse(readFileSync(cfgPath, "utf-8"));
|
|
4047
|
+
const servers = toolConfig?.[tool.configKey] || {};
|
|
4048
|
+
if (servers["context-vault"]) {
|
|
4049
|
+
const srv = servers["context-vault"];
|
|
4050
|
+
const cmd = [srv.command, ...(srv.args || [])].join(" ");
|
|
4051
|
+
console.log(` ${green("+")} ${tool.name}: ${dim(cmd)}`);
|
|
4052
|
+
anyToolConfigured = true;
|
|
4053
|
+
} else if (servers["context-mcp"]) {
|
|
4054
|
+
console.log(
|
|
4055
|
+
` ${yellow("!")} ${tool.name}: using old name "context-mcp"`,
|
|
4056
|
+
);
|
|
4057
|
+
console.log(` ${dim("Fix: run context-vault setup to update")}`);
|
|
4058
|
+
anyToolConfigured = true;
|
|
4059
|
+
}
|
|
4060
|
+
} catch {
|
|
4061
|
+
// config exists but unreadable — skip
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
// Check Codex
|
|
4066
|
+
try {
|
|
4067
|
+
const codexCheck = execSync("codex mcp list 2>/dev/null", {
|
|
4068
|
+
encoding: "utf-8",
|
|
4069
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4070
|
+
timeout: 5000,
|
|
4071
|
+
});
|
|
4072
|
+
if (codexCheck.includes("context-vault")) {
|
|
4073
|
+
console.log(` ${green("+")} Codex: ${dim("configured")}`);
|
|
4074
|
+
anyToolConfigured = true;
|
|
4075
|
+
}
|
|
4076
|
+
} catch {
|
|
4077
|
+
// codex not installed or not configured — skip
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4080
|
+
if (!anyToolConfigured) {
|
|
4081
|
+
console.log(` ${yellow("!")} No AI tools have context-vault configured`);
|
|
4082
|
+
console.log(` ${dim("Fix: run context-vault setup")}`);
|
|
4083
|
+
allOk = false;
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
// ── Claude Code hooks ──────────────────────────────────────────────────────
|
|
4087
|
+
console.log();
|
|
4088
|
+
console.log(bold(" Claude Code Hooks"));
|
|
4089
|
+
const settingsPath = claudeSettingsPath();
|
|
4090
|
+
if (existsSync(settingsPath)) {
|
|
4091
|
+
try {
|
|
4092
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
4093
|
+
const hooks = settings.hooks || {};
|
|
4094
|
+
let hookCount = 0;
|
|
4095
|
+
let staleHookCount = 0;
|
|
4096
|
+
|
|
4097
|
+
// Check recall hook
|
|
4098
|
+
const recallHooks = (hooks.UserPromptSubmit || []).filter((h) =>
|
|
4099
|
+
h.hooks?.some((hh) => hh.command?.includes("context-vault recall")),
|
|
4100
|
+
);
|
|
4101
|
+
if (recallHooks.length > 0) {
|
|
4102
|
+
console.log(` ${green("+")} Recall hook (UserPromptSubmit)`);
|
|
4103
|
+
hookCount++;
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
// Check session-end hooks
|
|
4107
|
+
const sessionHooks = (hooks.SessionEnd || []).filter((h) =>
|
|
4108
|
+
h.hooks?.some(
|
|
4109
|
+
(hh) =>
|
|
4110
|
+
hh.command?.includes("session-end.mjs") ||
|
|
4111
|
+
hh.command?.includes("context-vault session-end"),
|
|
4112
|
+
),
|
|
4113
|
+
);
|
|
4114
|
+
if (sessionHooks.length > 0) {
|
|
4115
|
+
// Check if using stale absolute path
|
|
4116
|
+
const hasStale = sessionHooks.some((h) =>
|
|
4117
|
+
h.hooks?.some(
|
|
4118
|
+
(hh) =>
|
|
4119
|
+
hh.command?.includes("session-end.mjs") &&
|
|
4120
|
+
!hh.command?.includes("context-vault session-end"),
|
|
4121
|
+
),
|
|
4122
|
+
);
|
|
4123
|
+
if (hasStale) {
|
|
4124
|
+
const cmd = sessionHooks[0]?.hooks?.[0]?.command || "";
|
|
4125
|
+
const pathMatch = cmd.match(/node\s+(.+session-end\.mjs)/);
|
|
4126
|
+
const hookPath = pathMatch ? pathMatch[1] : "";
|
|
4127
|
+
const pathExists = hookPath && existsSync(hookPath);
|
|
4128
|
+
if (!pathExists) {
|
|
4129
|
+
console.log(
|
|
4130
|
+
` ${red("✘")} Session capture hook: stale path ${dim(hookPath || "(unknown)")}`,
|
|
4131
|
+
);
|
|
4132
|
+
console.log(
|
|
4133
|
+
` ${dim("Fix: run context-vault hooks install to update")}`,
|
|
4134
|
+
);
|
|
4135
|
+
staleHookCount++;
|
|
4136
|
+
allOk = false;
|
|
4137
|
+
} else {
|
|
4138
|
+
console.log(
|
|
4139
|
+
` ${yellow("!")} Session capture hook: uses absolute path (fragile)`,
|
|
4140
|
+
);
|
|
4141
|
+
console.log(
|
|
4142
|
+
` ${dim("Fix: run context-vault hooks install to update to CLI command")}`,
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
} else {
|
|
4146
|
+
console.log(` ${green("+")} Session capture hook (SessionEnd)`);
|
|
4147
|
+
}
|
|
4148
|
+
hookCount++;
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
// Check flush hook
|
|
4152
|
+
const flushHooks = (hooks.SessionEnd || []).filter((h) =>
|
|
4153
|
+
h.hooks?.some((hh) => hh.command?.includes("context-vault flush")),
|
|
4154
|
+
);
|
|
4155
|
+
if (flushHooks.length > 0) {
|
|
4156
|
+
console.log(` ${green("+")} Flush hook (SessionEnd)`);
|
|
4157
|
+
hookCount++;
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
// Check post-tool-call hooks
|
|
4161
|
+
const ptcHooks = (hooks.PostToolCall || []).filter((h) =>
|
|
4162
|
+
h.hooks?.some(
|
|
4163
|
+
(hh) =>
|
|
4164
|
+
hh.command?.includes("post-tool-call.mjs") ||
|
|
4165
|
+
hh.command?.includes("context-vault post-tool-call"),
|
|
4166
|
+
),
|
|
4167
|
+
);
|
|
4168
|
+
if (ptcHooks.length > 0) {
|
|
4169
|
+
const hasStale = ptcHooks.some((h) =>
|
|
4170
|
+
h.hooks?.some(
|
|
4171
|
+
(hh) =>
|
|
4172
|
+
hh.command?.includes("post-tool-call.mjs") &&
|
|
4173
|
+
!hh.command?.includes("context-vault post-tool-call"),
|
|
4174
|
+
),
|
|
4175
|
+
);
|
|
4176
|
+
if (hasStale) {
|
|
4177
|
+
const cmd = ptcHooks[0]?.hooks?.[0]?.command || "";
|
|
4178
|
+
const pathMatch = cmd.match(/node\s+(.+post-tool-call\.mjs)/);
|
|
4179
|
+
const hookPath = pathMatch ? pathMatch[1] : "";
|
|
4180
|
+
const pathExists = hookPath && existsSync(hookPath);
|
|
4181
|
+
if (!pathExists) {
|
|
4182
|
+
console.log(
|
|
4183
|
+
` ${red("✘")} Auto-capture hook: stale path ${dim(hookPath || "(unknown)")}`,
|
|
4184
|
+
);
|
|
4185
|
+
console.log(
|
|
4186
|
+
` ${dim("Fix: run context-vault hooks install to update")}`,
|
|
4187
|
+
);
|
|
4188
|
+
staleHookCount++;
|
|
4189
|
+
allOk = false;
|
|
4190
|
+
} else {
|
|
4191
|
+
console.log(
|
|
4192
|
+
` ${yellow("!")} Auto-capture hook: uses absolute path (fragile)`,
|
|
4193
|
+
);
|
|
4194
|
+
console.log(
|
|
4195
|
+
` ${dim("Fix: run context-vault hooks install to update to CLI command")}`,
|
|
4196
|
+
);
|
|
4197
|
+
}
|
|
4198
|
+
} else {
|
|
4199
|
+
console.log(` ${green("+")} Auto-capture hook (PostToolCall)`);
|
|
4200
|
+
}
|
|
4201
|
+
hookCount++;
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4204
|
+
if (hookCount === 0) {
|
|
4205
|
+
console.log(` ${dim("-")} No context-vault hooks installed`);
|
|
4206
|
+
console.log(` ${dim("Optional: run context-vault hooks install")}`);
|
|
4207
|
+
}
|
|
4208
|
+
} catch {
|
|
4209
|
+
console.log(` ${yellow("!")} Could not read ${settingsPath}`);
|
|
4210
|
+
}
|
|
4211
|
+
} else {
|
|
4212
|
+
console.log(` ${dim("-")} No Claude Code settings found`);
|
|
4213
|
+
}
|
|
4214
|
+
|
|
3768
4215
|
// ── Summary ───────────────────────────────────────────────────────────────
|
|
3769
4216
|
console.log();
|
|
3770
4217
|
if (allOk) {
|
|
3771
4218
|
console.log(
|
|
3772
|
-
` ${green("
|
|
3773
|
-
);
|
|
3774
|
-
console.log(
|
|
3775
|
-
` ${dim("context-vault setup")} — reconfigure tool integrations`,
|
|
4219
|
+
` ${green("All checks passed.")} If the MCP server still fails, try restarting your AI tool.`,
|
|
3776
4220
|
);
|
|
3777
4221
|
} else {
|
|
3778
4222
|
console.log(
|
|
3779
|
-
` ${yellow("Some issues found.")} Address the items
|
|
3780
|
-
);
|
|
3781
|
-
console.log(
|
|
3782
|
-
` ${dim("context-vault setup")} — reconfigure and repair installation`,
|
|
4223
|
+
` ${yellow("Some issues found.")} Address the items marked with ${red("✘")} above.`,
|
|
3783
4224
|
);
|
|
3784
4225
|
}
|
|
3785
4226
|
console.log();
|
|
@@ -4117,7 +4558,7 @@ async function main() {
|
|
|
4117
4558
|
}
|
|
4118
4559
|
|
|
4119
4560
|
if (flags.has("--help") || command === "help") {
|
|
4120
|
-
showHelp();
|
|
4561
|
+
showHelp(flags.has("--all"));
|
|
4121
4562
|
return;
|
|
4122
4563
|
}
|
|
4123
4564
|
|
|
@@ -4162,6 +4603,12 @@ async function main() {
|
|
|
4162
4603
|
case "session-capture":
|
|
4163
4604
|
await runSessionCapture();
|
|
4164
4605
|
break;
|
|
4606
|
+
case "session-end":
|
|
4607
|
+
await runSessionEnd();
|
|
4608
|
+
break;
|
|
4609
|
+
case "post-tool-call":
|
|
4610
|
+
await runPostToolCall();
|
|
4611
|
+
break;
|
|
4165
4612
|
case "save":
|
|
4166
4613
|
await runSave();
|
|
4167
4614
|
break;
|
|
@@ -4183,6 +4630,9 @@ async function main() {
|
|
|
4183
4630
|
case "reindex":
|
|
4184
4631
|
await runReindex();
|
|
4185
4632
|
break;
|
|
4633
|
+
case "migrate-dirs":
|
|
4634
|
+
await runMigrateDirs();
|
|
4635
|
+
break;
|
|
4186
4636
|
case "prune":
|
|
4187
4637
|
await runPrune();
|
|
4188
4638
|
break;
|