opencode-rag-plugin 1.15.1 → 1.17.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/ReadMe.md +4 -6
- package/dist/api.js +44 -24
- package/dist/chunker/base.d.ts +34 -3
- package/dist/chunker/base.js +70 -21
- package/dist/chunker/factory.d.ts +4 -1
- package/dist/chunker/factory.js +12 -1
- package/dist/chunker/grammar.js +3 -0
- package/dist/chunker/image.js +8 -8
- package/dist/chunker/pdf.js +11 -13
- package/dist/chunker/xml.d.ts +2 -0
- package/dist/chunker/xml.js +2 -0
- package/dist/cli/commands/index-command.js +0 -4
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/init-helpers.d.ts +12 -0
- package/dist/cli/commands/init-helpers.js +84 -2
- package/dist/cli/commands/init.js +20 -2
- package/dist/cli/commands/query.js +1 -0
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.js +113 -0
- package/dist/cli/commands/status.js +64 -2
- package/dist/cli/index.js +2 -2
- package/dist/content/image.js +24 -3
- package/dist/content/reader.d.ts +6 -1
- package/dist/content/reader.js +49 -4
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.js +34 -4
- package/dist/core/desc-cache.d.ts +31 -0
- package/dist/core/desc-cache.js +124 -0
- package/dist/core/interfaces.d.ts +58 -3
- package/dist/core/manifest.d.ts +24 -1
- package/dist/core/manifest.js +34 -3
- package/dist/core/resolve-api-key.js +4 -2
- package/dist/core/runtime-overrides.js +9 -7
- package/dist/core/setup-runtime.d.ts +23 -0
- package/dist/core/setup-runtime.js +183 -0
- package/dist/core/version-check.d.ts +9 -0
- package/dist/core/version-check.js +49 -0
- package/dist/describer/anthropic.d.ts +2 -2
- package/dist/describer/anthropic.js +5 -7
- package/dist/describer/describer.d.ts +2 -2
- package/dist/describer/describer.js +6 -8
- package/dist/describer/gemini.d.ts +2 -2
- package/dist/describer/gemini.js +5 -7
- package/dist/embedder/factory.d.ts +3 -1
- package/dist/embedder/factory.js +7 -1
- package/dist/embedder/health.js +4 -0
- package/dist/embedder/http.d.ts +1 -1
- package/dist/embedder/http.js +74 -43
- package/dist/eval/compare-merge.d.ts +10 -0
- package/dist/eval/compare-merge.js +537 -0
- package/dist/eval/compare-rankings.d.ts +10 -0
- package/dist/eval/compare-rankings.js +245 -0
- package/dist/eval/dump-descriptions.d.ts +7 -0
- package/dist/eval/dump-descriptions.js +58 -0
- package/dist/eval/fast-index.d.ts +8 -0
- package/dist/eval/fast-index.js +283 -0
- package/dist/eval/run-branch-compare.d.ts +7 -0
- package/dist/eval/run-branch-compare.js +220 -0
- package/dist/eval/run-token-test.js +1 -0
- package/dist/eval/test-kw.d.ts +1 -0
- package/dist/eval/test-kw.js +22 -0
- package/dist/eval/update-descriptions.d.ts +7 -0
- package/dist/eval/update-descriptions.js +84 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/indexer/embed-stage.js +2 -1
- package/dist/indexer/git-diff.js +21 -9
- package/dist/indexer/pipeline.d.ts +6 -0
- package/dist/indexer/pipeline.js +290 -37
- package/dist/indexer/watch.js +1 -3
- package/dist/indexer/worker.d.ts +15 -2
- package/dist/indexer/worker.js +25 -12
- package/dist/mcp/handlers.d.ts +9 -0
- package/dist/mcp/handlers.js +23 -6
- package/dist/mcp/server.js +2 -0
- package/dist/opencode/create-read-tool.d.ts +2 -0
- package/dist/opencode/create-read-tool.js +8 -2
- package/dist/opencode/read-fallback.d.ts +1 -5
- package/dist/opencode/read-fallback.js +1 -18
- package/dist/opencode/read-format.js +5 -3
- package/dist/opencode/tools.js +5 -7
- package/dist/plugin.js +141 -57
- package/dist/retriever/keyword-index.d.ts +3 -2
- package/dist/retriever/keyword-index.js +25 -1
- package/dist/retriever/retriever.d.ts +4 -1
- package/dist/retriever/retriever.js +34 -56
- package/dist/vectorstore/lancedb.d.ts +35 -4
- package/dist/vectorstore/lancedb.js +146 -23
- package/dist/vectorstore/memory.d.ts +6 -1
- package/dist/vectorstore/memory.js +58 -0
- package/dist/watcher.js +3 -0
- package/dist/web/api.js +10 -2
- package/dist/web/server.js +18 -3
- package/package.json +8 -9
- package/scripts/postinstall-setup.js +82 -0
- package/dist/api.js.map +0 -1
- package/dist/chunker/base.js.map +0 -1
- package/dist/chunker/bash.js.map +0 -1
- package/dist/chunker/c.js.map +0 -1
- package/dist/chunker/cpp.js.map +0 -1
- package/dist/chunker/csharp.js.map +0 -1
- package/dist/chunker/css.js.map +0 -1
- package/dist/chunker/doc.js.map +0 -1
- package/dist/chunker/dockerfile.js.map +0 -1
- package/dist/chunker/docx.js.map +0 -1
- package/dist/chunker/excel.js.map +0 -1
- package/dist/chunker/factory.js.map +0 -1
- package/dist/chunker/fallback.js.map +0 -1
- package/dist/chunker/go.js.map +0 -1
- package/dist/chunker/grammar.js.map +0 -1
- package/dist/chunker/html.js.map +0 -1
- package/dist/chunker/image.js.map +0 -1
- package/dist/chunker/ini.js.map +0 -1
- package/dist/chunker/java.js.map +0 -1
- package/dist/chunker/javascript.js.map +0 -1
- package/dist/chunker/json.js.map +0 -1
- package/dist/chunker/kotlin.js.map +0 -1
- package/dist/chunker/loader.js.map +0 -1
- package/dist/chunker/markdown.js.map +0 -1
- package/dist/chunker/pdf.js.map +0 -1
- package/dist/chunker/php.js.map +0 -1
- package/dist/chunker/powershell.js.map +0 -1
- package/dist/chunker/python.js.map +0 -1
- package/dist/chunker/razor.js.map +0 -1
- package/dist/chunker/ruby.js.map +0 -1
- package/dist/chunker/rust.js.map +0 -1
- package/dist/chunker/sln.js.map +0 -1
- package/dist/chunker/sql.js.map +0 -1
- package/dist/chunker/ssl.js.map +0 -1
- package/dist/chunker/swift.js.map +0 -1
- package/dist/chunker/tex.js.map +0 -1
- package/dist/chunker/toml.js.map +0 -1
- package/dist/chunker/typescript.js.map +0 -1
- package/dist/chunker/uuid.js.map +0 -1
- package/dist/chunker/xml.js.map +0 -1
- package/dist/chunker/yaml.js.map +0 -1
- package/dist/cli/commands/clear.js.map +0 -1
- package/dist/cli/commands/describe-image.js.map +0 -1
- package/dist/cli/commands/dump.js.map +0 -1
- package/dist/cli/commands/eval.js.map +0 -1
- package/dist/cli/commands/index-command.js.map +0 -1
- package/dist/cli/commands/index.js.map +0 -1
- package/dist/cli/commands/init-helpers.js.map +0 -1
- package/dist/cli/commands/init.js.map +0 -1
- package/dist/cli/commands/list.js.map +0 -1
- package/dist/cli/commands/mcp.js.map +0 -1
- package/dist/cli/commands/query.js.map +0 -1
- package/dist/cli/commands/show.js.map +0 -1
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/ui.js.map +0 -1
- package/dist/cli/commands/update.d.ts +0 -17
- package/dist/cli/commands/update.js +0 -79
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/cli/format.js.map +0 -1
- package/dist/cli/helpers.js.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/progress.d.ts +0 -42
- package/dist/cli/progress.js +0 -137
- package/dist/cli/progress.js.map +0 -1
- package/dist/cli/types.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/content/doc.js.map +0 -1
- package/dist/content/docx.js.map +0 -1
- package/dist/content/excel.js.map +0 -1
- package/dist/content/image.js.map +0 -1
- package/dist/content/pdf.js.map +0 -1
- package/dist/content/reader.js.map +0 -1
- package/dist/content/types.js.map +0 -1
- package/dist/core/bootstrap.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/doc-progress.js.map +0 -1
- package/dist/core/fileLogger.js.map +0 -1
- package/dist/core/interfaces.js.map +0 -1
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/provider-defaults.js.map +0 -1
- package/dist/core/rag-injection-flag.js.map +0 -1
- package/dist/core/resolve-api-key.js.map +0 -1
- package/dist/core/runtime-overrides.js.map +0 -1
- package/dist/describer/anthropic.js.map +0 -1
- package/dist/describer/describer.js.map +0 -1
- package/dist/describer/factory.js.map +0 -1
- package/dist/describer/gemini.js.map +0 -1
- package/dist/describer/shared.js.map +0 -1
- package/dist/embedder/cohere.js.map +0 -1
- package/dist/embedder/factory.js.map +0 -1
- package/dist/embedder/health.js.map +0 -1
- package/dist/embedder/http.js.map +0 -1
- package/dist/embedder/ollama.js.map +0 -1
- package/dist/embedder/openai.js.map +0 -1
- package/dist/eval/index.js.map +0 -1
- package/dist/eval/run-token-test.js.map +0 -1
- package/dist/eval/session-logger.js.map +0 -1
- package/dist/eval/storage.js.map +0 -1
- package/dist/eval/token-analysis.js.map +0 -1
- package/dist/eval/token-counter.js.map +0 -1
- package/dist/eval/types.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/indexer/description-stage.js.map +0 -1
- package/dist/indexer/embed-stage.js.map +0 -1
- package/dist/indexer/git-diff.js.map +0 -1
- package/dist/indexer/metadata.js.map +0 -1
- package/dist/indexer/pipeline.js.map +0 -1
- package/dist/indexer/stats.js.map +0 -1
- package/dist/indexer/watch.js.map +0 -1
- package/dist/indexer/worker.js.map +0 -1
- package/dist/indexer.js.map +0 -1
- package/dist/mcp/cli.js.map +0 -1
- package/dist/mcp/handlers.js.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/opencode/create-read-tool.js.map +0 -1
- package/dist/opencode/read-fallback.js.map +0 -1
- package/dist/opencode/read-format.js.map +0 -1
- package/dist/opencode/read-query.js.map +0 -1
- package/dist/opencode/tool-args.js.map +0 -1
- package/dist/opencode/tools.js.map +0 -1
- package/dist/plugin-entry.js.map +0 -1
- package/dist/plugin.js.map +0 -1
- package/dist/retriever/context-optimizer.js.map +0 -1
- package/dist/retriever/keyword-index.js.map +0 -1
- package/dist/retriever/retriever.js.map +0 -1
- package/dist/tui.js.map +0 -1
- package/dist/updater.d.ts +0 -45
- package/dist/updater.js +0 -175
- package/dist/updater.js.map +0 -1
- package/dist/vectorstore/factory.js.map +0 -1
- package/dist/vectorstore/lancedb.js.map +0 -1
- package/dist/vectorstore/memory.js.map +0 -1
- package/dist/watcher.js.map +0 -1
- package/dist/web/api.js.map +0 -1
- package/dist/web/server.js.map +0 -1
- package/dist/web/static.js.map +0 -1
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* We request extra results up-front, then after hybrid fusion + minScore filtering,
|
|
3
3
|
* we slice back to the requested topK. */
|
|
4
4
|
const FETCH_OVERFETCH_FACTOR = 3;
|
|
5
|
+
const RRF_K = 60;
|
|
6
|
+
/** Multiply raw RRF scores by (K+1) to normalize to ~[0,1]. */
|
|
7
|
+
const RRF_NORMALIZE = RRF_K + 1;
|
|
5
8
|
/**
|
|
6
9
|
* Perform hybrid vector-keyword retrieval with configurable scoring.
|
|
7
10
|
*
|
|
@@ -28,13 +31,13 @@ export async function retrieve(query, embedder, store, options = {}) {
|
|
|
28
31
|
if (typeof embedding[0] !== "number") {
|
|
29
32
|
return [];
|
|
30
33
|
}
|
|
31
|
-
const vectorResults = await store.
|
|
34
|
+
const vectorResults = await store.searchWithFilter(embedding, topK * FETCH_OVERFETCH_FACTOR, options.filter);
|
|
32
35
|
let keywordResults = [];
|
|
33
|
-
if (options.keywordIndex) {
|
|
34
|
-
keywordResults = options.keywordIndex.search(query, topK * FETCH_OVERFETCH_FACTOR);
|
|
36
|
+
if (options.keywordIndex && options.hybridEnabled !== false) {
|
|
37
|
+
keywordResults = options.keywordIndex.search(query, topK * FETCH_OVERFETCH_FACTOR, options.filter);
|
|
35
38
|
}
|
|
36
39
|
if (keywordResults.length === 0) {
|
|
37
|
-
const filtered = vectorResults.filter((r) => r.score >= minScore);
|
|
40
|
+
const filtered = vectorResults.filter((r) => r.score >= minScore).slice(0, topK);
|
|
38
41
|
if (options.explain) {
|
|
39
42
|
const kw = options.keywordWeight ?? 0.4;
|
|
40
43
|
for (const r of filtered) {
|
|
@@ -51,65 +54,40 @@ export async function retrieve(query, embedder, store, options = {}) {
|
|
|
51
54
|
}
|
|
52
55
|
return filtered;
|
|
53
56
|
}
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
rawKScore: 0,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
for (const r of keywordResults) {
|
|
66
|
-
const existing = combined.get(r.chunk.id);
|
|
67
|
-
const normalizedK = kwTopScore > 0 ? r.score / kwTopScore : 0;
|
|
68
|
-
if (existing) {
|
|
69
|
-
existing.kScore = normalizedK;
|
|
70
|
-
existing.rawKScore = r.score;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
combined.set(r.chunk.id, {
|
|
74
|
-
chunk: r.chunk,
|
|
75
|
-
vScore: 0,
|
|
76
|
-
kScore: normalizedK,
|
|
77
|
-
rawVScore: 0,
|
|
78
|
-
rawKScore: r.score,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
57
|
+
const vRank = new Map(vectorResults.map((r, i) => [r.chunk.id, i]));
|
|
58
|
+
const kRank = new Map(keywordResults.map((r, i) => [r.chunk.id, i]));
|
|
59
|
+
const chunkById = new Map();
|
|
60
|
+
for (const r of vectorResults)
|
|
61
|
+
chunkById.set(r.chunk.id, r);
|
|
62
|
+
for (const r of keywordResults)
|
|
63
|
+
if (!chunkById.has(r.chunk.id))
|
|
64
|
+
chunkById.set(r.chunk.id, r);
|
|
82
65
|
const kw = options.keywordWeight ?? 0.4;
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
: entry.kScore * 0.9;
|
|
92
|
-
const result = {
|
|
93
|
-
chunk: entry.chunk,
|
|
94
|
-
score,
|
|
95
|
-
};
|
|
66
|
+
const allIds = new Set([...vRank.keys(), ...kRank.keys()]);
|
|
67
|
+
const combinedResults = [...allIds].map((id) => {
|
|
68
|
+
const vR = vRank.get(id);
|
|
69
|
+
const kR = kRank.get(id);
|
|
70
|
+
const vContrib = vR !== undefined ? ((1 - kw) * RRF_NORMALIZE) / (RRF_K + vR + 1) : 0;
|
|
71
|
+
const kContrib = kR !== undefined ? (kw * RRF_NORMALIZE) / (RRF_K + kR + 1) : 0;
|
|
72
|
+
const score = vContrib + kContrib;
|
|
73
|
+
const result = { chunk: chunkById.get(id).chunk, score };
|
|
96
74
|
if (options.explain) {
|
|
97
|
-
|
|
75
|
+
result.explanation = {
|
|
98
76
|
scoreBreakdown: {
|
|
99
|
-
vectorScore:
|
|
100
|
-
keywordScore:
|
|
101
|
-
rawVectorScore:
|
|
102
|
-
rawKeywordScore:
|
|
77
|
+
vectorScore: vContrib,
|
|
78
|
+
keywordScore: kContrib,
|
|
79
|
+
rawVectorScore: vR !== undefined ? vectorResults[vR].score : 0,
|
|
80
|
+
rawKeywordScore: kR !== undefined ? keywordResults[kR].score : 0,
|
|
103
81
|
keywordWeight: kw,
|
|
82
|
+
vectorRank: vR,
|
|
83
|
+
keywordRank: kR,
|
|
104
84
|
},
|
|
105
85
|
};
|
|
106
|
-
if (options.keywordIndex &&
|
|
107
|
-
const terms = options.keywordIndex.getMatchedTerms(query,
|
|
108
|
-
if (terms.length > 0)
|
|
109
|
-
explanation.matchedTerms = terms;
|
|
110
|
-
}
|
|
86
|
+
if (options.keywordIndex && kR !== undefined) {
|
|
87
|
+
const terms = options.keywordIndex.getMatchedTerms(query, id);
|
|
88
|
+
if (terms.length > 0)
|
|
89
|
+
result.explanation.matchedTerms = terms;
|
|
111
90
|
}
|
|
112
|
-
result.explanation = explanation;
|
|
113
91
|
}
|
|
114
92
|
return result;
|
|
115
93
|
})
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type { VectorStore, Chunk, SearchResult } from "../core/interfaces.js";
|
|
1
|
+
import type { VectorStore, Chunk, SearchResult, MetadataFilter } from "../core/interfaces.js";
|
|
2
|
+
/**
|
|
3
|
+
* L2-normalize a vector to unit length. Cosine models require unit vectors
|
|
4
|
+
* for the dot product to equal cosine similarity.
|
|
5
|
+
*/
|
|
6
|
+
export declare function l2Normalize(vec: number[]): number[];
|
|
2
7
|
/**
|
|
3
8
|
* Check whether an error is a LanceDB corruption error (table not found / broken).
|
|
4
9
|
* @param err - The error to inspect.
|
|
@@ -45,12 +50,13 @@ export declare class LanceDbStore implements VectorStore {
|
|
|
45
50
|
private addChunksInternal;
|
|
46
51
|
/**
|
|
47
52
|
* Perform ANN (approximate nearest neighbor) search using LanceDB's native vector index.
|
|
48
|
-
* Returns results scored as
|
|
53
|
+
* Returns results scored as cosine similarity (0-1). Falls back to repair on corruption.
|
|
49
54
|
* @param embedding - The query embedding vector.
|
|
50
55
|
* @param topK - Maximum number of results to return.
|
|
51
56
|
* @returns An array of search results sorted by descending score.
|
|
52
57
|
*/
|
|
53
58
|
search(embedding: number[], topK: number): Promise<SearchResult[]>;
|
|
59
|
+
searchWithFilter(embedding: number[], topK: number, filter?: MetadataFilter): Promise<SearchResult[]>;
|
|
54
60
|
private rowToSearchResult;
|
|
55
61
|
/**
|
|
56
62
|
* List all distinct file paths stored in the index, along with their language and chunk count.
|
|
@@ -82,12 +88,24 @@ export declare class LanceDbStore implements VectorStore {
|
|
|
82
88
|
content: string;
|
|
83
89
|
description: string;
|
|
84
90
|
}[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Perform ANN search internally, returning results scored as cosine similarity (0-1).
|
|
93
|
+
* This method is called by `search()` and handles the actual query logic.
|
|
94
|
+
* @param embedding - The query embedding vector.
|
|
95
|
+
* @param topK - The number of top results to return.
|
|
96
|
+
* @returns An array of search results with scores.
|
|
97
|
+
*/
|
|
85
98
|
private searchInternal;
|
|
86
99
|
/**
|
|
87
100
|
* Return the total number of chunks stored in the table.
|
|
88
101
|
* @returns The chunk count, or 0 if the table does not exist.
|
|
89
102
|
*/
|
|
90
103
|
count(): Promise<number>;
|
|
104
|
+
/**
|
|
105
|
+
* Return all unique file paths currently stored in the index.
|
|
106
|
+
* @returns An array of normalized file paths.
|
|
107
|
+
*/
|
|
108
|
+
getFilePaths(): Promise<string[]>;
|
|
91
109
|
/**
|
|
92
110
|
* Re-open the store, optionally pointing at a new database path.
|
|
93
111
|
* Closes any existing connection and resets internal state so that the
|
|
@@ -98,16 +116,29 @@ export declare class LanceDbStore implements VectorStore {
|
|
|
98
116
|
reopen(newPath?: string): Promise<void>;
|
|
99
117
|
/** Close the database connection and release resources. */
|
|
100
118
|
close(): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Back up the chunks.lance directory before a destructive operation.
|
|
121
|
+
* Creates a timestamped copy at chunks.lance.backup-<ISO timestamp>.
|
|
122
|
+
* Skips if chunks.lance doesn't exist or noBackup is set.
|
|
123
|
+
* @returns The backup path, or null if nothing was backed up.
|
|
124
|
+
*/
|
|
125
|
+
private backupBeforeClear;
|
|
101
126
|
/**
|
|
102
127
|
* Remove all chunks by dropping the underlying LanceDB table.
|
|
103
128
|
* Falls back to deleting the database directory if dropTable fails.
|
|
129
|
+
* @param options - Optional. Set `{ noBackup: true }` to skip backup (test use only).
|
|
104
130
|
*/
|
|
105
|
-
clear(
|
|
131
|
+
clear(options?: {
|
|
132
|
+
noBackup?: boolean;
|
|
133
|
+
}): Promise<void>;
|
|
106
134
|
/**
|
|
107
135
|
* Completely remove the entire LanceDB database directory from disk.
|
|
108
136
|
* All data is permanently lost.
|
|
137
|
+
* @param options - Optional. Set `{ noBackup: true }` to skip backup (test use only).
|
|
109
138
|
*/
|
|
110
|
-
dropDatabase(
|
|
139
|
+
dropDatabase(options?: {
|
|
140
|
+
noBackup?: boolean;
|
|
141
|
+
}): Promise<void>;
|
|
111
142
|
/**
|
|
112
143
|
* Remove all chunks associated with a given file path.
|
|
113
144
|
* Automatically attempts repair on corruption errors.
|
|
@@ -3,9 +3,23 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as lancedb from "@lancedb/lancedb";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
6
7
|
import { normalizeFilePath, manifestPathFor } from "../core/manifest.js";
|
|
7
8
|
const TABLE_NAME = "chunks";
|
|
8
9
|
const QUERY_COLUMNS = ["id", "content", "description", "filePath", "startLine", "endLine", "language"];
|
|
10
|
+
/**
|
|
11
|
+
* L2-normalize a vector to unit length. Cosine models require unit vectors
|
|
12
|
+
* for the dot product to equal cosine similarity.
|
|
13
|
+
*/
|
|
14
|
+
export function l2Normalize(vec) {
|
|
15
|
+
let norm = 0;
|
|
16
|
+
for (const v of vec)
|
|
17
|
+
norm += v * v;
|
|
18
|
+
norm = Math.sqrt(norm);
|
|
19
|
+
if (norm === 0)
|
|
20
|
+
return vec;
|
|
21
|
+
return vec.map((v) => v / norm);
|
|
22
|
+
}
|
|
9
23
|
/**
|
|
10
24
|
* Check whether an error is a LanceDB corruption error (table not found / broken).
|
|
11
25
|
* @param err - The error to inspect.
|
|
@@ -189,7 +203,7 @@ export class LanceDbStore {
|
|
|
189
203
|
id: c.id,
|
|
190
204
|
content: c.content,
|
|
191
205
|
description: c.description ?? "",
|
|
192
|
-
embedding: c.embedding,
|
|
206
|
+
embedding: l2Normalize(c.embedding),
|
|
193
207
|
filePath: normalizeFilePath(c.metadata.filePath),
|
|
194
208
|
startLine: c.metadata.startLine,
|
|
195
209
|
endLine: c.metadata.endLine,
|
|
@@ -213,15 +227,17 @@ export class LanceDbStore {
|
|
|
213
227
|
await table.add(rows);
|
|
214
228
|
// THEN DEDUP: remove old rows at the same (filePath, startLine) positions,
|
|
215
229
|
// but preserve the newly inserted rows by filtering out their IDs.
|
|
230
|
+
// IMPORTANT: use a single NOT IN clause per position so that when multiple
|
|
231
|
+
// new IDs share the same startLine they don't delete each other.
|
|
216
232
|
for (const [key, newIds] of newIdsByLine) {
|
|
217
233
|
const colonIdx = key.lastIndexOf(":");
|
|
218
234
|
const filePath = key.slice(0, colonIdx);
|
|
219
235
|
const startLine = parseInt(key.slice(colonIdx + 1), 10);
|
|
220
236
|
const escapedPath = filePath.replace(/'/g, "''");
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
237
|
+
const idList = [...newIds]
|
|
238
|
+
.map((id) => `'${id.replace(/'/g, "''")}'`)
|
|
239
|
+
.join(", ");
|
|
240
|
+
await table.delete(`filePath = '${escapedPath}' AND startLine = ${startLine} AND id NOT IN (${idList})`);
|
|
225
241
|
}
|
|
226
242
|
// FINALLY: remove stale chunks for the same file that belong to a
|
|
227
243
|
// previous revision (different startLines). Exclude the new inserts
|
|
@@ -247,20 +263,23 @@ export class LanceDbStore {
|
|
|
247
263
|
}
|
|
248
264
|
/**
|
|
249
265
|
* Perform ANN (approximate nearest neighbor) search using LanceDB's native vector index.
|
|
250
|
-
* Returns results scored as
|
|
266
|
+
* Returns results scored as cosine similarity (0-1). Falls back to repair on corruption.
|
|
251
267
|
* @param embedding - The query embedding vector.
|
|
252
268
|
* @param topK - Maximum number of results to return.
|
|
253
269
|
* @returns An array of search results sorted by descending score.
|
|
254
270
|
*/
|
|
255
271
|
async search(embedding, topK) {
|
|
272
|
+
return this.searchWithFilter(embedding, topK);
|
|
273
|
+
}
|
|
274
|
+
async searchWithFilter(embedding, topK, filter) {
|
|
256
275
|
try {
|
|
257
|
-
return await this.searchInternal(embedding, topK);
|
|
276
|
+
return await this.searchInternal(embedding, topK, filter);
|
|
258
277
|
}
|
|
259
278
|
catch (err) {
|
|
260
279
|
if (isCorruptionError(err)) {
|
|
261
280
|
const repaired = await this.tryRepair();
|
|
262
281
|
if (repaired) {
|
|
263
|
-
return this.searchInternal(embedding, topK);
|
|
282
|
+
return this.searchInternal(embedding, topK, filter);
|
|
264
283
|
}
|
|
265
284
|
}
|
|
266
285
|
return [];
|
|
@@ -268,7 +287,7 @@ export class LanceDbStore {
|
|
|
268
287
|
}
|
|
269
288
|
rowToSearchResult(row) {
|
|
270
289
|
return {
|
|
271
|
-
score: 1
|
|
290
|
+
score: Math.min(1, Math.max(0, 1 - (row._distance ?? 0) / 2)),
|
|
272
291
|
chunk: {
|
|
273
292
|
id: row.id,
|
|
274
293
|
content: row.content,
|
|
@@ -357,7 +376,14 @@ export class LanceDbStore {
|
|
|
357
376
|
description: row.description ?? "",
|
|
358
377
|
}));
|
|
359
378
|
}
|
|
360
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Perform ANN search internally, returning results scored as cosine similarity (0-1).
|
|
381
|
+
* This method is called by `search()` and handles the actual query logic.
|
|
382
|
+
* @param embedding - The query embedding vector.
|
|
383
|
+
* @param topK - The number of top results to return.
|
|
384
|
+
* @returns An array of search results with scores.
|
|
385
|
+
*/
|
|
386
|
+
async searchInternal(embedding, topK, filter) {
|
|
361
387
|
const db = await this.getDb();
|
|
362
388
|
const tableNames = await db.tableNames();
|
|
363
389
|
if (!tableNames.includes(TABLE_NAME))
|
|
@@ -366,7 +392,11 @@ export class LanceDbStore {
|
|
|
366
392
|
const count = await table.countRows();
|
|
367
393
|
if (count === 0)
|
|
368
394
|
return [];
|
|
369
|
-
|
|
395
|
+
let query = table.vectorSearch(l2Normalize(embedding)).distanceType("cosine");
|
|
396
|
+
const whereClause = buildWhereClause(filter);
|
|
397
|
+
if (whereClause)
|
|
398
|
+
query = query.where(whereClause);
|
|
399
|
+
const results = await query.limit(topK).toArray();
|
|
370
400
|
return results
|
|
371
401
|
.map((row) => this.rowToSearchResult(row))
|
|
372
402
|
.filter((r) => r.chunk.id !== "__seed__");
|
|
@@ -388,6 +418,30 @@ export class LanceDbStore {
|
|
|
388
418
|
return 0;
|
|
389
419
|
}
|
|
390
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Return all unique file paths currently stored in the index.
|
|
423
|
+
* @returns An array of normalized file paths.
|
|
424
|
+
*/
|
|
425
|
+
async getFilePaths() {
|
|
426
|
+
try {
|
|
427
|
+
const db = await this.getDb();
|
|
428
|
+
const tableNames = await db.tableNames();
|
|
429
|
+
if (!tableNames.includes(TABLE_NAME))
|
|
430
|
+
return [];
|
|
431
|
+
const table = await this.getTable();
|
|
432
|
+
const rows = await table.query().select(["filePath"]).toArray();
|
|
433
|
+
const paths = new Set();
|
|
434
|
+
for (const row of rows) {
|
|
435
|
+
const fp = row.filePath;
|
|
436
|
+
if (fp)
|
|
437
|
+
paths.add(fp);
|
|
438
|
+
}
|
|
439
|
+
return Array.from(paths);
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
}
|
|
391
445
|
/**
|
|
392
446
|
* Re-open the store, optionally pointing at a new database path.
|
|
393
447
|
* Closes any existing connection and resets internal state so that the
|
|
@@ -396,24 +450,52 @@ export class LanceDbStore {
|
|
|
396
450
|
* @param newPath - Optional new filesystem path for the LanceDB database.
|
|
397
451
|
*/
|
|
398
452
|
async reopen(newPath) {
|
|
399
|
-
|
|
400
|
-
|
|
453
|
+
await this.table?.close();
|
|
454
|
+
await this.db?.close();
|
|
401
455
|
this.table = null;
|
|
402
456
|
this.db = null;
|
|
403
457
|
this.tableInit = null;
|
|
458
|
+
if (newPath)
|
|
459
|
+
this.dbPath = newPath;
|
|
404
460
|
}
|
|
405
461
|
/** Close the database connection and release resources. */
|
|
406
462
|
async close() {
|
|
407
|
-
this.table?.close();
|
|
463
|
+
await this.table?.close();
|
|
408
464
|
this.table = null;
|
|
409
|
-
this.db?.close();
|
|
465
|
+
await this.db?.close();
|
|
410
466
|
this.db = null;
|
|
411
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Back up the chunks.lance directory before a destructive operation.
|
|
470
|
+
* Creates a timestamped copy at chunks.lance.backup-<ISO timestamp>.
|
|
471
|
+
* Skips if chunks.lance doesn't exist or noBackup is set.
|
|
472
|
+
* @returns The backup path, or null if nothing was backed up.
|
|
473
|
+
*/
|
|
474
|
+
async backupBeforeClear(noBackup) {
|
|
475
|
+
if (noBackup)
|
|
476
|
+
return null;
|
|
477
|
+
const lancePath = path.join(this.dbPath, "chunks.lance");
|
|
478
|
+
try {
|
|
479
|
+
await fs.access(lancePath);
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
485
|
+
const backupPath = `${lancePath}.backup-${timestamp}`;
|
|
486
|
+
await fs.cp(lancePath, backupPath, { recursive: true });
|
|
487
|
+
return backupPath;
|
|
488
|
+
}
|
|
412
489
|
/**
|
|
413
490
|
* Remove all chunks by dropping the underlying LanceDB table.
|
|
414
491
|
* Falls back to deleting the database directory if dropTable fails.
|
|
492
|
+
* @param options - Optional. Set `{ noBackup: true }` to skip backup (test use only).
|
|
415
493
|
*/
|
|
416
|
-
async clear() {
|
|
494
|
+
async clear(options) {
|
|
495
|
+
const backup = await this.backupBeforeClear(options?.noBackup);
|
|
496
|
+
if (backup)
|
|
497
|
+
console.warn(`[lancedb] Backed up chunks.lance to ${backup}`);
|
|
498
|
+
await this.table?.close();
|
|
417
499
|
this.table = null;
|
|
418
500
|
try {
|
|
419
501
|
const db = await this.getDb();
|
|
@@ -421,8 +503,11 @@ export class LanceDbStore {
|
|
|
421
503
|
if (tableNames.includes(TABLE_NAME)) {
|
|
422
504
|
await db.dropTable(TABLE_NAME);
|
|
423
505
|
}
|
|
506
|
+
await this.db?.close();
|
|
507
|
+
this.db = null;
|
|
424
508
|
}
|
|
425
509
|
catch {
|
|
510
|
+
await this.db?.close();
|
|
426
511
|
this.db = null;
|
|
427
512
|
try {
|
|
428
513
|
await fs.rm(this.dbPath, { recursive: true, force: true });
|
|
@@ -433,8 +518,27 @@ export class LanceDbStore {
|
|
|
433
518
|
/**
|
|
434
519
|
* Completely remove the entire LanceDB database directory from disk.
|
|
435
520
|
* All data is permanently lost.
|
|
521
|
+
* @param options - Optional. Set `{ noBackup: true }` to skip backup (test use only).
|
|
436
522
|
*/
|
|
437
|
-
async dropDatabase() {
|
|
523
|
+
async dropDatabase(options) {
|
|
524
|
+
if (!options?.noBackup) {
|
|
525
|
+
// Back up the entire store directory since dropDatabase deletes it.
|
|
526
|
+
try {
|
|
527
|
+
await fs.access(this.dbPath);
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
// nothing to back up
|
|
531
|
+
}
|
|
532
|
+
try {
|
|
533
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
534
|
+
const backupPath = `${this.dbPath}.backup-${timestamp}`;
|
|
535
|
+
await fs.cp(this.dbPath, backupPath, { recursive: true });
|
|
536
|
+
console.warn(`[lancedb] Backed up database to ${backupPath}`);
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
// backup failed, continue anyway
|
|
540
|
+
}
|
|
541
|
+
}
|
|
438
542
|
this.table = null;
|
|
439
543
|
this.db = null;
|
|
440
544
|
try {
|
|
@@ -481,8 +585,6 @@ export class LanceDbStore {
|
|
|
481
585
|
table = await db.openTable(TABLE_NAME);
|
|
482
586
|
}
|
|
483
587
|
catch {
|
|
484
|
-
// Table is corrupt and can't even be opened — leave it be so the user
|
|
485
|
-
// can run `opencode-rag index --force` to rebuild with full control.
|
|
486
588
|
console.error("[lancedb] Corrupt table detected. Run 'opencode-rag index --force' to rebuild.");
|
|
487
589
|
return false;
|
|
488
590
|
}
|
|
@@ -491,13 +593,10 @@ export class LanceDbStore {
|
|
|
491
593
|
versions = await table.listVersions();
|
|
492
594
|
}
|
|
493
595
|
catch {
|
|
494
|
-
// Can't list versions (corrupt version manifest). Don't nuke data —
|
|
495
|
-
// let the user decide how to proceed.
|
|
496
596
|
console.warn("[lancedb] Could not list table versions for repair. " +
|
|
497
597
|
"Run 'opencode-rag index --force' to rebuild if search results are incorrect.");
|
|
498
598
|
return false;
|
|
499
599
|
}
|
|
500
|
-
// No previous version to roll back to — not a corruption we can fix.
|
|
501
600
|
if (versions.length <= 1) {
|
|
502
601
|
return false;
|
|
503
602
|
}
|
|
@@ -515,7 +614,6 @@ export class LanceDbStore {
|
|
|
515
614
|
continue;
|
|
516
615
|
}
|
|
517
616
|
}
|
|
518
|
-
// Tried all versions, none worked — report failure but don't destroy data.
|
|
519
617
|
console.error("[lancedb] All version-restore attempts failed. " +
|
|
520
618
|
"Run 'opencode-rag index --force' to rebuild the index.");
|
|
521
619
|
return false;
|
|
@@ -525,4 +623,29 @@ export class LanceDbStore {
|
|
|
525
623
|
}
|
|
526
624
|
}
|
|
527
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Build a LanceDB SQL WHERE clause from a MetadataFilter.
|
|
628
|
+
* Handles path glob patterns and language filters with proper escaping.
|
|
629
|
+
*/
|
|
630
|
+
function buildWhereClause(filter) {
|
|
631
|
+
if (!filter)
|
|
632
|
+
return undefined;
|
|
633
|
+
const parts = [];
|
|
634
|
+
if (filter.languages?.length) {
|
|
635
|
+
const langs = filter.languages.map((l) => `'${l.replace(/'/g, "''")}'`).join(",");
|
|
636
|
+
parts.push(`language IN (${langs})`);
|
|
637
|
+
}
|
|
638
|
+
if (filter.pathPatterns?.length) {
|
|
639
|
+
const likes = filter.pathPatterns.map((p) => {
|
|
640
|
+
const escaped = p.replace(/'/g, "''");
|
|
641
|
+
const like = escaped
|
|
642
|
+
.replace(/\*\*/g, "%")
|
|
643
|
+
.replace(/\*/g, "%")
|
|
644
|
+
.replace(/\?/g, "_");
|
|
645
|
+
return `filePath LIKE '${like}'`;
|
|
646
|
+
});
|
|
647
|
+
parts.push(`(${likes.join(" OR ")})`);
|
|
648
|
+
}
|
|
649
|
+
return parts.length ? parts.join(" AND ") : undefined;
|
|
650
|
+
}
|
|
528
651
|
//# sourceMappingURL=lancedb.js.map
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Ephemeral in-memory vector store using cosine similarity search.
|
|
3
3
|
*/
|
|
4
|
-
import type { VectorStore, Chunk, SearchResult } from "../core/interfaces.js";
|
|
4
|
+
import type { VectorStore, Chunk, ChunkSummary, FileSummary, SearchResult, MetadataFilter } from "../core/interfaces.js";
|
|
5
5
|
/** Ephemeral in-memory vector store using cosine similarity search. */
|
|
6
6
|
export declare class InMemoryVectorStore implements VectorStore {
|
|
7
7
|
private chunks;
|
|
8
8
|
addChunks(chunks: Chunk[]): Promise<void>;
|
|
9
9
|
search(embedding: number[], topK: number): Promise<SearchResult[]>;
|
|
10
|
+
searchWithFilter(embedding: number[], topK: number, filter?: MetadataFilter): Promise<SearchResult[]>;
|
|
10
11
|
count(): Promise<number>;
|
|
11
12
|
clear(): Promise<void>;
|
|
13
|
+
getFilePaths(): Promise<string[]>;
|
|
12
14
|
/**
|
|
13
15
|
* Remove all chunks associated with a given file path.
|
|
14
16
|
* @param filePath - The file path whose chunks should be removed.
|
|
15
17
|
*/
|
|
16
18
|
deleteByFilePath(filePath: string): Promise<void>;
|
|
19
|
+
getChunks(offset: number, limit: number): Promise<ChunkSummary[]>;
|
|
20
|
+
listFiles(): Promise<FileSummary[]>;
|
|
21
|
+
getChunksByFilePath(filePath: string): Promise<Chunk[]>;
|
|
17
22
|
/** Release any held resources. No-op for the in-memory store. */
|
|
18
23
|
close(): Promise<void>;
|
|
19
24
|
}
|
|
@@ -5,8 +5,12 @@ export class InMemoryVectorStore {
|
|
|
5
5
|
this.chunks.push(...chunks.filter((c) => c.embedding && c.embedding.length > 0));
|
|
6
6
|
}
|
|
7
7
|
async search(embedding, topK) {
|
|
8
|
+
return this.searchWithFilter(embedding, topK);
|
|
9
|
+
}
|
|
10
|
+
async searchWithFilter(embedding, topK, filter) {
|
|
8
11
|
const withEmbeddings = this.chunks.filter((c) => c.embedding !== undefined && c.embedding.length === embedding.length);
|
|
9
12
|
const scored = withEmbeddings
|
|
13
|
+
.filter((c) => matchesFilter(c, filter))
|
|
10
14
|
.map((chunk) => {
|
|
11
15
|
const sim = cosineSimilarity(embedding, chunk.embedding);
|
|
12
16
|
return { chunk, score: sim };
|
|
@@ -21,6 +25,10 @@ export class InMemoryVectorStore {
|
|
|
21
25
|
async clear() {
|
|
22
26
|
this.chunks = [];
|
|
23
27
|
}
|
|
28
|
+
async getFilePaths() {
|
|
29
|
+
const paths = new Set(this.chunks.map((c) => c.metadata.filePath));
|
|
30
|
+
return Array.from(paths);
|
|
31
|
+
}
|
|
24
32
|
/**
|
|
25
33
|
* Remove all chunks associated with a given file path.
|
|
26
34
|
* @param filePath - The file path whose chunks should be removed.
|
|
@@ -28,6 +36,37 @@ export class InMemoryVectorStore {
|
|
|
28
36
|
async deleteByFilePath(filePath) {
|
|
29
37
|
this.chunks = this.chunks.filter((c) => c.metadata.filePath !== filePath);
|
|
30
38
|
}
|
|
39
|
+
async getChunks(offset, limit) {
|
|
40
|
+
return this.chunks.slice(offset, offset + limit).map((c) => ({
|
|
41
|
+
id: c.id,
|
|
42
|
+
filePath: c.metadata.filePath,
|
|
43
|
+
language: c.metadata.language,
|
|
44
|
+
startLine: c.metadata.startLine,
|
|
45
|
+
endLine: c.metadata.endLine,
|
|
46
|
+
content: c.content,
|
|
47
|
+
description: c.description ?? "",
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
async listFiles() {
|
|
51
|
+
const fileMap = new Map();
|
|
52
|
+
for (const c of this.chunks) {
|
|
53
|
+
const existing = fileMap.get(c.metadata.filePath);
|
|
54
|
+
if (existing) {
|
|
55
|
+
existing.chunkCount++;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
fileMap.set(c.metadata.filePath, { language: c.metadata.language, chunkCount: 1 });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return Array.from(fileMap.entries())
|
|
62
|
+
.map(([filePath, info]) => ({ filePath, ...info }))
|
|
63
|
+
.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
64
|
+
}
|
|
65
|
+
async getChunksByFilePath(filePath) {
|
|
66
|
+
return this.chunks
|
|
67
|
+
.filter((c) => c.metadata.filePath === filePath)
|
|
68
|
+
.sort((a, b) => a.metadata.startLine - b.metadata.startLine);
|
|
69
|
+
}
|
|
31
70
|
/** Release any held resources. No-op for the in-memory store. */
|
|
32
71
|
async close() {
|
|
33
72
|
}
|
|
@@ -38,6 +77,25 @@ export class InMemoryVectorStore {
|
|
|
38
77
|
* @param b - Second vector.
|
|
39
78
|
* @returns The cosine similarity (0 if either vector has zero magnitude).
|
|
40
79
|
*/
|
|
80
|
+
function globMatch(pattern, filePath) {
|
|
81
|
+
const GLOBSTAR = "\x00GS\x00";
|
|
82
|
+
let re = pattern.replace(/\*\*/g, GLOBSTAR);
|
|
83
|
+
re = re.replace(/([.+^${}()|[\]\\])/g, "\\$1");
|
|
84
|
+
re = re.replace(new RegExp(GLOBSTAR, "g"), ".*");
|
|
85
|
+
re = re.replace(/\*/g, "[^/]*");
|
|
86
|
+
re = re.replace(/\?/g, ".");
|
|
87
|
+
return new RegExp("^" + re + "$").test(filePath);
|
|
88
|
+
}
|
|
89
|
+
function matchesFilter(chunk, filter) {
|
|
90
|
+
if (!filter)
|
|
91
|
+
return true;
|
|
92
|
+
if (filter.languages?.length && !filter.languages.includes(chunk.metadata.language))
|
|
93
|
+
return false;
|
|
94
|
+
if (filter.pathPatterns?.length) {
|
|
95
|
+
return filter.pathPatterns.some((p) => globMatch(p, chunk.metadata.filePath));
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
41
99
|
function cosineSimilarity(a, b) {
|
|
42
100
|
let dot = 0;
|
|
43
101
|
let normA = 0;
|
package/dist/watcher.js
CHANGED
|
@@ -30,6 +30,7 @@ function writeWatcherStatus(storePath, status) {
|
|
|
30
30
|
export function createBackgroundIndexer(options) {
|
|
31
31
|
const { cwd, storePath, config, store, embedder, logFilePath, logLevel, keywordIndex, descriptionProvider } = options;
|
|
32
32
|
writeWatcherStatus(storePath, { running: false, lastRunAt: undefined });
|
|
33
|
+
const ac = new AbortController();
|
|
33
34
|
const updateStatus = (partial) => {
|
|
34
35
|
writeWatcherStatus(storePath, { running: false, lastRunAt: undefined, ...partial });
|
|
35
36
|
};
|
|
@@ -45,6 +46,7 @@ export function createBackgroundIndexer(options) {
|
|
|
45
46
|
keywordIndex,
|
|
46
47
|
descriptionProvider,
|
|
47
48
|
filterPaths,
|
|
49
|
+
abortSignal: ac.signal,
|
|
48
50
|
logger: {
|
|
49
51
|
info: (message) => appendDebugLog(logFilePath, { scope: "autoIndex", message }, logLevel),
|
|
50
52
|
warn: (message) => appendDebugLog(logFilePath, { scope: "autoIndex", message }, logLevel),
|
|
@@ -114,6 +116,7 @@ export function createBackgroundIndexer(options) {
|
|
|
114
116
|
async close() {
|
|
115
117
|
if (periodicTimer)
|
|
116
118
|
clearInterval(periodicTimer);
|
|
119
|
+
ac.abort();
|
|
117
120
|
scheduler.close();
|
|
118
121
|
await scheduler.waitForIdle();
|
|
119
122
|
await watcher.close();
|