context-vault 3.1.6 → 3.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/bin/cli.js +1369 -1774
  2. package/dist/archive.d.ts +23 -0
  3. package/dist/archive.d.ts.map +1 -0
  4. package/dist/archive.js +197 -0
  5. package/dist/archive.js.map +1 -0
  6. package/dist/consolidation.d.ts +14 -0
  7. package/dist/consolidation.d.ts.map +1 -0
  8. package/dist/consolidation.js +59 -0
  9. package/dist/consolidation.js.map +1 -0
  10. package/dist/error-log.d.ts +4 -0
  11. package/dist/error-log.d.ts.map +1 -0
  12. package/dist/error-log.js +33 -0
  13. package/dist/error-log.js.map +1 -0
  14. package/dist/helpers.d.ts +10 -0
  15. package/dist/helpers.d.ts.map +1 -0
  16. package/dist/helpers.js +42 -0
  17. package/dist/helpers.js.map +1 -0
  18. package/dist/linking.d.ts +13 -0
  19. package/dist/linking.d.ts.map +1 -0
  20. package/dist/linking.js +86 -0
  21. package/dist/linking.js.map +1 -0
  22. package/dist/migrate-dirs.d.ts +16 -0
  23. package/dist/migrate-dirs.d.ts.map +1 -0
  24. package/dist/migrate-dirs.js +127 -0
  25. package/dist/migrate-dirs.js.map +1 -0
  26. package/dist/register-tools.d.ts +3 -0
  27. package/dist/register-tools.d.ts.map +1 -0
  28. package/dist/register-tools.js +161 -0
  29. package/dist/register-tools.js.map +1 -0
  30. package/dist/server.d.ts +3 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +241 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/status.d.ts +18 -0
  35. package/dist/status.d.ts.map +1 -0
  36. package/dist/status.js +265 -0
  37. package/dist/status.js.map +1 -0
  38. package/dist/telemetry.d.ts +6 -0
  39. package/dist/telemetry.d.ts.map +1 -0
  40. package/dist/telemetry.js +74 -0
  41. package/dist/telemetry.js.map +1 -0
  42. package/dist/temporal.d.ts +9 -0
  43. package/dist/temporal.d.ts.map +1 -0
  44. package/dist/temporal.js +76 -0
  45. package/dist/temporal.js.map +1 -0
  46. package/dist/tools/clear-context.d.ts +11 -0
  47. package/dist/tools/clear-context.d.ts.map +1 -0
  48. package/dist/tools/clear-context.js +28 -0
  49. package/dist/tools/clear-context.js.map +1 -0
  50. package/dist/tools/context-status.d.ts +6 -0
  51. package/dist/tools/context-status.d.ts.map +1 -0
  52. package/dist/tools/context-status.js +160 -0
  53. package/dist/tools/context-status.js.map +1 -0
  54. package/dist/tools/create-snapshot.d.ts +13 -0
  55. package/dist/tools/create-snapshot.d.ts.map +1 -0
  56. package/dist/tools/create-snapshot.js +161 -0
  57. package/dist/tools/create-snapshot.js.map +1 -0
  58. package/dist/tools/delete-context.d.ts +9 -0
  59. package/dist/tools/delete-context.d.ts.map +1 -0
  60. package/dist/tools/delete-context.js +45 -0
  61. package/dist/tools/delete-context.js.map +1 -0
  62. package/dist/tools/get-context.d.ts +85 -0
  63. package/dist/tools/get-context.d.ts.map +1 -0
  64. package/dist/tools/get-context.js +576 -0
  65. package/dist/tools/get-context.js.map +1 -0
  66. package/dist/tools/ingest-project.d.ts +11 -0
  67. package/dist/tools/ingest-project.d.ts.map +1 -0
  68. package/dist/tools/ingest-project.js +226 -0
  69. package/dist/tools/ingest-project.js.map +1 -0
  70. package/dist/tools/ingest-url.d.ts +11 -0
  71. package/dist/tools/ingest-url.d.ts.map +1 -0
  72. package/dist/tools/ingest-url.js +62 -0
  73. package/dist/tools/ingest-url.js.map +1 -0
  74. package/dist/tools/list-buckets.d.ts +9 -0
  75. package/dist/tools/list-buckets.d.ts.map +1 -0
  76. package/dist/tools/list-buckets.js +76 -0
  77. package/dist/tools/list-buckets.js.map +1 -0
  78. package/dist/tools/list-context.d.ts +19 -0
  79. package/dist/tools/list-context.d.ts.map +1 -0
  80. package/dist/tools/list-context.js +110 -0
  81. package/dist/tools/list-context.js.map +1 -0
  82. package/dist/tools/save-context.d.ts +36 -0
  83. package/dist/tools/save-context.d.ts.map +1 -0
  84. package/dist/tools/save-context.js +458 -0
  85. package/dist/tools/save-context.js.map +1 -0
  86. package/dist/tools/session-start.d.ts +11 -0
  87. package/dist/tools/session-start.d.ts.map +1 -0
  88. package/dist/tools/session-start.js +224 -0
  89. package/dist/tools/session-start.js.map +1 -0
  90. package/dist/types.d.ts +37 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +2 -0
  93. package/dist/types.js.map +1 -0
  94. package/node_modules/@context-vault/core/dist/capture.d.ts +1 -1
  95. package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -1
  96. package/node_modules/@context-vault/core/dist/capture.js +34 -47
  97. package/node_modules/@context-vault/core/dist/capture.js.map +1 -1
  98. package/node_modules/@context-vault/core/dist/categories.js +30 -30
  99. package/node_modules/@context-vault/core/dist/config.d.ts +1 -1
  100. package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -1
  101. package/node_modules/@context-vault/core/dist/config.js +37 -43
  102. package/node_modules/@context-vault/core/dist/config.js.map +1 -1
  103. package/node_modules/@context-vault/core/dist/constants.d.ts +1 -1
  104. package/node_modules/@context-vault/core/dist/constants.d.ts.map +1 -1
  105. package/node_modules/@context-vault/core/dist/constants.js +4 -4
  106. package/node_modules/@context-vault/core/dist/constants.js.map +1 -1
  107. package/node_modules/@context-vault/core/dist/db.d.ts +2 -2
  108. package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -1
  109. package/node_modules/@context-vault/core/dist/db.js +21 -20
  110. package/node_modules/@context-vault/core/dist/db.js.map +1 -1
  111. package/node_modules/@context-vault/core/dist/embed.d.ts.map +1 -1
  112. package/node_modules/@context-vault/core/dist/embed.js +11 -11
  113. package/node_modules/@context-vault/core/dist/embed.js.map +1 -1
  114. package/node_modules/@context-vault/core/dist/files.d.ts.map +1 -1
  115. package/node_modules/@context-vault/core/dist/files.js +12 -13
  116. package/node_modules/@context-vault/core/dist/files.js.map +1 -1
  117. package/node_modules/@context-vault/core/dist/formatters.js +5 -5
  118. package/node_modules/@context-vault/core/dist/frontmatter.d.ts.map +1 -1
  119. package/node_modules/@context-vault/core/dist/frontmatter.js +23 -23
  120. package/node_modules/@context-vault/core/dist/frontmatter.js.map +1 -1
  121. package/node_modules/@context-vault/core/dist/index.d.ts +1 -1
  122. package/node_modules/@context-vault/core/dist/index.d.ts.map +1 -1
  123. package/node_modules/@context-vault/core/dist/index.js +58 -46
  124. package/node_modules/@context-vault/core/dist/index.js.map +1 -1
  125. package/node_modules/@context-vault/core/dist/ingest-url.d.ts.map +1 -1
  126. package/node_modules/@context-vault/core/dist/ingest-url.js +30 -33
  127. package/node_modules/@context-vault/core/dist/ingest-url.js.map +1 -1
  128. package/node_modules/@context-vault/core/dist/main.d.ts +13 -13
  129. package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -1
  130. package/node_modules/@context-vault/core/dist/main.js +12 -12
  131. package/node_modules/@context-vault/core/dist/main.js.map +1 -1
  132. package/node_modules/@context-vault/core/dist/search.d.ts +1 -1
  133. package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
  134. package/node_modules/@context-vault/core/dist/search.js +20 -22
  135. package/node_modules/@context-vault/core/dist/search.js.map +1 -1
  136. package/node_modules/@context-vault/core/dist/types.d.ts +1 -1
  137. package/node_modules/@context-vault/core/package.json +1 -1
  138. package/node_modules/@context-vault/core/src/capture.ts +44 -81
  139. package/node_modules/@context-vault/core/src/categories.ts +30 -30
  140. package/node_modules/@context-vault/core/src/config.ts +45 -60
  141. package/node_modules/@context-vault/core/src/constants.ts +8 -10
  142. package/node_modules/@context-vault/core/src/db.ts +37 -56
  143. package/node_modules/@context-vault/core/src/embed.ts +15 -26
  144. package/node_modules/@context-vault/core/src/files.ts +13 -16
  145. package/node_modules/@context-vault/core/src/formatters.ts +5 -5
  146. package/node_modules/@context-vault/core/src/frontmatter.ts +26 -30
  147. package/node_modules/@context-vault/core/src/index.ts +94 -100
  148. package/node_modules/@context-vault/core/src/ingest-url.ts +56 -93
  149. package/node_modules/@context-vault/core/src/main.ts +13 -18
  150. package/node_modules/@context-vault/core/src/search.ts +34 -56
  151. package/node_modules/@context-vault/core/src/types.ts +1 -1
  152. package/package.json +10 -4
  153. package/scripts/postinstall.js +18 -25
  154. package/scripts/prepack.js +13 -19
  155. package/src/archive.ts +244 -0
  156. package/src/consolidation.ts +78 -0
  157. package/src/{error-log.js → error-log.ts} +10 -10
  158. package/src/helpers.ts +61 -0
  159. package/src/{linking.js → linking.ts} +22 -20
  160. package/src/migrate-dirs.ts +152 -0
  161. package/src/register-tools.ts +183 -0
  162. package/src/{server.js → server.ts} +89 -109
  163. package/src/{status.js → status.ts} +94 -108
  164. package/src/telemetry.ts +80 -0
  165. package/src/{temporal.js → temporal.ts} +29 -33
  166. package/src/tools/clear-context.ts +41 -0
  167. package/src/tools/{context-status.js → context-status.ts} +43 -66
  168. package/src/tools/{create-snapshot.js → create-snapshot.ts} +54 -65
  169. package/src/tools/delete-context.ts +53 -0
  170. package/src/tools/{get-context.js → get-context.ts} +142 -205
  171. package/src/tools/ingest-project.ts +260 -0
  172. package/src/tools/ingest-url.ts +74 -0
  173. package/src/tools/{list-buckets.js → list-buckets.ts} +27 -37
  174. package/src/tools/{list-context.js → list-context.ts} +46 -71
  175. package/src/tools/{save-context.js → save-context.ts} +148 -204
  176. package/src/tools/{session-start.js → session-start.ts} +72 -79
  177. package/src/types.ts +29 -0
  178. package/src/helpers.js +0 -57
  179. package/src/register-tools.js +0 -175
  180. package/src/telemetry.js +0 -80
  181. package/src/tools/clear-context.js +0 -47
  182. package/src/tools/delete-context.js +0 -54
  183. package/src/tools/ingest-project.js +0 -272
  184. package/src/tools/ingest-url.js +0 -87
@@ -1,42 +1,39 @@
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
-
7
- function relativeTime(ts) {
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 ? "" : "s"} ago`;
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 ? "" : "s"} ago`;
14
+ return `${hrs} hour${hrs === 1 ? '' : 's'} ago`;
14
15
  }
15
16
 
16
- export const name = "context_status";
17
+ export const name = 'context_status';
17
18
 
18
19
  export const description =
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
+ '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 + " files" : "missing"})`,
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
- ? entry.last_updated.split("T")[0]
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, ".last-error");
112
+ const lastErrorPath = join(config.dataDir, '.last-error');
131
113
  if (existsSync(lastErrorPath)) {
132
114
  try {
133
- const lastError = readFileSync(lastErrorPath, "utf-8").trim();
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("", "### ⚠ Vault Growth Warning");
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(" Breakdown by kind:");
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("", "Suggested growth actions:");
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
- actions.push("- Run `context-vault reindex` to fix stale paths");
185
- if (status.embeddingStatus?.missing > 0)
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("- Run `context-vault setup` to create the vault directory");
167
+ actions.push('- Run `context-vault setup` to create the vault directory');
191
168
  if (status.kindCounts.length === 0 && config.vaultDirExists)
192
- actions.push("- Use `save_context` to add your first entry");
169
+ actions.push('- Use `save_context` to add your first entry');
193
170
 
194
171
  if (actions.length) {
195
- lines.push("", "### Suggested Actions", ...actions);
172
+ lines.push('', '### Suggested Actions', ...actions);
196
173
  }
197
174
 
198
- return ok(lines.join("\n"));
175
+ return ok(lines.join('\n'));
199
176
  } catch (e) {
200
- return err(e.message, "STATUS_FAILED");
177
+ return err(e instanceof Error ? e.message : String(e), 'STATUS_FAILED');
201
178
  }
202
179
  }
@@ -1,106 +1,106 @@
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
-
7
- const NOISE_KINDS = new Set(["prompt-history", "task-notification"]);
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 = "create_snapshot";
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("The topic or project name to snapshot"),
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("Optional tag filters — entries must match at least one"),
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("Optional kind filters to restrict which entry types are pulled"),
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
- "Deterministic key for the saved brief (defaults to slugified topic). Use the same key to overwrite a previous snapshot.",
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 ? "entry" : "entries"}. Synthesize the content below to extract key decisions, patterns, and constraints.*`,
45
- "",
46
- "---",
47
- "",
48
- ].join("\n");
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(", ") : "none";
54
- const updated = e.updated_at || e.created_at || "unknown";
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
- (e.body.length > MAX_BODY_PER_ENTRY ? "…" : "")
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("\n");
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("Required: topic (non-empty string)", "INVALID_INPUT");
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, "SEARCH_FAILED");
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
- "NO_ENTRIES",
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: "brief",
165
+ kind: 'brief',
173
166
  title: `${topic} — Context Brief`,
174
167
  body: briefBody,
175
168
  tags: briefTags,
176
- source: "create_snapshot",
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, "SAVE_FAILED");
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
- ? ` noise superseded: ${noiseIds.length} entries`
198
- : null,
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("\n");
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
+ }