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.
Files changed (232) hide show
  1. package/ReadMe.md +6 -7
  2. package/dist/api.js +44 -24
  3. package/dist/chunker/base.d.ts +34 -3
  4. package/dist/chunker/base.js +70 -21
  5. package/dist/chunker/factory.d.ts +4 -1
  6. package/dist/chunker/factory.js +12 -1
  7. package/dist/chunker/grammar.js +3 -0
  8. package/dist/chunker/image.js +8 -8
  9. package/dist/chunker/pdf.js +11 -13
  10. package/dist/chunker/xml.d.ts +2 -0
  11. package/dist/chunker/xml.js +2 -0
  12. package/dist/cli/commands/index-command.js +0 -4
  13. package/dist/cli/commands/index.d.ts +1 -1
  14. package/dist/cli/commands/index.js +1 -1
  15. package/dist/cli/commands/init-helpers.d.ts +12 -0
  16. package/dist/cli/commands/init-helpers.js +84 -2
  17. package/dist/cli/commands/init.js +20 -2
  18. package/dist/cli/commands/query.js +1 -0
  19. package/dist/cli/commands/setup.d.ts +2 -0
  20. package/dist/cli/commands/setup.js +113 -0
  21. package/dist/cli/commands/status.js +64 -2
  22. package/dist/cli/index.js +2 -2
  23. package/dist/content/image.js +24 -3
  24. package/dist/content/reader.d.ts +6 -1
  25. package/dist/content/reader.js +49 -4
  26. package/dist/core/config.d.ts +19 -0
  27. package/dist/core/config.js +34 -4
  28. package/dist/core/desc-cache.d.ts +31 -0
  29. package/dist/core/desc-cache.js +124 -0
  30. package/dist/core/interfaces.d.ts +58 -3
  31. package/dist/core/manifest.d.ts +24 -1
  32. package/dist/core/manifest.js +34 -3
  33. package/dist/core/resolve-api-key.js +4 -2
  34. package/dist/core/runtime-overrides.js +9 -7
  35. package/dist/core/setup-runtime.d.ts +23 -0
  36. package/dist/core/setup-runtime.js +183 -0
  37. package/dist/core/version-check.d.ts +9 -0
  38. package/dist/core/version-check.js +49 -0
  39. package/dist/describer/anthropic.d.ts +2 -2
  40. package/dist/describer/anthropic.js +5 -7
  41. package/dist/describer/describer.d.ts +2 -2
  42. package/dist/describer/describer.js +6 -8
  43. package/dist/describer/gemini.d.ts +2 -2
  44. package/dist/describer/gemini.js +5 -7
  45. package/dist/embedder/factory.d.ts +3 -1
  46. package/dist/embedder/factory.js +7 -1
  47. package/dist/embedder/health.js +4 -0
  48. package/dist/embedder/http.d.ts +1 -1
  49. package/dist/embedder/http.js +74 -43
  50. package/dist/eval/compare-merge.d.ts +10 -0
  51. package/dist/eval/compare-merge.js +537 -0
  52. package/dist/eval/compare-rankings.d.ts +10 -0
  53. package/dist/eval/compare-rankings.js +245 -0
  54. package/dist/eval/dump-descriptions.d.ts +7 -0
  55. package/dist/eval/dump-descriptions.js +58 -0
  56. package/dist/eval/fast-index.d.ts +8 -0
  57. package/dist/eval/fast-index.js +283 -0
  58. package/dist/eval/run-branch-compare.d.ts +7 -0
  59. package/dist/eval/run-branch-compare.js +220 -0
  60. package/dist/eval/run-token-test.js +1 -0
  61. package/dist/eval/test-kw.d.ts +1 -0
  62. package/dist/eval/test-kw.js +22 -0
  63. package/dist/eval/update-descriptions.d.ts +7 -0
  64. package/dist/eval/update-descriptions.js +84 -0
  65. package/dist/index.d.ts +1 -0
  66. package/dist/index.js +1 -0
  67. package/dist/indexer/embed-stage.js +2 -1
  68. package/dist/indexer/git-diff.js +21 -9
  69. package/dist/indexer/pipeline.d.ts +6 -0
  70. package/dist/indexer/pipeline.js +290 -37
  71. package/dist/indexer/watch.js +1 -3
  72. package/dist/indexer/worker.d.ts +15 -2
  73. package/dist/indexer/worker.js +25 -12
  74. package/dist/mcp/handlers.d.ts +9 -0
  75. package/dist/mcp/handlers.js +23 -6
  76. package/dist/mcp/server.js +2 -0
  77. package/dist/opencode/create-read-tool.d.ts +2 -0
  78. package/dist/opencode/create-read-tool.js +8 -2
  79. package/dist/opencode/read-fallback.d.ts +1 -5
  80. package/dist/opencode/read-fallback.js +1 -18
  81. package/dist/opencode/read-format.js +5 -3
  82. package/dist/opencode/tools.js +5 -7
  83. package/dist/plugin.js +196 -81
  84. package/dist/retriever/keyword-index.d.ts +3 -2
  85. package/dist/retriever/keyword-index.js +25 -1
  86. package/dist/retriever/retriever.d.ts +4 -1
  87. package/dist/retriever/retriever.js +34 -56
  88. package/dist/vectorstore/lancedb.d.ts +35 -4
  89. package/dist/vectorstore/lancedb.js +146 -23
  90. package/dist/vectorstore/memory.d.ts +6 -1
  91. package/dist/vectorstore/memory.js +58 -0
  92. package/dist/watcher.js +3 -0
  93. package/dist/web/api.js +10 -2
  94. package/dist/web/server.js +18 -3
  95. package/package.json +8 -9
  96. package/scripts/postinstall-setup.js +82 -0
  97. package/dist/api.js.map +0 -1
  98. package/dist/chunker/base.js.map +0 -1
  99. package/dist/chunker/bash.js.map +0 -1
  100. package/dist/chunker/c.js.map +0 -1
  101. package/dist/chunker/cpp.js.map +0 -1
  102. package/dist/chunker/csharp.js.map +0 -1
  103. package/dist/chunker/css.js.map +0 -1
  104. package/dist/chunker/doc.js.map +0 -1
  105. package/dist/chunker/dockerfile.js.map +0 -1
  106. package/dist/chunker/docx.js.map +0 -1
  107. package/dist/chunker/excel.js.map +0 -1
  108. package/dist/chunker/factory.js.map +0 -1
  109. package/dist/chunker/fallback.js.map +0 -1
  110. package/dist/chunker/go.js.map +0 -1
  111. package/dist/chunker/grammar.js.map +0 -1
  112. package/dist/chunker/html.js.map +0 -1
  113. package/dist/chunker/image.js.map +0 -1
  114. package/dist/chunker/ini.js.map +0 -1
  115. package/dist/chunker/java.js.map +0 -1
  116. package/dist/chunker/javascript.js.map +0 -1
  117. package/dist/chunker/json.js.map +0 -1
  118. package/dist/chunker/kotlin.js.map +0 -1
  119. package/dist/chunker/loader.js.map +0 -1
  120. package/dist/chunker/markdown.js.map +0 -1
  121. package/dist/chunker/pdf.js.map +0 -1
  122. package/dist/chunker/php.js.map +0 -1
  123. package/dist/chunker/powershell.js.map +0 -1
  124. package/dist/chunker/python.js.map +0 -1
  125. package/dist/chunker/razor.js.map +0 -1
  126. package/dist/chunker/ruby.js.map +0 -1
  127. package/dist/chunker/rust.js.map +0 -1
  128. package/dist/chunker/sln.js.map +0 -1
  129. package/dist/chunker/sql.js.map +0 -1
  130. package/dist/chunker/ssl.js.map +0 -1
  131. package/dist/chunker/swift.js.map +0 -1
  132. package/dist/chunker/tex.js.map +0 -1
  133. package/dist/chunker/toml.js.map +0 -1
  134. package/dist/chunker/typescript.js.map +0 -1
  135. package/dist/chunker/uuid.js.map +0 -1
  136. package/dist/chunker/xml.js.map +0 -1
  137. package/dist/chunker/yaml.js.map +0 -1
  138. package/dist/cli/commands/clear.js.map +0 -1
  139. package/dist/cli/commands/describe-image.js.map +0 -1
  140. package/dist/cli/commands/dump.js.map +0 -1
  141. package/dist/cli/commands/eval.js.map +0 -1
  142. package/dist/cli/commands/index-command.js.map +0 -1
  143. package/dist/cli/commands/index.js.map +0 -1
  144. package/dist/cli/commands/init-helpers.js.map +0 -1
  145. package/dist/cli/commands/init.js.map +0 -1
  146. package/dist/cli/commands/list.js.map +0 -1
  147. package/dist/cli/commands/mcp.js.map +0 -1
  148. package/dist/cli/commands/query.js.map +0 -1
  149. package/dist/cli/commands/show.js.map +0 -1
  150. package/dist/cli/commands/status.js.map +0 -1
  151. package/dist/cli/commands/ui.js.map +0 -1
  152. package/dist/cli/commands/update.d.ts +0 -17
  153. package/dist/cli/commands/update.js +0 -79
  154. package/dist/cli/commands/update.js.map +0 -1
  155. package/dist/cli/format.js.map +0 -1
  156. package/dist/cli/helpers.js.map +0 -1
  157. package/dist/cli/index.js.map +0 -1
  158. package/dist/cli/progress.d.ts +0 -42
  159. package/dist/cli/progress.js +0 -137
  160. package/dist/cli/progress.js.map +0 -1
  161. package/dist/cli/types.js.map +0 -1
  162. package/dist/cli.js.map +0 -1
  163. package/dist/content/doc.js.map +0 -1
  164. package/dist/content/docx.js.map +0 -1
  165. package/dist/content/excel.js.map +0 -1
  166. package/dist/content/image.js.map +0 -1
  167. package/dist/content/pdf.js.map +0 -1
  168. package/dist/content/reader.js.map +0 -1
  169. package/dist/content/types.js.map +0 -1
  170. package/dist/core/bootstrap.js.map +0 -1
  171. package/dist/core/config.js.map +0 -1
  172. package/dist/core/doc-progress.js.map +0 -1
  173. package/dist/core/fileLogger.js.map +0 -1
  174. package/dist/core/interfaces.js.map +0 -1
  175. package/dist/core/manifest.js.map +0 -1
  176. package/dist/core/provider-defaults.js.map +0 -1
  177. package/dist/core/rag-injection-flag.js.map +0 -1
  178. package/dist/core/resolve-api-key.js.map +0 -1
  179. package/dist/core/runtime-overrides.js.map +0 -1
  180. package/dist/describer/anthropic.js.map +0 -1
  181. package/dist/describer/describer.js.map +0 -1
  182. package/dist/describer/factory.js.map +0 -1
  183. package/dist/describer/gemini.js.map +0 -1
  184. package/dist/describer/shared.js.map +0 -1
  185. package/dist/embedder/cohere.js.map +0 -1
  186. package/dist/embedder/factory.js.map +0 -1
  187. package/dist/embedder/health.js.map +0 -1
  188. package/dist/embedder/http.js.map +0 -1
  189. package/dist/embedder/ollama.js.map +0 -1
  190. package/dist/embedder/openai.js.map +0 -1
  191. package/dist/eval/index.js.map +0 -1
  192. package/dist/eval/run-token-test.js.map +0 -1
  193. package/dist/eval/session-logger.js.map +0 -1
  194. package/dist/eval/storage.js.map +0 -1
  195. package/dist/eval/token-analysis.js.map +0 -1
  196. package/dist/eval/token-counter.js.map +0 -1
  197. package/dist/eval/types.js.map +0 -1
  198. package/dist/index.js.map +0 -1
  199. package/dist/indexer/description-stage.js.map +0 -1
  200. package/dist/indexer/embed-stage.js.map +0 -1
  201. package/dist/indexer/git-diff.js.map +0 -1
  202. package/dist/indexer/metadata.js.map +0 -1
  203. package/dist/indexer/pipeline.js.map +0 -1
  204. package/dist/indexer/stats.js.map +0 -1
  205. package/dist/indexer/watch.js.map +0 -1
  206. package/dist/indexer/worker.js.map +0 -1
  207. package/dist/indexer.js.map +0 -1
  208. package/dist/mcp/cli.js.map +0 -1
  209. package/dist/mcp/handlers.js.map +0 -1
  210. package/dist/mcp/server.js.map +0 -1
  211. package/dist/opencode/create-read-tool.js.map +0 -1
  212. package/dist/opencode/read-fallback.js.map +0 -1
  213. package/dist/opencode/read-format.js.map +0 -1
  214. package/dist/opencode/read-query.js.map +0 -1
  215. package/dist/opencode/tool-args.js.map +0 -1
  216. package/dist/opencode/tools.js.map +0 -1
  217. package/dist/plugin-entry.js.map +0 -1
  218. package/dist/plugin.js.map +0 -1
  219. package/dist/retriever/context-optimizer.js.map +0 -1
  220. package/dist/retriever/keyword-index.js.map +0 -1
  221. package/dist/retriever/retriever.js.map +0 -1
  222. package/dist/tui.js.map +0 -1
  223. package/dist/updater.d.ts +0 -45
  224. package/dist/updater.js +0 -175
  225. package/dist/updater.js.map +0 -1
  226. package/dist/vectorstore/factory.js.map +0 -1
  227. package/dist/vectorstore/lancedb.js.map +0 -1
  228. package/dist/vectorstore/memory.js.map +0 -1
  229. package/dist/watcher.js.map +0 -1
  230. package/dist/web/api.js.map +0 -1
  231. package/dist/web/server.js.map +0 -1
  232. 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 canvas) and produce "Plugin export is not a function".
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 'install.sh compile' / 'install.ps1 compile' first.");
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
  }
@@ -51,6 +51,7 @@ export function registerQueryCommand(program) {
51
51
  minScore,
52
52
  keywordIndex,
53
53
  keywordWeight: hybridCfg?.keywordWeight,
54
+ hybridEnabled: hybridCfg?.enabled,
54
55
  queryPrefix: config.embedding.queryPrefix,
55
56
  explain: options.explain ?? false,
56
57
  });
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerSetupCommand(program: Command): void;
@@ -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, skipKeywordIndex: 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
- logCliInfo(logFilePath, "status", `${c.label("Watch mode:")} ${c.dim("off")}`);
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, registerUpdateCommand, registerEvalSessionsCommand, registerEvalAnalyzeCommand, registerEvalCompareCommand, registerDescribeImageCommand, } from "./commands/index.js";
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
- registerUpdateCommand(program);
41
+ registerSetupCommand(program);
42
42
  registerEvalSessionsCommand(program);
43
43
  registerEvalAnalyzeCommand(program);
44
44
  registerEvalCompareCommand(program);
@@ -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
- const pixels = Buffer.alloc(width * absHeight * channels);
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
- return buffer;
118
+ catch (err) {
119
+ throw new Error(`Image resize failed: ${err instanceof Error ? err.message : String(err)}`);
99
120
  }
100
121
  }
101
122
  /**
@@ -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 {};
@@ -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
  }
@@ -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;