context-vault 2.8.0 → 2.8.4

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.
Files changed (33) hide show
  1. package/README.md +39 -375
  2. package/bin/cli.js +26 -67
  3. package/node_modules/@context-vault/core/package.json +10 -3
  4. package/node_modules/@context-vault/core/src/capture/file-ops.js +20 -2
  5. package/node_modules/@context-vault/core/src/capture/import-pipeline.js +0 -34
  6. package/node_modules/@context-vault/core/src/capture/importers.js +64 -37
  7. package/node_modules/@context-vault/core/src/capture/ingest-url.js +80 -44
  8. package/node_modules/@context-vault/core/src/constants.js +8 -0
  9. package/node_modules/@context-vault/core/src/core/config.js +65 -29
  10. package/node_modules/@context-vault/core/src/core/files.js +8 -15
  11. package/node_modules/@context-vault/core/src/core/frontmatter.js +22 -10
  12. package/node_modules/@context-vault/core/src/core/status.js +32 -15
  13. package/node_modules/@context-vault/core/src/index/db.js +47 -34
  14. package/node_modules/@context-vault/core/src/index/embed.js +15 -5
  15. package/node_modules/@context-vault/core/src/index.js +39 -6
  16. package/node_modules/@context-vault/core/src/retrieve/index.js +40 -8
  17. package/node_modules/@context-vault/core/src/server/helpers.js +8 -6
  18. package/node_modules/@context-vault/core/src/server/tools/context-status.js +24 -10
  19. package/node_modules/@context-vault/core/src/server/tools/delete-context.js +8 -3
  20. package/node_modules/@context-vault/core/src/server/tools/get-context.js +117 -35
  21. package/node_modules/@context-vault/core/src/server/tools/ingest-url.js +5 -4
  22. package/node_modules/@context-vault/core/src/server/tools/list-context.js +59 -18
  23. package/node_modules/@context-vault/core/src/server/tools/save-context.js +10 -10
  24. package/node_modules/@context-vault/core/src/server/tools.js +24 -28
  25. package/package.json +2 -2
  26. package/scripts/local-server.js +30 -30
  27. package/scripts/postinstall.js +25 -10
  28. package/scripts/prepack.js +18 -15
  29. package/src/server/index.js +78 -29
  30. package/app-dist/assets/index-DjXoWapE.css +0 -1
  31. package/app-dist/assets/index-R4n9Qz4U.js +0 -380
  32. package/app-dist/index.html +0 -16
  33. package/node_modules/@context-vault/core/src/server/types.js +0 -78
@@ -1,11 +1,5 @@
1
- /**
2
- * db.js — Database schema, initialization, and prepared statements
3
- */
4
-
5
1
  import { unlinkSync, copyFileSync, existsSync } from "node:fs";
6
2
 
7
- // ─── Native Module Error ────────────────────────────────────────────────────
8
-
9
3
  export class NativeModuleError extends Error {
10
4
  constructor(originalError) {
11
5
  const diagnostic = formatNativeModuleError(originalError);
@@ -18,7 +12,7 @@ export class NativeModuleError extends Error {
18
12
  function formatNativeModuleError(err) {
19
13
  const msg = err.message || "";
20
14
  const versionMatch = msg.match(
21
- /was compiled against a different Node\.js version using\s+NODE_MODULE_VERSION (\d+)\. This version of Node\.js requires\s+NODE_MODULE_VERSION (\d+)/
15
+ /was compiled against a different Node\.js version using\s+NODE_MODULE_VERSION (\d+)\. This version of Node\.js requires\s+NODE_MODULE_VERSION (\d+)/,
22
16
  );
23
17
 
24
18
  const lines = [
@@ -44,13 +38,12 @@ function formatNativeModuleError(err) {
44
38
  return lines.join("\n");
45
39
  }
46
40
 
47
- // ─── Lazy Native Module Loading ─────────────────────────────────────────────
48
-
49
41
  let _Database = null;
50
42
  let _sqliteVec = null;
51
43
 
52
44
  async function loadNativeModules() {
53
- if (_Database && _sqliteVec) return { Database: _Database, sqliteVec: _sqliteVec };
45
+ if (_Database && _sqliteVec)
46
+ return { Database: _Database, sqliteVec: _sqliteVec };
54
47
 
55
48
  try {
56
49
  const dbMod = await import("better-sqlite3");
@@ -69,8 +62,6 @@ async function loadNativeModules() {
69
62
  return { Database: _Database, sqliteVec: _sqliteVec };
70
63
  }
71
64
 
72
- // ─── Schema DDL (v7 — teams) ────────────────────────────────────────────────
73
-
74
65
  export const SCHEMA_DDL = `
75
66
  CREATE TABLE IF NOT EXISTS vault (
76
67
  id TEXT PRIMARY KEY,
@@ -126,8 +117,6 @@ export const SCHEMA_DDL = `
126
117
  CREATE VIRTUAL TABLE IF NOT EXISTS vault_vec USING vec0(embedding float[384]);
127
118
  `;
128
119
 
129
- // ─── Database Init ───────────────────────────────────────────────────────────
130
-
131
120
  export async function initDatabase(dbPath) {
132
121
  const { Database, sqliteVec } = await loadNativeModules();
133
122
 
@@ -148,7 +137,9 @@ export async function initDatabase(dbPath) {
148
137
 
149
138
  // Enforce fresh-DB-only — old schemas get a full rebuild (with backup)
150
139
  if (version > 0 && version < 5) {
151
- console.error(`[context-vault] Schema v${version} is outdated. Rebuilding database...`);
140
+ console.error(
141
+ `[context-vault] Schema v${version} is outdated. Rebuilding database...`,
142
+ );
152
143
 
153
144
  // Backup old DB before destroying it
154
145
  const backupPath = `${dbPath}.v${version}.backup`;
@@ -156,15 +147,23 @@ export async function initDatabase(dbPath) {
156
147
  db.close();
157
148
  if (existsSync(dbPath)) {
158
149
  copyFileSync(dbPath, backupPath);
159
- console.error(`[context-vault] Backed up old database to: ${backupPath}`);
150
+ console.error(
151
+ `[context-vault] Backed up old database to: ${backupPath}`,
152
+ );
160
153
  }
161
154
  } catch (backupErr) {
162
- console.error(`[context-vault] Warning: could not backup old database: ${backupErr.message}`);
155
+ console.error(
156
+ `[context-vault] Warning: could not backup old database: ${backupErr.message}`,
157
+ );
163
158
  }
164
159
 
165
160
  unlinkSync(dbPath);
166
- try { unlinkSync(dbPath + "-wal"); } catch {}
167
- try { unlinkSync(dbPath + "-shm"); } catch {}
161
+ try {
162
+ unlinkSync(dbPath + "-wal");
163
+ } catch {}
164
+ try {
165
+ unlinkSync(dbPath + "-shm");
166
+ } catch {}
168
167
 
169
168
  const freshDb = createDb(dbPath);
170
169
  freshDb.exec(SCHEMA_DDL);
@@ -180,7 +179,9 @@ export async function initDatabase(dbPath) {
180
179
  // Wrapped in transaction with duplicate-column guards for idempotent retry
181
180
  const migrate = db.transaction(() => {
182
181
  const addColumnSafe = (sql) => {
183
- try { db.exec(sql); } catch (e) {
182
+ try {
183
+ db.exec(sql);
184
+ } catch (e) {
184
185
  if (!e.message.includes("duplicate column")) throw e;
185
186
  }
186
187
  };
@@ -193,14 +194,18 @@ export async function initDatabase(dbPath) {
193
194
  db.exec(`CREATE INDEX IF NOT EXISTS idx_vault_user ON vault(user_id)`);
194
195
  db.exec(`CREATE INDEX IF NOT EXISTS idx_vault_team ON vault(team_id)`);
195
196
  db.exec(`DROP INDEX IF EXISTS idx_vault_identity`);
196
- db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_vault_identity ON vault(user_id, kind, identity_key) WHERE identity_key IS NOT NULL`);
197
+ db.exec(
198
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_vault_identity ON vault(user_id, kind, identity_key) WHERE identity_key IS NOT NULL`,
199
+ );
197
200
  db.pragma("user_version = 7");
198
201
  });
199
202
  migrate();
200
203
  } else if (version === 6) {
201
204
  // v6 -> v7 migration: add team_id column
202
205
  const migrate = db.transaction(() => {
203
- try { db.exec(`ALTER TABLE vault ADD COLUMN team_id TEXT`); } catch (e) {
206
+ try {
207
+ db.exec(`ALTER TABLE vault ADD COLUMN team_id TEXT`);
208
+ } catch (e) {
204
209
  if (!e.message.includes("duplicate column")) throw e;
205
210
  }
206
211
  db.exec(`CREATE INDEX IF NOT EXISTS idx_vault_team ON vault(team_id)`);
@@ -212,34 +217,42 @@ export async function initDatabase(dbPath) {
212
217
  return db;
213
218
  }
214
219
 
215
- // ─── Prepared Statements Factory ─────────────────────────────────────────────
216
-
217
220
  export function prepareStatements(db) {
218
221
  try {
219
222
  return {
220
- insertEntry: db.prepare(`INSERT INTO vault (id, user_id, kind, category, title, body, meta, tags, source, file_path, identity_key, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
221
- insertEntryEncrypted: db.prepare(`INSERT INTO vault (id, user_id, kind, category, title, body, meta, tags, source, file_path, identity_key, expires_at, created_at, body_encrypted, title_encrypted, meta_encrypted, iv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
222
- updateEntry: db.prepare(`UPDATE vault SET title = ?, body = ?, meta = ?, tags = ?, source = ?, category = ?, identity_key = ?, expires_at = ? WHERE file_path = ?`),
223
+ insertEntry: db.prepare(
224
+ `INSERT INTO vault (id, user_id, kind, category, title, body, meta, tags, source, file_path, identity_key, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
225
+ ),
226
+ insertEntryEncrypted: db.prepare(
227
+ `INSERT INTO vault (id, user_id, kind, category, title, body, meta, tags, source, file_path, identity_key, expires_at, created_at, body_encrypted, title_encrypted, meta_encrypted, iv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
228
+ ),
229
+ updateEntry: db.prepare(
230
+ `UPDATE vault SET title = ?, body = ?, meta = ?, tags = ?, source = ?, category = ?, identity_key = ?, expires_at = ? WHERE file_path = ?`,
231
+ ),
223
232
  deleteEntry: db.prepare(`DELETE FROM vault WHERE id = ?`),
224
233
  getRowid: db.prepare(`SELECT rowid FROM vault WHERE id = ?`),
225
234
  getRowidByPath: db.prepare(`SELECT rowid FROM vault WHERE file_path = ?`),
226
235
  getEntryById: db.prepare(`SELECT * FROM vault WHERE id = ?`),
227
- getByIdentityKey: db.prepare(`SELECT * FROM vault WHERE kind = ? AND identity_key = ? AND user_id IS ?`),
228
- upsertByIdentityKey: db.prepare(`UPDATE vault SET title = ?, body = ?, meta = ?, tags = ?, source = ?, category = ?, file_path = ?, expires_at = ? WHERE kind = ? AND identity_key = ? AND user_id IS ?`),
229
- insertVecStmt: db.prepare(`INSERT INTO vault_vec (rowid, embedding) VALUES (?, ?)`),
236
+ getByIdentityKey: db.prepare(
237
+ `SELECT * FROM vault WHERE kind = ? AND identity_key = ? AND user_id IS ?`,
238
+ ),
239
+ upsertByIdentityKey: db.prepare(
240
+ `UPDATE vault SET title = ?, body = ?, meta = ?, tags = ?, source = ?, category = ?, file_path = ?, expires_at = ? WHERE kind = ? AND identity_key = ? AND user_id IS ?`,
241
+ ),
242
+ insertVecStmt: db.prepare(
243
+ `INSERT INTO vault_vec (rowid, embedding) VALUES (?, ?)`,
244
+ ),
230
245
  deleteVecStmt: db.prepare(`DELETE FROM vault_vec WHERE rowid = ?`),
231
246
  };
232
247
  } catch (e) {
233
248
  throw new Error(
234
249
  `Failed to prepare database statements. The database may be corrupted.\n` +
235
- `Try deleting and rebuilding: rm "${db.name}" && context-vault reindex\n` +
236
- `Original error: ${e.message}`
250
+ `Try deleting and rebuilding: rm "${db.name}" && context-vault reindex\n` +
251
+ `Original error: ${e.message}`,
237
252
  );
238
253
  }
239
254
  }
240
255
 
241
- // ─── Vector Helpers (parameterized rowid via cached statements) ──────────────
242
-
243
256
  export function insertVec(stmts, rowid, embedding) {
244
257
  // sqlite-vec requires BigInt for primary key — better-sqlite3 binds Number as REAL,
245
258
  // but vec0 virtual tables only accept INTEGER rowids
@@ -30,14 +30,20 @@ async function ensurePipeline() {
30
30
  mkdirSync(modelCacheDir, { recursive: true });
31
31
  env.cacheDir = modelCacheDir;
32
32
 
33
- console.error("[context-vault] Loading embedding model (first run may download ~22MB)...");
33
+ console.error(
34
+ "[context-vault] Loading embedding model (first run may download ~22MB)...",
35
+ );
34
36
  extractor = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
35
37
  embedAvailable = true;
36
38
  return extractor;
37
39
  } catch (e) {
38
40
  embedAvailable = false;
39
- console.error(`[context-vault] Failed to load embedding model: ${e.message}`);
40
- console.error(`[context-vault] Semantic search disabled. Full-text search still works.`);
41
+ console.error(
42
+ `[context-vault] Failed to load embedding model: ${e.message}`,
43
+ );
44
+ console.error(
45
+ `[context-vault] Semantic search disabled. Full-text search still works.`,
46
+ );
41
47
  return null;
42
48
  }
43
49
  }
@@ -74,9 +80,13 @@ export async function embedBatch(texts) {
74
80
  }
75
81
  const dim = result.data.length / texts.length;
76
82
  if (!Number.isInteger(dim) || dim <= 0) {
77
- throw new Error(`Unexpected embedding dimension: ${result.data.length} / ${texts.length} = ${dim}`);
83
+ throw new Error(
84
+ `Unexpected embedding dimension: ${result.data.length} / ${texts.length} = ${dim}`,
85
+ );
78
86
  }
79
- return texts.map((_, i) => new Float32Array(result.data.buffer, i * dim * 4, dim));
87
+ return texts.map(
88
+ (_, i) => new Float32Array(result.data.buffer, i * dim * 4, dim),
89
+ );
80
90
  }
81
91
 
82
92
  /** Force re-initialization on next embed call. */
@@ -5,19 +5,47 @@
5
5
  */
6
6
 
7
7
  // Core utilities
8
- export { categoryFor, categoryDirFor, CATEGORY_DIRS } from "./core/categories.js";
8
+ export {
9
+ categoryFor,
10
+ categoryDirFor,
11
+ CATEGORY_DIRS,
12
+ } from "./core/categories.js";
9
13
  export { parseArgs, resolveConfig } from "./core/config.js";
10
- export { ulid, slugify, kindToDir, dirToKind, normalizeKind, kindToPath, safeJoin, walkDir } from "./core/files.js";
11
- export { formatFrontmatter, parseFrontmatter, extractCustomMeta, parseEntryFromMarkdown } from "./core/frontmatter.js";
14
+ export {
15
+ ulid,
16
+ slugify,
17
+ kindToDir,
18
+ dirToKind,
19
+ normalizeKind,
20
+ kindToPath,
21
+ safeJoin,
22
+ walkDir,
23
+ } from "./core/files.js";
24
+ export {
25
+ formatFrontmatter,
26
+ parseFrontmatter,
27
+ extractCustomMeta,
28
+ parseEntryFromMarkdown,
29
+ } from "./core/frontmatter.js";
12
30
  export { gatherVaultStatus } from "./core/status.js";
13
31
 
14
32
  // Capture layer
15
- export { writeEntry, updateEntryFile, captureAndIndex } from "./capture/index.js";
33
+ export {
34
+ writeEntry,
35
+ updateEntryFile,
36
+ captureAndIndex,
37
+ } from "./capture/index.js";
16
38
  export { writeEntryFile } from "./capture/file-ops.js";
17
39
  export { formatBody } from "./capture/formatters.js";
18
40
 
19
41
  // Index layer
20
- export { SCHEMA_DDL, initDatabase, prepareStatements, insertVec, deleteVec } from "./index/db.js";
42
+ export {
43
+ SCHEMA_DDL,
44
+ initDatabase,
45
+ prepareStatements,
46
+ insertVec,
47
+ deleteVec,
48
+ } from "./index/db.js";
21
49
  export { embed, embedBatch, resetEmbedPipeline } from "./index/embed.js";
22
50
  export { indexEntry, reindex } from "./index/index.js";
23
51
 
@@ -26,4 +54,9 @@ export { hybridSearch } from "./retrieve/index.js";
26
54
 
27
55
  // Server tools & helpers
28
56
  export { registerTools } from "./server/tools.js";
29
- export { ok, err, ensureVaultExists, ensureValidKind } from "./server/helpers.js";
57
+ export {
58
+ ok,
59
+ err,
60
+ ensureVaultExists,
61
+ ensureValidKind,
62
+ } from "./server/helpers.js";
@@ -38,7 +38,13 @@ export function recencyBoost(createdAt, category, decayDays = 30) {
38
38
  * Build additional WHERE clauses for category/time filtering.
39
39
  * Returns { clauses: string[], params: any[] }
40
40
  */
41
- export function buildFilterClauses({ categoryFilter, since, until, userIdFilter, teamIdFilter }) {
41
+ export function buildFilterClauses({
42
+ categoryFilter,
43
+ since,
44
+ until,
45
+ userIdFilter,
46
+ teamIdFilter,
47
+ }) {
42
48
  const clauses = [];
43
49
  const params = [];
44
50
  if (userIdFilter !== undefined) {
@@ -76,10 +82,26 @@ export function buildFilterClauses({ categoryFilter, since, until, userIdFilter,
76
82
  export async function hybridSearch(
77
83
  ctx,
78
84
  query,
79
- { kindFilter = null, categoryFilter = null, since = null, until = null, limit = 20, offset = 0, decayDays = 30, userIdFilter, teamIdFilter = null } = {}
85
+ {
86
+ kindFilter = null,
87
+ categoryFilter = null,
88
+ since = null,
89
+ until = null,
90
+ limit = 20,
91
+ offset = 0,
92
+ decayDays = 30,
93
+ userIdFilter,
94
+ teamIdFilter = null,
95
+ } = {},
80
96
  ) {
81
97
  const results = new Map();
82
- const extraFilters = buildFilterClauses({ categoryFilter, since, until, userIdFilter, teamIdFilter });
98
+ const extraFilters = buildFilterClauses({
99
+ categoryFilter,
100
+ since,
101
+ until,
102
+ userIdFilter,
103
+ teamIdFilter,
104
+ });
83
105
 
84
106
  // FTS5 search
85
107
  const ftsQuery = buildFtsQuery(query);
@@ -126,10 +148,16 @@ export async function hybridSearch(
126
148
  if (queryVec) {
127
149
  // Increase limits in hosted mode to compensate for post-filtering
128
150
  const hasPostFilter = userIdFilter !== undefined || teamIdFilter;
129
- const vecLimit = hasPostFilter ? (kindFilter ? 60 : 30) : (kindFilter ? 30 : 15);
151
+ const vecLimit = hasPostFilter
152
+ ? kindFilter
153
+ ? 60
154
+ : 30
155
+ : kindFilter
156
+ ? 30
157
+ : 15;
130
158
  const vecRows = ctx.db
131
159
  .prepare(
132
- `SELECT v.rowid, v.distance FROM vault_vec v WHERE embedding MATCH ? ORDER BY distance LIMIT ${vecLimit}`
160
+ `SELECT v.rowid, v.distance FROM vault_vec v WHERE embedding MATCH ? ORDER BY distance LIMIT ${vecLimit}`,
133
161
  )
134
162
  .all(queryVec);
135
163
 
@@ -138,7 +166,9 @@ export async function hybridSearch(
138
166
  const rowids = vecRows.map((vr) => vr.rowid);
139
167
  const placeholders = rowids.map(() => "?").join(",");
140
168
  const hydrated = ctx.db
141
- .prepare(`SELECT rowid, * FROM vault WHERE rowid IN (${placeholders})`)
169
+ .prepare(
170
+ `SELECT rowid, * FROM vault WHERE rowid IN (${placeholders})`,
171
+ )
142
172
  .all(...rowids);
143
173
 
144
174
  const byRowid = new Map();
@@ -147,13 +177,15 @@ export async function hybridSearch(
147
177
  for (const vr of vecRows) {
148
178
  const row = byRowid.get(vr.rowid);
149
179
  if (!row) continue;
150
- if (userIdFilter !== undefined && row.user_id !== userIdFilter) continue;
180
+ if (userIdFilter !== undefined && row.user_id !== userIdFilter)
181
+ continue;
151
182
  if (teamIdFilter && row.team_id !== teamIdFilter) continue;
152
183
  if (kindFilter && row.kind !== kindFilter) continue;
153
184
  if (categoryFilter && row.category !== categoryFilter) continue;
154
185
  if (since && row.created_at < since) continue;
155
186
  if (until && row.created_at > until) continue;
156
- if (row.expires_at && new Date(row.expires_at) <= new Date()) continue;
187
+ if (row.expires_at && new Date(row.expires_at) <= new Date())
188
+ continue;
157
189
 
158
190
  const { rowid: _rowid, ...cleanRow } = row;
159
191
  // sqlite-vec returns L2 distance [0, 2] for normalized vectors.
@@ -2,8 +2,6 @@
2
2
  * helpers.js — Shared MCP response helpers and validation
3
3
  */
4
4
 
5
- // ─── MCP Response Helpers ────────────────────────────────────────────────────
6
-
7
5
  export function ok(text) {
8
6
  return { content: [{ type: "text", text }] };
9
7
  }
@@ -12,18 +10,22 @@ export function err(text, code = "UNKNOWN") {
12
10
  return { content: [{ type: "text", text }], isError: true, code };
13
11
  }
14
12
 
15
- // ─── Validation Helpers ──────────────────────────────────────────────────────
16
-
17
13
  export function ensureVaultExists(config) {
18
14
  if (!config.vaultDirExists) {
19
- return err(`Vault directory not found: ${config.vaultDir}. Run context_status for diagnostics.`, "VAULT_NOT_FOUND");
15
+ return err(
16
+ `Vault directory not found: ${config.vaultDir}. Run context_status for diagnostics.`,
17
+ "VAULT_NOT_FOUND",
18
+ );
20
19
  }
21
20
  return null;
22
21
  }
23
22
 
24
23
  export function ensureValidKind(kind) {
25
24
  if (!/^[a-z][a-z0-9_-]*$/.test(kind)) {
26
- return err("Required: kind (lowercase alphanumeric, e.g. 'insight', 'reference')", "INVALID_KIND");
25
+ return err(
26
+ "Required: kind (lowercase alphanumeric, e.g. 'insight', 'reference')",
27
+ "INVALID_KIND",
28
+ );
27
29
  }
28
30
  return null;
29
31
  }
@@ -18,7 +18,7 @@ export function handler(_args, ctx) {
18
18
 
19
19
  const status = gatherVaultStatus(ctx, { userId });
20
20
 
21
- const hasIssues = status.stalePaths || (status.embeddingStatus?.missing > 0);
21
+ const hasIssues = status.stalePaths || status.embeddingStatus?.missing > 0;
22
22
  const healthIcon = hasIssues ? "⚠" : "✓";
23
23
 
24
24
  const lines = [
@@ -39,13 +39,17 @@ export function handler(_args, ctx) {
39
39
  lines.push(`Embeddings: ${indexed}/${total} (${pct}%)`);
40
40
  }
41
41
  if (status.embedModelAvailable === false) {
42
- lines.push(`Embed model: unavailable (semantic search disabled, FTS still works)`);
42
+ lines.push(
43
+ `Embed model: unavailable (semantic search disabled, FTS still works)`,
44
+ );
43
45
  } else if (status.embedModelAvailable === true) {
44
46
  lines.push(`Embed model: loaded`);
45
47
  }
46
48
  lines.push(`Decay: ${config.eventDecayDays} days (event recency window)`);
47
49
  if (status.expiredCount > 0) {
48
- lines.push(`Expired: ${status.expiredCount} entries (pruned on next reindex)`);
50
+ lines.push(
51
+ `Expired: ${status.expiredCount} entries (pruned on next reindex)`,
52
+ );
49
53
  }
50
54
 
51
55
  lines.push(``, `### Indexed`);
@@ -59,28 +63,38 @@ export function handler(_args, ctx) {
59
63
  if (status.categoryCounts.length) {
60
64
  lines.push(``);
61
65
  lines.push(`### Categories`);
62
- for (const { category, c } of status.categoryCounts) lines.push(`- ${category}: ${c}`);
66
+ for (const { category, c } of status.categoryCounts)
67
+ lines.push(`- ${category}: ${c}`);
63
68
  }
64
69
 
65
70
  if (status.subdirs.length) {
66
71
  lines.push(``);
67
72
  lines.push(`### Disk Directories`);
68
- for (const { name, count } of status.subdirs) lines.push(`- ${name}/: ${count} files`);
73
+ for (const { name, count } of status.subdirs)
74
+ lines.push(`- ${name}/: ${count} files`);
69
75
  }
70
76
 
71
77
  if (status.stalePaths) {
72
78
  lines.push(``);
73
79
  lines.push(`### ⚠ Stale Paths`);
74
- lines.push(`DB contains ${status.staleCount} paths not matching current vault dir.`);
80
+ lines.push(
81
+ `DB contains ${status.staleCount} paths not matching current vault dir.`,
82
+ );
75
83
  lines.push(`Auto-reindex will fix this on next search or save.`);
76
84
  }
77
85
 
78
86
  // Suggested actions
79
87
  const actions = [];
80
- if (status.stalePaths) actions.push("- Run `context-vault reindex` to fix stale paths");
81
- if (status.embeddingStatus?.missing > 0) actions.push("- Run `context-vault reindex` to generate missing embeddings");
82
- if (!config.vaultDirExists) actions.push("- Run `context-vault setup` to create the vault directory");
83
- if (status.kindCounts.length === 0 && config.vaultDirExists) actions.push("- Use `save_context` to add your first entry");
88
+ if (status.stalePaths)
89
+ actions.push("- Run `context-vault reindex` to fix stale paths");
90
+ if (status.embeddingStatus?.missing > 0)
91
+ actions.push(
92
+ "- Run `context-vault reindex` to generate missing embeddings",
93
+ );
94
+ if (!config.vaultDirExists)
95
+ actions.push("- Run `context-vault setup` to create the vault directory");
96
+ if (status.kindCounts.length === 0 && config.vaultDirExists)
97
+ actions.push("- Use `save_context` to add your first entry");
84
98
 
85
99
  if (actions.length) {
86
100
  lines.push("", "### Suggested Actions", ...actions);
@@ -19,7 +19,8 @@ export const inputSchema = {
19
19
  export async function handler({ id }, ctx, { ensureIndexed }) {
20
20
  const userId = ctx.userId !== undefined ? ctx.userId : undefined;
21
21
 
22
- if (!id?.trim()) return err("Required: id (non-empty string)", "INVALID_INPUT");
22
+ if (!id?.trim())
23
+ return err("Required: id (non-empty string)", "INVALID_INPUT");
23
24
  await ensureIndexed();
24
25
 
25
26
  const entry = ctx.stmts.getEntryById.get(id);
@@ -32,13 +33,17 @@ export async function handler({ id }, ctx, { ensureIndexed }) {
32
33
 
33
34
  // Delete file from disk first (source of truth)
34
35
  if (entry.file_path) {
35
- try { unlinkSync(entry.file_path); } catch {}
36
+ try {
37
+ unlinkSync(entry.file_path);
38
+ } catch {}
36
39
  }
37
40
 
38
41
  // Delete vector embedding
39
42
  const rowidResult = ctx.stmts.getRowid.get(id);
40
43
  if (rowidResult?.rowid) {
41
- try { ctx.deleteVec(Number(rowidResult.rowid)); } catch {}
44
+ try {
45
+ ctx.deleteVec(Number(rowidResult.rowid));
46
+ } catch {}
42
47
  }
43
48
 
44
49
  // Delete DB row (FTS trigger handles FTS cleanup)