opencode-rag-plugin 1.12.17 → 1.14.6

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 (335) hide show
  1. package/ReadMe.md +15 -13
  2. package/dist/api.d.ts +54 -2
  3. package/dist/api.js +41 -1
  4. package/dist/api.js.map +1 -1
  5. package/dist/chunker/base.js +2 -0
  6. package/dist/chunker/base.js.map +1 -1
  7. package/dist/chunker/bash.d.ts +5 -0
  8. package/dist/chunker/bash.js +5 -0
  9. package/dist/chunker/bash.js.map +1 -1
  10. package/dist/chunker/c.d.ts +6 -0
  11. package/dist/chunker/c.js +6 -0
  12. package/dist/chunker/c.js.map +1 -1
  13. package/dist/chunker/cpp.d.ts +6 -0
  14. package/dist/chunker/cpp.js +6 -0
  15. package/dist/chunker/cpp.js.map +1 -1
  16. package/dist/chunker/csharp.d.ts +6 -0
  17. package/dist/chunker/csharp.js +6 -0
  18. package/dist/chunker/csharp.js.map +1 -1
  19. package/dist/chunker/css.d.ts +6 -0
  20. package/dist/chunker/css.js +6 -0
  21. package/dist/chunker/css.js.map +1 -1
  22. package/dist/chunker/doc.d.ts +18 -0
  23. package/dist/chunker/doc.js +18 -0
  24. package/dist/chunker/doc.js.map +1 -1
  25. package/dist/chunker/dockerfile.d.ts +6 -0
  26. package/dist/chunker/dockerfile.js +6 -0
  27. package/dist/chunker/dockerfile.js.map +1 -1
  28. package/dist/chunker/docx.d.ts +18 -0
  29. package/dist/chunker/docx.js +18 -0
  30. package/dist/chunker/docx.js.map +1 -1
  31. package/dist/chunker/excel.d.ts +19 -0
  32. package/dist/chunker/excel.js +19 -0
  33. package/dist/chunker/excel.js.map +1 -1
  34. package/dist/chunker/go.d.ts +5 -0
  35. package/dist/chunker/go.js +5 -0
  36. package/dist/chunker/go.js.map +1 -1
  37. package/dist/chunker/grammar.d.ts +62 -0
  38. package/dist/chunker/grammar.js +133 -0
  39. package/dist/chunker/grammar.js.map +1 -1
  40. package/dist/chunker/html.d.ts +5 -0
  41. package/dist/chunker/html.js +5 -0
  42. package/dist/chunker/html.js.map +1 -1
  43. package/dist/chunker/image.d.ts +31 -0
  44. package/dist/chunker/image.js +50 -4
  45. package/dist/chunker/image.js.map +1 -1
  46. package/dist/chunker/ini.d.ts +5 -0
  47. package/dist/chunker/ini.js +5 -0
  48. package/dist/chunker/ini.js.map +1 -1
  49. package/dist/chunker/java.d.ts +5 -0
  50. package/dist/chunker/java.js +5 -0
  51. package/dist/chunker/java.js.map +1 -1
  52. package/dist/chunker/javascript.d.ts +6 -0
  53. package/dist/chunker/javascript.js +6 -0
  54. package/dist/chunker/javascript.js.map +1 -1
  55. package/dist/chunker/json.d.ts +5 -0
  56. package/dist/chunker/json.js +5 -0
  57. package/dist/chunker/json.js.map +1 -1
  58. package/dist/chunker/kotlin.d.ts +6 -0
  59. package/dist/chunker/kotlin.js +6 -0
  60. package/dist/chunker/kotlin.js.map +1 -1
  61. package/dist/chunker/loader.d.ts +11 -0
  62. package/dist/chunker/loader.js +20 -0
  63. package/dist/chunker/loader.js.map +1 -1
  64. package/dist/chunker/markdown.d.ts +5 -0
  65. package/dist/chunker/markdown.js +5 -0
  66. package/dist/chunker/markdown.js.map +1 -1
  67. package/dist/chunker/pdf.d.ts +19 -0
  68. package/dist/chunker/pdf.js +30 -0
  69. package/dist/chunker/pdf.js.map +1 -1
  70. package/dist/chunker/php.d.ts +5 -0
  71. package/dist/chunker/php.js +5 -0
  72. package/dist/chunker/php.js.map +1 -1
  73. package/dist/chunker/powershell.d.ts +5 -0
  74. package/dist/chunker/powershell.js +5 -0
  75. package/dist/chunker/powershell.js.map +1 -1
  76. package/dist/chunker/python.d.ts +5 -0
  77. package/dist/chunker/python.js +5 -0
  78. package/dist/chunker/python.js.map +1 -1
  79. package/dist/chunker/razor.d.ts +12 -0
  80. package/dist/chunker/razor.js +25 -0
  81. package/dist/chunker/razor.js.map +1 -1
  82. package/dist/chunker/ruby.d.ts +5 -0
  83. package/dist/chunker/ruby.js +5 -0
  84. package/dist/chunker/ruby.js.map +1 -1
  85. package/dist/chunker/rust.d.ts +6 -0
  86. package/dist/chunker/rust.js +6 -0
  87. package/dist/chunker/rust.js.map +1 -1
  88. package/dist/chunker/sln.d.ts +11 -0
  89. package/dist/chunker/sln.js +11 -0
  90. package/dist/chunker/sln.js.map +1 -1
  91. package/dist/chunker/sql.d.ts +5 -0
  92. package/dist/chunker/sql.js +5 -0
  93. package/dist/chunker/sql.js.map +1 -1
  94. package/dist/chunker/ssl.d.ts +12 -0
  95. package/dist/chunker/ssl.js +21 -2
  96. package/dist/chunker/ssl.js.map +1 -1
  97. package/dist/chunker/swift.d.ts +5 -0
  98. package/dist/chunker/swift.js +5 -0
  99. package/dist/chunker/swift.js.map +1 -1
  100. package/dist/chunker/tex.d.ts +12 -0
  101. package/dist/chunker/tex.js +12 -0
  102. package/dist/chunker/tex.js.map +1 -1
  103. package/dist/chunker/toml.d.ts +5 -0
  104. package/dist/chunker/toml.js +5 -0
  105. package/dist/chunker/toml.js.map +1 -1
  106. package/dist/chunker/typescript.d.ts +6 -0
  107. package/dist/chunker/typescript.js +6 -0
  108. package/dist/chunker/typescript.js.map +1 -1
  109. package/dist/chunker/xml.d.ts +5 -0
  110. package/dist/chunker/xml.js +5 -0
  111. package/dist/chunker/xml.js.map +1 -1
  112. package/dist/chunker/yaml.d.ts +5 -0
  113. package/dist/chunker/yaml.js +5 -0
  114. package/dist/chunker/yaml.js.map +1 -1
  115. package/dist/cli/commands/clear.d.ts +12 -0
  116. package/dist/cli/commands/clear.js +81 -0
  117. package/dist/cli/commands/clear.js.map +1 -0
  118. package/dist/cli/commands/describe-image.d.ts +14 -0
  119. package/dist/cli/commands/describe-image.js +68 -0
  120. package/dist/cli/commands/describe-image.js.map +1 -0
  121. package/dist/cli/commands/dump.d.ts +13 -0
  122. package/dist/cli/commands/dump.js +55 -0
  123. package/dist/cli/commands/dump.js.map +1 -0
  124. package/dist/cli/commands/eval.d.ts +32 -0
  125. package/dist/cli/commands/eval.js +158 -0
  126. package/dist/cli/commands/eval.js.map +1 -0
  127. package/dist/cli/commands/index-command.d.ts +17 -0
  128. package/dist/cli/commands/index-command.js +167 -0
  129. package/dist/cli/commands/index-command.js.map +1 -0
  130. package/dist/cli/commands/index.d.ts +16 -0
  131. package/dist/cli/commands/index.js +17 -0
  132. package/dist/cli/commands/index.js.map +1 -0
  133. package/dist/cli/commands/init-helpers.d.ts +101 -0
  134. package/dist/cli/commands/init-helpers.js +397 -0
  135. package/dist/cli/commands/init-helpers.js.map +1 -0
  136. package/dist/cli/commands/init.d.ts +19 -0
  137. package/dist/cli/commands/init.js +251 -0
  138. package/dist/cli/commands/init.js.map +1 -0
  139. package/dist/cli/commands/list.d.ts +13 -0
  140. package/dist/cli/commands/list.js +46 -0
  141. package/dist/cli/commands/list.js.map +1 -0
  142. package/dist/cli/commands/mcp.d.ts +14 -0
  143. package/dist/cli/commands/mcp.js +35 -0
  144. package/dist/cli/commands/mcp.js.map +1 -0
  145. package/dist/cli/commands/query.d.ts +14 -0
  146. package/dist/cli/commands/query.js +86 -0
  147. package/dist/cli/commands/query.js.map +1 -0
  148. package/dist/cli/commands/show.d.ts +13 -0
  149. package/dist/cli/commands/show.js +51 -0
  150. package/dist/cli/commands/show.js.map +1 -0
  151. package/dist/cli/commands/status.d.ts +14 -0
  152. package/dist/cli/commands/status.js +104 -0
  153. package/dist/cli/commands/status.js.map +1 -0
  154. package/dist/cli/commands/ui.d.ts +14 -0
  155. package/dist/cli/commands/ui.js +69 -0
  156. package/dist/cli/commands/ui.js.map +1 -0
  157. package/dist/cli/commands/update.d.ts +14 -0
  158. package/dist/cli/commands/update.js +76 -0
  159. package/dist/cli/commands/update.js.map +1 -0
  160. package/dist/cli/format.d.ts +112 -0
  161. package/dist/cli/format.js +184 -0
  162. package/dist/cli/format.js.map +1 -0
  163. package/dist/cli/helpers.d.ts +51 -0
  164. package/dist/cli/helpers.js +86 -0
  165. package/dist/cli/helpers.js.map +1 -0
  166. package/dist/cli/index.d.ts +28 -0
  167. package/dist/cli/index.js +104 -0
  168. package/dist/cli/index.js.map +1 -0
  169. package/dist/cli/progress.d.ts +39 -0
  170. package/dist/cli/progress.js +137 -0
  171. package/dist/cli/progress.js.map +1 -0
  172. package/dist/cli/types.d.ts +40 -0
  173. package/dist/cli/types.js +3 -0
  174. package/dist/cli/types.js.map +1 -0
  175. package/dist/cli.d.ts +11 -4
  176. package/dist/cli.js +9 -1253
  177. package/dist/cli.js.map +1 -1
  178. package/dist/content/doc.d.ts +1 -1
  179. package/dist/content/doc.js +1 -1
  180. package/dist/content/doc.js.map +1 -1
  181. package/dist/content/docx.d.ts +1 -1
  182. package/dist/content/docx.js +1 -1
  183. package/dist/content/docx.js.map +1 -1
  184. package/dist/content/excel.d.ts +1 -1
  185. package/dist/content/excel.js +1 -1
  186. package/dist/content/excel.js.map +1 -1
  187. package/dist/content/pdf.d.ts +1 -1
  188. package/dist/content/pdf.js +1 -1
  189. package/dist/content/pdf.js.map +1 -1
  190. package/dist/content/reader.d.ts +3 -1
  191. package/dist/content/reader.js +42 -10
  192. package/dist/content/reader.js.map +1 -1
  193. package/dist/core/bootstrap.d.ts +18 -0
  194. package/dist/core/bootstrap.js +9 -15
  195. package/dist/core/bootstrap.js.map +1 -1
  196. package/dist/core/config.d.ts +144 -0
  197. package/dist/core/config.js +65 -14
  198. package/dist/core/config.js.map +1 -1
  199. package/dist/core/doc-progress.d.ts +15 -0
  200. package/dist/core/doc-progress.js +61 -0
  201. package/dist/core/doc-progress.js.map +1 -0
  202. package/dist/core/fileLogger.d.ts +7 -0
  203. package/dist/core/fileLogger.js +2 -0
  204. package/dist/core/fileLogger.js.map +1 -1
  205. package/dist/core/interfaces.d.ts +52 -1
  206. package/dist/core/manifest.d.ts +25 -0
  207. package/dist/core/manifest.js +15 -1
  208. package/dist/core/manifest.js.map +1 -1
  209. package/dist/core/provider-defaults.d.ts +8 -0
  210. package/dist/core/provider-defaults.js +3 -0
  211. package/dist/core/provider-defaults.js.map +1 -1
  212. package/dist/core/rag-injection-flag.d.ts +3 -0
  213. package/dist/core/rag-injection-flag.js +2 -0
  214. package/dist/core/rag-injection-flag.js.map +1 -1
  215. package/dist/core/resolve-api-key.d.ts +1 -0
  216. package/dist/core/resolve-api-key.js +1 -0
  217. package/dist/core/resolve-api-key.js.map +1 -1
  218. package/dist/core/runtime-overrides.d.ts +4 -0
  219. package/dist/core/runtime-overrides.js +4 -1
  220. package/dist/core/runtime-overrides.js.map +1 -1
  221. package/dist/describer/anthropic.d.ts +22 -2
  222. package/dist/describer/anthropic.js +42 -110
  223. package/dist/describer/anthropic.js.map +1 -1
  224. package/dist/describer/describer.d.ts +21 -4
  225. package/dist/describer/describer.js +52 -111
  226. package/dist/describer/describer.js.map +1 -1
  227. package/dist/describer/gemini.d.ts +1 -2
  228. package/dist/describer/gemini.js +20 -113
  229. package/dist/describer/gemini.js.map +1 -1
  230. package/dist/describer/shared.d.ts +3 -0
  231. package/dist/describer/shared.js +19 -0
  232. package/dist/describer/shared.js.map +1 -0
  233. package/dist/embedder/factory.d.ts +25 -0
  234. package/dist/embedder/factory.js +25 -0
  235. package/dist/embedder/factory.js.map +1 -1
  236. package/dist/embedder/health.d.ts +10 -0
  237. package/dist/embedder/health.js +26 -14
  238. package/dist/embedder/health.js.map +1 -1
  239. package/dist/embedder/http.d.ts +47 -0
  240. package/dist/embedder/http.js +61 -3
  241. package/dist/embedder/http.js.map +1 -1
  242. package/dist/eval/index.d.ts +6 -0
  243. package/dist/eval/index.js.map +1 -1
  244. package/dist/eval/run-token-test.js +1 -5
  245. package/dist/eval/run-token-test.js.map +1 -1
  246. package/dist/eval/session-logger.d.ts +5 -0
  247. package/dist/eval/session-logger.js +4 -0
  248. package/dist/eval/session-logger.js.map +1 -1
  249. package/dist/eval/storage.d.ts +9 -0
  250. package/dist/eval/storage.js +9 -5
  251. package/dist/eval/storage.js.map +1 -1
  252. package/dist/eval/token-analysis.d.ts +8 -0
  253. package/dist/eval/token-analysis.js +6 -2
  254. package/dist/eval/token-analysis.js.map +1 -1
  255. package/dist/eval/token-counter.d.ts +1 -3
  256. package/dist/eval/token-counter.js +4 -5
  257. package/dist/eval/token-counter.js.map +1 -1
  258. package/dist/eval/types.d.ts +6 -0
  259. package/dist/eval/types.js +2 -0
  260. package/dist/eval/types.js.map +1 -1
  261. package/dist/index.d.ts +11 -0
  262. package/dist/index.js +12 -3
  263. package/dist/index.js.map +1 -1
  264. package/dist/indexer/description-stage.js +11 -2
  265. package/dist/indexer/description-stage.js.map +1 -1
  266. package/dist/indexer/embed-stage.js +0 -8
  267. package/dist/indexer/embed-stage.js.map +1 -1
  268. package/dist/indexer/pipeline.d.ts +27 -3
  269. package/dist/indexer/pipeline.js +314 -143
  270. package/dist/indexer/pipeline.js.map +1 -1
  271. package/dist/indexer/stats.d.ts +35 -0
  272. package/dist/indexer/stats.js +9 -0
  273. package/dist/indexer/stats.js.map +1 -1
  274. package/dist/indexer/watch.d.ts +25 -0
  275. package/dist/indexer/watch.js +21 -0
  276. package/dist/indexer/watch.js.map +1 -1
  277. package/dist/indexer/worker.d.ts +90 -2
  278. package/dist/indexer/worker.js +109 -24
  279. package/dist/indexer/worker.js.map +1 -1
  280. package/dist/indexer.d.ts +11 -1
  281. package/dist/indexer.js +9 -0
  282. package/dist/indexer.js.map +1 -1
  283. package/dist/mcp/cli.d.ts +3 -0
  284. package/dist/mcp/cli.js +1 -0
  285. package/dist/mcp/cli.js.map +1 -1
  286. package/dist/mcp/handlers.d.ts +41 -0
  287. package/dist/mcp/handlers.js +40 -1
  288. package/dist/mcp/handlers.js.map +1 -1
  289. package/dist/mcp/server.d.ts +8 -0
  290. package/dist/mcp/server.js +18 -1
  291. package/dist/mcp/server.js.map +1 -1
  292. package/dist/opencode/create-read-tool.js +1 -0
  293. package/dist/opencode/create-read-tool.js.map +1 -1
  294. package/dist/opencode/tools.d.ts +8 -17
  295. package/dist/opencode/tools.js +74 -109
  296. package/dist/opencode/tools.js.map +1 -1
  297. package/dist/plugin-entry.d.ts +6 -0
  298. package/dist/plugin-entry.js +6 -0
  299. package/dist/plugin-entry.js.map +1 -1
  300. package/dist/plugin.d.ts +37 -0
  301. package/dist/plugin.js +265 -40
  302. package/dist/plugin.js.map +1 -1
  303. package/dist/retriever/keyword-index.js +9 -9
  304. package/dist/retriever/keyword-index.js.map +1 -1
  305. package/dist/retriever/retriever.js +14 -4
  306. package/dist/retriever/retriever.js.map +1 -1
  307. package/dist/tui.d.ts +5 -0
  308. package/dist/tui.js +100 -12
  309. package/dist/tui.js.map +1 -1
  310. package/dist/types/opencode-plugin.d.ts +4 -1
  311. package/dist/updater.d.ts +23 -1
  312. package/dist/updater.js +55 -16
  313. package/dist/updater.js.map +1 -1
  314. package/dist/vectorstore/factory.d.ts +13 -0
  315. package/dist/vectorstore/factory.js +13 -0
  316. package/dist/vectorstore/factory.js.map +1 -1
  317. package/dist/vectorstore/lancedb.d.ts +78 -1
  318. package/dist/vectorstore/lancedb.js +195 -21
  319. package/dist/vectorstore/lancedb.js.map +1 -1
  320. package/dist/vectorstore/memory.d.ts +5 -0
  321. package/dist/vectorstore/memory.js +11 -0
  322. package/dist/vectorstore/memory.js.map +1 -1
  323. package/dist/watcher.d.ts +24 -0
  324. package/dist/watcher.js +17 -35
  325. package/dist/watcher.js.map +1 -1
  326. package/dist/web/api.d.ts +37 -0
  327. package/dist/web/api.js +54 -0
  328. package/dist/web/api.js.map +1 -1
  329. package/dist/web/server.d.ts +15 -0
  330. package/dist/web/server.js +14 -0
  331. package/dist/web/server.js.map +1 -1
  332. package/dist/web/static.d.ts +8 -0
  333. package/dist/web/static.js +8 -0
  334. package/dist/web/static.js.map +1 -1
  335. package/package.json +11 -15
package/dist/plugin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { tool } from "@opencode-ai/plugin/tool";
2
- import { loadConfig, DEFAULT_CONFIG, resolveLogConfig } from "./core/config.js";
2
+ import { loadConfig, findConfigFile, DEFAULT_CONFIG, resolveLogConfig } from "./core/config.js";
3
3
  import { createEmbedder } from "./embedder/factory.js";
4
4
  import { createDescriptionProvider } from "./describer/factory.js";
5
5
  import { createVectorStore } from "./vectorstore/factory.js";
@@ -9,9 +9,11 @@ import { appendDebugLog } from "./core/fileLogger.js";
9
9
  import { loadRuntimeOverrides, applyRuntimeOverrides } from "./core/runtime-overrides.js";
10
10
  import { createBackgroundIndexer } from "./watcher.js";
11
11
  import { createRagReadTool } from "./opencode/create-read-tool.js";
12
- import { createFileSkeletonTool, createFindUsagesTool, } from "./opencode/tools.js";
12
+ import { createFileSkeletonTool, createFindUsagesTool, createDescribeImageTool, } from "./opencode/tools.js";
13
13
  import { resolveApiKey } from "./core/resolve-api-key.js";
14
14
  import { consumePendingRagInjection } from "./core/rag-injection-flag.js";
15
+ import { loadDocProgress, markSubdirectoryDocumented } from "./core/doc-progress.js";
16
+ import { loadManifest } from "./core/manifest.js";
15
17
  import { createSessionLogger } from "./eval/session-logger.js";
16
18
  import { countTokens } from "./eval/token-counter.js";
17
19
  import { checkForUpdate } from "./updater.js";
@@ -20,12 +22,22 @@ import path from "node:path";
20
22
  import { fileURLToPath } from "node:url";
21
23
  import { spawn, execSync } from "node:child_process";
22
24
  import { tmpdir } from "node:os";
25
+ /** Cache of loaded RAG configurations keyed by workspace directory. */
23
26
  const configCache = new Map();
27
+ /** Active background indexer instances keyed by workspace directory. */
24
28
  const backgroundIndexers = new Map();
29
+ /** Active MCP server instances keyed by workspace directory. */
25
30
  const mcpServers = new Map();
31
+ /** Pending update notifications keyed by workspace directory. */
26
32
  const pendingUpdateInfo = new Map();
33
+ /** Name of the semantic search tool as exposed to the LLM. */
27
34
  const CONTEXT_TOOL_NAME = "search_semantic";
35
+ /** Marker string injected into context output to identify RAG-sourced content. */
28
36
  const CONTEXT_MARKER = "search_semantic retrieved context";
37
+ /**
38
+ * Append a verbose log entry with an optional structured payload.
39
+ * The payload is serialised using formatLogPayload for readability.
40
+ */
29
41
  function appendVerboseLog(logFilePath, scope, message, payload, logLevel) {
30
42
  appendDebugLog(logFilePath, {
31
43
  scope,
@@ -34,6 +46,10 @@ function appendVerboseLog(logFilePath, scope, message, payload, logLevel) {
34
46
  : message,
35
47
  }, logLevel);
36
48
  }
49
+ /**
50
+ * Format an arbitrary value as a human-readable indented string
51
+ * for debug logging. Handles null, primitives, arrays, and nested objects.
52
+ */
37
53
  function formatLogPayload(value, indent = 0) {
38
54
  const prefix = " ".repeat(indent);
39
55
  if (value === null) {
@@ -89,6 +105,10 @@ function formatLogPayload(value, indent = 0) {
89
105
  }
90
106
  return `${prefix}${String(value)}`;
91
107
  }
108
+ /**
109
+ * Indent every line of a multi-line string by the given number of
110
+ * two-space indentation levels.
111
+ */
92
112
  function indentMultiline(text, indent) {
93
113
  const prefix = " ".repeat(indent);
94
114
  return text
@@ -96,15 +116,17 @@ function indentMultiline(text, indent) {
96
116
  .map((line) => `${prefix}${line}`)
97
117
  .join("\n");
98
118
  }
119
+ /**
120
+ * Load the RAG configuration for a directory, searching in order:
121
+ * opencode-rag.json, .opencode/opencode-rag.json, .opencode/rag.json.
122
+ * Caches the result per directory.
123
+ */
99
124
  async function getConfig(directory) {
100
125
  const cached = configCache.get(directory);
101
126
  if (cached)
102
127
  return cached;
103
- for (const loc of ["opencode-rag.json", ".opencode/opencode-rag.json", ".opencode/rag.json"]) {
104
- const configPath = path.join(directory, loc);
105
- if (!existsSync(configPath)) {
106
- continue;
107
- }
128
+ const configPath = findConfigFile(directory);
129
+ if (configPath) {
108
130
  try {
109
131
  const cfg = loadConfig(configPath);
110
132
  await loadChunkersFromConfig(cfg, path.dirname(configPath));
@@ -122,6 +144,10 @@ async function getConfig(directory) {
122
144
  configCache.set(directory, DEFAULT_CONFIG);
123
145
  return DEFAULT_CONFIG;
124
146
  }
147
+ /**
148
+ * Format retrieval results into a Markdown context block for LLM injection.
149
+ * Includes per-chunk file paths, line numbers, relevance scores, and descriptions.
150
+ */
125
151
  function formatContext(results) {
126
152
  if (results.length === 0)
127
153
  return "";
@@ -142,6 +168,10 @@ function formatContext(results) {
142
168
  parts.push("---\n");
143
169
  return parts.join("\n");
144
170
  }
171
+ /**
172
+ * Format auto-injected context results, trimming to a token budget.
173
+ * Results are sorted by relevance and included up to maxChunks.
174
+ */
145
175
  function formatAutoInjectContext(results, worktree, maxTokens, maxChunks) {
146
176
  if (results.length === 0)
147
177
  return "";
@@ -177,6 +207,10 @@ function formatAutoInjectContext(results, worktree, maxTokens, maxChunks) {
177
207
  }
178
208
  return formatted;
179
209
  }
210
+ /**
211
+ * Build the effective query string from a RetrievalQueryHints object,
212
+ * appending path and language hints as additional context.
213
+ */
180
214
  function buildRetrievalQuery(hints) {
181
215
  const parts = [hints.query.trim()];
182
216
  const pathHints = hints.pathHints?.map((hint) => hint.trim()).filter((hint) => hint.length > 0) ?? [];
@@ -189,6 +223,10 @@ function buildRetrievalQuery(hints) {
189
223
  }
190
224
  return parts.join("\n").trim();
191
225
  }
226
+ /**
227
+ * Remove duplicate results based on file path + line range + content.
228
+ * Keeps the first occurrence of each unique result.
229
+ */
192
230
  function dedupeResults(results) {
193
231
  const seen = new Set();
194
232
  const deduped = [];
@@ -207,11 +245,19 @@ function dedupeResults(results) {
207
245
  }
208
246
  return deduped;
209
247
  }
248
+ /**
249
+ * Perform a retrieval query against the vector store with the given parameters.
250
+ * Returns an empty array for blank queries.
251
+ */
210
252
  async function retrieveContext(query, embedder, store, topK, retrieveFn = retrieve, minScore = 0, keywordIndex, keywordWeight, queryPrefix, explain = false) {
211
253
  if (query.trim().length === 0)
212
254
  return [];
213
255
  return retrieveFn(query, embedder, store, { topK, minScore, keywordIndex, keywordWeight, queryPrefix, explain });
214
256
  }
257
+ /**
258
+ * Load results from one or two queries (primary + optional extra), deduplicate,
259
+ * sort by descending score, and limit to maxContextChunks from config.
260
+ */
215
261
  async function loadRetrievedResults(query, embedder, store, cfg, retrieveFn = retrieve, topK = cfg.retrieval.topK, extraQuery, keywordIndex, queryPrefix, explain = false) {
216
262
  const minScore = cfg.retrieval.minScore;
217
263
  const kw = cfg.retrieval.hybridSearch?.keywordWeight;
@@ -223,11 +269,17 @@ async function loadRetrievedResults(query, embedder, store, cfg, retrieveFn = re
223
269
  .sort((a, b) => b.score - a.score)
224
270
  .slice(0, cfg.openCode.maxContextChunks);
225
271
  }
272
+ /** Default set of plugin dependencies using the real implementations. */
226
273
  const defaultDependencies = {
227
274
  createEmbedder,
228
275
  createStore: (storePath, dimension, config) => createVectorStore(config, storePath, dimension),
229
276
  retrieve,
230
277
  };
278
+ /**
279
+ * Format a list of relevant files (aggregated from chunks) for display.
280
+ * Groups chunks by file path and sorts by maximum relevance score.
281
+ * Includes an AGENTS.md-style tool usage reminder at the end.
282
+ */
231
283
  function formatFileList(results, worktree, maxFiles = 10) {
232
284
  const fileMap = new Map();
233
285
  for (const r of results) {
@@ -268,8 +320,12 @@ function formatFileList(results, worktree, maxFiles = 10) {
268
320
  *
269
321
  * Attempts to find the user's message content from output.message first
270
322
  * (via parts/text), then falls back to input fields.
323
+ *
324
+ * @param input - The incoming hook input containing session and message metadata.
325
+ * @param output - The output object potentially containing the user's message parts.
326
+ * @returns The extracted user message text, or an empty string if not found.
271
327
  */
272
- function extractUserMessageText(input, output) {
328
+ function extractUserMessageText(_input, output) {
273
329
  // Try to extract from output.parts first (most common path)
274
330
  if (output?.message) {
275
331
  const msg = output.message;
@@ -291,6 +347,19 @@ function extractUserMessageText(input, output) {
291
347
  }
292
348
  return "";
293
349
  }
350
+ /**
351
+ * Create the RAG plugin hook implementations for an OpenCode workspace.
352
+ *
353
+ * Wires together the vector store, embedder, keyword index, and description
354
+ * provider into OpenCode's hook system. Handles:
355
+ * - Automatic context injection on chat messages
356
+ * - Documentation mode auto-kickoff
357
+ * - Tool registration (semantic search, file skeleton, find usages, describe image)
358
+ * - Session-level caching and evaluation logging
359
+ *
360
+ * @param options - Configuration and dependencies for building the hooks.
361
+ * @returns A complete set of OpenCode hooks for RAG functionality.
362
+ */
294
363
  export function createRagHooks(options) {
295
364
  const dependencies = {
296
365
  ...defaultDependencies,
@@ -313,11 +382,6 @@ export function createRagHooks(options) {
313
382
  // Session-level caches for lazy retrieval
314
383
  const sessionLastMessage = new Map();
315
384
  const sessionRetrievalCache = new Map();
316
- // Session-level tool usage tracking for nudge mechanism
317
- const RAG_TOOL_NAMES = new Set(["search_semantic", "get_file_skeleton", "find_usages"]);
318
- const sessionRagToolCalls = new Map();
319
- const sessionMessageCount = new Map();
320
- const sessionSkeletonCalls = new Map(); // sessionID → set of filePaths
321
385
  // Evaluation session logger — captures OpenCode events for analysis
322
386
  const sessionLogger = createSessionLogger(options.storePath);
323
387
  appendDebugLog(options.logFilePath, {
@@ -464,6 +528,20 @@ export function createRagHooks(options) {
464
528
  error: err,
465
529
  });
466
530
  }
531
+ try {
532
+ const describeImageTool = createDescribeImageTool({
533
+ worktree: options.worktree,
534
+ config: effectiveCfg,
535
+ });
536
+ tools["describe_image"] = describeImageTool;
537
+ }
538
+ catch (err) {
539
+ appendDebugLog(options.logFilePath, {
540
+ scope: "plugin",
541
+ message: "Failed to register describe_image tool",
542
+ error: err,
543
+ });
544
+ }
467
545
  if (readOverride) {
468
546
  const readTool = createRagReadTool({
469
547
  worktree: options.worktree,
@@ -491,24 +569,6 @@ export function createRagHooks(options) {
491
569
  sessionLogger.onEvent(event);
492
570
  },
493
571
  tool: tools,
494
- async "tool.execute.after"(input, output) {
495
- // Track RAG tool usage per session
496
- if (RAG_TOOL_NAMES.has(input.tool)) {
497
- const prev = sessionRagToolCalls.get(input.sessionID) ?? 0;
498
- sessionRagToolCalls.set(input.sessionID, prev + 1);
499
- }
500
- },
501
- async "tool.execute.before"(input, output) {
502
- // Track skeleton calls per session for read-interception analytics
503
- if (input.tool === "get_file_skeleton") {
504
- const filePath = output.args?.filePath;
505
- if (filePath) {
506
- const set = sessionSkeletonCalls.get(input.sessionID) ?? new Set();
507
- set.add(filePath);
508
- sessionSkeletonCalls.set(input.sessionID, set);
509
- }
510
- }
511
- },
512
572
  async "experimental.chat.system.transform"(_input, output) {
513
573
  appendDebugLog(options.logFilePath, {
514
574
  scope: "experimental.chat.system.transform",
@@ -519,18 +579,21 @@ export function createRagHooks(options) {
519
579
  "- `search_semantic(query)`: retrieve relevant code chunks. Call BEFORE planning, editing, or answering. Accepts `pathHints` and `languageHints`.",
520
580
  "- `get_file_skeleton(filePath)`: structural overview of a file. Call BEFORE reading any file.",
521
581
  "- `find_usages(symbolName)`: find all references. Call BEFORE editing any function, class, or variable.",
582
+ "- `describe_image(filePath)`: describe an image file using a vision model. Call when user refers to a screenshot, diagram, or image.",
522
583
  "",
523
584
  "Decision tree — ALWAYS follow this order:",
524
585
  "1. User mentions code behavior/architecture → `search_semantic(query)`",
525
586
  "2. User mentions a file path → `get_file_skeleton(filePath)` THEN `read` on specific lines",
526
587
  "3. User mentions a function/class/variable to edit → `find_usages(symbolName)` THEN `search_semantic` THEN `edit`",
527
588
  "4. User asks a code question → `search_semantic` to gather context before answering",
589
+ "5. User asks about an image or visual asset → `describe_image(filePath)` to retrieve its generated description, then optionally `search_semantic` for related code",
528
590
  "",
529
591
  "Proactive triggers — you MUST call these tools when:",
530
592
  "- User asks about code behavior, architecture, or implementation details",
531
593
  "- User asks to edit, refactor, or fix code — call `find_usages` first",
532
594
  "- User references files or functions you haven't read yet",
533
595
  "- User says \"find\", \"search\", \"look up\", \"where is\", \"how does\"",
596
+ "- User refers to an image, screenshot, diagram, or visual asset",
534
597
  "- Before answering ANY code-related question, retrieve context first",
535
598
  "- Before reading ANY file, call `get_file_skeleton` to orient first",
536
599
  "",
@@ -539,6 +602,7 @@ export function createRagHooks(options) {
539
602
  "- Editing a function without calling `find_usages` first (breaks call sites)",
540
603
  "- Answering code questions without calling `search_semantic` first (you guess at behavior)",
541
604
  "- Using `grep`/`glob` when `search_semantic` would find the answer faster",
605
+ "- Treating image files as text — use `describe_image` instead of reading raw bytes",
542
606
  ];
543
607
  output.system.unshift(guidance.join("\n"));
544
608
  // Inject update notification if available
@@ -547,6 +611,11 @@ export function createRagHooks(options) {
547
611
  output.system.unshift(`OpenCodeRAG update available: ${updateInfo.currentVersion} → ${updateInfo.latestVersion}. ` +
548
612
  `Run \`opencode-rag update\` to install.`);
549
613
  }
614
+ // Inject documentation mode system prompt if enabled
615
+ const docMode = getEffectiveCfg().documentationMode;
616
+ if (docMode?.enabled && docMode.systemPrompt) {
617
+ output.system.unshift(docMode.systemPrompt);
618
+ }
550
619
  },
551
620
  async "chat.message"(input, output) {
552
621
  try {
@@ -554,9 +623,90 @@ export function createRagHooks(options) {
554
623
  if (text.length === 0)
555
624
  return;
556
625
  sessionLastMessage.set(input.sessionID, text);
557
- // Track message count for nudge mechanism
558
- const prevMsgCount = sessionMessageCount.get(input.sessionID) ?? 0;
559
- sessionMessageCount.set(input.sessionID, prevMsgCount + 1);
626
+ // Handle /doc slash command
627
+ if (text.startsWith("/doc")) {
628
+ const docMode = getEffectiveCfg().documentationMode;
629
+ if (!docMode?.enabled) {
630
+ const parts = output?.parts ?? output?.message?.parts;
631
+ if (Array.isArray(parts) && parts.length > 0) {
632
+ const first = parts[0];
633
+ if (typeof first.text === "string") {
634
+ parts[0] = { ...first, text: "Documentation mode is not enabled. Set `documentationMode.enabled` to `true` in opencode-rag.json." };
635
+ }
636
+ }
637
+ return;
638
+ }
639
+ const arg = text.slice(4).trim();
640
+ try {
641
+ const manifest = await loadManifest(options.storePath);
642
+ const allFiles = Object.keys(manifest.manifest.files);
643
+ if (allFiles.length === 0) {
644
+ const parts = output?.parts ?? output?.message?.parts;
645
+ if (Array.isArray(parts) && parts.length > 0) {
646
+ const first = parts[0];
647
+ if (typeof first.text === "string") {
648
+ parts[0] = { ...first, text: "No indexed files found. Run indexing first, then use `/doc`." };
649
+ }
650
+ }
651
+ return;
652
+ }
653
+ if (arg) {
654
+ markSubdirectoryDocumented(options.storePath, arg, allFiles);
655
+ }
656
+ const progress = loadDocProgress(options.storePath);
657
+ const remaining = allFiles.filter((f) => !progress.documented.includes(f));
658
+ let docMsg;
659
+ if (remaining.length === 0) {
660
+ docMsg = `**All ${allFiles.length} indexed files have been documented.** No remaining files.`;
661
+ }
662
+ else {
663
+ const grouped = {};
664
+ for (const f of remaining) {
665
+ const dir = path.dirname(f);
666
+ (grouped[dir] ??= []).push(f);
667
+ }
668
+ const lines = [
669
+ "## Documentation",
670
+ "",
671
+ "Pick a subdirectory and document ALL files within it using Google JSDoc style.",
672
+ `When done, type \`/doc <subdirectory>\` to mark it complete and see what's left.`,
673
+ "",
674
+ ];
675
+ for (const [dir, files] of Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))) {
676
+ lines.push(`### ${dir} (${files.length} file${files.length === 1 ? "" : "s"})`);
677
+ for (const f of files) {
678
+ lines.push(`- \`${f}\``);
679
+ }
680
+ lines.push("");
681
+ }
682
+ lines.push(`Progress: ${progress.documented.length} / ${allFiles.length} files documented.`);
683
+ lines.push(`Remaining: ${remaining.length} files.`);
684
+ docMsg = lines.join("\n");
685
+ }
686
+ const parts = output?.parts ?? output?.message?.parts;
687
+ if (Array.isArray(parts) && parts.length > 0) {
688
+ const first = parts[0];
689
+ if (typeof first.text === "string") {
690
+ parts[0] = { ...first, text: docMsg };
691
+ }
692
+ }
693
+ }
694
+ catch (err) {
695
+ appendDebugLog(options.logFilePath, {
696
+ scope: "chat.message",
697
+ message: "Failed to handle /doc command",
698
+ error: err,
699
+ });
700
+ const parts = output?.parts ?? output?.message?.parts;
701
+ if (Array.isArray(parts) && parts.length > 0) {
702
+ const first = parts[0];
703
+ if (typeof first.text === "string") {
704
+ parts[0] = { ...first, text: "Failed to start documentation. Ensure the workspace is indexed." };
705
+ }
706
+ }
707
+ }
708
+ return;
709
+ }
560
710
  const pendingInjection = consumePendingRagInjection(options.storePath);
561
711
  const count = await store.count();
562
712
  if (count === 0)
@@ -583,9 +733,32 @@ export function createRagHooks(options) {
583
733
  return;
584
734
  }
585
735
  if (pendingInjection === "chunks") {
586
- const maxChunks = effectiveCfg.openCode.autoInject?.maxChunks ?? 5;
587
- const topChunks = results.slice(0, maxChunks);
588
- const chunkContext = formatAutoInjectContext(topChunks, options.worktree, effectiveCfg.openCode.autoInject?.maxTokens ?? 3000, maxChunks);
736
+ const autoInjectCfg = effectiveCfg.openCode.autoInject;
737
+ if (autoInjectCfg?.enabled === false) {
738
+ sessionLogger.onRagContext(input.sessionID, input.messageID, {
739
+ chunkCount: 0,
740
+ uniqueFiles: 0,
741
+ contextTokens: 0,
742
+ topScore: 0,
743
+ retrievalTimeMs,
744
+ });
745
+ return;
746
+ }
747
+ const minScore = autoInjectCfg?.minScore ?? 0.85;
748
+ const maxChunks = autoInjectCfg?.maxChunks ?? 5;
749
+ const filtered = results.filter((r) => r.score >= minScore);
750
+ if (filtered.length === 0) {
751
+ sessionLogger.onRagContext(input.sessionID, input.messageID, {
752
+ chunkCount: 0,
753
+ uniqueFiles: 0,
754
+ contextTokens: 0,
755
+ topScore: 0,
756
+ retrievalTimeMs,
757
+ });
758
+ return;
759
+ }
760
+ const topChunks = filtered.slice(0, maxChunks);
761
+ const chunkContext = formatAutoInjectContext(topChunks, options.worktree, autoInjectCfg?.maxTokens ?? 3000, maxChunks);
589
762
  sessionLogger.onRagContext(input.sessionID, input.messageID, {
590
763
  chunkCount: topChunks.length,
591
764
  uniqueFiles: new Set(topChunks.map((r) => r.chunk.metadata.filePath)).size,
@@ -605,12 +778,35 @@ export function createRagHooks(options) {
605
778
  return;
606
779
  }
607
780
  if (pendingInjection === "files") {
608
- const fileList = formatFileList(results, options.worktree, effectiveCfg.retrieval.topK);
781
+ const autoInjectCfg = effectiveCfg.openCode.autoInject;
782
+ if (autoInjectCfg?.enabled === false) {
783
+ sessionLogger.onRagContext(input.sessionID, input.messageID, {
784
+ chunkCount: 0,
785
+ uniqueFiles: 0,
786
+ contextTokens: 0,
787
+ topScore: 0,
788
+ retrievalTimeMs,
789
+ });
790
+ return;
791
+ }
792
+ const minScore = autoInjectCfg?.minScore ?? 0.85;
793
+ const filtered = results.filter((r) => r.score >= minScore);
794
+ if (filtered.length === 0) {
795
+ sessionLogger.onRagContext(input.sessionID, input.messageID, {
796
+ chunkCount: 0,
797
+ uniqueFiles: 0,
798
+ contextTokens: 0,
799
+ topScore: 0,
800
+ retrievalTimeMs,
801
+ });
802
+ return;
803
+ }
804
+ const fileList = formatFileList(filtered, options.worktree, effectiveCfg.retrieval.topK);
609
805
  sessionLogger.onRagContext(input.sessionID, input.messageID, {
610
806
  chunkCount: 0,
611
807
  uniqueFiles: 0,
612
808
  contextTokens: fileList ? countTokens(fileList) : 0,
613
- topScore: results[0]?.score ?? 0,
809
+ topScore: filtered[0]?.score ?? 0,
614
810
  retrievalTimeMs,
615
811
  });
616
812
  if (!fileList)
@@ -675,6 +871,10 @@ export function createRagHooks(options) {
675
871
  },
676
872
  };
677
873
  }
874
+ /**
875
+ * Load the persistent keyword index from disk, falling back to an empty
876
+ * index if loading fails.
877
+ */
678
878
  async function loadKeywordIndex(storePath, logFilePath, logLevel) {
679
879
  const { KeywordIndex } = await import("./retriever/keyword-index.js");
680
880
  try {
@@ -695,6 +895,10 @@ async function loadKeywordIndex(storePath, logFilePath, logLevel) {
695
895
  }
696
896
  }
697
897
  let _cachedNodeExecutable;
898
+ /**
899
+ * Resolve the path to the Node.js executable, caching the result.
900
+ * Checks `process.execPath` first, then falls back to `where`/`which`.
901
+ */
698
902
  function resolveNodeExecutable() {
699
903
  if (_cachedNodeExecutable !== undefined)
700
904
  return _cachedNodeExecutable;
@@ -719,6 +923,10 @@ function resolveNodeExecutable() {
719
923
  _cachedNodeExecutable = found;
720
924
  return _cachedNodeExecutable;
721
925
  }
926
+ /**
927
+ * Resolve the MCP CLI entry point script path.
928
+ * Checks `dist/cli.js` first, then falls back to `src/cli.ts`.
929
+ */
722
930
  function resolveMcpCliEntry() {
723
931
  const selfDir = path.dirname(fileURLToPath(import.meta.url));
724
932
  const packageRoot = path.resolve(selfDir, "..");
@@ -730,6 +938,11 @@ function resolveMcpCliEntry() {
730
938
  return srcCli;
731
939
  return null;
732
940
  }
941
+ /**
942
+ * Start an MCP server process for the given workspace directory.
943
+ * Spawns the CLI in MCP mode as a child process and wires stdout/stderr
944
+ * to the debug log. Returns a handle for graceful shutdown.
945
+ */
733
946
  function startMcpServerProcess(cwd, logFilePath, logLevel) {
734
947
  const cliEntry = resolveMcpCliEntry();
735
948
  if (!cliEntry) {
@@ -797,6 +1010,18 @@ function startMcpServerProcess(cwd, logFilePath, logLevel) {
797
1010
  },
798
1011
  };
799
1012
  }
1013
+ /**
1014
+ * OpenCodeRAG plugin factory — invoked by OpenCode's plugin system for each workspace.
1015
+ *
1016
+ * Bootstraps the full RAG pipeline: loads config, probes vector dimensions,
1017
+ * creates embedder/store, starts the background auto-indexer (if enabled),
1018
+ * launches the MCP server (if enabled), checks for updates, and returns
1019
+ * the RAG hooks that power tools and automatic context injection.
1020
+ *
1021
+ * @param input - Plugin input containing the workspace directory and runtime context.
1022
+ * @param _options - Optional additional plugin options (unused).
1023
+ * @returns A complete set of OpenCode hooks for RAG functionality.
1024
+ */
800
1025
  export const ragPlugin = async (input, _options) => {
801
1026
  const cfg = await getConfig(input.directory);
802
1027
  const logFilePath = path.resolve(input.directory, resolveLogConfig(cfg).logFilePath);
@@ -898,7 +1123,7 @@ export const ragPlugin = async (input, _options) => {
898
1123
  descriptionProvider,
899
1124
  });
900
1125
  // Start background auto-indexer if enabled
901
- const autoIndexCfg = effectiveCfg.openCode.autoIndex ?? { enabled: true, debounceMs: 5000, intervalMs: 300000 };
1126
+ const autoIndexCfg = effectiveCfg.openCode.autoIndex ?? { enabled: false, debounceMs: 5000, intervalMs: 300000 };
902
1127
  if (autoIndexCfg.enabled) {
903
1128
  const indexer = createBackgroundIndexer({
904
1129
  cwd: input.directory,