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,45 +1,44 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { execSync } from
|
|
3
|
-
import { ok, err, ensureVaultExists } from
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { ok, err, ensureVaultExists } from '../helpers.js';
|
|
4
|
+
import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
|
|
4
5
|
|
|
5
6
|
const DEFAULT_MAX_TOKENS = 4000;
|
|
6
7
|
const RECENT_DAYS = 7;
|
|
7
8
|
const MAX_BODY_PER_ENTRY = 400;
|
|
8
|
-
const PRIORITY_KINDS = [
|
|
9
|
-
const SESSION_SUMMARY_KIND =
|
|
9
|
+
const PRIORITY_KINDS = ['decision', 'insight', 'pattern'];
|
|
10
|
+
const SESSION_SUMMARY_KIND = 'session';
|
|
10
11
|
|
|
11
|
-
export const name =
|
|
12
|
+
export const name = 'session_start';
|
|
12
13
|
|
|
13
14
|
export const description =
|
|
14
|
-
|
|
15
|
+
'Auto-assemble a context brief for the current project on session start. Pulls recent entries, last session summary, and active decisions/blockers into a token-budgeted capsule formatted for agent consumption.';
|
|
15
16
|
|
|
16
17
|
export const inputSchema = {
|
|
17
18
|
project: z
|
|
18
19
|
.string()
|
|
19
20
|
.optional()
|
|
20
21
|
.describe(
|
|
21
|
-
|
|
22
|
+
'Project name or tag to scope the brief. Auto-detected from cwd/git remote if not provided.'
|
|
22
23
|
),
|
|
23
24
|
max_tokens: z
|
|
24
25
|
.number()
|
|
25
26
|
.optional()
|
|
26
|
-
.describe(
|
|
27
|
-
"Token budget for the capsule (rough estimate: 1 token ~ 4 chars). Default: 4000.",
|
|
28
|
-
),
|
|
27
|
+
.describe('Token budget for the capsule (rough estimate: 1 token ~ 4 chars). Default: 4000.'),
|
|
29
28
|
buckets: z
|
|
30
29
|
.array(z.string())
|
|
31
30
|
.optional()
|
|
32
31
|
.describe(
|
|
33
|
-
"Bucket names to scope the session brief. Each name expands to a 'bucket:<name>' tag filter. When provided, the brief only includes entries from these buckets."
|
|
32
|
+
"Bucket names to scope the session brief. Each name expands to a 'bucket:<name>' tag filter. When provided, the brief only includes entries from these buckets."
|
|
34
33
|
),
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
function detectProject() {
|
|
38
37
|
try {
|
|
39
|
-
const remote = execSync(
|
|
40
|
-
encoding:
|
|
38
|
+
const remote = execSync('git remote get-url origin 2>/dev/null', {
|
|
39
|
+
encoding: 'utf-8',
|
|
41
40
|
timeout: 3000,
|
|
42
|
-
stdio: [
|
|
41
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
43
42
|
}).trim();
|
|
44
43
|
if (remote) {
|
|
45
44
|
const match = remote.match(/\/([^/]+?)(?:\.git)?$/);
|
|
@@ -56,32 +55,32 @@ function detectProject() {
|
|
|
56
55
|
return null;
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
function truncateBody(body, maxLen = MAX_BODY_PER_ENTRY) {
|
|
60
|
-
if (!body) return
|
|
58
|
+
function truncateBody(body: string | null | undefined, maxLen = MAX_BODY_PER_ENTRY): string {
|
|
59
|
+
if (!body) return '(no body)';
|
|
61
60
|
if (body.length <= maxLen) return body;
|
|
62
|
-
return body.slice(0, maxLen) +
|
|
61
|
+
return body.slice(0, maxLen) + '...';
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
function estimateTokens(text) {
|
|
66
|
-
return Math.ceil((text ||
|
|
64
|
+
function estimateTokens(text: string | null | undefined): number {
|
|
65
|
+
return Math.ceil((text || '').length / 4);
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
function formatEntry(entry) {
|
|
68
|
+
function formatEntry(entry: any): string {
|
|
70
69
|
const tags = entry.tags ? JSON.parse(entry.tags) : [];
|
|
71
|
-
const tagStr = tags.length ? tags.join(
|
|
72
|
-
const date = entry.updated_at || entry.created_at ||
|
|
70
|
+
const tagStr = tags.length ? tags.join(', ') : 'none';
|
|
71
|
+
const date = entry.updated_at || entry.created_at || 'unknown';
|
|
73
72
|
return [
|
|
74
|
-
`- **${entry.title ||
|
|
73
|
+
`- **${entry.title || '(untitled)'}** [${entry.kind}]`,
|
|
75
74
|
` tags: ${tagStr} | ${date} | id: \`${entry.id}\``,
|
|
76
|
-
` ${truncateBody(entry.body).replace(/\n+/g,
|
|
77
|
-
].join(
|
|
75
|
+
` ${truncateBody(entry.body).replace(/\n+/g, ' ').trim()}`,
|
|
76
|
+
].join('\n');
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
export async function handler(
|
|
81
|
-
{ project, max_tokens, buckets },
|
|
82
|
-
ctx,
|
|
83
|
-
{ ensureIndexed }
|
|
84
|
-
) {
|
|
80
|
+
{ project, max_tokens, buckets }: Record<string, any>,
|
|
81
|
+
ctx: LocalCtx,
|
|
82
|
+
{ ensureIndexed }: SharedCtx
|
|
83
|
+
): Promise<ToolResult> {
|
|
85
84
|
const { config } = ctx;
|
|
86
85
|
|
|
87
86
|
const vaultErr = ensureVaultExists(config);
|
|
@@ -92,36 +91,26 @@ export async function handler(
|
|
|
92
91
|
const effectiveProject = project?.trim() || detectProject();
|
|
93
92
|
const tokenBudget = max_tokens || DEFAULT_MAX_TOKENS;
|
|
94
93
|
|
|
95
|
-
const bucketTags = buckets?.length ? buckets.map((b) => `bucket:${b}`) : [];
|
|
96
|
-
const effectiveTags = bucketTags.length
|
|
97
|
-
? bucketTags
|
|
98
|
-
: effectiveProject
|
|
99
|
-
? [effectiveProject]
|
|
100
|
-
: [];
|
|
94
|
+
const bucketTags = buckets?.length ? buckets.map((b: string) => `bucket:${b}`) : [];
|
|
95
|
+
const effectiveTags = bucketTags.length ? bucketTags : effectiveProject ? [effectiveProject] : [];
|
|
101
96
|
|
|
102
97
|
const sinceDate = new Date(Date.now() - RECENT_DAYS * 86400000).toISOString();
|
|
103
98
|
|
|
104
99
|
const sections = [];
|
|
105
100
|
let tokensUsed = 0;
|
|
106
101
|
|
|
102
|
+
sections.push(`# Session Brief${effectiveProject ? ` — ${effectiveProject}` : ''}`);
|
|
103
|
+
const bucketsLabel = buckets?.length ? ` | buckets: ${buckets.join(', ')}` : '';
|
|
107
104
|
sections.push(
|
|
108
|
-
|
|
109
|
-
);
|
|
110
|
-
const bucketsLabel = buckets?.length
|
|
111
|
-
? ` | buckets: ${buckets.join(", ")}`
|
|
112
|
-
: "";
|
|
113
|
-
sections.push(
|
|
114
|
-
`_Generated ${new Date().toISOString().slice(0, 10)} | budget: ${tokenBudget} tokens${bucketsLabel}_\n`,
|
|
105
|
+
`_Generated ${new Date().toISOString().slice(0, 10)} | budget: ${tokenBudget} tokens${bucketsLabel}_\n`
|
|
115
106
|
);
|
|
116
|
-
tokensUsed += estimateTokens(sections.join(
|
|
107
|
+
tokensUsed += estimateTokens(sections.join('\n'));
|
|
117
108
|
|
|
118
109
|
const lastSession = queryLastSession(ctx, effectiveTags);
|
|
119
110
|
if (lastSession) {
|
|
120
|
-
const sessionBlock = [
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"",
|
|
124
|
-
].join("\n");
|
|
111
|
+
const sessionBlock = ['## Last Session Summary', truncateBody(lastSession.body, 600), ''].join(
|
|
112
|
+
'\n'
|
|
113
|
+
);
|
|
125
114
|
const sessionTokens = estimateTokens(sessionBlock);
|
|
126
115
|
if (tokensUsed + sessionTokens <= tokenBudget) {
|
|
127
116
|
sections.push(sessionBlock);
|
|
@@ -134,10 +123,10 @@ export async function handler(
|
|
|
134
123
|
PRIORITY_KINDS,
|
|
135
124
|
sinceDate,
|
|
136
125
|
|
|
137
|
-
effectiveTags
|
|
126
|
+
effectiveTags
|
|
138
127
|
);
|
|
139
128
|
if (decisions.length > 0) {
|
|
140
|
-
const header =
|
|
129
|
+
const header = '## Active Decisions, Insights & Patterns\n';
|
|
141
130
|
const headerTokens = estimateTokens(header);
|
|
142
131
|
if (tokensUsed + headerTokens <= tokenBudget) {
|
|
143
132
|
const entryLines = [];
|
|
@@ -150,15 +139,15 @@ export async function handler(
|
|
|
150
139
|
tokensUsed += lineTokens;
|
|
151
140
|
}
|
|
152
141
|
if (entryLines.length > 0) {
|
|
153
|
-
sections.push(header + entryLines.join(
|
|
142
|
+
sections.push(header + entryLines.join('\n') + '\n');
|
|
154
143
|
}
|
|
155
144
|
}
|
|
156
145
|
}
|
|
157
146
|
|
|
158
147
|
const recent = queryRecent(ctx, sinceDate, effectiveTags);
|
|
159
|
-
const seenIds = new Set(decisions.map((d) => d.id));
|
|
148
|
+
const seenIds = new Set(decisions.map((d: any) => d.id));
|
|
160
149
|
if (lastSession) seenIds.add(lastSession.id);
|
|
161
|
-
const deduped = recent.filter((r) => !seenIds.has(r.id));
|
|
150
|
+
const deduped = recent.filter((r: any) => !seenIds.has(r.id));
|
|
162
151
|
|
|
163
152
|
if (deduped.length > 0) {
|
|
164
153
|
const header = `## Recent Entries (last ${RECENT_DAYS} days)\n`;
|
|
@@ -174,7 +163,7 @@ export async function handler(
|
|
|
174
163
|
tokensUsed += lineTokens;
|
|
175
164
|
}
|
|
176
165
|
if (entryLines.length > 0) {
|
|
177
|
-
sections.push(header + entryLines.join(
|
|
166
|
+
sections.push(header + entryLines.join('\n') + '\n');
|
|
178
167
|
}
|
|
179
168
|
}
|
|
180
169
|
}
|
|
@@ -182,17 +171,16 @@ export async function handler(
|
|
|
182
171
|
const totalEntries =
|
|
183
172
|
(lastSession ? 1 : 0) +
|
|
184
173
|
decisions.length +
|
|
185
|
-
deduped.filter((
|
|
186
|
-
const line = formatEntry(d);
|
|
174
|
+
deduped.filter((_d: any) => {
|
|
187
175
|
return true;
|
|
188
176
|
}).length;
|
|
189
177
|
|
|
190
|
-
sections.push(
|
|
178
|
+
sections.push('---');
|
|
191
179
|
sections.push(
|
|
192
|
-
`_${tokensUsed} / ${tokenBudget} tokens used | project: ${effectiveProject ||
|
|
180
|
+
`_${tokensUsed} / ${tokenBudget} tokens used | project: ${effectiveProject || 'unscoped'}_`
|
|
193
181
|
);
|
|
194
182
|
|
|
195
|
-
const result = ok(sections.join(
|
|
183
|
+
const result: ToolResult = ok(sections.join('\n'));
|
|
196
184
|
result._meta = {
|
|
197
185
|
project: effectiveProject || null,
|
|
198
186
|
buckets: buckets || null,
|
|
@@ -207,76 +195,81 @@ export async function handler(
|
|
|
207
195
|
return result;
|
|
208
196
|
}
|
|
209
197
|
|
|
210
|
-
function queryLastSession(ctx, effectiveTags) {
|
|
198
|
+
function queryLastSession(ctx: LocalCtx, effectiveTags: string[]): any {
|
|
211
199
|
const clauses = [`kind = '${SESSION_SUMMARY_KIND}'`];
|
|
212
|
-
const params = [];
|
|
200
|
+
const params: any[] = [];
|
|
213
201
|
|
|
214
202
|
if (false) {
|
|
215
203
|
}
|
|
216
204
|
clauses.push("(expires_at IS NULL OR expires_at > datetime('now'))");
|
|
217
|
-
clauses.push(
|
|
205
|
+
clauses.push('superseded_by IS NULL');
|
|
218
206
|
|
|
219
|
-
const where = `WHERE ${clauses.join(
|
|
207
|
+
const where = `WHERE ${clauses.join(' AND ')}`;
|
|
220
208
|
const rows = ctx.db
|
|
221
209
|
.prepare(`SELECT * FROM vault ${where} ORDER BY created_at DESC LIMIT 5`)
|
|
222
210
|
.all(...params);
|
|
223
211
|
|
|
224
212
|
if (effectiveTags.length) {
|
|
225
|
-
const match = rows.find((r) => {
|
|
213
|
+
const match = rows.find((r: any) => {
|
|
226
214
|
const tags = r.tags ? JSON.parse(r.tags) : [];
|
|
227
|
-
return effectiveTags.some((t) => tags.includes(t));
|
|
215
|
+
return effectiveTags.some((t: string) => tags.includes(t));
|
|
228
216
|
});
|
|
229
217
|
if (match) return match;
|
|
230
218
|
}
|
|
231
219
|
return rows[0] || null;
|
|
232
220
|
}
|
|
233
221
|
|
|
234
|
-
function queryByKinds(
|
|
235
|
-
|
|
222
|
+
function queryByKinds(
|
|
223
|
+
ctx: LocalCtx,
|
|
224
|
+
kinds: string[],
|
|
225
|
+
since: string,
|
|
226
|
+
effectiveTags: string[]
|
|
227
|
+
): any[] {
|
|
228
|
+
const kindPlaceholders = kinds.map(() => '?').join(',');
|
|
236
229
|
const clauses = [`kind IN (${kindPlaceholders})`];
|
|
237
230
|
const params = [...kinds];
|
|
238
231
|
|
|
239
|
-
clauses.push(
|
|
232
|
+
clauses.push('created_at >= ?');
|
|
240
233
|
params.push(since);
|
|
241
234
|
|
|
242
235
|
if (false) {
|
|
243
236
|
}
|
|
244
237
|
clauses.push("(expires_at IS NULL OR expires_at > datetime('now'))");
|
|
245
|
-
clauses.push(
|
|
238
|
+
clauses.push('superseded_by IS NULL');
|
|
246
239
|
|
|
247
|
-
const where = `WHERE ${clauses.join(
|
|
240
|
+
const where = `WHERE ${clauses.join(' AND ')}`;
|
|
248
241
|
const rows = ctx.db
|
|
249
242
|
.prepare(`SELECT * FROM vault ${where} ORDER BY created_at DESC LIMIT 50`)
|
|
250
243
|
.all(...params);
|
|
251
244
|
|
|
252
245
|
if (effectiveTags.length) {
|
|
253
|
-
const tagged = rows.filter((r) => {
|
|
246
|
+
const tagged = rows.filter((r: any) => {
|
|
254
247
|
const tags = r.tags ? JSON.parse(r.tags) : [];
|
|
255
|
-
return effectiveTags.some((t) => tags.includes(t));
|
|
248
|
+
return effectiveTags.some((t: string) => tags.includes(t));
|
|
256
249
|
});
|
|
257
250
|
if (tagged.length > 0) return tagged;
|
|
258
251
|
}
|
|
259
252
|
return rows;
|
|
260
253
|
}
|
|
261
254
|
|
|
262
|
-
function queryRecent(ctx, since, effectiveTags) {
|
|
263
|
-
const clauses = [
|
|
255
|
+
function queryRecent(ctx: LocalCtx, since: string, effectiveTags: string[]): any[] {
|
|
256
|
+
const clauses = ['created_at >= ?'];
|
|
264
257
|
const params = [since];
|
|
265
258
|
|
|
266
259
|
if (false) {
|
|
267
260
|
}
|
|
268
261
|
clauses.push("(expires_at IS NULL OR expires_at > datetime('now'))");
|
|
269
|
-
clauses.push(
|
|
262
|
+
clauses.push('superseded_by IS NULL');
|
|
270
263
|
|
|
271
|
-
const where = `WHERE ${clauses.join(
|
|
264
|
+
const where = `WHERE ${clauses.join(' AND ')}`;
|
|
272
265
|
const rows = ctx.db
|
|
273
266
|
.prepare(`SELECT * FROM vault ${where} ORDER BY created_at DESC LIMIT 50`)
|
|
274
267
|
.all(...params);
|
|
275
268
|
|
|
276
269
|
if (effectiveTags.length) {
|
|
277
|
-
const tagged = rows.filter((r) => {
|
|
270
|
+
const tagged = rows.filter((r: any) => {
|
|
278
271
|
const tags = r.tags ? JSON.parse(r.tags) : [];
|
|
279
|
-
return effectiveTags.some((t) => tags.includes(t));
|
|
272
|
+
return effectiveTags.some((t: string) => tags.includes(t));
|
|
280
273
|
});
|
|
281
274
|
if (tagged.length > 0) return tagged;
|
|
282
275
|
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BaseCtx, VaultConfig } from '@context-vault/core/types';
|
|
2
|
+
|
|
3
|
+
export interface LocalCtx extends BaseCtx {
|
|
4
|
+
activeOps: { count: number };
|
|
5
|
+
toolStats: {
|
|
6
|
+
ok: number;
|
|
7
|
+
errors: number;
|
|
8
|
+
lastError: { tool: string; code: string; timestamp: number } | null;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SharedCtx {
|
|
13
|
+
ensureIndexed: (opts?: { blocking?: boolean }) => Promise<void>;
|
|
14
|
+
reindexFailed?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ToolResult {
|
|
18
|
+
content: Array<{ type: string; text: string }>;
|
|
19
|
+
isError?: boolean;
|
|
20
|
+
code?: string;
|
|
21
|
+
_meta?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ToolModule {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
inputSchema: Record<string, unknown>;
|
|
28
|
+
handler: (args: Record<string, unknown>, ctx: LocalCtx, shared: SharedCtx) => Promise<ToolResult>;
|
|
29
|
+
}
|
package/src/helpers.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { join, dirname } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const pkg = JSON.parse(
|
|
7
|
-
readFileSync(join(__dirname, "..", "package.json"), "utf-8"),
|
|
8
|
-
);
|
|
9
|
-
|
|
10
|
-
export function ok(text) {
|
|
11
|
-
return { content: [{ type: "text", text }] };
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function err(text, code = "UNKNOWN", meta = {}) {
|
|
15
|
-
return {
|
|
16
|
-
content: [{ type: "text", text }],
|
|
17
|
-
isError: true,
|
|
18
|
-
code,
|
|
19
|
-
_meta: {
|
|
20
|
-
cv_version: pkg.version,
|
|
21
|
-
node_version: process.version,
|
|
22
|
-
platform: process.platform,
|
|
23
|
-
arch: process.arch,
|
|
24
|
-
...meta,
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function errWithHint(text, code, hint) {
|
|
30
|
-
const prompt = hint
|
|
31
|
-
? `\n\n**Debug with AI:** Paste this into Claude Code or your AI assistant:\n> "${hint}"`
|
|
32
|
-
: "";
|
|
33
|
-
return err(text + prompt, code);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function ensureVaultExists(config) {
|
|
37
|
-
if (!config.vaultDirExists) {
|
|
38
|
-
return errWithHint(
|
|
39
|
-
`Vault directory not found: ${config.vaultDir}. Run context-status for diagnostics.`,
|
|
40
|
-
"VAULT_NOT_FOUND",
|
|
41
|
-
"My context-vault can't find the vault directory. Run `context-vault doctor` and help me fix it.",
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function ensureValidKind(kind) {
|
|
48
|
-
if (!/^[a-z][a-z0-9_-]*$/.test(kind)) {
|
|
49
|
-
return err(
|
|
50
|
-
"Required: kind (lowercase alphanumeric, e.g. 'insight', 'reference')",
|
|
51
|
-
"INVALID_KIND",
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export { pkg };
|
package/src/register-tools.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { reindex } from "@context-vault/core/index";
|
|
2
|
-
import { captureAndIndex } from "@context-vault/core/capture";
|
|
3
|
-
import { err } from "./helpers.js";
|
|
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
|
-
);
|
|
13
|
-
|
|
14
|
-
import * as getContext from "./tools/get-context.js";
|
|
15
|
-
import * as saveContext from "./tools/save-context.js";
|
|
16
|
-
import * as listContext from "./tools/list-context.js";
|
|
17
|
-
import * as deleteContext from "./tools/delete-context.js";
|
|
18
|
-
import * as ingestUrl from "./tools/ingest-url.js";
|
|
19
|
-
import * as contextStatus from "./tools/context-status.js";
|
|
20
|
-
import * as clearContext from "./tools/clear-context.js";
|
|
21
|
-
import * as createSnapshot from "./tools/create-snapshot.js";
|
|
22
|
-
import * as sessionStart from "./tools/session-start.js";
|
|
23
|
-
import * as listBuckets from "./tools/list-buckets.js";
|
|
24
|
-
import * as ingestProject from "./tools/ingest-project.js";
|
|
25
|
-
|
|
26
|
-
const toolModules = [
|
|
27
|
-
getContext,
|
|
28
|
-
saveContext,
|
|
29
|
-
listContext,
|
|
30
|
-
deleteContext,
|
|
31
|
-
ingestUrl,
|
|
32
|
-
ingestProject,
|
|
33
|
-
contextStatus,
|
|
34
|
-
clearContext,
|
|
35
|
-
createSnapshot,
|
|
36
|
-
sessionStart,
|
|
37
|
-
listBuckets,
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
const TOOL_TIMEOUT_MS = 60_000;
|
|
41
|
-
|
|
42
|
-
export function registerTools(server, ctx) {
|
|
43
|
-
function tracked(handler, toolName) {
|
|
44
|
-
return async (...args) => {
|
|
45
|
-
if (ctx.activeOps) ctx.activeOps.count++;
|
|
46
|
-
let timer;
|
|
47
|
-
let handlerPromise;
|
|
48
|
-
try {
|
|
49
|
-
handlerPromise = Promise.resolve(handler(...args));
|
|
50
|
-
const result = await Promise.race([
|
|
51
|
-
handlerPromise,
|
|
52
|
-
new Promise((_, reject) => {
|
|
53
|
-
timer = setTimeout(
|
|
54
|
-
() => reject(new Error("TOOL_TIMEOUT")),
|
|
55
|
-
TOOL_TIMEOUT_MS,
|
|
56
|
-
);
|
|
57
|
-
}),
|
|
58
|
-
]);
|
|
59
|
-
if (ctx.toolStats) ctx.toolStats.ok++;
|
|
60
|
-
return result;
|
|
61
|
-
} catch (e) {
|
|
62
|
-
if (e.message === "TOOL_TIMEOUT") {
|
|
63
|
-
handlerPromise?.catch(() => {});
|
|
64
|
-
if (ctx.toolStats) {
|
|
65
|
-
ctx.toolStats.errors++;
|
|
66
|
-
ctx.toolStats.lastError = {
|
|
67
|
-
tool: toolName,
|
|
68
|
-
code: "TIMEOUT",
|
|
69
|
-
timestamp: Date.now(),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
sendTelemetryEvent(ctx.config, {
|
|
73
|
-
event: "tool_error",
|
|
74
|
-
code: "TIMEOUT",
|
|
75
|
-
tool: toolName,
|
|
76
|
-
cv_version: pkg.version,
|
|
77
|
-
});
|
|
78
|
-
return err(
|
|
79
|
-
"Tool timed out after 60s. Try a simpler query or run `context-vault reindex` first.",
|
|
80
|
-
"TIMEOUT",
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
if (ctx.toolStats) {
|
|
84
|
-
ctx.toolStats.errors++;
|
|
85
|
-
ctx.toolStats.lastError = {
|
|
86
|
-
tool: toolName,
|
|
87
|
-
code: "UNKNOWN",
|
|
88
|
-
timestamp: Date.now(),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
sendTelemetryEvent(ctx.config, {
|
|
92
|
-
event: "tool_error",
|
|
93
|
-
code: "UNKNOWN",
|
|
94
|
-
tool: toolName,
|
|
95
|
-
cv_version: pkg.version,
|
|
96
|
-
});
|
|
97
|
-
try {
|
|
98
|
-
await captureAndIndex(ctx, {
|
|
99
|
-
kind: "feedback",
|
|
100
|
-
title: `Unhandled error in ${toolName ?? "tool"} call`,
|
|
101
|
-
body: `${e.message}\n\n${e.stack ?? ""}`,
|
|
102
|
-
tags: ["bug", "auto-captured"],
|
|
103
|
-
source: "auto-capture",
|
|
104
|
-
meta: {
|
|
105
|
-
tool: toolName,
|
|
106
|
-
error_type: e.constructor?.name,
|
|
107
|
-
cv_version: pkg.version,
|
|
108
|
-
auto: true,
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
} catch {}
|
|
112
|
-
return err(e.message, "INTERNAL_ERROR");
|
|
113
|
-
} finally {
|
|
114
|
-
clearTimeout(timer);
|
|
115
|
-
if (ctx.activeOps) ctx.activeOps.count--;
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
let reindexDone = false;
|
|
121
|
-
let reindexPromise = null;
|
|
122
|
-
let reindexAttempts = 0;
|
|
123
|
-
let reindexFailed = false;
|
|
124
|
-
const MAX_REINDEX_ATTEMPTS = 2;
|
|
125
|
-
|
|
126
|
-
async function ensureIndexed() {
|
|
127
|
-
if (reindexDone) return;
|
|
128
|
-
if (reindexPromise) return reindexPromise;
|
|
129
|
-
const promise = reindex(ctx, { fullSync: true })
|
|
130
|
-
.then((stats) => {
|
|
131
|
-
reindexDone = true;
|
|
132
|
-
const total = stats.added + stats.updated + stats.removed;
|
|
133
|
-
if (total > 0) {
|
|
134
|
-
console.error(
|
|
135
|
-
`[context-vault] Auto-reindex: +${stats.added} ~${stats.updated} -${stats.removed} (${stats.unchanged} unchanged)`,
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
.catch((e) => {
|
|
140
|
-
reindexAttempts++;
|
|
141
|
-
console.error(
|
|
142
|
-
`[context-vault] Auto-reindex failed (attempt ${reindexAttempts}/${MAX_REINDEX_ATTEMPTS}): ${e.message}`,
|
|
143
|
-
);
|
|
144
|
-
if (reindexAttempts >= MAX_REINDEX_ATTEMPTS) {
|
|
145
|
-
console.error(
|
|
146
|
-
`[context-vault] Giving up on auto-reindex. Run \`context-vault reindex\` manually to diagnose.`,
|
|
147
|
-
);
|
|
148
|
-
reindexDone = true;
|
|
149
|
-
reindexFailed = true;
|
|
150
|
-
} else {
|
|
151
|
-
reindexPromise = null;
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
reindexPromise = promise;
|
|
155
|
-
return reindexPromise;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const shared = {
|
|
159
|
-
ensureIndexed,
|
|
160
|
-
get reindexFailed() {
|
|
161
|
-
return reindexFailed;
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
for (const mod of toolModules) {
|
|
166
|
-
server.tool(
|
|
167
|
-
mod.name,
|
|
168
|
-
mod.description,
|
|
169
|
-
mod.inputSchema,
|
|
170
|
-
tracked((args) => mod.handler(args, ctx, shared), mod.name),
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
ensureIndexed().catch(() => {});
|
|
175
|
-
}
|
package/src/telemetry.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { existsSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
API_URL,
|
|
5
|
-
MARKETING_URL,
|
|
6
|
-
GITHUB_ISSUES_URL,
|
|
7
|
-
} from "@context-vault/core/constants";
|
|
8
|
-
|
|
9
|
-
const TELEMETRY_ENDPOINT = `${API_URL}/telemetry`;
|
|
10
|
-
const NOTICE_MARKER = ".telemetry-notice-shown";
|
|
11
|
-
const FEEDBACK_PROMPT_MARKER = ".feedback-prompt-shown";
|
|
12
|
-
|
|
13
|
-
export function isTelemetryEnabled(config) {
|
|
14
|
-
const envVal = process.env.CONTEXT_VAULT_TELEMETRY;
|
|
15
|
-
if (envVal !== undefined) return envVal === "1" || envVal === "true";
|
|
16
|
-
return config?.telemetry === true;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function sendTelemetryEvent(config, payload) {
|
|
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) {
|
|
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) {
|
|
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
|
-
}
|