@context-vault/core 2.15.0 → 2.17.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/package.json +1 -1
- package/src/constants.js +7 -2
- package/src/core/config.js +9 -2
- package/src/core/status.js +28 -2
- package/src/index/index.js +24 -20
- package/src/retrieve/index.js +7 -0
- package/src/server/tools/context-status.js +7 -0
- package/src/server/tools/get-context.js +14 -0
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -14,8 +14,13 @@ export const MAX_SOURCE_LENGTH = 200;
|
|
|
14
14
|
export const MAX_IDENTITY_KEY_LENGTH = 200;
|
|
15
15
|
|
|
16
16
|
export const DEFAULT_GROWTH_THRESHOLDS = {
|
|
17
|
-
totalEntries: { warn:
|
|
18
|
-
eventEntries: { warn:
|
|
17
|
+
totalEntries: { warn: 2000, critical: 5000 },
|
|
18
|
+
eventEntries: { warn: 1000, critical: 3000 },
|
|
19
19
|
vaultSizeBytes: { warn: 50 * 1024 * 1024, critical: 200 * 1024 * 1024 },
|
|
20
20
|
eventsWithoutTtl: { warn: 200 },
|
|
21
21
|
};
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_LIFECYCLE = {
|
|
24
|
+
event: { archiveAfterDays: 90 },
|
|
25
|
+
ephemeral: { archiveAfterDays: 30 },
|
|
26
|
+
};
|
package/src/core/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { DEFAULT_GROWTH_THRESHOLDS } from "../constants.js";
|
|
4
|
+
import { DEFAULT_GROWTH_THRESHOLDS, DEFAULT_LIFECYCLE } from "../constants.js";
|
|
5
5
|
|
|
6
6
|
export function parseArgs(argv) {
|
|
7
7
|
const args = {};
|
|
@@ -27,7 +27,7 @@ export function resolveConfig() {
|
|
|
27
27
|
join(HOME, ".context-mcp"),
|
|
28
28
|
);
|
|
29
29
|
const config = {
|
|
30
|
-
vaultDir: join(HOME, "vault"),
|
|
30
|
+
vaultDir: join(HOME, ".vault"),
|
|
31
31
|
dataDir,
|
|
32
32
|
dbPath: join(dataDir, "vault.db"),
|
|
33
33
|
devDir: join(HOME, "dev"),
|
|
@@ -48,6 +48,7 @@ export function resolveConfig() {
|
|
|
48
48
|
maxAgeDays: 7,
|
|
49
49
|
autoConsolidate: false,
|
|
50
50
|
},
|
|
51
|
+
lifecycle: structuredClone(DEFAULT_LIFECYCLE),
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
const configPath = join(dataDir, "config.json");
|
|
@@ -62,6 +63,12 @@ export function resolveConfig() {
|
|
|
62
63
|
if (fc.dbPath) config.dbPath = fc.dbPath;
|
|
63
64
|
if (fc.devDir) config.devDir = fc.devDir;
|
|
64
65
|
if (fc.eventDecayDays != null) config.eventDecayDays = fc.eventDecayDays;
|
|
66
|
+
if (fc.growthWarningThreshold != null) {
|
|
67
|
+
config.thresholds.totalEntries = {
|
|
68
|
+
...config.thresholds.totalEntries,
|
|
69
|
+
warn: Number(fc.growthWarningThreshold),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
65
72
|
if (fc.thresholds) {
|
|
66
73
|
const t = fc.thresholds;
|
|
67
74
|
if (t.totalEntries)
|
package/src/core/status.js
CHANGED
|
@@ -212,7 +212,7 @@ export function gatherVaultStatus(ctx, opts = {}) {
|
|
|
212
212
|
*
|
|
213
213
|
* @param {object} status — result of gatherVaultStatus()
|
|
214
214
|
* @param {object} thresholds — from config.thresholds
|
|
215
|
-
* @returns {{ warnings: Array, hasCritical: boolean, hasWarnings: boolean, actions: string[] }}
|
|
215
|
+
* @returns {{ warnings: Array, hasCritical: boolean, hasWarnings: boolean, actions: string[], kindBreakdown: Array }}
|
|
216
216
|
*/
|
|
217
217
|
export function computeGrowthWarnings(status, thresholds) {
|
|
218
218
|
if (!thresholds)
|
|
@@ -221,6 +221,7 @@ export function computeGrowthWarnings(status, thresholds) {
|
|
|
221
221
|
hasCritical: false,
|
|
222
222
|
hasWarnings: false,
|
|
223
223
|
actions: [],
|
|
224
|
+
kindBreakdown: [],
|
|
224
225
|
};
|
|
225
226
|
|
|
226
227
|
const t = thresholds;
|
|
@@ -235,12 +236,16 @@ export function computeGrowthWarnings(status, thresholds) {
|
|
|
235
236
|
dbSizeBytes = 0,
|
|
236
237
|
} = status;
|
|
237
238
|
|
|
239
|
+
let totalExceeded = false;
|
|
240
|
+
|
|
238
241
|
if (t.totalEntries?.critical != null && total >= t.totalEntries.critical) {
|
|
242
|
+
totalExceeded = true;
|
|
239
243
|
warnings.push({
|
|
240
244
|
level: "critical",
|
|
241
245
|
message: `Total entries: ${total.toLocaleString()} (exceeds critical limit of ${t.totalEntries.critical.toLocaleString()})`,
|
|
242
246
|
});
|
|
243
247
|
} else if (t.totalEntries?.warn != null && total >= t.totalEntries.warn) {
|
|
248
|
+
totalExceeded = true;
|
|
244
249
|
warnings.push({
|
|
245
250
|
level: "warn",
|
|
246
251
|
message: `Total entries: ${total.toLocaleString()} (exceeds recommended ${t.totalEntries.warn.toLocaleString()})`,
|
|
@@ -320,5 +325,26 @@ export function computeGrowthWarnings(status, thresholds) {
|
|
|
320
325
|
actions.push("Consider archiving events older than 90 days");
|
|
321
326
|
}
|
|
322
327
|
|
|
323
|
-
|
|
328
|
+
const kindBreakdown = totalExceeded
|
|
329
|
+
? buildKindBreakdown(status.kindCounts, total)
|
|
330
|
+
: [];
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
warnings,
|
|
334
|
+
hasCritical,
|
|
335
|
+
hasWarnings: warnings.length > 0,
|
|
336
|
+
actions,
|
|
337
|
+
kindBreakdown,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function buildKindBreakdown(kindCounts, total) {
|
|
342
|
+
if (!kindCounts?.length || total === 0) return [];
|
|
343
|
+
return [...kindCounts]
|
|
344
|
+
.sort((a, b) => b.c - a.c)
|
|
345
|
+
.map(({ kind, c }) => ({
|
|
346
|
+
kind,
|
|
347
|
+
count: c,
|
|
348
|
+
pct: Math.round((c / total) * 100),
|
|
349
|
+
}));
|
|
324
350
|
}
|
package/src/index/index.js
CHANGED
|
@@ -196,18 +196,20 @@ export async function indexEntry(
|
|
|
196
196
|
);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
// Skip embedding generation for event entries — they are excluded from
|
|
200
|
+
// default semantic search and don't need vector representations
|
|
201
|
+
if (cat !== "event") {
|
|
202
|
+
const embeddingText = [title, body].filter(Boolean).join(" ");
|
|
203
|
+
const embedding = await ctx.embed(embeddingText);
|
|
202
204
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
if (embedding) {
|
|
206
|
+
try {
|
|
207
|
+
ctx.deleteVec(rowid);
|
|
208
|
+
} catch {
|
|
209
|
+
/* no-op if not found */
|
|
210
|
+
}
|
|
211
|
+
ctx.insertVec(rowid, embedding);
|
|
209
212
|
}
|
|
210
|
-
ctx.insertVec(rowid, embedding);
|
|
211
213
|
}
|
|
212
214
|
}
|
|
213
215
|
|
|
@@ -370,15 +372,17 @@ export async function reindex(ctx, opts = {}) {
|
|
|
370
372
|
fmMeta.updated || created,
|
|
371
373
|
);
|
|
372
374
|
if (result.changes > 0) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
375
|
+
if (category !== "event") {
|
|
376
|
+
const rowidResult = ctx.stmts.getRowid.get(id);
|
|
377
|
+
if (rowidResult?.rowid) {
|
|
378
|
+
const embeddingText = [parsed.title, parsed.body]
|
|
379
|
+
.filter(Boolean)
|
|
380
|
+
.join(" ");
|
|
381
|
+
pendingEmbeds.push({
|
|
382
|
+
rowid: rowidResult.rowid,
|
|
383
|
+
text: embeddingText,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
382
386
|
}
|
|
383
387
|
stats.added++;
|
|
384
388
|
} else {
|
|
@@ -407,7 +411,7 @@ export async function reindex(ctx, opts = {}) {
|
|
|
407
411
|
);
|
|
408
412
|
|
|
409
413
|
// Queue re-embed if title or body changed (vector ops deferred to Phase 2)
|
|
410
|
-
if (bodyChanged || titleChanged) {
|
|
414
|
+
if ((bodyChanged || titleChanged) && category !== "event") {
|
|
411
415
|
const rowid = ctx.stmts.getRowid.get(existing.id)?.rowid;
|
|
412
416
|
if (rowid) {
|
|
413
417
|
const embeddingText = [parsed.title, parsed.body]
|
package/src/retrieve/index.js
CHANGED
|
@@ -74,6 +74,7 @@ export function recencyBoost(createdAt, category, decayDays = 30) {
|
|
|
74
74
|
*/
|
|
75
75
|
export function buildFilterClauses({
|
|
76
76
|
categoryFilter,
|
|
77
|
+
excludeEvents = false,
|
|
77
78
|
since,
|
|
78
79
|
until,
|
|
79
80
|
userIdFilter,
|
|
@@ -94,6 +95,9 @@ export function buildFilterClauses({
|
|
|
94
95
|
clauses.push("e.category = ?");
|
|
95
96
|
params.push(categoryFilter);
|
|
96
97
|
}
|
|
98
|
+
if (excludeEvents && !categoryFilter) {
|
|
99
|
+
clauses.push("e.category != 'event'");
|
|
100
|
+
}
|
|
97
101
|
if (since) {
|
|
98
102
|
clauses.push("e.created_at >= ?");
|
|
99
103
|
params.push(since);
|
|
@@ -242,6 +246,7 @@ export async function hybridSearch(
|
|
|
242
246
|
{
|
|
243
247
|
kindFilter = null,
|
|
244
248
|
categoryFilter = null,
|
|
249
|
+
excludeEvents = false,
|
|
245
250
|
since = null,
|
|
246
251
|
until = null,
|
|
247
252
|
limit = 20,
|
|
@@ -258,6 +263,7 @@ export async function hybridSearch(
|
|
|
258
263
|
|
|
259
264
|
const extraFilters = buildFilterClauses({
|
|
260
265
|
categoryFilter,
|
|
266
|
+
excludeEvents,
|
|
261
267
|
since,
|
|
262
268
|
until,
|
|
263
269
|
userIdFilter,
|
|
@@ -340,6 +346,7 @@ export async function hybridSearch(
|
|
|
340
346
|
if (teamIdFilter && row.team_id !== teamIdFilter) continue;
|
|
341
347
|
if (kindFilter && row.kind !== kindFilter) continue;
|
|
342
348
|
if (categoryFilter && row.category !== categoryFilter) continue;
|
|
349
|
+
if (excludeEvents && row.category === "event") continue;
|
|
343
350
|
if (since && row.created_at < since) continue;
|
|
344
351
|
if (until && row.created_at > until) continue;
|
|
345
352
|
if (row.expires_at && new Date(row.expires_at) <= new Date())
|
|
@@ -146,6 +146,13 @@ export function handler(_args, ctx) {
|
|
|
146
146
|
for (const w of growth.warnings) {
|
|
147
147
|
lines.push(` ${w.message}`);
|
|
148
148
|
}
|
|
149
|
+
if (growth.kindBreakdown.length) {
|
|
150
|
+
lines.push("");
|
|
151
|
+
lines.push(" Breakdown by kind:");
|
|
152
|
+
for (const { kind, count, pct } of growth.kindBreakdown) {
|
|
153
|
+
lines.push(` ${kind}: ${count.toLocaleString()} (${pct}%)`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
149
156
|
if (growth.actions.length) {
|
|
150
157
|
lines.push("", "Suggested growth actions:");
|
|
151
158
|
for (const a of growth.actions) {
|
|
@@ -316,6 +316,12 @@ export const inputSchema = {
|
|
|
316
316
|
.describe(
|
|
317
317
|
"If true, include ephemeral tier entries in results. Default: false — only working and durable tiers are returned.",
|
|
318
318
|
),
|
|
319
|
+
include_events: z
|
|
320
|
+
.boolean()
|
|
321
|
+
.optional()
|
|
322
|
+
.describe(
|
|
323
|
+
"If true, include event category entries in semantic search results. Default: false — events are excluded from query-based search but remain accessible via category/tag filters.",
|
|
324
|
+
),
|
|
319
325
|
};
|
|
320
326
|
|
|
321
327
|
/**
|
|
@@ -339,6 +345,7 @@ export async function handler(
|
|
|
339
345
|
max_tokens,
|
|
340
346
|
pivot_count,
|
|
341
347
|
include_ephemeral,
|
|
348
|
+
include_events,
|
|
342
349
|
},
|
|
343
350
|
ctx,
|
|
344
351
|
{ ensureIndexed, reindexFailed },
|
|
@@ -347,6 +354,7 @@ export async function handler(
|
|
|
347
354
|
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
348
355
|
|
|
349
356
|
const hasQuery = query?.trim();
|
|
357
|
+
const shouldExcludeEvents = hasQuery && !include_events && !category;
|
|
350
358
|
// Expand buckets to bucket: prefixed tags and merge with explicit tags
|
|
351
359
|
const bucketTags = buckets?.length ? buckets.map((b) => `bucket:${b}`) : [];
|
|
352
360
|
const effectiveTags = [...(tags ?? []), ...bucketTags];
|
|
@@ -413,6 +421,7 @@ export async function handler(
|
|
|
413
421
|
const sorted = await hybridSearch(ctx, query, {
|
|
414
422
|
kindFilter,
|
|
415
423
|
categoryFilter: category || null,
|
|
424
|
+
excludeEvents: shouldExcludeEvents,
|
|
416
425
|
since: effectiveSince,
|
|
417
426
|
until: effectiveUntil,
|
|
418
427
|
limit: fetchLimit,
|
|
@@ -490,6 +499,11 @@ export async function handler(
|
|
|
490
499
|
filtered = filtered.filter((r) => r.tier !== "ephemeral");
|
|
491
500
|
}
|
|
492
501
|
|
|
502
|
+
// Event category filter: exclude events from semantic search by default
|
|
503
|
+
if (shouldExcludeEvents) {
|
|
504
|
+
filtered = filtered.filter((r) => r.category !== "event");
|
|
505
|
+
}
|
|
506
|
+
|
|
493
507
|
if (!filtered.length) {
|
|
494
508
|
if (autoWindowed) {
|
|
495
509
|
const days = config.eventDecayDays || 30;
|