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
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Configuration types and defaults for OpenCodeRAG. Defines RagConfig,
3
3
  * all sub-config interfaces, DEFAULT_CONFIG, validateConfig, and loadConfig.
4
4
  */
5
- import { existsSync, readFileSync } from "node:fs";
5
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import path from "node:path";
7
7
  import { env } from "node:process";
8
8
  export const DEFAULT_CONFIG = {
@@ -106,6 +106,7 @@ export const DEFAULT_CONFIG = {
106
106
  embedConcurrency: 6,
107
107
  ollamaMaxBatchSize: 4000,
108
108
  descriptionConcurrency: 4,
109
+ maxSvgSizeBytes: 1_048_576,
109
110
  },
110
111
  vectorStore: {
111
112
  path: "./.opencode/rag_db",
@@ -113,7 +114,7 @@ export const DEFAULT_CONFIG = {
113
114
  },
114
115
  retrieval: {
115
116
  topK: 20,
116
- minScore: 0.5,
117
+ minScore: 0.35,
117
118
  hybridSearch: {
118
119
  enabled: true,
119
120
  keywordWeight: 0.4,
@@ -130,6 +131,7 @@ export const DEFAULT_CONFIG = {
130
131
  enabled: true,
131
132
  maxContextChunks: 10,
132
133
  readOverride: true,
134
+ injectSystemPrompt: true,
133
135
  autoIndex: {
134
136
  enabled: false,
135
137
  debounceMs: 2000,
@@ -143,7 +145,7 @@ export const DEFAULT_CONFIG = {
143
145
  model: "minicpm-v4.6:latest",
144
146
  baseUrl: "http://127.0.0.1:11434/api",
145
147
  timeoutMs: 60000,
146
- prompt: "Describe this image 10-20 comma-separated keywords.",
148
+ prompt: "Describe this image in 1-2 sentences: what it shows, its purpose, and key visual elements.",
147
149
  think: false,
148
150
  numCtx: 2048,
149
151
  resizeMaxDimension: 1024,
@@ -156,7 +158,7 @@ export const DEFAULT_CONFIG = {
156
158
  think: false,
157
159
  numCtx: 4096,
158
160
  timeoutMs: 60000,
159
- systemPrompt: "Describe this code in 10-20 comma-separated keywords.",
161
+ systemPrompt: "Describe this code in 2-3 sentences: purpose, key concepts, inputs/outputs, and dependencies. No code repetition.",
160
162
  batchConcurrency: 1,
161
163
  retryMax: 3,
162
164
  retryBaseDelayMs: 1000,
@@ -426,4 +428,32 @@ export function loadConfig(filePath, validate = true) {
426
428
  }
427
429
  return cfg;
428
430
  }
431
+ /**
432
+ * Persist a probed embedding vector dimension into the config JSON file.
433
+ * Sets `embedding.vectorDimension` so subsequent startups skip the probe.
434
+ * This is a best-effort operation — failures are silently ignored.
435
+ *
436
+ * @param configPath - Absolute path to the config JSON file.
437
+ * @param dimension - The vector dimension to persist.
438
+ */
439
+ export function persistProbedDimension(configPath, dimension) {
440
+ let raw;
441
+ try {
442
+ raw = readFileSync(configPath, "utf-8");
443
+ }
444
+ catch {
445
+ return;
446
+ }
447
+ try {
448
+ const obj = JSON.parse(raw);
449
+ const embedding = (obj.embedding ??= {});
450
+ if (embedding.vectorDimension === dimension)
451
+ return; // already set
452
+ embedding.vectorDimension = dimension;
453
+ writeFileSync(configPath, JSON.stringify(obj, null, 2), "utf-8");
454
+ }
455
+ catch {
456
+ // best-effort
457
+ }
458
+ }
429
459
  //# sourceMappingURL=config.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @fileoverview Persistent description cache keyed by content hash + description config hash.
3
+ * Survives aborted index runs so that descriptions are not regenerated unnecessarily.
4
+ * Two namespaces: "code" (per-chunk descriptions) and "image" (per-file image descriptions).
5
+ */
6
+ export declare class DescriptionCache {
7
+ private cache;
8
+ private cacheDir;
9
+ private dirty;
10
+ private savePromise;
11
+ private loadPromise;
12
+ constructor(cacheDir: string);
13
+ /** Load the cache from disk. */
14
+ load(): Promise<void>;
15
+ /** Get a cached description for the given key. */
16
+ get(key: string): string | undefined;
17
+ /** Set a cached description for the given key. */
18
+ set(key: string, description: string): void;
19
+ /** Set multiple entries at once. */
20
+ setMany(entries: Array<[string, string]>): void;
21
+ /** Check if a key exists in the cache. */
22
+ has(key: string): boolean;
23
+ /** Persist the cache to disk if dirty. Safe to call multiple times. */
24
+ save(): Promise<void>;
25
+ /** Build a cache key for a code chunk. */
26
+ static codeKey(content: string, descConfigHash: string): string;
27
+ /** Build a cache key for an image file. */
28
+ static imageKey(imageBytesHash: string, imageDescConfigHash: string): string;
29
+ /** Remove old entries to stay under the limit. */
30
+ private maybeEvict;
31
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @fileoverview Persistent description cache keyed by content hash + description config hash.
3
+ * Survives aborted index runs so that descriptions are not regenerated unnecessarily.
4
+ * Two namespaces: "code" (per-chunk descriptions) and "image" (per-file image descriptions).
5
+ */
6
+ import fs from "node:fs/promises";
7
+ import path from "node:path";
8
+ import { createHash } from "node:crypto";
9
+ const CACHE_VERSION = 1;
10
+ const MAX_ENTRIES = 50_000;
11
+ const MAX_AGE_DAYS = 30;
12
+ export class DescriptionCache {
13
+ cache = { version: CACHE_VERSION, entries: {} };
14
+ cacheDir;
15
+ dirty = false;
16
+ savePromise = Promise.resolve();
17
+ loadPromise = null;
18
+ constructor(cacheDir) {
19
+ this.cacheDir = cacheDir;
20
+ }
21
+ /** Load the cache from disk. */
22
+ async load() {
23
+ if (this.loadPromise)
24
+ return this.loadPromise;
25
+ this.loadPromise = (async () => {
26
+ const cachePath = path.join(this.cacheDir, ".desc-cache.json");
27
+ try {
28
+ const raw = await fs.readFile(cachePath, "utf-8");
29
+ const parsed = JSON.parse(raw);
30
+ if (parsed.version === CACHE_VERSION && parsed.entries) {
31
+ this.cache = parsed;
32
+ this.dirty = false;
33
+ }
34
+ }
35
+ catch {
36
+ // Cache file missing or corrupt — start fresh
37
+ }
38
+ })();
39
+ return this.loadPromise;
40
+ }
41
+ /** Get a cached description for the given key. */
42
+ get(key) {
43
+ return this.cache.entries[key]?.description;
44
+ }
45
+ /** Set a cached description for the given key. */
46
+ set(key, description) {
47
+ this.cache.entries[key] = {
48
+ description,
49
+ createdAt: Date.now(),
50
+ };
51
+ this.dirty = true;
52
+ this.maybeEvict();
53
+ }
54
+ /** Set multiple entries at once. */
55
+ setMany(entries) {
56
+ const now = Date.now();
57
+ for (const [key, description] of entries) {
58
+ this.cache.entries[key] = { description, createdAt: now };
59
+ }
60
+ this.dirty = true;
61
+ this.maybeEvict();
62
+ }
63
+ /** Check if a key exists in the cache. */
64
+ has(key) {
65
+ return key in this.cache.entries;
66
+ }
67
+ /** Persist the cache to disk if dirty. Safe to call multiple times. */
68
+ async save() {
69
+ if (!this.dirty)
70
+ return;
71
+ this.savePromise = this.savePromise.then(async () => {
72
+ const cachePath = path.join(this.cacheDir, ".desc-cache.json");
73
+ const tmpPath = cachePath + ".tmp";
74
+ try {
75
+ await fs.mkdir(this.cacheDir, { recursive: true });
76
+ await fs.writeFile(tmpPath, JSON.stringify(this.cache), "utf-8");
77
+ try {
78
+ await fs.rename(tmpPath, cachePath);
79
+ }
80
+ catch {
81
+ await fs.unlink(cachePath).catch(() => { });
82
+ await fs.rename(tmpPath, cachePath);
83
+ }
84
+ this.dirty = false;
85
+ }
86
+ catch {
87
+ // Best-effort save
88
+ }
89
+ });
90
+ return this.savePromise;
91
+ }
92
+ /** Build a cache key for a code chunk. */
93
+ static codeKey(content, descConfigHash) {
94
+ const contentHash = createHash("sha256").update(content).digest("hex").slice(0, 16);
95
+ return contentHash + "_" + descConfigHash.slice(0, 16);
96
+ }
97
+ /** Build a cache key for an image file. */
98
+ static imageKey(imageBytesHash, imageDescConfigHash) {
99
+ return imageBytesHash.slice(0, 16) + "_" + imageDescConfigHash.slice(0, 16);
100
+ }
101
+ /** Remove old entries to stay under the limit. */
102
+ maybeEvict() {
103
+ const keys = Object.keys(this.cache.entries);
104
+ if (keys.length <= MAX_ENTRIES)
105
+ return;
106
+ const cutoff = Date.now() - MAX_AGE_DAYS * 24 * 60 * 60 * 1000;
107
+ const entries = this.cache.entries;
108
+ for (const key of keys) {
109
+ if (entries[key].createdAt < cutoff) {
110
+ delete entries[key];
111
+ }
112
+ }
113
+ // If still over limit, delete oldest
114
+ if (Object.keys(entries).length > MAX_ENTRIES) {
115
+ const sorted = Object.entries(entries)
116
+ .sort(([, a], [, b]) => a.createdAt - b.createdAt);
117
+ const toDelete = Object.keys(entries).length - MAX_ENTRIES;
118
+ for (let i = 0; i < toDelete; i++) {
119
+ delete entries[sorted[i][0]];
120
+ }
121
+ }
122
+ }
123
+ }
124
+ //# sourceMappingURL=desc-cache.js.map
@@ -22,12 +22,26 @@ export interface DescriptionLogger {
22
22
  warn(message: string): void;
23
23
  debug(message: string): void;
24
24
  }
25
+ /** Progress callback invoked after each chunk is described during batch generation. */
26
+ export type ChunkProgressFn = (chunk: Chunk, completed: number, total: number) => void;
27
+ /** Optional progress reporting for batch description generation. */
28
+ export interface BatchDescriptionOptions {
29
+ /** Total number of chunks across the whole run (may exceed `chunks.length` when called per sub-batch). */
30
+ total?: number;
31
+ /** Called once per completed chunk with the chunk, the running completed count, and `total`. */
32
+ onProgress?: ChunkProgressFn;
33
+ }
25
34
  /** Generates natural-language descriptions for code chunks using an LLM. */
26
35
  export interface DescriptionProvider {
27
36
  /** Generate a description for a single chunk. */
28
37
  generateDescription(chunk: Chunk): Promise<string>;
29
- /** Generate descriptions for multiple chunks concurrently. Returns a Map of chunk ID to description. */
30
- generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger): Promise<Map<string, string>>;
38
+ /**
39
+ * Generate descriptions for multiple chunks concurrently. Returns a Map of chunk ID to description.
40
+ * @param chunks - Chunks to describe.
41
+ * @param logger - Optional logger for diagnostic messages.
42
+ * @param opts - Optional progress reporting options.
43
+ */
44
+ generateBatchDescriptions(chunks: Chunk[], logger?: DescriptionLogger, opts?: BatchDescriptionOptions): Promise<Map<string, string>>;
31
45
  }
32
46
  /** Explains how a search result score was computed, including vector and keyword contributions. */
33
47
  export interface SearchExplanation {
@@ -43,6 +57,10 @@ export interface SearchExplanation {
43
57
  rawKeywordScore: number;
44
58
  /** Weight applied to keyword score during fusion (0-1). */
45
59
  keywordWeight: number;
60
+ /** Rank (0-indexed) in the vector store results. */
61
+ vectorRank?: number;
62
+ /** Rank (0-indexed) in the keyword index results. */
63
+ keywordRank?: number;
46
64
  };
47
65
  /** Query terms that matched in the keyword index, if hybrid search was used. */
48
66
  matchedTerms?: string[];
@@ -86,12 +104,14 @@ export interface EmbeddingProvider {
86
104
  }
87
105
  /** In-memory TF-IDF inverted index for keyword-based search alongside vector search. */
88
106
  export interface KeywordIndex {
107
+ /** Release resources and free memory. */
108
+ close(): void;
89
109
  /** Index a set of chunks by tokenizing their content. */
90
110
  addChunks(chunks: Chunk[]): void;
91
111
  /** Remove all entries for a given file path from the index. */
92
112
  removeByFilePath(filePath: string): void;
93
113
  /** Search the index for the top-K matching chunks. */
94
- search(query: string, topK: number): SearchResult[];
114
+ search(query: string, topK: number, filter?: MetadataFilter): SearchResult[];
95
115
  /** Get the terms from a query that matched a specific chunk. */
96
116
  getMatchedTerms(query: string, chunkId: string): string[];
97
117
  /** Clear all indexed data. */
@@ -101,20 +121,55 @@ export interface KeywordIndex {
101
121
  /** Persist the index to disk as JSON. */
102
122
  save(filePath?: string): Promise<void>;
103
123
  }
124
+ /** A paginated chunk summary for dump/list operations. */
125
+ export interface ChunkSummary {
126
+ id: string;
127
+ filePath: string;
128
+ language: string;
129
+ startLine: number;
130
+ endLine: number;
131
+ content: string;
132
+ description: string;
133
+ }
134
+ /** A file summary for list operations. */
135
+ export interface FileSummary {
136
+ filePath: string;
137
+ language: string;
138
+ chunkCount: number;
139
+ }
104
140
  /** Persistent vector storage and retrieval backend (LanceDB or in-memory). */
105
141
  export interface VectorStore {
106
142
  /** Store a batch of chunks with their embeddings. */
107
143
  addChunks(chunks: Chunk[]): Promise<void>;
108
144
  /** Search for the top-K nearest neighbor chunks by embedding similarity. */
109
145
  search(embedding: number[], topK: number): Promise<SearchResult[]>;
146
+ /** Search with optional metadata filtering. */
147
+ searchWithFilter(embedding: number[], topK: number, filter?: MetadataFilter): Promise<SearchResult[]>;
110
148
  /** Return the total number of stored chunks. */
111
149
  count(): Promise<number>;
112
150
  /** Remove all stored data. */
113
151
  clear(): Promise<void>;
114
152
  /** Delete all chunks associated with a specific file path. */
115
153
  deleteByFilePath(filePath: string): Promise<void>;
154
+ /** Return all unique file paths currently stored in the index. */
155
+ getFilePaths(): Promise<string[]>;
116
156
  /** Release any held resources and close the store. */
117
157
  close(): Promise<void>;
158
+ /** Retrieve a paginated list of chunks without embeddings. */
159
+ getChunks(offset: number, limit: number): Promise<ChunkSummary[]>;
160
+ /** List all distinct file paths with language and chunk count. */
161
+ listFiles(): Promise<FileSummary[]>;
162
+ /** Retrieve all chunks for a specific file path, sorted by start line. */
163
+ getChunksByFilePath(filePath: string): Promise<Chunk[]>;
164
+ /** Re-open the store, optionally pointing at a new database path. */
165
+ reopen?(newPath?: string): Promise<void>;
166
+ }
167
+ /** Filter criteria for narrowing search results by file path patterns or language. */
168
+ export interface MetadataFilter {
169
+ /** Glob-style path patterns (e.g. "src/**", "lib/auth/*"). */
170
+ pathPatterns?: string[];
171
+ /** Language identifiers (e.g. ["typescript", "tsx"]). */
172
+ languages?: string[];
118
173
  }
119
174
  /** Callback interface for reporting indexing progress to the UI or CLI. */
120
175
  export interface IndexProgress {
@@ -2,6 +2,7 @@
2
2
  * @fileoverview File hash manifest for tracking indexing state across sessions.
3
3
  * Provides load/save with atomic writes, schema versioning, and corruption detection.
4
4
  */
5
+ import type { RagConfig } from "./config.js";
5
6
  /** Metadata entry for a single indexed file. */
6
7
  export interface ManifestEntry {
7
8
  /** SHA-256 hash of the file content. */
@@ -16,9 +17,22 @@ export interface ManifestEntry {
16
17
  size?: number;
17
18
  /** Whether the description generation step failed for this file. */
18
19
  descriptionFailed?: boolean;
20
+ /**
21
+ * Hash of the description config (provider, model, baseUrl, prompts) that
22
+ * was active when descriptions were generated for this file. Skipped when
23
+ * no description provider is configured. Used to avoid re-describing
24
+ * files whose source content and description config are both unchanged.
25
+ */
26
+ descHash?: string;
19
27
  }
20
28
  /** Current schema version for manifest files. */
21
- export declare const SCHEMA_VERSION = 1;
29
+ export declare const SCHEMA_VERSION = 3;
30
+ /**
31
+ * Oldest schema version still accepted as valid.
32
+ * Bumping this intentionally marks older manifests corrupt and forces a
33
+ * rebuild, so only raise it when a migration is not practical.
34
+ */
35
+ export declare const MIN_SUPPORTED_SCHEMA_VERSION = 3;
22
36
  /** Persistent manifest tracking indexing state across sessions. */
23
37
  export interface FileManifest {
24
38
  /** Unix timestamp of the last full index pass. */
@@ -49,6 +63,15 @@ export declare function manifestPathFor(dbPath: string): string;
49
63
  export declare function normalizeFilePath(filePath: string): string;
50
64
  /** Compute the SHA-256 hex hash of a string. */
51
65
  export declare function computeFileHash(content: string): string;
66
+ /**
67
+ * Compute a hash of the description configuration (provider, model, baseUrl,
68
+ * systemPrompt, image prompt, etc.) so that files are only re-described when
69
+ * the config actually changes.
70
+ *
71
+ * Returns `undefined` when neither `description` nor `imageDescription` is
72
+ * configured — callers treat this as "no descriptions needed".
73
+ */
74
+ export declare function computeDescriptionConfigHash(config: RagConfig): string | undefined;
52
75
  /** Load a manifest from disk, handling missing or corrupt files gracefully. */
53
76
  export declare function loadManifest(dbPath: string): Promise<LoadedManifest>;
54
77
  /** Persist a manifest to disk with atomic write (temp file + rename, with Windows fallback). */
@@ -6,7 +6,13 @@ import { createHash } from "node:crypto";
6
6
  import fs from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  /** Current schema version for manifest files. */
9
- export const SCHEMA_VERSION = 1;
9
+ export const SCHEMA_VERSION = 3;
10
+ /**
11
+ * Oldest schema version still accepted as valid.
12
+ * Bumping this intentionally marks older manifests corrupt and forces a
13
+ * rebuild, so only raise it when a migration is not practical.
14
+ */
15
+ export const MIN_SUPPORTED_SCHEMA_VERSION = 3;
10
16
  /** Create a new empty manifest with the current schema version. */
11
17
  export function createEmptyManifest() {
12
18
  return { files: {}, schemaVersion: SCHEMA_VERSION };
@@ -23,6 +29,30 @@ export function normalizeFilePath(filePath) {
23
29
  export function computeFileHash(content) {
24
30
  return createHash("sha256").update(content).digest("hex");
25
31
  }
32
+ /**
33
+ * Compute a hash of the description configuration (provider, model, baseUrl,
34
+ * systemPrompt, image prompt, etc.) so that files are only re-described when
35
+ * the config actually changes.
36
+ *
37
+ * Returns `undefined` when neither `description` nor `imageDescription` is
38
+ * configured — callers treat this as "no descriptions needed".
39
+ */
40
+ export function computeDescriptionConfigHash(config) {
41
+ const desc = config.description;
42
+ const img = config.imageDescription;
43
+ if (!desc && !img)
44
+ return undefined;
45
+ const parts = [];
46
+ if (desc) {
47
+ parts.push(`desc:${desc.provider}|${desc.model}|${desc.baseUrl}|${desc.systemPrompt}`);
48
+ }
49
+ if (img) {
50
+ parts.push(`img:${img.provider}|${img.model}|${img.baseUrl}|${img.prompt}`);
51
+ }
52
+ if (parts.length === 0)
53
+ return undefined;
54
+ return createHash("sha256").update(parts.join("||")).digest("hex").slice(0, 16);
55
+ }
26
56
  /** Load a manifest from disk, handling missing or corrupt files gracefully. */
27
57
  export async function loadManifest(dbPath) {
28
58
  const filePath = manifestPathFor(dbPath);
@@ -32,14 +62,15 @@ export async function loadManifest(dbPath) {
32
62
  if (!parsed || typeof parsed !== "object" || !parsed.files || typeof parsed.files !== "object") {
33
63
  return { manifest: createEmptyManifest(), path: filePath, status: "corrupt" };
34
64
  }
65
+ const version = parsed.schemaVersion ?? 0;
35
66
  return {
36
67
  manifest: {
37
68
  lastIndexedAt: typeof parsed.lastIndexedAt === "number" ? parsed.lastIndexedAt : undefined,
38
- schemaVersion: parsed.schemaVersion,
69
+ schemaVersion: version,
39
70
  files: parsed.files,
40
71
  },
41
72
  path: filePath,
42
- status: parsed.schemaVersion === SCHEMA_VERSION ? "ok" : "corrupt",
73
+ status: version >= MIN_SUPPORTED_SCHEMA_VERSION ? "ok" : "corrupt",
43
74
  };
44
75
  }
45
76
  catch (error) {
@@ -41,7 +41,7 @@ function resolveForSection(provider, section, worktree) {
41
41
  // so createEmbedder can throw a clear error about the missing key
42
42
  }
43
43
  function stripJsoncComments(text) {
44
- return text.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
44
+ return text.replace(/("[^"\\]*(?:\\.[^"\\]*)*")|(\/\/[^\n]*|\/\*[\s\S]*?\*\/)/g, (_, string) => string ?? "");
45
45
  }
46
46
  function readOpenCodeProviderKey(worktree, providerId) {
47
47
  const locations = [
@@ -57,7 +57,9 @@ function readOpenCodeProviderKey(worktree, providerId) {
57
57
  if (!existsSync(loc))
58
58
  continue;
59
59
  const raw = readFileSync(loc, "utf-8");
60
- const cleaned = stripJsoncComments(raw);
60
+ // Only strip JSONC comments from .jsonc files; plain JSON may contain
61
+ // "https://" URLs that look like line-comment markers after "//".
62
+ const cleaned = loc.endsWith(".jsonc") ? stripJsoncComments(raw) : raw;
61
63
  const config = JSON.parse(cleaned);
62
64
  const providerSection = config.provider;
63
65
  if (!providerSection)
@@ -80,41 +80,43 @@ export function applyRuntimeOverrides(cfg, overrides) {
80
80
  merged.embedding.baseUrl = overrides.embedding.baseUrl;
81
81
  }
82
82
  if (overrides.description) {
83
+ const defaultDesc = DEFAULT_CONFIG.description;
83
84
  if (overrides.description.enabled !== undefined) {
84
85
  if (!merged.description)
85
- merged.description = { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
86
+ merged.description = { ...defaultDesc };
86
87
  merged.description.enabled = overrides.description.enabled;
87
88
  }
88
89
  if (overrides.description.provider !== undefined) {
89
90
  if (!merged.description)
90
- merged.description = { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
91
+ merged.description = { ...defaultDesc };
91
92
  merged.description.provider = overrides.description.provider;
92
93
  }
93
94
  if (overrides.description.model !== undefined) {
94
95
  if (!merged.description)
95
- merged.description = { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
96
+ merged.description = { ...defaultDesc };
96
97
  merged.description.model = overrides.description.model;
97
98
  }
98
99
  if (overrides.description.baseUrl !== undefined) {
99
100
  if (!merged.description)
100
- merged.description = { enabled: true, provider: "ollama", baseUrl: "http://127.0.0.1:11434/api", model: "qwen2.5:3b", systemPrompt: "" };
101
+ merged.description = { ...defaultDesc };
101
102
  merged.description.baseUrl = overrides.description.baseUrl;
102
103
  }
103
104
  }
104
105
  if (overrides.imageDescription) {
106
+ const defaultImg = DEFAULT_CONFIG.imageDescription;
105
107
  if (overrides.imageDescription.enabled !== undefined) {
106
108
  if (!merged.imageDescription)
107
- merged.imageDescription = { enabled: false, provider: "ollama", model: "llama3.2-vision", baseUrl: "http://127.0.0.1:11434/api", timeoutMs: 60000, prompt: "" };
109
+ merged.imageDescription = { ...defaultImg };
108
110
  merged.imageDescription.enabled = overrides.imageDescription.enabled;
109
111
  }
110
112
  if (overrides.imageDescription.provider !== undefined) {
111
113
  if (!merged.imageDescription)
112
- merged.imageDescription = { enabled: false, provider: "ollama", model: "llama3.2-vision", baseUrl: "http://127.0.0.1:11434/api", timeoutMs: 60000, prompt: "" };
114
+ merged.imageDescription = { ...defaultImg };
113
115
  merged.imageDescription.provider = overrides.imageDescription.provider;
114
116
  }
115
117
  if (overrides.imageDescription.model !== undefined) {
116
118
  if (!merged.imageDescription)
117
- merged.imageDescription = { enabled: false, provider: "ollama", model: "llama3.2-vision", baseUrl: "http://127.0.0.1:11434/api", timeoutMs: 60000, prompt: "" };
119
+ merged.imageDescription = { ...defaultImg };
118
120
  merged.imageDescription.model = overrides.imageDescription.model;
119
121
  }
120
122
  }
@@ -0,0 +1,23 @@
1
+ export interface SetupResult {
2
+ success: boolean;
3
+ errors: string[];
4
+ }
5
+ export declare function getRuntimeDir(): string;
6
+ export declare function getNpmGlobalRoot(): string;
7
+ export declare function getVersionFile(runtimeDir: string): string;
8
+ export declare function readVersionFile(versionFile: string): string | null;
9
+ /**
10
+ * npm-generated .cmd / .ps1 wrappers on Windows invoke `.js` files directly
11
+ * via file association. If the `.js` association points to a text editor
12
+ * (e.g. Notepad++) instead of Node.js, the CLI opens the editor instead of
13
+ * running. This function patches the wrappers to call `node` explicitly.
14
+ *
15
+ * Runs only on Windows. Safe to call on every setup — detects already-patched
16
+ * wrappers by checking for the `node` prefix.
17
+ */
18
+ export declare function patchWindowsWrappers(npmGlobalRoot: string): void;
19
+ export declare function setupRuntime(options?: {
20
+ force?: boolean;
21
+ silent?: boolean;
22
+ version?: string;
23
+ }): Promise<SetupResult>;