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,42 +1,39 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { gatherVaultStatus, computeGrowthWarnings } from
|
|
4
|
-
import { errorLogPath, errorLogCount } from
|
|
5
|
-
import { ok, err } from
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { gatherVaultStatus, computeGrowthWarnings } from '../status.js';
|
|
4
|
+
import { errorLogPath, errorLogCount } from '../error-log.js';
|
|
5
|
+
import { ok, err } from '../helpers.js';
|
|
6
|
+
import type { LocalCtx, ToolResult } from '../types.js';
|
|
7
|
+
|
|
8
|
+
function relativeTime(ts: number): string {
|
|
8
9
|
const secs = Math.floor((Date.now() - ts) / 1000);
|
|
9
10
|
if (secs < 60) return `${secs}s ago`;
|
|
10
11
|
const mins = Math.floor(secs / 60);
|
|
11
|
-
if (mins < 60) return `${mins} minute${mins === 1 ?
|
|
12
|
+
if (mins < 60) return `${mins} minute${mins === 1 ? '' : 's'} ago`;
|
|
12
13
|
const hrs = Math.floor(mins / 60);
|
|
13
|
-
return `${hrs} hour${hrs === 1 ?
|
|
14
|
+
return `${hrs} hour${hrs === 1 ? '' : 's'} ago`;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const name =
|
|
17
|
+
export const name = 'context_status';
|
|
17
18
|
|
|
18
19
|
export const description =
|
|
19
|
-
|
|
20
|
+
'Show vault health: resolved config, file counts per kind, database size, and any issues. Use to verify setup or troubleshoot. Call this when a user asks about their vault or to debug search issues.';
|
|
20
21
|
|
|
21
22
|
export const inputSchema = {};
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
* @param {object} _args
|
|
25
|
-
* @param {import('../types.js').BaseCtx & Partial<import('../types.js').HostedCtxExtensions>} ctx
|
|
26
|
-
*/
|
|
27
|
-
export function handler(_args, ctx) {
|
|
24
|
+
export function handler(_args: Record<string, any>, ctx: LocalCtx): ToolResult {
|
|
28
25
|
try {
|
|
29
26
|
const { config } = ctx;
|
|
30
27
|
|
|
31
28
|
const status = gatherVaultStatus(ctx);
|
|
32
29
|
|
|
33
|
-
const hasIssues = status.stalePaths || status.embeddingStatus?.missing > 0;
|
|
34
|
-
const healthIcon = hasIssues ?
|
|
30
|
+
const hasIssues = status.stalePaths || (status.embeddingStatus?.missing ?? 0) > 0;
|
|
31
|
+
const healthIcon = hasIssues ? '⚠' : '✓';
|
|
35
32
|
|
|
36
33
|
const lines = [
|
|
37
34
|
`## ${healthIcon} Vault Status (connected)`,
|
|
38
35
|
``,
|
|
39
|
-
`Vault: ${config.vaultDir} (${config.vaultDirExists ? status.fileCount +
|
|
36
|
+
`Vault: ${config.vaultDir} (${config.vaultDirExists ? status.fileCount + ' files' : 'missing'})`,
|
|
40
37
|
`Database: ${config.dbPath} (${status.dbSize})`,
|
|
41
38
|
`Dev dir: ${config.devDir}`,
|
|
42
39
|
`Data dir: ${config.dataDir}`,
|
|
@@ -51,26 +48,21 @@ export function handler(_args, ctx) {
|
|
|
51
48
|
lines.push(`Embeddings: ${indexed}/${total} (${pct}%)`);
|
|
52
49
|
}
|
|
53
50
|
if (status.embedModelAvailable === false) {
|
|
54
|
-
lines.push(
|
|
55
|
-
`Embed model: unavailable (semantic search disabled, FTS still works)`,
|
|
56
|
-
);
|
|
51
|
+
lines.push(`Embed model: unavailable (semantic search disabled, FTS still works)`);
|
|
57
52
|
} else if (status.embedModelAvailable === true) {
|
|
58
53
|
lines.push(`Embed model: loaded`);
|
|
59
54
|
}
|
|
60
|
-
lines.push(
|
|
61
|
-
`Decay: ${config.eventDecayDays} days (event recency window)`,
|
|
62
|
-
);
|
|
55
|
+
lines.push(`Decay: ${config.eventDecayDays} days (event recency window)`);
|
|
63
56
|
if (status.expiredCount > 0) {
|
|
64
57
|
lines.push(
|
|
65
|
-
`Expired: ${status.expiredCount} entries pending prune (run \`context-vault prune\` to remove now)
|
|
58
|
+
`Expired: ${status.expiredCount} entries pending prune (run \`context-vault prune\` to remove now)`
|
|
66
59
|
);
|
|
67
60
|
}
|
|
68
61
|
|
|
69
62
|
lines.push(``, `### Indexed`);
|
|
70
63
|
|
|
71
64
|
if (status.kindCounts.length) {
|
|
72
|
-
for (const { kind, c } of status.kindCounts)
|
|
73
|
-
lines.push(`- ${c} ${kind}s`);
|
|
65
|
+
for (const { kind, c } of status.kindCounts) lines.push(`- ${c} ${kind}s`);
|
|
74
66
|
} else {
|
|
75
67
|
lines.push(`- (empty)`);
|
|
76
68
|
}
|
|
@@ -78,23 +70,19 @@ export function handler(_args, ctx) {
|
|
|
78
70
|
if (status.categoryCounts.length) {
|
|
79
71
|
lines.push(``);
|
|
80
72
|
lines.push(`### Categories`);
|
|
81
|
-
for (const { category, c } of status.categoryCounts)
|
|
82
|
-
lines.push(`- ${category}: ${c}`);
|
|
73
|
+
for (const { category, c } of status.categoryCounts) lines.push(`- ${category}: ${c}`);
|
|
83
74
|
}
|
|
84
75
|
|
|
85
76
|
if (status.subdirs.length) {
|
|
86
77
|
lines.push(``);
|
|
87
78
|
lines.push(`### Disk Directories`);
|
|
88
|
-
for (const { name, count } of status.subdirs)
|
|
89
|
-
lines.push(`- ${name}/: ${count} files`);
|
|
79
|
+
for (const { name, count } of status.subdirs) lines.push(`- ${name}/: ${count} files`);
|
|
90
80
|
}
|
|
91
81
|
|
|
92
82
|
if (status.stalePaths) {
|
|
93
83
|
lines.push(``);
|
|
94
84
|
lines.push(`### ⚠ Stale Paths`);
|
|
95
|
-
lines.push(
|
|
96
|
-
`DB contains ${status.staleCount} paths not matching current vault dir.`,
|
|
97
|
-
);
|
|
85
|
+
lines.push(`DB contains ${status.staleCount} paths not matching current vault dir.`);
|
|
98
86
|
lines.push(`Auto-reindex will fix this on next search or save.`);
|
|
99
87
|
}
|
|
100
88
|
|
|
@@ -102,19 +90,13 @@ export function handler(_args, ctx) {
|
|
|
102
90
|
lines.push(``);
|
|
103
91
|
lines.push(`### ⚠ Potentially Stale Knowledge`);
|
|
104
92
|
lines.push(
|
|
105
|
-
`Not updated within kind staleness window (pattern: 180d, decision: 365d, reference: 90d)
|
|
93
|
+
`Not updated within kind staleness window (pattern: 180d, decision: 365d, reference: 90d):`
|
|
106
94
|
);
|
|
107
95
|
for (const entry of status.staleKnowledge) {
|
|
108
|
-
const lastUpdated = entry.last_updated
|
|
109
|
-
|
|
110
|
-
: "unknown";
|
|
111
|
-
lines.push(
|
|
112
|
-
`- "${entry.title}" (${entry.kind}) — last updated ${lastUpdated}`,
|
|
113
|
-
);
|
|
96
|
+
const lastUpdated = entry.last_updated ? entry.last_updated.split('T')[0] : 'unknown';
|
|
97
|
+
lines.push(`- "${entry.title}" (${entry.kind}) — last updated ${lastUpdated}`);
|
|
114
98
|
}
|
|
115
|
-
lines.push(
|
|
116
|
-
`Use save_context to refresh or add expires_at to retire stale entries.`,
|
|
117
|
-
);
|
|
99
|
+
lines.push(`Use save_context to refresh or add expires_at to retire stale entries.`);
|
|
118
100
|
}
|
|
119
101
|
|
|
120
102
|
// Error log
|
|
@@ -127,10 +109,10 @@ export function handler(_args, ctx) {
|
|
|
127
109
|
}
|
|
128
110
|
|
|
129
111
|
// Last startup error
|
|
130
|
-
const lastErrorPath = join(config.dataDir,
|
|
112
|
+
const lastErrorPath = join(config.dataDir, '.last-error');
|
|
131
113
|
if (existsSync(lastErrorPath)) {
|
|
132
114
|
try {
|
|
133
|
-
const lastError = readFileSync(lastErrorPath,
|
|
115
|
+
const lastError = readFileSync(lastErrorPath, 'utf-8').trim();
|
|
134
116
|
lines.push(``, `### Last Startup Error`);
|
|
135
117
|
lines.push(`\`\`\``);
|
|
136
118
|
lines.push(lastError);
|
|
@@ -145,13 +127,11 @@ export function handler(_args, ctx) {
|
|
|
145
127
|
lines.push(`- Tool calls (session): ${ts.ok} ok, ${ts.errors} errors`);
|
|
146
128
|
if (ts.lastError) {
|
|
147
129
|
const { tool, code, timestamp } = ts.lastError;
|
|
148
|
-
lines.push(
|
|
149
|
-
`- Last error: ${tool ?? "unknown"} — ${code} (${relativeTime(timestamp)})`,
|
|
150
|
-
);
|
|
130
|
+
lines.push(`- Last error: ${tool ?? 'unknown'} — ${code} (${relativeTime(timestamp)})`);
|
|
151
131
|
}
|
|
152
132
|
if (status.autoCapturedFeedbackCount > 0) {
|
|
153
133
|
lines.push(
|
|
154
|
-
`- Auto-captured feedback entries: ${status.autoCapturedFeedbackCount} (run get_context with kind:feedback tags:auto-captured)
|
|
134
|
+
`- Auto-captured feedback entries: ${status.autoCapturedFeedbackCount} (run get_context with kind:feedback tags:auto-captured)`
|
|
155
135
|
);
|
|
156
136
|
}
|
|
157
137
|
}
|
|
@@ -159,19 +139,19 @@ export function handler(_args, ctx) {
|
|
|
159
139
|
// Growth warnings
|
|
160
140
|
const growth = computeGrowthWarnings(status, config.thresholds);
|
|
161
141
|
if (growth.hasWarnings) {
|
|
162
|
-
lines.push(
|
|
142
|
+
lines.push('', '### ⚠ Vault Growth Warning');
|
|
163
143
|
for (const w of growth.warnings) {
|
|
164
144
|
lines.push(` ${w.message}`);
|
|
165
145
|
}
|
|
166
146
|
if (growth.kindBreakdown.length) {
|
|
167
|
-
lines.push(
|
|
168
|
-
lines.push(
|
|
147
|
+
lines.push('');
|
|
148
|
+
lines.push(' Breakdown by kind:');
|
|
169
149
|
for (const { kind, count, pct } of growth.kindBreakdown) {
|
|
170
150
|
lines.push(` ${kind}: ${count.toLocaleString()} (${pct}%)`);
|
|
171
151
|
}
|
|
172
152
|
}
|
|
173
153
|
if (growth.actions.length) {
|
|
174
|
-
lines.push(
|
|
154
|
+
lines.push('', 'Suggested growth actions:');
|
|
175
155
|
for (const a of growth.actions) {
|
|
176
156
|
lines.push(` • ${a}`);
|
|
177
157
|
}
|
|
@@ -180,23 +160,20 @@ export function handler(_args, ctx) {
|
|
|
180
160
|
|
|
181
161
|
// Suggested actions
|
|
182
162
|
const actions = [];
|
|
183
|
-
if (status.stalePaths)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
actions.push(
|
|
187
|
-
"- Run `context-vault reindex` to generate missing embeddings",
|
|
188
|
-
);
|
|
163
|
+
if (status.stalePaths) actions.push('- Run `context-vault reindex` to fix stale paths');
|
|
164
|
+
if ((status.embeddingStatus?.missing ?? 0) > 0)
|
|
165
|
+
actions.push('- Run `context-vault reindex` to generate missing embeddings');
|
|
189
166
|
if (!config.vaultDirExists)
|
|
190
|
-
actions.push(
|
|
167
|
+
actions.push('- Run `context-vault setup` to create the vault directory');
|
|
191
168
|
if (status.kindCounts.length === 0 && config.vaultDirExists)
|
|
192
|
-
actions.push(
|
|
169
|
+
actions.push('- Use `save_context` to add your first entry');
|
|
193
170
|
|
|
194
171
|
if (actions.length) {
|
|
195
|
-
lines.push(
|
|
172
|
+
lines.push('', '### Suggested Actions', ...actions);
|
|
196
173
|
}
|
|
197
174
|
|
|
198
|
-
return ok(lines.join(
|
|
175
|
+
return ok(lines.join('\n'));
|
|
199
176
|
} catch (e) {
|
|
200
|
-
return err(e.message,
|
|
177
|
+
return err(e instanceof Error ? e.message : String(e), 'STATUS_FAILED');
|
|
201
178
|
}
|
|
202
179
|
}
|
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { hybridSearch } from
|
|
3
|
-
import { captureAndIndex } from
|
|
4
|
-
import { normalizeKind } from
|
|
5
|
-
import { ok, err, ensureVaultExists } from
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { hybridSearch } from '@context-vault/core/search';
|
|
3
|
+
import { captureAndIndex } from '@context-vault/core/capture';
|
|
4
|
+
import { normalizeKind } from '@context-vault/core/files';
|
|
5
|
+
import { ok, err, ensureVaultExists } from '../helpers.js';
|
|
6
|
+
import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
|
|
7
|
+
|
|
8
|
+
const NOISE_KINDS = new Set(['prompt-history', 'task-notification']);
|
|
8
9
|
const MAX_ENTRIES_FOR_GATHER = 40;
|
|
9
10
|
const MAX_BODY_PER_ENTRY = 600;
|
|
10
11
|
|
|
11
|
-
export const name =
|
|
12
|
+
export const name = 'create_snapshot';
|
|
12
13
|
|
|
13
14
|
export const description =
|
|
14
15
|
"Pull all relevant vault entries matching a topic, deduplicate, and save them as a structured context brief (kind: 'brief'). Entries are formatted as markdown — no external API or LLM call required. The calling agent can synthesize the gathered content directly. Retrieve with: get_context(kind: 'brief', identity_key: '<key>').";
|
|
15
16
|
|
|
16
17
|
export const inputSchema = {
|
|
17
|
-
topic: z.string().describe(
|
|
18
|
+
topic: z.string().describe('The topic or project name to snapshot'),
|
|
18
19
|
tags: z
|
|
19
20
|
.array(z.string())
|
|
20
21
|
.optional()
|
|
21
|
-
.describe(
|
|
22
|
+
.describe('Optional tag filters — entries must match at least one'),
|
|
22
23
|
buckets: z
|
|
23
24
|
.array(z.string())
|
|
24
25
|
.optional()
|
|
25
26
|
.describe(
|
|
26
|
-
"Filter by project-scoped buckets. Each name expands to a 'bucket:<name>' tag. Composes with 'tags' via OR (entries matching any tag or any bucket are included)."
|
|
27
|
+
"Filter by project-scoped buckets. Each name expands to a 'bucket:<name>' tag. Composes with 'tags' via OR (entries matching any tag or any bucket are included)."
|
|
27
28
|
),
|
|
28
29
|
kinds: z
|
|
29
30
|
.array(z.string())
|
|
30
31
|
.optional()
|
|
31
|
-
.describe(
|
|
32
|
+
.describe('Optional kind filters to restrict which entry types are pulled'),
|
|
32
33
|
identity_key: z
|
|
33
34
|
.string()
|
|
34
35
|
.optional()
|
|
35
36
|
.describe(
|
|
36
|
-
|
|
37
|
+
'Deterministic key for the saved brief (defaults to slugified topic). Use the same key to overwrite a previous snapshot.'
|
|
37
38
|
),
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
function formatGatheredEntries(topic, entries) {
|
|
41
|
+
function formatGatheredEntries(topic: string, entries: any[]): string {
|
|
41
42
|
const header = [
|
|
42
43
|
`# ${topic} — Context Brief`,
|
|
43
|
-
|
|
44
|
-
`*Gathered from ${entries.length} vault ${entries.length === 1 ?
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
].join(
|
|
44
|
+
'',
|
|
45
|
+
`*Gathered from ${entries.length} vault ${entries.length === 1 ? 'entry' : 'entries'}. Synthesize the content below to extract key decisions, patterns, and constraints.*`,
|
|
46
|
+
'',
|
|
47
|
+
'---',
|
|
48
|
+
'',
|
|
49
|
+
].join('\n');
|
|
49
50
|
|
|
50
51
|
const body = entries
|
|
51
|
-
.map((e, i) => {
|
|
52
|
+
.map((e: any, i: number) => {
|
|
52
53
|
const tags = e.tags ? JSON.parse(e.tags) : [];
|
|
53
|
-
const tagStr = tags.length ? tags.join(
|
|
54
|
-
const updated = e.updated_at || e.created_at ||
|
|
54
|
+
const tagStr = tags.length ? tags.join(', ') : 'none';
|
|
55
|
+
const updated = e.updated_at || e.created_at || 'unknown';
|
|
55
56
|
const bodyText = e.body
|
|
56
|
-
? e.body.slice(0, MAX_BODY_PER_ENTRY) +
|
|
57
|
-
|
|
58
|
-
: "(no body)";
|
|
57
|
+
? e.body.slice(0, MAX_BODY_PER_ENTRY) + (e.body.length > MAX_BODY_PER_ENTRY ? '…' : '')
|
|
58
|
+
: '(no body)';
|
|
59
59
|
const title = e.title || `Entry ${i + 1}`;
|
|
60
60
|
return [
|
|
61
61
|
`## ${i + 1}. [${e.kind}] ${title}`,
|
|
62
|
-
|
|
62
|
+
'',
|
|
63
63
|
`**Tags:** ${tagStr}`,
|
|
64
64
|
`**Updated:** ${updated}`,
|
|
65
65
|
`**ID:** \`${e.id}\``,
|
|
66
|
-
|
|
66
|
+
'',
|
|
67
67
|
bodyText,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
].join(
|
|
68
|
+
'',
|
|
69
|
+
'---',
|
|
70
|
+
'',
|
|
71
|
+
].join('\n');
|
|
72
72
|
})
|
|
73
|
-
.join(
|
|
73
|
+
.join('');
|
|
74
74
|
|
|
75
75
|
return header + body;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function slugifyTopic(topic) {
|
|
78
|
+
function slugifyTopic(topic: string): string {
|
|
79
79
|
return topic
|
|
80
80
|
.toLowerCase()
|
|
81
|
-
.replace(/[^a-z0-9]+/g,
|
|
82
|
-
.replace(/^-+|-+$/g,
|
|
81
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
82
|
+
.replace(/^-+|-+$/g, '')
|
|
83
83
|
.slice(0, 120);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
export async function handler(
|
|
87
|
-
{ topic, tags, buckets, kinds, identity_key },
|
|
88
|
-
ctx,
|
|
89
|
-
{ ensureIndexed }
|
|
90
|
-
) {
|
|
87
|
+
{ topic, tags, buckets, kinds, identity_key }: Record<string, any>,
|
|
88
|
+
ctx: LocalCtx,
|
|
89
|
+
{ ensureIndexed }: SharedCtx
|
|
90
|
+
): Promise<ToolResult> {
|
|
91
91
|
const { config } = ctx;
|
|
92
92
|
|
|
93
93
|
const vaultErr = ensureVaultExists(config);
|
|
94
94
|
if (vaultErr) return vaultErr;
|
|
95
95
|
|
|
96
96
|
if (!topic?.trim()) {
|
|
97
|
-
return err(
|
|
97
|
+
return err('Required: topic (non-empty string)', 'INVALID_INPUT');
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
await ensureIndexed();
|
|
101
101
|
|
|
102
102
|
const normalizedKinds = kinds?.map(normalizeKind) ?? [];
|
|
103
|
-
const bucketTags = buckets?.length ? buckets.map((b) => `bucket:${b}`) : [];
|
|
103
|
+
const bucketTags = buckets?.length ? buckets.map((b: string) => `bucket:${b}`) : [];
|
|
104
104
|
const effectiveTags = [...(tags ?? []), ...bucketTags];
|
|
105
105
|
|
|
106
106
|
let candidates = [];
|
|
@@ -130,7 +130,7 @@ export async function handler(
|
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
} catch (e) {
|
|
133
|
-
return err(e.message,
|
|
133
|
+
return err(e instanceof Error ? e.message : String(e), 'SEARCH_FAILED');
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
if (effectiveTags.length) {
|
|
@@ -140,40 +140,33 @@ export async function handler(
|
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
const noiseIds = candidates
|
|
144
|
-
.filter((r) => NOISE_KINDS.has(r.kind))
|
|
145
|
-
.map((r) => r.id);
|
|
143
|
+
const noiseIds = candidates.filter((r) => NOISE_KINDS.has(r.kind)).map((r) => r.id);
|
|
146
144
|
|
|
147
145
|
const gatherEntries = candidates.filter((r) => !NOISE_KINDS.has(r.kind));
|
|
148
146
|
|
|
149
147
|
if (gatherEntries.length === 0) {
|
|
150
148
|
return err(
|
|
151
149
|
`No entries found for topic "${topic}". Try a broader topic or different tags.`,
|
|
152
|
-
|
|
150
|
+
'NO_ENTRIES'
|
|
153
151
|
);
|
|
154
152
|
}
|
|
155
153
|
|
|
156
154
|
const briefBody = formatGatheredEntries(topic, gatherEntries);
|
|
157
155
|
|
|
158
|
-
const effectiveIdentityKey =
|
|
159
|
-
identity_key ?? `snapshot-${slugifyTopic(topic)}`;
|
|
156
|
+
const effectiveIdentityKey = identity_key ?? `snapshot-${slugifyTopic(topic)}`;
|
|
160
157
|
|
|
161
|
-
const briefTags = [
|
|
162
|
-
"snapshot",
|
|
163
|
-
...(tags ?? []),
|
|
164
|
-
...(normalizedKinds.length > 0 ? [] : []),
|
|
165
|
-
];
|
|
158
|
+
const briefTags = ['snapshot', ...(tags ?? []), ...(normalizedKinds.length > 0 ? [] : [])];
|
|
166
159
|
|
|
167
160
|
const supersedes = noiseIds.length > 0 ? noiseIds : undefined;
|
|
168
161
|
|
|
169
162
|
let entry;
|
|
170
163
|
try {
|
|
171
164
|
entry = await captureAndIndex(ctx, {
|
|
172
|
-
kind:
|
|
165
|
+
kind: 'brief',
|
|
173
166
|
title: `${topic} — Context Brief`,
|
|
174
167
|
body: briefBody,
|
|
175
168
|
tags: briefTags,
|
|
176
|
-
source:
|
|
169
|
+
source: 'create_snapshot',
|
|
177
170
|
identity_key: effectiveIdentityKey,
|
|
178
171
|
supersedes,
|
|
179
172
|
|
|
@@ -185,7 +178,7 @@ export async function handler(
|
|
|
185
178
|
},
|
|
186
179
|
});
|
|
187
180
|
} catch (e) {
|
|
188
|
-
return err(e.message,
|
|
181
|
+
return err(e instanceof Error ? e.message : String(e), 'SAVE_FAILED');
|
|
189
182
|
}
|
|
190
183
|
|
|
191
184
|
const parts = [
|
|
@@ -193,16 +186,12 @@ export async function handler(
|
|
|
193
186
|
` title: ${entry.title}`,
|
|
194
187
|
` identity_key: ${effectiveIdentityKey}`,
|
|
195
188
|
` synthesized from: ${gatherEntries.length} entries`,
|
|
196
|
-
noiseIds.length > 0
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"",
|
|
200
|
-
"_Retrieve with: get_context(kind: 'brief', identity_key: '" +
|
|
201
|
-
effectiveIdentityKey +
|
|
202
|
-
"')_",
|
|
189
|
+
noiseIds.length > 0 ? ` noise superseded: ${noiseIds.length} entries` : null,
|
|
190
|
+
'',
|
|
191
|
+
"_Retrieve with: get_context(kind: 'brief', identity_key: '" + effectiveIdentityKey + "')_",
|
|
203
192
|
]
|
|
204
193
|
.filter((l) => l !== null)
|
|
205
|
-
.join(
|
|
194
|
+
.join('\n');
|
|
206
195
|
|
|
207
196
|
return ok(parts);
|
|
208
197
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { unlinkSync } from 'node:fs';
|
|
3
|
+
import { ok, err } from '../helpers.js';
|
|
4
|
+
import type { LocalCtx, SharedCtx, ToolResult } from '../types.js';
|
|
5
|
+
|
|
6
|
+
export const name = 'delete_context';
|
|
7
|
+
|
|
8
|
+
export const description =
|
|
9
|
+
'Delete an entry from your vault by its ULID id. Removes the file from disk and cleans up the search index.';
|
|
10
|
+
|
|
11
|
+
export const inputSchema = {
|
|
12
|
+
id: z.string().describe('The entry ULID to delete'),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function handler(
|
|
16
|
+
{ id }: Record<string, any>,
|
|
17
|
+
ctx: LocalCtx,
|
|
18
|
+
{ ensureIndexed }: SharedCtx
|
|
19
|
+
): Promise<ToolResult> {
|
|
20
|
+
if (!id?.trim()) return err('Required: id (non-empty string)', 'INVALID_INPUT');
|
|
21
|
+
await ensureIndexed();
|
|
22
|
+
|
|
23
|
+
const entry = ctx.stmts.getEntryById.get(id);
|
|
24
|
+
if (!entry) return err(`Entry not found: ${id}`, 'NOT_FOUND');
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Delete DB record first — if this fails, the file stays and no orphan is created
|
|
28
|
+
const rowidResult = ctx.stmts.getRowid.get(id);
|
|
29
|
+
if (rowidResult?.rowid) {
|
|
30
|
+
try {
|
|
31
|
+
ctx.deleteVec(Number(rowidResult.rowid));
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
ctx.stmts.deleteEntry.run(id);
|
|
35
|
+
|
|
36
|
+
// Delete file from disk after successful DB delete
|
|
37
|
+
let fileWarning = null;
|
|
38
|
+
if (entry.file_path) {
|
|
39
|
+
try {
|
|
40
|
+
unlinkSync(entry.file_path as string);
|
|
41
|
+
} catch (e: any) {
|
|
42
|
+
if (e.code !== 'ENOENT') {
|
|
43
|
+
fileWarning = `file could not be removed from disk (${e.code}): ${entry.file_path}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const msg = `Deleted ${entry.kind}: ${entry.title || '(untitled)'} [${id}]`;
|
|
49
|
+
return ok(fileWarning ? `${msg}\nWarning: ${fileWarning}` : msg);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return err(e instanceof Error ? e.message : String(e), 'DELETE_FAILED');
|
|
52
|
+
}
|
|
53
|
+
}
|