context-vault 3.1.6 → 3.1.8
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 +1369 -1774
- 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 +85 -0
- package/dist/tools/get-context.d.ts.map +1 -0
- package/dist/tools/get-context.js +576 -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 +458 -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/capture.d.ts +1 -1
- package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/capture.js +34 -47
- package/node_modules/@context-vault/core/dist/capture.js.map +1 -1
- package/node_modules/@context-vault/core/dist/categories.js +30 -30
- package/node_modules/@context-vault/core/dist/config.d.ts +1 -1
- package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/config.js +37 -43
- package/node_modules/@context-vault/core/dist/config.js.map +1 -1
- 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 +4 -4
- package/node_modules/@context-vault/core/dist/constants.js.map +1 -1
- package/node_modules/@context-vault/core/dist/db.d.ts +2 -2
- package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/db.js +21 -20
- package/node_modules/@context-vault/core/dist/db.js.map +1 -1
- package/node_modules/@context-vault/core/dist/embed.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/embed.js +11 -11
- package/node_modules/@context-vault/core/dist/embed.js.map +1 -1
- package/node_modules/@context-vault/core/dist/files.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/files.js +12 -13
- package/node_modules/@context-vault/core/dist/files.js.map +1 -1
- package/node_modules/@context-vault/core/dist/formatters.js +5 -5
- package/node_modules/@context-vault/core/dist/frontmatter.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/frontmatter.js +23 -23
- package/node_modules/@context-vault/core/dist/frontmatter.js.map +1 -1
- package/node_modules/@context-vault/core/dist/index.d.ts +1 -1
- package/node_modules/@context-vault/core/dist/index.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/index.js +58 -46
- package/node_modules/@context-vault/core/dist/index.js.map +1 -1
- package/node_modules/@context-vault/core/dist/ingest-url.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/ingest-url.js +30 -33
- package/node_modules/@context-vault/core/dist/ingest-url.js.map +1 -1
- package/node_modules/@context-vault/core/dist/main.d.ts +13 -13
- package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/main.js +12 -12
- package/node_modules/@context-vault/core/dist/main.js.map +1 -1
- package/node_modules/@context-vault/core/dist/search.d.ts +1 -1
- package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/search.js +20 -22
- package/node_modules/@context-vault/core/dist/search.js.map +1 -1
- package/node_modules/@context-vault/core/dist/types.d.ts +1 -1
- package/node_modules/@context-vault/core/package.json +1 -1
- package/node_modules/@context-vault/core/src/capture.ts +44 -81
- package/node_modules/@context-vault/core/src/categories.ts +30 -30
- package/node_modules/@context-vault/core/src/config.ts +45 -60
- package/node_modules/@context-vault/core/src/constants.ts +8 -10
- package/node_modules/@context-vault/core/src/db.ts +37 -56
- package/node_modules/@context-vault/core/src/embed.ts +15 -26
- package/node_modules/@context-vault/core/src/files.ts +13 -16
- package/node_modules/@context-vault/core/src/formatters.ts +5 -5
- package/node_modules/@context-vault/core/src/frontmatter.ts +26 -30
- package/node_modules/@context-vault/core/src/index.ts +94 -100
- package/node_modules/@context-vault/core/src/ingest-url.ts +56 -93
- package/node_modules/@context-vault/core/src/main.ts +13 -18
- package/node_modules/@context-vault/core/src/search.ts +34 -56
- package/node_modules/@context-vault/core/src/types.ts +1 -1
- package/package.json +10 -4
- package/scripts/postinstall.js +18 -25
- package/scripts/prepack.js +13 -19
- package/src/archive.ts +244 -0
- package/src/consolidation.ts +78 -0
- package/src/{error-log.js → error-log.ts} +10 -10
- package/src/helpers.ts +61 -0
- package/src/{linking.js → linking.ts} +22 -20
- package/src/migrate-dirs.ts +152 -0
- package/src/register-tools.ts +183 -0
- package/src/{server.js → server.ts} +89 -109
- package/src/{status.js → status.ts} +94 -108
- package/src/telemetry.ts +80 -0
- package/src/{temporal.js → temporal.ts} +29 -33
- package/src/tools/clear-context.ts +41 -0
- package/src/tools/{context-status.js → context-status.ts} +43 -66
- package/src/tools/{create-snapshot.js → create-snapshot.ts} +54 -65
- package/src/tools/delete-context.ts +53 -0
- package/src/tools/{get-context.js → get-context.ts} +142 -205
- package/src/tools/ingest-project.ts +260 -0
- package/src/tools/ingest-url.ts +74 -0
- package/src/tools/{list-buckets.js → list-buckets.ts} +27 -37
- package/src/tools/{list-context.js → list-context.ts} +46 -71
- package/src/tools/{save-context.js → save-context.ts} +148 -204
- package/src/tools/{session-start.js → session-start.ts} +72 -79
- package/src/types.ts +29 -0
- package/src/helpers.js +0 -57
- package/src/register-tools.js +0 -175
- package/src/telemetry.js +0 -80
- package/src/tools/clear-context.js +0 -47
- package/src/tools/delete-context.js +0 -54
- package/src/tools/ingest-project.js +0 -272
- package/src/tools/ingest-url.js +0 -87
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { existsSync, readdirSync, statSync } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { walkDir } from
|
|
4
|
-
import { isEmbedAvailable } from
|
|
5
|
-
import { KIND_STALENESS_DAYS } from
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { walkDir } from '@context-vault/core/files';
|
|
4
|
+
import { isEmbedAvailable } from '@context-vault/core/embed';
|
|
5
|
+
import { KIND_STALENESS_DAYS } from '@context-vault/core/categories';
|
|
6
|
+
import type { LocalCtx } from './types.js';
|
|
7
|
+
import type { GrowthThresholds } from '@context-vault/core/types';
|
|
6
8
|
|
|
7
|
-
function countArchivedEntries(vaultDir) {
|
|
8
|
-
const archRoot = join(vaultDir,
|
|
9
|
+
function countArchivedEntries(vaultDir: string): number {
|
|
10
|
+
const archRoot = join(vaultDir, '_archive');
|
|
9
11
|
if (!existsSync(archRoot)) return 0;
|
|
10
12
|
try {
|
|
11
13
|
return walkDir(archRoot).length;
|
|
@@ -14,12 +16,13 @@ function countArchivedEntries(vaultDir) {
|
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
export function gatherVaultStatus(ctx, opts = {}) {
|
|
19
|
+
export function gatherVaultStatus(ctx: LocalCtx, opts: Record<string, unknown> = {}): any {
|
|
20
|
+
void opts;
|
|
18
21
|
const { db, config } = ctx;
|
|
19
|
-
const errors = [];
|
|
22
|
+
const errors: string[] = [];
|
|
20
23
|
|
|
21
24
|
let fileCount = 0;
|
|
22
|
-
const subdirs = [];
|
|
25
|
+
const subdirs: Array<{ name: string; count: number }> = [];
|
|
23
26
|
try {
|
|
24
27
|
if (existsSync(config.vaultDir)) {
|
|
25
28
|
for (const d of readdirSync(config.vaultDir, { withFileTypes: true })) {
|
|
@@ -32,28 +35,26 @@ export function gatherVaultStatus(ctx, opts = {}) {
|
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
} catch (e) {
|
|
35
|
-
errors.push(`File scan failed: ${e.message}`);
|
|
38
|
+
errors.push(`File scan failed: ${(e as Error).message}`);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
let kindCounts = [];
|
|
41
|
+
let kindCounts: unknown[] = [];
|
|
39
42
|
try {
|
|
40
|
-
kindCounts = db
|
|
41
|
-
.prepare(`SELECT kind, COUNT(*) as c FROM vault GROUP BY kind`)
|
|
42
|
-
.all();
|
|
43
|
+
kindCounts = db.prepare(`SELECT kind, COUNT(*) as c FROM vault GROUP BY kind`).all();
|
|
43
44
|
} catch (e) {
|
|
44
|
-
errors.push(`Kind count query failed: ${e.message}`);
|
|
45
|
+
errors.push(`Kind count query failed: ${(e as Error).message}`);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
let categoryCounts = [];
|
|
48
|
+
let categoryCounts: unknown[] = [];
|
|
48
49
|
try {
|
|
49
50
|
categoryCounts = db
|
|
50
51
|
.prepare(`SELECT category, COUNT(*) as c FROM vault GROUP BY category`)
|
|
51
52
|
.all();
|
|
52
53
|
} catch (e) {
|
|
53
|
-
errors.push(`Category count query failed: ${e.message}`);
|
|
54
|
+
errors.push(`Category count query failed: ${(e as Error).message}`);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
let dbSize =
|
|
57
|
+
let dbSize = 'n/a';
|
|
57
58
|
let dbSizeBytes = 0;
|
|
58
59
|
try {
|
|
59
60
|
if (existsSync(config.dbPath)) {
|
|
@@ -64,105 +65,107 @@ export function gatherVaultStatus(ctx, opts = {}) {
|
|
|
64
65
|
: `${(dbSizeBytes / 1024).toFixed(1)}KB`;
|
|
65
66
|
}
|
|
66
67
|
} catch (e) {
|
|
67
|
-
errors.push(`DB size check failed: ${e.message}`);
|
|
68
|
+
errors.push(`DB size check failed: ${(e as Error).message}`);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
let stalePaths = false;
|
|
71
72
|
let staleCount = 0;
|
|
72
73
|
try {
|
|
73
74
|
const result = db
|
|
74
|
-
.prepare(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.get(config.vaultDir);
|
|
78
|
-
staleCount = result.c;
|
|
75
|
+
.prepare(`SELECT COUNT(*) as c FROM vault WHERE file_path NOT LIKE ? || '%'`)
|
|
76
|
+
.get(config.vaultDir) as { c: number } | undefined;
|
|
77
|
+
staleCount = result?.c ?? 0;
|
|
79
78
|
stalePaths = staleCount > 0;
|
|
80
79
|
} catch (e) {
|
|
81
|
-
errors.push(`Stale path check failed: ${e.message}`);
|
|
80
|
+
errors.push(`Stale path check failed: ${(e as Error).message}`);
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
let expiredCount = 0;
|
|
85
84
|
try {
|
|
86
|
-
|
|
85
|
+
const row = db
|
|
87
86
|
.prepare(
|
|
88
|
-
`SELECT COUNT(*) as c FROM vault WHERE expires_at IS NOT NULL AND expires_at <= datetime('now')
|
|
87
|
+
`SELECT COUNT(*) as c FROM vault WHERE expires_at IS NOT NULL AND expires_at <= datetime('now')`
|
|
89
88
|
)
|
|
90
|
-
.get()
|
|
89
|
+
.get() as { c: number } | undefined;
|
|
90
|
+
expiredCount = row?.c ?? 0;
|
|
91
91
|
} catch (e) {
|
|
92
|
-
errors.push(`Expired count failed: ${e.message}`);
|
|
92
|
+
errors.push(`Expired count failed: ${(e as Error).message}`);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
let eventCount = 0;
|
|
96
96
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
const row = db.prepare(`SELECT COUNT(*) as c FROM vault WHERE category = 'event'`).get() as
|
|
98
|
+
| { c: number }
|
|
99
|
+
| undefined;
|
|
100
|
+
eventCount = row?.c ?? 0;
|
|
100
101
|
} catch (e) {
|
|
101
|
-
errors.push(`Event count failed: ${e.message}`);
|
|
102
|
+
errors.push(`Event count failed: ${(e as Error).message}`);
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
let eventsWithoutTtlCount = 0;
|
|
105
106
|
try {
|
|
106
|
-
|
|
107
|
-
.prepare(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.get().c;
|
|
107
|
+
const row = db
|
|
108
|
+
.prepare(`SELECT COUNT(*) as c FROM vault WHERE category = 'event' AND expires_at IS NULL`)
|
|
109
|
+
.get() as { c: number } | undefined;
|
|
110
|
+
eventsWithoutTtlCount = row?.c ?? 0;
|
|
111
111
|
} catch (e) {
|
|
112
|
-
errors.push(`Events without TTL count failed: ${e.message}`);
|
|
112
|
+
errors.push(`Events without TTL count failed: ${(e as Error).message}`);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
let embeddingStatus = null;
|
|
115
|
+
let embeddingStatus: { indexed: number; total: number; missing: number } | null = null;
|
|
116
116
|
try {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
.get()
|
|
117
|
+
const totalRow = db.prepare(`SELECT COUNT(*) as c FROM vault`).get() as
|
|
118
|
+
| { c: number }
|
|
119
|
+
| undefined;
|
|
120
|
+
const indexedRow = db
|
|
121
|
+
.prepare(`SELECT COUNT(*) as c FROM vault WHERE rowid IN (SELECT rowid FROM vault_vec)`)
|
|
122
|
+
.get() as { c: number } | undefined;
|
|
123
|
+
const total = totalRow?.c ?? 0;
|
|
124
|
+
const indexed = indexedRow?.c ?? 0;
|
|
123
125
|
embeddingStatus = { indexed, total, missing: total - indexed };
|
|
124
126
|
} catch (e) {
|
|
125
|
-
errors.push(`Embedding status check failed: ${e.message}`);
|
|
127
|
+
errors.push(`Embedding status check failed: ${(e as Error).message}`);
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
const embedModelAvailable = isEmbedAvailable();
|
|
129
131
|
|
|
130
132
|
let autoCapturedFeedbackCount = 0;
|
|
131
133
|
try {
|
|
132
|
-
|
|
134
|
+
const row = db
|
|
133
135
|
.prepare(
|
|
134
|
-
`SELECT COUNT(*) as c FROM vault WHERE kind = 'feedback' AND tags LIKE '%"auto-captured"%'
|
|
136
|
+
`SELECT COUNT(*) as c FROM vault WHERE kind = 'feedback' AND tags LIKE '%"auto-captured"%'`
|
|
135
137
|
)
|
|
136
|
-
.get()
|
|
138
|
+
.get() as { c: number } | undefined;
|
|
139
|
+
autoCapturedFeedbackCount = row?.c ?? 0;
|
|
137
140
|
} catch (e) {
|
|
138
|
-
errors.push(`Auto-captured feedback count failed: ${e.message}`);
|
|
141
|
+
errors.push(`Auto-captured feedback count failed: ${(e as Error).message}`);
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
let archivedCount = 0;
|
|
142
145
|
try {
|
|
143
146
|
archivedCount = countArchivedEntries(config.vaultDir);
|
|
144
147
|
} catch (e) {
|
|
145
|
-
errors.push(`Archived count failed: ${e.message}`);
|
|
148
|
+
errors.push(`Archived count failed: ${(e as Error).message}`);
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
let staleKnowledge = [];
|
|
151
|
+
let staleKnowledge: unknown[] = [];
|
|
149
152
|
try {
|
|
150
153
|
const stalenessKinds = Object.entries(KIND_STALENESS_DAYS);
|
|
151
154
|
if (stalenessKinds.length > 0) {
|
|
152
155
|
const kindClauses = stalenessKinds
|
|
153
156
|
.map(
|
|
154
157
|
([kind, days]) =>
|
|
155
|
-
`(kind = '${kind}' AND COALESCE(updated_at, created_at) <= datetime('now', '-${days} days'))
|
|
158
|
+
`(kind = '${kind}' AND COALESCE(updated_at, created_at) <= datetime('now', '-${days} days'))`
|
|
156
159
|
)
|
|
157
|
-
.join(
|
|
160
|
+
.join(' OR ');
|
|
158
161
|
staleKnowledge = db
|
|
159
162
|
.prepare(
|
|
160
|
-
`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')) ORDER BY last_updated ASC LIMIT 10
|
|
163
|
+
`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')) ORDER BY last_updated ASC LIMIT 10`
|
|
161
164
|
)
|
|
162
165
|
.all();
|
|
163
166
|
}
|
|
164
167
|
} catch (e) {
|
|
165
|
-
errors.push(`Stale knowledge check failed: ${e.message}`);
|
|
168
|
+
errors.push(`Stale knowledge check failed: ${(e as Error).message}`);
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
return {
|
|
@@ -187,7 +190,16 @@ export function gatherVaultStatus(ctx, opts = {}) {
|
|
|
187
190
|
};
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
export function computeGrowthWarnings(
|
|
193
|
+
export function computeGrowthWarnings(
|
|
194
|
+
status: Record<string, any>,
|
|
195
|
+
thresholds: GrowthThresholds | null | undefined
|
|
196
|
+
): {
|
|
197
|
+
warnings: Array<{ level: string; message: string }>;
|
|
198
|
+
hasCritical: boolean;
|
|
199
|
+
hasWarnings: boolean;
|
|
200
|
+
actions: string[];
|
|
201
|
+
kindBreakdown: Array<{ kind: string; count: number; pct: number }>;
|
|
202
|
+
} {
|
|
191
203
|
if (!thresholds)
|
|
192
204
|
return {
|
|
193
205
|
warnings: [],
|
|
@@ -198,88 +210,66 @@ export function computeGrowthWarnings(status, thresholds) {
|
|
|
198
210
|
};
|
|
199
211
|
|
|
200
212
|
const t = thresholds;
|
|
201
|
-
const warnings = [];
|
|
202
|
-
const actions = [];
|
|
213
|
+
const warnings: Array<{ level: string; message: string }> = [];
|
|
214
|
+
const actions: string[] = [];
|
|
203
215
|
|
|
204
|
-
const total = status.embeddingStatus?.total ?? 0;
|
|
205
|
-
const {
|
|
206
|
-
eventCount = 0,
|
|
207
|
-
eventsWithoutTtlCount = 0,
|
|
208
|
-
expiredCount = 0,
|
|
209
|
-
dbSizeBytes = 0,
|
|
210
|
-
} = status;
|
|
216
|
+
const total: number = status.embeddingStatus?.total ?? 0;
|
|
217
|
+
const { eventCount = 0, eventsWithoutTtlCount = 0, expiredCount = 0, dbSizeBytes = 0 } = status;
|
|
211
218
|
|
|
212
219
|
let totalExceeded = false;
|
|
213
220
|
|
|
214
221
|
if (t.totalEntries?.critical != null && total >= t.totalEntries.critical) {
|
|
215
222
|
totalExceeded = true;
|
|
216
223
|
warnings.push({
|
|
217
|
-
level:
|
|
224
|
+
level: 'critical',
|
|
218
225
|
message: `Total entries: ${total.toLocaleString()} (exceeds critical limit of ${t.totalEntries.critical.toLocaleString()})`,
|
|
219
226
|
});
|
|
220
227
|
} else if (t.totalEntries?.warn != null && total >= t.totalEntries.warn) {
|
|
221
228
|
totalExceeded = true;
|
|
222
229
|
warnings.push({
|
|
223
|
-
level:
|
|
230
|
+
level: 'warn',
|
|
224
231
|
message: `Total entries: ${total.toLocaleString()} (exceeds recommended ${t.totalEntries.warn.toLocaleString()})`,
|
|
225
232
|
});
|
|
226
233
|
}
|
|
227
234
|
|
|
228
|
-
if (
|
|
229
|
-
t.eventEntries?.critical != null &&
|
|
230
|
-
eventCount >= t.eventEntries.critical
|
|
231
|
-
) {
|
|
235
|
+
if (t.eventEntries?.critical != null && eventCount >= t.eventEntries.critical) {
|
|
232
236
|
warnings.push({
|
|
233
|
-
level:
|
|
237
|
+
level: 'critical',
|
|
234
238
|
message: `Event entries: ${eventCount.toLocaleString()} (exceeds critical limit of ${t.eventEntries.critical.toLocaleString()})`,
|
|
235
239
|
});
|
|
236
|
-
} else if (
|
|
237
|
-
t.eventEntries?.warn != null &&
|
|
238
|
-
eventCount >= t.eventEntries.warn
|
|
239
|
-
) {
|
|
240
|
+
} else if (t.eventEntries?.warn != null && eventCount >= t.eventEntries.warn) {
|
|
240
241
|
const ttlNote =
|
|
241
|
-
eventsWithoutTtlCount > 0
|
|
242
|
-
? ` (${eventsWithoutTtlCount.toLocaleString()} without TTL)`
|
|
243
|
-
: "";
|
|
242
|
+
eventsWithoutTtlCount > 0 ? ` (${eventsWithoutTtlCount.toLocaleString()} without TTL)` : '';
|
|
244
243
|
warnings.push({
|
|
245
|
-
level:
|
|
244
|
+
level: 'warn',
|
|
246
245
|
message: `Event entries: ${eventCount.toLocaleString()}${ttlNote} (exceeds recommended ${t.eventEntries.warn.toLocaleString()})`,
|
|
247
246
|
});
|
|
248
247
|
}
|
|
249
248
|
|
|
250
|
-
if (
|
|
251
|
-
t.vaultSizeBytes?.critical != null &&
|
|
252
|
-
dbSizeBytes >= t.vaultSizeBytes.critical
|
|
253
|
-
) {
|
|
249
|
+
if (t.vaultSizeBytes?.critical != null && dbSizeBytes >= t.vaultSizeBytes.critical) {
|
|
254
250
|
warnings.push({
|
|
255
|
-
level:
|
|
251
|
+
level: 'critical',
|
|
256
252
|
message: `Database size: ${(dbSizeBytes / 1024 / 1024).toFixed(1)}MB (exceeds critical limit of ${(t.vaultSizeBytes.critical / 1024 / 1024).toFixed(0)}MB)`,
|
|
257
253
|
});
|
|
258
|
-
} else if (
|
|
259
|
-
t.vaultSizeBytes?.warn != null &&
|
|
260
|
-
dbSizeBytes >= t.vaultSizeBytes.warn
|
|
261
|
-
) {
|
|
254
|
+
} else if (t.vaultSizeBytes?.warn != null && dbSizeBytes >= t.vaultSizeBytes.warn) {
|
|
262
255
|
warnings.push({
|
|
263
|
-
level:
|
|
256
|
+
level: 'warn',
|
|
264
257
|
message: `Database size: ${(dbSizeBytes / 1024 / 1024).toFixed(1)}MB (exceeds recommended ${(t.vaultSizeBytes.warn / 1024 / 1024).toFixed(0)}MB)`,
|
|
265
258
|
});
|
|
266
259
|
}
|
|
267
260
|
|
|
268
|
-
if (
|
|
269
|
-
t.eventsWithoutTtl?.warn != null &&
|
|
270
|
-
eventsWithoutTtlCount >= t.eventsWithoutTtl.warn
|
|
271
|
-
) {
|
|
261
|
+
if (t.eventsWithoutTtl?.warn != null && eventsWithoutTtlCount >= t.eventsWithoutTtl.warn) {
|
|
272
262
|
warnings.push({
|
|
273
|
-
level:
|
|
263
|
+
level: 'warn',
|
|
274
264
|
message: `Event entries without expires_at: ${eventsWithoutTtlCount.toLocaleString()} (exceeds recommended ${t.eventsWithoutTtl.warn.toLocaleString()})`,
|
|
275
265
|
});
|
|
276
266
|
}
|
|
277
267
|
|
|
278
|
-
const hasCritical = warnings.some((w) => w.level ===
|
|
268
|
+
const hasCritical = warnings.some((w) => w.level === 'critical');
|
|
279
269
|
|
|
280
270
|
if (expiredCount > 0) {
|
|
281
271
|
actions.push(
|
|
282
|
-
`Run \`context-vault prune\` to remove ${expiredCount} expired event entr${expiredCount === 1 ?
|
|
272
|
+
`Run \`context-vault prune\` to remove ${expiredCount} expired event entr${expiredCount === 1 ? 'y' : 'ies'}`
|
|
283
273
|
);
|
|
284
274
|
}
|
|
285
275
|
if (
|
|
@@ -287,19 +277,15 @@ export function computeGrowthWarnings(status, thresholds) {
|
|
|
287
277
|
(eventCount >= (t.eventEntries?.warn ?? Infinity) ||
|
|
288
278
|
eventsWithoutTtlCount >= (t.eventsWithoutTtl?.warn ?? Infinity))
|
|
289
279
|
) {
|
|
290
|
-
actions.push(
|
|
291
|
-
"Add `expires_at` to event/session entries to enable automatic cleanup",
|
|
292
|
-
);
|
|
280
|
+
actions.push('Add `expires_at` to event/session entries to enable automatic cleanup');
|
|
293
281
|
}
|
|
294
282
|
if (total >= (t.totalEntries?.warn ?? Infinity)) {
|
|
295
|
-
actions.push(
|
|
296
|
-
"Run `context-vault archive` to move old ephemeral/event entries to _archive/",
|
|
297
|
-
);
|
|
283
|
+
actions.push('Run `context-vault archive` to move old ephemeral/event entries to _archive/');
|
|
298
284
|
}
|
|
299
285
|
|
|
300
|
-
const kindBreakdown =
|
|
286
|
+
const kindBreakdown: Array<{ kind: string; count: number; pct: number }> =
|
|
301
287
|
totalExceeded && status.kindCounts?.length
|
|
302
|
-
? [...status.kindCounts]
|
|
288
|
+
? [...(status.kindCounts as Array<{ kind: string; c: number }>)]
|
|
303
289
|
.sort((a, b) => b.c - a.c)
|
|
304
290
|
.map(({ kind, c }) => ({
|
|
305
291
|
kind,
|
package/src/telemetry.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { API_URL, MARKETING_URL, GITHUB_ISSUES_URL } from '@context-vault/core/constants';
|
|
4
|
+
import type { VaultConfig } from '@context-vault/core/types';
|
|
5
|
+
|
|
6
|
+
const TELEMETRY_ENDPOINT = `${API_URL}/telemetry`;
|
|
7
|
+
const NOTICE_MARKER = '.telemetry-notice-shown';
|
|
8
|
+
const FEEDBACK_PROMPT_MARKER = '.feedback-prompt-shown';
|
|
9
|
+
|
|
10
|
+
export function isTelemetryEnabled(config: VaultConfig | null | undefined): boolean {
|
|
11
|
+
const envVal = process.env.CONTEXT_VAULT_TELEMETRY;
|
|
12
|
+
if (envVal !== undefined) return envVal === '1' || envVal === 'true';
|
|
13
|
+
return config?.telemetry === true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sendTelemetryEvent(
|
|
17
|
+
config: VaultConfig | null | undefined,
|
|
18
|
+
payload: Record<string, unknown>
|
|
19
|
+
): void {
|
|
20
|
+
if (!isTelemetryEnabled(config)) return;
|
|
21
|
+
|
|
22
|
+
const event = {
|
|
23
|
+
event: payload.event,
|
|
24
|
+
code: payload.code || null,
|
|
25
|
+
tool: payload.tool || null,
|
|
26
|
+
cv_version: payload.cv_version,
|
|
27
|
+
node_version: process.version,
|
|
28
|
+
platform: process.platform,
|
|
29
|
+
arch: process.arch,
|
|
30
|
+
ts: new Date().toISOString(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
fetch(TELEMETRY_ENDPOINT, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
body: JSON.stringify(event),
|
|
37
|
+
signal: AbortSignal.timeout(5000),
|
|
38
|
+
}).catch(() => {});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function maybeShowTelemetryNotice(dataDir: string): void {
|
|
42
|
+
try {
|
|
43
|
+
const markerPath = join(dataDir, NOTICE_MARKER);
|
|
44
|
+
if (existsSync(markerPath)) return;
|
|
45
|
+
writeFileSync(markerPath, new Date().toISOString() + '\n');
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const lines = [
|
|
51
|
+
'[context-vault] Telemetry: disabled by default.',
|
|
52
|
+
'[context-vault] To help improve context-vault, you can opt in to anonymous error reporting.',
|
|
53
|
+
'[context-vault] Reports contain only: event type, error code, tool name, version, node version, platform, arch, timestamp.',
|
|
54
|
+
'[context-vault] No vault content, file paths, or personal data is ever sent.',
|
|
55
|
+
'[context-vault] Opt in: set "telemetry": true in ~/.context-mcp/config.json or set CONTEXT_VAULT_TELEMETRY=1.',
|
|
56
|
+
`[context-vault] Full payload schema: ${MARKETING_URL}/telemetry`,
|
|
57
|
+
];
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
process.stderr.write(line + '\n');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function maybeShowFeedbackPrompt(dataDir: string): void {
|
|
64
|
+
try {
|
|
65
|
+
const markerPath = join(dataDir, FEEDBACK_PROMPT_MARKER);
|
|
66
|
+
if (existsSync(markerPath)) return;
|
|
67
|
+
writeFileSync(markerPath, new Date().toISOString() + '\n');
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const lines = [
|
|
73
|
+
'[context-vault] First entry saved — nice work!',
|
|
74
|
+
'[context-vault] Got feedback, a bug, or a feature request?',
|
|
75
|
+
`[context-vault] Open an issue: ${GITHUB_ISSUES_URL}`,
|
|
76
|
+
];
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
process.stderr.write(line + '\n');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
const SHORTCUT_RE = /^last[_ ](\d+)[_ ](day|days|week|weeks|month|months)$/i;
|
|
2
2
|
|
|
3
|
-
function startOfToday(now) {
|
|
3
|
+
function startOfToday(now: Date): Date {
|
|
4
4
|
const d = new Date(now);
|
|
5
5
|
d.setUTCHours(0, 0, 0, 0);
|
|
6
6
|
return d;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function resolveTemporalShortcut(
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export function resolveTemporalShortcut(
|
|
10
|
+
role: 'since' | 'until',
|
|
11
|
+
value: string,
|
|
12
|
+
now: Date = new Date()
|
|
13
|
+
): string {
|
|
14
|
+
if (!value || typeof value !== 'string') return value;
|
|
15
|
+
const trimmed = value.trim().toLowerCase().replace(/\s+/g, '_');
|
|
12
16
|
|
|
13
|
-
if (trimmed ===
|
|
17
|
+
if (trimmed === 'today') {
|
|
14
18
|
const start = startOfToday(now);
|
|
15
|
-
if (role ===
|
|
19
|
+
if (role === 'until') {
|
|
16
20
|
const end = new Date(start);
|
|
17
21
|
end.setUTCDate(end.getUTCDate() + 1);
|
|
18
22
|
return end.toISOString();
|
|
@@ -20,42 +24,40 @@ export function resolveTemporalShortcut(role, value, now = new Date()) {
|
|
|
20
24
|
return start.toISOString();
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
if (trimmed ===
|
|
27
|
+
if (trimmed === 'yesterday') {
|
|
24
28
|
const todayStart = startOfToday(now);
|
|
25
29
|
const yesterdayStart = new Date(todayStart);
|
|
26
30
|
yesterdayStart.setUTCDate(yesterdayStart.getUTCDate() - 1);
|
|
27
|
-
if (role ===
|
|
31
|
+
if (role === 'since') return yesterdayStart.toISOString();
|
|
28
32
|
return todayStart.toISOString();
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
if (trimmed ===
|
|
35
|
+
if (trimmed === 'this_week') {
|
|
32
36
|
const todayStart = startOfToday(now);
|
|
33
37
|
const dayOfWeek = todayStart.getUTCDay();
|
|
34
38
|
const daysFromMonday = (dayOfWeek + 6) % 7;
|
|
35
39
|
const monday = new Date(todayStart);
|
|
36
40
|
monday.setUTCDate(monday.getUTCDate() - daysFromMonday);
|
|
37
|
-
if (role ===
|
|
41
|
+
if (role === 'since') return monday.toISOString();
|
|
38
42
|
const endOfToday = new Date(todayStart);
|
|
39
43
|
endOfToday.setUTCDate(endOfToday.getUTCDate() + 1);
|
|
40
44
|
return endOfToday.toISOString();
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
if (trimmed ===
|
|
47
|
+
if (trimmed === 'this_month') {
|
|
44
48
|
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
|
|
45
|
-
if (role ===
|
|
46
|
-
const endOfMonth = new Date(
|
|
47
|
-
Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1),
|
|
48
|
-
);
|
|
49
|
+
if (role === 'since') return d.toISOString();
|
|
50
|
+
const endOfMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1));
|
|
49
51
|
return endOfMonth.toISOString();
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
const m = SHORTCUT_RE.exec(trimmed);
|
|
53
55
|
if (m) {
|
|
54
56
|
const n = parseInt(m[1], 10);
|
|
55
|
-
const unit = m[2].replace(/s$/,
|
|
57
|
+
const unit = m[2].replace(/s$/, '');
|
|
56
58
|
let ms;
|
|
57
|
-
if (unit ===
|
|
58
|
-
else if (unit ===
|
|
59
|
+
if (unit === 'day') ms = n * 86400000;
|
|
60
|
+
else if (unit === 'week') ms = n * 7 * 86400000;
|
|
59
61
|
else ms = n * 30 * 86400000;
|
|
60
62
|
const target = new Date(now.getTime() - ms);
|
|
61
63
|
target.setUTCHours(0, 0, 0, 0);
|
|
@@ -65,26 +67,20 @@ export function resolveTemporalShortcut(role, value, now = new Date()) {
|
|
|
65
67
|
return value;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
export function resolveTemporalParams(
|
|
70
|
+
export function resolveTemporalParams(
|
|
71
|
+
params: { since?: string; until?: string },
|
|
72
|
+
now: Date = new Date()
|
|
73
|
+
): { since?: string; until?: string } {
|
|
69
74
|
let { since, until } = params;
|
|
70
75
|
|
|
71
|
-
if (
|
|
72
|
-
since
|
|
73
|
-
|
|
74
|
-
) {
|
|
75
|
-
since = resolveTemporalShortcut("since", since, now);
|
|
76
|
-
until = resolveTemporalShortcut("until", "yesterday", now);
|
|
76
|
+
if (since?.trim().toLowerCase() === 'yesterday' && (until === undefined || until === null)) {
|
|
77
|
+
since = resolveTemporalShortcut('since', since, now);
|
|
78
|
+
until = resolveTemporalShortcut('until', 'yesterday', now);
|
|
77
79
|
return { since, until };
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
return {
|
|
81
|
-
since:
|
|
82
|
-
|
|
83
|
-
? resolveTemporalShortcut("since", since, now)
|
|
84
|
-
: since,
|
|
85
|
-
until:
|
|
86
|
-
until !== undefined
|
|
87
|
-
? resolveTemporalShortcut("until", until, now)
|
|
88
|
-
: until,
|
|
83
|
+
since: since !== undefined ? resolveTemporalShortcut('since', since, now) : since,
|
|
84
|
+
until: until !== undefined ? resolveTemporalShortcut('until', until, now) : until,
|
|
89
85
|
};
|
|
90
86
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ok } from '../helpers.js';
|
|
3
|
+
import type { ToolResult } from '../types.js';
|
|
4
|
+
|
|
5
|
+
export const name = 'clear_context';
|
|
6
|
+
|
|
7
|
+
export const description =
|
|
8
|
+
'Reset active in-memory session context without deleting vault entries. Call this when switching projects or topics mid-session. With `scope`, all subsequent get_context calls should filter to that tag/project. Vault data is never modified.';
|
|
9
|
+
|
|
10
|
+
export const inputSchema = {
|
|
11
|
+
scope: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe(
|
|
15
|
+
'Optional tag or project name to focus on going forward. When provided, treat subsequent get_context calls as if filtered to this tag.'
|
|
16
|
+
),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function handler({ scope }: { scope?: string } = {}): ToolResult {
|
|
20
|
+
const lines = [
|
|
21
|
+
'## Context Reset',
|
|
22
|
+
'',
|
|
23
|
+
'Active session context has been cleared. All previous context from this session should be disregarded.',
|
|
24
|
+
'',
|
|
25
|
+
'Vault entries are unchanged — no data was deleted.',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
if (scope?.trim()) {
|
|
29
|
+
const trimmed = scope.trim();
|
|
30
|
+
lines.push(
|
|
31
|
+
'',
|
|
32
|
+
`### Active Scope: \`${trimmed}\``,
|
|
33
|
+
'',
|
|
34
|
+
`Going forward, treat \`get_context\` calls as scoped to the tag or project **"${trimmed}"** unless the user explicitly requests a different scope or passes their own tag filters.`
|
|
35
|
+
);
|
|
36
|
+
} else {
|
|
37
|
+
lines.push('', 'No scope set. Use `get_context` normally — all vault entries are accessible.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return ok(lines.join('\n'));
|
|
41
|
+
}
|