context-vault 3.1.3 → 3.1.5
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 +146 -65
- 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 +20 -9
- package/node_modules/@context-vault/core/dist/capture.js.map +1 -1
- package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -1
- 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.map +1 -1
- package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/db.js +10 -0
- 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.map +1 -1
- package/node_modules/@context-vault/core/dist/formatters.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/formatters.js.map +1 -1
- package/node_modules/@context-vault/core/dist/frontmatter.d.ts.map +1 -1
- 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 +27 -6
- 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 +32 -9
- package/node_modules/@context-vault/core/dist/ingest-url.js.map +1 -1
- package/node_modules/@context-vault/core/dist/main.d.ts +3 -3
- package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/main.js +3 -3
- 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 +3 -2
- package/node_modules/@context-vault/core/dist/search.js.map +1 -1
- package/node_modules/@context-vault/core/package.json +1 -1
- package/node_modules/@context-vault/core/src/capture.ts +63 -19
- package/node_modules/@context-vault/core/src/config.ts +4 -8
- package/node_modules/@context-vault/core/src/constants.ts +5 -7
- package/node_modules/@context-vault/core/src/db.ts +11 -7
- package/node_modules/@context-vault/core/src/embed.ts +3 -1
- package/node_modules/@context-vault/core/src/formatters.ts +1 -4
- package/node_modules/@context-vault/core/src/frontmatter.ts +6 -4
- package/node_modules/@context-vault/core/src/index.ts +155 -46
- package/node_modules/@context-vault/core/src/ingest-url.ts +153 -40
- package/node_modules/@context-vault/core/src/main.ts +8 -11
- package/node_modules/@context-vault/core/src/search.ts +29 -10
- package/package.json +2 -2
- package/src/register-tools.js +1 -1
- package/src/server.js +1 -1
- package/src/status.js +111 -28
- package/src/telemetry.js +5 -1
- package/src/tools/context-status.js +145 -142
- package/src/tools/get-context.js +13 -13
- package/src/tools/ingest-project.js +39 -10
- package/src/tools/list-buckets.js +3 -5
- package/src/tools/save-context.js +34 -22
- package/src/tools/session-start.js +9 -3
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
unlinkSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import { resolve, relative } from "node:path";
|
|
3
9
|
import { ulid, slugify, kindToPath } from "./files.js";
|
|
4
10
|
import { categoryFor, defaultTierFor } from "./categories.js";
|
|
5
11
|
import { parseFrontmatter, formatFrontmatter } from "./frontmatter.js";
|
|
6
12
|
import { formatBody } from "./formatters.js";
|
|
7
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
BaseCtx,
|
|
15
|
+
CaptureInput,
|
|
16
|
+
CaptureResult,
|
|
17
|
+
IndexEntryInput,
|
|
18
|
+
} from "./types.js";
|
|
8
19
|
import { indexEntry } from "./index.js";
|
|
9
20
|
|
|
10
|
-
function safeFolderPath(
|
|
21
|
+
function safeFolderPath(
|
|
22
|
+
vaultDir: string,
|
|
23
|
+
kind: string,
|
|
24
|
+
folder?: string | null,
|
|
25
|
+
): string {
|
|
11
26
|
const base = resolve(vaultDir, kindToPath(kind));
|
|
12
27
|
if (!folder) return base;
|
|
13
28
|
const resolved = resolve(base, folder);
|
|
@@ -44,7 +59,9 @@ function writeEntryFile(
|
|
|
44
59
|
try {
|
|
45
60
|
mkdirSync(dir, { recursive: true });
|
|
46
61
|
} catch (e) {
|
|
47
|
-
throw new Error(
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Failed to create directory "${dir}": ${(e as Error).message}`,
|
|
64
|
+
);
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
const created = params.createdAt || new Date().toISOString();
|
|
@@ -91,7 +108,9 @@ function writeEntryFile(
|
|
|
91
108
|
try {
|
|
92
109
|
writeFileSync(filePath, md);
|
|
93
110
|
} catch (e) {
|
|
94
|
-
throw new Error(
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Failed to write entry file "${filePath}": ${(e as Error).message}`,
|
|
113
|
+
);
|
|
95
114
|
}
|
|
96
115
|
|
|
97
116
|
return filePath;
|
|
@@ -190,7 +209,10 @@ export function updateEntryFile(
|
|
|
190
209
|
related_to?: string[] | null;
|
|
191
210
|
source_files?: Array<{ path: string; hash: string }> | null;
|
|
192
211
|
},
|
|
193
|
-
): IndexEntryInput & {
|
|
212
|
+
): IndexEntryInput & {
|
|
213
|
+
supersedes?: string[] | null;
|
|
214
|
+
related_to?: string[] | null;
|
|
215
|
+
} {
|
|
194
216
|
const raw = readFileSync(existing.file_path as string, "utf-8");
|
|
195
217
|
const { meta: fmMeta } = parseFrontmatter(raw);
|
|
196
218
|
|
|
@@ -200,18 +222,35 @@ export function updateEntryFile(
|
|
|
200
222
|
? JSON.parse(existing.related_to as string)
|
|
201
223
|
: (fmMeta.related_to as string[]) || null;
|
|
202
224
|
|
|
203
|
-
const title =
|
|
204
|
-
|
|
225
|
+
const title =
|
|
226
|
+
updates.title !== undefined
|
|
227
|
+
? updates.title
|
|
228
|
+
: (existing.title as string | null);
|
|
229
|
+
const body =
|
|
230
|
+
updates.body !== undefined
|
|
231
|
+
? (updates.body as string)
|
|
232
|
+
: (existing.body as string);
|
|
205
233
|
const tags = updates.tags !== undefined ? updates.tags : existingTags;
|
|
206
|
-
const source =
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
234
|
+
const source =
|
|
235
|
+
updates.source !== undefined
|
|
236
|
+
? updates.source
|
|
237
|
+
: (existing.source as string | null);
|
|
238
|
+
const expires_at =
|
|
239
|
+
updates.expires_at !== undefined
|
|
240
|
+
? updates.expires_at
|
|
241
|
+
: (existing.expires_at as string | null);
|
|
242
|
+
const supersedes =
|
|
243
|
+
updates.supersedes !== undefined
|
|
244
|
+
? updates.supersedes
|
|
245
|
+
: (fmMeta.supersedes as string[]) || null;
|
|
246
|
+
const related_to =
|
|
247
|
+
updates.related_to !== undefined ? updates.related_to : existingRelatedTo;
|
|
248
|
+
const source_files =
|
|
249
|
+
updates.source_files !== undefined
|
|
250
|
+
? updates.source_files
|
|
251
|
+
: existing.source_files
|
|
252
|
+
? JSON.parse(existing.source_files as string)
|
|
253
|
+
: null;
|
|
215
254
|
|
|
216
255
|
let mergedMeta: Record<string, unknown>;
|
|
217
256
|
if (updates.meta !== undefined) {
|
|
@@ -232,7 +271,8 @@ export function updateEntryFile(
|
|
|
232
271
|
if (related_to?.length) fmFields.related_to = related_to;
|
|
233
272
|
fmFields.tags = tags;
|
|
234
273
|
fmFields.source = source || "claude-code";
|
|
235
|
-
fmFields.created =
|
|
274
|
+
fmFields.created =
|
|
275
|
+
(fmMeta.created as string) || (existing.created_at as string);
|
|
236
276
|
if (now !== fmFields.created) fmFields.updated = now;
|
|
237
277
|
|
|
238
278
|
const mdBody = formatBody(existing.kind as string, {
|
|
@@ -266,7 +306,11 @@ export function updateEntryFile(
|
|
|
266
306
|
};
|
|
267
307
|
}
|
|
268
308
|
|
|
269
|
-
export async function captureAndIndex(
|
|
309
|
+
export async function captureAndIndex(
|
|
310
|
+
ctx: BaseCtx,
|
|
311
|
+
data: CaptureInput,
|
|
312
|
+
precomputedEmbedding?: Float32Array | null,
|
|
313
|
+
): Promise<CaptureResult> {
|
|
270
314
|
let previousContent: string | null = null;
|
|
271
315
|
if (categoryFor(data.kind) === "entity" && data.identity_key) {
|
|
272
316
|
const identitySlug = slugify(data.identity_key);
|
|
@@ -7,14 +7,10 @@ import type { VaultConfig } from "./types.js";
|
|
|
7
7
|
export function parseArgs(argv: string[]): Record<string, string | number> {
|
|
8
8
|
const args: Record<string, string | number> = {};
|
|
9
9
|
for (let i = 2; i < argv.length; i++) {
|
|
10
|
-
if (argv[i] === "--vault-dir" && argv[i + 1])
|
|
11
|
-
|
|
12
|
-
else if (argv[i] === "--
|
|
13
|
-
|
|
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];
|
|
10
|
+
if (argv[i] === "--vault-dir" && argv[i + 1]) args.vaultDir = argv[++i];
|
|
11
|
+
else if (argv[i] === "--data-dir" && argv[i + 1]) args.dataDir = argv[++i];
|
|
12
|
+
else if (argv[i] === "--db-path" && argv[i + 1]) args.dbPath = argv[++i];
|
|
13
|
+
else if (argv[i] === "--dev-dir" && argv[i + 1]) args.devDir = argv[++i];
|
|
18
14
|
else if (argv[i] === "--event-decay-days" && argv[i + 1])
|
|
19
15
|
args.eventDecayDays = Number(argv[++i]);
|
|
20
16
|
}
|
|
@@ -20,10 +20,8 @@ export const DEFAULT_GROWTH_THRESHOLDS = {
|
|
|
20
20
|
eventsWithoutTtl: { warn: 200 },
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export const DEFAULT_LIFECYCLE: Record<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
ephemeral: { archiveAfterDays: 30 },
|
|
29
|
-
};
|
|
23
|
+
export const DEFAULT_LIFECYCLE: Record<string, { archiveAfterDays?: number }> =
|
|
24
|
+
{
|
|
25
|
+
event: { archiveAfterDays: 90 },
|
|
26
|
+
ephemeral: { archiveAfterDays: 30 },
|
|
27
|
+
};
|
|
@@ -117,7 +117,9 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
const db = createDb(dbPath);
|
|
120
|
-
const version = (
|
|
120
|
+
const version = (
|
|
121
|
+
db.prepare("PRAGMA user_version").get() as { user_version: number }
|
|
122
|
+
).user_version;
|
|
121
123
|
|
|
122
124
|
if (version > 0 && version < 15) {
|
|
123
125
|
console.error(
|
|
@@ -146,13 +148,17 @@ export async function initDatabase(dbPath: string): Promise<DatabaseSync> {
|
|
|
146
148
|
if (!backupSucceeded) {
|
|
147
149
|
throw new Error(
|
|
148
150
|
`[context-vault] Aborting schema migration: backup failed for ${dbPath}. ` +
|
|
149
|
-
|
|
151
|
+
`Fix the backup issue or manually back up the file before upgrading.`,
|
|
150
152
|
);
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
unlinkSync(dbPath);
|
|
154
|
-
try {
|
|
155
|
-
|
|
156
|
+
try {
|
|
157
|
+
unlinkSync(dbPath + "-wal");
|
|
158
|
+
} catch {}
|
|
159
|
+
try {
|
|
160
|
+
unlinkSync(dbPath + "-shm");
|
|
161
|
+
} catch {}
|
|
156
162
|
|
|
157
163
|
const freshDb = createDb(dbPath);
|
|
158
164
|
freshDb.exec(SCHEMA_DDL);
|
|
@@ -179,9 +185,7 @@ export function prepareStatements(db: DatabaseSync): PreparedStatements {
|
|
|
179
185
|
),
|
|
180
186
|
deleteEntry: db.prepare(`DELETE FROM vault WHERE id = ?`),
|
|
181
187
|
getRowid: db.prepare(`SELECT rowid FROM vault WHERE id = ?`),
|
|
182
|
-
getRowidByPath: db.prepare(
|
|
183
|
-
`SELECT rowid FROM vault WHERE file_path = ?`,
|
|
184
|
-
),
|
|
188
|
+
getRowidByPath: db.prepare(`SELECT rowid FROM vault WHERE file_path = ?`),
|
|
185
189
|
getEntryById: db.prepare(`SELECT * FROM vault WHERE id = ?`),
|
|
186
190
|
getByIdentityKey: db.prepare(
|
|
187
191
|
`SELECT * FROM vault WHERE kind = ? AND identity_key = ?`,
|
|
@@ -60,7 +60,9 @@ export async function embed(text: string): Promise<Float32Array | null> {
|
|
|
60
60
|
return new Float32Array(result.data);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export async function embedBatch(
|
|
63
|
+
export async function embedBatch(
|
|
64
|
+
texts: string[],
|
|
65
|
+
): Promise<(Float32Array | null)[]> {
|
|
64
66
|
if (!texts.length) return [];
|
|
65
67
|
const ext = await ensurePipeline();
|
|
66
68
|
if (!ext) return texts.map(() => null);
|
|
@@ -22,10 +22,7 @@ const FORMATTERS: Record<string, (input: FormatInput) => string> = {
|
|
|
22
22
|
const DEFAULT_FORMATTER = ({ title, body }: FormatInput): string =>
|
|
23
23
|
title ? "\n# " + title + "\n\n" + body + "\n" : "\n" + body + "\n";
|
|
24
24
|
|
|
25
|
-
export function formatBody(
|
|
26
|
-
kind: string,
|
|
27
|
-
input: FormatInput,
|
|
28
|
-
): string {
|
|
25
|
+
export function formatBody(kind: string, input: FormatInput): string {
|
|
29
26
|
const fn = FORMATTERS[kind] || DEFAULT_FORMATTER;
|
|
30
27
|
return fn(input);
|
|
31
28
|
}
|
|
@@ -84,15 +84,17 @@ export function parseEntryFromMarkdown(
|
|
|
84
84
|
kind: string,
|
|
85
85
|
body: string,
|
|
86
86
|
fmMeta: Record<string, unknown>,
|
|
87
|
-
): {
|
|
87
|
+
): {
|
|
88
|
+
title: string | null;
|
|
89
|
+
body: string;
|
|
90
|
+
meta: Record<string, unknown> | null;
|
|
91
|
+
} {
|
|
88
92
|
if (kind === "insight") {
|
|
89
93
|
return { title: null, body, meta: extractCustomMeta(fmMeta) };
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
if (kind === "decision") {
|
|
93
|
-
const titleMatch = body.match(
|
|
94
|
-
/^## Decision\s*\n+([\s\S]*?)(?=\n## |\n*$)/,
|
|
95
|
-
);
|
|
97
|
+
const titleMatch = body.match(/^## Decision\s*\n+([\s\S]*?)(?=\n## |\n*$)/);
|
|
96
98
|
const rationaleMatch = body.match(/## Rationale\s*\n+([\s\S]*?)$/);
|
|
97
99
|
const title = titleMatch ? titleMatch[1].trim() : body.slice(0, 100);
|
|
98
100
|
const rationale = rationaleMatch ? rationaleMatch[1].trim() : body;
|
|
@@ -12,12 +12,27 @@ const EMBED_BATCH_SIZE = 32;
|
|
|
12
12
|
|
|
13
13
|
export async function indexEntry(
|
|
14
14
|
ctx: BaseCtx,
|
|
15
|
-
entry: IndexEntryInput & {
|
|
15
|
+
entry: IndexEntryInput & {
|
|
16
|
+
supersedes?: string[] | null;
|
|
17
|
+
related_to?: string[] | null;
|
|
18
|
+
},
|
|
16
19
|
precomputedEmbedding?: Float32Array | null,
|
|
17
20
|
): Promise<void> {
|
|
18
21
|
const {
|
|
19
|
-
id,
|
|
20
|
-
|
|
22
|
+
id,
|
|
23
|
+
kind,
|
|
24
|
+
category,
|
|
25
|
+
title,
|
|
26
|
+
body,
|
|
27
|
+
meta,
|
|
28
|
+
tags,
|
|
29
|
+
source,
|
|
30
|
+
filePath,
|
|
31
|
+
createdAt,
|
|
32
|
+
identity_key,
|
|
33
|
+
expires_at,
|
|
34
|
+
source_files,
|
|
35
|
+
tier,
|
|
21
36
|
} = entry;
|
|
22
37
|
|
|
23
38
|
if (expires_at && new Date(expires_at) <= new Date()) return;
|
|
@@ -31,13 +46,22 @@ export async function indexEntry(
|
|
|
31
46
|
let wasUpdate = false;
|
|
32
47
|
|
|
33
48
|
if (cat === "entity" && identity_key) {
|
|
34
|
-
const existing = ctx.stmts.getByIdentityKey.get(kind, identity_key) as
|
|
49
|
+
const existing = ctx.stmts.getByIdentityKey.get(kind, identity_key) as
|
|
50
|
+
| Record<string, unknown>
|
|
51
|
+
| undefined;
|
|
35
52
|
if (existing) {
|
|
36
53
|
ctx.stmts.upsertByIdentityKey.run(
|
|
37
|
-
title || null,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
title || null,
|
|
55
|
+
body,
|
|
56
|
+
metaJson,
|
|
57
|
+
tagsJson,
|
|
58
|
+
source || "claude-code",
|
|
59
|
+
cat,
|
|
60
|
+
filePath,
|
|
61
|
+
expires_at || null,
|
|
62
|
+
sourceFilesJson,
|
|
63
|
+
kind,
|
|
64
|
+
identity_key,
|
|
41
65
|
);
|
|
42
66
|
wasUpdate = true;
|
|
43
67
|
}
|
|
@@ -46,20 +70,39 @@ export async function indexEntry(
|
|
|
46
70
|
if (!wasUpdate) {
|
|
47
71
|
try {
|
|
48
72
|
ctx.stmts.insertEntry.run(
|
|
49
|
-
id,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
id,
|
|
74
|
+
kind,
|
|
75
|
+
cat,
|
|
76
|
+
title || null,
|
|
77
|
+
body,
|
|
78
|
+
metaJson,
|
|
79
|
+
tagsJson,
|
|
80
|
+
source || "claude-code",
|
|
81
|
+
filePath,
|
|
82
|
+
identity_key || null,
|
|
83
|
+
expires_at || null,
|
|
84
|
+
createdAt,
|
|
85
|
+
createdAt,
|
|
86
|
+
sourceFilesJson,
|
|
87
|
+
effectiveTier,
|
|
53
88
|
);
|
|
54
89
|
} catch (e) {
|
|
55
90
|
if ((e as Error).message.includes("UNIQUE constraint")) {
|
|
56
91
|
ctx.stmts.updateEntry.run(
|
|
57
|
-
title || null,
|
|
58
|
-
|
|
59
|
-
|
|
92
|
+
title || null,
|
|
93
|
+
body,
|
|
94
|
+
metaJson,
|
|
95
|
+
tagsJson,
|
|
96
|
+
source || "claude-code",
|
|
97
|
+
cat,
|
|
98
|
+
identity_key || null,
|
|
99
|
+
expires_at || null,
|
|
100
|
+
filePath,
|
|
60
101
|
);
|
|
61
102
|
if (sourceFilesJson !== null && ctx.stmts.updateSourceFiles) {
|
|
62
|
-
const entryRow = ctx.stmts.getRowidByPath.get(filePath) as
|
|
103
|
+
const entryRow = ctx.stmts.getRowidByPath.get(filePath) as
|
|
104
|
+
| { rowid: number }
|
|
105
|
+
| undefined;
|
|
63
106
|
if (entryRow) {
|
|
64
107
|
const idRow = ctx.db
|
|
65
108
|
.prepare("SELECT id FROM vault WHERE file_path = ?")
|
|
@@ -76,8 +119,8 @@ export async function indexEntry(
|
|
|
76
119
|
}
|
|
77
120
|
|
|
78
121
|
const rowidResult = wasUpdate
|
|
79
|
-
? ctx.stmts.getRowidByPath.get(filePath) as { rowid: number } | undefined
|
|
80
|
-
: ctx.stmts.getRowid.get(id) as { rowid: number } | undefined;
|
|
122
|
+
? (ctx.stmts.getRowidByPath.get(filePath) as { rowid: number } | undefined)
|
|
123
|
+
: (ctx.stmts.getRowid.get(id) as { rowid: number } | undefined);
|
|
81
124
|
|
|
82
125
|
if (!rowidResult || rowidResult.rowid == null) {
|
|
83
126
|
throw new Error(
|
|
@@ -100,12 +143,18 @@ export async function indexEntry(
|
|
|
100
143
|
try {
|
|
101
144
|
embedding = await ctx.embed([title, body].filter(Boolean).join(" "));
|
|
102
145
|
} catch (embedErr) {
|
|
103
|
-
console.warn(
|
|
146
|
+
console.warn(
|
|
147
|
+
`[context-vault] embed() failed for entry ${id} — skipping vec insert: ${(embedErr as Error).message}`,
|
|
148
|
+
);
|
|
104
149
|
}
|
|
105
150
|
}
|
|
106
151
|
|
|
107
152
|
if (embedding) {
|
|
108
|
-
try {
|
|
153
|
+
try {
|
|
154
|
+
ctx.deleteVec(rowid);
|
|
155
|
+
} catch {
|
|
156
|
+
/* no-op */
|
|
157
|
+
}
|
|
109
158
|
ctx.insertVec(rowid, embedding);
|
|
110
159
|
}
|
|
111
160
|
}
|
|
@@ -120,11 +169,17 @@ export async function pruneExpired(ctx: BaseCtx): Promise<number> {
|
|
|
120
169
|
|
|
121
170
|
for (const row of expired) {
|
|
122
171
|
if (row.file_path) {
|
|
123
|
-
try {
|
|
172
|
+
try {
|
|
173
|
+
unlinkSync(row.file_path);
|
|
174
|
+
} catch {}
|
|
124
175
|
}
|
|
125
|
-
const vRowid = (
|
|
176
|
+
const vRowid = (
|
|
177
|
+
ctx.stmts.getRowid.get(row.id) as { rowid: number } | undefined
|
|
178
|
+
)?.rowid;
|
|
126
179
|
if (vRowid) {
|
|
127
|
-
try {
|
|
180
|
+
try {
|
|
181
|
+
ctx.deleteVec(Number(vRowid));
|
|
182
|
+
} catch {}
|
|
128
183
|
}
|
|
129
184
|
ctx.stmts.deleteEntry.run(row.id);
|
|
130
185
|
}
|
|
@@ -137,7 +192,12 @@ export async function reindex(
|
|
|
137
192
|
opts: { fullSync?: boolean } = {},
|
|
138
193
|
): Promise<ReindexStats> {
|
|
139
194
|
const { fullSync = true } = opts;
|
|
140
|
-
const stats: ReindexStats = {
|
|
195
|
+
const stats: ReindexStats = {
|
|
196
|
+
added: 0,
|
|
197
|
+
updated: 0,
|
|
198
|
+
removed: 0,
|
|
199
|
+
unchanged: 0,
|
|
200
|
+
};
|
|
141
201
|
|
|
142
202
|
if (!existsSync(ctx.config.vaultDir)) return stats;
|
|
143
203
|
|
|
@@ -224,20 +284,32 @@ export async function reindex(
|
|
|
224
284
|
if (!existing) {
|
|
225
285
|
const id = (fmMeta.id as string) || ulid();
|
|
226
286
|
const tagsJson = fmMeta.tags ? JSON.stringify(fmMeta.tags) : null;
|
|
227
|
-
const created =
|
|
287
|
+
const created =
|
|
288
|
+
(fmMeta.created as string) || new Date().toISOString();
|
|
228
289
|
|
|
229
290
|
const result = upsertEntry.run(
|
|
230
|
-
id,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
291
|
+
id,
|
|
292
|
+
kind,
|
|
293
|
+
category,
|
|
294
|
+
parsed.title || null,
|
|
295
|
+
parsed.body,
|
|
296
|
+
metaJson,
|
|
297
|
+
tagsJson,
|
|
298
|
+
(fmMeta.source as string) || "file",
|
|
299
|
+
filePath,
|
|
300
|
+
identity_key,
|
|
301
|
+
expires_at,
|
|
302
|
+
created,
|
|
303
|
+
(fmMeta.updated as string) || created,
|
|
234
304
|
);
|
|
235
305
|
if ((result as { changes: number }).changes > 0) {
|
|
236
306
|
if (relatedToJson && ctx.stmts.updateRelatedTo) {
|
|
237
307
|
ctx.stmts.updateRelatedTo.run(relatedToJson, id);
|
|
238
308
|
}
|
|
239
309
|
if (category !== "event") {
|
|
240
|
-
const rowidResult = ctx.stmts.getRowid.get(id) as
|
|
310
|
+
const rowidResult = ctx.stmts.getRowid.get(id) as
|
|
311
|
+
| { rowid: number }
|
|
312
|
+
| undefined;
|
|
241
313
|
if (rowidResult?.rowid) {
|
|
242
314
|
const embeddingText = [parsed.title, parsed.body]
|
|
243
315
|
.filter(Boolean)
|
|
@@ -254,24 +326,45 @@ export async function reindex(
|
|
|
254
326
|
}
|
|
255
327
|
} else if (fullSync) {
|
|
256
328
|
const tagsJson = fmMeta.tags ? JSON.stringify(fmMeta.tags) : null;
|
|
257
|
-
const titleChanged =
|
|
329
|
+
const titleChanged =
|
|
330
|
+
(parsed.title || null) !== ((existing.title as string) || null);
|
|
258
331
|
const bodyChanged = (existing.body as string) !== parsed.body;
|
|
259
332
|
const tagsChanged = tagsJson !== ((existing.tags as string) || null);
|
|
260
333
|
const metaChanged = metaJson !== ((existing.meta as string) || null);
|
|
261
|
-
const relatedToChanged =
|
|
262
|
-
|
|
263
|
-
|
|
334
|
+
const relatedToChanged =
|
|
335
|
+
relatedToJson !== ((existing.related_to as string) || null);
|
|
336
|
+
|
|
337
|
+
if (
|
|
338
|
+
bodyChanged ||
|
|
339
|
+
titleChanged ||
|
|
340
|
+
tagsChanged ||
|
|
341
|
+
metaChanged ||
|
|
342
|
+
relatedToChanged
|
|
343
|
+
) {
|
|
264
344
|
ctx.stmts.updateEntry.run(
|
|
265
|
-
parsed.title || null,
|
|
266
|
-
|
|
267
|
-
|
|
345
|
+
parsed.title || null,
|
|
346
|
+
parsed.body,
|
|
347
|
+
metaJson,
|
|
348
|
+
tagsJson,
|
|
349
|
+
(fmMeta.source as string) || "file",
|
|
350
|
+
category,
|
|
351
|
+
identity_key,
|
|
352
|
+
expires_at,
|
|
353
|
+
filePath,
|
|
268
354
|
);
|
|
269
355
|
if (relatedToChanged && ctx.stmts.updateRelatedTo) {
|
|
270
|
-
ctx.stmts.updateRelatedTo.run(
|
|
356
|
+
ctx.stmts.updateRelatedTo.run(
|
|
357
|
+
relatedToJson,
|
|
358
|
+
existing.id as string,
|
|
359
|
+
);
|
|
271
360
|
}
|
|
272
361
|
|
|
273
362
|
if ((bodyChanged || titleChanged) && category !== "event") {
|
|
274
|
-
const rowid = (
|
|
363
|
+
const rowid = (
|
|
364
|
+
ctx.stmts.getRowid.get(existing.id as string) as
|
|
365
|
+
| { rowid: number }
|
|
366
|
+
| undefined
|
|
367
|
+
)?.rowid;
|
|
275
368
|
if (rowid) {
|
|
276
369
|
const embeddingText = [parsed.title, parsed.body]
|
|
277
370
|
.filter(Boolean)
|
|
@@ -291,9 +384,15 @@ export async function reindex(
|
|
|
291
384
|
if (fullSync) {
|
|
292
385
|
for (const [dbPath, row] of dbByPath) {
|
|
293
386
|
if (!diskPaths.has(dbPath)) {
|
|
294
|
-
const vRowid = (
|
|
387
|
+
const vRowid = (
|
|
388
|
+
ctx.stmts.getRowid.get(row.id as string) as
|
|
389
|
+
| { rowid: number }
|
|
390
|
+
| undefined
|
|
391
|
+
)?.rowid;
|
|
295
392
|
if (vRowid) {
|
|
296
|
-
try {
|
|
393
|
+
try {
|
|
394
|
+
ctx.deleteVec(vRowid);
|
|
395
|
+
} catch {}
|
|
297
396
|
}
|
|
298
397
|
ctx.stmts.deleteEntry.run(row.id as string);
|
|
299
398
|
stats.removed++;
|
|
@@ -313,7 +412,9 @@ export async function reindex(
|
|
|
313
412
|
.prepare("SELECT id, rowid FROM vault WHERE kind = ?")
|
|
314
413
|
.all(kind) as { id: string; rowid: number }[];
|
|
315
414
|
for (const row of orphaned) {
|
|
316
|
-
try {
|
|
415
|
+
try {
|
|
416
|
+
ctx.deleteVec(row.rowid);
|
|
417
|
+
} catch {}
|
|
317
418
|
ctx.stmts.deleteEntry.run(row.id);
|
|
318
419
|
stats.removed++;
|
|
319
420
|
}
|
|
@@ -329,11 +430,17 @@ export async function reindex(
|
|
|
329
430
|
|
|
330
431
|
for (const row of expired) {
|
|
331
432
|
if (row.file_path) {
|
|
332
|
-
try {
|
|
433
|
+
try {
|
|
434
|
+
unlinkSync(row.file_path);
|
|
435
|
+
} catch {}
|
|
333
436
|
}
|
|
334
|
-
const vRowid = (
|
|
437
|
+
const vRowid = (
|
|
438
|
+
ctx.stmts.getRowid.get(row.id) as { rowid: number } | undefined
|
|
439
|
+
)?.rowid;
|
|
335
440
|
if (vRowid) {
|
|
336
|
-
try {
|
|
441
|
+
try {
|
|
442
|
+
ctx.deleteVec(Number(vRowid));
|
|
443
|
+
} catch {}
|
|
337
444
|
}
|
|
338
445
|
ctx.stmts.deleteEntry.run(row.id);
|
|
339
446
|
stats.removed++;
|
|
@@ -350,7 +457,9 @@ export async function reindex(
|
|
|
350
457
|
const embeddings = await embedBatch(batch.map((e) => e.text));
|
|
351
458
|
for (let j = 0; j < batch.length; j++) {
|
|
352
459
|
if (embeddings[j]) {
|
|
353
|
-
try {
|
|
460
|
+
try {
|
|
461
|
+
ctx.deleteVec(batch[j].rowid);
|
|
462
|
+
} catch {}
|
|
354
463
|
ctx.insertVec(batch[j].rowid, embeddings[j]!);
|
|
355
464
|
}
|
|
356
465
|
}
|