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.
- package/bin/cli.js +11 -11
- package/dist/archive.d.ts +23 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +197 -0
- package/dist/archive.js.map +1 -0
- package/dist/consolidation.d.ts +14 -0
- package/dist/consolidation.d.ts.map +1 -0
- package/dist/consolidation.js +59 -0
- package/dist/consolidation.js.map +1 -0
- package/dist/error-log.d.ts +4 -0
- package/dist/error-log.d.ts.map +1 -0
- package/dist/error-log.js +33 -0
- package/dist/error-log.js.map +1 -0
- package/dist/helpers.d.ts +10 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +42 -0
- package/dist/helpers.js.map +1 -0
- package/dist/linking.d.ts +13 -0
- package/dist/linking.d.ts.map +1 -0
- package/dist/linking.js +86 -0
- package/dist/linking.js.map +1 -0
- package/dist/migrate-dirs.d.ts +16 -0
- package/dist/migrate-dirs.d.ts.map +1 -0
- package/dist/migrate-dirs.js +127 -0
- package/dist/migrate-dirs.js.map +1 -0
- package/dist/register-tools.d.ts +3 -0
- package/dist/register-tools.d.ts.map +1 -0
- package/dist/register-tools.js +161 -0
- package/dist/register-tools.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +241 -0
- package/dist/server.js.map +1 -0
- package/dist/status.d.ts +18 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +265 -0
- package/dist/status.js.map +1 -0
- package/dist/telemetry.d.ts +6 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +74 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/temporal.d.ts +9 -0
- package/dist/temporal.d.ts.map +1 -0
- package/dist/temporal.js +76 -0
- package/dist/temporal.js.map +1 -0
- package/dist/tools/clear-context.d.ts +11 -0
- package/dist/tools/clear-context.d.ts.map +1 -0
- package/dist/tools/clear-context.js +28 -0
- package/dist/tools/clear-context.js.map +1 -0
- package/dist/tools/context-status.d.ts +6 -0
- package/dist/tools/context-status.d.ts.map +1 -0
- package/dist/tools/context-status.js +160 -0
- package/dist/tools/context-status.js.map +1 -0
- package/dist/tools/create-snapshot.d.ts +13 -0
- package/dist/tools/create-snapshot.d.ts.map +1 -0
- package/dist/tools/create-snapshot.js +161 -0
- package/dist/tools/create-snapshot.js.map +1 -0
- package/dist/tools/delete-context.d.ts +9 -0
- package/dist/tools/delete-context.d.ts.map +1 -0
- package/dist/tools/delete-context.js +45 -0
- package/dist/tools/delete-context.js.map +1 -0
- package/dist/tools/get-context.d.ts +83 -0
- package/dist/tools/get-context.d.ts.map +1 -0
- package/dist/tools/get-context.js +598 -0
- package/dist/tools/get-context.js.map +1 -0
- package/dist/tools/ingest-project.d.ts +11 -0
- package/dist/tools/ingest-project.d.ts.map +1 -0
- package/dist/tools/ingest-project.js +226 -0
- package/dist/tools/ingest-project.js.map +1 -0
- package/dist/tools/ingest-url.d.ts +11 -0
- package/dist/tools/ingest-url.d.ts.map +1 -0
- package/dist/tools/ingest-url.js +62 -0
- package/dist/tools/ingest-url.js.map +1 -0
- package/dist/tools/list-buckets.d.ts +9 -0
- package/dist/tools/list-buckets.d.ts.map +1 -0
- package/dist/tools/list-buckets.js +76 -0
- package/dist/tools/list-buckets.js.map +1 -0
- package/dist/tools/list-context.d.ts +19 -0
- package/dist/tools/list-context.d.ts.map +1 -0
- package/dist/tools/list-context.js +110 -0
- package/dist/tools/list-context.js.map +1 -0
- package/dist/tools/save-context.d.ts +36 -0
- package/dist/tools/save-context.d.ts.map +1 -0
- package/dist/tools/save-context.js +466 -0
- package/dist/tools/save-context.js.map +1 -0
- package/dist/tools/session-start.d.ts +11 -0
- package/dist/tools/session-start.d.ts.map +1 -0
- package/dist/tools/session-start.js +224 -0
- package/dist/tools/session-start.js.map +1 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/node_modules/@context-vault/core/dist/constants.d.ts +1 -1
- package/node_modules/@context-vault/core/dist/constants.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/constants.js +1 -1
- package/node_modules/@context-vault/core/dist/constants.js.map +1 -1
- package/node_modules/@context-vault/core/dist/search.d.ts +2 -1
- package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/search.js +6 -2
- package/node_modules/@context-vault/core/dist/search.js.map +1 -1
- package/node_modules/@context-vault/core/dist/types.d.ts +1 -0
- package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -1
- package/node_modules/@context-vault/core/package.json +1 -1
- package/node_modules/@context-vault/core/src/constants.ts +1 -1
- package/node_modules/@context-vault/core/src/search.ts +7 -0
- package/node_modules/@context-vault/core/src/types.ts +1 -0
- package/package.json +10 -4
- package/src/{archive.js → archive.ts} +63 -30
- package/src/consolidation.ts +78 -0
- package/src/{error-log.js → error-log.ts} +3 -3
- package/src/{helpers.js → helpers.ts} +11 -5
- package/src/{linking.js → linking.ts} +14 -9
- package/src/{migrate-dirs.js → migrate-dirs.ts} +21 -8
- package/src/{register-tools.js → register-tools.ts} +21 -11
- package/src/{server.js → server.ts} +26 -20
- package/src/{status.js → status.ts} +60 -38
- package/src/{telemetry.js → telemetry.ts} +8 -4
- package/src/{temporal.js → temporal.ts} +10 -3
- package/src/tools/{clear-context.js → clear-context.ts} +2 -5
- package/src/tools/{context-status.js → context-status.ts} +6 -9
- package/src/tools/{create-snapshot.js → create-snapshot.ts} +11 -10
- package/src/tools/{delete-context.js → delete-context.ts} +9 -9
- package/src/tools/{get-context.js → get-context.ts} +72 -62
- package/src/tools/{ingest-project.js → ingest-project.ts} +19 -11
- package/src/tools/{ingest-url.js → ingest-url.ts} +11 -8
- package/src/tools/{list-buckets.js → list-buckets.ts} +11 -15
- package/src/tools/{list-context.js → list-context.ts} +13 -15
- package/src/tools/{save-context.js → save-context.ts} +54 -42
- package/src/tools/{session-start.js → session-start.ts} +29 -20
- 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
|
-
|
|
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('
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
182
|
+
`SELECT created_at FROM vault WHERE kind = 'brief' AND tags LIKE ? ORDER BY created_at DESC LIMIT 1`
|
|
174
183
|
)
|
|
175
|
-
.get(
|
|
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
|
-
|
|
354
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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('
|
|
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(
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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(
|
|
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 =
|
|
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;
|