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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import { existsSync, mkdirSync, rmSync, symlinkSync, } from "node:fs";
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
11
12
|
import { DEFAULT_CONFIG } from "../../core/config.js";
|
|
12
13
|
import { c } from "../format.js";
|
|
13
14
|
import { getStringRecord, readJsonObject, writeJsonFile, } from "../helpers.js";
|
|
@@ -61,7 +62,7 @@ export function buildOpencodeConfig(existing) {
|
|
|
61
62
|
// Plugin is loaded via .opencode/plugins/rag-plugin.js auto-discovery,
|
|
62
63
|
// not via npm package resolution. Stale "plugin" entries from older
|
|
63
64
|
// init versions would trigger npm install (which fails due to native
|
|
64
|
-
// dependencies like
|
|
65
|
+
// dependencies like sharp) and produce "Plugin export is not a function".
|
|
65
66
|
delete next.plugin;
|
|
66
67
|
return next;
|
|
67
68
|
}
|
|
@@ -247,6 +248,48 @@ export function mergeGitignoreContent(existingContent) {
|
|
|
247
248
|
merged.push("# OpenCodeRAG workspace state", ...missing, "");
|
|
248
249
|
return merged.join("\n");
|
|
249
250
|
}
|
|
251
|
+
const BEGIN_MARKER = "<!-- BEGIN opencode-rag -->";
|
|
252
|
+
const END_MARKER = "<!-- END opencode-rag -->";
|
|
253
|
+
const AGENTS_MD_SECTION = [
|
|
254
|
+
BEGIN_MARKER,
|
|
255
|
+
"## Code Navigation",
|
|
256
|
+
"",
|
|
257
|
+
"ALWAYS use OpenCodeRAG tools before reading or editing:",
|
|
258
|
+
"- **Search first** — `search_semantic(query)` instead of grep/glob",
|
|
259
|
+
"- **Skeleton before read** — `get_file_skeleton(filePath)` then read specific lines",
|
|
260
|
+
"- **Usages before edit** — `find_usages(symbolName)` before modifying any symbol",
|
|
261
|
+
"- **Images via describe** — `describe_image(filePath)` — never read raw bytes",
|
|
262
|
+
"",
|
|
263
|
+
"If no results, run `opencode-rag index`.",
|
|
264
|
+
END_MARKER,
|
|
265
|
+
].join("\n");
|
|
266
|
+
/**
|
|
267
|
+
* Merge the OpenCodeRAG directive section into an existing `AGENTS.md`.
|
|
268
|
+
*
|
|
269
|
+
* The directive is wrapped in sentinel markers so re-runs replace the
|
|
270
|
+
* section in place rather than appending duplicates. Existing content
|
|
271
|
+
* outside the markers is always preserved. If no existing content is
|
|
272
|
+
* provided, the section alone is returned as a new file.
|
|
273
|
+
*
|
|
274
|
+
* @param existingContent - The current `AGENTS.md` content, or `undefined` if absent.
|
|
275
|
+
* @returns The merged `AGENTS.md` content with a trailing newline.
|
|
276
|
+
*/
|
|
277
|
+
export function mergeAgentsMdContent(existingContent) {
|
|
278
|
+
if (!existingContent) {
|
|
279
|
+
return `${AGENTS_MD_SECTION}\n`;
|
|
280
|
+
}
|
|
281
|
+
const beginIdx = existingContent.indexOf(BEGIN_MARKER);
|
|
282
|
+
const endIdx = existingContent.indexOf(END_MARKER);
|
|
283
|
+
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
|
|
284
|
+
const before = existingContent.slice(0, beginIdx);
|
|
285
|
+
const after = existingContent.slice(endIdx + END_MARKER.length);
|
|
286
|
+
const merged = `${before}${AGENTS_MD_SECTION}${after}`;
|
|
287
|
+
return merged.endsWith("\n") ? merged : `${merged}\n`;
|
|
288
|
+
}
|
|
289
|
+
const trimmed = existingContent.trimEnd();
|
|
290
|
+
const separator = trimmed.endsWith(BEGIN_MARKER) ? "" : "\n\n";
|
|
291
|
+
return `${trimmed}${separator}${AGENTS_MD_SECTION}\n`;
|
|
292
|
+
}
|
|
250
293
|
/**
|
|
251
294
|
* Get the runtime directory path (`~/.opencode`).
|
|
252
295
|
*
|
|
@@ -290,8 +333,47 @@ export async function installPluginFromGlobal(opencodeDir, packageName, skipInst
|
|
|
290
333
|
const globalPluginDir = path.join(runtimeDir, "node_modules", packageName);
|
|
291
334
|
const workspaceTarget = path.join(opencodeDir, "node_modules", packageName);
|
|
292
335
|
if (!existsSync(globalPluginDir)) {
|
|
336
|
+
// Fallback: try the global npm prefix
|
|
337
|
+
let globalRoot;
|
|
338
|
+
try {
|
|
339
|
+
globalRoot = execSync("npm root -g", { encoding: "utf-8", timeout: 10_000 }).trim();
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
throw new Error(`Global plugin cache not found at ${globalPluginDir}. ` +
|
|
343
|
+
"Run 'opencode-rag setup' or install globally with 'npm install -g opencode-rag-plugin' first.");
|
|
344
|
+
}
|
|
345
|
+
const globalNpmPluginDir = path.join(globalRoot, packageName);
|
|
346
|
+
if (existsSync(globalNpmPluginDir) && existsSync(path.join(globalNpmPluginDir, "dist", "cli.js"))) {
|
|
347
|
+
console.log(` ${c.created("Found:")} ${packageName} in global npm prefix, linking...`);
|
|
348
|
+
if (existsSync(workspaceTarget)) {
|
|
349
|
+
rmSync(workspaceTarget, { recursive: true, force: true });
|
|
350
|
+
}
|
|
351
|
+
mkdirSync(path.dirname(workspaceTarget), { recursive: true });
|
|
352
|
+
createJunction(globalNpmPluginDir, workspaceTarget);
|
|
353
|
+
const cliEntry = path.join(workspaceTarget, "dist", "cli.js");
|
|
354
|
+
if (existsSync(cliEntry)) {
|
|
355
|
+
console.log(` ${c.success("Linked:")} ${packageName} from global npm`);
|
|
356
|
+
// Also link @opencode-ai/plugin from global npm
|
|
357
|
+
const globalSdkDir = path.join(globalRoot, "@opencode-ai", "plugin");
|
|
358
|
+
const workspaceSdkDir = path.join(opencodeDir, "node_modules", "@opencode-ai", "plugin");
|
|
359
|
+
if (existsSync(globalSdkDir) && !existsSync(workspaceSdkDir)) {
|
|
360
|
+
mkdirSync(path.dirname(workspaceSdkDir), { recursive: true });
|
|
361
|
+
try {
|
|
362
|
+
createJunction(globalSdkDir, workspaceSdkDir);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
const { cpSync } = await import("node:fs");
|
|
366
|
+
cpSync(globalSdkDir, workspaceSdkDir, { recursive: true });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
console.log(` ${c.success("Linked:")} @opencode-ai/plugin from global npm`);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
// Junction failed or incomplete — fall through to error
|
|
373
|
+
rmSync(workspaceTarget, { recursive: true, force: true });
|
|
374
|
+
}
|
|
293
375
|
throw new Error(`Global plugin cache not found at ${globalPluginDir}. ` +
|
|
294
|
-
"Run '
|
|
376
|
+
"Run 'opencode-rag setup' or install globally with 'npm install -g opencode-rag-plugin' first.");
|
|
295
377
|
}
|
|
296
378
|
// Remove any stale copy (e.g. from a previous 'npm install' run)
|
|
297
379
|
if (existsSync(workspaceTarget)) {
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* installation.
|
|
11
11
|
*/
|
|
12
12
|
import path from "node:path";
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from "node:fs";
|
|
14
14
|
import { loadConfig } from "../../core/config.js";
|
|
15
15
|
import { checkProviderHealth, pullOllamaModels } from "../../embedder/health.js";
|
|
16
16
|
import { destroyAllPooledConnections } from "../../embedder/http.js";
|
|
17
17
|
import { c } from "../format.js";
|
|
18
18
|
import { getPackageMetadata, readJsonObject, writeJsonFile } from "../helpers.js";
|
|
19
|
-
import { buildOpencodeConfig, buildWorkspacePackageJson, generateDefaultConfigJson, generateSkillFile, generateWorkspacePluginFile, generateWorkspaceTuiPluginFile, installPluginFromGlobal, mergeGitignoreContent, } from "./init-helpers.js";
|
|
19
|
+
import { buildOpencodeConfig, buildWorkspacePackageJson, generateDefaultConfigJson, generateSkillFile, generateWorkspacePluginFile, generateWorkspaceTuiPluginFile, installPluginFromGlobal, mergeAgentsMdContent, mergeGitignoreContent, } from "./init-helpers.js";
|
|
20
20
|
/**
|
|
21
21
|
* Register the `init` command on the given Commander program.
|
|
22
22
|
*
|
|
@@ -142,6 +142,20 @@ export function registerInitCommand(program) {
|
|
|
142
142
|
else {
|
|
143
143
|
console.log(` ${c.exists("Exists:")} .opencode/skills/opencode-rag/SKILL.md`);
|
|
144
144
|
}
|
|
145
|
+
const agentsMdPath = path.join(cwd, "AGENTS.md");
|
|
146
|
+
const agentsMdExists = existsSync(agentsMdPath);
|
|
147
|
+
const nextAgentsMd = mergeAgentsMdContent(agentsMdExists ? readFileSync(agentsMdPath, "utf-8") : undefined);
|
|
148
|
+
if (!agentsMdExists || options.force) {
|
|
149
|
+
writeFileSync(agentsMdPath, nextAgentsMd, "utf-8");
|
|
150
|
+
console.log(` ${agentsMdExists ? c.updated("Updated:") : c.created("Created:")} AGENTS.md`);
|
|
151
|
+
}
|
|
152
|
+
else if (readFileSync(agentsMdPath, "utf-8") !== nextAgentsMd) {
|
|
153
|
+
writeFileSync(agentsMdPath, nextAgentsMd, "utf-8");
|
|
154
|
+
console.log(` ${c.updated("Updated:")} AGENTS.md`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.log(` ${c.exists("Exists:")} AGENTS.md`);
|
|
158
|
+
}
|
|
145
159
|
const workspacePackageExists = existsSync(opencodePackagePath);
|
|
146
160
|
const nextWorkspacePackage = buildWorkspacePackageJson(readJsonObject(opencodePackagePath), packageMetadata);
|
|
147
161
|
if (!workspacePackageExists || options.force) {
|
|
@@ -157,6 +171,10 @@ export function registerInitCommand(program) {
|
|
|
157
171
|
}
|
|
158
172
|
const configExists = existsSync(configPath);
|
|
159
173
|
if (!configExists || options.force) {
|
|
174
|
+
if (configExists && options.force) {
|
|
175
|
+
copyFileSync(configPath, configPath + ".bak");
|
|
176
|
+
console.log(` ${c.dim("Backup:")} ${configPath}.bak`);
|
|
177
|
+
}
|
|
160
178
|
writeFileSync(configPath, generateDefaultConfigJson(), "utf-8");
|
|
161
179
|
console.log(` ${configExists ? c.updated("Updated:") : c.created("Created:")} opencode-rag.json`);
|
|
162
180
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { existsSync, lstatSync, rmSync } from "node:fs";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { c } from "../format.js";
|
|
5
|
+
import { getPackageMetadata } from "../helpers.js";
|
|
6
|
+
import { getRuntimeDir, getVersionFile, readVersionFile, setupRuntime, } from "../../core/setup-runtime.js";
|
|
7
|
+
const PLUGIN_NAME = "opencode-rag-plugin";
|
|
8
|
+
function removeIfExists(targetPath) {
|
|
9
|
+
if (existsSync(targetPath)) {
|
|
10
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function checkOpenCodeRunning() {
|
|
14
|
+
try {
|
|
15
|
+
const pids = execSync("pgrep -x opencode 2>/dev/null || (Get-Process -Name opencode -ErrorAction SilentlyContinue 2>nul | ForEach-Object { $_.Id })", {
|
|
16
|
+
encoding: "utf-8",
|
|
17
|
+
timeout: 3_000,
|
|
18
|
+
}).trim();
|
|
19
|
+
if (pids) {
|
|
20
|
+
console.log(`\n ${c.warn("OpenCode is currently running.")} Restart it to load the updated plugin.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// pgrep/Get-Process aren't available or succeeded silently
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function registerSetupCommand(program) {
|
|
28
|
+
program
|
|
29
|
+
.command("setup")
|
|
30
|
+
.description("Set up the OpenCodeRAG runtime (~/.opencode/) for OpenCode plugin discovery")
|
|
31
|
+
.option("--uninstall", "remove the runtime and cleanup")
|
|
32
|
+
.option("-f, --force", "force re-setup even if up-to-date")
|
|
33
|
+
.option("--check", "check whether the runtime is correctly installed")
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
const pkg = getPackageMetadata();
|
|
36
|
+
const pluginVersion = pkg.version;
|
|
37
|
+
const runtimeDir = getRuntimeDir();
|
|
38
|
+
const versionFile = getVersionFile(runtimeDir);
|
|
39
|
+
const runtimePluginDir = path.join(runtimeDir, "node_modules", PLUGIN_NAME);
|
|
40
|
+
const runtimeSdkDir = path.join(runtimeDir, "node_modules", "@opencode-ai");
|
|
41
|
+
const runtimeSdkPluginDir = path.join(runtimeSdkDir, "plugin");
|
|
42
|
+
// --- check status ---
|
|
43
|
+
if (options.check) {
|
|
44
|
+
console.log(`\n${c.heading("OpenCodeRAG Runtime Status")}\n`);
|
|
45
|
+
const runtimeDist = path.join(runtimePluginDir, "dist");
|
|
46
|
+
if (existsSync(runtimeDist)) {
|
|
47
|
+
console.log(` ${c.label("Runtime:")} ${c.success("installed")} at ${c.file(runtimeDir)}`);
|
|
48
|
+
const installedVersion = readVersionFile(versionFile);
|
|
49
|
+
console.log(` ${c.label("Installed:")} ${c.value(installedVersion ?? "?")}`);
|
|
50
|
+
if (installedVersion !== pluginVersion) {
|
|
51
|
+
console.log(` ${c.label("Published:")} ${c.value(pluginVersion)} ${c.warn("(update available — run `opencode-rag setup` to sync)")}`);
|
|
52
|
+
}
|
|
53
|
+
console.log(` ${c.label("Plugin:")} ${existsSync(runtimePluginDir) ? (() => {
|
|
54
|
+
try {
|
|
55
|
+
const stat = lstatSync(runtimePluginDir);
|
|
56
|
+
return stat.isSymbolicLink() ? "junction" : "directory";
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return "?";
|
|
60
|
+
}
|
|
61
|
+
})() : "missing"}`);
|
|
62
|
+
console.log(` ${c.label("SDK:")} ${existsSync(runtimeSdkPluginDir) ? "present" : "missing"}`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(` ${c.label("Runtime:")} ${c.warn("not installed")}`);
|
|
66
|
+
console.log(` ${c.label("Run:")} ${c.file("opencode-rag setup")} to install`);
|
|
67
|
+
}
|
|
68
|
+
console.log();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// --- uninstall ---
|
|
72
|
+
if (options.uninstall) {
|
|
73
|
+
console.log(`\n${c.heading("Removing OpenCodeRAG runtime...")}\n`);
|
|
74
|
+
removeIfExists(runtimePluginDir);
|
|
75
|
+
removeIfExists(runtimeSdkDir);
|
|
76
|
+
removeIfExists(versionFile);
|
|
77
|
+
console.log(` ${c.updated("Removed:")} ${c.file(runtimeDir)}`);
|
|
78
|
+
console.log(`\n ${c.success("Done.")} Run ${c.file("npm uninstall -g opencode-rag-plugin")} to remove the global package.\n`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// --- install ---
|
|
82
|
+
console.log(`\n${c.heading("Setting up OpenCodeRAG runtime...")}\n`);
|
|
83
|
+
// Check if already installed and up-to-date
|
|
84
|
+
const installedVersion = readVersionFile(versionFile);
|
|
85
|
+
const runtimeDist = path.join(runtimePluginDir, "dist");
|
|
86
|
+
const alreadyInstalled = existsSync(runtimeDist);
|
|
87
|
+
if (alreadyInstalled && installedVersion === pluginVersion && !options.force) {
|
|
88
|
+
console.log(` ${c.success("Already up-to-date.")} (${c.value(pluginVersion)}) at ${c.file(runtimeDir)}\n`);
|
|
89
|
+
console.log(` ${c.dim("Run `opencode-rag setup --force` to re-install.")}\n`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const result = await setupRuntime({ force: options.force, version: pluginVersion });
|
|
93
|
+
if (result.success) {
|
|
94
|
+
console.log(` ${c.created("Updated:")} runtime at ${c.file(runtimeDir)}`);
|
|
95
|
+
console.log(` ${c.created("Version:")} ${c.value(pluginVersion)}`);
|
|
96
|
+
console.log(`\n${c.success("Setup complete.")}`);
|
|
97
|
+
console.log(`\n ${c.dim("Next steps:")}`);
|
|
98
|
+
console.log(` ${c.dim(" 1. Restart OpenCode if it is running")}`);
|
|
99
|
+
console.log(` ${c.dim(" 2. Run `opencode-rag init` in each workspace")}`);
|
|
100
|
+
console.log(` ${c.dim(" 3. Run `opencode-rag index` to build the search index")}`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
for (const err of result.errors) {
|
|
104
|
+
console.error(` ${c.error("✗")} ${err}`);
|
|
105
|
+
}
|
|
106
|
+
console.error(`\n ${c.error("Setup failed. Please check the errors above or run with --force.\n")}`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
checkOpenCodeRunning();
|
|
110
|
+
console.log();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* `status` command — shows index statistics, store health, and configuration summary.
|
|
6
6
|
*/
|
|
7
7
|
import path from "node:path";
|
|
8
|
+
import os from "node:os";
|
|
8
9
|
import fs from "node:fs";
|
|
9
10
|
import { c, resolveCliContext, cleanupContext, logCliError, logCliInfo, formatTimestamp } from "../format.js";
|
|
10
11
|
import { getIndexStatusSummary } from "../../indexer.js";
|
|
12
|
+
import { getPackageMetadata } from "../helpers.js";
|
|
13
|
+
import { checkForUpdate } from "../../core/version-check.js";
|
|
11
14
|
/**
|
|
12
15
|
* Check for a stale index.lock file and clean it up if the owning process is dead.
|
|
13
16
|
*
|
|
@@ -62,7 +65,7 @@ export function registerStatusCommand(program) {
|
|
|
62
65
|
try {
|
|
63
66
|
const cwd = process.cwd();
|
|
64
67
|
let logFilePath = path.resolve(cwd, ".opencode", "opencode-rag.log");
|
|
65
|
-
const ctx = await resolveCliContext(options, logFilePath, { skipProbe: true
|
|
68
|
+
const ctx = await resolveCliContext(options, logFilePath, { skipProbe: true });
|
|
66
69
|
const { config, store, storePath, keywordIndex } = ctx;
|
|
67
70
|
logFilePath = ctx.logFilePath;
|
|
68
71
|
checkStaleLock(storePath, logFilePath);
|
|
@@ -83,7 +86,34 @@ export function registerStatusCommand(program) {
|
|
|
83
86
|
logCliInfo(logFilePath, "status", `${c.label("Pending files:")} ${c.num(summary.pendingFiles)}`);
|
|
84
87
|
logCliInfo(logFilePath, "status", `${c.label("Indexed chunks:")} ${c.num(summary.storeChunkCount)}`);
|
|
85
88
|
logCliInfo(logFilePath, "status", `${c.label("Expected chunks:")} ${c.num(summary.manifestExpectedChunks)}`);
|
|
86
|
-
|
|
89
|
+
// Read watcher status from the background auto-indexer
|
|
90
|
+
const watcherStatusPath = path.join(storePath, "watcher-status.json");
|
|
91
|
+
let watchModeDisplay;
|
|
92
|
+
if (fs.existsSync(watcherStatusPath)) {
|
|
93
|
+
try {
|
|
94
|
+
const raw = fs.readFileSync(watcherStatusPath, "utf-8");
|
|
95
|
+
const ws = JSON.parse(raw);
|
|
96
|
+
if (ws.lastRunAt) {
|
|
97
|
+
const lastRun = formatTimestamp(ws.lastRunAt);
|
|
98
|
+
watchModeDisplay = ws.running
|
|
99
|
+
? c.enabled("active") + c.dim(" (last run: " + lastRun + ")")
|
|
100
|
+
: c.enabled("on") + c.dim(" (last run: " + lastRun + ")");
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
watchModeDisplay = c.enabled("on");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
watchModeDisplay = c.enabled("on") + c.dim(" (status unknown)");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (config.openCode.autoIndex?.enabled) {
|
|
111
|
+
watchModeDisplay = c.enabled("enabled (config)");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
watchModeDisplay = c.dim("off");
|
|
115
|
+
}
|
|
116
|
+
logCliInfo(logFilePath, "status", `${c.label("Watch mode:")} ${watchModeDisplay}`);
|
|
87
117
|
const kiCount = config.retrieval.hybridSearch?.enabled
|
|
88
118
|
? keywordIndex?.count() ?? 0
|
|
89
119
|
: 0;
|
|
@@ -94,6 +124,38 @@ export function registerStatusCommand(program) {
|
|
|
94
124
|
if (summary.storeChunkCount > 0 && summary.manifestExpectedChunks > 0 && summary.storeChunkCount < summary.manifestExpectedChunks * 0.5) {
|
|
95
125
|
logCliInfo(logFilePath, "status", `${c.label("Data loss detected:")} ${c.warn("yes")} — store has fewer chunks than expected. Run 'opencode-rag index' to rebuild.`);
|
|
96
126
|
}
|
|
127
|
+
// Version & runtime status
|
|
128
|
+
const pkg = getPackageMetadata();
|
|
129
|
+
logCliInfo(logFilePath, "status", `${c.label("Plugin version:")} ${c.value(pkg.version)}`);
|
|
130
|
+
const versionFilePath = path.join(os.homedir(), ".opencode", ".bundle-version");
|
|
131
|
+
const runtimeDir = path.join(os.homedir(), ".opencode", "node_modules", "opencode-rag-plugin", "dist");
|
|
132
|
+
const runtimeOk = fs.existsSync(runtimeDir);
|
|
133
|
+
if (!runtimeOk) {
|
|
134
|
+
logCliInfo(logFilePath, "status", `${c.label("Runtime:")} ${c.warn("not set up — run `opencode-rag setup`")}`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
try {
|
|
138
|
+
const installedVersion = fs.readFileSync(versionFilePath, "utf-8").trim();
|
|
139
|
+
if (installedVersion !== pkg.version) {
|
|
140
|
+
logCliInfo(logFilePath, "status", `${c.label("Runtime version:")} ${c.warn(installedVersion)} ${c.dim("(sync with `opencode-rag setup`)")}`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
logCliInfo(logFilePath, "status", `${c.label("Runtime:")} ${c.success("up-to-date")}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
logCliInfo(logFilePath, "status", `${c.label("Runtime:")} ${c.warn("version unknown — run `opencode-rag setup`")}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Async GitHub update check (fire-and-forget, 5s timeout)
|
|
151
|
+
// Only phone home if autoUpdate is explicitly enabled (opt-in).
|
|
152
|
+
if (config.autoUpdate?.enabled) {
|
|
153
|
+
checkForUpdate(pkg.version).then((info) => {
|
|
154
|
+
if (info.updateAvailable) {
|
|
155
|
+
process.stdout.write(` ${c.label("Update:")} ${c.warn(`v${info.latestVersion} available — npm update -g opencode-rag-plugin`)}\n`);
|
|
156
|
+
}
|
|
157
|
+
}).catch(() => { });
|
|
158
|
+
}
|
|
97
159
|
await cleanupContext(ctx);
|
|
98
160
|
}
|
|
99
161
|
catch (err) {
|
package/dist/cli/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { realpathSync } from "node:fs";
|
|
|
14
14
|
import { basename, dirname } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { getPackageMetadata } from "./helpers.js";
|
|
17
|
-
import { registerIndexCommand, registerQueryCommand, registerClearCommand, registerStatusCommand, registerListCommand, registerShowCommand, registerDumpCommand, registerInitCommand, registerUiCommand, registerMcpCommand,
|
|
17
|
+
import { registerIndexCommand, registerQueryCommand, registerClearCommand, registerStatusCommand, registerListCommand, registerShowCommand, registerDumpCommand, registerInitCommand, registerUiCommand, registerMcpCommand, registerSetupCommand, registerEvalSessionsCommand, registerEvalAnalyzeCommand, registerEvalCompareCommand, registerDescribeImageCommand, } from "./commands/index.js";
|
|
18
18
|
/**
|
|
19
19
|
* The top-level Commander program instance that defines the `opencode-rag` CLI.
|
|
20
20
|
*
|
|
@@ -38,7 +38,7 @@ registerDumpCommand(program);
|
|
|
38
38
|
registerDescribeImageCommand(program);
|
|
39
39
|
registerUiCommand(program);
|
|
40
40
|
registerMcpCommand(program);
|
|
41
|
-
|
|
41
|
+
registerSetupCommand(program);
|
|
42
42
|
registerEvalSessionsCommand(program);
|
|
43
43
|
registerEvalAnalyzeCommand(program);
|
|
44
44
|
registerEvalCompareCommand(program);
|
package/dist/content/image.js
CHANGED
|
@@ -19,14 +19,22 @@ export function isImageFile(fp) {
|
|
|
19
19
|
function isBmpFile(fp) {
|
|
20
20
|
return fp.toLowerCase().endsWith(".bmp");
|
|
21
21
|
}
|
|
22
|
+
const MAX_BMP_DIMENSION = 10_000;
|
|
23
|
+
const MAX_BMP_ALLOC = 500 * 1024 * 1024; // 500 MB
|
|
22
24
|
function decodeBmp(buffer) {
|
|
23
25
|
const signature = buffer.toString("ascii", 0, 2);
|
|
24
26
|
if (signature !== "BM")
|
|
25
27
|
throw new Error("Not a BMP file");
|
|
28
|
+
if (buffer.length < 54)
|
|
29
|
+
throw new Error("Truncated BMP header");
|
|
26
30
|
const dibSize = buffer.readUInt32LE(14);
|
|
27
31
|
const width = buffer.readInt32LE(18);
|
|
28
32
|
const rawHeight = buffer.readInt32LE(22);
|
|
29
33
|
const bitsPerPixel = buffer.readUInt16LE(28);
|
|
34
|
+
if (width <= 0 || Math.abs(rawHeight) <= 0)
|
|
35
|
+
throw new Error("Invalid BMP dimensions");
|
|
36
|
+
if (width > MAX_BMP_DIMENSION || Math.abs(rawHeight) > MAX_BMP_DIMENSION)
|
|
37
|
+
throw new Error(`BMP dimensions (${width}x${rawHeight}) exceed ${MAX_BMP_DIMENSION}x${MAX_BMP_DIMENSION} limit`);
|
|
30
38
|
if (bitsPerPixel !== 24 && bitsPerPixel !== 32)
|
|
31
39
|
throw new Error(`Unsupported BMP bit depth: ${bitsPerPixel}. Only 24 and 32 bit are supported.`);
|
|
32
40
|
if (dibSize < 40)
|
|
@@ -38,8 +46,17 @@ function decodeBmp(buffer) {
|
|
|
38
46
|
const rowSize = Math.floor((bitsPerPixel * width + 31) / 32) * 4;
|
|
39
47
|
const topDown = rawHeight < 0;
|
|
40
48
|
const absHeight = Math.abs(rawHeight);
|
|
49
|
+
const allocSize = width * absHeight * channels;
|
|
50
|
+
if (allocSize > MAX_BMP_ALLOC)
|
|
51
|
+
throw new Error(`BMP allocation (${allocSize} bytes) exceeds ${MAX_BMP_ALLOC} byte limit`);
|
|
41
52
|
const pixelOffset = buffer.readUInt32LE(10);
|
|
42
|
-
|
|
53
|
+
// Clamp dimensions to prevent OOM from crafted BMP headers.
|
|
54
|
+
const MAX_BMP_DIM = 16384;
|
|
55
|
+
if (width > MAX_BMP_DIM || absHeight > MAX_BMP_DIM) {
|
|
56
|
+
throw new Error(`BMP dimensions too large: ${width}x${absHeight} (max ${MAX_BMP_DIM})`);
|
|
57
|
+
}
|
|
58
|
+
const pixels = Buffer.alloc(allocSize);
|
|
59
|
+
const lastValidSrc = buffer.length - 1;
|
|
43
60
|
for (let y = 0; y < absHeight; y++) {
|
|
44
61
|
const srcY = topDown ? y : absHeight - 1 - y;
|
|
45
62
|
const srcRow = pixelOffset + srcY * rowSize;
|
|
@@ -47,6 +64,10 @@ function decodeBmp(buffer) {
|
|
|
47
64
|
for (let x = 0; x < width; x++) {
|
|
48
65
|
const srcPx = srcRow + x * (bitsPerPixel / 8);
|
|
49
66
|
const dstPx = dstRow + x * channels;
|
|
67
|
+
if (srcPx + 3 > lastValidSrc) {
|
|
68
|
+
pixels.fill(0, dstPx, dstPx + channels);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
50
71
|
if (channels === 4) {
|
|
51
72
|
pixels[dstPx] = buffer[srcPx + 2];
|
|
52
73
|
pixels[dstPx + 1] = buffer[srcPx + 1];
|
|
@@ -94,8 +115,8 @@ export async function resizeImage(buffer, filePath, maxDimension) {
|
|
|
94
115
|
.jpeg({ quality: 80 })
|
|
95
116
|
.toBuffer();
|
|
96
117
|
}
|
|
97
|
-
catch {
|
|
98
|
-
|
|
118
|
+
catch (err) {
|
|
119
|
+
throw new Error(`Image resize failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
99
120
|
}
|
|
100
121
|
}
|
|
101
122
|
/**
|
package/dist/content/reader.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { RagConfig } from "../core/config.js";
|
|
5
5
|
import { type FileManifest } from "../core/manifest.js";
|
|
6
|
+
import { DescriptionCache } from "../core/desc-cache.js";
|
|
7
|
+
import { type ImageVisionProvider } from "../chunker/image.js";
|
|
6
8
|
/** Metadata and extracted content for a single workspace file discovered during scanning. */
|
|
7
9
|
export interface WorkspaceFile {
|
|
8
10
|
filePath: string;
|
|
@@ -32,6 +34,9 @@ export declare function walkFiles(dir: string, extensions: Set<string>, excludeD
|
|
|
32
34
|
* Scan the workspace directory for indexable files, reading content or dispatching
|
|
33
35
|
* binary extraction (PDF, DOCX, DOC, Excel, images). Respects the file manifest
|
|
34
36
|
* for incremental re-indexing by skipping unchanged files.
|
|
37
|
+
*
|
|
38
|
+
* @param descCache - Optional persistent description cache. When set, image
|
|
39
|
+
* descriptions are cached and reused across sessions (survives aborted runs).
|
|
35
40
|
*/
|
|
36
|
-
export declare function scanWorkspaceFiles(cwd: string, config: RagConfig, logger?: Logger, manifest?: FileManifest, filterPaths?: string[]): Promise<WorkspaceFile[]>;
|
|
41
|
+
export declare function scanWorkspaceFiles(cwd: string, config: RagConfig, logger?: Logger, manifest?: FileManifest, filterPaths?: string[], injectedVisionProvider?: ImageVisionProvider, descCache?: DescriptionCache): Promise<WorkspaceFile[]>;
|
|
37
42
|
export {};
|
package/dist/content/reader.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import pLimit from "p-limit";
|
|
7
|
-
import { computeFileHash, normalizeFilePath } from "../core/manifest.js";
|
|
7
|
+
import { computeFileHash, computeDescriptionConfigHash, normalizeFilePath } from "../core/manifest.js";
|
|
8
|
+
import { DescriptionCache } from "../core/desc-cache.js";
|
|
8
9
|
import { createImageVisionProvider, } from "../chunker/image.js";
|
|
9
10
|
import * as pdfExtractor from "./pdf.js";
|
|
10
11
|
import * as docxExtractor from "./docx.js";
|
|
@@ -84,8 +85,11 @@ async function dispatchExtraction(filePath, buffer, imageVisionProvider, imagePr
|
|
|
84
85
|
* Scan the workspace directory for indexable files, reading content or dispatching
|
|
85
86
|
* binary extraction (PDF, DOCX, DOC, Excel, images). Respects the file manifest
|
|
86
87
|
* for incremental re-indexing by skipping unchanged files.
|
|
88
|
+
*
|
|
89
|
+
* @param descCache - Optional persistent description cache. When set, image
|
|
90
|
+
* descriptions are cached and reused across sessions (survives aborted runs).
|
|
87
91
|
*/
|
|
88
|
-
export async function scanWorkspaceFiles(cwd, config, logger, manifest, filterPaths) {
|
|
92
|
+
export async function scanWorkspaceFiles(cwd, config, logger, manifest, filterPaths, injectedVisionProvider, descCache) {
|
|
89
93
|
const extensions = new Set(config.indexing.includeExtensions);
|
|
90
94
|
let imageVisionProvider = null;
|
|
91
95
|
let imagePrompt;
|
|
@@ -95,7 +99,7 @@ export async function scanWorkspaceFiles(cwd, config, logger, manifest, filterPa
|
|
|
95
99
|
for (const ext of imageExtractor.SUPPORTED_IMAGE_EXTENSIONS) {
|
|
96
100
|
extensions.add(ext.toLowerCase());
|
|
97
101
|
}
|
|
98
|
-
imageVisionProvider = createImageVisionProvider(imageCfg);
|
|
102
|
+
imageVisionProvider = injectedVisionProvider ?? createImageVisionProvider(imageCfg);
|
|
99
103
|
imagePrompt = imageCfg.prompt;
|
|
100
104
|
imageResizeMaxDimension = imageCfg.resizeMaxDimension;
|
|
101
105
|
}
|
|
@@ -135,6 +139,10 @@ export async function scanWorkspaceFiles(cwd, config, logger, manifest, filterPa
|
|
|
135
139
|
const scanConcurrency = Math.min(config.indexing.concurrency * 2, 16);
|
|
136
140
|
const textLimit = pLimit(scanConcurrency);
|
|
137
141
|
const imageLimit = pLimit(1);
|
|
142
|
+
// Compute a hash of the image description config — used to build cache keys.
|
|
143
|
+
const imageDescConfigHash = config.imageDescription?.enabled
|
|
144
|
+
? computeDescriptionConfigHash(config) ?? ""
|
|
145
|
+
: "";
|
|
138
146
|
let completed = 0;
|
|
139
147
|
async function processFile(filePath) {
|
|
140
148
|
const normalizedPath = normalizeFilePath(filePath);
|
|
@@ -168,11 +176,48 @@ export async function scanWorkspaceFiles(cwd, config, logger, manifest, filterPa
|
|
|
168
176
|
excelExtractor.EXCEL_EXTENSIONS.has(filePath.toLowerCase()) ||
|
|
169
177
|
isImage;
|
|
170
178
|
logger?.info(`Reading: ${filePath}`);
|
|
179
|
+
const buffer = isBinary ? await fs.readFile(filePath) : Buffer.alloc(0);
|
|
180
|
+
// For images, check the persistent description cache before calling the vision provider
|
|
181
|
+
if (isImage && descCache && imageDescConfigHash) {
|
|
182
|
+
const imageBytesHash = computeFileHash(buffer.toString("base64"));
|
|
183
|
+
const cacheKey = DescriptionCache.imageKey(imageBytesHash, imageDescConfigHash);
|
|
184
|
+
const cachedDesc = descCache.get(cacheKey);
|
|
185
|
+
if (cachedDesc) {
|
|
186
|
+
logger?.info(` Using cached image description: ${filePath}`);
|
|
187
|
+
const content = cachedDesc;
|
|
188
|
+
let fileMtime;
|
|
189
|
+
let fileSize;
|
|
190
|
+
try {
|
|
191
|
+
const stat = await fs.stat(filePath);
|
|
192
|
+
fileMtime = stat.mtimeMs;
|
|
193
|
+
fileSize = stat.size;
|
|
194
|
+
}
|
|
195
|
+
catch { /* best-effort stat */ }
|
|
196
|
+
completed++;
|
|
197
|
+
return {
|
|
198
|
+
filePath,
|
|
199
|
+
normalizedPath,
|
|
200
|
+
content,
|
|
201
|
+
hash: computeFileHash(content),
|
|
202
|
+
isEmpty: false,
|
|
203
|
+
isTooSmall: false,
|
|
204
|
+
extractionStatus: "ok",
|
|
205
|
+
mtime: fileMtime,
|
|
206
|
+
size: fileSize,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
171
210
|
if (isImage) {
|
|
172
211
|
logger?.info(` Describing image: ${filePath}`);
|
|
173
212
|
}
|
|
174
|
-
const buffer = isBinary ? await fs.readFile(filePath) : Buffer.alloc(0);
|
|
175
213
|
const result = await dispatchExtraction(filePath, buffer, imageVisionProvider, imagePrompt, imageResizeMaxDimension);
|
|
214
|
+
// Cache the image description for future runs
|
|
215
|
+
if (isImage && result.ok && descCache && imageDescConfigHash) {
|
|
216
|
+
const imageBytesHash = computeFileHash(buffer.toString("base64"));
|
|
217
|
+
const cacheKey = DescriptionCache.imageKey(imageBytesHash, imageDescConfigHash);
|
|
218
|
+
descCache.set(cacheKey, result.content);
|
|
219
|
+
await descCache.save();
|
|
220
|
+
}
|
|
176
221
|
if (!result.ok) {
|
|
177
222
|
logger?.warn(` ${filePath} (extraction failed: ${result.error})`);
|
|
178
223
|
}
|
package/dist/core/config.d.ts
CHANGED
|
@@ -164,6 +164,8 @@ export interface RagConfig {
|
|
|
164
164
|
documentPrefix?: string;
|
|
165
165
|
/** Prefix prepended to queries before embedding (e.g. "search_query:"). */
|
|
166
166
|
queryPrefix?: string;
|
|
167
|
+
/** Cached embedding vector dimension. Probed once on first startup, then persisted to config. */
|
|
168
|
+
vectorDimension?: number;
|
|
167
169
|
};
|
|
168
170
|
/** Indexing pipeline controls: what to index, concurrency, batch sizes. */
|
|
169
171
|
indexing: {
|
|
@@ -187,6 +189,12 @@ export interface RagConfig {
|
|
|
187
189
|
ollamaMaxBatchSize?: number;
|
|
188
190
|
/** Maximum concurrent description generation requests. */
|
|
189
191
|
descriptionConcurrency?: number;
|
|
192
|
+
/**
|
|
193
|
+
* Maximum file size in bytes for SVG/XML files before chunking is skipped.
|
|
194
|
+
* Large SVGs can cause tree-sitter to hang. Set to 0 for no limit.
|
|
195
|
+
* @default 1_048_576 (1 MB)
|
|
196
|
+
*/
|
|
197
|
+
maxSvgSizeBytes?: number;
|
|
190
198
|
};
|
|
191
199
|
/** Vector storage backend configuration. */
|
|
192
200
|
vectorStore: {
|
|
@@ -227,6 +235,8 @@ export interface RagConfig {
|
|
|
227
235
|
readNoResultsBehavior?: ReadNoResultsBehavior;
|
|
228
236
|
/** Maximum related files shown when read results are empty. */
|
|
229
237
|
readRelatedFilesMax?: number;
|
|
238
|
+
/** Whether to inject RAG tool guidance into system prompts (default true). */
|
|
239
|
+
injectSystemPrompt?: boolean;
|
|
230
240
|
};
|
|
231
241
|
/** Custom chunker module registrations. */
|
|
232
242
|
chunkers?: ChunkerConfig[];
|
|
@@ -293,3 +303,12 @@ export declare const CONFIG_FILE_CANDIDATES: string[];
|
|
|
293
303
|
export declare function findConfigFile(directory: string): string | undefined;
|
|
294
304
|
/** Load and parse a JSON config file, deep-merge with defaults, optionally validate. */
|
|
295
305
|
export declare function loadConfig(filePath: string, validate?: boolean): RagConfig;
|
|
306
|
+
/**
|
|
307
|
+
* Persist a probed embedding vector dimension into the config JSON file.
|
|
308
|
+
* Sets `embedding.vectorDimension` so subsequent startups skip the probe.
|
|
309
|
+
* This is a best-effort operation — failures are silently ignored.
|
|
310
|
+
*
|
|
311
|
+
* @param configPath - Absolute path to the config JSON file.
|
|
312
|
+
* @param dimension - The vector dimension to persist.
|
|
313
|
+
*/
|
|
314
|
+
export declare function persistProbedDimension(configPath: string, dimension: number): void;
|