opencode-rag-plugin 1.15.0 → 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 +6 -7
- 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 +196 -81
- 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
package/dist/indexer/pipeline.js
CHANGED
|
@@ -5,12 +5,13 @@ import fs from "node:fs/promises";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import pLimit from "p-limit";
|
|
7
7
|
import { scanWorkspaceFiles } from "../content/reader.js";
|
|
8
|
-
import { loadManifest, saveManifest, normalizeFilePath } from "../core/manifest.js";
|
|
8
|
+
import { loadManifest, saveManifest, normalizeFilePath, computeDescriptionConfigHash } from "../core/manifest.js";
|
|
9
|
+
import { DescriptionCache } from "../core/desc-cache.js";
|
|
9
10
|
import { embedBatch } from "../embedder/factory.js";
|
|
10
11
|
import { createVectorStore } from "../vectorstore/factory.js";
|
|
11
12
|
import { swapStoreDirectories } from "../vectorstore/lancedb.js";
|
|
12
13
|
import { createIndexStats } from "./stats.js";
|
|
13
|
-
import { prepareFile, buildTextsToEmbed
|
|
14
|
+
import { prepareFile, buildTextsToEmbed } from "./worker.js";
|
|
14
15
|
import { buildFallbackDescription } from "./description-stage.js";
|
|
15
16
|
import { getCurrentCommit, getChangedFilesSince, getUntrackedFiles, getRepoRoot } from "./git-diff.js";
|
|
16
17
|
export { createWatchPassScheduler, createWatchIgnore } from "./watch.js";
|
|
@@ -99,6 +100,11 @@ async function runIndexPassInner(options, logger) {
|
|
|
99
100
|
manifestStatus = "missing";
|
|
100
101
|
logger.debug("Force mode: ignoring manifest");
|
|
101
102
|
}
|
|
103
|
+
// Compute a hash of the current description config — used to skip re-description
|
|
104
|
+
// when neither the file content nor the description config has changed.
|
|
105
|
+
const descHash = options.descriptionProvider
|
|
106
|
+
? computeDescriptionConfigHash(options.config)
|
|
107
|
+
: undefined;
|
|
102
108
|
// Clear files that had description failures so they are fully re-indexed
|
|
103
109
|
const descFailedPaths = Object.keys(manifest.files).filter((p) => manifest.files[p]?.descriptionFailed);
|
|
104
110
|
if (descFailedPaths.length > 0) {
|
|
@@ -107,6 +113,39 @@ async function runIndexPassInner(options, logger) {
|
|
|
107
113
|
delete manifest.files[p];
|
|
108
114
|
}
|
|
109
115
|
}
|
|
116
|
+
// Persistent description cache — survives aborted runs so that descriptions
|
|
117
|
+
// generated before an abort are reused on the next run.
|
|
118
|
+
const descCachePath = options.storePath;
|
|
119
|
+
const descCache = new DescriptionCache(descCachePath);
|
|
120
|
+
await descCache.load();
|
|
121
|
+
// Resume-on-interrupt: detect manifest entries that are inconsistent with
|
|
122
|
+
// the vector store (e.g. indexing was aborted before all chunks were stored,
|
|
123
|
+
// or a full rebuild was aborted leaving a stale manifest pointing to old data).
|
|
124
|
+
// These entries are removed so they will be re-processed in this pass.
|
|
125
|
+
if (!options.force && manifestStatus === "ok" && Object.keys(manifest.files).length > 0) {
|
|
126
|
+
try {
|
|
127
|
+
const storedPaths = await options.store.getFilePaths();
|
|
128
|
+
const storedSet = new Set(storedPaths);
|
|
129
|
+
let removedCount = 0;
|
|
130
|
+
for (const [p, entry] of Object.entries(manifest.files)) {
|
|
131
|
+
// Path missing from store entirely (aborted before store finished)
|
|
132
|
+
if (entry.chunkCount > 0 && !storedSet.has(p)) {
|
|
133
|
+
delete manifest.files[p];
|
|
134
|
+
removedCount++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (removedCount > 0) {
|
|
138
|
+
logger.info(` ${removedCount} file(s) in manifest but missing from store — re-indexing (resume after interruption)`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
logger.debug(" Could not check store paths for resume — proceeding without");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Clear files where the stored descHash doesn't match current descHash and the
|
|
146
|
+
// file content IS different — these would be caught by hash comparison below but
|
|
147
|
+
// this pre-clear ensures the description cache is consulted during re-description.
|
|
148
|
+
// (Files with same hash but different descHash already fall through in prepareFile.)
|
|
110
149
|
let filterPaths;
|
|
111
150
|
let gitDeletedPaths = [];
|
|
112
151
|
if (!options.force && manifestStatus === "ok" && manifest.lastGitCommit) {
|
|
@@ -127,7 +166,7 @@ async function runIndexPassInner(options, logger) {
|
|
|
127
166
|
}
|
|
128
167
|
}
|
|
129
168
|
const scanStart = Date.now();
|
|
130
|
-
const workspaceFiles = await scanWorkspaceFiles(options.cwd, options.config, logger, options.force ? undefined : manifest, filterPaths);
|
|
169
|
+
const workspaceFiles = await scanWorkspaceFiles(options.cwd, options.config, logger, options.force ? undefined : manifest, filterPaths, options.imageVisionProvider, descCache);
|
|
131
170
|
const scanSec = ((Date.now() - scanStart) / 1000).toFixed(1);
|
|
132
171
|
logger.info(`Workspace scan complete: ${workspaceFiles.length} files in ${scanSec}s`);
|
|
133
172
|
const existingCount = await options.store.count();
|
|
@@ -237,12 +276,19 @@ async function runIndexPassInner(options, logger) {
|
|
|
237
276
|
if (isActive) {
|
|
238
277
|
options.progress?.startFile(fileLabel);
|
|
239
278
|
}
|
|
240
|
-
const prep = await prepareFile(file, options.cwd, manifest.files[file.normalizedPath], options.config, options.keywordIndex, options.descriptionProvider, logger, deferDescriptions);
|
|
279
|
+
const prep = await prepareFile(file, options.cwd, manifest.files[file.normalizedPath], options.config, options.keywordIndex, options.descriptionProvider, logger, deferDescriptions, descHash);
|
|
241
280
|
if (prep.earlyResult && isActive) {
|
|
242
281
|
options.progress?.finishFile(fileLabel);
|
|
243
282
|
}
|
|
244
283
|
return prep;
|
|
245
284
|
})));
|
|
285
|
+
const aborted = () => options.abortSignal?.aborted ?? false;
|
|
286
|
+
// Shared progress logger: "stage <file> (chunk i/n) — X/total remaining (P%)".
|
|
287
|
+
const logChunkProgress = (stage, fileLabel, index, count, completed, total) => {
|
|
288
|
+
const remaining = total - completed;
|
|
289
|
+
const pct = total > 0 ? ((remaining / total) * 100).toFixed(1) : "0.0";
|
|
290
|
+
logger.info(`${stage} ${fileLabel} (chunk ${index}/${count}) — ${remaining}/${total} remaining (${pct}%)`);
|
|
291
|
+
};
|
|
246
292
|
if (deferDescriptions) {
|
|
247
293
|
const deferredPreps = prepared.filter((p) => p.chunks && p.chunks.length > 0 && p.relPath !== undefined);
|
|
248
294
|
if (deferredPreps.length > 0) {
|
|
@@ -264,13 +310,139 @@ async function runIndexPassInner(options, logger) {
|
|
|
264
310
|
for (const chunk of oversizedChunks) {
|
|
265
311
|
chunk.description = buildFallbackDescription(chunk);
|
|
266
312
|
}
|
|
313
|
+
// Per-file chunk index + label for clear progress reporting, plus a shared
|
|
314
|
+
// progress logger emitting "stage <file> (chunk i/n) — X/total remaining (P%)".
|
|
315
|
+
const chunkToFileLabel = new Map();
|
|
316
|
+
for (const prep of deferredPreps) {
|
|
317
|
+
for (const chunk of prep.chunks ?? []) {
|
|
318
|
+
chunkToFileLabel.set(chunk.id, prep.fileLabel);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const chunkMeta = new Map();
|
|
322
|
+
{
|
|
323
|
+
const perFileCount = new Map();
|
|
324
|
+
for (const chunk of allChunks) {
|
|
325
|
+
perFileCount.set(chunk.metadata.filePath, (perFileCount.get(chunk.metadata.filePath) ?? 0) + 1);
|
|
326
|
+
}
|
|
327
|
+
const perFileSeen = new Map();
|
|
328
|
+
for (const chunk of allChunks) {
|
|
329
|
+
const seen = perFileSeen.get(chunk.metadata.filePath) ?? 0;
|
|
330
|
+
perFileSeen.set(chunk.metadata.filePath, seen + 1);
|
|
331
|
+
chunkMeta.set(chunk.id, {
|
|
332
|
+
fileLabel: chunkToFileLabel.get(chunk.id) ?? chunk.metadata.filePath,
|
|
333
|
+
index: seen + 1,
|
|
334
|
+
count: perFileCount.get(chunk.metadata.filePath) ?? 1,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
267
338
|
// Advance progress to Description stage before descriptions start
|
|
268
339
|
for (const prep of deferredPreps) {
|
|
269
340
|
options.progress?.finishStage(prep.fileLabel);
|
|
270
341
|
}
|
|
271
|
-
if (allChunks.length > 0) {
|
|
342
|
+
if (allChunks.length > 0 && descHash) {
|
|
343
|
+
// Check description cache first — reuse cached descriptions for unchanged chunks
|
|
344
|
+
const cacheHits = [];
|
|
345
|
+
const cacheMisses = [];
|
|
346
|
+
for (const chunk of allChunks) {
|
|
347
|
+
const cacheKey = DescriptionCache.codeKey(chunk.content, descHash);
|
|
348
|
+
const cached = descCache.get(cacheKey);
|
|
349
|
+
if (cached) {
|
|
350
|
+
cacheHits.push({ chunk, desc: cached });
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
cacheMisses.push(chunk);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (cacheHits.length > 0) {
|
|
357
|
+
logger.debug(` Using ${cacheHits.length} cached descriptions`);
|
|
358
|
+
for (const { chunk, desc } of cacheHits) {
|
|
359
|
+
chunk.description = desc;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Build chunkId → prep map for tracking description failures per-file
|
|
363
|
+
const chunkToPrep = new Map();
|
|
364
|
+
for (const prep of deferredPreps) {
|
|
365
|
+
for (const chunk of prep.chunks ?? []) {
|
|
366
|
+
chunkToPrep.set(chunk.id, prep);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Process cache misses in parallel waves with throttled cache saves.
|
|
370
|
+
// Parallel sub-batches reduce serial LLM turnaround time.
|
|
371
|
+
// Cache saves are throttled to avoid O(n^2) full-JSON serialization I/O.
|
|
372
|
+
if (cacheMisses.length > 0) {
|
|
373
|
+
const totalMisses = cacheMisses.length;
|
|
374
|
+
const descConcurrency = options.config.indexing.descriptionConcurrency ?? 4;
|
|
375
|
+
const SUB_BATCH = 10;
|
|
376
|
+
let describedDone = 0;
|
|
377
|
+
let describedSinceLastSave = 0;
|
|
378
|
+
const SAVE_INTERVAL = 200;
|
|
379
|
+
const descLimit = pLimit(descConcurrency);
|
|
380
|
+
const subBatchTasks = [];
|
|
381
|
+
for (let i = 0; i < totalMisses; i += SUB_BATCH) {
|
|
382
|
+
subBatchTasks.push({ i, chunks: cacheMisses.slice(i, i + SUB_BATCH) });
|
|
383
|
+
}
|
|
384
|
+
await Promise.all(subBatchTasks.map(({ i, chunks: subBatch }) => descLimit(async () => {
|
|
385
|
+
if (aborted())
|
|
386
|
+
return;
|
|
387
|
+
try {
|
|
388
|
+
const batchResult = await options.descriptionProvider.generateBatchDescriptions(subBatch, logger, {
|
|
389
|
+
total: totalMisses,
|
|
390
|
+
onProgress: (chunk) => {
|
|
391
|
+
describedDone++;
|
|
392
|
+
const meta = chunkMeta.get(chunk.id);
|
|
393
|
+
if (meta)
|
|
394
|
+
logChunkProgress("Describing", meta.fileLabel, meta.index, meta.count, describedDone, totalMisses);
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
const newCacheEntries = [];
|
|
398
|
+
for (const chunk of subBatch) {
|
|
399
|
+
const desc = batchResult.get(chunk.id);
|
|
400
|
+
if (desc && desc.trim().length > 0) {
|
|
401
|
+
chunk.description = desc;
|
|
402
|
+
newCacheEntries.push([DescriptionCache.codeKey(chunk.content, descHash), desc]);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (newCacheEntries.length > 0) {
|
|
406
|
+
descCache.setMany(newCacheEntries);
|
|
407
|
+
describedSinceLastSave += newCacheEntries.length;
|
|
408
|
+
if (describedSinceLastSave >= SAVE_INTERVAL) {
|
|
409
|
+
await descCache.save();
|
|
410
|
+
describedSinceLastSave = 0;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (err) {
|
|
415
|
+
logger.warn(` Description sub-batch failed (${i}-${i + subBatch.length}): ${err.message}`);
|
|
416
|
+
const failedPreps = new Set();
|
|
417
|
+
for (const chunk of subBatch) {
|
|
418
|
+
const prep = chunkToPrep.get(chunk.id);
|
|
419
|
+
if (prep)
|
|
420
|
+
failedPreps.add(prep);
|
|
421
|
+
}
|
|
422
|
+
for (const prep of failedPreps) {
|
|
423
|
+
prep.descriptionFailed = true;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
})));
|
|
427
|
+
if (describedSinceLastSave > 0) {
|
|
428
|
+
await descCache.save();
|
|
429
|
+
}
|
|
430
|
+
logger.debug(`Descriptions generated for ${totalMisses} chunks (concurrency: ${descConcurrency})`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
else if (allChunks.length > 0) {
|
|
434
|
+
// No descHash available (no description provider) — still generate descriptions
|
|
435
|
+
let describedDone = 0;
|
|
272
436
|
try {
|
|
273
|
-
const batchResult = await options.descriptionProvider.generateBatchDescriptions(allChunks, logger
|
|
437
|
+
const batchResult = await options.descriptionProvider.generateBatchDescriptions(allChunks, logger, {
|
|
438
|
+
total: allChunks.length,
|
|
439
|
+
onProgress: (chunk) => {
|
|
440
|
+
describedDone++;
|
|
441
|
+
const meta = chunkMeta.get(chunk.id);
|
|
442
|
+
if (meta)
|
|
443
|
+
logChunkProgress("Describing", meta.fileLabel, meta.index, meta.count, describedDone, allChunks.length);
|
|
444
|
+
},
|
|
445
|
+
});
|
|
274
446
|
for (const chunk of allChunks) {
|
|
275
447
|
const desc = batchResult.get(chunk.id);
|
|
276
448
|
if (desc && desc.trim().length > 0) {
|
|
@@ -292,10 +464,9 @@ async function runIndexPassInner(options, logger) {
|
|
|
292
464
|
}
|
|
293
465
|
}
|
|
294
466
|
}
|
|
295
|
-
//
|
|
296
|
-
//
|
|
467
|
+
// Cross-file embedding batch: collect all texts into a single queue,
|
|
468
|
+
// embed in one batched call (with concurrency), then distribute back.
|
|
297
469
|
const isOllama = options.embedder.name === "ollama";
|
|
298
|
-
const ollamaMaxBatch = options.config.indexing.ollamaMaxBatchSize ?? 4000;
|
|
299
470
|
const defaultBatchSize = options.config.indexing.embedBatchSize;
|
|
300
471
|
const defaultConcurrency = options.config.indexing.embedConcurrency ?? 1;
|
|
301
472
|
// File metadata look-up for manifest entries
|
|
@@ -303,48 +474,100 @@ async function runIndexPassInner(options, logger) {
|
|
|
303
474
|
// Serialised manifest-save queue — prevents concurrent write races and acts
|
|
304
475
|
// as a checkpoint for Ctrl+C resilience. Each worker appends to this chain
|
|
305
476
|
// after a successful store, so previously completed files are never lost.
|
|
477
|
+
// During a temp-store rebuild, saves go to the temp path so the real
|
|
478
|
+
// manifest stays consistent with the real store if the process is aborted.
|
|
479
|
+
const manifestTargetPath = () => tempStorePath ?? options.storePath;
|
|
306
480
|
let manifestSaveChain = Promise.resolve(undefined);
|
|
307
481
|
function enqueueManifestSave() {
|
|
308
|
-
manifestSaveChain = manifestSaveChain.then(() => saveManifest(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
482
|
+
manifestSaveChain = manifestSaveChain.then(() => saveManifest(manifestTargetPath(), manifest).catch((err) => {
|
|
483
|
+
options.logger?.warn?.(`Failed to save manifest: ${err.message}`);
|
|
484
|
+
}));
|
|
485
|
+
}
|
|
486
|
+
// ── Phase 1: Collect embed queue + handle early results ────────────────
|
|
487
|
+
const embedQueue = [];
|
|
488
|
+
let totalEmbedChunks = 0;
|
|
489
|
+
const earlyWorkerResults = new Map();
|
|
490
|
+
for (let fi = 0; fi < prepared.length; fi++) {
|
|
491
|
+
const prep = prepared[fi];
|
|
316
492
|
if (prep.earlyResult) {
|
|
317
|
-
// Remove stale manifest/store entries for files that no longer produce chunks
|
|
318
493
|
if (prep.earlyResult.isRemoved) {
|
|
319
494
|
await effectiveStore.deleteByFilePath(prep.normalizedPath);
|
|
320
495
|
options.keywordIndex?.removeByFilePath(prep.normalizedPath);
|
|
321
496
|
delete manifest.files[prep.normalizedPath];
|
|
322
497
|
enqueueManifestSave();
|
|
323
498
|
}
|
|
324
|
-
|
|
499
|
+
earlyWorkerResults.set(fi, prep.earlyResult);
|
|
500
|
+
continue;
|
|
325
501
|
}
|
|
326
502
|
if (!prep.chunks || !prep.textToEmbed || prep.textToEmbed.length === 0) {
|
|
327
503
|
options.progress?.finishFile(prep.fileLabel);
|
|
328
|
-
|
|
504
|
+
earlyWorkerResults.set(fi, {
|
|
329
505
|
normalizedPath: prep.normalizedPath, hash: prep.hash, chunkCount: 0,
|
|
330
506
|
fileLabel: prep.fileLabel,
|
|
331
507
|
isNew: false, isModified: false, isUnchanged: false, isEmpty: false,
|
|
332
508
|
isTooSmall: false, isRemoved: true, hadChunks: false,
|
|
333
509
|
descriptionFailed: prep.descriptionFailed,
|
|
334
|
-
};
|
|
510
|
+
});
|
|
511
|
+
continue;
|
|
335
512
|
}
|
|
336
|
-
const batchSize = isOllama
|
|
337
|
-
? Math.min(prep.textToEmbed.length, ollamaMaxBatch)
|
|
338
|
-
: defaultBatchSize;
|
|
339
|
-
const concurrency = isOllama ? 1 : defaultConcurrency;
|
|
340
513
|
options.progress?.finishStage(prep.fileLabel);
|
|
341
|
-
let
|
|
514
|
+
for (let ci = 0; ci < prep.textToEmbed.length; ci++) {
|
|
515
|
+
embedQueue.push({ fileIdx: fi, chunkIdx: ci, text: prep.textToEmbed[ci] });
|
|
516
|
+
}
|
|
517
|
+
totalEmbedChunks += prep.textToEmbed.length;
|
|
518
|
+
}
|
|
519
|
+
// ── Phase 2: Embed all texts in a single batched call ──────────────────
|
|
520
|
+
const batchSize = isOllama
|
|
521
|
+
? Math.min(options.config.indexing.ollamaMaxBatchSize ?? 4000, defaultBatchSize)
|
|
522
|
+
: defaultBatchSize;
|
|
523
|
+
let embeddedDone = 0;
|
|
524
|
+
const allTexts = embedQueue.map(item => item.text);
|
|
525
|
+
let allEmbeddings = [];
|
|
526
|
+
if (allTexts.length > 0) {
|
|
342
527
|
try {
|
|
343
|
-
|
|
528
|
+
allEmbeddings = await embedBatch(options.embedder, allTexts, batchSize, "document", defaultConcurrency, (completed, total) => {
|
|
529
|
+
embeddedDone = completed;
|
|
530
|
+
logChunkProgress("Embedding", "", completed, total, embeddedDone, totalEmbedChunks);
|
|
531
|
+
});
|
|
344
532
|
}
|
|
345
533
|
catch (err) {
|
|
346
|
-
logger.warn(`
|
|
347
|
-
|
|
534
|
+
logger.warn(` Global embedding failed: ${err.message}`);
|
|
535
|
+
for (const { fileIdx } of embedQueue) {
|
|
536
|
+
options.progress?.failFile(prepared[fileIdx].fileLabel);
|
|
537
|
+
earlyWorkerResults.set(fileIdx, {
|
|
538
|
+
normalizedPath: prepared[fileIdx].normalizedPath,
|
|
539
|
+
hash: prepared[fileIdx].hash,
|
|
540
|
+
chunkCount: 0, fileLabel: prepared[fileIdx].fileLabel,
|
|
541
|
+
isNew: false, isModified: false, isUnchanged: false, isEmpty: false,
|
|
542
|
+
isTooSmall: false, isRemoved: true, hadChunks: false,
|
|
543
|
+
descriptionFailed: prepared[fileIdx].descriptionFailed,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
embedQueue.length = 0; // prevent double-processing in store phase
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// ── Distribute embeddings back to per-file chunks ─────────────────────
|
|
550
|
+
for (let i = 0; i < embedQueue.length; i++) {
|
|
551
|
+
const { fileIdx, chunkIdx } = embedQueue[i];
|
|
552
|
+
const emb = allEmbeddings[i];
|
|
553
|
+
const prep = prepared[fileIdx];
|
|
554
|
+
if (prep.chunks && prep.chunks[chunkIdx] && Array.isArray(emb) && emb.length > 0 && typeof emb[0] === "number") {
|
|
555
|
+
prep.chunks[chunkIdx].embedding = emb;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// ── Phase 3: Store + manifest update per file (parallel) ──────────────
|
|
559
|
+
const storeLimit = pLimit(options.config.indexing.concurrency);
|
|
560
|
+
const storeResults = await Promise.all(prepared.map((prep, fi) => storeLimit(async () => {
|
|
561
|
+
if (aborted()) {
|
|
562
|
+
return { normalizedPath: prep.normalizedPath, skipped: true };
|
|
563
|
+
}
|
|
564
|
+
// Return early results from phase 1
|
|
565
|
+
const earlyResult = earlyWorkerResults.get(fi);
|
|
566
|
+
if (earlyResult)
|
|
567
|
+
return earlyResult;
|
|
568
|
+
// No-embed path (shouldn't reach here but guard anyway)
|
|
569
|
+
if (!prep.chunks || prep.textToEmbed?.length === 0) {
|
|
570
|
+
options.progress?.finishFile(prep.fileLabel);
|
|
348
571
|
return {
|
|
349
572
|
normalizedPath: prep.normalizedPath, hash: prep.hash, chunkCount: 0,
|
|
350
573
|
fileLabel: prep.fileLabel,
|
|
@@ -353,12 +576,30 @@ async function runIndexPassInner(options, logger) {
|
|
|
353
576
|
descriptionFailed: prep.descriptionFailed,
|
|
354
577
|
};
|
|
355
578
|
}
|
|
356
|
-
//
|
|
357
|
-
const
|
|
358
|
-
|
|
579
|
+
// Store chunks with pre-attached embeddings
|
|
580
|
+
const validChunks = (prep.chunks ?? []).filter((c) => c.embedding && c.embedding.length > 0);
|
|
581
|
+
if (validChunks.length > 0) {
|
|
582
|
+
await effectiveStore.addChunks(validChunks);
|
|
583
|
+
}
|
|
584
|
+
const result = {
|
|
585
|
+
normalizedPath: prep.normalizedPath,
|
|
586
|
+
hash: prep.hash,
|
|
587
|
+
chunkCount: prep.chunks?.length ?? 0,
|
|
588
|
+
fileLabel: prep.fileLabel,
|
|
589
|
+
isNew: !prep.isModified,
|
|
590
|
+
isModified: prep.isModified,
|
|
591
|
+
isUnchanged: false,
|
|
592
|
+
isEmpty: false,
|
|
593
|
+
isTooSmall: false,
|
|
594
|
+
isRemoved: validChunks.length === 0,
|
|
595
|
+
hadChunks: (prep.chunks?.length ?? 0) > 0,
|
|
596
|
+
descriptionFailed: prep.descriptionFailed,
|
|
597
|
+
descHash: prep.descHash,
|
|
598
|
+
};
|
|
599
|
+
// Update manifest
|
|
359
600
|
if (result.chunkCount > 0 && !result.isRemoved) {
|
|
360
601
|
const meta = fileMeta.get(result.normalizedPath);
|
|
361
|
-
|
|
602
|
+
const entry = {
|
|
362
603
|
hash: result.hash,
|
|
363
604
|
chunkCount: result.chunkCount,
|
|
364
605
|
indexedAt: Date.now(),
|
|
@@ -366,6 +607,10 @@ async function runIndexPassInner(options, logger) {
|
|
|
366
607
|
size: meta?.size,
|
|
367
608
|
descriptionFailed: result.descriptionFailed,
|
|
368
609
|
};
|
|
610
|
+
if (result.descHash) {
|
|
611
|
+
entry.descHash = result.descHash;
|
|
612
|
+
}
|
|
613
|
+
manifest.files[result.normalizedPath] = entry;
|
|
369
614
|
enqueueManifestSave();
|
|
370
615
|
}
|
|
371
616
|
else if (result.isRemoved) {
|
|
@@ -375,6 +620,7 @@ async function runIndexPassInner(options, logger) {
|
|
|
375
620
|
options.progress?.finishFile(prep.fileLabel);
|
|
376
621
|
return result;
|
|
377
622
|
})));
|
|
623
|
+
const workerResults = storeResults;
|
|
378
624
|
const finalResults = [];
|
|
379
625
|
for (const r of workerResults) {
|
|
380
626
|
if (r.skipped)
|
|
@@ -406,9 +652,7 @@ async function runIndexPassInner(options, logger) {
|
|
|
406
652
|
// Swap the newly-built temp directory into the real path
|
|
407
653
|
await swapStoreDirectories(tempStorePath, options.storePath);
|
|
408
654
|
// Re-open the original store handle so callers can search the new data
|
|
409
|
-
|
|
410
|
-
await options.store.reopen(options.storePath);
|
|
411
|
-
}
|
|
655
|
+
await options.store.reopen?.(options.storePath);
|
|
412
656
|
logger.debug(`Promoted temporary store ${tempStorePath} → ${options.storePath}`);
|
|
413
657
|
}
|
|
414
658
|
catch (err) {
|
|
@@ -421,19 +665,28 @@ async function runIndexPassInner(options, logger) {
|
|
|
421
665
|
}
|
|
422
666
|
}
|
|
423
667
|
else {
|
|
424
|
-
// Aborted — discard temp, keep original data intact
|
|
668
|
+
// Aborted — discard temp, keep original data intact.
|
|
669
|
+
// Do NOT save the manifest to the real path — the in-memory manifest
|
|
670
|
+
// was cleared at rebuild start and only partially rebuilt. The old
|
|
671
|
+
// manifest on disk (at the real path) is still consistent with the
|
|
672
|
+
// old store data.
|
|
425
673
|
effectiveStore.close().catch(() => { });
|
|
426
674
|
try {
|
|
427
675
|
await fs.rm(tempStorePath, { recursive: true, force: true });
|
|
428
676
|
}
|
|
429
677
|
catch { }
|
|
430
678
|
logger.debug("Index pass cancelled; discarded temporary store.");
|
|
679
|
+
// Skip final manifest/keyword save since we want the old state preserved
|
|
680
|
+
await descCache.save();
|
|
681
|
+
return stats;
|
|
431
682
|
}
|
|
432
683
|
}
|
|
433
684
|
// Save manifest and keyword index (always to the real store path — after
|
|
434
685
|
// a successful swap this points to the new data; after an abort it's the old).
|
|
435
686
|
await saveManifest(options.storePath, manifest);
|
|
436
687
|
await options.keywordIndex?.save(options.storePath);
|
|
688
|
+
// Persist description cache
|
|
689
|
+
await descCache.save();
|
|
437
690
|
// Count from the store — after a successful swap, the original handle has
|
|
438
691
|
// been reopened pointing to the new directory.
|
|
439
692
|
try {
|
package/dist/indexer/watch.js
CHANGED
package/dist/indexer/worker.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface WorkerResult {
|
|
|
26
26
|
hadChunks: boolean;
|
|
27
27
|
/** Whether description generation failed for this file. */
|
|
28
28
|
descriptionFailed?: boolean;
|
|
29
|
+
/** Hash of the description config used when generating descriptions for this file. */
|
|
30
|
+
descHash?: string;
|
|
29
31
|
}
|
|
30
32
|
/** Intermediate result after chunking a file but before embedding/storing. */
|
|
31
33
|
export interface PreparedFile {
|
|
@@ -53,6 +55,8 @@ export interface PreparedFile {
|
|
|
53
55
|
docPrefix?: string;
|
|
54
56
|
/** Whether the source file is an image. */
|
|
55
57
|
isImageFile?: boolean;
|
|
58
|
+
/** Hash of the description config used when generating descriptions. */
|
|
59
|
+
descHash?: string;
|
|
56
60
|
}
|
|
57
61
|
/**
|
|
58
62
|
* Build the list of text strings that will be sent to the embedding provider.
|
|
@@ -76,6 +80,7 @@ interface ManifestFile {
|
|
|
76
80
|
hash: string;
|
|
77
81
|
chunkCount: number;
|
|
78
82
|
indexedAt?: number;
|
|
83
|
+
descHash?: string;
|
|
79
84
|
}
|
|
80
85
|
/**
|
|
81
86
|
* Prepare a single workspace file for indexing: chunk the content, build
|
|
@@ -92,6 +97,10 @@ interface ManifestFile {
|
|
|
92
97
|
* @param deferDescriptions - When true, skip description generation and build
|
|
93
98
|
* fallback descriptions instead; descriptions are
|
|
94
99
|
* expected to be generated in a later global pass.
|
|
100
|
+
* @param descHash - Hash of the current description config. When the file
|
|
101
|
+
* is unchanged and `previous.descHash === descHash`,
|
|
102
|
+
* descriptions are skipped entirely (they already exist
|
|
103
|
+
* in the vector store from a prior run).
|
|
95
104
|
* @returns A prepared file descriptor ready for embedding and storage.
|
|
96
105
|
*/
|
|
97
106
|
export declare function prepareFile(file: WorkspaceFile, cwd: string, previous: ManifestFile | undefined, config: {
|
|
@@ -104,7 +113,10 @@ export declare function prepareFile(file: WorkspaceFile, cwd: string, previous:
|
|
|
104
113
|
description?: {
|
|
105
114
|
maxContentChars?: number;
|
|
106
115
|
};
|
|
107
|
-
|
|
116
|
+
indexing?: {
|
|
117
|
+
maxSvgSizeBytes?: number;
|
|
118
|
+
};
|
|
119
|
+
}, keywordIndex: KeywordIndex | undefined, descriptionProvider: DescriptionProvider | undefined, logger: Logger, deferDescriptions?: boolean, descHash?: string): Promise<PreparedFile>;
|
|
108
120
|
/**
|
|
109
121
|
* Store the chunks of a prepared file into the vector store, attaching their
|
|
110
122
|
* computed embeddings.
|
|
@@ -139,6 +151,7 @@ export declare function processFile(file: WorkspaceFile, cwd: string, previous:
|
|
|
139
151
|
indexing: {
|
|
140
152
|
embedBatchSize: number;
|
|
141
153
|
embedConcurrency?: number;
|
|
154
|
+
maxSvgSizeBytes?: number;
|
|
142
155
|
};
|
|
143
156
|
chunking?: {
|
|
144
157
|
nodeTypes?: Record<string, string[]>;
|
|
@@ -146,5 +159,5 @@ export declare function processFile(file: WorkspaceFile, cwd: string, previous:
|
|
|
146
159
|
description?: {
|
|
147
160
|
maxContentChars?: number;
|
|
148
161
|
};
|
|
149
|
-
}, store: VectorStore, keywordIndex: KeywordIndex | undefined, embedder: EmbeddingProvider, descriptionProvider: DescriptionProvider | undefined, logger: Logger): Promise<WorkerResult>;
|
|
162
|
+
}, store: VectorStore, keywordIndex: KeywordIndex | undefined, embedder: EmbeddingProvider, descriptionProvider: DescriptionProvider | undefined, logger: Logger, descHash?: string): Promise<WorkerResult>;
|
|
150
163
|
export {};
|
package/dist/indexer/worker.js
CHANGED
|
@@ -52,9 +52,13 @@ export function buildTextsToEmbed(chunks, relPath, metaHeader, docPrefix, isImag
|
|
|
52
52
|
* @param deferDescriptions - When true, skip description generation and build
|
|
53
53
|
* fallback descriptions instead; descriptions are
|
|
54
54
|
* expected to be generated in a later global pass.
|
|
55
|
+
* @param descHash - Hash of the current description config. When the file
|
|
56
|
+
* is unchanged and `previous.descHash === descHash`,
|
|
57
|
+
* descriptions are skipped entirely (they already exist
|
|
58
|
+
* in the vector store from a prior run).
|
|
55
59
|
* @returns A prepared file descriptor ready for embedding and storage.
|
|
56
60
|
*/
|
|
57
|
-
export async function prepareFile(file, cwd, previous, config, keywordIndex, descriptionProvider, logger, deferDescriptions) {
|
|
61
|
+
export async function prepareFile(file, cwd, previous, config, keywordIndex, descriptionProvider, logger, deferDescriptions, descHash) {
|
|
58
62
|
const fileLabel = path.relative(cwd, file.filePath).replace(/\\/g, "/");
|
|
59
63
|
if (file.isEmpty) {
|
|
60
64
|
return {
|
|
@@ -77,14 +81,18 @@ export async function prepareFile(file, cwd, previous, config, keywordIndex, des
|
|
|
77
81
|
};
|
|
78
82
|
}
|
|
79
83
|
if (previous && previous.hash === file.hash) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
if (!descHash || previous.descHash === descHash) {
|
|
85
|
+
return {
|
|
86
|
+
normalizedPath: file.normalizedPath, hash: file.hash, fileLabel,
|
|
87
|
+
isModified: false,
|
|
88
|
+
earlyResult: {
|
|
89
|
+
normalizedPath: file.normalizedPath, hash: file.hash, chunkCount: 0, fileLabel,
|
|
90
|
+
isNew: false, isModified: false, isUnchanged: true, isEmpty: false, isTooSmall: false, isRemoved: false, hadChunks: false,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// File content is unchanged but description config changed — fall through to re-describe
|
|
95
|
+
logger.debug(` ${fileLabel} (unchanged but descHash differs — re-describing)`);
|
|
88
96
|
}
|
|
89
97
|
const isModified = !!previous;
|
|
90
98
|
let chunks;
|
|
@@ -102,7 +110,9 @@ export async function prepareFile(file, cwd, previous, config, keywordIndex, des
|
|
|
102
110
|
}];
|
|
103
111
|
}
|
|
104
112
|
else {
|
|
105
|
-
chunks = await chunkFile(file.filePath, file.content, config.chunking?.nodeTypes
|
|
113
|
+
chunks = await chunkFile(file.filePath, file.content, config.chunking?.nodeTypes, {
|
|
114
|
+
maxSvgSizeBytes: config.indexing?.maxSvgSizeBytes,
|
|
115
|
+
}).catch((err) => {
|
|
106
116
|
logger.warn(` ${fileLabel} (chunking failed: ${err.message})`);
|
|
107
117
|
return null;
|
|
108
118
|
});
|
|
@@ -152,6 +162,7 @@ export async function prepareFile(file, cwd, previous, config, keywordIndex, des
|
|
|
152
162
|
metaHeader,
|
|
153
163
|
docPrefix,
|
|
154
164
|
isImageFile: isImage,
|
|
165
|
+
descHash,
|
|
155
166
|
};
|
|
156
167
|
}
|
|
157
168
|
let descriptionFailed = false;
|
|
@@ -188,6 +199,7 @@ export async function prepareFile(file, cwd, previous, config, keywordIndex, des
|
|
|
188
199
|
chunks,
|
|
189
200
|
textToEmbed,
|
|
190
201
|
descriptionFailed,
|
|
202
|
+
descHash,
|
|
191
203
|
};
|
|
192
204
|
}
|
|
193
205
|
/**
|
|
@@ -235,6 +247,7 @@ export async function storeFileChunks(prep, embeddings, store, _logger) {
|
|
|
235
247
|
isRemoved: false,
|
|
236
248
|
hadChunks: prep.chunks.length > 0,
|
|
237
249
|
descriptionFailed: prep.descriptionFailed,
|
|
250
|
+
descHash: prep.descHash,
|
|
238
251
|
};
|
|
239
252
|
}
|
|
240
253
|
/**
|
|
@@ -253,8 +266,8 @@ export async function storeFileChunks(prep, embeddings, store, _logger) {
|
|
|
253
266
|
* @param logger - Logger for diagnostic messages.
|
|
254
267
|
* @returns A worker result describing the outcome.
|
|
255
268
|
*/
|
|
256
|
-
export async function processFile(file, cwd, previous, config, store, keywordIndex, embedder, descriptionProvider, logger) {
|
|
257
|
-
const prep = await prepareFile(file, cwd, previous, config, keywordIndex, descriptionProvider, logger);
|
|
269
|
+
export async function processFile(file, cwd, previous, config, store, keywordIndex, embedder, descriptionProvider, logger, descHash) {
|
|
270
|
+
const prep = await prepareFile(file, cwd, previous, config, keywordIndex, descriptionProvider, logger, false, descHash);
|
|
258
271
|
if (prep.earlyResult) {
|
|
259
272
|
if ((prep.earlyResult.isEmpty || prep.earlyResult.isTooSmall) && prep.earlyResult.isRemoved) {
|
|
260
273
|
await store.deleteByFilePath(prep.normalizedPath);
|
package/dist/mcp/handlers.d.ts
CHANGED
|
@@ -5,6 +5,15 @@ import type { EmbeddingProvider, VectorStore, KeywordIndex, SearchResult } from
|
|
|
5
5
|
import type { RagConfig } from "../core/config.js";
|
|
6
6
|
import { type ImageVisionProvider } from "../chunker/image.js";
|
|
7
7
|
import { retrieve } from "../retriever/retriever.js";
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a user-supplied file path against the worktree root, refusing to
|
|
10
|
+
* escape the worktree. Absolute paths are accepted only if they already reside
|
|
11
|
+
* under the worktree; relative paths are joined to the worktree and any `..`
|
|
12
|
+
* segments that would escape are rejected.
|
|
13
|
+
*
|
|
14
|
+
* @throws {Error} if the resolved path lies outside `worktree`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveFilePath(filePath: string, worktree: string): string;
|
|
8
17
|
/** Parameters for the search_semantic MCP tool. */
|
|
9
18
|
export interface SearchSemanticParams {
|
|
10
19
|
/** Natural language search query. */
|