@context-vault/core 2.17.0 → 3.0.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 (101) hide show
  1. package/dist/capture.d.ts +21 -0
  2. package/dist/capture.d.ts.map +1 -0
  3. package/dist/capture.js +269 -0
  4. package/dist/capture.js.map +1 -0
  5. package/dist/categories.d.ts +6 -0
  6. package/dist/categories.d.ts.map +1 -0
  7. package/dist/categories.js +50 -0
  8. package/dist/categories.js.map +1 -0
  9. package/dist/config.d.ts +4 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +190 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/constants.d.ts +33 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/constants.js +23 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/db.d.ts +13 -0
  18. package/dist/db.d.ts.map +1 -0
  19. package/dist/db.js +191 -0
  20. package/dist/db.js.map +1 -0
  21. package/dist/embed.d.ts +5 -0
  22. package/dist/embed.d.ts.map +1 -0
  23. package/dist/embed.js +78 -0
  24. package/dist/embed.js.map +1 -0
  25. package/dist/files.d.ts +13 -0
  26. package/dist/files.d.ts.map +1 -0
  27. package/dist/files.js +66 -0
  28. package/dist/files.js.map +1 -0
  29. package/dist/formatters.d.ts +8 -0
  30. package/dist/formatters.d.ts.map +1 -0
  31. package/dist/formatters.js +18 -0
  32. package/dist/formatters.js.map +1 -0
  33. package/dist/frontmatter.d.ts +12 -0
  34. package/dist/frontmatter.d.ts.map +1 -0
  35. package/dist/frontmatter.js +101 -0
  36. package/dist/frontmatter.js.map +1 -0
  37. package/dist/index.d.ts +10 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +297 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/ingest-url.d.ts +20 -0
  42. package/dist/ingest-url.d.ts.map +1 -0
  43. package/dist/ingest-url.js +113 -0
  44. package/dist/ingest-url.js.map +1 -0
  45. package/dist/main.d.ts +14 -0
  46. package/dist/main.d.ts.map +1 -0
  47. package/dist/main.js +25 -0
  48. package/dist/main.js.map +1 -0
  49. package/dist/search.d.ts +18 -0
  50. package/dist/search.d.ts.map +1 -0
  51. package/dist/search.js +238 -0
  52. package/dist/search.js.map +1 -0
  53. package/dist/types.d.ts +176 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +2 -0
  56. package/dist/types.js.map +1 -0
  57. package/package.json +66 -17
  58. package/src/capture.ts +308 -0
  59. package/src/categories.ts +54 -0
  60. package/src/{core/config.js → config.ts} +34 -33
  61. package/src/{constants.js → constants.ts} +6 -3
  62. package/src/db.ts +229 -0
  63. package/src/{index/embed.js → embed.ts} +10 -35
  64. package/src/files.ts +80 -0
  65. package/src/{capture/formatters.js → formatters.ts} +13 -11
  66. package/src/{core/frontmatter.js → frontmatter.ts} +27 -33
  67. package/src/index.ts +351 -0
  68. package/src/ingest-url.ts +99 -0
  69. package/src/main.ts +111 -0
  70. package/src/search.ts +285 -0
  71. package/src/types.ts +166 -0
  72. package/src/capture/file-ops.js +0 -97
  73. package/src/capture/import-pipeline.js +0 -46
  74. package/src/capture/importers.js +0 -387
  75. package/src/capture/index.js +0 -236
  76. package/src/capture/ingest-url.js +0 -252
  77. package/src/consolidation/index.js +0 -112
  78. package/src/core/categories.js +0 -72
  79. package/src/core/error-log.js +0 -54
  80. package/src/core/files.js +0 -108
  81. package/src/core/status.js +0 -350
  82. package/src/core/telemetry.js +0 -90
  83. package/src/index/db.js +0 -416
  84. package/src/index/index.js +0 -522
  85. package/src/index.js +0 -66
  86. package/src/retrieve/index.js +0 -500
  87. package/src/server/helpers.js +0 -44
  88. package/src/server/tools/clear-context.js +0 -47
  89. package/src/server/tools/context-status.js +0 -182
  90. package/src/server/tools/create-snapshot.js +0 -231
  91. package/src/server/tools/delete-context.js +0 -60
  92. package/src/server/tools/get-context.js +0 -678
  93. package/src/server/tools/ingest-project.js +0 -244
  94. package/src/server/tools/ingest-url.js +0 -88
  95. package/src/server/tools/list-buckets.js +0 -116
  96. package/src/server/tools/list-context.js +0 -163
  97. package/src/server/tools/save-context.js +0 -609
  98. package/src/server/tools/session-start.js +0 -285
  99. package/src/server/tools/submit-feedback.js +0 -55
  100. package/src/server/tools.js +0 -174
  101. package/src/sync/sync.js +0 -235
@@ -1,350 +0,0 @@
1
- /**
2
- * status.js — Vault status/diagnostics data gathering
3
- */
4
-
5
- import { existsSync, readdirSync, statSync } from "node:fs";
6
- import { join } from "node:path";
7
- import { walkDir } from "./files.js";
8
- import { isEmbedAvailable } from "../index/embed.js";
9
- import { KIND_STALENESS_DAYS } from "./categories.js";
10
-
11
- /**
12
- * Gather raw vault status data for formatting by consumers.
13
- *
14
- * @param {import('../server/types.js').BaseCtx} ctx
15
- * @param {{ userId?: string }} opts — optional userId for per-user stats
16
- * @returns {{ fileCount, subdirs, kindCounts, dbSize, stalePaths, resolvedFrom, embeddingStatus, errors }}
17
- */
18
- export function gatherVaultStatus(ctx, opts = {}) {
19
- const { db, config } = ctx;
20
- const { userId } = opts;
21
- const errors = [];
22
-
23
- // Build user filter clause for DB queries
24
- const hasUser = userId !== undefined;
25
- const userWhere = hasUser ? "WHERE user_id = ?" : "";
26
- const userAnd = hasUser ? "AND user_id = ?" : "";
27
- const userParams = hasUser ? [userId] : [];
28
-
29
- // Count files in vault subdirs (auto-discover)
30
- let fileCount = 0;
31
- const subdirs = [];
32
- try {
33
- if (existsSync(config.vaultDir)) {
34
- for (const d of readdirSync(config.vaultDir, { withFileTypes: true })) {
35
- if (d.isDirectory()) {
36
- const dir = join(config.vaultDir, d.name);
37
- const count = walkDir(dir).length;
38
- fileCount += count;
39
- if (count > 0) subdirs.push({ name: d.name, count });
40
- }
41
- }
42
- }
43
- } catch (e) {
44
- errors.push(`File scan failed: ${e.message}`);
45
- }
46
-
47
- // Count DB rows by kind
48
- let kindCounts = [];
49
- try {
50
- kindCounts = db
51
- .prepare(
52
- `SELECT kind, COUNT(*) as c FROM vault ${userWhere} GROUP BY kind`,
53
- )
54
- .all(...userParams);
55
- } catch (e) {
56
- errors.push(`Kind count query failed: ${e.message}`);
57
- }
58
-
59
- // Count DB rows by category
60
- let categoryCounts = [];
61
- try {
62
- categoryCounts = db
63
- .prepare(
64
- `SELECT category, COUNT(*) as c FROM vault ${userWhere} GROUP BY category`,
65
- )
66
- .all(...userParams);
67
- } catch (e) {
68
- errors.push(`Category count query failed: ${e.message}`);
69
- }
70
-
71
- // DB file size
72
- let dbSize = "n/a";
73
- let dbSizeBytes = 0;
74
- try {
75
- if (existsSync(config.dbPath)) {
76
- dbSizeBytes = statSync(config.dbPath).size;
77
- dbSize =
78
- dbSizeBytes > 1024 * 1024
79
- ? `${(dbSizeBytes / 1024 / 1024).toFixed(1)}MB`
80
- : `${(dbSizeBytes / 1024).toFixed(1)}KB`;
81
- }
82
- } catch (e) {
83
- errors.push(`DB size check failed: ${e.message}`);
84
- }
85
-
86
- // Check for stale paths (count all mismatches, not just a sample)
87
- let stalePaths = false;
88
- let staleCount = 0;
89
- try {
90
- const result = db
91
- .prepare(
92
- `SELECT COUNT(*) as c FROM vault WHERE file_path NOT LIKE ? || '%' ${userAnd}`,
93
- )
94
- .get(config.vaultDir, ...userParams);
95
- staleCount = result.c;
96
- stalePaths = staleCount > 0;
97
- } catch (e) {
98
- errors.push(`Stale path check failed: ${e.message}`);
99
- }
100
-
101
- // Count expired entries pending pruning
102
- let expiredCount = 0;
103
- try {
104
- expiredCount = db
105
- .prepare(
106
- `SELECT COUNT(*) as c FROM vault WHERE expires_at IS NOT NULL AND expires_at <= datetime('now') ${userAnd}`,
107
- )
108
- .get(...userParams).c;
109
- } catch (e) {
110
- errors.push(`Expired count failed: ${e.message}`);
111
- }
112
-
113
- // Count event-category entries
114
- let eventCount = 0;
115
- try {
116
- eventCount = db
117
- .prepare(
118
- `SELECT COUNT(*) as c FROM vault WHERE category = 'event' ${userAnd}`,
119
- )
120
- .get(...userParams).c;
121
- } catch (e) {
122
- errors.push(`Event count failed: ${e.message}`);
123
- }
124
-
125
- // Count event entries without expires_at
126
- let eventsWithoutTtlCount = 0;
127
- try {
128
- eventsWithoutTtlCount = db
129
- .prepare(
130
- `SELECT COUNT(*) as c FROM vault WHERE category = 'event' AND expires_at IS NULL ${userAnd}`,
131
- )
132
- .get(...userParams).c;
133
- } catch (e) {
134
- errors.push(`Events without TTL count failed: ${e.message}`);
135
- }
136
-
137
- // Embedding/vector status
138
- let embeddingStatus = null;
139
- try {
140
- const total = db
141
- .prepare(`SELECT COUNT(*) as c FROM vault ${userWhere}`)
142
- .get(...userParams).c;
143
- const indexed = db
144
- .prepare(
145
- `SELECT COUNT(*) as c FROM vault WHERE rowid IN (SELECT rowid FROM vault_vec) ${userAnd}`,
146
- )
147
- .get(...userParams).c;
148
- embeddingStatus = { indexed, total, missing: total - indexed };
149
- } catch (e) {
150
- errors.push(`Embedding status check failed: ${e.message}`);
151
- }
152
-
153
- // Embedding model availability
154
- const embedModelAvailable = isEmbedAvailable();
155
-
156
- // Count auto-captured feedback entries (written by tracked() on unhandled errors)
157
- let autoCapturedFeedbackCount = 0;
158
- try {
159
- autoCapturedFeedbackCount = db
160
- .prepare(
161
- `SELECT COUNT(*) as c FROM vault WHERE kind = 'feedback' AND tags LIKE '%"auto-captured"%' ${userAnd}`,
162
- )
163
- .get(...userParams).c;
164
- } catch (e) {
165
- errors.push(`Auto-captured feedback count failed: ${e.message}`);
166
- }
167
-
168
- // Stale knowledge entries — kinds with a threshold, not updated within N days
169
- let staleKnowledge = [];
170
- try {
171
- const stalenessKinds = Object.entries(KIND_STALENESS_DAYS);
172
- if (stalenessKinds.length > 0) {
173
- const kindClauses = stalenessKinds
174
- .map(
175
- ([kind, days]) =>
176
- `(kind = '${kind}' AND COALESCE(updated_at, created_at) <= datetime('now', '-${days} days'))`,
177
- )
178
- .join(" OR ");
179
- staleKnowledge = db
180
- .prepare(
181
- `SELECT kind, title, COALESCE(updated_at, created_at) as last_updated FROM vault WHERE category = 'knowledge' AND (${kindClauses}) AND (expires_at IS NULL OR expires_at > datetime('now')) ${userAnd} ORDER BY last_updated ASC LIMIT 10`,
182
- )
183
- .all(...userParams);
184
- }
185
- } catch (e) {
186
- errors.push(`Stale knowledge check failed: ${e.message}`);
187
- }
188
-
189
- return {
190
- fileCount,
191
- subdirs,
192
- kindCounts,
193
- categoryCounts,
194
- dbSize,
195
- dbSizeBytes,
196
- stalePaths,
197
- staleCount,
198
- expiredCount,
199
- eventCount,
200
- eventsWithoutTtlCount,
201
- embeddingStatus,
202
- embedModelAvailable,
203
- autoCapturedFeedbackCount,
204
- staleKnowledge,
205
- resolvedFrom: config.resolvedFrom,
206
- errors,
207
- };
208
- }
209
-
210
- /**
211
- * Compute growth warnings based on vault status and configured thresholds.
212
- *
213
- * @param {object} status — result of gatherVaultStatus()
214
- * @param {object} thresholds — from config.thresholds
215
- * @returns {{ warnings: Array, hasCritical: boolean, hasWarnings: boolean, actions: string[], kindBreakdown: Array }}
216
- */
217
- export function computeGrowthWarnings(status, thresholds) {
218
- if (!thresholds)
219
- return {
220
- warnings: [],
221
- hasCritical: false,
222
- hasWarnings: false,
223
- actions: [],
224
- kindBreakdown: [],
225
- };
226
-
227
- const t = thresholds;
228
- const warnings = [];
229
- const actions = [];
230
-
231
- const total = status.embeddingStatus?.total ?? 0;
232
- const {
233
- eventCount = 0,
234
- eventsWithoutTtlCount = 0,
235
- expiredCount = 0,
236
- dbSizeBytes = 0,
237
- } = status;
238
-
239
- let totalExceeded = false;
240
-
241
- if (t.totalEntries?.critical != null && total >= t.totalEntries.critical) {
242
- totalExceeded = true;
243
- warnings.push({
244
- level: "critical",
245
- message: `Total entries: ${total.toLocaleString()} (exceeds critical limit of ${t.totalEntries.critical.toLocaleString()})`,
246
- });
247
- } else if (t.totalEntries?.warn != null && total >= t.totalEntries.warn) {
248
- totalExceeded = true;
249
- warnings.push({
250
- level: "warn",
251
- message: `Total entries: ${total.toLocaleString()} (exceeds recommended ${t.totalEntries.warn.toLocaleString()})`,
252
- });
253
- }
254
-
255
- if (
256
- t.eventEntries?.critical != null &&
257
- eventCount >= t.eventEntries.critical
258
- ) {
259
- warnings.push({
260
- level: "critical",
261
- message: `Event entries: ${eventCount.toLocaleString()} (exceeds critical limit of ${t.eventEntries.critical.toLocaleString()})`,
262
- });
263
- } else if (
264
- t.eventEntries?.warn != null &&
265
- eventCount >= t.eventEntries.warn
266
- ) {
267
- const ttlNote =
268
- eventsWithoutTtlCount > 0
269
- ? ` (${eventsWithoutTtlCount.toLocaleString()} without TTL)`
270
- : "";
271
- warnings.push({
272
- level: "warn",
273
- message: `Event entries: ${eventCount.toLocaleString()}${ttlNote} (exceeds recommended ${t.eventEntries.warn.toLocaleString()})`,
274
- });
275
- }
276
-
277
- if (
278
- t.vaultSizeBytes?.critical != null &&
279
- dbSizeBytes >= t.vaultSizeBytes.critical
280
- ) {
281
- warnings.push({
282
- level: "critical",
283
- message: `Database size: ${(dbSizeBytes / 1024 / 1024).toFixed(1)}MB (exceeds critical limit of ${(t.vaultSizeBytes.critical / 1024 / 1024).toFixed(0)}MB)`,
284
- });
285
- } else if (
286
- t.vaultSizeBytes?.warn != null &&
287
- dbSizeBytes >= t.vaultSizeBytes.warn
288
- ) {
289
- warnings.push({
290
- level: "warn",
291
- message: `Database size: ${(dbSizeBytes / 1024 / 1024).toFixed(1)}MB (exceeds recommended ${(t.vaultSizeBytes.warn / 1024 / 1024).toFixed(0)}MB)`,
292
- });
293
- }
294
-
295
- if (
296
- t.eventsWithoutTtl?.warn != null &&
297
- eventsWithoutTtlCount >= t.eventsWithoutTtl.warn
298
- ) {
299
- warnings.push({
300
- level: "warn",
301
- message: `Event entries without expires_at: ${eventsWithoutTtlCount.toLocaleString()} (exceeds recommended ${t.eventsWithoutTtl.warn.toLocaleString()})`,
302
- });
303
- }
304
-
305
- const hasCritical = warnings.some((w) => w.level === "critical");
306
-
307
- if (expiredCount > 0) {
308
- actions.push(
309
- `Run \`context-vault prune\` to remove ${expiredCount} expired event entr${expiredCount === 1 ? "y" : "ies"}`,
310
- );
311
- }
312
- const eventThresholdExceeded =
313
- eventCount >= (t.eventEntries?.warn ?? Infinity);
314
- const ttlThresholdExceeded =
315
- eventsWithoutTtlCount >= (t.eventsWithoutTtl?.warn ?? Infinity);
316
- if (
317
- eventsWithoutTtlCount > 0 &&
318
- (eventThresholdExceeded || ttlThresholdExceeded)
319
- ) {
320
- actions.push(
321
- "Add `expires_at` to event/session entries to enable automatic cleanup",
322
- );
323
- }
324
- if (total >= (t.totalEntries?.warn ?? Infinity)) {
325
- actions.push("Consider archiving events older than 90 days");
326
- }
327
-
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
- }));
350
- }
@@ -1,90 +0,0 @@
1
- import { existsSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { API_URL, MARKETING_URL, GITHUB_ISSUES_URL } from "../constants.js";
4
-
5
- const TELEMETRY_ENDPOINT = `${API_URL}/telemetry`;
6
- const NOTICE_MARKER = ".telemetry-notice-shown";
7
- const FEEDBACK_PROMPT_MARKER = ".feedback-prompt-shown";
8
-
9
- export function isTelemetryEnabled(config) {
10
- const envVal = process.env.CONTEXT_VAULT_TELEMETRY;
11
- if (envVal !== undefined) return envVal === "1" || envVal === "true";
12
- return config?.telemetry === true;
13
- }
14
-
15
- /**
16
- * Fire-and-forget telemetry event. Never throws, never blocks.
17
- * Payload contains only: event, code, tool, cv_version, node_version, platform, arch, ts.
18
- * No message text, stack traces, vault content, file paths, or user identifiers.
19
- */
20
- export function sendTelemetryEvent(config, payload) {
21
- if (!isTelemetryEnabled(config)) return;
22
-
23
- const event = {
24
- event: payload.event,
25
- code: payload.code || null,
26
- tool: payload.tool || null,
27
- cv_version: payload.cv_version,
28
- node_version: process.version,
29
- platform: process.platform,
30
- arch: process.arch,
31
- ts: new Date().toISOString(),
32
- };
33
-
34
- fetch(TELEMETRY_ENDPOINT, {
35
- method: "POST",
36
- headers: { "Content-Type": "application/json" },
37
- body: JSON.stringify(event),
38
- signal: AbortSignal.timeout(5000),
39
- }).catch(() => {});
40
- }
41
-
42
- /**
43
- * Print the one-time telemetry notice to stderr.
44
- * Uses a marker file in dataDir to ensure it's only shown once.
45
- */
46
- export function maybeShowTelemetryNotice(dataDir) {
47
- try {
48
- const markerPath = join(dataDir, NOTICE_MARKER);
49
- if (existsSync(markerPath)) return;
50
- writeFileSync(markerPath, new Date().toISOString() + "\n");
51
- } catch {
52
- return;
53
- }
54
-
55
- const lines = [
56
- "[context-vault] Telemetry: disabled by default.",
57
- "[context-vault] To help improve context-vault, you can opt in to anonymous error reporting.",
58
- "[context-vault] Reports contain only: event type, error code, tool name, version, node version, platform, arch, timestamp.",
59
- "[context-vault] No vault content, file paths, or personal data is ever sent.",
60
- '[context-vault] Opt in: set "telemetry": true in ~/.context-mcp/config.json or set CONTEXT_VAULT_TELEMETRY=1.',
61
- `[context-vault] Full payload schema: ${MARKETING_URL}/telemetry`,
62
- ];
63
- for (const line of lines) {
64
- process.stderr.write(line + "\n");
65
- }
66
- }
67
-
68
- /**
69
- * Print a one-time feedback prompt after the user's first successful save.
70
- * Uses a marker file in dataDir to ensure it's only shown once.
71
- * Never throws, never blocks.
72
- */
73
- export function maybeShowFeedbackPrompt(dataDir) {
74
- try {
75
- const markerPath = join(dataDir, FEEDBACK_PROMPT_MARKER);
76
- if (existsSync(markerPath)) return;
77
- writeFileSync(markerPath, new Date().toISOString() + "\n");
78
- } catch {
79
- return;
80
- }
81
-
82
- const lines = [
83
- "[context-vault] First entry saved — nice work!",
84
- "[context-vault] Got feedback, a bug, or a feature request?",
85
- `[context-vault] Open an issue: ${GITHUB_ISSUES_URL}`,
86
- ];
87
- for (const line of lines) {
88
- process.stderr.write(line + "\n");
89
- }
90
- }