context-vault 3.1.7 → 3.2.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.
Files changed (131) hide show
  1. package/bin/cli.js +11 -11
  2. package/dist/archive.d.ts +23 -0
  3. package/dist/archive.d.ts.map +1 -0
  4. package/dist/archive.js +197 -0
  5. package/dist/archive.js.map +1 -0
  6. package/dist/consolidation.d.ts +14 -0
  7. package/dist/consolidation.d.ts.map +1 -0
  8. package/dist/consolidation.js +59 -0
  9. package/dist/consolidation.js.map +1 -0
  10. package/dist/error-log.d.ts +4 -0
  11. package/dist/error-log.d.ts.map +1 -0
  12. package/dist/error-log.js +33 -0
  13. package/dist/error-log.js.map +1 -0
  14. package/dist/helpers.d.ts +10 -0
  15. package/dist/helpers.d.ts.map +1 -0
  16. package/dist/helpers.js +42 -0
  17. package/dist/helpers.js.map +1 -0
  18. package/dist/linking.d.ts +13 -0
  19. package/dist/linking.d.ts.map +1 -0
  20. package/dist/linking.js +86 -0
  21. package/dist/linking.js.map +1 -0
  22. package/dist/migrate-dirs.d.ts +16 -0
  23. package/dist/migrate-dirs.d.ts.map +1 -0
  24. package/dist/migrate-dirs.js +127 -0
  25. package/dist/migrate-dirs.js.map +1 -0
  26. package/dist/register-tools.d.ts +3 -0
  27. package/dist/register-tools.d.ts.map +1 -0
  28. package/dist/register-tools.js +161 -0
  29. package/dist/register-tools.js.map +1 -0
  30. package/dist/server.d.ts +3 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +241 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/status.d.ts +18 -0
  35. package/dist/status.d.ts.map +1 -0
  36. package/dist/status.js +265 -0
  37. package/dist/status.js.map +1 -0
  38. package/dist/telemetry.d.ts +6 -0
  39. package/dist/telemetry.d.ts.map +1 -0
  40. package/dist/telemetry.js +74 -0
  41. package/dist/telemetry.js.map +1 -0
  42. package/dist/temporal.d.ts +9 -0
  43. package/dist/temporal.d.ts.map +1 -0
  44. package/dist/temporal.js +76 -0
  45. package/dist/temporal.js.map +1 -0
  46. package/dist/tools/clear-context.d.ts +11 -0
  47. package/dist/tools/clear-context.d.ts.map +1 -0
  48. package/dist/tools/clear-context.js +28 -0
  49. package/dist/tools/clear-context.js.map +1 -0
  50. package/dist/tools/context-status.d.ts +6 -0
  51. package/dist/tools/context-status.d.ts.map +1 -0
  52. package/dist/tools/context-status.js +160 -0
  53. package/dist/tools/context-status.js.map +1 -0
  54. package/dist/tools/create-snapshot.d.ts +13 -0
  55. package/dist/tools/create-snapshot.d.ts.map +1 -0
  56. package/dist/tools/create-snapshot.js +161 -0
  57. package/dist/tools/create-snapshot.js.map +1 -0
  58. package/dist/tools/delete-context.d.ts +9 -0
  59. package/dist/tools/delete-context.d.ts.map +1 -0
  60. package/dist/tools/delete-context.js +45 -0
  61. package/dist/tools/delete-context.js.map +1 -0
  62. package/dist/tools/get-context.d.ts +83 -0
  63. package/dist/tools/get-context.d.ts.map +1 -0
  64. package/dist/tools/get-context.js +598 -0
  65. package/dist/tools/get-context.js.map +1 -0
  66. package/dist/tools/ingest-project.d.ts +11 -0
  67. package/dist/tools/ingest-project.d.ts.map +1 -0
  68. package/dist/tools/ingest-project.js +226 -0
  69. package/dist/tools/ingest-project.js.map +1 -0
  70. package/dist/tools/ingest-url.d.ts +11 -0
  71. package/dist/tools/ingest-url.d.ts.map +1 -0
  72. package/dist/tools/ingest-url.js +62 -0
  73. package/dist/tools/ingest-url.js.map +1 -0
  74. package/dist/tools/list-buckets.d.ts +9 -0
  75. package/dist/tools/list-buckets.d.ts.map +1 -0
  76. package/dist/tools/list-buckets.js +76 -0
  77. package/dist/tools/list-buckets.js.map +1 -0
  78. package/dist/tools/list-context.d.ts +19 -0
  79. package/dist/tools/list-context.d.ts.map +1 -0
  80. package/dist/tools/list-context.js +110 -0
  81. package/dist/tools/list-context.js.map +1 -0
  82. package/dist/tools/save-context.d.ts +36 -0
  83. package/dist/tools/save-context.d.ts.map +1 -0
  84. package/dist/tools/save-context.js +466 -0
  85. package/dist/tools/save-context.js.map +1 -0
  86. package/dist/tools/session-start.d.ts +11 -0
  87. package/dist/tools/session-start.d.ts.map +1 -0
  88. package/dist/tools/session-start.js +224 -0
  89. package/dist/tools/session-start.js.map +1 -0
  90. package/dist/types.d.ts +37 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +2 -0
  93. package/dist/types.js.map +1 -0
  94. package/node_modules/@context-vault/core/dist/constants.d.ts +1 -1
  95. package/node_modules/@context-vault/core/dist/constants.d.ts.map +1 -1
  96. package/node_modules/@context-vault/core/dist/constants.js +1 -1
  97. package/node_modules/@context-vault/core/dist/constants.js.map +1 -1
  98. package/node_modules/@context-vault/core/dist/search.d.ts +2 -1
  99. package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
  100. package/node_modules/@context-vault/core/dist/search.js +6 -2
  101. package/node_modules/@context-vault/core/dist/search.js.map +1 -1
  102. package/node_modules/@context-vault/core/dist/types.d.ts +1 -0
  103. package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -1
  104. package/node_modules/@context-vault/core/package.json +1 -1
  105. package/node_modules/@context-vault/core/src/constants.ts +1 -1
  106. package/node_modules/@context-vault/core/src/search.ts +7 -0
  107. package/node_modules/@context-vault/core/src/types.ts +1 -0
  108. package/package.json +10 -4
  109. package/src/{archive.js → archive.ts} +63 -30
  110. package/src/consolidation.ts +78 -0
  111. package/src/{error-log.js → error-log.ts} +3 -3
  112. package/src/{helpers.js → helpers.ts} +11 -5
  113. package/src/{linking.js → linking.ts} +14 -9
  114. package/src/{migrate-dirs.js → migrate-dirs.ts} +21 -8
  115. package/src/{register-tools.js → register-tools.ts} +21 -11
  116. package/src/{server.js → server.ts} +26 -20
  117. package/src/{status.js → status.ts} +60 -38
  118. package/src/{telemetry.js → telemetry.ts} +8 -4
  119. package/src/{temporal.js → temporal.ts} +10 -3
  120. package/src/tools/{clear-context.js → clear-context.ts} +2 -5
  121. package/src/tools/{context-status.js → context-status.ts} +6 -9
  122. package/src/tools/{create-snapshot.js → create-snapshot.ts} +11 -10
  123. package/src/tools/{delete-context.js → delete-context.ts} +9 -9
  124. package/src/tools/{get-context.js → get-context.ts} +72 -62
  125. package/src/tools/{ingest-project.js → ingest-project.ts} +19 -11
  126. package/src/tools/{ingest-url.js → ingest-url.ts} +11 -8
  127. package/src/tools/{list-buckets.js → list-buckets.ts} +11 -15
  128. package/src/tools/{list-context.js → list-context.ts} +13 -15
  129. package/src/tools/{save-context.js → save-context.ts} +54 -42
  130. package/src/tools/{session-start.js → session-start.ts} +29 -20
  131. package/src/types.ts +29 -0
@@ -9,6 +9,7 @@ import { resolveTemporalParams } from '../temporal.js';
9
9
  import { collectLinkedEntries } from '../linking.js';
10
10
  import { ok, err, errWithHint } from '../helpers.js';
11
11
  import { isEmbedAvailable } from '@context-vault/core/embed';
12
+ import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
12
13
 
13
14
  const STALE_DUPLICATE_DAYS = 7;
14
15
  const DEFAULT_PIVOT_COUNT = 2;
@@ -21,7 +22,14 @@ const BRIEF_SCORE_BOOST = 0.05;
21
22
  * Truncate a body string to ~SKELETON_BODY_CHARS, breaking at sentence or
22
23
  * word boundary. Returns the truncated string with "..." appended.
23
24
  */
24
- export function skeletonBody(body) {
25
+ function truncateBody(body: string | null | undefined, limit: number): string {
26
+ if (!body) return '';
27
+ if (limit === 0) return body;
28
+ if (body.length <= limit) return body;
29
+ return body.slice(0, limit) + '...';
30
+ }
31
+
32
+ export function skeletonBody(body: string | null | undefined): string {
25
33
  if (!body) return '';
26
34
  if (body.length <= SKELETON_BODY_CHARS) return body;
27
35
  const slice = body.slice(0, SKELETON_BODY_CHARS);
@@ -50,10 +58,13 @@ export function skeletonBody(body) {
50
58
  * rows already fetched from the DB.
51
59
  *
52
60
  * @param {Array} entries - Result rows (as returned by hybridSearch / filter-only mode)
53
- * @param {import('../types.js').BaseCtx} _ctx - Unused for now; reserved for future DB look-ups
61
+ * @param {import('@context-vault/core/types').BaseCtx} _ctx
54
62
  * @returns {Array<{entry_a_id: string, entry_b_id: string, reason: string, recommendation: string}>}
55
63
  */
56
- export function detectConflicts(entries, _ctx) {
64
+ export function detectConflicts(
65
+ entries: any[],
66
+ _ctx: any
67
+ ): Array<{ entry_a_id: string; entry_b_id: string; reason: string; recommendation: string }> {
57
68
  const conflicts = [];
58
69
  const idSet = new Set(entries.map((e) => e.id));
59
70
 
@@ -90,14 +101,14 @@ export function detectConflicts(entries, _ctx) {
90
101
  if (!tagsA.length || !tagsB.length) continue;
91
102
 
92
103
  const tagsSetA = new Set(tagsA);
93
- const sharedTag = tagsB.some((t) => tagsSetA.has(t));
104
+ const sharedTag = tagsB.some((t: any) => tagsSetA.has(t));
94
105
  if (!sharedTag) continue;
95
106
 
96
107
  const dateA = new Date(a.updated_at || a.created_at);
97
108
  const dateB = new Date(b.updated_at || b.created_at);
98
109
  if (isNaN(dateA.getTime()) || isNaN(dateB.getTime())) continue;
99
110
 
100
- const diffDays = Math.abs(dateA - dateB) / 86400000;
111
+ const diffDays = Math.abs(dateA.getTime() - dateB.getTime()) / 86400000;
101
112
  if (diffDays <= STALE_DUPLICATE_DAYS) continue;
102
113
 
103
114
  const [older, newer] = dateA < dateB ? [a, b] : [b, a];
@@ -129,11 +140,15 @@ export function detectConflicts(entries, _ctx) {
129
140
  * @param {{ tagThreshold?: number, maxAgeDays?: number }} opts - Configurable thresholds
130
141
  * @returns {Array<{tag: string, entry_count: number, last_snapshot_age_days: number|null}>}
131
142
  */
132
- export function detectConsolidationHints(entries, db, opts = {}) {
143
+ export function detectConsolidationHints(
144
+ entries: any[],
145
+ db: any,
146
+ opts: { tagThreshold?: number; maxAgeDays?: number } = {}
147
+ ): Array<{ tag: string; entry_count: number; last_snapshot_age_days: number | null }> {
133
148
  const tagThreshold = opts.tagThreshold ?? CONSOLIDATION_TAG_THRESHOLD;
134
149
  const maxAgeDays = opts.maxAgeDays ?? CONSOLIDATION_SNAPSHOT_MAX_AGE_DAYS;
135
150
 
136
- const candidateTags = new Set();
151
+ const candidateTags = new Set<string>();
137
152
  for (const entry of entries) {
138
153
  if (entry.kind === 'brief') continue;
139
154
  const entryTags = entry.tags ? JSON.parse(entry.tags) : [];
@@ -148,15 +163,11 @@ export function detectConsolidationHints(entries, db, opts = {}) {
148
163
  for (const tag of candidateTags) {
149
164
  let vaultCount = 0;
150
165
  try {
151
- // When userId is defined (hosted mode), scope to that user.
152
- // When userId is undefined (local mode), no user scoping — column may not exist.
153
- const userClause = '';
154
- const countParams = false ? [`%"${tag}"%`] : [`%"${tag}"%`];
155
166
  const countRow = db
156
167
  .prepare(
157
- `SELECT COUNT(*) as c FROM vault WHERE kind != 'brief' AND tags LIKE ?${userClause} AND (expires_at IS NULL OR expires_at > datetime('now')) AND superseded_by IS NULL`
168
+ `SELECT COUNT(*) as c FROM vault WHERE kind != 'brief' AND tags LIKE ? AND (expires_at IS NULL OR expires_at > datetime('now')) AND superseded_by IS NULL`
158
169
  )
159
- .get(...countParams);
170
+ .get(`%"${tag}"%`);
160
171
  vaultCount = countRow?.c ?? 0;
161
172
  } catch {
162
173
  continue;
@@ -166,13 +177,11 @@ export function detectConsolidationHints(entries, db, opts = {}) {
166
177
 
167
178
  let lastSnapshotAgeDays = null;
168
179
  try {
169
- const userClause = '';
170
- const params = false ? [`%"${tag}"%`] : [`%"${tag}"%`];
171
180
  const recentBrief = db
172
181
  .prepare(
173
- `SELECT created_at FROM vault WHERE kind = 'brief' AND tags LIKE ?${userClause} ORDER BY created_at DESC LIMIT 1`
182
+ `SELECT created_at FROM vault WHERE kind = 'brief' AND tags LIKE ? ORDER BY created_at DESC LIMIT 1`
174
183
  )
175
- .get(...params);
184
+ .get(`%"${tag}"%`);
176
185
 
177
186
  if (recentBrief) {
178
187
  lastSnapshotAgeDays = Math.round(
@@ -202,7 +211,7 @@ export function detectConsolidationHints(entries, db, opts = {}) {
202
211
  * @param {object} entry - DB row with source_files JSON column
203
212
  * @returns {{ stale: boolean, stale_reason: string } | null}
204
213
  */
205
- function checkStaleness(entry) {
214
+ function checkStaleness(entry: any): { stale: boolean; stale_reason: string } | null {
206
215
  if (!entry.source_files) return null;
207
216
  let sourceFiles;
208
217
  try {
@@ -323,13 +332,22 @@ export const inputSchema = {
323
332
  .describe(
324
333
  'If true, follow related_to links from result entries and include linked entries (forward links) and backlinks (entries that reference the results). Enables bidirectional graph traversal.'
325
334
  ),
335
+ body_limit: z
336
+ .number()
337
+ .min(0)
338
+ .max(10000)
339
+ .optional()
340
+ .describe(
341
+ 'Max characters of body to return per entry (default 300). Set to 0 for no limit. Does not affect skeleton entries.'
342
+ ),
343
+ strict: z
344
+ .boolean()
345
+ .optional()
346
+ .describe(
347
+ 'When true with identity_key, return a clear "not found" instead of falling through to semantic search on miss. Default: false.'
348
+ ),
326
349
  };
327
350
 
328
- /**
329
- * @param {object} args
330
- * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
331
- * @param {import('../types.js').ToolShared} shared
332
- */
333
351
  export async function handler(
334
352
  {
335
353
  query,
@@ -349,10 +367,12 @@ export async function handler(
349
367
  include_events,
350
368
  scope,
351
369
  follow_links,
352
- },
353
- ctx,
354
- { ensureIndexed, reindexFailed }
355
- ) {
370
+ body_limit,
371
+ strict,
372
+ }: Record<string, any>,
373
+ ctx: LocalCtx,
374
+ { ensureIndexed, reindexFailed }: SharedCtx
375
+ ): Promise<ToolResult> {
356
376
  const { config } = ctx;
357
377
 
358
378
  // Resolve natural-language temporal shortcuts → ISO date strings
@@ -375,7 +395,7 @@ export async function handler(
375
395
  const scopedCategory = !category && effectiveScope === 'events' ? 'event' : category;
376
396
  const shouldExcludeEvents = hasQuery && effectiveScope === 'hot' && !scopedCategory;
377
397
  // Expand buckets to bucket: prefixed tags and merge with explicit tags
378
- const bucketTags = buckets?.length ? buckets.map((b) => `bucket:${b}`) : [];
398
+ const bucketTags = buckets?.length ? buckets.map((b: string) => `bucket:${b}`) : [];
379
399
  const effectiveTags = [...(tags ?? []), ...bucketTags];
380
400
  const hasFilters =
381
401
  kind || scopedCategory || effectiveTags.length || since || until || identity_key;
@@ -391,7 +411,7 @@ export async function handler(
391
411
  // Gap 1: Entity exact-match by identity_key
392
412
  if (identity_key) {
393
413
  if (!kindFilter) return err('identity_key requires kind to be specified', 'INVALID_INPUT');
394
- const match = ctx.stmts.getByIdentityKey.get(kindFilter, identity_key);
414
+ const match = ctx.stmts.getByIdentityKey.get(kindFilter, identity_key) as any;
395
415
  if (match) {
396
416
  const entryTags = match.tags ? JSON.parse(match.tags) : [];
397
417
  const tagStr = entryTags.length ? entryTags.join(', ') : 'none';
@@ -403,9 +423,13 @@ export async function handler(
403
423
  `## Entity Match (exact)\n`,
404
424
  `### ${match.title || '(untitled)'} [${match.kind}/${match.category}]`,
405
425
  `1.000 · ${tagStr} · ${relPath} · id: \`${match.id}\``,
406
- match.body?.slice(0, 300) + (match.body?.length > 300 ? '...' : ''),
426
+ truncateBody(match.body, body_limit ?? 300),
407
427
  ];
408
428
  return ok(lines.join('\n'));
429
+ } else if (strict) {
430
+ return ok(
431
+ `## Entity Match (exact)\n\nNo entry found for identity_key: \`${identity_key}\` (kind: ${kindFilter}).\n\nUse strict: false (default) to fall through to semantic search.`
432
+ );
409
433
  }
410
434
  // Fall through to semantic search as fallback
411
435
  }
@@ -428,7 +452,7 @@ export async function handler(
428
452
  ? Math.min(effectiveLimit * 10, MAX_FETCH_LIMIT)
429
453
  : effectiveLimit;
430
454
 
431
- let filtered;
455
+ let filtered: any[];
432
456
  if (hasQuery) {
433
457
  // Hybrid search mode
434
458
  const sorted = await hybridSearch(ctx, query, {
@@ -439,8 +463,8 @@ export async function handler(
439
463
  until: effectiveUntil,
440
464
  limit: fetchLimit,
441
465
  decayDays: config.eventDecayDays || 30,
442
-
443
466
  includeSuperseeded: include_superseded ?? false,
467
+ includeEphemeral: include_ephemeral ?? false,
444
468
  });
445
469
 
446
470
  // Post-filter by tags if provided, then apply requested limit
@@ -474,6 +498,9 @@ export async function handler(
474
498
  clauses.push('created_at <= ?');
475
499
  params.push(effectiveUntil);
476
500
  }
501
+ if (!include_ephemeral) {
502
+ clauses.push("tier != 'ephemeral'");
503
+ }
477
504
  clauses.push("(expires_at IS NULL OR expires_at > datetime('now'))");
478
505
  if (!include_superseded) {
479
506
  clauses.push('superseded_by IS NULL');
@@ -487,7 +514,7 @@ export async function handler(
487
514
  .all(...params);
488
515
  } catch (e) {
489
516
  return errWithHint(
490
- e.message,
517
+ e instanceof Error ? e.message : String(e),
491
518
  'DB_ERROR',
492
519
  'context-vault get_context DB_ERROR. Check `cat ~/.context-mcp/error.log | tail -5` and help me debug.'
493
520
  );
@@ -496,9 +523,9 @@ export async function handler(
496
523
  // Post-filter by tags if provided, then apply requested limit
497
524
  filtered = effectiveTags.length
498
525
  ? rows
499
- .filter((r) => {
526
+ .filter((r: any) => {
500
527
  const entryTags = r.tags ? JSON.parse(r.tags) : [];
501
- return effectiveTags.some((t) => entryTags.includes(t));
528
+ return effectiveTags.some((t: string) => entryTags.includes(t));
502
529
  })
503
530
  .slice(0, effectiveLimit)
504
531
  : rows;
@@ -512,16 +539,11 @@ export async function handler(
512
539
  for (const r of filtered) {
513
540
  if (r.kind === 'brief') r.score = (r.score || 0) + BRIEF_SCORE_BOOST;
514
541
  }
515
- filtered.sort((a, b) => b.score - a.score);
516
-
517
- // Tier filter: exclude ephemeral entries by default (NULL tier treated as working)
518
- if (!include_ephemeral) {
519
- filtered = filtered.filter((r) => r.tier !== 'ephemeral');
520
- }
542
+ filtered.sort((a: any, b: any) => b.score - a.score);
521
543
 
522
544
  // Event category filter: exclude events from semantic search by default
523
545
  if (shouldExcludeEvents) {
524
- filtered = filtered.filter((r) => r.category !== 'event');
546
+ filtered = filtered.filter((r: any) => r.category !== 'event');
525
547
  }
526
548
 
527
549
  if (!filtered.length) {
@@ -538,18 +560,6 @@ export async function handler(
538
560
  );
539
561
  }
540
562
 
541
- // Decrypt encrypted entries if ctx.decrypt is available
542
- if (ctx.decrypt) {
543
- for (const r of filtered) {
544
- if (r.body_encrypted) {
545
- const decrypted = await ctx.decrypt(r);
546
- r.body = decrypted.body;
547
- if (decrypted.title) r.title = decrypted.title;
548
- if (decrypted.meta) r.meta = JSON.stringify(decrypted.meta);
549
- }
550
- }
551
- }
552
-
553
563
  // Token-budgeted packing
554
564
  let tokensBudget = null;
555
565
  let tokensUsed = null;
@@ -625,7 +635,7 @@ export async function handler(
625
635
  if (isSkeleton) {
626
636
  lines.push(skeletonBody(r.body));
627
637
  } else {
628
- lines.push(r.body?.slice(0, 300) + (r.body?.length > 300 ? '...' : ''));
638
+ lines.push(truncateBody(r.body, body_limit ?? 300));
629
639
  }
630
640
  lines.push('');
631
641
  }
@@ -645,10 +655,10 @@ export async function handler(
645
655
 
646
656
  // Graph traversal: follow related_to links bidirectionally
647
657
  if (follow_links) {
648
- const { forward, backward } = collectLinkedEntries(ctx.db, filtered);
649
- const allLinked = [...forward, ...backward];
658
+ const { forward, backward } = collectLinkedEntries(ctx.db, filtered as any);
659
+ const allLinked: any[] = [...(forward as any[]), ...(backward as any[])];
650
660
  const seen = new Set();
651
- const uniqueLinked = allLinked.filter((e) => {
661
+ const uniqueLinked = allLinked.filter((e: any) => {
652
662
  if (seen.has(e.id)) return false;
653
663
  seen.add(e.id);
654
664
  return true;
@@ -657,7 +667,7 @@ export async function handler(
657
667
  if (uniqueLinked.length > 0) {
658
668
  lines.push(`## Linked Entries (${uniqueLinked.length} via related_to)\n`);
659
669
  for (const r of uniqueLinked) {
660
- const direction = forward.some((f) => f.id === r.id) ? '→ forward' : '← backlink';
670
+ const direction = forward.some((f: any) => f.id === r.id) ? '→ forward' : '← backlink';
661
671
  const entryTags = r.tags ? JSON.parse(r.tags) : [];
662
672
  const tagStr = entryTags.length ? entryTags.join(', ') : 'none';
663
673
  const relPath =
@@ -666,7 +676,7 @@ export async function handler(
666
676
  : r.file_path || 'n/a';
667
677
  lines.push(`### ${r.title || '(untitled)'} [${r.kind}/${r.category}] ${direction}`);
668
678
  lines.push(`${tagStr} · ${relPath} · id: \`${r.id}\``);
669
- lines.push(r.body?.slice(0, 200) + (r.body?.length > 200 ? '...' : ''));
679
+ lines.push(truncateBody(r.body, body_limit ?? 300));
670
680
  lines.push('');
671
681
  }
672
682
  } else {
@@ -696,8 +706,8 @@ export async function handler(
696
706
  }
697
707
  }
698
708
 
699
- const result = ok(lines.join('\n'));
700
- const meta = {};
709
+ const result: ToolResult = ok(lines.join('\n'));
710
+ const meta: Record<string, unknown> = {};
701
711
  meta.scope = effectiveScope;
702
712
  if (tokensBudget != null) {
703
713
  meta.tokens_used = tokensUsed;
@@ -4,6 +4,7 @@ import { execSync } from 'node:child_process';
4
4
  import { join, basename } from 'node:path';
5
5
  import { captureAndIndex } from '@context-vault/core/capture';
6
6
  import { ok, err, ensureVaultExists } from '../helpers.js';
7
+ import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
7
8
 
8
9
  export const name = 'ingest_project';
9
10
 
@@ -19,7 +20,7 @@ export const inputSchema = {
19
20
  pillar: z.string().optional().describe('Parent pillar/domain name — creates a bucket:pillar tag'),
20
21
  };
21
22
 
22
- function safeRead(filePath) {
23
+ function safeRead(filePath: string): string | null {
23
24
  try {
24
25
  return readFileSync(filePath, 'utf-8');
25
26
  } catch {
@@ -27,7 +28,7 @@ function safeRead(filePath) {
27
28
  }
28
29
  }
29
30
 
30
- function safeExec(cmd, cwd) {
31
+ function safeExec(cmd: string, cwd: string): string | null {
31
32
  try {
32
33
  return execSync(cmd, {
33
34
  cwd,
@@ -39,7 +40,7 @@ function safeExec(cmd, cwd) {
39
40
  }
40
41
  }
41
42
 
42
- function detectTechStack(projectPath, pkgJson) {
43
+ function detectTechStack(projectPath: string, pkgJson: any): string[] {
43
44
  const stack = [];
44
45
 
45
46
  if (
@@ -78,7 +79,7 @@ function detectTechStack(projectPath, pkgJson) {
78
79
  return [...new Set(stack)];
79
80
  }
80
81
 
81
- function extractReadmeDescription(projectPath) {
82
+ function extractReadmeDescription(projectPath: string): string | null {
82
83
  const raw = safeRead(join(projectPath, 'README.md')) || safeRead(join(projectPath, 'readme.md'));
83
84
  if (!raw) return null;
84
85
  for (const line of raw.split('\n')) {
@@ -97,7 +98,15 @@ function buildProjectBody({
97
98
  lastCommit,
98
99
  projectPath,
99
100
  hasClaudeMd,
100
- }) {
101
+ }: {
102
+ projectName: string;
103
+ description: string | null;
104
+ techStack: string[];
105
+ repoUrl: string | null;
106
+ lastCommit: string | null;
107
+ projectPath: string;
108
+ hasClaudeMd: boolean;
109
+ }): string {
101
110
  const lines = [];
102
111
  lines.push(`## ${projectName}`);
103
112
  if (description) lines.push('', description);
@@ -110,12 +119,11 @@ function buildProjectBody({
110
119
  return lines.join('\n');
111
120
  }
112
121
 
113
- /**
114
- * @param {object} args
115
- * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
116
- * @param {import('../types.js').ToolShared} shared
117
- */
118
- export async function handler({ path: projectPath, tags, pillar }, ctx, { ensureIndexed }) {
122
+ export async function handler(
123
+ { path: projectPath, tags, pillar }: Record<string, any>,
124
+ ctx: LocalCtx,
125
+ { ensureIndexed }: SharedCtx
126
+ ): Promise<ToolResult> {
119
127
  const { config } = ctx;
120
128
 
121
129
  const vaultErr = ensureVaultExists(config);
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { captureAndIndex } from '@context-vault/core/capture';
3
3
  import { ok, err, ensureVaultExists } from '../helpers.js';
4
4
  import { MAX_KIND_LENGTH, MAX_TAG_LENGTH, MAX_TAGS_COUNT } from '@context-vault/core/constants';
5
+ import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
5
6
 
6
7
  const MAX_URL_LENGTH = 2048;
7
8
 
@@ -16,12 +17,11 @@ export const inputSchema = {
16
17
  tags: z.array(z.string()).optional().describe('Tags for the entry'),
17
18
  };
18
19
 
19
- /**
20
- * @param {object} args
21
- * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
22
- * @param {import('../types.js').ToolShared} shared
23
- */
24
- export async function handler({ url: targetUrl, kind, tags }, ctx, { ensureIndexed }) {
20
+ export async function handler(
21
+ { url: targetUrl, kind, tags }: Record<string, any>,
22
+ ctx: LocalCtx,
23
+ { ensureIndexed }: SharedCtx
24
+ ): Promise<ToolResult> {
25
25
  const { config } = ctx;
26
26
 
27
27
  const vaultErr = ensureVaultExists(config);
@@ -49,7 +49,7 @@ export async function handler({ url: targetUrl, kind, tags }, ctx, { ensureIndex
49
49
  await ensureIndexed();
50
50
 
51
51
  try {
52
- const { ingestUrl } = await import('../../capture/ingest-url.js');
52
+ const { ingestUrl } = await import('@context-vault/core/ingest-url');
53
53
  const entryData = await ingestUrl(targetUrl, { kind, tags });
54
54
  const entry = await captureAndIndex(ctx, { ...entryData });
55
55
  const relPath = entry.filePath
@@ -66,6 +66,9 @@ export async function handler({ url: targetUrl, kind, tags }, ctx, { ensureIndex
66
66
  parts.push('', '_Use this id to update or delete later._');
67
67
  return ok(parts.join('\n'));
68
68
  } catch (e) {
69
- return err(`Failed to ingest URL: ${e.message}`, 'INGEST_FAILED');
69
+ return err(
70
+ `Failed to ingest URL: ${e instanceof Error ? e.message : String(e)}`,
71
+ 'INGEST_FAILED'
72
+ );
70
73
  }
71
74
  }
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { ok } from '../helpers.js';
3
+ import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
3
4
 
4
5
  export const name = 'list_buckets';
5
6
 
@@ -15,17 +16,13 @@ export const inputSchema = {
15
16
  ),
16
17
  };
17
18
 
18
- /**
19
- * @param {object} args
20
- * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
21
- * @param {import('../types.js').ToolShared} shared
22
- */
23
- export async function handler({ include_counts = true }, ctx, { ensureIndexed, reindexFailed }) {
19
+ export async function handler(
20
+ { include_counts = true }: Record<string, any>,
21
+ ctx: LocalCtx,
22
+ { ensureIndexed, reindexFailed }: SharedCtx
23
+ ): Promise<ToolResult> {
24
24
  await ensureIndexed();
25
25
 
26
- const userClause = '';
27
- const userParams = [];
28
-
29
26
  const buckets = ctx.db
30
27
  .prepare(
31
28
  `SELECT id, title, identity_key, body, tags, meta, created_at, updated_at
@@ -33,10 +30,9 @@ export async function handler({ include_counts = true }, ctx, { ensureIndexed, r
33
30
  WHERE kind = 'bucket'
34
31
  AND (expires_at IS NULL OR expires_at > datetime('now'))
35
32
  AND superseded_by IS NULL
36
- ${userClause}
37
33
  ORDER BY title ASC`
38
34
  )
39
- .all(...userParams);
35
+ .all();
40
36
 
41
37
  if (!buckets.length) {
42
38
  return ok(
@@ -52,8 +48,8 @@ export async function handler({ include_counts = true }, ctx, { ensureIndexed, r
52
48
  }
53
49
  lines.push(`## Registered Buckets (${buckets.length})\n`);
54
50
 
55
- for (const b of buckets) {
56
- let meta = {};
51
+ for (const b of buckets as any[]) {
52
+ let meta: Record<string, any> = {};
57
53
  if (b.meta) {
58
54
  try {
59
55
  meta = typeof b.meta === 'string' ? JSON.parse(b.meta) : b.meta;
@@ -64,12 +60,12 @@ export async function handler({ include_counts = true }, ctx, { ensureIndexed, r
64
60
 
65
61
  const bucketTags = b.tags ? JSON.parse(b.tags) : [];
66
62
  const name = b.identity_key ? b.identity_key.replace(/^bucket:/, '') : b.title || b.id;
67
- const parent = meta.parent || null;
63
+ const parent: string | null = meta.parent || null;
68
64
 
69
65
  let entryCount = null;
70
66
  if (include_counts && b.identity_key) {
71
67
  const countUserClause = '';
72
- const countParams = [];
68
+ const countParams: any[] = [];
73
69
  const row = ctx.db
74
70
  .prepare(
75
71
  `SELECT COUNT(*) as c FROM vault
@@ -3,6 +3,7 @@ import { normalizeKind } from '@context-vault/core/files';
3
3
  import { categoryFor } from '@context-vault/core/categories';
4
4
  import { ok, err, errWithHint } from '../helpers.js';
5
5
  import { resolveTemporalParams } from '../temporal.js';
6
+ import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
6
7
 
7
8
  export const name = 'list_context';
8
9
 
@@ -19,16 +20,11 @@ export const inputSchema = {
19
20
  offset: z.number().optional().describe('Skip first N results for pagination'),
20
21
  };
21
22
 
22
- /**
23
- * @param {object} args
24
- * @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
25
- * @param {import('../types.js').ToolShared} shared
26
- */
27
23
  export async function handler(
28
- { kind, category, tags, since, until, limit, offset },
29
- ctx,
30
- { ensureIndexed, reindexFailed }
31
- ) {
24
+ { kind, category, tags, since, until, limit, offset }: Record<string, any>,
25
+ ctx: LocalCtx,
26
+ { ensureIndexed, reindexFailed }: SharedCtx
27
+ ): Promise<ToolResult> {
32
28
  const { config } = ctx;
33
29
 
34
30
  await ensureIndexed();
@@ -76,19 +72,21 @@ export async function handler(
76
72
 
77
73
  const countParams = [...params];
78
74
  let total;
79
- let rows;
75
+ let rows: any[];
80
76
  try {
81
- total = ctx.db.prepare(`SELECT COUNT(*) as c FROM vault ${where}`).get(...countParams).c;
77
+ total =
78
+ (ctx.db.prepare(`SELECT COUNT(*) as c FROM vault ${where}`).get(...countParams) as any)?.c ??
79
+ 0;
82
80
 
83
81
  params.push(fetchLimit, effectiveOffset);
84
82
  rows = ctx.db
85
83
  .prepare(
86
84
  `SELECT id, title, kind, category, tags, created_at, updated_at, SUBSTR(body, 1, 120) as preview FROM vault ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`
87
85
  )
88
- .all(...params);
86
+ .all(...params) as any[];
89
87
  } catch (e) {
90
88
  return errWithHint(
91
- e.message,
89
+ e instanceof Error ? e.message : String(e),
92
90
  'DB_ERROR',
93
91
  'context-vault list_context DB_ERROR. Check `cat ~/.context-mcp/error.log | tail -5` and help me debug.'
94
92
  );
@@ -97,9 +95,9 @@ export async function handler(
97
95
  // Post-filter by tags if provided, then apply requested limit
98
96
  const filtered = tags?.length
99
97
  ? rows
100
- .filter((r) => {
98
+ .filter((r: any) => {
101
99
  const entryTags = r.tags ? JSON.parse(r.tags) : [];
102
- return tags.some((t) => entryTags.includes(t));
100
+ return tags.some((t: string) => entryTags.includes(t));
103
101
  })
104
102
  .slice(0, effectiveLimit)
105
103
  : rows;