context-vault 2.7.1 → 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 (37) hide show
  1. package/README.md +39 -375
  2. package/bin/cli.js +373 -230
  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 +11 -50
  6. package/node_modules/@context-vault/core/src/capture/importers.js +64 -37
  7. package/node_modules/@context-vault/core/src/capture/index.js +57 -15
  8. package/node_modules/@context-vault/core/src/capture/ingest-url.js +80 -44
  9. package/node_modules/@context-vault/core/src/constants.js +8 -0
  10. package/node_modules/@context-vault/core/src/core/config.js +65 -29
  11. package/node_modules/@context-vault/core/src/core/files.js +8 -15
  12. package/node_modules/@context-vault/core/src/core/frontmatter.js +22 -10
  13. package/node_modules/@context-vault/core/src/core/status.js +32 -15
  14. package/node_modules/@context-vault/core/src/index/db.js +47 -34
  15. package/node_modules/@context-vault/core/src/index/embed.js +15 -5
  16. package/node_modules/@context-vault/core/src/index/index.js +206 -52
  17. package/node_modules/@context-vault/core/src/index.js +39 -6
  18. package/node_modules/@context-vault/core/src/retrieve/index.js +40 -8
  19. package/node_modules/@context-vault/core/src/server/helpers.js +8 -6
  20. package/node_modules/@context-vault/core/src/server/tools/context-status.js +24 -10
  21. package/node_modules/@context-vault/core/src/server/tools/delete-context.js +8 -3
  22. package/node_modules/@context-vault/core/src/server/tools/get-context.js +117 -35
  23. package/node_modules/@context-vault/core/src/server/tools/ingest-url.js +34 -15
  24. package/node_modules/@context-vault/core/src/server/tools/list-context.js +59 -18
  25. package/node_modules/@context-vault/core/src/server/tools/save-context.js +164 -40
  26. package/node_modules/@context-vault/core/src/server/tools/submit-feedback.js +24 -18
  27. package/node_modules/@context-vault/core/src/server/tools.js +24 -28
  28. package/node_modules/@context-vault/core/src/sync/sync.js +24 -19
  29. package/package.json +2 -2
  30. package/scripts/local-server.js +334 -122
  31. package/scripts/postinstall.js +25 -10
  32. package/scripts/prepack.js +18 -15
  33. package/src/server/index.js +78 -29
  34. package/app-dist/assets/index-DjXoWapE.css +0 -1
  35. package/app-dist/assets/index-R4n9Qz4U.js +0 -380
  36. package/app-dist/index.html +0 -16
  37. package/node_modules/@context-vault/core/src/server/types.js +0 -78
@@ -4,59 +4,93 @@ 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.
20
20
  */
21
- function validateSaveInput({ kind, title, body, tags, meta, source, identity_key }) {
21
+ function validateSaveInput({
22
+ kind,
23
+ title,
24
+ body,
25
+ tags,
26
+ meta,
27
+ source,
28
+ identity_key,
29
+ }) {
22
30
  if (kind !== undefined && kind !== null) {
23
31
  if (typeof kind !== "string" || kind.length > MAX_KIND_LENGTH) {
24
- return err(`kind must be a string, max ${MAX_KIND_LENGTH} chars`, "INVALID_INPUT");
32
+ return err(
33
+ `kind must be a string, max ${MAX_KIND_LENGTH} chars`,
34
+ "INVALID_INPUT",
35
+ );
25
36
  }
26
37
  }
27
38
  if (body !== undefined && body !== null) {
28
39
  if (typeof body !== "string" || body.length > MAX_BODY_LENGTH) {
29
- return err(`body must be a string, max ${MAX_BODY_LENGTH / 1024}KB`, "INVALID_INPUT");
40
+ return err(
41
+ `body must be a string, max ${MAX_BODY_LENGTH / 1024}KB`,
42
+ "INVALID_INPUT",
43
+ );
30
44
  }
31
45
  }
32
46
  if (title !== undefined && title !== null) {
33
47
  if (typeof title !== "string" || title.length > MAX_TITLE_LENGTH) {
34
- return err(`title must be a string, max ${MAX_TITLE_LENGTH} chars`, "INVALID_INPUT");
48
+ return err(
49
+ `title must be a string, max ${MAX_TITLE_LENGTH} chars`,
50
+ "INVALID_INPUT",
51
+ );
35
52
  }
36
53
  }
37
54
  if (tags !== undefined && tags !== null) {
38
- if (!Array.isArray(tags)) return err("tags must be an array of strings", "INVALID_INPUT");
39
- if (tags.length > MAX_TAGS_COUNT) return err(`tags: max ${MAX_TAGS_COUNT} tags allowed`, "INVALID_INPUT");
55
+ if (!Array.isArray(tags))
56
+ return err("tags must be an array of strings", "INVALID_INPUT");
57
+ if (tags.length > MAX_TAGS_COUNT)
58
+ return err(`tags: max ${MAX_TAGS_COUNT} tags allowed`, "INVALID_INPUT");
40
59
  for (const tag of tags) {
41
60
  if (typeof tag !== "string" || tag.length > MAX_TAG_LENGTH) {
42
- return err(`each tag must be a string, max ${MAX_TAG_LENGTH} chars`, "INVALID_INPUT");
61
+ return err(
62
+ `each tag must be a string, max ${MAX_TAG_LENGTH} chars`,
63
+ "INVALID_INPUT",
64
+ );
43
65
  }
44
66
  }
45
67
  }
46
68
  if (meta !== undefined && meta !== null) {
47
69
  const metaStr = JSON.stringify(meta);
48
70
  if (metaStr.length > MAX_META_LENGTH) {
49
- return err(`meta must be under ${MAX_META_LENGTH / 1024}KB when serialized`, "INVALID_INPUT");
71
+ return err(
72
+ `meta must be under ${MAX_META_LENGTH / 1024}KB when serialized`,
73
+ "INVALID_INPUT",
74
+ );
50
75
  }
51
76
  }
52
77
  if (source !== undefined && source !== null) {
53
78
  if (typeof source !== "string" || source.length > MAX_SOURCE_LENGTH) {
54
- return err(`source must be a string, max ${MAX_SOURCE_LENGTH} chars`, "INVALID_INPUT");
79
+ return err(
80
+ `source must be a string, max ${MAX_SOURCE_LENGTH} chars`,
81
+ "INVALID_INPUT",
82
+ );
55
83
  }
56
84
  }
57
85
  if (identity_key !== undefined && identity_key !== null) {
58
- if (typeof identity_key !== "string" || identity_key.length > MAX_IDENTITY_KEY_LENGTH) {
59
- return err(`identity_key must be a string, max ${MAX_IDENTITY_KEY_LENGTH} chars`, "INVALID_INPUT");
86
+ if (
87
+ typeof identity_key !== "string" ||
88
+ identity_key.length > MAX_IDENTITY_KEY_LENGTH
89
+ ) {
90
+ return err(
91
+ `identity_key must be a string, max ${MAX_IDENTITY_KEY_LENGTH} chars`,
92
+ "INVALID_INPUT",
93
+ );
60
94
  }
61
95
  }
62
96
  return null;
@@ -68,15 +102,44 @@ export const description =
68
102
  "Save knowledge to your vault. Creates a .md file and indexes it for search. Use for any kind of context: insights, decisions, patterns, references, or any custom kind. To update an existing entry, pass its `id` — omitted fields are preserved.";
69
103
 
70
104
  export const inputSchema = {
71
- id: z.string().optional().describe("Entry ULID to update. When provided, updates the existing entry instead of creating new. Omitted fields are preserved."),
72
- kind: z.string().optional().describe("Entry kind — determines folder (e.g. 'insight', 'decision', 'pattern', 'reference', or any custom kind). Required for new entries."),
105
+ id: z
106
+ .string()
107
+ .optional()
108
+ .describe(
109
+ "Entry ULID to update. When provided, updates the existing entry instead of creating new. Omitted fields are preserved.",
110
+ ),
111
+ kind: z
112
+ .string()
113
+ .optional()
114
+ .describe(
115
+ "Entry kind — determines folder (e.g. 'insight', 'decision', 'pattern', 'reference', or any custom kind). Required for new entries.",
116
+ ),
73
117
  title: z.string().optional().describe("Entry title (optional for insights)"),
74
- body: z.string().optional().describe("Main content. Required for new entries."),
75
- tags: z.array(z.string()).optional().describe("Tags for categorization and search"),
76
- meta: z.any().optional().describe("Additional structured metadata (JSON object, e.g. { language: 'js', status: 'accepted' })"),
77
- folder: z.string().optional().describe("Subfolder within the kind directory (e.g. 'react/hooks')"),
118
+ body: z
119
+ .string()
120
+ .optional()
121
+ .describe("Main content. Required for new entries."),
122
+ tags: z
123
+ .array(z.string())
124
+ .optional()
125
+ .describe("Tags for categorization and search"),
126
+ meta: z
127
+ .any()
128
+ .optional()
129
+ .describe(
130
+ "Additional structured metadata (JSON object, e.g. { language: 'js', status: 'accepted' })",
131
+ ),
132
+ folder: z
133
+ .string()
134
+ .optional()
135
+ .describe("Subfolder within the kind directory (e.g. 'react/hooks')"),
78
136
  source: z.string().optional().describe("Where this knowledge came from"),
79
- identity_key: z.string().optional().describe("Required for entity kinds (contact, project, tool, source). The unique identifier for this entity."),
137
+ identity_key: z
138
+ .string()
139
+ .optional()
140
+ .describe(
141
+ "Required for entity kinds (contact, project, tool, source). The unique identifier for this entity.",
142
+ ),
80
143
  expires_at: z.string().optional().describe("ISO date for TTL expiry"),
81
144
  };
82
145
 
@@ -85,14 +148,37 @@ export const inputSchema = {
85
148
  * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
86
149
  * @param {import('../types.js').ToolShared} shared
87
150
  */
88
- export async function handler({ id, kind, title, body, tags, meta, folder, source, identity_key, expires_at }, ctx, { ensureIndexed }) {
151
+ export async function handler(
152
+ {
153
+ id,
154
+ kind,
155
+ title,
156
+ body,
157
+ tags,
158
+ meta,
159
+ folder,
160
+ source,
161
+ identity_key,
162
+ expires_at,
163
+ },
164
+ ctx,
165
+ { ensureIndexed },
166
+ ) {
89
167
  const { config } = ctx;
90
168
  const userId = ctx.userId !== undefined ? ctx.userId : undefined;
91
169
 
92
170
  const vaultErr = ensureVaultExists(config);
93
171
  if (vaultErr) return vaultErr;
94
172
 
95
- const inputErr = validateSaveInput({ kind, title, body, tags, meta, source, identity_key });
173
+ const inputErr = validateSaveInput({
174
+ kind,
175
+ title,
176
+ body,
177
+ tags,
178
+ meta,
179
+ source,
180
+ identity_key,
181
+ });
96
182
  if (inputErr) return inputErr;
97
183
 
98
184
  // ── Update mode ──
@@ -108,10 +194,16 @@ export async function handler({ id, kind, title, body, tags, meta, folder, sourc
108
194
  }
109
195
 
110
196
  if (kind && normalizeKind(kind) !== existing.kind) {
111
- return err(`Cannot change kind (current: "${existing.kind}"). Delete and re-create instead.`, "INVALID_UPDATE");
197
+ return err(
198
+ `Cannot change kind (current: "${existing.kind}"). Delete and re-create instead.`,
199
+ "INVALID_UPDATE",
200
+ );
112
201
  }
113
202
  if (identity_key && identity_key !== existing.identity_key) {
114
- return err(`Cannot change identity_key (current: "${existing.identity_key}"). Delete and re-create instead.`, "INVALID_UPDATE");
203
+ return err(
204
+ `Cannot change identity_key (current: "${existing.identity_key}"). Delete and re-create instead.`,
205
+ "INVALID_UPDATE",
206
+ );
115
207
  }
116
208
 
117
209
  // Decrypt existing entry before merge if encrypted
@@ -122,9 +214,18 @@ export async function handler({ id, kind, title, body, tags, meta, folder, sourc
122
214
  if (decrypted.meta) existing.meta = JSON.stringify(decrypted.meta);
123
215
  }
124
216
 
125
- const entry = updateEntryFile(ctx, existing, { title, body, tags, meta, source, expires_at });
217
+ const entry = updateEntryFile(ctx, existing, {
218
+ title,
219
+ body,
220
+ tags,
221
+ meta,
222
+ source,
223
+ expires_at,
224
+ });
126
225
  await indexEntry(ctx, entry);
127
- const relPath = entry.filePath ? entry.filePath.replace(config.vaultDir + "/", "") : entry.filePath;
226
+ const relPath = entry.filePath
227
+ ? entry.filePath.replace(config.vaultDir + "/", "")
228
+ : entry.filePath;
128
229
  const parts = [`✓ Updated ${entry.kind} → ${relPath}`, ` id: ${entry.id}`];
129
230
  if (entry.title) parts.push(` title: ${entry.title}`);
130
231
  const entryTags = entry.tags || [];
@@ -137,23 +238,33 @@ export async function handler({ id, kind, title, body, tags, meta, folder, sourc
137
238
  if (!kind) return err("Required: kind (for new entries)", "INVALID_INPUT");
138
239
  const kindErr = ensureValidKind(kind);
139
240
  if (kindErr) return kindErr;
140
- if (!body?.trim()) return err("Required: body (for new entries)", "INVALID_INPUT");
241
+ if (!body?.trim())
242
+ return err("Required: body (for new entries)", "INVALID_INPUT");
141
243
 
142
244
  // Normalize kind to canonical singular form (e.g. "insights" → "insight")
143
245
  const normalizedKind = normalizeKind(kind);
144
246
 
145
247
  if (categoryFor(normalizedKind) === "entity" && !identity_key) {
146
- return err(`Entity kind "${normalizedKind}" requires identity_key`, "MISSING_IDENTITY_KEY");
248
+ return err(
249
+ `Entity kind "${normalizedKind}" requires identity_key`,
250
+ "MISSING_IDENTITY_KEY",
251
+ );
147
252
  }
148
253
 
149
254
  // Hosted tier limit enforcement (skipped in local mode — no checkLimits on ctx)
150
255
  if (ctx.checkLimits) {
151
256
  const usage = ctx.checkLimits();
152
257
  if (usage.entryCount >= usage.maxEntries) {
153
- return err(`Entry limit reached (${usage.maxEntries}). Upgrade to Pro for unlimited entries.`, "LIMIT_EXCEEDED");
258
+ return err(
259
+ `Entry limit reached (${usage.maxEntries}). Upgrade to Pro for unlimited entries.`,
260
+ "LIMIT_EXCEEDED",
261
+ );
154
262
  }
155
263
  if (usage.storageMb >= usage.maxStorageMb) {
156
- return err(`Storage limit reached (${usage.maxStorageMb} MB). Upgrade to Pro for more storage.`, "LIMIT_EXCEEDED");
264
+ return err(
265
+ `Storage limit reached (${usage.maxStorageMb} MB). Upgrade to Pro for more storage.`,
266
+ "LIMIT_EXCEEDED",
267
+ );
157
268
  }
158
269
  }
159
270
 
@@ -163,8 +274,21 @@ export async function handler({ id, kind, title, body, tags, meta, folder, sourc
163
274
  if (folder) mergedMeta.folder = folder;
164
275
  const finalMeta = Object.keys(mergedMeta).length ? mergedMeta : undefined;
165
276
 
166
- const entry = await captureAndIndex(ctx, { kind: normalizedKind, title, body, meta: finalMeta, tags, source, folder, identity_key, expires_at, userId }, indexEntry);
167
- const relPath = entry.filePath ? entry.filePath.replace(config.vaultDir + "/", "") : entry.filePath;
277
+ const entry = await captureAndIndex(ctx, {
278
+ kind: normalizedKind,
279
+ title,
280
+ body,
281
+ meta: finalMeta,
282
+ tags,
283
+ source,
284
+ folder,
285
+ identity_key,
286
+ expires_at,
287
+ userId,
288
+ });
289
+ const relPath = entry.filePath
290
+ ? entry.filePath.replace(config.vaultDir + "/", "")
291
+ : entry.filePath;
168
292
  const parts = [`✓ Saved ${normalizedKind} → ${relPath}`, ` id: ${entry.id}`];
169
293
  if (title) parts.push(` title: ${title}`);
170
294
  if (tags?.length) parts.push(` tags: ${tags.join(", ")}`);
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  import { captureAndIndex } from "../../capture/index.js";
3
- import { indexEntry } from "../../index/index.js";
4
3
  import { ok, ensureVaultExists } from "../helpers.js";
5
4
 
6
5
  export const name = "submit_feedback";
@@ -12,7 +11,10 @@ export const inputSchema = {
12
11
  type: z.enum(["bug", "feature", "improvement"]).describe("Type of feedback"),
13
12
  title: z.string().describe("Short summary of the feedback"),
14
13
  body: z.string().describe("Detailed description"),
15
- severity: z.enum(["low", "medium", "high"]).optional().describe("Severity level (default: medium)"),
14
+ severity: z
15
+ .enum(["low", "medium", "high"])
16
+ .optional()
17
+ .describe("Severity level (default: medium)"),
16
18
  };
17
19
 
18
20
  /**
@@ -20,7 +22,11 @@ export const inputSchema = {
20
22
  * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
21
23
  * @param {import('../types.js').ToolShared} shared
22
24
  */
23
- export async function handler({ type, title, body, severity }, ctx, { ensureIndexed }) {
25
+ export async function handler(
26
+ { type, title, body, severity },
27
+ ctx,
28
+ { ensureIndexed },
29
+ ) {
24
30
  const { config } = ctx;
25
31
  const userId = ctx.userId !== undefined ? ctx.userId : undefined;
26
32
 
@@ -30,20 +36,20 @@ export async function handler({ type, title, body, severity }, ctx, { ensureInde
30
36
  await ensureIndexed();
31
37
 
32
38
  const effectiveSeverity = severity || "medium";
33
- const entry = await captureAndIndex(
34
- ctx,
35
- {
36
- kind: "feedback",
37
- title,
38
- body,
39
- tags: [type, effectiveSeverity],
40
- source: "submit_feedback",
41
- meta: { feedback_type: type, severity: effectiveSeverity, status: "new" },
42
- userId,
43
- },
44
- indexEntry
39
+ const entry = await captureAndIndex(ctx, {
40
+ kind: "feedback",
41
+ title,
42
+ body,
43
+ tags: [type, effectiveSeverity],
44
+ source: "submit_feedback",
45
+ meta: { feedback_type: type, severity: effectiveSeverity, status: "new" },
46
+ userId,
47
+ });
48
+
49
+ const relPath = entry.filePath
50
+ ? entry.filePath.replace(config.vaultDir + "/", "")
51
+ : entry.filePath;
52
+ return ok(
53
+ `Feedback submitted: ${type} [${effectiveSeverity}] → ${relPath}\n id: ${entry.id}\n title: ${title}`,
45
54
  );
46
-
47
- const relPath = entry.filePath ? entry.filePath.replace(config.vaultDir + "/", "") : entry.filePath;
48
- return ok(`Feedback submitted: ${type} [${effectiveSeverity}] → ${relPath}\n id: ${entry.id}\n title: ${title}`);
49
55
  }
@@ -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
  }
@@ -9,7 +9,6 @@
9
9
  */
10
10
 
11
11
  import { captureAndIndex } from "../capture/index.js";
12
- import { indexEntry } from "../index/index.js";
13
12
 
14
13
  /**
15
14
  * Build a manifest of local vault entries (id → { id, created_at, kind, title }).
@@ -19,7 +18,9 @@ import { indexEntry } from "../index/index.js";
19
18
  */
20
19
  export function buildLocalManifest(ctx) {
21
20
  const rows = ctx.db
22
- .prepare("SELECT id, created_at, kind, title FROM vault WHERE (expires_at IS NULL OR expires_at > datetime('now'))")
21
+ .prepare(
22
+ "SELECT id, created_at, kind, title FROM vault WHERE (expires_at IS NULL OR expires_at > datetime('now'))",
23
+ )
23
24
  .all();
24
25
 
25
26
  const manifest = new Map();
@@ -111,7 +112,10 @@ export function computeSyncPlan(local, remote) {
111
112
  * @param {{ hostedUrl: string, apiKey: string, plan: SyncPlan, onProgress?: (phase: string, current: number, total: number) => void }} opts
112
113
  * @returns {Promise<{ pushed: number, pulled: number, failed: number, errors: string[] }>}
113
114
  */
114
- export async function executeSync(ctx, { hostedUrl, apiKey, plan, onProgress }) {
115
+ export async function executeSync(
116
+ ctx,
117
+ { hostedUrl, apiKey, plan, onProgress },
118
+ ) {
115
119
  let pushed = 0;
116
120
  let pulled = 0;
117
121
  let failed = 0;
@@ -157,7 +161,9 @@ export async function executeSync(ctx, { hostedUrl, apiKey, plan, onProgress })
157
161
  if (!response.ok) {
158
162
  const errData = await response.json().catch(() => ({}));
159
163
  failed += batch.length;
160
- errors.push(`Push batch failed: HTTP ${response.status} — ${errData.error || "unknown"}`);
164
+ errors.push(
165
+ `Push batch failed: HTTP ${response.status} — ${errData.error || "unknown"}`,
166
+ );
161
167
  continue;
162
168
  }
163
169
 
@@ -199,21 +205,20 @@ export async function executeSync(ctx, { hostedUrl, apiKey, plan, onProgress })
199
205
  if (onProgress) onProgress("pull", i + 1, entriesToPull.length);
200
206
 
201
207
  try {
202
- await captureAndIndex(
203
- ctx,
204
- {
205
- kind: entry.kind,
206
- title: entry.title,
207
- body: entry.body,
208
- meta: entry.meta && typeof entry.meta === "object" ? entry.meta : undefined,
209
- tags: Array.isArray(entry.tags) ? entry.tags : undefined,
210
- source: entry.source || "sync-pull",
211
- identity_key: entry.identity_key,
212
- expires_at: entry.expires_at,
213
- userId: ctx.userId || null,
214
- },
215
- indexEntry
216
- );
208
+ await captureAndIndex(ctx, {
209
+ kind: entry.kind,
210
+ title: entry.title,
211
+ body: entry.body,
212
+ meta:
213
+ entry.meta && typeof entry.meta === "object"
214
+ ? entry.meta
215
+ : undefined,
216
+ tags: Array.isArray(entry.tags) ? entry.tags : undefined,
217
+ source: entry.source || "sync-pull",
218
+ identity_key: entry.identity_key,
219
+ expires_at: entry.expires_at,
220
+ userId: ctx.userId || null,
221
+ });
217
222
  pulled++;
218
223
  } catch (err) {
219
224
  failed++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-vault",
3
- "version": "2.7.1",
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.7.1",
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"