@context-vault/core 2.17.1 → 3.0.3
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/dist/capture.d.ts +21 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +269 -0
- package/dist/capture.js.map +1 -0
- package/dist/categories.d.ts +6 -0
- package/dist/categories.d.ts.map +1 -0
- package/dist/categories.js +50 -0
- package/dist/categories.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +190 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +33 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +23 -0
- package/dist/constants.js.map +1 -0
- package/dist/db.d.ts +13 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +191 -0
- package/dist/db.js.map +1 -0
- package/dist/embed.d.ts +5 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +78 -0
- package/dist/embed.js.map +1 -0
- package/dist/files.d.ts +13 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +66 -0
- package/dist/files.js.map +1 -0
- package/dist/formatters.d.ts +8 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +18 -0
- package/dist/formatters.js.map +1 -0
- package/dist/frontmatter.d.ts +12 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +101 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +297 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest-url.d.ts +20 -0
- package/dist/ingest-url.d.ts.map +1 -0
- package/dist/ingest-url.js +113 -0
- package/dist/ingest-url.js.map +1 -0
- package/dist/main.d.ts +14 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +25 -0
- package/dist/main.js.map +1 -0
- package/dist/search.d.ts +18 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +238 -0
- package/dist/search.js.map +1 -0
- package/dist/types.d.ts +176 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -16
- package/src/capture.ts +308 -0
- package/src/categories.ts +54 -0
- package/src/{core/config.js → config.ts} +34 -33
- package/src/{constants.js → constants.ts} +6 -3
- package/src/db.ts +229 -0
- package/src/{index/embed.js → embed.ts} +10 -35
- package/src/{core/files.js → files.ts} +15 -20
- package/src/{capture/formatters.js → formatters.ts} +13 -11
- package/src/{core/frontmatter.js → frontmatter.ts} +26 -33
- package/src/index.ts +353 -0
- package/src/ingest-url.ts +99 -0
- package/src/main.ts +111 -0
- package/src/{retrieve/index.js → search.ts} +62 -150
- package/src/types.ts +166 -0
- package/src/capture/file-ops.js +0 -99
- package/src/capture/import-pipeline.js +0 -46
- package/src/capture/importers.js +0 -387
- package/src/capture/index.js +0 -250
- package/src/capture/ingest-url.js +0 -252
- package/src/consolidation/index.js +0 -112
- package/src/core/categories.js +0 -73
- package/src/core/error-log.js +0 -54
- package/src/core/linking.js +0 -161
- package/src/core/migrate-dirs.js +0 -196
- package/src/core/status.js +0 -350
- package/src/core/telemetry.js +0 -90
- package/src/core/temporal.js +0 -146
- package/src/index/db.js +0 -586
- package/src/index/index.js +0 -583
- package/src/index.js +0 -71
- package/src/server/helpers.js +0 -44
- package/src/server/tools/clear-context.js +0 -47
- package/src/server/tools/context-status.js +0 -182
- package/src/server/tools/create-snapshot.js +0 -200
- package/src/server/tools/delete-context.js +0 -60
- package/src/server/tools/get-context.js +0 -765
- package/src/server/tools/ingest-project.js +0 -244
- package/src/server/tools/ingest-url.js +0 -88
- package/src/server/tools/list-buckets.js +0 -116
- package/src/server/tools/list-context.js +0 -163
- package/src/server/tools/save-context.js +0 -632
- package/src/server/tools/session-start.js +0 -285
- package/src/server/tools.js +0 -172
- package/src/sync/sync.js +0 -235
package/src/capture.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { resolve, relative } from "node:path";
|
|
3
|
+
import { ulid, slugify, kindToPath } from "./files.js";
|
|
4
|
+
import { categoryFor, defaultTierFor } from "./categories.js";
|
|
5
|
+
import { parseFrontmatter, formatFrontmatter } from "./frontmatter.js";
|
|
6
|
+
import { formatBody } from "./formatters.js";
|
|
7
|
+
import type { BaseCtx, CaptureInput, CaptureResult, IndexEntryInput } from "./types.js";
|
|
8
|
+
import { indexEntry } from "./index.js";
|
|
9
|
+
|
|
10
|
+
function safeFolderPath(vaultDir: string, kind: string, folder?: string | null): string {
|
|
11
|
+
const base = resolve(vaultDir, kindToPath(kind));
|
|
12
|
+
if (!folder) return base;
|
|
13
|
+
const resolved = resolve(base, folder);
|
|
14
|
+
const rel = relative(base, resolved);
|
|
15
|
+
if (rel.startsWith("..") || resolve(base, rel) !== resolved) {
|
|
16
|
+
throw new Error(`Folder path escapes vault: "${folder}"`);
|
|
17
|
+
}
|
|
18
|
+
return resolved;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeEntryFile(
|
|
22
|
+
vaultDir: string,
|
|
23
|
+
kind: string,
|
|
24
|
+
params: {
|
|
25
|
+
id: string;
|
|
26
|
+
title?: string | null;
|
|
27
|
+
body: string;
|
|
28
|
+
meta?: Record<string, unknown> | null;
|
|
29
|
+
tags?: string[] | null;
|
|
30
|
+
source?: string | null;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
folder?: string | null;
|
|
34
|
+
category: string;
|
|
35
|
+
identity_key?: string | null;
|
|
36
|
+
expires_at?: string | null;
|
|
37
|
+
supersedes?: string[] | null;
|
|
38
|
+
related_to?: string[] | null;
|
|
39
|
+
},
|
|
40
|
+
): string {
|
|
41
|
+
const resolvedFolder = params.folder || (params.meta?.folder as string) || "";
|
|
42
|
+
const dir = safeFolderPath(vaultDir, kind, resolvedFolder);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(dir, { recursive: true });
|
|
46
|
+
} catch (e) {
|
|
47
|
+
throw new Error(`Failed to create directory "${dir}": ${(e as Error).message}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const created = params.createdAt || new Date().toISOString();
|
|
51
|
+
const fmFields: Record<string, unknown> = { id: params.id };
|
|
52
|
+
|
|
53
|
+
if (params.meta) {
|
|
54
|
+
for (const [k, v] of Object.entries(params.meta)) {
|
|
55
|
+
if (k === "folder") continue;
|
|
56
|
+
if (v !== null && v !== undefined) fmFields[k] = v;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (params.identity_key) fmFields.identity_key = params.identity_key;
|
|
61
|
+
if (params.expires_at) fmFields.expires_at = params.expires_at;
|
|
62
|
+
if (params.supersedes?.length) fmFields.supersedes = params.supersedes;
|
|
63
|
+
if (params.related_to?.length) fmFields.related_to = params.related_to;
|
|
64
|
+
fmFields.tags = params.tags || [];
|
|
65
|
+
fmFields.source = params.source || "claude-code";
|
|
66
|
+
fmFields.created = created;
|
|
67
|
+
if (params.updatedAt && params.updatedAt !== created)
|
|
68
|
+
fmFields.updated = params.updatedAt;
|
|
69
|
+
|
|
70
|
+
const mdBody = formatBody(kind, {
|
|
71
|
+
title: params.title || undefined,
|
|
72
|
+
body: params.body,
|
|
73
|
+
meta: params.meta || undefined,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
let filename: string;
|
|
77
|
+
if (params.category === "entity" && params.identity_key) {
|
|
78
|
+
const identitySlug = slugify(params.identity_key);
|
|
79
|
+
filename = identitySlug
|
|
80
|
+
? `${identitySlug}.md`
|
|
81
|
+
: `${params.id.slice(-8).toLowerCase()}.md`;
|
|
82
|
+
} else {
|
|
83
|
+
const slug = slugify((params.title || params.body).slice(0, 40));
|
|
84
|
+
const shortId = params.id.slice(-8).toLowerCase();
|
|
85
|
+
filename = slug ? `${slug}-${shortId}.md` : `${shortId}.md`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const filePath = resolve(dir, filename);
|
|
89
|
+
const md = formatFrontmatter(fmFields) + mdBody;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
writeFileSync(filePath, md);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
throw new Error(`Failed to write entry file "${filePath}": ${(e as Error).message}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return filePath;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function writeEntry(ctx: BaseCtx, data: CaptureInput): CaptureResult {
|
|
101
|
+
if (!data.kind || typeof data.kind !== "string") {
|
|
102
|
+
throw new Error("writeEntry: kind is required (non-empty string)");
|
|
103
|
+
}
|
|
104
|
+
if (!data.body || typeof data.body !== "string" || !data.body.trim()) {
|
|
105
|
+
throw new Error("writeEntry: body is required (non-empty string)");
|
|
106
|
+
}
|
|
107
|
+
if (data.tags != null && !Array.isArray(data.tags)) {
|
|
108
|
+
throw new Error("writeEntry: tags must be an array if provided");
|
|
109
|
+
}
|
|
110
|
+
if (data.meta != null && typeof data.meta !== "object") {
|
|
111
|
+
throw new Error("writeEntry: meta must be an object if provided");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const category = categoryFor(data.kind);
|
|
115
|
+
|
|
116
|
+
let id: string;
|
|
117
|
+
let createdAt: string;
|
|
118
|
+
let updatedAt: string;
|
|
119
|
+
if (category === "entity" && data.identity_key) {
|
|
120
|
+
const identitySlug = slugify(data.identity_key);
|
|
121
|
+
const dir = resolve(ctx.config.vaultDir, kindToPath(data.kind));
|
|
122
|
+
const existingPath = resolve(dir, `${identitySlug}.md`);
|
|
123
|
+
|
|
124
|
+
if (existsSync(existingPath)) {
|
|
125
|
+
const raw = readFileSync(existingPath, "utf-8");
|
|
126
|
+
const { meta: fmMeta } = parseFrontmatter(raw);
|
|
127
|
+
id = (fmMeta.id as string) || ulid();
|
|
128
|
+
createdAt = (fmMeta.created as string) || new Date().toISOString();
|
|
129
|
+
updatedAt = new Date().toISOString();
|
|
130
|
+
} else {
|
|
131
|
+
id = ulid();
|
|
132
|
+
createdAt = new Date().toISOString();
|
|
133
|
+
updatedAt = createdAt;
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
id = ulid();
|
|
137
|
+
createdAt = new Date().toISOString();
|
|
138
|
+
updatedAt = createdAt;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const filePath = writeEntryFile(ctx.config.vaultDir, data.kind, {
|
|
142
|
+
id,
|
|
143
|
+
title: data.title,
|
|
144
|
+
body: data.body,
|
|
145
|
+
meta: data.meta,
|
|
146
|
+
tags: data.tags,
|
|
147
|
+
source: data.source,
|
|
148
|
+
createdAt,
|
|
149
|
+
updatedAt,
|
|
150
|
+
folder: data.folder,
|
|
151
|
+
category,
|
|
152
|
+
identity_key: data.identity_key,
|
|
153
|
+
expires_at: data.expires_at,
|
|
154
|
+
supersedes: data.supersedes,
|
|
155
|
+
related_to: data.related_to,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
id,
|
|
160
|
+
filePath,
|
|
161
|
+
kind: data.kind,
|
|
162
|
+
category,
|
|
163
|
+
title: data.title || null,
|
|
164
|
+
body: data.body,
|
|
165
|
+
meta: data.meta || undefined,
|
|
166
|
+
tags: data.tags || null,
|
|
167
|
+
source: data.source || null,
|
|
168
|
+
createdAt,
|
|
169
|
+
updatedAt,
|
|
170
|
+
identity_key: data.identity_key || null,
|
|
171
|
+
expires_at: data.expires_at || null,
|
|
172
|
+
supersedes: data.supersedes || null,
|
|
173
|
+
related_to: data.related_to || null,
|
|
174
|
+
source_files: data.source_files || null,
|
|
175
|
+
tier: data.tier || null,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function updateEntryFile(
|
|
180
|
+
ctx: BaseCtx,
|
|
181
|
+
existing: Record<string, unknown>,
|
|
182
|
+
updates: {
|
|
183
|
+
title?: string | null;
|
|
184
|
+
body?: string | null;
|
|
185
|
+
tags?: string[] | null;
|
|
186
|
+
meta?: Record<string, unknown> | null;
|
|
187
|
+
source?: string | null;
|
|
188
|
+
expires_at?: string | null;
|
|
189
|
+
supersedes?: string[] | null;
|
|
190
|
+
related_to?: string[] | null;
|
|
191
|
+
source_files?: Array<{ path: string; hash: string }> | null;
|
|
192
|
+
},
|
|
193
|
+
): IndexEntryInput & { supersedes?: string[] | null; related_to?: string[] | null } {
|
|
194
|
+
const raw = readFileSync(existing.file_path as string, "utf-8");
|
|
195
|
+
const { meta: fmMeta } = parseFrontmatter(raw);
|
|
196
|
+
|
|
197
|
+
const existingMeta = existing.meta ? JSON.parse(existing.meta as string) : {};
|
|
198
|
+
const existingTags = existing.tags ? JSON.parse(existing.tags as string) : [];
|
|
199
|
+
const existingRelatedTo = existing.related_to
|
|
200
|
+
? JSON.parse(existing.related_to as string)
|
|
201
|
+
: (fmMeta.related_to as string[]) || null;
|
|
202
|
+
|
|
203
|
+
const title = updates.title !== undefined ? updates.title : (existing.title as string | null);
|
|
204
|
+
const body = updates.body !== undefined ? (updates.body as string) : (existing.body as string);
|
|
205
|
+
const tags = updates.tags !== undefined ? updates.tags : existingTags;
|
|
206
|
+
const source = updates.source !== undefined ? updates.source : (existing.source as string | null);
|
|
207
|
+
const expires_at = updates.expires_at !== undefined ? updates.expires_at : (existing.expires_at as string | null);
|
|
208
|
+
const supersedes = updates.supersedes !== undefined ? updates.supersedes : (fmMeta.supersedes as string[] || null);
|
|
209
|
+
const related_to = updates.related_to !== undefined ? updates.related_to : existingRelatedTo;
|
|
210
|
+
const source_files = updates.source_files !== undefined
|
|
211
|
+
? updates.source_files
|
|
212
|
+
: existing.source_files
|
|
213
|
+
? JSON.parse(existing.source_files as string)
|
|
214
|
+
: null;
|
|
215
|
+
|
|
216
|
+
let mergedMeta: Record<string, unknown>;
|
|
217
|
+
if (updates.meta !== undefined) {
|
|
218
|
+
mergedMeta = { ...existingMeta, ...(updates.meta || {}) };
|
|
219
|
+
} else {
|
|
220
|
+
mergedMeta = { ...existingMeta };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const now = new Date().toISOString();
|
|
224
|
+
const fmFields: Record<string, unknown> = { id: existing.id };
|
|
225
|
+
for (const [k, v] of Object.entries(mergedMeta)) {
|
|
226
|
+
if (k === "folder") continue;
|
|
227
|
+
if (v !== null && v !== undefined) fmFields[k] = v;
|
|
228
|
+
}
|
|
229
|
+
if (existing.identity_key) fmFields.identity_key = existing.identity_key;
|
|
230
|
+
if (expires_at) fmFields.expires_at = expires_at;
|
|
231
|
+
if (supersedes?.length) fmFields.supersedes = supersedes;
|
|
232
|
+
if (related_to?.length) fmFields.related_to = related_to;
|
|
233
|
+
fmFields.tags = tags;
|
|
234
|
+
fmFields.source = source || "claude-code";
|
|
235
|
+
fmFields.created = (fmMeta.created as string) || (existing.created_at as string);
|
|
236
|
+
if (now !== fmFields.created) fmFields.updated = now;
|
|
237
|
+
|
|
238
|
+
const mdBody = formatBody(existing.kind as string, {
|
|
239
|
+
title: title || undefined,
|
|
240
|
+
body,
|
|
241
|
+
meta: mergedMeta,
|
|
242
|
+
});
|
|
243
|
+
const md = formatFrontmatter(fmFields) + mdBody;
|
|
244
|
+
|
|
245
|
+
writeFileSync(existing.file_path as string, md);
|
|
246
|
+
|
|
247
|
+
const finalMeta = Object.keys(mergedMeta).length ? mergedMeta : undefined;
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
id: existing.id as string,
|
|
251
|
+
filePath: existing.file_path as string,
|
|
252
|
+
kind: existing.kind as string,
|
|
253
|
+
category: existing.category as string,
|
|
254
|
+
title: title || null,
|
|
255
|
+
body,
|
|
256
|
+
meta: finalMeta,
|
|
257
|
+
tags,
|
|
258
|
+
source,
|
|
259
|
+
createdAt: (fmMeta.created as string) || (existing.created_at as string),
|
|
260
|
+
identity_key: (existing.identity_key as string) || null,
|
|
261
|
+
expires_at: expires_at || null,
|
|
262
|
+
supersedes,
|
|
263
|
+
related_to: related_to || null,
|
|
264
|
+
source_files: source_files || null,
|
|
265
|
+
tier: (existing.tier as string) || null,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function captureAndIndex(ctx: BaseCtx, data: CaptureInput, precomputedEmbedding?: Float32Array | null): Promise<CaptureResult> {
|
|
270
|
+
let previousContent: string | null = null;
|
|
271
|
+
if (categoryFor(data.kind) === "entity" && data.identity_key) {
|
|
272
|
+
const identitySlug = slugify(data.identity_key);
|
|
273
|
+
const dir = resolve(ctx.config.vaultDir, kindToPath(data.kind));
|
|
274
|
+
const existingPath = resolve(dir, `${identitySlug}.md`);
|
|
275
|
+
if (existsSync(existingPath)) {
|
|
276
|
+
previousContent = readFileSync(existingPath, "utf-8");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const entry = writeEntry(ctx, data);
|
|
281
|
+
try {
|
|
282
|
+
await indexEntry(ctx, entry, precomputedEmbedding);
|
|
283
|
+
if (entry.supersedes?.length && ctx.stmts.updateSupersededBy) {
|
|
284
|
+
for (const supersededId of entry.supersedes) {
|
|
285
|
+
if (typeof supersededId === "string" && supersededId.trim()) {
|
|
286
|
+
ctx.stmts.updateSupersededBy.run(entry.id, supersededId.trim());
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (entry.related_to?.length && ctx.stmts.updateRelatedTo) {
|
|
291
|
+
ctx.stmts.updateRelatedTo.run(JSON.stringify(entry.related_to), entry.id);
|
|
292
|
+
}
|
|
293
|
+
return entry;
|
|
294
|
+
} catch (err) {
|
|
295
|
+
if (previousContent) {
|
|
296
|
+
try {
|
|
297
|
+
writeFileSync(entry.filePath, previousContent);
|
|
298
|
+
} catch {}
|
|
299
|
+
} else {
|
|
300
|
+
try {
|
|
301
|
+
unlinkSync(entry.filePath);
|
|
302
|
+
} catch {}
|
|
303
|
+
}
|
|
304
|
+
throw new Error(
|
|
305
|
+
`Capture succeeded but indexing failed — file rolled back. ${(err as Error).message}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const KIND_CATEGORY: Record<string, string> = {
|
|
2
|
+
insight: "knowledge",
|
|
3
|
+
decision: "knowledge",
|
|
4
|
+
pattern: "knowledge",
|
|
5
|
+
prompt: "knowledge",
|
|
6
|
+
note: "knowledge",
|
|
7
|
+
document: "knowledge",
|
|
8
|
+
reference: "knowledge",
|
|
9
|
+
contact: "entity",
|
|
10
|
+
project: "entity",
|
|
11
|
+
tool: "entity",
|
|
12
|
+
source: "entity",
|
|
13
|
+
bucket: "entity",
|
|
14
|
+
event: "event",
|
|
15
|
+
conversation: "event",
|
|
16
|
+
message: "event",
|
|
17
|
+
session: "event",
|
|
18
|
+
task: "event",
|
|
19
|
+
log: "event",
|
|
20
|
+
feedback: "event",
|
|
21
|
+
inbox: "event",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const CATEGORY_DIR_NAMES: Record<string, string> = {
|
|
25
|
+
knowledge: "knowledge",
|
|
26
|
+
entity: "entities",
|
|
27
|
+
event: "events",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const CATEGORY_DIRS = new Set(Object.values(CATEGORY_DIR_NAMES));
|
|
31
|
+
|
|
32
|
+
export const KIND_STALENESS_DAYS: Record<string, number> = {
|
|
33
|
+
pattern: 180,
|
|
34
|
+
decision: 365,
|
|
35
|
+
reference: 90,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const DURABLE_KINDS = new Set(["decision", "architecture", "pattern"]);
|
|
39
|
+
const EPHEMERAL_KINDS = new Set(["session", "observation"]);
|
|
40
|
+
|
|
41
|
+
export function categoryFor(kind: string): string {
|
|
42
|
+
return KIND_CATEGORY[kind] || "knowledge";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function defaultTierFor(kind: string): string {
|
|
46
|
+
if (DURABLE_KINDS.has(kind)) return "durable";
|
|
47
|
+
if (EPHEMERAL_KINDS.has(kind)) return "ephemeral";
|
|
48
|
+
return "working";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function categoryDirFor(kind: string): string {
|
|
52
|
+
const cat = categoryFor(kind);
|
|
53
|
+
return CATEGORY_DIR_NAMES[cat] || "knowledge";
|
|
54
|
+
}
|
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { DEFAULT_GROWTH_THRESHOLDS, DEFAULT_LIFECYCLE } from "
|
|
4
|
+
import { DEFAULT_GROWTH_THRESHOLDS, DEFAULT_LIFECYCLE } from "./constants.js";
|
|
5
|
+
import type { VaultConfig } from "./types.js";
|
|
5
6
|
|
|
6
|
-
export function parseArgs(argv) {
|
|
7
|
-
const args = {};
|
|
7
|
+
export function parseArgs(argv: string[]): Record<string, string | number> {
|
|
8
|
+
const args: Record<string, string | number> = {};
|
|
8
9
|
for (let i = 2; i < argv.length; i++) {
|
|
9
|
-
if (argv[i] === "--vault-dir" && argv[i + 1])
|
|
10
|
-
|
|
11
|
-
else if (argv[i] === "--
|
|
12
|
-
|
|
10
|
+
if (argv[i] === "--vault-dir" && argv[i + 1])
|
|
11
|
+
args.vaultDir = argv[++i];
|
|
12
|
+
else if (argv[i] === "--data-dir" && argv[i + 1])
|
|
13
|
+
args.dataDir = argv[++i];
|
|
14
|
+
else if (argv[i] === "--db-path" && argv[i + 1])
|
|
15
|
+
args.dbPath = argv[++i];
|
|
16
|
+
else if (argv[i] === "--dev-dir" && argv[i + 1])
|
|
17
|
+
args.devDir = argv[++i];
|
|
13
18
|
else if (argv[i] === "--event-decay-days" && argv[i + 1])
|
|
14
19
|
args.eventDecayDays = Number(argv[++i]);
|
|
15
20
|
}
|
|
16
21
|
return args;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
export function resolveConfig() {
|
|
24
|
+
export function resolveConfig(): VaultConfig {
|
|
20
25
|
const HOME = homedir();
|
|
21
26
|
const cliArgs = parseArgs(process.argv);
|
|
22
27
|
|
|
23
28
|
const dataDir = resolve(
|
|
24
|
-
cliArgs.dataDir ||
|
|
29
|
+
(cliArgs.dataDir as string) ||
|
|
25
30
|
process.env.CONTEXT_VAULT_DATA_DIR ||
|
|
26
31
|
process.env.CONTEXT_MCP_DATA_DIR ||
|
|
27
32
|
join(HOME, ".context-mcp"),
|
|
28
33
|
);
|
|
29
|
-
const config = {
|
|
34
|
+
const config: VaultConfig = {
|
|
30
35
|
vaultDir: join(HOME, ".vault"),
|
|
31
36
|
dataDir,
|
|
32
37
|
dbPath: join(dataDir, "vault.db"),
|
|
@@ -117,16 +122,21 @@ export function resolveConfig() {
|
|
|
117
122
|
if (c.autoConsolidate != null)
|
|
118
123
|
config.consolidation.autoConsolidate = c.autoConsolidate === true;
|
|
119
124
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
if (fc.lifecycle && typeof fc.lifecycle === "object") {
|
|
126
|
+
for (const [tier, rules] of Object.entries(fc.lifecycle)) {
|
|
127
|
+
if (rules && typeof rules === "object") {
|
|
128
|
+
if (!config.lifecycle[tier]) config.lifecycle[tier] = {};
|
|
129
|
+
if ((rules as Record<string, unknown>).archiveAfterDays != null)
|
|
130
|
+
config.lifecycle[tier].archiveAfterDays = Number(
|
|
131
|
+
(rules as Record<string, unknown>).archiveAfterDays,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
126
136
|
config.resolvedFrom = "config file";
|
|
127
137
|
} catch (e) {
|
|
128
138
|
throw new Error(
|
|
129
|
-
`[context-vault] Invalid config at ${configPath}: ${e.message}`,
|
|
139
|
+
`[context-vault] Invalid config at ${configPath}: ${(e as Error).message}`,
|
|
130
140
|
);
|
|
131
141
|
}
|
|
132
142
|
}
|
|
@@ -137,17 +147,17 @@ export function resolveConfig() {
|
|
|
137
147
|
process.env.CONTEXT_MCP_VAULT_DIR
|
|
138
148
|
) {
|
|
139
149
|
config.vaultDir =
|
|
140
|
-
process.env.CONTEXT_VAULT_VAULT_DIR || process.env.CONTEXT_MCP_VAULT_DIR
|
|
150
|
+
process.env.CONTEXT_VAULT_VAULT_DIR || process.env.CONTEXT_MCP_VAULT_DIR!;
|
|
141
151
|
config.resolvedFrom = "env";
|
|
142
152
|
}
|
|
143
153
|
if (process.env.CONTEXT_VAULT_DB_PATH || process.env.CONTEXT_MCP_DB_PATH) {
|
|
144
154
|
config.dbPath =
|
|
145
|
-
process.env.CONTEXT_VAULT_DB_PATH || process.env.CONTEXT_MCP_DB_PATH
|
|
155
|
+
process.env.CONTEXT_VAULT_DB_PATH || process.env.CONTEXT_MCP_DB_PATH!;
|
|
146
156
|
config.resolvedFrom = "env";
|
|
147
157
|
}
|
|
148
158
|
if (process.env.CONTEXT_VAULT_DEV_DIR || process.env.CONTEXT_MCP_DEV_DIR) {
|
|
149
159
|
config.devDir =
|
|
150
|
-
process.env.CONTEXT_VAULT_DEV_DIR || process.env.CONTEXT_MCP_DEV_DIR
|
|
160
|
+
process.env.CONTEXT_VAULT_DEV_DIR || process.env.CONTEXT_MCP_DEV_DIR!;
|
|
151
161
|
config.resolvedFrom = "env";
|
|
152
162
|
}
|
|
153
163
|
if (
|
|
@@ -161,12 +171,6 @@ export function resolveConfig() {
|
|
|
161
171
|
config.resolvedFrom = "env";
|
|
162
172
|
}
|
|
163
173
|
|
|
164
|
-
if (process.env.CONTEXT_VAULT_API_KEY) {
|
|
165
|
-
config.apiKey = process.env.CONTEXT_VAULT_API_KEY;
|
|
166
|
-
}
|
|
167
|
-
if (process.env.CONTEXT_VAULT_HOSTED_URL) {
|
|
168
|
-
config.hostedUrl = process.env.CONTEXT_VAULT_HOSTED_URL;
|
|
169
|
-
}
|
|
170
174
|
if (process.env.CONTEXT_VAULT_TELEMETRY !== undefined) {
|
|
171
175
|
config.telemetry =
|
|
172
176
|
process.env.CONTEXT_VAULT_TELEMETRY === "1" ||
|
|
@@ -174,29 +178,26 @@ export function resolveConfig() {
|
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
if (cliArgs.vaultDir) {
|
|
177
|
-
config.vaultDir = cliArgs.vaultDir;
|
|
181
|
+
config.vaultDir = cliArgs.vaultDir as string;
|
|
178
182
|
config.resolvedFrom = "CLI args";
|
|
179
183
|
}
|
|
180
184
|
if (cliArgs.dbPath) {
|
|
181
|
-
config.dbPath = cliArgs.dbPath;
|
|
185
|
+
config.dbPath = cliArgs.dbPath as string;
|
|
182
186
|
config.resolvedFrom = "CLI args";
|
|
183
187
|
}
|
|
184
188
|
if (cliArgs.devDir) {
|
|
185
|
-
config.devDir = cliArgs.devDir;
|
|
189
|
+
config.devDir = cliArgs.devDir as string;
|
|
186
190
|
config.resolvedFrom = "CLI args";
|
|
187
191
|
}
|
|
188
192
|
if (cliArgs.eventDecayDays != null) {
|
|
189
|
-
config.eventDecayDays = cliArgs.eventDecayDays;
|
|
193
|
+
config.eventDecayDays = cliArgs.eventDecayDays as number;
|
|
190
194
|
config.resolvedFrom = "CLI args";
|
|
191
195
|
}
|
|
192
196
|
|
|
193
|
-
// Resolve all paths to absolute
|
|
194
197
|
config.vaultDir = resolve(config.vaultDir);
|
|
195
198
|
config.dataDir = resolve(config.dataDir);
|
|
196
199
|
config.dbPath = resolve(config.dbPath);
|
|
197
200
|
config.devDir = resolve(config.devDir);
|
|
198
|
-
|
|
199
|
-
// Check existence
|
|
200
201
|
config.vaultDirExists = existsSync(config.vaultDir);
|
|
201
202
|
|
|
202
203
|
return config;
|
|
@@ -4,12 +4,12 @@ export const MARKETING_URL = "https://contextvault.dev";
|
|
|
4
4
|
export const GITHUB_ISSUES_URL =
|
|
5
5
|
"https://github.com/fellanH/context-vault/issues";
|
|
6
6
|
|
|
7
|
-
export const MAX_BODY_LENGTH = 100 * 1024;
|
|
7
|
+
export const MAX_BODY_LENGTH = 100 * 1024;
|
|
8
8
|
export const MAX_TITLE_LENGTH = 500;
|
|
9
9
|
export const MAX_KIND_LENGTH = 64;
|
|
10
10
|
export const MAX_TAG_LENGTH = 100;
|
|
11
11
|
export const MAX_TAGS_COUNT = 20;
|
|
12
|
-
export const MAX_META_LENGTH = 10 * 1024;
|
|
12
|
+
export const MAX_META_LENGTH = 10 * 1024;
|
|
13
13
|
export const MAX_SOURCE_LENGTH = 200;
|
|
14
14
|
export const MAX_IDENTITY_KEY_LENGTH = 200;
|
|
15
15
|
|
|
@@ -20,7 +20,10 @@ export const DEFAULT_GROWTH_THRESHOLDS = {
|
|
|
20
20
|
eventsWithoutTtl: { warn: 200 },
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export const DEFAULT_LIFECYCLE
|
|
23
|
+
export const DEFAULT_LIFECYCLE: Record<
|
|
24
|
+
string,
|
|
25
|
+
{ archiveAfterDays?: number }
|
|
26
|
+
> = {
|
|
24
27
|
event: { archiveAfterDays: 90 },
|
|
25
28
|
ephemeral: { archiveAfterDays: 30 },
|
|
26
29
|
};
|