context-vault 2.8.0 → 2.8.3

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 +7 -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
@@ -11,13 +11,36 @@ export const description =
11
11
  "Search your knowledge vault. Returns entries ranked by relevance using hybrid full-text + semantic search. Use this to find insights, decisions, patterns, or any saved context. Each result includes an `id` you can use with save_context or delete_context.";
12
12
 
13
13
  export const inputSchema = {
14
- query: z.string().optional().describe("Search query (natural language or keywords). Optional if filters (tags, kind, category) are provided."),
15
- kind: z.string().optional().describe("Filter by kind (e.g. 'insight', 'decision', 'pattern')"),
16
- category: z.enum(["knowledge", "entity", "event"]).optional().describe("Filter by category"),
17
- identity_key: z.string().optional().describe("For entity lookup: exact match on identity key. Requires kind."),
18
- tags: z.array(z.string()).optional().describe("Filter by tags (entries must match at least one)"),
19
- since: z.string().optional().describe("ISO date, return entries created after this"),
20
- until: z.string().optional().describe("ISO date, return entries created before this"),
14
+ query: z
15
+ .string()
16
+ .optional()
17
+ .describe(
18
+ "Search query (natural language or keywords). Optional if filters (tags, kind, category) are provided.",
19
+ ),
20
+ kind: z
21
+ .string()
22
+ .optional()
23
+ .describe("Filter by kind (e.g. 'insight', 'decision', 'pattern')"),
24
+ category: z
25
+ .enum(["knowledge", "entity", "event"])
26
+ .optional()
27
+ .describe("Filter by category"),
28
+ identity_key: z
29
+ .string()
30
+ .optional()
31
+ .describe("For entity lookup: exact match on identity key. Requires kind."),
32
+ tags: z
33
+ .array(z.string())
34
+ .optional()
35
+ .describe("Filter by tags (entries must match at least one)"),
36
+ since: z
37
+ .string()
38
+ .optional()
39
+ .describe("ISO date, return entries created after this"),
40
+ until: z
41
+ .string()
42
+ .optional()
43
+ .describe("ISO date, return entries created before this"),
21
44
  limit: z.number().optional().describe("Max results to return (default 10)"),
22
45
  };
23
46
 
@@ -26,25 +49,42 @@ export const inputSchema = {
26
49
  * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
27
50
  * @param {import('../types.js').ToolShared} shared
28
51
  */
29
- export async function handler({ query, kind, category, identity_key, tags, since, until, limit }, ctx, { ensureIndexed, reindexFailed }) {
52
+ export async function handler(
53
+ { query, kind, category, identity_key, tags, since, until, limit },
54
+ ctx,
55
+ { ensureIndexed, reindexFailed },
56
+ ) {
30
57
  const { config } = ctx;
31
58
  const userId = ctx.userId !== undefined ? ctx.userId : undefined;
32
59
 
33
60
  const hasQuery = query?.trim();
34
- const hasFilters = kind || category || tags?.length || since || until || identity_key;
35
- if (!hasQuery && !hasFilters) return err("Required: query or at least one filter (kind, category, tags, since, until, identity_key)", "INVALID_INPUT");
61
+ const hasFilters =
62
+ kind || category || tags?.length || since || until || identity_key;
63
+ if (!hasQuery && !hasFilters)
64
+ return err(
65
+ "Required: query or at least one filter (kind, category, tags, since, until, identity_key)",
66
+ "INVALID_INPUT",
67
+ );
36
68
  await ensureIndexed();
37
69
 
38
70
  const kindFilter = kind ? normalizeKind(kind) : null;
39
71
 
40
72
  // Gap 1: Entity exact-match by identity_key
41
73
  if (identity_key) {
42
- if (!kindFilter) return err("identity_key requires kind to be specified", "INVALID_INPUT");
43
- const match = ctx.stmts.getByIdentityKey.get(kindFilter, identity_key, userId !== undefined ? userId : null);
74
+ if (!kindFilter)
75
+ return err("identity_key requires kind to be specified", "INVALID_INPUT");
76
+ const match = ctx.stmts.getByIdentityKey.get(
77
+ kindFilter,
78
+ identity_key,
79
+ userId !== undefined ? userId : null,
80
+ );
44
81
  if (match) {
45
82
  const entryTags = match.tags ? JSON.parse(match.tags) : [];
46
83
  const tagStr = entryTags.length ? entryTags.join(", ") : "none";
47
- const relPath = match.file_path && config.vaultDir ? match.file_path.replace(config.vaultDir + "/", "") : match.file_path || "n/a";
84
+ const relPath =
85
+ match.file_path && config.vaultDir
86
+ ? match.file_path.replace(config.vaultDir + "/", "")
87
+ : match.file_path || "n/a";
48
88
  const lines = [
49
89
  `## Entity Match (exact)\n`,
50
90
  `### ${match.title || "(untitled)"} [${match.kind}/${match.category}]`,
@@ -57,7 +97,8 @@ export async function handler({ query, kind, category, identity_key, tags, since
57
97
  }
58
98
 
59
99
  // Gap 2: Event default time-window
60
- const effectiveCategory = category || (kindFilter ? categoryFor(kindFilter) : null);
100
+ const effectiveCategory =
101
+ category || (kindFilter ? categoryFor(kindFilter) : null);
61
102
  let effectiveSince = since || null;
62
103
  let effectiveUntil = until || null;
63
104
  let autoWindowed = false;
@@ -86,38 +127,64 @@ export async function handler({ query, kind, category, identity_key, tags, since
86
127
 
87
128
  // Post-filter by tags if provided, then apply requested limit
88
129
  filtered = tags?.length
89
- ? sorted.filter((r) => {
90
- const entryTags = r.tags ? JSON.parse(r.tags) : [];
91
- return tags.some((t) => entryTags.includes(t));
92
- }).slice(0, effectiveLimit)
130
+ ? sorted
131
+ .filter((r) => {
132
+ const entryTags = r.tags ? JSON.parse(r.tags) : [];
133
+ return tags.some((t) => entryTags.includes(t));
134
+ })
135
+ .slice(0, effectiveLimit)
93
136
  : sorted;
94
137
  } else {
95
138
  // Filter-only mode (no query, use SQL directly)
96
139
  const clauses = [];
97
140
  const params = [];
98
- if (userId !== undefined) { clauses.push("user_id = ?"); params.push(userId); }
99
- if (kindFilter) { clauses.push("kind = ?"); params.push(kindFilter); }
100
- if (category) { clauses.push("category = ?"); params.push(category); }
101
- if (effectiveSince) { clauses.push("created_at >= ?"); params.push(effectiveSince); }
102
- if (effectiveUntil) { clauses.push("created_at <= ?"); params.push(effectiveUntil); }
141
+ if (userId !== undefined) {
142
+ clauses.push("user_id = ?");
143
+ params.push(userId);
144
+ }
145
+ if (kindFilter) {
146
+ clauses.push("kind = ?");
147
+ params.push(kindFilter);
148
+ }
149
+ if (category) {
150
+ clauses.push("category = ?");
151
+ params.push(category);
152
+ }
153
+ if (effectiveSince) {
154
+ clauses.push("created_at >= ?");
155
+ params.push(effectiveSince);
156
+ }
157
+ if (effectiveUntil) {
158
+ clauses.push("created_at <= ?");
159
+ params.push(effectiveUntil);
160
+ }
103
161
  clauses.push("(expires_at IS NULL OR expires_at > datetime('now'))");
104
162
  const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
105
163
  params.push(fetchLimit);
106
- const rows = ctx.db.prepare(`SELECT * FROM vault ${where} ORDER BY created_at DESC LIMIT ?`).all(...params);
164
+ const rows = ctx.db
165
+ .prepare(`SELECT * FROM vault ${where} ORDER BY created_at DESC LIMIT ?`)
166
+ .all(...params);
107
167
 
108
168
  // Post-filter by tags if provided, then apply requested limit
109
169
  filtered = tags?.length
110
- ? rows.filter((r) => {
111
- const entryTags = r.tags ? JSON.parse(r.tags) : [];
112
- return tags.some((t) => entryTags.includes(t));
113
- }).slice(0, effectiveLimit)
170
+ ? rows
171
+ .filter((r) => {
172
+ const entryTags = r.tags ? JSON.parse(r.tags) : [];
173
+ return tags.some((t) => entryTags.includes(t));
174
+ })
175
+ .slice(0, effectiveLimit)
114
176
  : rows;
115
177
 
116
178
  // Add score field for consistent output
117
179
  for (const r of filtered) r.score = 0;
118
180
  }
119
181
 
120
- if (!filtered.length) return ok(hasQuery ? "No results found for: " + query : "No entries found matching the given filters.");
182
+ if (!filtered.length)
183
+ return ok(
184
+ hasQuery
185
+ ? "No results found for: " + query
186
+ : "No entries found matching the given filters.",
187
+ );
121
188
 
122
189
  // Decrypt encrypted entries if ctx.decrypt is available
123
190
  if (ctx.decrypt) {
@@ -132,22 +199,37 @@ export async function handler({ query, kind, category, identity_key, tags, since
132
199
  }
133
200
 
134
201
  const lines = [];
135
- if (reindexFailed) lines.push(`> **Warning:** Auto-reindex failed. Results may be stale. Run \`context-vault reindex\` to fix.\n`);
136
- if (hasQuery && isEmbedAvailable() === false) lines.push(`> **Note:** Semantic search unavailable — results ranked by keyword match only. Run \`context-vault setup\` to download the embedding model.\n`);
202
+ if (reindexFailed)
203
+ lines.push(
204
+ `> **Warning:** Auto-reindex failed. Results may be stale. Run \`context-vault reindex\` to fix.\n`,
205
+ );
206
+ if (hasQuery && isEmbedAvailable() === false)
207
+ lines.push(
208
+ `> **Note:** Semantic search unavailable — results ranked by keyword match only. Run \`context-vault setup\` to download the embedding model.\n`,
209
+ );
137
210
  const heading = hasQuery ? `Results for "${query}"` : "Filtered entries";
138
211
  lines.push(`## ${heading} (${filtered.length} matches)\n`);
139
212
  for (let i = 0; i < filtered.length; i++) {
140
213
  const r = filtered[i];
141
214
  const entryTags = r.tags ? JSON.parse(r.tags) : [];
142
215
  const tagStr = entryTags.length ? entryTags.join(", ") : "none";
143
- const relPath = r.file_path && config.vaultDir ? r.file_path.replace(config.vaultDir + "/", "") : r.file_path || "n/a";
144
- lines.push(`### [${i + 1}/${filtered.length}] ${r.title || "(untitled)"} [${r.kind}/${r.category}]`);
145
- lines.push(`${r.score.toFixed(3)} · ${tagStr} · ${relPath} · id: \`${r.id}\``);
216
+ const relPath =
217
+ r.file_path && config.vaultDir
218
+ ? r.file_path.replace(config.vaultDir + "/", "")
219
+ : r.file_path || "n/a";
220
+ lines.push(
221
+ `### [${i + 1}/${filtered.length}] ${r.title || "(untitled)"} [${r.kind}/${r.category}]`,
222
+ );
223
+ lines.push(
224
+ `${r.score.toFixed(3)} · ${tagStr} · ${relPath} · id: \`${r.id}\``,
225
+ );
146
226
  lines.push(r.body?.slice(0, 300) + (r.body?.length > 300 ? "..." : ""));
147
227
  lines.push("");
148
228
  }
149
229
  if (autoWindowed) {
150
- lines.push(`_Showing events from last ${config.eventDecayDays || 30} days. Use since/until for custom range._`);
230
+ lines.push(
231
+ `_Showing events from last ${config.eventDecayDays || 30} days. Use since/until for custom range._`,
232
+ );
151
233
  }
152
234
  return ok(lines.join("\n"));
153
235
  }
@@ -1,12 +1,13 @@
1
1
  import { z } from "zod";
2
2
  import { captureAndIndex } from "../../capture/index.js";
3
3
  import { ok, err, ensureVaultExists } from "../helpers.js";
4
+ import {
5
+ MAX_KIND_LENGTH,
6
+ MAX_TAG_LENGTH,
7
+ MAX_TAGS_COUNT,
8
+ } from "../../constants.js";
4
9
 
5
- // ─── Input size limits (mirrors hosted validation) ────────────────────────────
6
10
  const MAX_URL_LENGTH = 2048;
7
- const MAX_KIND_LENGTH = 64;
8
- const MAX_TAG_LENGTH = 100;
9
- const MAX_TAGS_COUNT = 20;
10
11
 
11
12
  export const name = "ingest_url";
12
13
 
@@ -8,12 +8,30 @@ export const description =
8
8
  "Browse vault entries without a search query. Returns id, title, kind, category, tags, created_at. Use get_context with a query for semantic search. Use this to browse by tags or find recent entries.";
9
9
 
10
10
  export const inputSchema = {
11
- kind: z.string().optional().describe("Filter by kind (e.g. 'insight', 'decision', 'pattern')"),
12
- category: z.enum(["knowledge", "entity", "event"]).optional().describe("Filter by category"),
13
- tags: z.array(z.string()).optional().describe("Filter by tags (entries must match at least one)"),
14
- since: z.string().optional().describe("ISO date, return entries created after this"),
15
- until: z.string().optional().describe("ISO date, return entries created before this"),
16
- limit: z.number().optional().describe("Max results to return (default 20, max 100)"),
11
+ kind: z
12
+ .string()
13
+ .optional()
14
+ .describe("Filter by kind (e.g. 'insight', 'decision', 'pattern')"),
15
+ category: z
16
+ .enum(["knowledge", "entity", "event"])
17
+ .optional()
18
+ .describe("Filter by category"),
19
+ tags: z
20
+ .array(z.string())
21
+ .optional()
22
+ .describe("Filter by tags (entries must match at least one)"),
23
+ since: z
24
+ .string()
25
+ .optional()
26
+ .describe("ISO date, return entries created after this"),
27
+ until: z
28
+ .string()
29
+ .optional()
30
+ .describe("ISO date, return entries created before this"),
31
+ limit: z
32
+ .number()
33
+ .optional()
34
+ .describe("Max results to return (default 20, max 100)"),
17
35
  offset: z.number().optional().describe("Skip first N results for pagination"),
18
36
  };
19
37
 
@@ -22,7 +40,11 @@ export const inputSchema = {
22
40
  * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
23
41
  * @param {import('../types.js').ToolShared} shared
24
42
  */
25
- export async function handler({ kind, category, tags, since, until, limit, offset }, ctx, { ensureIndexed, reindexFailed }) {
43
+ export async function handler(
44
+ { kind, category, tags, since, until, limit, offset },
45
+ ctx,
46
+ { ensureIndexed, reindexFailed },
47
+ ) {
26
48
  const { config } = ctx;
27
49
  const userId = ctx.userId !== undefined ? ctx.userId : undefined;
28
50
 
@@ -60,33 +82,52 @@ export async function handler({ kind, category, tags, since, until, limit, offse
60
82
  const fetchLimit = tags?.length ? effectiveLimit * 10 : effectiveLimit;
61
83
 
62
84
  const countParams = [...params];
63
- const total = ctx.db.prepare(`SELECT COUNT(*) as c FROM vault ${where}`).get(...countParams).c;
85
+ const total = ctx.db
86
+ .prepare(`SELECT COUNT(*) as c FROM vault ${where}`)
87
+ .get(...countParams).c;
64
88
 
65
89
  params.push(fetchLimit, effectiveOffset);
66
- const rows = ctx.db.prepare(`SELECT id, title, kind, category, tags, created_at, SUBSTR(body, 1, 120) as preview FROM vault ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params);
90
+ const rows = ctx.db
91
+ .prepare(
92
+ `SELECT id, title, kind, category, tags, created_at, SUBSTR(body, 1, 120) as preview FROM vault ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
93
+ )
94
+ .all(...params);
67
95
 
68
96
  // Post-filter by tags if provided, then apply requested limit
69
97
  const filtered = tags?.length
70
- ? rows.filter((r) => {
71
- const entryTags = r.tags ? JSON.parse(r.tags) : [];
72
- return tags.some((t) => entryTags.includes(t));
73
- }).slice(0, effectiveLimit)
98
+ ? rows
99
+ .filter((r) => {
100
+ const entryTags = r.tags ? JSON.parse(r.tags) : [];
101
+ return tags.some((t) => entryTags.includes(t));
102
+ })
103
+ .slice(0, effectiveLimit)
74
104
  : rows;
75
105
 
76
- if (!filtered.length) return ok("No entries found matching the given filters.");
106
+ if (!filtered.length)
107
+ return ok("No entries found matching the given filters.");
77
108
 
78
109
  const lines = [];
79
- if (reindexFailed) lines.push(`> **Warning:** Auto-reindex failed. Results may be stale. Run \`context-vault reindex\` to fix.\n`);
110
+ if (reindexFailed)
111
+ lines.push(
112
+ `> **Warning:** Auto-reindex failed. Results may be stale. Run \`context-vault reindex\` to fix.\n`,
113
+ );
80
114
  lines.push(`## Vault Entries (${filtered.length} shown, ${total} total)\n`);
81
115
  for (const r of filtered) {
82
116
  const entryTags = r.tags ? JSON.parse(r.tags) : [];
83
117
  const tagStr = entryTags.length ? entryTags.join(", ") : "none";
84
- lines.push(`- **${r.title || "(untitled)"}** [${r.kind}/${r.category}] — ${tagStr} — ${r.created_at} — \`${r.id}\``);
85
- if (r.preview) lines.push(` ${r.preview.replace(/\n+/g, " ").trim()}${r.preview.length >= 120 ? "…" : ""}`);
118
+ lines.push(
119
+ `- **${r.title || "(untitled)"}** [${r.kind}/${r.category}] ${tagStr} ${r.created_at} \`${r.id}\``,
120
+ );
121
+ if (r.preview)
122
+ lines.push(
123
+ ` ${r.preview.replace(/\n+/g, " ").trim()}${r.preview.length >= 120 ? "…" : ""}`,
124
+ );
86
125
  }
87
126
 
88
127
  if (effectiveOffset + effectiveLimit < total) {
89
- lines.push(`\n_Page ${Math.floor(effectiveOffset / effectiveLimit) + 1}. Use offset: ${effectiveOffset + effectiveLimit} for next page._`);
128
+ lines.push(
129
+ `\n_Page ${Math.floor(effectiveOffset / effectiveLimit) + 1}. Use offset: ${effectiveOffset + effectiveLimit} for next page._`,
130
+ );
90
131
  }
91
132
 
92
133
  return ok(lines.join("\n"));
@@ -4,16 +4,16 @@ import { indexEntry } from "../../index/index.js";
4
4
  import { categoryFor } from "../../core/categories.js";
5
5
  import { normalizeKind } from "../../core/files.js";
6
6
  import { ok, err, ensureVaultExists, ensureValidKind } from "../helpers.js";
7
-
8
- // ─── Input size limits (mirrors hosted validation) ────────────────────────────
9
- const MAX_BODY_LENGTH = 100 * 1024; // 100KB
10
- const MAX_TITLE_LENGTH = 500;
11
- const MAX_KIND_LENGTH = 64;
12
- const MAX_TAG_LENGTH = 100;
13
- const MAX_TAGS_COUNT = 20;
14
- const MAX_META_LENGTH = 10 * 1024; // 10KB
15
- const MAX_SOURCE_LENGTH = 200;
16
- const MAX_IDENTITY_KEY_LENGTH = 200;
7
+ import {
8
+ MAX_BODY_LENGTH,
9
+ MAX_TITLE_LENGTH,
10
+ MAX_KIND_LENGTH,
11
+ MAX_TAG_LENGTH,
12
+ MAX_TAGS_COUNT,
13
+ MAX_META_LENGTH,
14
+ MAX_SOURCE_LENGTH,
15
+ MAX_IDENTITY_KEY_LENGTH,
16
+ } from "../../constants.js";
17
17
 
18
18
  /**
19
19
  * Validate input fields for save_context. Returns an error response or null.
@@ -1,12 +1,3 @@
1
- /**
2
- * tools.js — MCP tool registrations (orchestrator)
3
- *
4
- * Seven tools: get_context (search), save_context (write/update), list_context (browse),
5
- * delete_context (remove), submit_feedback (bug/feature reports), ingest_url (fetch+save),
6
- * context_status (diag).
7
- * Auto-reindex runs transparently on first tool call per session.
8
- */
9
-
10
1
  import { reindex } from "../index/index.js";
11
2
  import { err } from "./helpers.js";
12
3
 
@@ -30,17 +21,9 @@ const toolModules = [
30
21
 
31
22
  const TOOL_TIMEOUT_MS = 60_000;
32
23
 
33
- /**
34
- * Register all MCP tools on the server.
35
- *
36
- * @param {import("@modelcontextprotocol/sdk/server/mcp.js").McpServer} server
37
- * @param {import('./types.js').BaseCtx & Partial<import('./types.js').HostedCtxExtensions>} ctx
38
- */
39
24
  export function registerTools(server, ctx) {
40
25
  const userId = ctx.userId !== undefined ? ctx.userId : undefined;
41
26
 
42
- // ─── Tool wrapper: tracks in-flight ops for graceful shutdown + timeout ────
43
-
44
27
  function tracked(handler) {
45
28
  return async (...args) => {
46
29
  if (ctx.activeOps) ctx.activeOps.count++;
@@ -49,12 +32,18 @@ export function registerTools(server, ctx) {
49
32
  return await Promise.race([
50
33
  Promise.resolve(handler(...args)),
51
34
  new Promise((_, reject) => {
52
- timer = setTimeout(() => reject(new Error("TOOL_TIMEOUT")), TOOL_TIMEOUT_MS);
35
+ timer = setTimeout(
36
+ () => reject(new Error("TOOL_TIMEOUT")),
37
+ TOOL_TIMEOUT_MS,
38
+ );
53
39
  }),
54
40
  ]);
55
41
  } catch (e) {
56
42
  if (e.message === "TOOL_TIMEOUT") {
57
- return err("Tool timed out after 60s. Try a simpler query or run `context-vault reindex` first.", "TIMEOUT");
43
+ return err(
44
+ "Tool timed out after 60s. Try a simpler query or run `context-vault reindex` first.",
45
+ "TIMEOUT",
46
+ );
58
47
  }
59
48
  throw e;
60
49
  } finally {
@@ -64,8 +53,6 @@ export function registerTools(server, ctx) {
64
53
  };
65
54
  }
66
55
 
67
- // ─── Auto-Reindex (runs once per session, on first tool call) ──────────────
68
-
69
56
  // In hosted mode, skip reindex — DB is always in sync via writeEntry→indexEntry
70
57
  let reindexDone = userId !== undefined ? true : false;
71
58
  let reindexPromise = null;
@@ -82,14 +69,20 @@ export function registerTools(server, ctx) {
82
69
  reindexDone = true;
83
70
  const total = stats.added + stats.updated + stats.removed;
84
71
  if (total > 0) {
85
- console.error(`[context-vault] Auto-reindex: +${stats.added} ~${stats.updated} -${stats.removed} (${stats.unchanged} unchanged)`);
72
+ console.error(
73
+ `[context-vault] Auto-reindex: +${stats.added} ~${stats.updated} -${stats.removed} (${stats.unchanged} unchanged)`,
74
+ );
86
75
  }
87
76
  })
88
77
  .catch((e) => {
89
78
  reindexAttempts++;
90
- console.error(`[context-vault] Auto-reindex failed (attempt ${reindexAttempts}/${MAX_REINDEX_ATTEMPTS}): ${e.message}`);
79
+ console.error(
80
+ `[context-vault] Auto-reindex failed (attempt ${reindexAttempts}/${MAX_REINDEX_ATTEMPTS}): ${e.message}`,
81
+ );
91
82
  if (reindexAttempts >= MAX_REINDEX_ATTEMPTS) {
92
- console.error(`[context-vault] Giving up on auto-reindex. Run \`context-vault reindex\` manually to diagnose.`);
83
+ console.error(
84
+ `[context-vault] Giving up on auto-reindex. Run \`context-vault reindex\` manually to diagnose.`,
85
+ );
93
86
  reindexDone = true;
94
87
  reindexFailed = true;
95
88
  } else {
@@ -100,16 +93,19 @@ export function registerTools(server, ctx) {
100
93
  return reindexPromise;
101
94
  }
102
95
 
103
- // ─── Register all tool handlers ─────────────────────────────────────────────
104
-
105
- const shared = { ensureIndexed, get reindexFailed() { return reindexFailed; } };
96
+ const shared = {
97
+ ensureIndexed,
98
+ get reindexFailed() {
99
+ return reindexFailed;
100
+ },
101
+ };
106
102
 
107
103
  for (const mod of toolModules) {
108
104
  server.tool(
109
105
  mod.name,
110
106
  mod.description,
111
107
  mod.inputSchema,
112
- tracked((args) => mod.handler(args, ctx, shared))
108
+ tracked((args) => mod.handler(args, ctx, shared)),
113
109
  );
114
110
  }
115
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-vault",
3
- "version": "2.8.0",
3
+ "version": "2.8.3",
4
4
  "type": "module",
5
5
  "description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
6
6
  "bin": {
@@ -56,7 +56,7 @@
56
56
  "@context-vault/core"
57
57
  ],
58
58
  "dependencies": {
59
- "@context-vault/core": "^2.8.0",
59
+ "@context-vault/core": "^2.8.3",
60
60
  "@modelcontextprotocol/sdk": "^1.26.0",
61
61
  "better-sqlite3": "^12.6.2",
62
62
  "sqlite-vec": "^0.1.0"