opencode-rag-plugin 1.17.3 → 1.18.0
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/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/init-helpers.js +4 -0
- package/dist/cli/commands/quirk.d.ts +10 -0
- package/dist/cli/commands/quirk.js +141 -0
- package/dist/cli/index.js +2 -1
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.js +20 -1
- package/dist/core/interfaces.d.ts +16 -1
- package/dist/opencode/tools.d.ts +38 -0
- package/dist/opencode/tools.js +127 -0
- package/dist/plugin.js +135 -1
- package/dist/quirks/monitor.d.ts +5 -0
- package/dist/quirks/monitor.js +23 -0
- package/dist/quirks/quirk-store.d.ts +25 -0
- package/dist/quirks/quirk-store.js +217 -0
- package/dist/quirks/types.d.ts +20 -0
- package/dist/quirks/types.js +2 -0
- package/dist/retriever/keyword-index.js +16 -0
- package/dist/vectorstore/lancedb.d.ts +11 -10
- package/dist/vectorstore/lancedb.js +146 -16
- package/dist/vectorstore/memory.js +3 -0
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import fs from "node:fs/promises";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { normalizeFilePath, manifestPathFor } from "../core/manifest.js";
|
|
8
8
|
const TABLE_NAME = "chunks";
|
|
9
|
-
const QUERY_COLUMNS = ["id", "content", "description", "filePath", "startLine", "endLine", "language"];
|
|
9
|
+
const QUERY_COLUMNS = ["id", "content", "description", "filePath", "startLine", "endLine", "language", "kind", "quirkType", "tags"];
|
|
10
10
|
/**
|
|
11
11
|
* L2-normalize a vector to unit length. Cosine models require unit vectors
|
|
12
12
|
* for the dot product to equal cosine similarity.
|
|
@@ -109,12 +109,14 @@ export class LanceDbStore {
|
|
|
109
109
|
if (tableNames.includes(TABLE_NAME)) {
|
|
110
110
|
this.table = await db.openTable(TABLE_NAME);
|
|
111
111
|
if (await this.tableHasDescriptionColumn()) {
|
|
112
|
+
await this.migrateNewColumns();
|
|
112
113
|
return this.table;
|
|
113
114
|
}
|
|
114
115
|
// Schema missing 'description' column -- try to add it gracefully first.
|
|
115
116
|
try {
|
|
116
117
|
await this.table.addColumns([{ name: "description", valueSql: "''" }]);
|
|
117
118
|
console.warn("[lancedb] Added missing 'description' column to existing table.");
|
|
119
|
+
await this.migrateNewColumns();
|
|
118
120
|
return this.table;
|
|
119
121
|
}
|
|
120
122
|
catch {
|
|
@@ -150,6 +152,9 @@ export class LanceDbStore {
|
|
|
150
152
|
startLine: 0,
|
|
151
153
|
endLine: 0,
|
|
152
154
|
language: "",
|
|
155
|
+
kind: "",
|
|
156
|
+
quirkType: "",
|
|
157
|
+
tags: "",
|
|
153
158
|
};
|
|
154
159
|
this.table = await db.createTable({
|
|
155
160
|
name: TABLE_NAME,
|
|
@@ -175,6 +180,58 @@ export class LanceDbStore {
|
|
|
175
180
|
return false;
|
|
176
181
|
}
|
|
177
182
|
}
|
|
183
|
+
async hasColumn(name) {
|
|
184
|
+
try {
|
|
185
|
+
const schema = await this.table.schema();
|
|
186
|
+
return schema.fields.some((f) => f.name === name);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Add kind/quirkType/tags columns if missing from an existing table. */
|
|
193
|
+
async migrateNewColumns() {
|
|
194
|
+
const missing = [];
|
|
195
|
+
for (const col of ["kind", "quirkType", "tags"]) {
|
|
196
|
+
if (!(await this.hasColumn(col))) {
|
|
197
|
+
missing.push({ name: col, valueSql: "''" });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (missing.length > 0) {
|
|
201
|
+
try {
|
|
202
|
+
await this.table.addColumns(missing);
|
|
203
|
+
console.warn(`[lancedb] Added missing columns: ${missing.map((c) => c.name).join(", ")}`);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
console.warn("[lancedb] Could not auto-add missing columns. " +
|
|
207
|
+
"Run 'opencode-rag index --force' to rebuild the index with the correct schema.");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
await this.ensureColumnsNullable(["kind", "quirkType", "tags"]);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Ensure that kind/quirkType/tags columns are nullable so that queries
|
|
215
|
+
* with WHERE clauses on these columns do not panic on old fragments
|
|
216
|
+
* written before the columns existed.
|
|
217
|
+
*/
|
|
218
|
+
async ensureColumnsNullable(columns) {
|
|
219
|
+
for (const col of columns) {
|
|
220
|
+
if (!(await this.hasColumn(col)))
|
|
221
|
+
continue;
|
|
222
|
+
try {
|
|
223
|
+
const schema = await this.table.schema();
|
|
224
|
+
const field = schema.fields.find((f) => f.name === col);
|
|
225
|
+
if (field && field.nullable === false) {
|
|
226
|
+
await this.table.alterColumns([{ path: col, nullable: true }]);
|
|
227
|
+
console.warn(`[lancedb] Made column '${col}' nullable to avoid null-fragment panics.`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
console.warn(`[lancedb] Could not alter nullability of column '${col}'.`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
178
235
|
/**
|
|
179
236
|
* Store chunks in the LanceDB table. New rows are inserted first, then
|
|
180
237
|
* any old rows at the same (filePath, startLine) with different IDs are
|
|
@@ -214,6 +271,9 @@ export class LanceDbStore {
|
|
|
214
271
|
startLine: c.metadata.startLine,
|
|
215
272
|
endLine: c.metadata.endLine,
|
|
216
273
|
language: c.metadata.language,
|
|
274
|
+
kind: c.metadata.kind ?? "",
|
|
275
|
+
quirkType: c.metadata.quirkType ?? "",
|
|
276
|
+
tags: c.metadata.tags ? JSON.stringify(c.metadata.tags) : "",
|
|
217
277
|
}));
|
|
218
278
|
if (rows.length === 0)
|
|
219
279
|
return;
|
|
@@ -292,6 +352,15 @@ export class LanceDbStore {
|
|
|
292
352
|
}
|
|
293
353
|
}
|
|
294
354
|
rowToSearchResult(row) {
|
|
355
|
+
let tags;
|
|
356
|
+
try {
|
|
357
|
+
const raw = row.tags;
|
|
358
|
+
if (raw)
|
|
359
|
+
tags = JSON.parse(raw);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
tags = undefined;
|
|
363
|
+
}
|
|
295
364
|
return {
|
|
296
365
|
score: Math.min(1, Math.max(0, 1 - (row._distance ?? 0) / 2)),
|
|
297
366
|
chunk: {
|
|
@@ -303,6 +372,9 @@ export class LanceDbStore {
|
|
|
303
372
|
startLine: row.startLine,
|
|
304
373
|
endLine: row.endLine,
|
|
305
374
|
language: row.language,
|
|
375
|
+
kind: row.kind || undefined,
|
|
376
|
+
quirkType: row.quirkType || undefined,
|
|
377
|
+
tags,
|
|
306
378
|
},
|
|
307
379
|
},
|
|
308
380
|
};
|
|
@@ -346,17 +418,31 @@ export class LanceDbStore {
|
|
|
346
418
|
.where(`filePath = '${normalizedPath}'`)
|
|
347
419
|
.toArray();
|
|
348
420
|
return rows
|
|
349
|
-
.map((row) =>
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
421
|
+
.map((row) => {
|
|
422
|
+
let tags;
|
|
423
|
+
try {
|
|
424
|
+
const raw = row.tags;
|
|
425
|
+
if (raw)
|
|
426
|
+
tags = JSON.parse(raw);
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
tags = undefined;
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
id: row.id,
|
|
433
|
+
content: row.content,
|
|
434
|
+
description: row.description ?? "",
|
|
435
|
+
metadata: {
|
|
436
|
+
filePath: row.filePath,
|
|
437
|
+
startLine: row.startLine,
|
|
438
|
+
endLine: row.endLine,
|
|
439
|
+
language: row.language,
|
|
440
|
+
kind: row.kind || undefined,
|
|
441
|
+
quirkType: row.quirkType || undefined,
|
|
442
|
+
tags,
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
})
|
|
360
446
|
.sort((a, b) => a.metadata.startLine - b.metadata.startLine);
|
|
361
447
|
}
|
|
362
448
|
/**
|
|
@@ -380,6 +466,9 @@ export class LanceDbStore {
|
|
|
380
466
|
endLine: row.endLine,
|
|
381
467
|
content: row.content,
|
|
382
468
|
description: row.description ?? "",
|
|
469
|
+
kind: row.kind ?? "",
|
|
470
|
+
quirkType: row.quirkType ?? "",
|
|
471
|
+
tags: row.tags ?? "",
|
|
383
472
|
}));
|
|
384
473
|
}
|
|
385
474
|
/**
|
|
@@ -398,11 +487,26 @@ export class LanceDbStore {
|
|
|
398
487
|
const count = await table.countRows();
|
|
399
488
|
if (count === 0)
|
|
400
489
|
return [];
|
|
401
|
-
let query = table.vectorSearch(l2Normalize(embedding)).distanceType("cosine");
|
|
402
490
|
const whereClause = buildWhereClause(filter);
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
491
|
+
let results;
|
|
492
|
+
try {
|
|
493
|
+
let query = table.vectorSearch(l2Normalize(embedding)).distanceType("cosine");
|
|
494
|
+
if (whereClause)
|
|
495
|
+
query = query.where(whereClause);
|
|
496
|
+
results = await query.limit(topK).toArray();
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
if (err instanceof Error && err.message.includes("non-nullable") && filter) {
|
|
500
|
+
const fallbackQuery = table.vectorSearch(l2Normalize(embedding)).distanceType("cosine");
|
|
501
|
+
const rawResults = await fallbackQuery.limit(topK * 5).toArray();
|
|
502
|
+
return rawResults
|
|
503
|
+
.map((row) => this.rowToSearchResult(row))
|
|
504
|
+
.filter((r) => r.chunk.id !== "__seed__")
|
|
505
|
+
.filter((r) => matchesFilterLocal(r.chunk, filter))
|
|
506
|
+
.slice(0, topK);
|
|
507
|
+
}
|
|
508
|
+
throw err;
|
|
509
|
+
}
|
|
406
510
|
return results
|
|
407
511
|
.map((row) => this.rowToSearchResult(row))
|
|
408
512
|
.filter((r) => r.chunk.id !== "__seed__");
|
|
@@ -641,6 +745,10 @@ function buildWhereClause(filter) {
|
|
|
641
745
|
const langs = filter.languages.map((l) => `'${l.replace(/'/g, "''")}'`).join(",");
|
|
642
746
|
parts.push(`language IN (${langs})`);
|
|
643
747
|
}
|
|
748
|
+
if (filter.kinds?.length) {
|
|
749
|
+
const kinds = filter.kinds.map((k) => `'${k.replace(/'/g, "''")}'`).join(",");
|
|
750
|
+
parts.push(`kind IN (${kinds})`);
|
|
751
|
+
}
|
|
644
752
|
if (filter.pathPatterns?.length) {
|
|
645
753
|
const likes = filter.pathPatterns.map((p) => {
|
|
646
754
|
const escaped = p.replace(/'/g, "''");
|
|
@@ -654,4 +762,26 @@ function buildWhereClause(filter) {
|
|
|
654
762
|
}
|
|
655
763
|
return parts.length ? parts.join(" AND ") : undefined;
|
|
656
764
|
}
|
|
765
|
+
/** Client-side metadata filter for use as a fallback when LanceDB WHERE panics. */
|
|
766
|
+
function matchesFilterLocal(chunk, filter) {
|
|
767
|
+
if (!filter)
|
|
768
|
+
return true;
|
|
769
|
+
if (filter.languages?.length && !filter.languages.includes(chunk.metadata.language))
|
|
770
|
+
return false;
|
|
771
|
+
if (filter.kinds?.length && !filter.kinds.includes(chunk.metadata.kind ?? ""))
|
|
772
|
+
return false;
|
|
773
|
+
if (filter.pathPatterns?.length) {
|
|
774
|
+
return filter.pathPatterns.some((p) => globMatchLocal(p, chunk.metadata.filePath));
|
|
775
|
+
}
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
function globMatchLocal(pattern, filePath) {
|
|
779
|
+
const GLOBSTAR = "\x00GS\x00";
|
|
780
|
+
let re = pattern.replace(/\*\*/g, GLOBSTAR);
|
|
781
|
+
re = re.replace(/([.+^${}()|[\]\\])/g, "\\$1");
|
|
782
|
+
re = re.replace(new RegExp(GLOBSTAR, "g"), ".*");
|
|
783
|
+
re = re.replace(/\*/g, "[^/]*");
|
|
784
|
+
re = re.replace(/\?/g, ".");
|
|
785
|
+
return new RegExp("^" + re + "$").test(filePath);
|
|
786
|
+
}
|
|
657
787
|
//# sourceMappingURL=lancedb.js.map
|
|
@@ -45,6 +45,9 @@ export class InMemoryVectorStore {
|
|
|
45
45
|
endLine: c.metadata.endLine,
|
|
46
46
|
content: c.content,
|
|
47
47
|
description: c.description ?? "",
|
|
48
|
+
kind: c.metadata.kind ?? "",
|
|
49
|
+
quirkType: c.metadata.quirkType ?? "",
|
|
50
|
+
tags: c.metadata.tags ? JSON.stringify(c.metadata.tags) : "",
|
|
48
51
|
}));
|
|
49
52
|
}
|
|
50
53
|
async listFiles() {
|