context-vault 2.17.0 → 3.0.1
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 +783 -108
- package/node_modules/@context-vault/core/dist/capture.d.ts +21 -0
- package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/capture.js +269 -0
- package/node_modules/@context-vault/core/dist/capture.js.map +1 -0
- package/node_modules/@context-vault/core/dist/categories.d.ts +6 -0
- package/node_modules/@context-vault/core/dist/categories.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/categories.js +50 -0
- package/node_modules/@context-vault/core/dist/categories.js.map +1 -0
- package/node_modules/@context-vault/core/dist/config.d.ts +4 -0
- package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/config.js +190 -0
- package/node_modules/@context-vault/core/dist/config.js.map +1 -0
- package/node_modules/@context-vault/core/dist/constants.d.ts +33 -0
- package/node_modules/@context-vault/core/dist/constants.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/constants.js +23 -0
- package/node_modules/@context-vault/core/dist/constants.js.map +1 -0
- package/node_modules/@context-vault/core/dist/db.d.ts +13 -0
- package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/db.js +191 -0
- package/node_modules/@context-vault/core/dist/db.js.map +1 -0
- package/node_modules/@context-vault/core/dist/embed.d.ts +5 -0
- package/node_modules/@context-vault/core/dist/embed.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/embed.js +78 -0
- package/node_modules/@context-vault/core/dist/embed.js.map +1 -0
- package/node_modules/@context-vault/core/dist/files.d.ts +13 -0
- package/node_modules/@context-vault/core/dist/files.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/files.js +66 -0
- package/node_modules/@context-vault/core/dist/files.js.map +1 -0
- package/node_modules/@context-vault/core/dist/formatters.d.ts +8 -0
- package/node_modules/@context-vault/core/dist/formatters.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/formatters.js +18 -0
- package/node_modules/@context-vault/core/dist/formatters.js.map +1 -0
- package/node_modules/@context-vault/core/dist/frontmatter.d.ts +12 -0
- package/node_modules/@context-vault/core/dist/frontmatter.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/frontmatter.js +101 -0
- package/node_modules/@context-vault/core/dist/frontmatter.js.map +1 -0
- package/node_modules/@context-vault/core/dist/index.d.ts +10 -0
- package/node_modules/@context-vault/core/dist/index.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/index.js +297 -0
- package/node_modules/@context-vault/core/dist/index.js.map +1 -0
- package/node_modules/@context-vault/core/dist/ingest-url.d.ts +20 -0
- package/node_modules/@context-vault/core/dist/ingest-url.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/ingest-url.js +113 -0
- package/node_modules/@context-vault/core/dist/ingest-url.js.map +1 -0
- package/node_modules/@context-vault/core/dist/main.d.ts +14 -0
- package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/main.js +25 -0
- package/node_modules/@context-vault/core/dist/main.js.map +1 -0
- package/node_modules/@context-vault/core/dist/search.d.ts +18 -0
- package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/search.js +238 -0
- package/node_modules/@context-vault/core/dist/search.js.map +1 -0
- package/node_modules/@context-vault/core/dist/types.d.ts +176 -0
- package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/types.js +2 -0
- package/node_modules/@context-vault/core/dist/types.js.map +1 -0
- package/node_modules/@context-vault/core/package.json +66 -16
- package/node_modules/@context-vault/core/src/capture.ts +308 -0
- package/node_modules/@context-vault/core/src/categories.ts +54 -0
- package/node_modules/@context-vault/core/src/{core/config.js → config.ts} +34 -33
- package/node_modules/@context-vault/core/src/{constants.js → constants.ts} +6 -3
- package/node_modules/@context-vault/core/src/db.ts +229 -0
- package/node_modules/@context-vault/core/src/{index/embed.js → embed.ts} +10 -35
- package/node_modules/@context-vault/core/src/files.ts +80 -0
- package/node_modules/@context-vault/core/src/{capture/formatters.js → formatters.ts} +13 -11
- package/node_modules/@context-vault/core/src/{core/frontmatter.js → frontmatter.ts} +27 -33
- package/node_modules/@context-vault/core/src/index.ts +351 -0
- package/node_modules/@context-vault/core/src/ingest-url.ts +99 -0
- package/node_modules/@context-vault/core/src/main.ts +111 -0
- package/node_modules/@context-vault/core/src/search.ts +285 -0
- package/node_modules/@context-vault/core/src/types.ts +166 -0
- package/package.json +12 -7
- package/scripts/postinstall.js +1 -1
- package/{node_modules/@context-vault/core/src/core → src}/error-log.js +1 -15
- package/{node_modules/@context-vault/core/src/server → src}/helpers.js +9 -4
- package/src/linking.js +100 -0
- package/{node_modules/@context-vault/core/src/server/tools.js → src/register-tools.js} +14 -15
- package/src/{server/index.js → server.js} +8 -35
- package/src/status.js +235 -0
- package/{node_modules/@context-vault/core/src/core → src}/telemetry.js +9 -19
- package/src/temporal.js +97 -0
- package/{node_modules/@context-vault/core/src/server → src}/tools/context-status.js +3 -4
- package/{node_modules/@context-vault/core/src/server → src}/tools/create-snapshot.js +43 -75
- package/{node_modules/@context-vault/core/src/server → src}/tools/delete-context.js +0 -2
- package/{node_modules/@context-vault/core/src/server → src}/tools/get-context.js +118 -35
- package/{node_modules/@context-vault/core/src/server → src}/tools/ingest-project.js +5 -6
- package/{node_modules/@context-vault/core/src/server → src}/tools/ingest-url.js +3 -4
- package/{node_modules/@context-vault/core/src/server → src}/tools/list-buckets.js +4 -5
- package/{node_modules/@context-vault/core/src/server → src}/tools/list-context.js +3 -6
- package/{node_modules/@context-vault/core/src/server → src}/tools/save-context.js +41 -21
- package/{node_modules/@context-vault/core/src/server → src}/tools/session-start.js +9 -16
- package/node_modules/@context-vault/core/src/capture/file-ops.js +0 -97
- package/node_modules/@context-vault/core/src/capture/import-pipeline.js +0 -46
- package/node_modules/@context-vault/core/src/capture/importers.js +0 -387
- package/node_modules/@context-vault/core/src/capture/index.js +0 -236
- package/node_modules/@context-vault/core/src/capture/ingest-url.js +0 -252
- package/node_modules/@context-vault/core/src/consolidation/index.js +0 -112
- package/node_modules/@context-vault/core/src/core/categories.js +0 -72
- package/node_modules/@context-vault/core/src/core/files.js +0 -108
- package/node_modules/@context-vault/core/src/core/status.js +0 -350
- package/node_modules/@context-vault/core/src/index/db.js +0 -416
- package/node_modules/@context-vault/core/src/index/index.js +0 -522
- package/node_modules/@context-vault/core/src/index.js +0 -66
- package/node_modules/@context-vault/core/src/retrieve/index.js +0 -500
- package/node_modules/@context-vault/core/src/server/tools/submit-feedback.js +0 -55
- package/node_modules/@context-vault/core/src/sync/sync.js +0 -235
- package/src/hooks/post-tool-call.mjs +0 -62
- package/src/hooks/session-end.mjs +0 -492
- /package/{node_modules/@context-vault/core/src/server → src}/tools/clear-context.js +0 -0
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import { reindex } from "
|
|
2
|
-
import { captureAndIndex } from "
|
|
1
|
+
import { reindex } from "@context-vault/core/index";
|
|
2
|
+
import { captureAndIndex } from "@context-vault/core/capture";
|
|
3
3
|
import { err } from "./helpers.js";
|
|
4
|
-
import { sendTelemetryEvent } from "
|
|
5
|
-
import
|
|
4
|
+
import { sendTelemetryEvent } from "./telemetry.js";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { join, dirname } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const pkg = JSON.parse(
|
|
11
|
+
readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8"),
|
|
12
|
+
);
|
|
6
13
|
|
|
7
14
|
import * as getContext from "./tools/get-context.js";
|
|
8
15
|
import * as saveContext from "./tools/save-context.js";
|
|
9
16
|
import * as listContext from "./tools/list-context.js";
|
|
10
17
|
import * as deleteContext from "./tools/delete-context.js";
|
|
11
|
-
import * as submitFeedback from "./tools/submit-feedback.js";
|
|
12
18
|
import * as ingestUrl from "./tools/ingest-url.js";
|
|
13
19
|
import * as contextStatus from "./tools/context-status.js";
|
|
14
20
|
import * as clearContext from "./tools/clear-context.js";
|
|
@@ -22,7 +28,6 @@ const toolModules = [
|
|
|
22
28
|
saveContext,
|
|
23
29
|
listContext,
|
|
24
30
|
deleteContext,
|
|
25
|
-
submitFeedback,
|
|
26
31
|
ingestUrl,
|
|
27
32
|
ingestProject,
|
|
28
33
|
contextStatus,
|
|
@@ -35,8 +40,6 @@ const toolModules = [
|
|
|
35
40
|
const TOOL_TIMEOUT_MS = 60_000;
|
|
36
41
|
|
|
37
42
|
export function registerTools(server, ctx) {
|
|
38
|
-
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
39
|
-
|
|
40
43
|
function tracked(handler, toolName) {
|
|
41
44
|
return async (...args) => {
|
|
42
45
|
if (ctx.activeOps) ctx.activeOps.count++;
|
|
@@ -57,8 +60,6 @@ export function registerTools(server, ctx) {
|
|
|
57
60
|
return result;
|
|
58
61
|
} catch (e) {
|
|
59
62
|
if (e.message === "TOOL_TIMEOUT") {
|
|
60
|
-
// Suppress any late rejection from the still-running handler to
|
|
61
|
-
// prevent unhandled promise rejection warnings in the host process.
|
|
62
63
|
handlerPromise?.catch(() => {});
|
|
63
64
|
if (ctx.toolStats) {
|
|
64
65
|
ctx.toolStats.errors++;
|
|
@@ -107,7 +108,7 @@ export function registerTools(server, ctx) {
|
|
|
107
108
|
auto: true,
|
|
108
109
|
},
|
|
109
110
|
});
|
|
110
|
-
} catch {}
|
|
111
|
+
} catch {}
|
|
111
112
|
throw e;
|
|
112
113
|
} finally {
|
|
113
114
|
clearTimeout(timer);
|
|
@@ -116,8 +117,7 @@ export function registerTools(server, ctx) {
|
|
|
116
117
|
};
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
let reindexDone = userId !== undefined ? true : false;
|
|
120
|
+
let reindexDone = false;
|
|
121
121
|
let reindexPromise = null;
|
|
122
122
|
let reindexAttempts = 0;
|
|
123
123
|
let reindexFailed = false;
|
|
@@ -126,7 +126,6 @@ export function registerTools(server, ctx) {
|
|
|
126
126
|
async function ensureIndexed() {
|
|
127
127
|
if (reindexDone) return;
|
|
128
128
|
if (reindexPromise) return reindexPromise;
|
|
129
|
-
// Assign promise synchronously to prevent concurrent calls from both entering reindex()
|
|
130
129
|
const promise = reindex(ctx, { fullSync: true })
|
|
131
130
|
.then((stats) => {
|
|
132
131
|
reindexDone = true;
|
|
@@ -149,7 +148,7 @@ export function registerTools(server, ctx) {
|
|
|
149
148
|
reindexDone = true;
|
|
150
149
|
reindexFailed = true;
|
|
151
150
|
} else {
|
|
152
|
-
reindexPromise = null;
|
|
151
|
+
reindexPromise = null;
|
|
153
152
|
}
|
|
154
153
|
});
|
|
155
154
|
reindexPromise = promise;
|
|
@@ -18,24 +18,19 @@ const pkg = JSON.parse(
|
|
|
18
18
|
readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8"),
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
-
import { resolveConfig } from "@context-vault/core/
|
|
22
|
-
import { appendErrorLog } from "
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
maybeShowTelemetryNotice,
|
|
26
|
-
} from "@context-vault/core/core/telemetry";
|
|
27
|
-
import { embed } from "@context-vault/core/index/embed";
|
|
21
|
+
import { resolveConfig } from "@context-vault/core/config";
|
|
22
|
+
import { appendErrorLog } from "./error-log.js";
|
|
23
|
+
import { sendTelemetryEvent, maybeShowTelemetryNotice } from "./telemetry.js";
|
|
24
|
+
import { embed } from "@context-vault/core/embed";
|
|
28
25
|
import {
|
|
29
26
|
initDatabase,
|
|
30
27
|
NativeModuleError,
|
|
31
28
|
prepareStatements,
|
|
32
29
|
insertVec,
|
|
33
30
|
deleteVec,
|
|
34
|
-
} from "@context-vault/core/
|
|
35
|
-
import { registerTools } from "
|
|
36
|
-
import { pruneExpired } from "@context-vault/core/index
|
|
37
|
-
|
|
38
|
-
// ─── Phased Startup ─────────────────────────────────────────────────────────
|
|
31
|
+
} from "@context-vault/core/db";
|
|
32
|
+
import { registerTools } from "./register-tools.js";
|
|
33
|
+
import { pruneExpired } from "@context-vault/core/index";
|
|
39
34
|
|
|
40
35
|
async function main() {
|
|
41
36
|
let phase = "CONFIG";
|
|
@@ -43,16 +38,13 @@ async function main() {
|
|
|
43
38
|
let config;
|
|
44
39
|
|
|
45
40
|
try {
|
|
46
|
-
// ── Phase: CONFIG ────────────────────────────────────────────────────────
|
|
47
41
|
config = resolveConfig();
|
|
48
42
|
|
|
49
|
-
// ── Phase: DIRS ──────────────────────────────────────────────────────────
|
|
50
43
|
phase = "DIRS";
|
|
51
44
|
mkdirSync(config.dataDir, { recursive: true });
|
|
52
45
|
mkdirSync(config.vaultDir, { recursive: true });
|
|
53
46
|
maybeShowTelemetryNotice(config.dataDir);
|
|
54
47
|
|
|
55
|
-
// Verify vault directory is writable (catch permission issues early)
|
|
56
48
|
try {
|
|
57
49
|
const probe = join(config.vaultDir, ".write-probe");
|
|
58
50
|
writeFileSync(probe, "");
|
|
@@ -68,7 +60,6 @@ async function main() {
|
|
|
68
60
|
process.exit(1);
|
|
69
61
|
}
|
|
70
62
|
|
|
71
|
-
// Write .context-mcp marker (non-fatal)
|
|
72
63
|
try {
|
|
73
64
|
const markerPath = join(config.vaultDir, ".context-mcp");
|
|
74
65
|
const markerData = existsSync(markerPath)
|
|
@@ -93,7 +84,6 @@ async function main() {
|
|
|
93
84
|
|
|
94
85
|
config.vaultDirExists = existsSync(config.vaultDir);
|
|
95
86
|
|
|
96
|
-
// Startup diagnostics
|
|
97
87
|
console.error(`[context-vault] Vault: ${config.vaultDir}`);
|
|
98
88
|
console.error(`[context-vault] Database: ${config.dbPath}`);
|
|
99
89
|
console.error(`[context-vault] Dev dir: ${config.devDir}`);
|
|
@@ -101,7 +91,6 @@ async function main() {
|
|
|
101
91
|
console.error(`[context-vault] WARNING: Vault directory not found!`);
|
|
102
92
|
}
|
|
103
93
|
|
|
104
|
-
// ── Phase: DB ────────────────────────────────────────────────────────────
|
|
105
94
|
phase = "DB";
|
|
106
95
|
db = await initDatabase(config.dbPath);
|
|
107
96
|
const stmts = prepareStatements(db);
|
|
@@ -117,7 +106,6 @@ async function main() {
|
|
|
117
106
|
toolStats: { ok: 0, errors: 0, lastError: null },
|
|
118
107
|
};
|
|
119
108
|
|
|
120
|
-
// ── Phase: PRUNE ─────────────────────────────────────────────────────────
|
|
121
109
|
try {
|
|
122
110
|
const pruned = await pruneExpired(ctx);
|
|
123
111
|
if (pruned > 0) {
|
|
@@ -131,16 +119,12 @@ async function main() {
|
|
|
131
119
|
);
|
|
132
120
|
}
|
|
133
121
|
|
|
134
|
-
// ── Phase: SERVER ────────────────────────────────────────────────────────
|
|
135
122
|
phase = "SERVER";
|
|
136
123
|
const server = new McpServer(
|
|
137
124
|
{ name: "context-vault", version: pkg.version },
|
|
138
125
|
{ capabilities: { tools: {} } },
|
|
139
126
|
);
|
|
140
127
|
|
|
141
|
-
// Hot-reload config.json on every tool call (Option C from #144).
|
|
142
|
-
// resolveConfig() re-reads the small file each time — negligible I/O
|
|
143
|
-
// compared to DB queries and embedding operations that follow.
|
|
144
128
|
let lastVaultDir = config.vaultDir;
|
|
145
129
|
Object.defineProperty(ctx, "config", {
|
|
146
130
|
get() {
|
|
@@ -159,7 +143,6 @@ async function main() {
|
|
|
159
143
|
|
|
160
144
|
registerTools(server, ctx);
|
|
161
145
|
|
|
162
|
-
// ── Graceful Shutdown ────────────────────────────────────────────────────
|
|
163
146
|
function closeDb() {
|
|
164
147
|
try {
|
|
165
148
|
if (db.inTransaction) {
|
|
@@ -188,7 +171,6 @@ async function main() {
|
|
|
188
171
|
closeDb();
|
|
189
172
|
}
|
|
190
173
|
}, 100);
|
|
191
|
-
// Force shutdown after 5 seconds even if ops are still running
|
|
192
174
|
setTimeout(() => {
|
|
193
175
|
clearInterval(check);
|
|
194
176
|
console.error(
|
|
@@ -203,12 +185,10 @@ async function main() {
|
|
|
203
185
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
204
186
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
205
187
|
|
|
206
|
-
// ── Phase: CONNECTED ─────────────────────────────────────────────────────
|
|
207
188
|
phase = "CONNECTED";
|
|
208
189
|
const transport = new StdioServerTransport();
|
|
209
190
|
await server.connect(transport);
|
|
210
191
|
|
|
211
|
-
// ── Non-blocking Update Check ────────────────────────────────────────────
|
|
212
192
|
setTimeout(() => {
|
|
213
193
|
import("node:child_process")
|
|
214
194
|
.then(({ execSync }) => {
|
|
@@ -250,7 +230,6 @@ async function main() {
|
|
|
250
230
|
});
|
|
251
231
|
|
|
252
232
|
if (err instanceof NativeModuleError) {
|
|
253
|
-
// Boxed diagnostic for native module mismatch
|
|
254
233
|
console.error("");
|
|
255
234
|
console.error(
|
|
256
235
|
"╔══════════════════════════════════════════════════════════════╗",
|
|
@@ -268,7 +247,7 @@ async function main() {
|
|
|
268
247
|
console.error(` Node.js version: ${process.version}`);
|
|
269
248
|
console.error(` Error log: ${join(dataDir, "error.log")}`);
|
|
270
249
|
console.error("");
|
|
271
|
-
process.exit(78);
|
|
250
|
+
process.exit(78);
|
|
272
251
|
}
|
|
273
252
|
|
|
274
253
|
console.error(
|
|
@@ -284,12 +263,6 @@ async function main() {
|
|
|
284
263
|
}
|
|
285
264
|
}
|
|
286
265
|
|
|
287
|
-
// ─── Top-level Safety Net ────────────────────────────────────────────────────
|
|
288
|
-
// Catch any errors that escape the main() try/catch (e.g. thrown in MCP
|
|
289
|
-
// transport callbacks or in unrelated async chains). Claude Code surfaces
|
|
290
|
-
// stderr when a server exits unexpectedly, so every message written here will
|
|
291
|
-
// be visible to the user.
|
|
292
|
-
|
|
293
266
|
process.on("uncaughtException", (err) => {
|
|
294
267
|
const dataDir = join(homedir(), ".context-mcp");
|
|
295
268
|
const logEntry = {
|
package/src/status.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
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
|
+
|
|
7
|
+
function countArchivedEntries(vaultDir) {
|
|
8
|
+
const archRoot = join(vaultDir, "_archive");
|
|
9
|
+
if (!existsSync(archRoot)) return 0;
|
|
10
|
+
try {
|
|
11
|
+
return walkDir(archRoot).length;
|
|
12
|
+
} catch {
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function gatherVaultStatus(ctx, opts = {}) {
|
|
18
|
+
const { db, config } = ctx;
|
|
19
|
+
const errors = [];
|
|
20
|
+
|
|
21
|
+
let fileCount = 0;
|
|
22
|
+
const subdirs = [];
|
|
23
|
+
try {
|
|
24
|
+
if (existsSync(config.vaultDir)) {
|
|
25
|
+
for (const d of readdirSync(config.vaultDir, { withFileTypes: true })) {
|
|
26
|
+
if (d.isDirectory()) {
|
|
27
|
+
const dir = join(config.vaultDir, d.name);
|
|
28
|
+
const count = walkDir(dir).length;
|
|
29
|
+
fileCount += count;
|
|
30
|
+
if (count > 0) subdirs.push({ name: d.name, count });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (e) {
|
|
35
|
+
errors.push(`File scan failed: ${e.message}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let kindCounts = [];
|
|
39
|
+
try {
|
|
40
|
+
kindCounts = db
|
|
41
|
+
.prepare(`SELECT kind, COUNT(*) as c FROM vault GROUP BY kind`)
|
|
42
|
+
.all();
|
|
43
|
+
} catch (e) {
|
|
44
|
+
errors.push(`Kind count query failed: ${e.message}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let categoryCounts = [];
|
|
48
|
+
try {
|
|
49
|
+
categoryCounts = db
|
|
50
|
+
.prepare(`SELECT category, COUNT(*) as c FROM vault GROUP BY category`)
|
|
51
|
+
.all();
|
|
52
|
+
} catch (e) {
|
|
53
|
+
errors.push(`Category count query failed: ${e.message}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let dbSize = "n/a";
|
|
57
|
+
let dbSizeBytes = 0;
|
|
58
|
+
try {
|
|
59
|
+
if (existsSync(config.dbPath)) {
|
|
60
|
+
dbSizeBytes = statSync(config.dbPath).size;
|
|
61
|
+
dbSize =
|
|
62
|
+
dbSizeBytes > 1024 * 1024
|
|
63
|
+
? `${(dbSizeBytes / 1024 / 1024).toFixed(1)}MB`
|
|
64
|
+
: `${(dbSizeBytes / 1024).toFixed(1)}KB`;
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
errors.push(`DB size check failed: ${e.message}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let stalePaths = false;
|
|
71
|
+
let staleCount = 0;
|
|
72
|
+
try {
|
|
73
|
+
const result = db
|
|
74
|
+
.prepare(`SELECT COUNT(*) as c FROM vault WHERE file_path NOT LIKE ? || '%'`)
|
|
75
|
+
.get(config.vaultDir);
|
|
76
|
+
staleCount = result.c;
|
|
77
|
+
stalePaths = staleCount > 0;
|
|
78
|
+
} catch (e) {
|
|
79
|
+
errors.push(`Stale path check failed: ${e.message}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let expiredCount = 0;
|
|
83
|
+
try {
|
|
84
|
+
expiredCount = db
|
|
85
|
+
.prepare(`SELECT COUNT(*) as c FROM vault WHERE expires_at IS NOT NULL AND expires_at <= datetime('now')`)
|
|
86
|
+
.get().c;
|
|
87
|
+
} catch (e) {
|
|
88
|
+
errors.push(`Expired count failed: ${e.message}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let eventCount = 0;
|
|
92
|
+
try {
|
|
93
|
+
eventCount = db
|
|
94
|
+
.prepare(`SELECT COUNT(*) as c FROM vault WHERE category = 'event'`)
|
|
95
|
+
.get().c;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
errors.push(`Event count failed: ${e.message}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let eventsWithoutTtlCount = 0;
|
|
101
|
+
try {
|
|
102
|
+
eventsWithoutTtlCount = db
|
|
103
|
+
.prepare(`SELECT COUNT(*) as c FROM vault WHERE category = 'event' AND expires_at IS NULL`)
|
|
104
|
+
.get().c;
|
|
105
|
+
} catch (e) {
|
|
106
|
+
errors.push(`Events without TTL count failed: ${e.message}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let embeddingStatus = null;
|
|
110
|
+
try {
|
|
111
|
+
const total = db.prepare(`SELECT COUNT(*) as c FROM vault`).get().c;
|
|
112
|
+
const indexed = db
|
|
113
|
+
.prepare(`SELECT COUNT(*) as c FROM vault WHERE rowid IN (SELECT rowid FROM vault_vec)`)
|
|
114
|
+
.get().c;
|
|
115
|
+
embeddingStatus = { indexed, total, missing: total - indexed };
|
|
116
|
+
} catch (e) {
|
|
117
|
+
errors.push(`Embedding status check failed: ${e.message}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const embedModelAvailable = isEmbedAvailable();
|
|
121
|
+
|
|
122
|
+
let autoCapturedFeedbackCount = 0;
|
|
123
|
+
try {
|
|
124
|
+
autoCapturedFeedbackCount = db
|
|
125
|
+
.prepare(`SELECT COUNT(*) as c FROM vault WHERE kind = 'feedback' AND tags LIKE '%"auto-captured"%'`)
|
|
126
|
+
.get().c;
|
|
127
|
+
} catch (e) {
|
|
128
|
+
errors.push(`Auto-captured feedback count failed: ${e.message}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let archivedCount = 0;
|
|
132
|
+
try {
|
|
133
|
+
archivedCount = countArchivedEntries(config.vaultDir);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
errors.push(`Archived count failed: ${e.message}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let staleKnowledge = [];
|
|
139
|
+
try {
|
|
140
|
+
const stalenessKinds = Object.entries(KIND_STALENESS_DAYS);
|
|
141
|
+
if (stalenessKinds.length > 0) {
|
|
142
|
+
const kindClauses = stalenessKinds
|
|
143
|
+
.map(
|
|
144
|
+
([kind, days]) =>
|
|
145
|
+
`(kind = '${kind}' AND COALESCE(updated_at, created_at) <= datetime('now', '-${days} days'))`,
|
|
146
|
+
)
|
|
147
|
+
.join(" OR ");
|
|
148
|
+
staleKnowledge = db
|
|
149
|
+
.prepare(
|
|
150
|
+
`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`,
|
|
151
|
+
)
|
|
152
|
+
.all();
|
|
153
|
+
}
|
|
154
|
+
} catch (e) {
|
|
155
|
+
errors.push(`Stale knowledge check failed: ${e.message}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
fileCount,
|
|
160
|
+
subdirs,
|
|
161
|
+
kindCounts,
|
|
162
|
+
categoryCounts,
|
|
163
|
+
dbSize,
|
|
164
|
+
dbSizeBytes,
|
|
165
|
+
stalePaths,
|
|
166
|
+
staleCount,
|
|
167
|
+
expiredCount,
|
|
168
|
+
eventCount,
|
|
169
|
+
eventsWithoutTtlCount,
|
|
170
|
+
embeddingStatus,
|
|
171
|
+
embedModelAvailable,
|
|
172
|
+
autoCapturedFeedbackCount,
|
|
173
|
+
archivedCount,
|
|
174
|
+
staleKnowledge,
|
|
175
|
+
resolvedFrom: config.resolvedFrom,
|
|
176
|
+
errors,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function computeGrowthWarnings(status, thresholds) {
|
|
181
|
+
if (!thresholds)
|
|
182
|
+
return { warnings: [], hasCritical: false, hasWarnings: false, actions: [], kindBreakdown: [] };
|
|
183
|
+
|
|
184
|
+
const t = thresholds;
|
|
185
|
+
const warnings = [];
|
|
186
|
+
const actions = [];
|
|
187
|
+
|
|
188
|
+
const total = status.embeddingStatus?.total ?? 0;
|
|
189
|
+
const { eventCount = 0, eventsWithoutTtlCount = 0, expiredCount = 0, dbSizeBytes = 0 } = status;
|
|
190
|
+
|
|
191
|
+
let totalExceeded = false;
|
|
192
|
+
|
|
193
|
+
if (t.totalEntries?.critical != null && total >= t.totalEntries.critical) {
|
|
194
|
+
totalExceeded = true;
|
|
195
|
+
warnings.push({ level: "critical", message: `Total entries: ${total.toLocaleString()} (exceeds critical limit of ${t.totalEntries.critical.toLocaleString()})` });
|
|
196
|
+
} else if (t.totalEntries?.warn != null && total >= t.totalEntries.warn) {
|
|
197
|
+
totalExceeded = true;
|
|
198
|
+
warnings.push({ level: "warn", message: `Total entries: ${total.toLocaleString()} (exceeds recommended ${t.totalEntries.warn.toLocaleString()})` });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (t.eventEntries?.critical != null && eventCount >= t.eventEntries.critical) {
|
|
202
|
+
warnings.push({ level: "critical", message: `Event entries: ${eventCount.toLocaleString()} (exceeds critical limit of ${t.eventEntries.critical.toLocaleString()})` });
|
|
203
|
+
} else if (t.eventEntries?.warn != null && eventCount >= t.eventEntries.warn) {
|
|
204
|
+
const ttlNote = eventsWithoutTtlCount > 0 ? ` (${eventsWithoutTtlCount.toLocaleString()} without TTL)` : "";
|
|
205
|
+
warnings.push({ level: "warn", message: `Event entries: ${eventCount.toLocaleString()}${ttlNote} (exceeds recommended ${t.eventEntries.warn.toLocaleString()})` });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (t.vaultSizeBytes?.critical != null && dbSizeBytes >= t.vaultSizeBytes.critical) {
|
|
209
|
+
warnings.push({ level: "critical", message: `Database size: ${(dbSizeBytes / 1024 / 1024).toFixed(1)}MB (exceeds critical limit of ${(t.vaultSizeBytes.critical / 1024 / 1024).toFixed(0)}MB)` });
|
|
210
|
+
} else if (t.vaultSizeBytes?.warn != null && dbSizeBytes >= t.vaultSizeBytes.warn) {
|
|
211
|
+
warnings.push({ level: "warn", message: `Database size: ${(dbSizeBytes / 1024 / 1024).toFixed(1)}MB (exceeds recommended ${(t.vaultSizeBytes.warn / 1024 / 1024).toFixed(0)}MB)` });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (t.eventsWithoutTtl?.warn != null && eventsWithoutTtlCount >= t.eventsWithoutTtl.warn) {
|
|
215
|
+
warnings.push({ level: "warn", message: `Event entries without expires_at: ${eventsWithoutTtlCount.toLocaleString()} (exceeds recommended ${t.eventsWithoutTtl.warn.toLocaleString()})` });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const hasCritical = warnings.some((w) => w.level === "critical");
|
|
219
|
+
|
|
220
|
+
if (expiredCount > 0) {
|
|
221
|
+
actions.push(`Run \`context-vault prune\` to remove ${expiredCount} expired event entr${expiredCount === 1 ? "y" : "ies"}`);
|
|
222
|
+
}
|
|
223
|
+
if (eventsWithoutTtlCount > 0 && (eventCount >= (t.eventEntries?.warn ?? Infinity) || eventsWithoutTtlCount >= (t.eventsWithoutTtl?.warn ?? Infinity))) {
|
|
224
|
+
actions.push("Add `expires_at` to event/session entries to enable automatic cleanup");
|
|
225
|
+
}
|
|
226
|
+
if (total >= (t.totalEntries?.warn ?? Infinity)) {
|
|
227
|
+
actions.push("Run `context-vault archive` to move old ephemeral/event entries to _archive/");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const kindBreakdown = totalExceeded && status.kindCounts?.length
|
|
231
|
+
? [...status.kindCounts].sort((a, b) => b.c - a.c).map(({ kind, c }) => ({ kind, count: c, pct: Math.round((c / total) * 100) }))
|
|
232
|
+
: [];
|
|
233
|
+
|
|
234
|
+
return { warnings, hasCritical, hasWarnings: warnings.length > 0, actions, kindBreakdown };
|
|
235
|
+
}
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import { existsSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { API_URL, MARKETING_URL, GITHUB_ISSUES_URL } from "
|
|
3
|
+
import { API_URL, MARKETING_URL, GITHUB_ISSUES_URL } from "@context-vault/core/constants";
|
|
4
|
+
import type { VaultConfig } from "@context-vault/core/types";
|
|
4
5
|
|
|
5
6
|
const TELEMETRY_ENDPOINT = `${API_URL}/telemetry`;
|
|
6
7
|
const NOTICE_MARKER = ".telemetry-notice-shown";
|
|
7
8
|
const FEEDBACK_PROMPT_MARKER = ".feedback-prompt-shown";
|
|
8
9
|
|
|
9
|
-
export function isTelemetryEnabled(config) {
|
|
10
|
+
export function isTelemetryEnabled(config: VaultConfig | undefined): boolean {
|
|
10
11
|
const envVal = process.env.CONTEXT_VAULT_TELEMETRY;
|
|
11
12
|
if (envVal !== undefined) return envVal === "1" || envVal === "true";
|
|
12
13
|
return config?.telemetry === true;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
export function sendTelemetryEvent(config, payload) {
|
|
16
|
+
export function sendTelemetryEvent(
|
|
17
|
+
config: VaultConfig | undefined,
|
|
18
|
+
payload: { event: string; code?: string | null; tool?: string | null; cv_version: string },
|
|
19
|
+
): void {
|
|
21
20
|
if (!isTelemetryEnabled(config)) return;
|
|
22
21
|
|
|
23
22
|
const event = {
|
|
@@ -39,11 +38,7 @@ export function sendTelemetryEvent(config, payload) {
|
|
|
39
38
|
}).catch(() => {});
|
|
40
39
|
}
|
|
41
40
|
|
|
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) {
|
|
41
|
+
export function maybeShowTelemetryNotice(dataDir: string): void {
|
|
47
42
|
try {
|
|
48
43
|
const markerPath = join(dataDir, NOTICE_MARKER);
|
|
49
44
|
if (existsSync(markerPath)) return;
|
|
@@ -65,12 +60,7 @@ export function maybeShowTelemetryNotice(dataDir) {
|
|
|
65
60
|
}
|
|
66
61
|
}
|
|
67
62
|
|
|
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) {
|
|
63
|
+
export function maybeShowFeedbackPrompt(dataDir: string): void {
|
|
74
64
|
try {
|
|
75
65
|
const markerPath = join(dataDir, FEEDBACK_PROMPT_MARKER);
|
|
76
66
|
if (existsSync(markerPath)) return;
|
package/src/temporal.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const SHORTCUT_RE = /^last[_ ](\d+)[_ ](day|days|week|weeks|month|months)$/i;
|
|
2
|
+
|
|
3
|
+
function startOfToday(now: Date): Date {
|
|
4
|
+
const d = new Date(now);
|
|
5
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
6
|
+
return d;
|
|
7
|
+
}
|
|
8
|
+
|
|
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, "_");
|
|
16
|
+
|
|
17
|
+
if (trimmed === "today") {
|
|
18
|
+
const start = startOfToday(now);
|
|
19
|
+
if (role === "until") {
|
|
20
|
+
const end = new Date(start);
|
|
21
|
+
end.setUTCDate(end.getUTCDate() + 1);
|
|
22
|
+
return end.toISOString();
|
|
23
|
+
}
|
|
24
|
+
return start.toISOString();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (trimmed === "yesterday") {
|
|
28
|
+
const todayStart = startOfToday(now);
|
|
29
|
+
const yesterdayStart = new Date(todayStart);
|
|
30
|
+
yesterdayStart.setUTCDate(yesterdayStart.getUTCDate() - 1);
|
|
31
|
+
if (role === "since") return yesterdayStart.toISOString();
|
|
32
|
+
return todayStart.toISOString();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (trimmed === "this_week") {
|
|
36
|
+
const todayStart = startOfToday(now);
|
|
37
|
+
const dayOfWeek = todayStart.getUTCDay();
|
|
38
|
+
const daysFromMonday = (dayOfWeek + 6) % 7;
|
|
39
|
+
const monday = new Date(todayStart);
|
|
40
|
+
monday.setUTCDate(monday.getUTCDate() - daysFromMonday);
|
|
41
|
+
if (role === "since") return monday.toISOString();
|
|
42
|
+
const endOfToday = new Date(todayStart);
|
|
43
|
+
endOfToday.setUTCDate(endOfToday.getUTCDate() + 1);
|
|
44
|
+
return endOfToday.toISOString();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (trimmed === "this_month") {
|
|
48
|
+
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
|
|
49
|
+
if (role === "since") return d.toISOString();
|
|
50
|
+
const endOfMonth = new Date(
|
|
51
|
+
Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1),
|
|
52
|
+
);
|
|
53
|
+
return endOfMonth.toISOString();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const m = SHORTCUT_RE.exec(trimmed);
|
|
57
|
+
if (m) {
|
|
58
|
+
const n = parseInt(m[1], 10);
|
|
59
|
+
const unit = m[2].replace(/s$/, "");
|
|
60
|
+
let ms: number;
|
|
61
|
+
if (unit === "day") ms = n * 86400000;
|
|
62
|
+
else if (unit === "week") ms = n * 7 * 86400000;
|
|
63
|
+
else ms = n * 30 * 86400000;
|
|
64
|
+
const target = new Date(now.getTime() - ms);
|
|
65
|
+
target.setUTCHours(0, 0, 0, 0);
|
|
66
|
+
return target.toISOString();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resolveTemporalParams(
|
|
73
|
+
params: { since?: string; until?: string },
|
|
74
|
+
now: Date = new Date(),
|
|
75
|
+
): { since: string | undefined; until: string | undefined } {
|
|
76
|
+
let { since, until } = params;
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
since?.trim().toLowerCase() === "yesterday" &&
|
|
80
|
+
(until === undefined || until === null)
|
|
81
|
+
) {
|
|
82
|
+
since = resolveTemporalShortcut("since", since, now);
|
|
83
|
+
until = resolveTemporalShortcut("until", "yesterday", now);
|
|
84
|
+
return { since, until };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
since:
|
|
89
|
+
since !== undefined
|
|
90
|
+
? resolveTemporalShortcut("since", since, now)
|
|
91
|
+
: since,
|
|
92
|
+
until:
|
|
93
|
+
until !== undefined
|
|
94
|
+
? resolveTemporalShortcut("until", until, now)
|
|
95
|
+
: until,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { gatherVaultStatus, computeGrowthWarnings } from "
|
|
2
|
-
import { errorLogPath, errorLogCount } from "
|
|
1
|
+
import { gatherVaultStatus, computeGrowthWarnings } from "../status.js";
|
|
2
|
+
import { errorLogPath, errorLogCount } from "../error-log.js";
|
|
3
3
|
import { ok } from "../helpers.js";
|
|
4
4
|
|
|
5
5
|
function relativeTime(ts) {
|
|
@@ -24,9 +24,8 @@ export const inputSchema = {};
|
|
|
24
24
|
*/
|
|
25
25
|
export function handler(_args, ctx) {
|
|
26
26
|
const { config } = ctx;
|
|
27
|
-
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
28
27
|
|
|
29
|
-
const status = gatherVaultStatus(ctx
|
|
28
|
+
const status = gatherVaultStatus(ctx);
|
|
30
29
|
|
|
31
30
|
const hasIssues = status.stalePaths || status.embeddingStatus?.missing > 0;
|
|
32
31
|
const healthIcon = hasIssues ? "⚠" : "✓";
|