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