context-vault 3.5.1 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/vault-error-hook.mjs +106 -0
- package/assets/vault-recall-hook.mjs +67 -0
- package/bin/cli.js +607 -2
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +4 -0
- package/dist/register-tools.js.map +1 -1
- package/dist/tools/clear-context.d.ts +7 -3
- package/dist/tools/clear-context.d.ts.map +1 -1
- package/dist/tools/clear-context.js +157 -8
- package/dist/tools/clear-context.js.map +1 -1
- package/dist/tools/context-status.d.ts.map +1 -1
- package/dist/tools/context-status.js +42 -0
- package/dist/tools/context-status.js.map +1 -1
- package/dist/tools/get-context.d.ts.map +1 -1
- package/dist/tools/get-context.js +66 -1
- package/dist/tools/get-context.js.map +1 -1
- package/dist/tools/recall.d.ts +25 -0
- package/dist/tools/recall.d.ts.map +1 -0
- package/dist/tools/recall.js +257 -0
- package/dist/tools/recall.js.map +1 -0
- package/dist/tools/save-context.d.ts.map +1 -1
- package/dist/tools/save-context.js +20 -0
- package/dist/tools/save-context.js.map +1 -1
- package/dist/tools/session-end.d.ts +20 -0
- package/dist/tools/session-end.d.ts.map +1 -0
- package/dist/tools/session-end.js +288 -0
- package/dist/tools/session-end.js.map +1 -0
- package/dist/tools/session-start.d.ts +2 -1
- package/dist/tools/session-start.d.ts.map +1 -1
- package/dist/tools/session-start.js +16 -5
- package/dist/tools/session-start.js.map +1 -1
- package/node_modules/@context-vault/core/package.json +1 -1
- package/package.json +2 -2
- package/src/register-tools.ts +4 -0
- package/src/tools/clear-context.ts +195 -10
- package/src/tools/context-status.ts +65 -0
- package/src/tools/get-context.ts +86 -1
- package/src/tools/recall.ts +307 -0
- package/src/tools/save-context.ts +25 -0
- package/src/tools/session-end.ts +338 -0
- package/src/tools/session-start.ts +18 -5
package/bin/cli.js
CHANGED
|
@@ -396,6 +396,7 @@ ${bold('Commands:')}
|
|
|
396
396
|
${cyan('ingest')} <url> Fetch URL and save as vault entry
|
|
397
397
|
${cyan('ingest-project')} <path> Scan project directory and register as project entity
|
|
398
398
|
${cyan('reindex')} Rebuild search index from knowledge files
|
|
399
|
+
${cyan('sync')} [dir] Index .context/ files into vault DB (use --dry-run to preview)
|
|
399
400
|
${cyan('migrate-dirs')} [--dry-run] Rename plural vault dirs to singular (post-2.18.0)
|
|
400
401
|
${cyan('archive')} Archive old ephemeral/event entries (use --dry-run to preview)
|
|
401
402
|
${cyan('restore')} <id> Restore an archived entry back into the vault
|
|
@@ -428,6 +429,9 @@ ${bold('Commands:')}
|
|
|
428
429
|
--force Overwrite existing config without confirmation
|
|
429
430
|
--skip-embeddings Skip embedding model download (FTS-only mode)
|
|
430
431
|
--dry-run Show what setup would do without writing anything
|
|
432
|
+
--upgrade Upgrade installed agent rules to the latest bundled version
|
|
433
|
+
--no-rules Skip agent rules installation during setup
|
|
434
|
+
--no-hooks Skip recall/error hook installation during setup
|
|
431
435
|
`);
|
|
432
436
|
}
|
|
433
437
|
|
|
@@ -445,6 +449,106 @@ async function runSetup() {
|
|
|
445
449
|
}
|
|
446
450
|
console.log();
|
|
447
451
|
|
|
452
|
+
// --upgrade: only upgrade agent rules, then exit
|
|
453
|
+
if (flags.has('--upgrade')) {
|
|
454
|
+
console.log(dim(' Checking agent rules for updates...\n'));
|
|
455
|
+
const bundled = loadAgentRules();
|
|
456
|
+
if (!bundled) {
|
|
457
|
+
console.log(` ${yellow('!')} Agent rules file not found in package.\n`);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const bundledVersion = extractRulesVersion(bundled);
|
|
461
|
+
|
|
462
|
+
// Check all known tool paths (not just detected tools, since a tool may have been
|
|
463
|
+
// uninstalled but its rules file still exists)
|
|
464
|
+
const allToolsWithRules = TOOLS.filter((t) => t.rulesPath);
|
|
465
|
+
let found = 0;
|
|
466
|
+
let upgraded = 0;
|
|
467
|
+
const upgradeable = [];
|
|
468
|
+
|
|
469
|
+
for (const tool of allToolsWithRules) {
|
|
470
|
+
const installed = getInstalledRulesForTool(tool);
|
|
471
|
+
if (!installed) continue;
|
|
472
|
+
found++;
|
|
473
|
+
|
|
474
|
+
const installedVersion = extractRulesVersion(installed);
|
|
475
|
+
if (installed.trim() === bundled.trim()) {
|
|
476
|
+
console.log(` ${green('✓')} ${tool.name}: up to date${bundledVersion ? ` (v${bundledVersion})` : ''}`);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
upgradeable.push({ tool, installed, installedVersion });
|
|
481
|
+
console.log(
|
|
482
|
+
` ${yellow('!')} ${tool.name}: ${installedVersion ? `v${installedVersion}` : 'unknown version'} → ${bundledVersion ? `v${bundledVersion}` : 'bundled'}`
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
// Show a compact diff
|
|
486
|
+
const installedLines = installed.split('\n');
|
|
487
|
+
const bundledLines = bundled.split('\n');
|
|
488
|
+
const maxLines = Math.max(installedLines.length, bundledLines.length);
|
|
489
|
+
let diffLines = 0;
|
|
490
|
+
for (let i = 0; i < maxLines; i++) {
|
|
491
|
+
const a = installedLines[i];
|
|
492
|
+
const b = bundledLines[i];
|
|
493
|
+
if (a === b) continue;
|
|
494
|
+
if (diffLines === 0) console.log();
|
|
495
|
+
if (diffLines >= 20) {
|
|
496
|
+
console.log(dim(` ... and more changes`));
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
if (a === undefined) {
|
|
500
|
+
console.log(` ${green('+')} ${b}`);
|
|
501
|
+
} else if (b === undefined) {
|
|
502
|
+
console.log(` ${red('-')} ${a}`);
|
|
503
|
+
} else {
|
|
504
|
+
console.log(` ${red('-')} ${a}`);
|
|
505
|
+
console.log(` ${green('+')} ${b}`);
|
|
506
|
+
}
|
|
507
|
+
diffLines++;
|
|
508
|
+
}
|
|
509
|
+
console.log();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (found === 0) {
|
|
513
|
+
console.log(` ${yellow('!')} No installed rules found. Run ${cyan('context-vault rules install')} first.\n`);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (upgradeable.length === 0) {
|
|
518
|
+
console.log(`\n ${green('✓')} All rules are up to date.\n`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!isDryRun) {
|
|
523
|
+
const answer = isNonInteractive
|
|
524
|
+
? 'Y'
|
|
525
|
+
: await prompt(` Upgrade ${upgradeable.length} rules file(s)? (Y/n):`, 'Y');
|
|
526
|
+
if (answer.toLowerCase() === 'n') {
|
|
527
|
+
console.log(dim(' Skipped.\n'));
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
for (const { tool } of upgradeable) {
|
|
532
|
+
try {
|
|
533
|
+
installAgentRulesForTool(tool, bundled);
|
|
534
|
+
console.log(` ${green('+')} ${tool.name} — upgraded`);
|
|
535
|
+
upgraded++;
|
|
536
|
+
} catch (e) {
|
|
537
|
+
console.log(` ${red('x')} ${tool.name} — ${e.message}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
console.log(dim(` [dry-run] Would upgrade ${upgradeable.length} rules file(s).`));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
console.log();
|
|
545
|
+
if (upgraded > 0) {
|
|
546
|
+
console.log(dim(' Restart your AI tools to apply the updated rules.'));
|
|
547
|
+
console.log();
|
|
548
|
+
}
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
448
552
|
// Check for existing installation
|
|
449
553
|
const existingConfig = join(HOME, '.context-mcp', 'config.json');
|
|
450
554
|
if (existsSync(existingConfig) && !isNonInteractive && !isDryRun) {
|
|
@@ -1032,7 +1136,7 @@ async function runSetup() {
|
|
|
1032
1136
|
|
|
1033
1137
|
if (claudeConfigured) {
|
|
1034
1138
|
if (isDryRun) {
|
|
1035
|
-
console.log(` ${yellow('[dry-run]')} Would install Claude Code hooks (memory recall, session capture, auto-capture)`);
|
|
1139
|
+
console.log(` ${yellow('[dry-run]')} Would install Claude Code hooks (memory recall, session capture, auto-capture, vault recall, error recall)`);
|
|
1036
1140
|
console.log(` ${yellow('[dry-run]')} Would install Claude Code skills (compile-context, vault-setup)`);
|
|
1037
1141
|
} else {
|
|
1038
1142
|
// Bundled hooks prompt: one Y/n for all three hooks
|
|
@@ -1063,6 +1167,20 @@ async function runSetup() {
|
|
|
1063
1167
|
} catch (e) {
|
|
1064
1168
|
console.log(` ${red('x')} Auto-capture hook failed: ${e.message}`);
|
|
1065
1169
|
}
|
|
1170
|
+
if (!flags.has('--no-hooks')) {
|
|
1171
|
+
try {
|
|
1172
|
+
const recallInstalled = installRecallHook();
|
|
1173
|
+
if (recallInstalled) console.log(` ${green('+')} Vault recall hook installed`);
|
|
1174
|
+
} catch (e) {
|
|
1175
|
+
console.log(` ${red('x')} Recall hook failed: ${e.message}`);
|
|
1176
|
+
}
|
|
1177
|
+
try {
|
|
1178
|
+
const errorInstalled = installErrorHook();
|
|
1179
|
+
if (errorInstalled) console.log(` ${green('+')} Vault error hook installed`);
|
|
1180
|
+
} catch (e) {
|
|
1181
|
+
console.log(` ${red('x')} Error hook failed: ${e.message}`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1066
1184
|
} else {
|
|
1067
1185
|
console.log(dim(` Hooks skipped. Install later: context-vault hooks install`));
|
|
1068
1186
|
}
|
|
@@ -2040,6 +2158,269 @@ async function runReindex() {
|
|
|
2040
2158
|
}
|
|
2041
2159
|
}
|
|
2042
2160
|
|
|
2161
|
+
async function runSync() {
|
|
2162
|
+
const dryRun = flags.has('--dry-run');
|
|
2163
|
+
const positional = args.slice(1).find((a) => !a.startsWith('--'));
|
|
2164
|
+
const scanDir = positional ? resolve(positional) : process.cwd();
|
|
2165
|
+
|
|
2166
|
+
const contextDir = join(scanDir, '.context');
|
|
2167
|
+
if (!existsSync(contextDir)) {
|
|
2168
|
+
console.error(red(`No .context/ directory found in ${scanDir}`));
|
|
2169
|
+
console.error(dim('The .context/ directory is created automatically when save_context is called from a workspace.'));
|
|
2170
|
+
process.exit(1);
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
console.log(dim(dryRun ? 'Scanning .context/ (dry run)...' : 'Syncing .context/ to vault...'));
|
|
2174
|
+
|
|
2175
|
+
const { resolveConfig } = await import('@context-vault/core/config');
|
|
2176
|
+
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
2177
|
+
await import('@context-vault/core/db');
|
|
2178
|
+
const { embed } = await import('@context-vault/core/embed');
|
|
2179
|
+
const { parseFrontmatter, parseEntryFromMarkdown } = await import('@context-vault/core/frontmatter');
|
|
2180
|
+
const { categoryFor, defaultTierFor } = await import('@context-vault/core/categories');
|
|
2181
|
+
const { dirToKind, walkDir } = await import('@context-vault/core/files');
|
|
2182
|
+
const { shouldIndex } = await import('@context-vault/core/indexing');
|
|
2183
|
+
const { DEFAULT_INDEXING } = await import('@context-vault/core/constants');
|
|
2184
|
+
|
|
2185
|
+
const config = resolveConfig();
|
|
2186
|
+
if (!config.vaultDirExists) {
|
|
2187
|
+
console.error(red(`Vault directory not found: ${config.vaultDir}`));
|
|
2188
|
+
console.error('Run ' + cyan('context-vault setup') + ' to configure.');
|
|
2189
|
+
process.exit(1);
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
const db = await initDatabase(config.dbPath);
|
|
2193
|
+
const stmts = prepareStatements(db);
|
|
2194
|
+
const ixConfig = config.indexing ?? DEFAULT_INDEXING;
|
|
2195
|
+
|
|
2196
|
+
let synced = 0;
|
|
2197
|
+
let alreadyIndexed = 0;
|
|
2198
|
+
let updated = 0;
|
|
2199
|
+
let errors = 0;
|
|
2200
|
+
let skippedIndexing = 0;
|
|
2201
|
+
|
|
2202
|
+
// Discover kind directories inside .context/
|
|
2203
|
+
let kindDirs;
|
|
2204
|
+
try {
|
|
2205
|
+
kindDirs = readdirSync(contextDir, { withFileTypes: true })
|
|
2206
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith('.') && !d.name.startsWith('_'));
|
|
2207
|
+
} catch (e) {
|
|
2208
|
+
console.error(red(`Failed to read .context/: ${e.message}`));
|
|
2209
|
+
db.close();
|
|
2210
|
+
process.exit(1);
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
const pendingEmbeds = [];
|
|
2214
|
+
|
|
2215
|
+
if (!dryRun) db.exec('BEGIN');
|
|
2216
|
+
try {
|
|
2217
|
+
for (const kindEntry of kindDirs) {
|
|
2218
|
+
const kind = dirToKind(kindEntry.name);
|
|
2219
|
+
const kindDir = join(contextDir, kindEntry.name);
|
|
2220
|
+
const mdFiles = walkDir(kindDir).filter((f) => f.filePath.endsWith('.md'));
|
|
2221
|
+
|
|
2222
|
+
for (const { filePath, relDir } of mdFiles) {
|
|
2223
|
+
let raw;
|
|
2224
|
+
try {
|
|
2225
|
+
raw = readFileSync(filePath, 'utf-8');
|
|
2226
|
+
} catch (e) {
|
|
2227
|
+
console.error(dim(` skip: could not read ${filePath}: ${e.message}`));
|
|
2228
|
+
errors++;
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
if (!raw.startsWith('---\n')) {
|
|
2233
|
+
console.error(dim(` skip (no frontmatter): ${filePath}`));
|
|
2234
|
+
errors++;
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
const { meta: fmMeta, body: rawBody } = parseFrontmatter(raw);
|
|
2239
|
+
const entryId = fmMeta.id;
|
|
2240
|
+
if (!entryId) {
|
|
2241
|
+
console.error(dim(` skip (no id in frontmatter): ${filePath}`));
|
|
2242
|
+
errors++;
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
const parsed = parseEntryFromMarkdown(kind, rawBody, fmMeta);
|
|
2247
|
+
const category = categoryFor(kind);
|
|
2248
|
+
|
|
2249
|
+
// Check if entry exists in DB
|
|
2250
|
+
const existing = stmts.getEntryById.get(entryId);
|
|
2251
|
+
|
|
2252
|
+
if (existing) {
|
|
2253
|
+
// Check if content differs
|
|
2254
|
+
const bodyChanged = existing.body !== parsed.body;
|
|
2255
|
+
const titleChanged = (parsed.title || null) !== (existing.title || null);
|
|
2256
|
+
|
|
2257
|
+
if (!bodyChanged && !titleChanged) {
|
|
2258
|
+
alreadyIndexed++;
|
|
2259
|
+
continue;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
if (dryRun) {
|
|
2263
|
+
console.log(` ${yellow('~')} would update: ${entryId} (${parsed.title || '(untitled)'})`);
|
|
2264
|
+
updated++;
|
|
2265
|
+
continue;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// Update existing entry
|
|
2269
|
+
const tagsJson = fmMeta.tags ? JSON.stringify(fmMeta.tags) : null;
|
|
2270
|
+
const meta = { ...(parsed.meta || {}) };
|
|
2271
|
+
if (relDir) meta.folder = relDir;
|
|
2272
|
+
const metaJson = Object.keys(meta).length ? JSON.stringify(meta) : null;
|
|
2273
|
+
const identity_key = fmMeta.identity_key || null;
|
|
2274
|
+
const expires_at = fmMeta.expires_at || null;
|
|
2275
|
+
|
|
2276
|
+
stmts.updateEntry.run(
|
|
2277
|
+
parsed.title || null,
|
|
2278
|
+
parsed.body,
|
|
2279
|
+
metaJson,
|
|
2280
|
+
tagsJson,
|
|
2281
|
+
fmMeta.source || 'file',
|
|
2282
|
+
category,
|
|
2283
|
+
identity_key,
|
|
2284
|
+
expires_at,
|
|
2285
|
+
existing.file_path
|
|
2286
|
+
);
|
|
2287
|
+
|
|
2288
|
+
const entryIndexed = shouldIndex(
|
|
2289
|
+
{ kind, category, bodyLength: parsed.body.length },
|
|
2290
|
+
ixConfig
|
|
2291
|
+
);
|
|
2292
|
+
|
|
2293
|
+
if (entryIndexed && category !== 'event') {
|
|
2294
|
+
const rowidResult = stmts.getRowid.get(entryId);
|
|
2295
|
+
if (rowidResult?.rowid) {
|
|
2296
|
+
const embeddingText = [parsed.title, parsed.body].filter(Boolean).join(' ');
|
|
2297
|
+
pendingEmbeds.push({ rowid: rowidResult.rowid, text: embeddingText });
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
updated++;
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// Entry not in DB: index it
|
|
2306
|
+
const entryIndexed = shouldIndex(
|
|
2307
|
+
{ kind, category, bodyLength: parsed.body.length },
|
|
2308
|
+
ixConfig
|
|
2309
|
+
);
|
|
2310
|
+
|
|
2311
|
+
if (dryRun) {
|
|
2312
|
+
if (entryIndexed) {
|
|
2313
|
+
console.log(` ${green('+')} would sync: ${entryId} (${parsed.title || '(untitled)'})`);
|
|
2314
|
+
synced++;
|
|
2315
|
+
} else {
|
|
2316
|
+
console.log(` ${dim('o')} would skip indexing: ${entryId}`);
|
|
2317
|
+
skippedIndexing++;
|
|
2318
|
+
}
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
const tagsJson = fmMeta.tags ? JSON.stringify(fmMeta.tags) : null;
|
|
2323
|
+
const meta = { ...(parsed.meta || {}) };
|
|
2324
|
+
if (relDir) meta.folder = relDir;
|
|
2325
|
+
const metaJson = Object.keys(meta).length ? JSON.stringify(meta) : null;
|
|
2326
|
+
const created = fmMeta.created || new Date().toISOString();
|
|
2327
|
+
const identity_key = fmMeta.identity_key || null;
|
|
2328
|
+
const expires_at = fmMeta.expires_at || null;
|
|
2329
|
+
const effectiveTier = fmMeta.tier || defaultTierFor(kind);
|
|
2330
|
+
|
|
2331
|
+
// The entry should point to the vault file path (if it exists there), else use the .context path
|
|
2332
|
+
const vaultFilePath = existing?.file_path || fmMeta.file_path || filePath;
|
|
2333
|
+
|
|
2334
|
+
try {
|
|
2335
|
+
const upsertEntry = db.prepare(
|
|
2336
|
+
`INSERT OR IGNORE INTO vault (id, kind, category, title, body, meta, tags, source, file_path, identity_key, expires_at, created_at, updated_at, tier, indexed) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2337
|
+
);
|
|
2338
|
+
const result = upsertEntry.run(
|
|
2339
|
+
entryId,
|
|
2340
|
+
kind,
|
|
2341
|
+
category,
|
|
2342
|
+
parsed.title || null,
|
|
2343
|
+
parsed.body,
|
|
2344
|
+
metaJson,
|
|
2345
|
+
tagsJson,
|
|
2346
|
+
fmMeta.source || 'file',
|
|
2347
|
+
vaultFilePath,
|
|
2348
|
+
identity_key,
|
|
2349
|
+
expires_at,
|
|
2350
|
+
created,
|
|
2351
|
+
fmMeta.updated || created,
|
|
2352
|
+
effectiveTier,
|
|
2353
|
+
entryIndexed ? 1 : 0
|
|
2354
|
+
);
|
|
2355
|
+
|
|
2356
|
+
if (result.changes > 0) {
|
|
2357
|
+
if (entryIndexed && category !== 'event') {
|
|
2358
|
+
const rowidResult = stmts.getRowid.get(entryId);
|
|
2359
|
+
if (rowidResult?.rowid) {
|
|
2360
|
+
const embeddingText = [parsed.title, parsed.body].filter(Boolean).join(' ');
|
|
2361
|
+
pendingEmbeds.push({ rowid: rowidResult.rowid, text: embeddingText });
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
if (!entryIndexed) skippedIndexing++;
|
|
2365
|
+
synced++;
|
|
2366
|
+
} else {
|
|
2367
|
+
alreadyIndexed++;
|
|
2368
|
+
}
|
|
2369
|
+
} catch (e) {
|
|
2370
|
+
console.error(dim(` error indexing ${entryId}: ${e.message}`));
|
|
2371
|
+
errors++;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// Generate embeddings in batch
|
|
2377
|
+
if (!dryRun && pendingEmbeds.length > 0) {
|
|
2378
|
+
const { embedBatch: batchEmbed } = await import('@context-vault/core/embed');
|
|
2379
|
+
const BATCH_SIZE = 32;
|
|
2380
|
+
for (let i = 0; i < pendingEmbeds.length; i += BATCH_SIZE) {
|
|
2381
|
+
const batch = pendingEmbeds.slice(i, i + BATCH_SIZE);
|
|
2382
|
+
const texts = batch.map((b) => b.text);
|
|
2383
|
+
try {
|
|
2384
|
+
const embeddings = await batchEmbed(texts);
|
|
2385
|
+
for (let j = 0; j < batch.length; j++) {
|
|
2386
|
+
if (embeddings[j]) {
|
|
2387
|
+
try { deleteVec(stmts, batch[j].rowid); } catch {}
|
|
2388
|
+
insertVec(stmts, batch[j].rowid, embeddings[j]);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
} catch (e) {
|
|
2392
|
+
console.warn(dim(` embedding batch failed: ${e.message}`));
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
if (!dryRun) db.exec('COMMIT');
|
|
2398
|
+
} catch (e) {
|
|
2399
|
+
if (!dryRun) {
|
|
2400
|
+
try { db.exec('ROLLBACK'); } catch {}
|
|
2401
|
+
}
|
|
2402
|
+
throw e;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
db.close();
|
|
2406
|
+
|
|
2407
|
+
if (dryRun) {
|
|
2408
|
+
console.log(yellow('Dry run results (no changes made):'));
|
|
2409
|
+
console.log(` Would sync: ${synced}`);
|
|
2410
|
+
console.log(` Would update: ${updated}`);
|
|
2411
|
+
console.log(` Already indexed: ${alreadyIndexed}`);
|
|
2412
|
+
if (skippedIndexing) console.log(` Would skip indexing: ${skippedIndexing}`);
|
|
2413
|
+
if (errors) console.log(` ${red('Errors:')} ${errors}`);
|
|
2414
|
+
} else {
|
|
2415
|
+
console.log(green('Sync complete'));
|
|
2416
|
+
console.log(` ${green('+')} ${synced} synced`);
|
|
2417
|
+
if (updated) console.log(` ${yellow('~')} ${updated} updated`);
|
|
2418
|
+
console.log(` ${dim('.')} ${alreadyIndexed} already indexed`);
|
|
2419
|
+
if (skippedIndexing) console.log(` ${dim('o')} ${skippedIndexing} skipped indexing`);
|
|
2420
|
+
if (errors) console.log(` ${red('!')} ${errors} errors`);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2043
2424
|
async function runMigrateDirs() {
|
|
2044
2425
|
const dryRun = flags.has('--dry-run');
|
|
2045
2426
|
|
|
@@ -2542,7 +2923,9 @@ async function runUninstall() {
|
|
|
2542
2923
|
const captureRemoved = removeSessionCaptureHook();
|
|
2543
2924
|
const flushRemoved = removeSessionEndHook();
|
|
2544
2925
|
const autoCaptureRemoved = removePostToolCallHook();
|
|
2545
|
-
|
|
2926
|
+
const recallHookRemoved = removeRecallHook();
|
|
2927
|
+
const errorHookRemoved = removeErrorHook();
|
|
2928
|
+
if (recallRemoved || captureRemoved || flushRemoved || autoCaptureRemoved || recallHookRemoved || errorHookRemoved) {
|
|
2546
2929
|
console.log(` ${green('+')} Removed Claude Code hooks`);
|
|
2547
2930
|
} else {
|
|
2548
2931
|
console.log(` ${dim('-')} No Claude Code hooks to remove`);
|
|
@@ -4280,6 +4663,35 @@ function loadAgentRules() {
|
|
|
4280
4663
|
return readFileSync(rulesPath, 'utf-8');
|
|
4281
4664
|
}
|
|
4282
4665
|
|
|
4666
|
+
/**
|
|
4667
|
+
* Extract the version string from a rules file content.
|
|
4668
|
+
* Looks for <!-- context-vault-rules vX.Y --> comment on the first line.
|
|
4669
|
+
* Returns the version string (e.g. "1.0") or null if not found.
|
|
4670
|
+
*/
|
|
4671
|
+
function extractRulesVersion(content) {
|
|
4672
|
+
if (!content) return null;
|
|
4673
|
+
const match = content.match(/<!--\s*context-vault-rules\s+v([\d.]+)\s*-->/);
|
|
4674
|
+
return match ? match[1] : null;
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4677
|
+
/**
|
|
4678
|
+
* Get the installed rules content for a tool, handling both write and append methods.
|
|
4679
|
+
* For append-based tools (Windsurf), extracts only the delimited section.
|
|
4680
|
+
* Returns the rules content or null if not installed.
|
|
4681
|
+
*/
|
|
4682
|
+
function getInstalledRulesForTool(tool) {
|
|
4683
|
+
const rulesPath = tool.rulesPath;
|
|
4684
|
+
if (!rulesPath || !existsSync(rulesPath)) return null;
|
|
4685
|
+
const content = readFileSync(rulesPath, 'utf-8');
|
|
4686
|
+
if (tool.rulesMethod === 'append') {
|
|
4687
|
+
const match = content.match(
|
|
4688
|
+
new RegExp(`${RULES_DELIMITER_START}\\n([\\s\\S]*?)\\n${RULES_DELIMITER_END}`)
|
|
4689
|
+
);
|
|
4690
|
+
return match ? match[1] : null;
|
|
4691
|
+
}
|
|
4692
|
+
return content;
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4283
4695
|
/**
|
|
4284
4696
|
* Return the path where agent rules are/would be installed for a given tool.
|
|
4285
4697
|
* Returns null for tools with no rules install path.
|
|
@@ -4656,6 +5068,159 @@ function removePostToolCallHook() {
|
|
|
4656
5068
|
return true;
|
|
4657
5069
|
}
|
|
4658
5070
|
|
|
5071
|
+
/**
|
|
5072
|
+
* Install the vault-recall-hook.mjs into ~/.claude/hooks/ and register it
|
|
5073
|
+
* as a UserPromptSubmit hook in ~/.claude/settings.json.
|
|
5074
|
+
* Returns true if installed, false if already present.
|
|
5075
|
+
*/
|
|
5076
|
+
function installRecallHook() {
|
|
5077
|
+
const srcPath = join(ROOT, 'assets', 'vault-recall-hook.mjs');
|
|
5078
|
+
if (!existsSync(srcPath)) return false;
|
|
5079
|
+
|
|
5080
|
+
const hooksDir = join(HOME, '.claude', 'hooks');
|
|
5081
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
5082
|
+
const destPath = join(hooksDir, 'vault-recall-hook.mjs');
|
|
5083
|
+
copyFileSync(srcPath, destPath);
|
|
5084
|
+
|
|
5085
|
+
const settingsPath = claudeSettingsPath();
|
|
5086
|
+
let settings = {};
|
|
5087
|
+
if (existsSync(settingsPath)) {
|
|
5088
|
+
try {
|
|
5089
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
5090
|
+
} catch {
|
|
5091
|
+
const bak = settingsPath + '.bak';
|
|
5092
|
+
copyFileSync(settingsPath, bak);
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
|
|
5096
|
+
if (!settings.hooks) settings.hooks = {};
|
|
5097
|
+
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
5098
|
+
|
|
5099
|
+
const hookCmd = `node ${destPath}`;
|
|
5100
|
+
const alreadyInstalled = settings.hooks.UserPromptSubmit.some((h) =>
|
|
5101
|
+
h.hooks?.some((hh) => hh.command?.includes('vault-recall-hook'))
|
|
5102
|
+
);
|
|
5103
|
+
if (alreadyInstalled) return false;
|
|
5104
|
+
|
|
5105
|
+
settings.hooks.UserPromptSubmit.push({
|
|
5106
|
+
hooks: [
|
|
5107
|
+
{
|
|
5108
|
+
type: 'command',
|
|
5109
|
+
command: hookCmd,
|
|
5110
|
+
timeout: 5,
|
|
5111
|
+
},
|
|
5112
|
+
],
|
|
5113
|
+
});
|
|
5114
|
+
|
|
5115
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
5116
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
5117
|
+
return true;
|
|
5118
|
+
}
|
|
5119
|
+
|
|
5120
|
+
/**
|
|
5121
|
+
* Remove the vault-recall-hook UserPromptSubmit hook from settings.json.
|
|
5122
|
+
* Returns true if removed, false if not found.
|
|
5123
|
+
*/
|
|
5124
|
+
function removeRecallHook() {
|
|
5125
|
+
const settingsPath = claudeSettingsPath();
|
|
5126
|
+
if (!existsSync(settingsPath)) return false;
|
|
5127
|
+
|
|
5128
|
+
let settings;
|
|
5129
|
+
try {
|
|
5130
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
5131
|
+
} catch {
|
|
5132
|
+
return false;
|
|
5133
|
+
}
|
|
5134
|
+
|
|
5135
|
+
if (!settings.hooks?.UserPromptSubmit) return false;
|
|
5136
|
+
|
|
5137
|
+
const before = settings.hooks.UserPromptSubmit.length;
|
|
5138
|
+
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
|
|
5139
|
+
(h) => !h.hooks?.some((hh) => hh.command?.includes('vault-recall-hook'))
|
|
5140
|
+
);
|
|
5141
|
+
|
|
5142
|
+
if (settings.hooks.UserPromptSubmit.length === before) return false;
|
|
5143
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
5144
|
+
return true;
|
|
5145
|
+
}
|
|
5146
|
+
|
|
5147
|
+
/**
|
|
5148
|
+
* Install the vault-error-hook.mjs into ~/.claude/hooks/ and register it
|
|
5149
|
+
* as a PostToolUse hook (matcher: Bash) in ~/.claude/settings.json.
|
|
5150
|
+
* Returns true if installed, false if already present.
|
|
5151
|
+
*/
|
|
5152
|
+
function installErrorHook() {
|
|
5153
|
+
const srcPath = join(ROOT, 'assets', 'vault-error-hook.mjs');
|
|
5154
|
+
if (!existsSync(srcPath)) return false;
|
|
5155
|
+
|
|
5156
|
+
const hooksDir = join(HOME, '.claude', 'hooks');
|
|
5157
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
5158
|
+
const destPath = join(hooksDir, 'vault-error-hook.mjs');
|
|
5159
|
+
copyFileSync(srcPath, destPath);
|
|
5160
|
+
|
|
5161
|
+
const settingsPath = claudeSettingsPath();
|
|
5162
|
+
let settings = {};
|
|
5163
|
+
if (existsSync(settingsPath)) {
|
|
5164
|
+
try {
|
|
5165
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
5166
|
+
} catch {
|
|
5167
|
+
const bak = settingsPath + '.bak';
|
|
5168
|
+
copyFileSync(settingsPath, bak);
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5171
|
+
|
|
5172
|
+
if (!settings.hooks) settings.hooks = {};
|
|
5173
|
+
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
5174
|
+
|
|
5175
|
+
const hookCmd = `node ${destPath}`;
|
|
5176
|
+
const alreadyInstalled = settings.hooks.PostToolUse.some((h) =>
|
|
5177
|
+
h.hooks?.some((hh) => hh.command?.includes('vault-error-hook'))
|
|
5178
|
+
);
|
|
5179
|
+
if (alreadyInstalled) return false;
|
|
5180
|
+
|
|
5181
|
+
settings.hooks.PostToolUse.push({
|
|
5182
|
+
matcher: 'Bash',
|
|
5183
|
+
hooks: [
|
|
5184
|
+
{
|
|
5185
|
+
type: 'command',
|
|
5186
|
+
command: hookCmd,
|
|
5187
|
+
timeout: 5,
|
|
5188
|
+
},
|
|
5189
|
+
],
|
|
5190
|
+
});
|
|
5191
|
+
|
|
5192
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
5193
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
5194
|
+
return true;
|
|
5195
|
+
}
|
|
5196
|
+
|
|
5197
|
+
/**
|
|
5198
|
+
* Remove the vault-error-hook PostToolUse hook from settings.json.
|
|
5199
|
+
* Returns true if removed, false if not found.
|
|
5200
|
+
*/
|
|
5201
|
+
function removeErrorHook() {
|
|
5202
|
+
const settingsPath = claudeSettingsPath();
|
|
5203
|
+
if (!existsSync(settingsPath)) return false;
|
|
5204
|
+
|
|
5205
|
+
let settings;
|
|
5206
|
+
try {
|
|
5207
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
5208
|
+
} catch {
|
|
5209
|
+
return false;
|
|
5210
|
+
}
|
|
5211
|
+
|
|
5212
|
+
if (!settings.hooks?.PostToolUse) return false;
|
|
5213
|
+
|
|
5214
|
+
const before = settings.hooks.PostToolUse.length;
|
|
5215
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
|
|
5216
|
+
(h) => !h.hooks?.some((hh) => hh.command?.includes('vault-error-hook'))
|
|
5217
|
+
);
|
|
5218
|
+
|
|
5219
|
+
if (settings.hooks.PostToolUse.length === before) return false;
|
|
5220
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
5221
|
+
return true;
|
|
5222
|
+
}
|
|
5223
|
+
|
|
4659
5224
|
async function runSkills() {
|
|
4660
5225
|
const sub = args[1];
|
|
4661
5226
|
|
|
@@ -4953,6 +5518,25 @@ async function runHooksInstall() {
|
|
|
4953
5518
|
}
|
|
4954
5519
|
console.log();
|
|
4955
5520
|
}
|
|
5521
|
+
|
|
5522
|
+
// Proactive surfacing hooks (vault recall + error recall)
|
|
5523
|
+
try {
|
|
5524
|
+
const recallInstalled = installRecallHook();
|
|
5525
|
+
if (recallInstalled) {
|
|
5526
|
+
console.log(` ${green('✓')} Vault recall hook installed (proactive surfacing on prompts)`);
|
|
5527
|
+
}
|
|
5528
|
+
} catch (e) {
|
|
5529
|
+
console.error(` ${red('x')} Vault recall hook failed: ${e.message}`);
|
|
5530
|
+
}
|
|
5531
|
+
try {
|
|
5532
|
+
const errorInstalled = installErrorHook();
|
|
5533
|
+
if (errorInstalled) {
|
|
5534
|
+
console.log(` ${green('✓')} Vault error hook installed (surfaces past errors on Bash failures)`);
|
|
5535
|
+
}
|
|
5536
|
+
} catch (e) {
|
|
5537
|
+
console.error(` ${red('x')} Vault error hook failed: ${e.message}`);
|
|
5538
|
+
}
|
|
5539
|
+
console.log();
|
|
4956
5540
|
}
|
|
4957
5541
|
|
|
4958
5542
|
async function runHooksUninstall() {
|
|
@@ -4994,6 +5578,24 @@ async function runHooksUninstall() {
|
|
|
4994
5578
|
} catch (e) {
|
|
4995
5579
|
console.error(`\n ${red('x')} Failed to remove auto-capture hook: ${e.message}\n`);
|
|
4996
5580
|
}
|
|
5581
|
+
|
|
5582
|
+
try {
|
|
5583
|
+
const recallHookRemoved = removeRecallHook();
|
|
5584
|
+
if (recallHookRemoved) {
|
|
5585
|
+
console.log(`\n ${green('✓')} Vault recall hook removed.\n`);
|
|
5586
|
+
}
|
|
5587
|
+
} catch (e) {
|
|
5588
|
+
console.error(`\n ${red('x')} Failed to remove recall hook: ${e.message}\n`);
|
|
5589
|
+
}
|
|
5590
|
+
|
|
5591
|
+
try {
|
|
5592
|
+
const errorHookRemoved = removeErrorHook();
|
|
5593
|
+
if (errorHookRemoved) {
|
|
5594
|
+
console.log(`\n ${green('✓')} Vault error hook removed.\n`);
|
|
5595
|
+
}
|
|
5596
|
+
} catch (e) {
|
|
5597
|
+
console.error(`\n ${red('x')} Failed to remove error hook: ${e.message}\n`);
|
|
5598
|
+
}
|
|
4997
5599
|
}
|
|
4998
5600
|
|
|
4999
5601
|
async function runHooks() {
|
|
@@ -6402,6 +7004,9 @@ async function main() {
|
|
|
6402
7004
|
case 'reindex':
|
|
6403
7005
|
await runReindex();
|
|
6404
7006
|
break;
|
|
7007
|
+
case 'sync':
|
|
7008
|
+
await runSync();
|
|
7009
|
+
break;
|
|
6405
7010
|
case 'migrate-dirs':
|
|
6406
7011
|
await runMigrateDirs();
|
|
6407
7012
|
break;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-tools.d.ts","sourceRoot":"","sources":["../src/register-tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAyB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"register-tools.d.ts","sourceRoot":"","sources":["../src/register-tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAyB,MAAM,YAAY,CAAC;AAiDlE,wBAAgB,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAwI9D"}
|