opencode-rag-plugin 1.15.1 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/ReadMe.md +4 -6
  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 +141 -57
  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
@@ -0,0 +1,183 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, symlinkSync, } from "node:fs";
4
+ import { execSync } from "node:child_process";
5
+ const PLUGIN_NAME = "opencode-rag-plugin";
6
+ export function getRuntimeDir() {
7
+ return path.join(os.homedir(), ".opencode");
8
+ }
9
+ export function getNpmGlobalRoot() {
10
+ return execSync("npm root -g", {
11
+ encoding: "utf-8",
12
+ timeout: 10_000,
13
+ }).trim();
14
+ }
15
+ function createJunction(targetPath, linkPath) {
16
+ const type = process.platform === "win32" ? "junction" : "dir";
17
+ symlinkSync(targetPath, linkPath, type);
18
+ }
19
+ function removeIfExists(targetPath) {
20
+ if (existsSync(targetPath)) {
21
+ rmSync(targetPath, { recursive: true, force: true });
22
+ }
23
+ }
24
+ export function getVersionFile(runtimeDir) {
25
+ return path.join(runtimeDir, ".bundle-version");
26
+ }
27
+ export function readVersionFile(versionFile) {
28
+ try {
29
+ return readFileSync(versionFile, "utf-8").trim();
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ /** Bin name npm generates for this package (without .cmd/.ps1). */
36
+ function getBinName() {
37
+ const pkgJson = path.join(getNpmGlobalRoot(), PLUGIN_NAME, "package.json");
38
+ try {
39
+ const pkg = JSON.parse(readFileSync(pkgJson, "utf-8"));
40
+ if (pkg.bin && typeof pkg.bin === "object") {
41
+ return Object.keys(pkg.bin)[0] ?? PLUGIN_NAME;
42
+ }
43
+ if (typeof pkg.bin === "string") {
44
+ return PLUGIN_NAME;
45
+ }
46
+ }
47
+ catch {
48
+ // fall through
49
+ }
50
+ return PLUGIN_NAME;
51
+ }
52
+ /**
53
+ * npm-generated .cmd / .ps1 wrappers on Windows invoke `.js` files directly
54
+ * via file association. If the `.js` association points to a text editor
55
+ * (e.g. Notepad++) instead of Node.js, the CLI opens the editor instead of
56
+ * running. This function patches the wrappers to call `node` explicitly.
57
+ *
58
+ * Runs only on Windows. Safe to call on every setup — detects already-patched
59
+ * wrappers by checking for the `node` prefix.
60
+ */
61
+ export function patchWindowsWrappers(npmGlobalRoot) {
62
+ if (process.platform !== "win32")
63
+ return;
64
+ const binDir = path.resolve(npmGlobalRoot, "..");
65
+ const binName = getBinName();
66
+ // ── .cmd wrapper ──────────────────────────────────────────────
67
+ const cmdFile = path.join(binDir, `${binName}.cmd`);
68
+ if (existsSync(cmdFile)) {
69
+ const content = readFileSync(cmdFile, "utf-8");
70
+ // npm generates: "%dp0%\node_modules\opencode-rag-plugin\dist\cli\index.js" %*
71
+ // We want: node "%dp0%\node_modules\opencode-rag-plugin\dist\cli\index.js" %*
72
+ const cmdJsLine = /^(?!node\s)("[^"]+\.js"\s+%\*)$/m;
73
+ if (cmdJsLine.test(content)) {
74
+ const patched = content.replace(cmdJsLine, "node $1");
75
+ writeFileSync(cmdFile, patched, "utf-8");
76
+ }
77
+ }
78
+ // ── PowerShell wrapper ────────────────────────────────────────
79
+ const ps1File = path.join(binDir, `${binName}.ps1`);
80
+ if (existsSync(ps1File)) {
81
+ let content = readFileSync(ps1File, "utf-8");
82
+ // Skip if already patched
83
+ if (content.includes('node "$basedir'))
84
+ return;
85
+ // npm generates:
86
+ // $input | & "$basedir/node_modules/.../index.js" $args
87
+ // & "$basedir/node_modules/.../index.js" $args
88
+ // Replace to: & node "$basedir/.../index.js" $args
89
+ const ps1DirectLine = /(&\s+)("\$basedir\/[^"]+\.js"\s+\$args)/g;
90
+ if (ps1DirectLine.test(content)) {
91
+ content = content.replace(ps1DirectLine, "$1node $2");
92
+ writeFileSync(ps1File, content, "utf-8");
93
+ }
94
+ }
95
+ }
96
+ export async function setupRuntime(options) {
97
+ const errors = [];
98
+ const pluginVersion = options?.version || process.env.OPCODE_RAG_VERSION || "0.0.0";
99
+ const runtimeDir = getRuntimeDir();
100
+ const versionFile = getVersionFile(runtimeDir);
101
+ const runtimePluginDir = path.join(runtimeDir, "node_modules", PLUGIN_NAME);
102
+ const runtimeSdkDir = path.join(runtimeDir, "node_modules", "@opencode-ai");
103
+ const runtimeSdkPluginDir = path.join(runtimeSdkDir, "plugin");
104
+ const installedVersion = readVersionFile(versionFile);
105
+ const runtimeDist = path.join(runtimePluginDir, "dist");
106
+ const alreadyInstalled = existsSync(runtimeDist);
107
+ if (alreadyInstalled && installedVersion === pluginVersion && !options?.force) {
108
+ return { success: true, errors: [] };
109
+ }
110
+ let npmGlobalRoot;
111
+ try {
112
+ npmGlobalRoot = getNpmGlobalRoot();
113
+ }
114
+ catch {
115
+ errors.push("npm is not available on PATH. Cannot determine global package location.");
116
+ return { success: false, errors };
117
+ }
118
+ const globalPluginDir = path.join(npmGlobalRoot, PLUGIN_NAME);
119
+ const globalSdkPluginDir = path.join(npmGlobalRoot, "@opencode-ai", "plugin");
120
+ if (!existsSync(globalPluginDir)) {
121
+ errors.push(`Plugin not found at: ${globalPluginDir}`);
122
+ return { success: false, errors };
123
+ }
124
+ if (!existsSync(path.join(globalPluginDir, "dist", "cli.js"))) {
125
+ errors.push(`Global install seems incomplete: dist/ not found in ${globalPluginDir}`);
126
+ return { success: false, errors };
127
+ }
128
+ mkdirSync(runtimeDir, { recursive: true });
129
+ const runtimePkg = path.join(runtimeDir, "package.json");
130
+ if (!existsSync(runtimePkg)) {
131
+ writeFileSync(runtimePkg, JSON.stringify({ private: true, type: "module" }, null, 2) + "\n", "utf-8");
132
+ }
133
+ removeIfExists(runtimePluginDir);
134
+ mkdirSync(path.dirname(runtimePluginDir), { recursive: true });
135
+ try {
136
+ createJunction(globalPluginDir, runtimePluginDir);
137
+ }
138
+ catch {
139
+ const { cpSync } = await import("node:fs");
140
+ cpSync(globalPluginDir, runtimePluginDir, { recursive: true });
141
+ }
142
+ if (existsSync(globalSdkPluginDir)) {
143
+ removeIfExists(runtimeSdkPluginDir);
144
+ mkdirSync(runtimeSdkDir, { recursive: true });
145
+ try {
146
+ createJunction(globalSdkPluginDir, runtimeSdkPluginDir);
147
+ }
148
+ catch {
149
+ const { cpSync } = await import("node:fs");
150
+ cpSync(globalSdkPluginDir, runtimeSdkPluginDir, { recursive: true });
151
+ }
152
+ }
153
+ else {
154
+ mkdirSync(runtimeSdkDir, { recursive: true });
155
+ try {
156
+ execSync(`npm install @opencode-ai/plugin --no-save`, {
157
+ cwd: runtimeDir,
158
+ stdio: "pipe",
159
+ timeout: 60_000,
160
+ });
161
+ }
162
+ catch (cause) {
163
+ errors.push(`Failed to install @opencode-ai/plugin SDK: ${cause.message}`);
164
+ return { success: false, errors };
165
+ }
166
+ }
167
+ writeFileSync(versionFile, pluginVersion, "utf-8");
168
+ patchWindowsWrappers(npmGlobalRoot);
169
+ const cliEntry = path.join(runtimePluginDir, "dist", "cli.js");
170
+ const pluginEntry = path.join(runtimePluginDir, "dist", "plugin-entry.js");
171
+ const sdkPkg = path.join(runtimeSdkPluginDir, "package.json");
172
+ const success = existsSync(cliEntry) && existsSync(pluginEntry) && existsSync(sdkPkg);
173
+ if (!success) {
174
+ if (!existsSync(cliEntry))
175
+ errors.push(`CLI entry missing: ${cliEntry}`);
176
+ if (!existsSync(pluginEntry))
177
+ errors.push(`Plugin entry missing: ${pluginEntry}`);
178
+ if (!existsSync(sdkPkg))
179
+ errors.push(`Plugin SDK missing: ${sdkPkg}`);
180
+ }
181
+ return { success, errors };
182
+ }
183
+ //# sourceMappingURL=setup-runtime.js.map
@@ -0,0 +1,9 @@
1
+ export interface UpdateInfo {
2
+ currentVersion: string;
3
+ latestVersion: string;
4
+ updateAvailable: boolean;
5
+ releaseUrl: string;
6
+ publishedAt: string;
7
+ }
8
+ export declare function compareVersions(a: string, b: string): number;
9
+ export declare function checkForUpdate(currentVersion: string): Promise<UpdateInfo>;
@@ -0,0 +1,49 @@
1
+ export function compareVersions(a, b) {
2
+ const pa = a.split(".").map((s) => parseInt(s, 10));
3
+ const pb = b.split(".").map((s) => parseInt(s, 10));
4
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
5
+ const na = pa[i] ?? 0;
6
+ const nb = pb[i] ?? 0;
7
+ if (na > nb)
8
+ return 1;
9
+ if (na < nb)
10
+ return -1;
11
+ }
12
+ return 0;
13
+ }
14
+ export async function checkForUpdate(currentVersion) {
15
+ const controller = new AbortController();
16
+ const timeout = setTimeout(() => controller.abort(), 5_000);
17
+ try {
18
+ const response = await fetch("https://api.github.com/repos/MrDoe/OpenCodeRAG/releases/latest", {
19
+ headers: {
20
+ Accept: "application/vnd.github+json",
21
+ "User-Agent": "opencode-rag-updater",
22
+ },
23
+ signal: controller.signal,
24
+ });
25
+ if (!response.ok) {
26
+ return { currentVersion, latestVersion: currentVersion, updateAvailable: false, releaseUrl: "", publishedAt: "" };
27
+ }
28
+ const data = (await response.json());
29
+ const tagName = data.tag_name;
30
+ if (!tagName) {
31
+ return { currentVersion, latestVersion: currentVersion, updateAvailable: false, releaseUrl: "", publishedAt: "" };
32
+ }
33
+ const latestVersion = tagName.replace(/^v/i, "");
34
+ return {
35
+ currentVersion,
36
+ latestVersion,
37
+ updateAvailable: compareVersions(latestVersion, currentVersion) > 0,
38
+ releaseUrl: data.html_url ?? "",
39
+ publishedAt: data.published_at ?? "",
40
+ };
41
+ }
42
+ catch {
43
+ return { currentVersion, latestVersion: currentVersion, updateAvailable: false, releaseUrl: "", publishedAt: "" };
44
+ }
45
+ finally {
46
+ clearTimeout(timeout);
47
+ }
48
+ }
49
+ //# sourceMappingURL=version-check.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Anthropic Messages API description provider for generating natural-language descriptions of code chunks.
3
3
  */
4
- import type { Chunk, DescriptionProvider, DescriptionLogger } from "../core/interfaces.js";
4
+ import type { BatchDescriptionOptions, Chunk, DescriptionProvider, DescriptionLogger } from "../core/interfaces.js";
5
5
  import type { DescriptionConfig } from "../core/config.js";
6
6
  /**
7
7
  * Description provider that uses Anthropic's Messages API to generate natural-language descriptions of code chunks.
@@ -18,7 +18,7 @@ export declare class AnthropicDescriptionProvider implements DescriptionProvider
18
18
  /** @inheritdoc */
19
19
  generateDescription(chunk: Chunk): Promise<string>;
20
20
  /** @inheritdoc */
21
- generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger): Promise<Map<string, string>>;
21
+ generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger, opts?: BatchDescriptionOptions): Promise<Map<string, string>>;
22
22
  /**
23
23
  * Sends a request to the Anthropic Messages API with retry and exponential backoff.
24
24
  * The system prompt is combined with all user messages into a single message payload, and
@@ -25,11 +25,11 @@ export class AnthropicDescriptionProvider {
25
25
  return this.chatRequest(messages, this.config.timeoutMs ?? 60000);
26
26
  }
27
27
  /** @inheritdoc */
28
- async generateBatchDescriptions(chunks, logger) {
29
- const log = logger ?? { info: (msg) => console.log(msg), warn: (msg) => console.warn(msg), debug: (msg) => console.debug(msg) };
28
+ async generateBatchDescriptions(chunks, logger, opts) {
29
+ const log = logger ?? { info: (msg) => process.stderr.write(`${msg}\n`), warn: (msg) => process.stderr.write(`${msg}\n`), debug: (msg) => process.stderr.write(`${msg}\n`) };
30
30
  const concurrency = this.config.batchConcurrency ?? 3;
31
31
  const total = chunks.length;
32
- log.info(`[describer] Generating descriptions for ${total} chunks (concurrency: ${concurrency})`);
32
+ log.debug(`[describer] Generating descriptions for ${total} chunks (concurrency: ${concurrency})`);
33
33
  const result = new Map();
34
34
  const limit = pLimit(concurrency);
35
35
  let completed = 0;
@@ -45,11 +45,9 @@ export class AnthropicDescriptionProvider {
45
45
  log.warn(`[describer] Failed to describe chunk ${chunk.id} (${chunk.metadata.filePath}:${chunk.metadata.startLine}): ${err instanceof Error ? err.message : String(err)}`);
46
46
  }
47
47
  completed++;
48
- if (completed % 25 === 0 || completed === total) {
49
- log.info(`[describer] Progress: ${completed}/${total}`);
50
- }
48
+ opts?.onProgress?.(chunk, completed, opts.total ?? total);
51
49
  })));
52
- log.info(`[describer] Descriptions generated: ${result.size}/${total}`);
50
+ log.debug(`[describer] Descriptions generated: ${result.size}/${total}`);
53
51
  return result;
54
52
  }
55
53
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview OpenAI-compatible LLM description provider for generating natural-language descriptions of code chunks.
3
3
  */
4
- import type { Chunk, DescriptionProvider, DescriptionLogger } from "../core/interfaces.js";
4
+ import type { BatchDescriptionOptions, Chunk, DescriptionProvider, DescriptionLogger } from "../core/interfaces.js";
5
5
  import type { DescriptionConfig } from "../core/config.js";
6
6
  /**
7
7
  * Description provider that works with any OpenAI-compatible chat API (including Ollama).
@@ -18,7 +18,7 @@ export declare class LlmDescriptionProvider implements DescriptionProvider {
18
18
  /** @inheritdoc */
19
19
  generateDescription(chunk: Chunk): Promise<string>;
20
20
  /** @inheritdoc */
21
- generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger): Promise<Map<string, string>>;
21
+ generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger, opts?: BatchDescriptionOptions): Promise<Map<string, string>>;
22
22
  /**
23
23
  * Sends a chat completion request to the LLM API with retry and exponential backoff.
24
24
  * For Ollama, uses the `/api/chat` endpoint with streaming disabled; otherwise uses the standard `/v1/chat/completions` endpoint.
@@ -2,7 +2,7 @@ import { postJson } from "../embedder/http.js";
2
2
  import { buildUserMessage, sleep } from "./shared.js";
3
3
  import pLimit from "p-limit";
4
4
  /** HTTP status codes that are safe to retry on. */
5
- const RETRYABLE_STATUSES = new Set([404, 408, 429, 500, 502, 503, 504]);
5
+ const RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
6
6
  /**
7
7
  * Description provider that works with any OpenAI-compatible chat API (including Ollama).
8
8
  *
@@ -26,11 +26,11 @@ export class LlmDescriptionProvider {
26
26
  return this.chatRequest(messages, this.config.timeoutMs ?? 60000);
27
27
  }
28
28
  /** @inheritdoc */
29
- async generateBatchDescriptions(chunks, logger) {
30
- const log = logger ?? { info: (msg) => process.stderr.write(`${msg}\n`), warn: (msg) => console.warn(msg), debug: (msg) => console.debug(msg) };
29
+ async generateBatchDescriptions(chunks, logger, opts) {
30
+ const log = logger ?? { info: (msg) => process.stderr.write(`${msg}\n`), warn: (msg) => process.stderr.write(`${msg}\n`), debug: (msg) => process.stderr.write(`${msg}\n`) };
31
31
  const concurrency = this.config.batchConcurrency ?? 3;
32
32
  const total = chunks.length;
33
- log.info(`Generating descriptions for ${total} chunks via ${this.config.provider}/${this.config.model} (concurrency: ${concurrency})...`);
33
+ log.debug(`[describer] Generating descriptions for ${total} chunks via ${this.config.provider}/${this.config.model} (concurrency: ${concurrency})`);
34
34
  const result = new Map();
35
35
  const limit = pLimit(concurrency);
36
36
  let completed = 0;
@@ -46,11 +46,9 @@ export class LlmDescriptionProvider {
46
46
  log.warn(`[describer] Failed to describe chunk ${chunk.id} (${chunk.metadata.filePath}:${chunk.metadata.startLine}): ${err instanceof Error ? err.message : String(err)}`);
47
47
  }
48
48
  completed++;
49
- if (completed % 25 === 0 || completed === total) {
50
- log.info(`Descriptions: ${completed}/${total}`);
51
- }
49
+ opts?.onProgress?.(chunk, completed, opts.total ?? total);
52
50
  })));
53
- log.info(`Descriptions: ${result.size}/${total} done.`);
51
+ log.debug(`[describer] Descriptions generated: ${result.size}/${total}`);
54
52
  return result;
55
53
  }
56
54
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Google Gemini description provider for generating natural-language descriptions of code chunks.
3
3
  */
4
- import type { Chunk, DescriptionProvider, DescriptionLogger } from "../core/interfaces.js";
4
+ import type { BatchDescriptionOptions, Chunk, DescriptionProvider, DescriptionLogger } from "../core/interfaces.js";
5
5
  import type { DescriptionConfig } from "../core/config.js";
6
6
  /**
7
7
  * Description provider that uses Google Gemini's generateContent API to describe code chunks.
@@ -12,6 +12,6 @@ export declare class GeminiDescriptionProvider implements DescriptionProvider {
12
12
  private readonly config;
13
13
  constructor(config: DescriptionConfig);
14
14
  generateDescription(chunk: Chunk): Promise<string>;
15
- generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger): Promise<Map<string, string>>;
15
+ generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger, opts?: BatchDescriptionOptions): Promise<Map<string, string>>;
16
16
  private chatRequest;
17
17
  }
@@ -21,11 +21,11 @@ export class GeminiDescriptionProvider {
21
21
  ];
22
22
  return this.chatRequest(contents, this.config.timeoutMs ?? 60000);
23
23
  }
24
- async generateBatchDescriptions(chunks, logger) {
25
- const log = logger ?? { info: (msg) => console.log(msg), warn: (msg) => console.warn(msg), debug: (msg) => console.debug(msg) };
24
+ async generateBatchDescriptions(chunks, logger, opts) {
25
+ const log = logger ?? { info: (msg) => process.stderr.write(`${msg}\n`), warn: (msg) => process.stderr.write(`${msg}\n`), debug: (msg) => process.stderr.write(`${msg}\n`) };
26
26
  const concurrency = this.config.batchConcurrency ?? 3;
27
27
  const total = chunks.length;
28
- log.info(`[describer] Generating descriptions for ${total} chunks (concurrency: ${concurrency})`);
28
+ log.debug(`[describer] Generating descriptions for ${total} chunks (concurrency: ${concurrency})`);
29
29
  const result = new Map();
30
30
  const limit = pLimit(concurrency);
31
31
  let completed = 0;
@@ -41,11 +41,9 @@ export class GeminiDescriptionProvider {
41
41
  log.warn(`[describer] Failed to describe chunk ${chunk.id} (${chunk.metadata.filePath}:${chunk.metadata.startLine}): ${err instanceof Error ? err.message : String(err)}`);
42
42
  }
43
43
  completed++;
44
- if (completed % 25 === 0 || completed === total) {
45
- log.info(`[describer] Progress: ${completed}/${total}`);
46
- }
44
+ opts?.onProgress?.(chunk, completed, opts.total ?? total);
47
45
  })));
48
- log.info(`[describer] Descriptions generated: ${result.size}/${total}`);
46
+ log.debug(`[describer] Descriptions generated: ${result.size}/${total}`);
49
47
  return result;
50
48
  }
51
49
  async chatRequest(contents, timeoutMs) {
@@ -27,6 +27,8 @@ export declare function createEmbedder(config: RagConfig): EmbeddingProvider;
27
27
  * @param batchSize - Number of texts per batch (default 10)
28
28
  * @param purpose - Optional hint for query vs. document embedding
29
29
  * @param concurrency - Maximum number of concurrent batch requests (default 1)
30
+ * @param onProgress - Optional callback invoked after each batch with the running
31
+ * completed count and total; per-text granularity when `concurrency <= 1`.
30
32
  * @returns A promise resolving to a flat array of embedding vectors (one per input text)
31
33
  */
32
- export declare function embedBatch(embedder: EmbeddingProvider, texts: string[], batchSize?: number, purpose?: "query" | "document", concurrency?: number): Promise<number[][]>;
34
+ export declare function embedBatch(embedder: EmbeddingProvider, texts: string[], batchSize?: number, purpose?: "query" | "document", concurrency?: number, onProgress?: (completed: number, total: number) => void): Promise<number[][]>;
@@ -46,9 +46,11 @@ export function createEmbedder(config) {
46
46
  * @param batchSize - Number of texts per batch (default 10)
47
47
  * @param purpose - Optional hint for query vs. document embedding
48
48
  * @param concurrency - Maximum number of concurrent batch requests (default 1)
49
+ * @param onProgress - Optional callback invoked after each batch with the running
50
+ * completed count and total; per-text granularity when `concurrency <= 1`.
49
51
  * @returns A promise resolving to a flat array of embedding vectors (one per input text)
50
52
  */
51
- export async function embedBatch(embedder, texts, batchSize = 10, purpose, concurrency = 1) {
53
+ export async function embedBatch(embedder, texts, batchSize = 10, purpose, concurrency = 1, onProgress) {
52
54
  if (texts.length === 0)
53
55
  return [];
54
56
  const batches = [];
@@ -60,12 +62,16 @@ export async function embedBatch(embedder, texts, batchSize = 10, purpose, concu
60
62
  for (const batch of batches) {
61
63
  const embeddings = await embedder.embed(batch.texts, purpose);
62
64
  results.push(...embeddings);
65
+ onProgress?.(results.length, texts.length);
63
66
  }
64
67
  return results;
65
68
  }
66
69
  const limit = pLimit(concurrency);
70
+ let completedCount = 0;
67
71
  const batchResults = await Promise.all(batches.map((batch) => limit(async () => {
68
72
  const embeddings = await embedder.embed(batch.texts, purpose);
73
+ completedCount += embeddings.length;
74
+ onProgress?.(completedCount, texts.length);
69
75
  return { index: batch.index, embeddings };
70
76
  })));
71
77
  batchResults.sort((a, b) => a.index - b.index);
@@ -328,6 +328,10 @@ export async function pullOllamaModels(models, onProgress) {
328
328
  }
329
329
  }
330
330
  finally {
331
+ try {
332
+ await reader.cancel();
333
+ }
334
+ catch { }
331
335
  reader.releaseLock();
332
336
  }
333
337
  }
@@ -58,4 +58,4 @@ export declare function directRequest(url: URL, body: unknown, headers: Record<s
58
58
  * @param proxy - Optional proxy configuration
59
59
  * @returns A promise resolving to an HttpResponseLike
60
60
  */
61
- export declare function postJson(urlString: string, body: unknown, headers: Record<string, string>, timeoutMs: number, proxy?: ProxyConfig): Promise<HttpResponseLike>;
61
+ export declare function postJson(urlString: string, body: unknown, headers: Record<string, string>, timeoutMs: number, proxy?: ProxyConfig, signal?: AbortSignal): Promise<HttpResponseLike>;
@@ -6,6 +6,10 @@ import tls from "node:tls";
6
6
  const MAX_POOL_SIZE = 4;
7
7
  const IDLE_TIMEOUT_MS = 30000;
8
8
  const connectionPool = new Map();
9
+ // Serialize proxied fetch requests that mutate process.env,
10
+ // preventing races when concurrent requests interleave
11
+ // set/restore of HTTP_PROXY/HTTPS_PROXY.
12
+ let proxyRequestQueue = Promise.resolve(undefined);
9
13
  function poolKey(host, port, isHttps) {
10
14
  return `${isHttps ? "tls" : "tcp"}:${host}:${port}`;
11
15
  }
@@ -43,7 +47,13 @@ function releaseConnection(socket, host, port, isHttps) {
43
47
  pool.splice(idx, 1);
44
48
  socket.destroy();
45
49
  }, IDLE_TIMEOUT_MS);
46
- socket.removeAllListeners();
50
+ // Avoid removeAllListeners — that would strip the error handler
51
+ // and cause an unhandled crash on ECONNRESET/TLS alert.
52
+ socket.removeAllListeners("data");
53
+ socket.removeAllListeners("end");
54
+ socket.removeAllListeners("drain");
55
+ socket.removeAllListeners("error");
56
+ socket.on("error", () => socket.destroy());
47
57
  pool.push({ socket, idleTimer });
48
58
  }
49
59
  /** Destroy all pooled TCP/TLS sockets and clear the connection pool. */
@@ -307,9 +317,9 @@ async function sendRawHttpRequest(url, body, headers, timeoutMs, redirectCount)
307
317
  return;
308
318
  }
309
319
  if (isChunked) {
310
- if (bodyBuffer.length >= 5) {
311
- const tail = bodyBuffer.slice(-7).toString("ascii");
312
- if (tail === "\r\n0\r\n\r\n" || bodyBuffer.toString("ascii") === "0\r\n\r\n") {
320
+ if (bodyBuffer.length >= 7) {
321
+ const ascii = bodyBuffer.toString("ascii");
322
+ if (/0(?:;[^\n]*)?\r\n(?:[^\n]+:[^\n]*\r\n)*\r\n$/.test(ascii)) {
313
323
  settle(() => {
314
324
  releaseOrDestroy();
315
325
  resolve(assembled);
@@ -371,7 +381,15 @@ async function sendRawHttpRequest(url, body, headers, timeoutMs, redirectCount)
371
381
  },
372
382
  };
373
383
  }
374
- return sendRawHttpRequest(new URL(location, url), body, headers, timeoutMs, redirectCount + 1);
384
+ const redirectUrl = new URL(location, url);
385
+ const sameOrigin = url.protocol === redirectUrl.protocol &&
386
+ url.hostname === redirectUrl.hostname &&
387
+ (url.port || (url.protocol === "https:" ? "443" : "80")) ===
388
+ (redirectUrl.port || (redirectUrl.protocol === "https:" ? "443" : "80"));
389
+ const safeHeaders = sameOrigin
390
+ ? headers
391
+ : Object.fromEntries(Object.entries(headers).filter(([key]) => key.toLowerCase() !== "authorization"));
392
+ return sendRawHttpRequest(redirectUrl, body, safeHeaders, timeoutMs, redirectCount + 1);
375
393
  }
376
394
  const text = response.body.toString("utf8");
377
395
  return {
@@ -399,52 +417,65 @@ async function sendRawHttpRequest(url, body, headers, timeoutMs, redirectCount)
399
417
  * @param proxy - Optional proxy configuration
400
418
  * @returns A promise resolving to an HttpResponseLike
401
419
  */
402
- export async function postJson(urlString, body, headers, timeoutMs, proxy) {
420
+ export async function postJson(urlString, body, headers, timeoutMs, proxy, signal) {
403
421
  const url = new URL(urlString);
404
422
  const bypassProxy = isLocalhost(url.hostname) || matchesNoProxy(url.hostname, proxy?.noProxy);
405
423
  if (bypassProxy || !proxy?.url) {
406
424
  return directRequest(url, body, headers, timeoutMs);
407
425
  }
408
- return postJsonViaFetch(urlString, body, headers, timeoutMs, proxy);
426
+ return postJsonViaFetch(urlString, body, headers, timeoutMs, proxy, signal);
409
427
  }
410
428
  /** Send JSON via the global `fetch` API with proxy environment variable overrides. */
411
- async function postJsonViaFetch(urlString, body, headers, timeoutMs, proxy) {
429
+ async function postJsonViaFetch(urlString, body, headers, timeoutMs, proxy, signal) {
412
430
  const authHeader = buildProxyAuthHeader(proxy);
413
431
  const envOverride = applyProxyEnv(proxy);
414
- const savedHttpProxy = process.env.HTTP_PROXY;
415
- const savedHttpsProxy = process.env.HTTPS_PROXY;
416
- try {
417
- if (envOverride) {
418
- process.env.HTTP_PROXY = envOverride.httpProxy;
419
- process.env.HTTPS_PROXY = envOverride.httpsProxy;
420
- }
421
- const requestHeaders = {
422
- "Content-Type": "application/json",
423
- ...headers,
424
- };
425
- if (authHeader) {
426
- requestHeaders["Proxy-Authorization"] = authHeader;
427
- }
428
- const controller = new AbortController();
429
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
430
- try {
431
- const response = await fetch(urlString, {
432
- method: "POST",
433
- headers: requestHeaders,
434
- body: JSON.stringify(body),
435
- signal: controller.signal,
436
- });
437
- return response;
438
- }
439
- finally {
440
- clearTimeout(timeout);
441
- }
442
- }
443
- finally {
444
- if (envOverride) {
445
- process.env.HTTP_PROXY = savedHttpProxy;
446
- process.env.HTTPS_PROXY = savedHttpsProxy;
447
- }
448
- }
432
+ // Serialize fetch requests that mutate process.env to prevent
433
+ // concurrent requests from interleaving set/restore of proxy vars.
434
+ const [savedHttpProxy, savedHttpsProxy] = [process.env.HTTP_PROXY, process.env.HTTPS_PROXY];
435
+ return new Promise((resolve, reject) => {
436
+ proxyRequestQueue = proxyRequestQueue.then(async () => {
437
+ try {
438
+ if (envOverride) {
439
+ process.env.HTTP_PROXY = envOverride.httpProxy;
440
+ process.env.HTTPS_PROXY = envOverride.httpsProxy;
441
+ }
442
+ const requestHeaders = {
443
+ "Content-Type": "application/json",
444
+ ...headers,
445
+ };
446
+ if (authHeader) {
447
+ requestHeaders["Proxy-Authorization"] = authHeader;
448
+ }
449
+ const controller = new AbortController();
450
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
451
+ const onAbort = () => { controller.abort(); clearTimeout(timeout); };
452
+ signal?.addEventListener("abort", onAbort, { once: true });
453
+ try {
454
+ const response = await fetch(urlString, {
455
+ method: "POST",
456
+ headers: requestHeaders,
457
+ body: JSON.stringify(body),
458
+ signal: controller.signal,
459
+ });
460
+ resolve(response);
461
+ }
462
+ finally {
463
+ clearTimeout(timeout);
464
+ signal?.removeEventListener("abort", onAbort);
465
+ if (envOverride) {
466
+ process.env.HTTP_PROXY = savedHttpProxy;
467
+ process.env.HTTPS_PROXY = savedHttpsProxy;
468
+ }
469
+ }
470
+ }
471
+ catch (err) {
472
+ if (envOverride) {
473
+ process.env.HTTP_PROXY = savedHttpProxy;
474
+ process.env.HTTPS_PROXY = savedHttpsProxy;
475
+ }
476
+ reject(err);
477
+ }
478
+ });
479
+ });
449
480
  }
450
481
  //# sourceMappingURL=http.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @fileoverview Compare two branch benchmark JSON outputs and produce a side-by-side
3
+ * analysis report (console table + markdown file).
4
+ *
5
+ * Usage: node --import tsx src/eval/compare-merge.ts
6
+ * --main .opencode/rag_db/eval-results/main.json
7
+ * --branch .opencode/rag_db/eval-results/t1-cosine-l2.json
8
+ * --output .opencode/rag_db/eval-results/branch-compare-report.md
9
+ */
10
+ export {};