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.
Files changed (184) hide show
  1. package/bin/cli.js +1369 -1774
  2. package/dist/archive.d.ts +23 -0
  3. package/dist/archive.d.ts.map +1 -0
  4. package/dist/archive.js +197 -0
  5. package/dist/archive.js.map +1 -0
  6. package/dist/consolidation.d.ts +14 -0
  7. package/dist/consolidation.d.ts.map +1 -0
  8. package/dist/consolidation.js +59 -0
  9. package/dist/consolidation.js.map +1 -0
  10. package/dist/error-log.d.ts +4 -0
  11. package/dist/error-log.d.ts.map +1 -0
  12. package/dist/error-log.js +33 -0
  13. package/dist/error-log.js.map +1 -0
  14. package/dist/helpers.d.ts +10 -0
  15. package/dist/helpers.d.ts.map +1 -0
  16. package/dist/helpers.js +42 -0
  17. package/dist/helpers.js.map +1 -0
  18. package/dist/linking.d.ts +13 -0
  19. package/dist/linking.d.ts.map +1 -0
  20. package/dist/linking.js +86 -0
  21. package/dist/linking.js.map +1 -0
  22. package/dist/migrate-dirs.d.ts +16 -0
  23. package/dist/migrate-dirs.d.ts.map +1 -0
  24. package/dist/migrate-dirs.js +127 -0
  25. package/dist/migrate-dirs.js.map +1 -0
  26. package/dist/register-tools.d.ts +3 -0
  27. package/dist/register-tools.d.ts.map +1 -0
  28. package/dist/register-tools.js +161 -0
  29. package/dist/register-tools.js.map +1 -0
  30. package/dist/server.d.ts +3 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +241 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/status.d.ts +18 -0
  35. package/dist/status.d.ts.map +1 -0
  36. package/dist/status.js +265 -0
  37. package/dist/status.js.map +1 -0
  38. package/dist/telemetry.d.ts +6 -0
  39. package/dist/telemetry.d.ts.map +1 -0
  40. package/dist/telemetry.js +74 -0
  41. package/dist/telemetry.js.map +1 -0
  42. package/dist/temporal.d.ts +9 -0
  43. package/dist/temporal.d.ts.map +1 -0
  44. package/dist/temporal.js +76 -0
  45. package/dist/temporal.js.map +1 -0
  46. package/dist/tools/clear-context.d.ts +11 -0
  47. package/dist/tools/clear-context.d.ts.map +1 -0
  48. package/dist/tools/clear-context.js +28 -0
  49. package/dist/tools/clear-context.js.map +1 -0
  50. package/dist/tools/context-status.d.ts +6 -0
  51. package/dist/tools/context-status.d.ts.map +1 -0
  52. package/dist/tools/context-status.js +160 -0
  53. package/dist/tools/context-status.js.map +1 -0
  54. package/dist/tools/create-snapshot.d.ts +13 -0
  55. package/dist/tools/create-snapshot.d.ts.map +1 -0
  56. package/dist/tools/create-snapshot.js +161 -0
  57. package/dist/tools/create-snapshot.js.map +1 -0
  58. package/dist/tools/delete-context.d.ts +9 -0
  59. package/dist/tools/delete-context.d.ts.map +1 -0
  60. package/dist/tools/delete-context.js +45 -0
  61. package/dist/tools/delete-context.js.map +1 -0
  62. package/dist/tools/get-context.d.ts +85 -0
  63. package/dist/tools/get-context.d.ts.map +1 -0
  64. package/dist/tools/get-context.js +576 -0
  65. package/dist/tools/get-context.js.map +1 -0
  66. package/dist/tools/ingest-project.d.ts +11 -0
  67. package/dist/tools/ingest-project.d.ts.map +1 -0
  68. package/dist/tools/ingest-project.js +226 -0
  69. package/dist/tools/ingest-project.js.map +1 -0
  70. package/dist/tools/ingest-url.d.ts +11 -0
  71. package/dist/tools/ingest-url.d.ts.map +1 -0
  72. package/dist/tools/ingest-url.js +62 -0
  73. package/dist/tools/ingest-url.js.map +1 -0
  74. package/dist/tools/list-buckets.d.ts +9 -0
  75. package/dist/tools/list-buckets.d.ts.map +1 -0
  76. package/dist/tools/list-buckets.js +76 -0
  77. package/dist/tools/list-buckets.js.map +1 -0
  78. package/dist/tools/list-context.d.ts +19 -0
  79. package/dist/tools/list-context.d.ts.map +1 -0
  80. package/dist/tools/list-context.js +110 -0
  81. package/dist/tools/list-context.js.map +1 -0
  82. package/dist/tools/save-context.d.ts +36 -0
  83. package/dist/tools/save-context.d.ts.map +1 -0
  84. package/dist/tools/save-context.js +458 -0
  85. package/dist/tools/save-context.js.map +1 -0
  86. package/dist/tools/session-start.d.ts +11 -0
  87. package/dist/tools/session-start.d.ts.map +1 -0
  88. package/dist/tools/session-start.js +224 -0
  89. package/dist/tools/session-start.js.map +1 -0
  90. package/dist/types.d.ts +37 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +2 -0
  93. package/dist/types.js.map +1 -0
  94. package/node_modules/@context-vault/core/dist/capture.d.ts +1 -1
  95. package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -1
  96. package/node_modules/@context-vault/core/dist/capture.js +34 -47
  97. package/node_modules/@context-vault/core/dist/capture.js.map +1 -1
  98. package/node_modules/@context-vault/core/dist/categories.js +30 -30
  99. package/node_modules/@context-vault/core/dist/config.d.ts +1 -1
  100. package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -1
  101. package/node_modules/@context-vault/core/dist/config.js +37 -43
  102. package/node_modules/@context-vault/core/dist/config.js.map +1 -1
  103. package/node_modules/@context-vault/core/dist/constants.d.ts +1 -1
  104. package/node_modules/@context-vault/core/dist/constants.d.ts.map +1 -1
  105. package/node_modules/@context-vault/core/dist/constants.js +4 -4
  106. package/node_modules/@context-vault/core/dist/constants.js.map +1 -1
  107. package/node_modules/@context-vault/core/dist/db.d.ts +2 -2
  108. package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -1
  109. package/node_modules/@context-vault/core/dist/db.js +21 -20
  110. package/node_modules/@context-vault/core/dist/db.js.map +1 -1
  111. package/node_modules/@context-vault/core/dist/embed.d.ts.map +1 -1
  112. package/node_modules/@context-vault/core/dist/embed.js +11 -11
  113. package/node_modules/@context-vault/core/dist/embed.js.map +1 -1
  114. package/node_modules/@context-vault/core/dist/files.d.ts.map +1 -1
  115. package/node_modules/@context-vault/core/dist/files.js +12 -13
  116. package/node_modules/@context-vault/core/dist/files.js.map +1 -1
  117. package/node_modules/@context-vault/core/dist/formatters.js +5 -5
  118. package/node_modules/@context-vault/core/dist/frontmatter.d.ts.map +1 -1
  119. package/node_modules/@context-vault/core/dist/frontmatter.js +23 -23
  120. package/node_modules/@context-vault/core/dist/frontmatter.js.map +1 -1
  121. package/node_modules/@context-vault/core/dist/index.d.ts +1 -1
  122. package/node_modules/@context-vault/core/dist/index.d.ts.map +1 -1
  123. package/node_modules/@context-vault/core/dist/index.js +58 -46
  124. package/node_modules/@context-vault/core/dist/index.js.map +1 -1
  125. package/node_modules/@context-vault/core/dist/ingest-url.d.ts.map +1 -1
  126. package/node_modules/@context-vault/core/dist/ingest-url.js +30 -33
  127. package/node_modules/@context-vault/core/dist/ingest-url.js.map +1 -1
  128. package/node_modules/@context-vault/core/dist/main.d.ts +13 -13
  129. package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -1
  130. package/node_modules/@context-vault/core/dist/main.js +12 -12
  131. package/node_modules/@context-vault/core/dist/main.js.map +1 -1
  132. package/node_modules/@context-vault/core/dist/search.d.ts +1 -1
  133. package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
  134. package/node_modules/@context-vault/core/dist/search.js +20 -22
  135. package/node_modules/@context-vault/core/dist/search.js.map +1 -1
  136. package/node_modules/@context-vault/core/dist/types.d.ts +1 -1
  137. package/node_modules/@context-vault/core/package.json +1 -1
  138. package/node_modules/@context-vault/core/src/capture.ts +44 -81
  139. package/node_modules/@context-vault/core/src/categories.ts +30 -30
  140. package/node_modules/@context-vault/core/src/config.ts +45 -60
  141. package/node_modules/@context-vault/core/src/constants.ts +8 -10
  142. package/node_modules/@context-vault/core/src/db.ts +37 -56
  143. package/node_modules/@context-vault/core/src/embed.ts +15 -26
  144. package/node_modules/@context-vault/core/src/files.ts +13 -16
  145. package/node_modules/@context-vault/core/src/formatters.ts +5 -5
  146. package/node_modules/@context-vault/core/src/frontmatter.ts +26 -30
  147. package/node_modules/@context-vault/core/src/index.ts +94 -100
  148. package/node_modules/@context-vault/core/src/ingest-url.ts +56 -93
  149. package/node_modules/@context-vault/core/src/main.ts +13 -18
  150. package/node_modules/@context-vault/core/src/search.ts +34 -56
  151. package/node_modules/@context-vault/core/src/types.ts +1 -1
  152. package/package.json +10 -4
  153. package/scripts/postinstall.js +18 -25
  154. package/scripts/prepack.js +13 -19
  155. package/src/archive.ts +244 -0
  156. package/src/consolidation.ts +78 -0
  157. package/src/{error-log.js → error-log.ts} +10 -10
  158. package/src/helpers.ts +61 -0
  159. package/src/{linking.js → linking.ts} +22 -20
  160. package/src/migrate-dirs.ts +152 -0
  161. package/src/register-tools.ts +183 -0
  162. package/src/{server.js → server.ts} +89 -109
  163. package/src/{status.js → status.ts} +94 -108
  164. package/src/telemetry.ts +80 -0
  165. package/src/{temporal.js → temporal.ts} +29 -33
  166. package/src/tools/clear-context.ts +41 -0
  167. package/src/tools/{context-status.js → context-status.ts} +43 -66
  168. package/src/tools/{create-snapshot.js → create-snapshot.ts} +54 -65
  169. package/src/tools/delete-context.ts +53 -0
  170. package/src/tools/{get-context.js → get-context.ts} +142 -205
  171. package/src/tools/ingest-project.ts +260 -0
  172. package/src/tools/ingest-url.ts +74 -0
  173. package/src/tools/{list-buckets.js → list-buckets.ts} +27 -37
  174. package/src/tools/{list-context.js → list-context.ts} +46 -71
  175. package/src/tools/{save-context.js → save-context.ts} +148 -204
  176. package/src/tools/{session-start.js → session-start.ts} +72 -79
  177. package/src/types.ts +29 -0
  178. package/src/helpers.js +0 -57
  179. package/src/register-tools.js +0 -175
  180. package/src/telemetry.js +0 -80
  181. package/src/tools/clear-context.js +0 -47
  182. package/src/tools/delete-context.js +0 -54
  183. package/src/tools/ingest-project.js +0 -272
  184. package/src/tools/ingest-url.js +0 -87
@@ -1,11 +1,13 @@
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";
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, "_archive");
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 = "n/a";
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
- `SELECT COUNT(*) as c FROM vault WHERE file_path NOT LIKE ? || '%'`,
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
- expiredCount = db
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().c;
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
- eventCount = db
98
- .prepare(`SELECT COUNT(*) as c FROM vault WHERE category = 'event'`)
99
- .get().c;
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
- eventsWithoutTtlCount = db
107
- .prepare(
108
- `SELECT COUNT(*) as c FROM vault WHERE category = 'event' AND expires_at IS NULL`,
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 total = db.prepare(`SELECT COUNT(*) as c FROM vault`).get().c;
118
- const indexed = db
119
- .prepare(
120
- `SELECT COUNT(*) as c FROM vault WHERE rowid IN (SELECT rowid FROM vault_vec)`,
121
- )
122
- .get().c;
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
- autoCapturedFeedbackCount = db
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().c;
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(" OR ");
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(status, thresholds) {
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: "critical",
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: "warn",
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: "critical",
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: "warn",
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: "critical",
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: "warn",
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: "warn",
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 === "critical");
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 ? "y" : "ies"}`,
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,
@@ -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(role, value, now = new Date()) {
10
- if (!value || typeof value !== "string") return value;
11
- const trimmed = value.trim().toLowerCase().replace(/\s+/g, "_");
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 === "today") {
17
+ if (trimmed === 'today') {
14
18
  const start = startOfToday(now);
15
- if (role === "until") {
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 === "yesterday") {
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 === "since") return yesterdayStart.toISOString();
31
+ if (role === 'since') return yesterdayStart.toISOString();
28
32
  return todayStart.toISOString();
29
33
  }
30
34
 
31
- if (trimmed === "this_week") {
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 === "since") return monday.toISOString();
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 === "this_month") {
47
+ if (trimmed === 'this_month') {
44
48
  const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
45
- if (role === "since") return d.toISOString();
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 === "day") ms = n * 86400000;
58
- else if (unit === "week") ms = n * 7 * 86400000;
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(params, now = new Date()) {
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?.trim().toLowerCase() === "yesterday" &&
73
- (until === undefined || until === null)
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
- since !== undefined
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
+ }