botholomew 0.8.9 → 0.9.4

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/src/db/context.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  import { resolve as resolvePath } from "node:path";
2
+ import {
3
+ type DriveTarget,
4
+ formatDriveRef,
5
+ parseDriveRef,
6
+ } from "../context/drives.ts";
2
7
  import type { DbConnection } from "./connection.ts";
3
8
  import { buildSetClauses, buildWhereClause, sanitizeInt } from "./query.ts";
4
9
  import { isUuid, uuidv7 } from "./uuid.ts";
@@ -10,9 +15,8 @@ export interface ContextItem {
10
15
  content: string | null;
11
16
  mime_type: string;
12
17
  is_textual: boolean;
13
- source_type: "file" | "url";
14
- source_path: string | null;
15
- context_path: string;
18
+ drive: string;
19
+ path: string;
16
20
  indexed_at: Date | null;
17
21
  created_at: Date;
18
22
  updated_at: Date;
@@ -32,9 +36,8 @@ interface ContextItemRow {
32
36
  content_blob: unknown;
33
37
  mime_type: string;
34
38
  is_textual: boolean;
35
- source_type: string;
36
- source_path: string | null;
37
- context_path: string;
39
+ drive: string;
40
+ path: string;
38
41
  indexed_at: string | null;
39
42
  created_at: string;
40
43
  updated_at: string;
@@ -48,9 +51,8 @@ function rowToContextItem(row: ContextItemRow): ContextItem {
48
51
  content: row.content,
49
52
  mime_type: row.mime_type,
50
53
  is_textual: !!row.is_textual,
51
- source_type: row.source_type as "file" | "url",
52
- source_path: row.source_path,
53
- context_path: row.context_path,
54
+ drive: row.drive,
55
+ path: row.path,
54
56
  indexed_at: row.indexed_at ? new Date(row.indexed_at) : null,
55
57
  created_at: new Date(row.created_at),
56
58
  updated_at: new Date(row.updated_at),
@@ -59,12 +61,14 @@ function rowToContextItem(row: ContextItemRow): ContextItem {
59
61
 
60
62
  export class PathConflictError extends Error {
61
63
  existingId: string;
62
- contextPath: string;
63
- constructor(existingId: string, contextPath: string) {
64
- super(`context_path already exists: ${contextPath}`);
64
+ drive: string;
65
+ path: string;
66
+ constructor(existingId: string, target: DriveTarget) {
67
+ super(`Path already exists: ${formatDriveRef(target)}`);
65
68
  this.name = "PathConflictError";
66
69
  this.existingId = existingId;
67
- this.contextPath = contextPath;
70
+ this.drive = target.drive;
71
+ this.path = target.path;
68
72
  }
69
73
  }
70
74
 
@@ -76,17 +80,16 @@ export async function createContextItem(
76
80
  title: string;
77
81
  content?: string;
78
82
  mimeType?: string;
79
- sourceType?: "file" | "url";
80
- sourcePath?: string;
81
- contextPath: string;
83
+ drive: string;
84
+ path: string;
82
85
  description?: string;
83
86
  isTextual?: boolean;
84
87
  },
85
88
  ): Promise<ContextItem> {
86
89
  const id = uuidv7();
87
90
  const row = await db.queryGet<ContextItemRow>(
88
- `INSERT INTO context_items (id, title, description, content, mime_type, is_textual, source_type, source_path, context_path)
89
- VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
91
+ `INSERT INTO context_items (id, title, description, content, mime_type, is_textual, drive, path)
92
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
90
93
  RETURNING *`,
91
94
  id,
92
95
  params.title,
@@ -94,16 +97,15 @@ export async function createContextItem(
94
97
  params.content ?? null,
95
98
  params.mimeType ?? "text/plain",
96
99
  params.isTextual !== false,
97
- params.sourceType ?? "file",
98
- params.sourcePath ?? null,
99
- params.contextPath,
100
+ params.drive,
101
+ params.path,
100
102
  );
101
103
  if (!row) throw new Error("INSERT did not return a row");
102
104
  return rowToContextItem(row);
103
105
  }
104
106
 
105
107
  /**
106
- * Atomic upsert by context_path: updates if the path exists, inserts otherwise.
108
+ * Atomic upsert by (drive, path): updates if the pair exists, inserts otherwise.
107
109
  *
108
110
  * DuckDB implements UPDATE as delete+insert on tables with unique indexes,
109
111
  * which violates foreign keys from the embeddings table. We must delete
@@ -116,28 +118,33 @@ export async function upsertContextItem(
116
118
  title: string;
117
119
  content?: string;
118
120
  mimeType?: string;
119
- sourceType?: "file" | "url";
120
- sourcePath?: string;
121
- contextPath: string;
121
+ drive: string;
122
+ path: string;
122
123
  description?: string;
123
124
  isTextual?: boolean;
124
125
  },
125
126
  ): Promise<ContextItem> {
126
- const existing = await getContextItemByPath(db, params.contextPath);
127
+ const existing = await getContextItem(db, {
128
+ drive: params.drive,
129
+ path: params.path,
130
+ });
127
131
  if (existing) {
128
132
  const updated = await updateContextItem(db, existing.id, {
129
133
  title: params.title,
130
134
  content: params.content,
131
135
  mime_type: params.mimeType,
132
136
  });
133
- if (!updated) throw new Error(`Failed to update: ${params.contextPath}`);
137
+ if (!updated)
138
+ throw new Error(
139
+ `Failed to update: ${formatDriveRef({ drive: params.drive, path: params.path })}`,
140
+ );
134
141
  return updated;
135
142
  }
136
143
  return createContextItem(db, params);
137
144
  }
138
145
 
139
146
  /**
140
- * Strict creator: throws PathConflictError if context_path already exists.
147
+ * Strict creator: throws PathConflictError if (drive, path) already exists.
141
148
  * Use when callers want to surface collisions instead of silently overwriting.
142
149
  */
143
150
  export async function createContextItemStrict(
@@ -146,19 +153,25 @@ export async function createContextItemStrict(
146
153
  title: string;
147
154
  content?: string;
148
155
  mimeType?: string;
149
- sourceType?: "file" | "url";
150
- sourcePath?: string;
151
- contextPath: string;
156
+ drive: string;
157
+ path: string;
152
158
  description?: string;
153
159
  isTextual?: boolean;
154
160
  },
155
161
  ): Promise<ContextItem> {
156
- const existing = await getContextItemByPath(db, params.contextPath);
157
- if (existing) throw new PathConflictError(existing.id, params.contextPath);
162
+ const existing = await getContextItem(db, {
163
+ drive: params.drive,
164
+ path: params.path,
165
+ });
166
+ if (existing)
167
+ throw new PathConflictError(existing.id, {
168
+ drive: params.drive,
169
+ path: params.path,
170
+ });
158
171
  return createContextItem(db, params);
159
172
  }
160
173
 
161
- export async function getContextItem(
174
+ export async function getContextItemById(
162
175
  db: DbConnection,
163
176
  id: string,
164
177
  ): Promise<ContextItem | null> {
@@ -169,51 +182,38 @@ export async function getContextItem(
169
182
  return row ? rowToContextItem(row) : null;
170
183
  }
171
184
 
172
- export async function getContextItemByPath(
173
- db: DbConnection,
174
- contextPath: string,
175
- ): Promise<ContextItem | null> {
176
- const row = await db.queryGet<ContextItemRow>(
177
- "SELECT * FROM context_items WHERE context_path = ?1",
178
- contextPath,
179
- );
180
- return row ? rowToContextItem(row) : null;
181
- }
182
-
183
- export async function getContextItemBySourcePath(
185
+ export async function getContextItem(
184
186
  db: DbConnection,
185
- sourcePath: string,
186
- sourceType: "file" | "url",
187
+ target: DriveTarget,
187
188
  ): Promise<ContextItem | null> {
188
189
  const row = await db.queryGet<ContextItemRow>(
189
- "SELECT * FROM context_items WHERE source_path = ?1 AND source_type = ?2 LIMIT 1",
190
- sourcePath,
191
- sourceType,
190
+ "SELECT * FROM context_items WHERE drive = ?1 AND path = ?2",
191
+ target.drive,
192
+ target.path,
192
193
  );
193
194
  return row ? rowToContextItem(row) : null;
194
195
  }
195
196
 
196
197
  /**
197
- * Look up a context item by UUID, `context_path`, or `source_path`.
198
+ * Look up a context item by UUID, `drive:/path`, or bare filesystem path
199
+ * (resolved against cwd and treated as `disk:/...`).
198
200
  *
199
- * `source_path` fallbacks let users pass the same argument they used for
200
- * `context add` (e.g. a bare `README.md`) to management commands like
201
- * `context refresh` / `context chunks`. Relative file paths are resolved
202
- * against `process.cwd()` to match the absolute `source_path` stored on add.
201
+ * The bare-path fallback lets users pass the same argument they used for
202
+ * `context add` (e.g. a relative `README.md`) to management commands like
203
+ * `context refresh` / `context chunks`.
203
204
  */
204
205
  export async function resolveContextItem(
205
206
  db: DbConnection,
206
- pathOrId: string,
207
+ ref: string,
207
208
  ): Promise<ContextItem | null> {
208
- if (isUuid(pathOrId)) return getContextItem(db, pathOrId);
209
+ if (isUuid(ref)) return getContextItemById(db, ref);
209
210
 
210
- const byContextPath = await getContextItemByPath(db, pathOrId);
211
- if (byContextPath) return byContextPath;
211
+ const parsed = parseDriveRef(ref);
212
+ if (parsed) return getContextItem(db, parsed);
212
213
 
213
- const byUrl = await getContextItemBySourcePath(db, pathOrId, "url");
214
- if (byUrl) return byUrl;
215
-
216
- return getContextItemBySourcePath(db, resolvePath(pathOrId), "file");
214
+ // Bare filesystem path try the `disk` drive with an absolute path.
215
+ const absolute = resolvePath(ref);
216
+ return getContextItem(db, { drive: "disk", path: absolute });
217
217
  }
218
218
 
219
219
  /**
@@ -221,31 +221,79 @@ export async function resolveContextItem(
221
221
  */
222
222
  export async function resolveContextItemOrThrow(
223
223
  db: DbConnection,
224
- pathOrId: string,
224
+ ref: string,
225
225
  ): Promise<ContextItem> {
226
- const item = await resolveContextItem(db, pathOrId);
227
- if (!item) throw new Error(`Not found: ${pathOrId}`);
226
+ const item = await resolveContextItem(db, ref);
227
+ if (!item) throw new Error(`Not found: ${ref}`);
228
228
  return item;
229
229
  }
230
230
 
231
+ export interface NearbyContextPaths {
232
+ /** Directory we found neighbours under (may be an ancestor if the direct parent was empty). */
233
+ parent: string;
234
+ /** Exact `drive:/path` values of the parent's immediate children. */
235
+ siblings: string[];
236
+ /** True if we walked up from the requested path's direct parent to find a populated ancestor. */
237
+ walkedUp: boolean;
238
+ }
239
+
240
+ /**
241
+ * Find context items near a requested path to power "did you mean?" suggestions
242
+ * when a lookup misses. Returns up to `limit` immediate neighbours within the
243
+ * same drive; if the parent has no rows, walks up until it finds a populated
244
+ * ancestor (or hits root).
245
+ */
246
+ export async function findNearbyContextPaths(
247
+ db: DbConnection,
248
+ drive: string,
249
+ requestedPath: string,
250
+ limit = 5,
251
+ ): Promise<NearbyContextPaths> {
252
+ let parent = parentDir(requestedPath);
253
+ let walkedUp = false;
254
+ while (true) {
255
+ const items = await listContextItemsByPrefix(db, drive, parent, {
256
+ recursive: false,
257
+ limit,
258
+ });
259
+ if (items.length > 0 || parent === "/") {
260
+ return {
261
+ parent: `${drive}:${parent}`,
262
+ siblings: items.map((i) => formatDriveRef(i)),
263
+ walkedUp,
264
+ };
265
+ }
266
+ parent = parentDir(parent);
267
+ walkedUp = true;
268
+ }
269
+ }
270
+
271
+ function parentDir(p: string): string {
272
+ if (!p || p === "/") return "/";
273
+ const trimmed = p.endsWith("/") && p.length > 1 ? p.slice(0, -1) : p;
274
+ const idx = trimmed.lastIndexOf("/");
275
+ if (idx <= 0) return "/";
276
+ return trimmed.slice(0, idx);
277
+ }
278
+
231
279
  export async function listContextItems(
232
280
  db: DbConnection,
233
281
  filters?: {
234
- contextPath?: string;
282
+ drive?: string;
235
283
  mimeType?: string;
236
284
  limit?: number;
237
285
  offset?: number;
238
286
  },
239
287
  ): Promise<ContextItem[]> {
240
288
  const { where, params } = buildWhereClause([
241
- ["context_path", filters?.contextPath],
289
+ ["drive", filters?.drive],
242
290
  ["mime_type", filters?.mimeType],
243
291
  ]);
244
292
  const limit = filters?.limit ? `LIMIT ${sanitizeInt(filters.limit)}` : "";
245
293
  const offset = filters?.offset ? `OFFSET ${sanitizeInt(filters.offset)}` : "";
246
294
 
247
295
  const rows = await db.queryAll<ContextItemRow>(
248
- `SELECT * FROM context_items ${where} ORDER BY context_path ASC ${limit} ${offset}`,
296
+ `SELECT * FROM context_items ${where} ORDER BY drive ASC, path ASC, id ASC ${limit} ${offset}`,
249
297
  ...params,
250
298
  );
251
299
  return rows.map(rowToContextItem);
@@ -253,6 +301,7 @@ export async function listContextItems(
253
301
 
254
302
  export async function listContextItemsByPrefix(
255
303
  db: DbConnection,
304
+ drive: string,
256
305
  prefix: string,
257
306
  opts?: { recursive?: boolean; limit?: number; offset?: number },
258
307
  ): Promise<ContextItem[]> {
@@ -265,17 +314,18 @@ export async function listContextItemsByPrefix(
265
314
  if (opts?.recursive) {
266
315
  rows = await db.queryAll<ContextItemRow>(
267
316
  `SELECT * FROM context_items
268
- WHERE context_path LIKE ?1
269
- ORDER BY context_path ASC ${limit} ${offset}`,
317
+ WHERE drive = ?1 AND path LIKE ?2
318
+ ORDER BY path ASC, id ASC ${limit} ${offset}`,
319
+ drive,
270
320
  `${normalizedPrefix}%`,
271
321
  );
272
322
  } else {
273
- // Only immediate children: match prefix but no further slashes
274
323
  rows = await db.queryAll<ContextItemRow>(
275
324
  `SELECT * FROM context_items
276
- WHERE context_path LIKE ?1
277
- AND context_path NOT LIKE ?2
278
- ORDER BY context_path ASC ${limit} ${offset}`,
325
+ WHERE drive = ?1 AND path LIKE ?2
326
+ AND path NOT LIKE ?3
327
+ ORDER BY path ASC, id ASC ${limit} ${offset}`,
328
+ drive,
279
329
  `${normalizedPrefix}%`,
280
330
  `${normalizedPrefix}%/%`,
281
331
  );
@@ -286,17 +336,19 @@ export async function listContextItemsByPrefix(
286
336
 
287
337
  export async function contextPathExists(
288
338
  db: DbConnection,
289
- contextPath: string,
339
+ target: DriveTarget,
290
340
  ): Promise<boolean> {
291
341
  const row = await db.queryGet(
292
- "SELECT 1 AS found FROM context_items WHERE context_path = ?1 LIMIT 1",
293
- contextPath,
342
+ "SELECT 1 AS found FROM context_items WHERE drive = ?1 AND path = ?2 LIMIT 1",
343
+ target.drive,
344
+ target.path,
294
345
  );
295
346
  return row != null;
296
347
  }
297
348
 
298
349
  export async function countContextItemsByPrefix(
299
350
  db: DbConnection,
351
+ drive: string,
300
352
  prefix: string,
301
353
  opts?: { recursive?: boolean },
302
354
  ): Promise<number> {
@@ -304,13 +356,15 @@ export async function countContextItemsByPrefix(
304
356
  let row: { cnt: number } | null;
305
357
  if (opts?.recursive !== false) {
306
358
  row = await db.queryGet<{ cnt: number }>(
307
- `SELECT COUNT(*) AS cnt FROM context_items WHERE context_path LIKE ?1`,
359
+ `SELECT COUNT(*) AS cnt FROM context_items WHERE drive = ?1 AND path LIKE ?2`,
360
+ drive,
308
361
  `${normalizedPrefix}%`,
309
362
  );
310
363
  } else {
311
364
  row = await db.queryGet<{ cnt: number }>(
312
365
  `SELECT COUNT(*) AS cnt FROM context_items
313
- WHERE context_path LIKE ?1 AND context_path NOT LIKE ?2`,
366
+ WHERE drive = ?1 AND path LIKE ?2 AND path NOT LIKE ?3`,
367
+ drive,
314
368
  `${normalizedPrefix}%`,
315
369
  `${normalizedPrefix}%/%`,
316
370
  );
@@ -320,6 +374,7 @@ export async function countContextItemsByPrefix(
320
374
 
321
375
  export async function getDistinctDirectories(
322
376
  db: DbConnection,
377
+ drive: string,
323
378
  prefix?: string,
324
379
  ): Promise<string[]> {
325
380
  const normalizedPrefix = prefix
@@ -332,20 +387,35 @@ export async function getDistinctDirectories(
332
387
  const rows = await db.queryAll<{ dir: string }>(
333
388
  `SELECT DISTINCT
334
389
  ?1 || CASE
335
- WHEN strpos(substr(context_path, length(?1) + 1), '/') > 0
336
- THEN substr(substr(context_path, length(?1) + 1), 1, strpos(substr(context_path, length(?1) + 1), '/') - 1)
337
- ELSE substr(context_path, length(?1) + 1)
390
+ WHEN strpos(substr(path, length(?1) + 1), '/') > 0
391
+ THEN substr(substr(path, length(?1) + 1), 1, strpos(substr(path, length(?1) + 1), '/') - 1)
392
+ ELSE substr(path, length(?1) + 1)
338
393
  END AS dir
339
394
  FROM context_items
340
- WHERE context_path LIKE ?2
395
+ WHERE drive = ?2 AND path LIKE ?3
341
396
  ORDER BY dir ASC`,
342
397
  normalizedPrefix,
398
+ drive,
343
399
  `${normalizedPrefix}%/%`,
344
400
  );
345
401
 
346
402
  return rows.map((row) => row.dir);
347
403
  }
348
404
 
405
+ export interface DriveSummary {
406
+ drive: string;
407
+ count: number;
408
+ }
409
+
410
+ export async function listDriveSummaries(
411
+ db: DbConnection,
412
+ ): Promise<DriveSummary[]> {
413
+ const rows = await db.queryAll<{ drive: string; cnt: number }>(
414
+ "SELECT drive, COUNT(*) AS cnt FROM context_items GROUP BY drive ORDER BY drive ASC",
415
+ );
416
+ return rows.map((r) => ({ drive: r.drive, count: Number(r.cnt) }));
417
+ }
418
+
349
419
  // --- Mutations ---
350
420
 
351
421
  // `UPDATE context_items ... RETURNING *` can crash @duckdb/node-api via a C++
@@ -375,32 +445,34 @@ export async function updateContextItem(
375
445
  WHERE id = ?${params.length}`,
376
446
  ...params,
377
447
  );
378
- return getContextItem(db, id);
448
+ return getContextItemById(db, id);
379
449
  }
380
450
 
381
451
  export async function updateContextItemContent(
382
452
  db: DbConnection,
383
- contextPath: string,
453
+ target: DriveTarget,
384
454
  content: string,
385
455
  ): Promise<ContextItem | null> {
386
456
  await db.queryRun(
387
457
  `UPDATE context_items
388
458
  SET content = ?1, updated_at = current_timestamp::VARCHAR
389
- WHERE context_path = ?2`,
459
+ WHERE drive = ?2 AND path = ?3`,
390
460
  content,
391
- contextPath,
461
+ target.drive,
462
+ target.path,
392
463
  );
393
- return getContextItemByPath(db, contextPath);
464
+ return getContextItem(db, target);
394
465
  }
395
466
 
396
467
  export async function applyPatchesToContextItem(
397
468
  db: DbConnection,
398
- contextPath: string,
469
+ target: DriveTarget,
399
470
  patches: Patch[],
400
471
  ): Promise<{ item: ContextItem; applied: number }> {
401
- const item = await getContextItemByPath(db, contextPath);
402
- if (!item) throw new Error(`Not found: ${contextPath}`);
403
- if (item.content == null) throw new Error(`No text content: ${contextPath}`);
472
+ const item = await getContextItem(db, target);
473
+ if (!item) throw new Error(`Not found: ${formatDriveRef(target)}`);
474
+ if (item.content == null)
475
+ throw new Error(`No text content: ${formatDriveRef(target)}`);
404
476
 
405
477
  const lines = item.content.split("\n");
406
478
 
@@ -421,45 +493,47 @@ export async function applyPatchesToContextItem(
421
493
  }
422
494
 
423
495
  const newContent = lines.join("\n");
424
- const updated = await updateContextItemContent(db, contextPath, newContent);
425
- if (!updated) throw new Error(`Failed to update: ${contextPath}`);
496
+ const updated = await updateContextItemContent(db, target, newContent);
497
+ if (!updated) throw new Error(`Failed to update: ${formatDriveRef(target)}`);
426
498
  return { item: updated, applied: patches.length };
427
499
  }
428
500
 
429
501
  export async function copyContextItem(
430
502
  db: DbConnection,
431
- srcPath: string,
432
- dstPath: string,
503
+ src: DriveTarget,
504
+ dst: DriveTarget,
433
505
  ): Promise<ContextItem> {
434
- const src = await getContextItemByPath(db, srcPath);
435
- if (!src) throw new Error(`Not found: ${srcPath}`);
506
+ const source = await getContextItem(db, src);
507
+ if (!source) throw new Error(`Not found: ${formatDriveRef(src)}`);
436
508
 
437
509
  return createContextItem(db, {
438
- title: src.title,
439
- description: src.description,
440
- content: src.content ?? undefined,
441
- mimeType: src.mime_type,
442
- sourcePath: src.source_path ?? undefined,
443
- contextPath: dstPath,
444
- isTextual: src.is_textual,
510
+ title: source.title,
511
+ description: source.description,
512
+ content: source.content ?? undefined,
513
+ mimeType: source.mime_type,
514
+ drive: dst.drive,
515
+ path: dst.path,
516
+ isTextual: source.is_textual,
445
517
  });
446
518
  }
447
519
 
448
520
  export async function moveContextItem(
449
521
  db: DbConnection,
450
- oldPath: string,
451
- newPath: string,
522
+ src: DriveTarget,
523
+ dst: DriveTarget,
452
524
  ): Promise<void> {
453
525
  const row = await db.queryGet(
454
526
  `UPDATE context_items
455
- SET context_path = ?1, updated_at = current_timestamp::VARCHAR
456
- WHERE context_path = ?2
527
+ SET drive = ?1, path = ?2, updated_at = current_timestamp::VARCHAR
528
+ WHERE drive = ?3 AND path = ?4
457
529
  RETURNING id`,
458
- newPath,
459
- oldPath,
530
+ dst.drive,
531
+ dst.path,
532
+ src.drive,
533
+ src.path,
460
534
  );
461
535
  if (!row) {
462
- throw new Error(`Not found: ${oldPath}`);
536
+ throw new Error(`Not found: ${formatDriveRef(src)}`);
463
537
  }
464
538
  }
465
539
 
@@ -480,10 +554,9 @@ export async function deleteContextItem(
480
554
 
481
555
  export async function deleteContextItemByPath(
482
556
  db: DbConnection,
483
- contextPath: string,
557
+ target: DriveTarget,
484
558
  ): Promise<boolean> {
485
- // Get ID first so we can cascade embeddings
486
- const item = await getContextItemByPath(db, contextPath);
559
+ const item = await getContextItem(db, target);
487
560
  if (!item) return false;
488
561
  return deleteContextItem(db, item.id);
489
562
  }
@@ -501,6 +574,7 @@ export async function deleteAllContextItems(
501
574
 
502
575
  export async function deleteContextItemsByPrefix(
503
576
  db: DbConnection,
577
+ drive: string,
504
578
  prefix: string,
505
579
  ): Promise<number> {
506
580
  const normalizedPrefix = prefix.endsWith("/") ? prefix : `${prefix}/`;
@@ -510,15 +584,17 @@ export async function deleteContextItemsByPrefix(
510
584
  `DELETE FROM embeddings
511
585
  WHERE context_item_id IN (
512
586
  SELECT id FROM context_items
513
- WHERE context_path LIKE ?1
587
+ WHERE drive = ?1 AND path LIKE ?2
514
588
  )`,
589
+ drive,
515
590
  `${normalizedPrefix}%`,
516
591
  );
517
592
 
518
593
  const rows = await db.queryAll(
519
594
  `DELETE FROM context_items
520
- WHERE context_path LIKE ?1
595
+ WHERE drive = ?1 AND path LIKE ?2
521
596
  RETURNING id`,
597
+ drive,
522
598
  `${normalizedPrefix}%`,
523
599
  );
524
600
  return rows.length;