opencode-rag-plugin 1.19.3 → 1.19.5

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 (82) hide show
  1. package/dist/api.d.ts +1 -1
  2. package/dist/api.js +2 -0
  3. package/dist/chunker/base.js +19 -5
  4. package/dist/chunker/factory.js +27 -9
  5. package/dist/chunker/grammar.d.ts +18 -1
  6. package/dist/chunker/grammar.js +48 -10
  7. package/dist/chunker/pdf.js +30 -14
  8. package/dist/cli/commands/init-helpers.js +15 -2
  9. package/dist/cli/commands/init.js +31 -21
  10. package/dist/cli/commands/query.js +2 -0
  11. package/dist/cli/commands/quirk.js +9 -3
  12. package/dist/cli/commands/setup.js +5 -2
  13. package/dist/cli/commands/status.js +14 -5
  14. package/dist/cli/commands/ui.js +23 -9
  15. package/dist/cli/commands/update.js +4 -5
  16. package/dist/cli/format.d.ts +5 -2
  17. package/dist/cli/format.js +14 -5
  18. package/dist/content/image.js +33 -11
  19. package/dist/content/reader.js +79 -17
  20. package/dist/core/bootstrap.js +10 -3
  21. package/dist/core/config.js +31 -0
  22. package/dist/core/desc-cache.d.ts +8 -2
  23. package/dist/core/desc-cache.js +10 -3
  24. package/dist/core/doc-progress.js +5 -2
  25. package/dist/core/interfaces.d.ts +8 -0
  26. package/dist/core/interfaces.js +8 -1
  27. package/dist/core/provider-defaults.d.ts +2 -0
  28. package/dist/core/provider-defaults.js +19 -4
  29. package/dist/core/runtime-overrides.d.ts +0 -6
  30. package/dist/core/version-check.d.ts +5 -0
  31. package/dist/core/version-check.js +8 -2
  32. package/dist/describer/anthropic.d.ts +2 -2
  33. package/dist/describer/anthropic.js +19 -5
  34. package/dist/describer/describer.js +15 -2
  35. package/dist/describer/gemini.js +25 -10
  36. package/dist/embedder/factory.d.ts +5 -3
  37. package/dist/embedder/factory.js +41 -8
  38. package/dist/embedder/health.js +19 -19
  39. package/dist/embedder/http.d.ts +14 -1
  40. package/dist/embedder/http.js +60 -6
  41. package/dist/eval/session-logger.js +7 -0
  42. package/dist/eval/storage.js +8 -0
  43. package/dist/indexer/git-diff.d.ts +1 -1
  44. package/dist/indexer/git-diff.js +5 -1
  45. package/dist/indexer/pipeline.js +421 -344
  46. package/dist/indexer/stats.d.ts +2 -0
  47. package/dist/indexer/stats.js +1 -0
  48. package/dist/indexer/watch.js +8 -1
  49. package/dist/indexer/worker.js +21 -0
  50. package/dist/mcp/cli.js +4 -0
  51. package/dist/mcp/handlers.d.ts +1 -1
  52. package/dist/mcp/handlers.js +23 -6
  53. package/dist/mcp/server.js +3 -0
  54. package/dist/opencode/create-read-tool.d.ts +1 -1
  55. package/dist/opencode/create-read-tool.js +17 -5
  56. package/dist/opencode/tool-args.js +23 -1
  57. package/dist/opencode/tools.d.ts +1 -1
  58. package/dist/opencode/tools.js +3 -1
  59. package/dist/plugin.d.ts +1 -1
  60. package/dist/plugin.js +69 -152
  61. package/dist/quirks/auto-capture.js +5 -0
  62. package/dist/quirks/quirk-store.d.ts +1 -1
  63. package/dist/quirks/quirk-store.js +56 -17
  64. package/dist/retriever/context-optimizer.js +18 -4
  65. package/dist/retriever/keyword-index.d.ts +2 -0
  66. package/dist/retriever/keyword-index.js +38 -4
  67. package/dist/retriever/retriever.js +6 -1
  68. package/dist/tui.js +41 -4
  69. package/dist/vectorstore/lancedb.d.ts +25 -1
  70. package/dist/vectorstore/lancedb.js +157 -11
  71. package/dist/vectorstore/memory.js +5 -1
  72. package/dist/watcher.js +30 -4
  73. package/dist/web/api.d.ts +6 -2
  74. package/dist/web/api.js +198 -70
  75. package/dist/web/server.d.ts +2 -0
  76. package/dist/web/server.js +66 -28
  77. package/dist/web/static.d.ts +5 -2
  78. package/dist/web/static.js +9 -5
  79. package/dist/web/ui/assets/index-BDPYdtA1.js +3 -0
  80. package/dist/web/ui/index.html +1 -1
  81. package/package.json +1 -1
  82. package/dist/web/ui/assets/index-CJBvt6e0.js +0 -3
@@ -38,6 +38,8 @@ export interface IndexRunStats {
38
38
  }>;
39
39
  /** Number of files where description generation failed. */
40
40
  descriptionFailedFiles: number;
41
+ /** True when the pass was skipped because another pass holds the lock. */
42
+ skipped: boolean;
41
43
  }
42
44
  /** Summary of the current index health without running a full pass. */
43
45
  export interface IndexStatusSummary {
@@ -24,6 +24,7 @@ export function createIndexStats(totalFiles, manifestStatus) {
24
24
  extractionFailures: 0,
25
25
  extractionErrors: [],
26
26
  descriptionFailedFiles: 0,
27
+ skipped: false,
27
28
  };
28
29
  }
29
30
  //# sourceMappingURL=stats.js.map
@@ -120,9 +120,16 @@ export function createWatchIgnore(cwd, config, storePath) {
120
120
  const manifestPath = manifestPathFor(storePath);
121
121
  const dirMatcher = createExcludeMatcher(config.indexing.excludeDirs);
122
122
  const fileMatcher = createExcludeMatcher(config.indexing.excludeFiles ?? []);
123
+ // Prefix check with a trailing separator so sibling dirs like
124
+ // `<storePath>2` are NOT ignored; case-insensitive on win32 so a
125
+ // differently-cased store path cannot cause self-triggering watch loops.
126
+ const storePrefix = storePath.endsWith(path.sep) ? storePath : storePath + path.sep;
127
+ const win32 = process.platform === "win32";
123
128
  return (watchedPath) => {
124
129
  const resolved = path.resolve(watchedPath);
125
- if (resolved.startsWith(storePath))
130
+ const compareResolved = win32 ? resolved.toLowerCase() : resolved;
131
+ const compareStorePrefix = win32 ? storePrefix.toLowerCase() : storePrefix;
132
+ if (resolved === storePath || compareResolved.startsWith(compareStorePrefix))
126
133
  return true;
127
134
  if (resolved === manifestPath)
128
135
  return true;
@@ -128,6 +128,21 @@ export async function prepareFile(file, cwd, previous, config, keywordIndex, des
128
128
  },
129
129
  };
130
130
  }
131
+ // No chunks from non-empty content: file may genuinely be chunk-less.
132
+ // BUT if the content is empty and the file is unchanged (hash matches),
133
+ // this is the re-describe fast-path edge — keep the old index entry
134
+ // instead of deleting it (a delete would wipe previously-indexed chunks
135
+ // just because we lacked content to re-chunk).
136
+ if (previous && previous.hash === file.hash && file.content.trim().length === 0) {
137
+ return {
138
+ normalizedPath: file.normalizedPath, hash: file.hash, fileLabel,
139
+ isModified: false,
140
+ earlyResult: {
141
+ normalizedPath: file.normalizedPath, hash: file.hash, chunkCount: previous.chunkCount, fileLabel,
142
+ isNew: false, isModified: false, isUnchanged: true, isEmpty: false, isTooSmall: false, isRemoved: false, hadChunks: previous.chunkCount > 0,
143
+ },
144
+ };
145
+ }
131
146
  return {
132
147
  normalizedPath: file.normalizedPath, hash: file.hash, fileLabel,
133
148
  isModified: false,
@@ -138,6 +153,12 @@ export async function prepareFile(file, cwd, previous, config, keywordIndex, des
138
153
  };
139
154
  }
140
155
  logger.debug(` ${fileLabel}: ${chunks.length} chunks produced`);
156
+ // Remove stale keyword entries for a previously-indexed revision of this
157
+ // file (new UUIDs are generated for every chunk) so hybrid search never
158
+ // surfaces old content and the persisted index stops growing unboundedly.
159
+ if (previous) {
160
+ keywordIndex?.removeByFilePath(file.normalizedPath);
161
+ }
141
162
  keywordIndex?.addChunks(chunks);
142
163
  const docPrefix = config.embedding.documentPrefix ?? "";
143
164
  const relPath = path.relative(cwd, file.filePath).replace(/\\/g, "/");
package/dist/mcp/cli.js CHANGED
@@ -16,6 +16,10 @@ export async function runMcpServer(options) {
16
16
  }
17
17
  process.on("SIGINT", shutdown);
18
18
  process.on("SIGTERM", shutdown);
19
+ // If the parent MCP client disconnects, stdin closes — exit instead of
20
+ // becoming a zombie that holds the store open forever.
21
+ process.stdin.on("end", shutdown);
22
+ process.stdin.on("close", shutdown);
19
23
  await new Promise(() => { });
20
24
  }
21
25
  //# sourceMappingURL=cli.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Handler implementations for all MCP tools: semantic search, file skeleton, symbol usage lookup, and image description.
3
3
  */
4
- import type { EmbeddingProvider, VectorStore, KeywordIndex, SearchResult } from "../core/interfaces.js";
4
+ import { type EmbeddingProvider, type VectorStore, type KeywordIndex, type SearchResult } from "../core/interfaces.js";
5
5
  import type { RagConfig } from "../core/config.js";
6
6
  import { type ImageVisionProvider } from "../chunker/image.js";
7
7
  import { retrieve } from "../retriever/retriever.js";
@@ -1,10 +1,14 @@
1
+ /**
2
+ * @fileoverview Handler implementations for all MCP tools: semantic search, file skeleton, symbol usage lookup, and image description.
3
+ */
4
+ import { CODE_SEARCH_FILTER } from "../core/interfaces.js";
1
5
  import { SUPPORTED_IMAGE_EXTENSIONS } from "../chunker/image.js";
2
6
  import { retrieve } from "../retriever/retriever.js";
3
7
  import { optimizeContext, DEFAULT_CONTEXT_OPTIMIZATION } from "../retriever/context-optimizer.js";
4
8
  import { Parser } from "web-tree-sitter";
5
9
  import { initParser, loadLanguage, walkTree } from "../chunker/grammar.js";
6
10
  import { readFileSync } from "node:fs";
7
- import { resolve, isAbsolute, relative } from "node:path";
11
+ import { resolve, isAbsolute, relative, sep as pathSep } from "node:path";
8
12
  /**
9
13
  * Resolve a user-supplied file path against the worktree root, refusing to
10
14
  * escape the worktree. Absolute paths are accepted only if they already reside
@@ -19,7 +23,9 @@ export function resolveFilePath(filePath, worktree) {
19
23
  const rel = relative(root, candidate);
20
24
  if (rel === "")
21
25
  return root;
22
- if (rel.startsWith("..") || isAbsolute(rel)) {
26
+ // ".." or "../..." escapes; a sibling literally named "..foo" must NOT be
27
+ // rejected. isAbsolute(rel) covers cross-drive paths on Windows.
28
+ if (rel === ".." || rel.startsWith(`..${pathSep}`) || isAbsolute(rel)) {
23
29
  throw new Error(`Path "${filePath}" escapes worktree root "${root}" — access denied.`);
24
30
  }
25
31
  return candidate;
@@ -178,6 +184,7 @@ export async function handleSearchSemantic(params, embedder, store, cfg, keyword
178
184
  keywordWeight: cfg.retrieval.hybridSearch?.keywordWeight,
179
185
  hybridEnabled: cfg.retrieval.hybridSearch?.enabled,
180
186
  queryPrefix: cfg.embedding.queryPrefix,
187
+ filter: CODE_SEARCH_FILTER,
181
188
  };
182
189
  const rawResults = await retrieveFn_(query, embedder, store, retrieveOpts);
183
190
  if (rawResults.length === 0) {
@@ -256,7 +263,7 @@ export async function handleFindUsages(params, embedder, store, cfg, keywordInde
256
263
  const symbolName = params.symbolName.trim();
257
264
  const topK = params.topK ?? 30;
258
265
  const kwResults = keywordIndex
259
- ? keywordIndex.search(symbolName, topK)
266
+ ? keywordIndex.search(symbolName, topK, CODE_SEARCH_FILTER)
260
267
  : [];
261
268
  const count = await store.count();
262
269
  const vsResults = count > 0
@@ -265,6 +272,7 @@ export async function handleFindUsages(params, embedder, store, cfg, keywordInde
265
272
  minScore: 0,
266
273
  keywordIndex: undefined,
267
274
  queryPrefix: cfg.embedding.queryPrefix,
275
+ filter: CODE_SEARCH_FILTER,
268
276
  })
269
277
  : [];
270
278
  const seen = new Set();
@@ -302,7 +310,10 @@ export async function handleFindUsages(params, embedder, store, cfg, keywordInde
302
310
  }
303
311
  for (let i = 0; i < lines.length; i++) {
304
312
  const line = lines[i];
305
- const lineNum = i + 1;
313
+ // tree-sitter line numbers are absolute file lines (startLine + offset),
314
+ // NOT chunk-relative — chunk-relative numbers were wrong for every
315
+ // chunk not starting at line 1 and broke the definition skip.
316
+ const lineNum = m.startLine + i;
306
317
  if (definitionLine !== undefined && lineNum === definitionLine)
307
318
  continue;
308
319
  const symbolPattern = new RegExp(`\\b${escapeRegex(symbolName)}\\b`);
@@ -317,7 +328,12 @@ export async function handleFindUsages(params, embedder, store, cfg, keywordInde
317
328
  fileGroups.set(m.filePath, group);
318
329
  }
319
330
  if (!group.usages.some((g) => g.line === lineNum)) {
320
- group.usages.push({ line: lineNum, content: line.trimEnd(), context: contextLines });
331
+ group.usages.push({
332
+ line: lineNum,
333
+ content: line.trimEnd(),
334
+ context: contextLines,
335
+ matchedIndex: i - ctxStart,
336
+ });
321
337
  }
322
338
  }
323
339
  }
@@ -340,7 +356,8 @@ export async function handleFindUsages(params, embedder, store, cfg, keywordInde
340
356
  formattedLines.push("|------|------|");
341
357
  for (const usage of group.usages) {
342
358
  const ctx = usage.context;
343
- const codeLine = ctx[Math.min(2, ctx.length - 1)] ?? usage.content;
359
+ // Show the actual matched line (not an arbitrary middle context line)
360
+ const codeLine = ctx[usage.matchedIndex] ?? usage.content;
344
361
  const escaped = codeLine.length > 100 ? codeLine.slice(0, 97) + "..." : codeLine;
345
362
  formattedLines.push(`| ${usage.line} | \`${escaped}\` |`);
346
363
  allMatches.push({ ...usage, filePath, language: group.language });
@@ -97,6 +97,9 @@ export async function createMcpServer(options) {
97
97
  await server.close();
98
98
  await ctx.store.close();
99
99
  ctx.keywordIndex.close();
100
+ // Release keep-alive sockets held by the embedder/description providers
101
+ const { destroyAllPooledConnections } = await import("../embedder/http.js");
102
+ destroyAllPooledConnections();
100
103
  },
101
104
  };
102
105
  }
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Creates the RAG-backed read tool that returns full file contents with supplementary semantic context.
3
3
  */
4
4
  import { tool } from "@opencode-ai/plugin/tool";
5
- import type { EmbeddingProvider, KeywordIndex, VectorStore, SearchResult } from "../core/interfaces.js";
5
+ import { type EmbeddingProvider, type KeywordIndex, type VectorStore, type SearchResult } from "../core/interfaces.js";
6
6
  import type { RagConfig } from "../core/config.js";
7
7
  export interface RagReadToolOptions {
8
8
  /** Workspace root directory. */
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import fs from "node:fs/promises";
5
5
  import { tool } from "@opencode-ai/plugin/tool";
6
+ import { CODE_SEARCH_FILTER } from "../core/interfaces.js";
6
7
  import { retrieve } from "../retriever/retriever.js";
7
8
  import { normalizeReadArgs, resolveWorkspacePath } from "./tool-args.js";
8
9
  import { buildReadQuery } from "./read-query.js";
@@ -58,19 +59,23 @@ export function createRagReadTool(options) {
58
59
  let rawResults;
59
60
  if (sessionID && sessionRetrievalCache) {
60
61
  const cached = sessionRetrievalCache.get(sessionID);
61
- if (cached && cached.messageText === messageText) {
62
+ // The cache key must include the FILE and line range: without
63
+ // them, the first read of a message reused its retrieval results
64
+ // for every later read of a DIFFERENT file in the same message.
65
+ const cacheKey = `${messageText}::${resolvedPath}::${normalized.startLine ?? ""}-${normalized.endLine ?? ""}`;
66
+ if (cached && cached.messageText === cacheKey) {
62
67
  rawResults = cached.rawResults;
63
68
  }
64
69
  else {
65
70
  const retrievalQuery = buildSessionQuery(messageText, resolvedPath, normalized);
66
- rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, hybridEnabled: config.retrieval.hybridSearch?.enabled, queryPrefix: config.embedding.queryPrefix });
71
+ rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, hybridEnabled: config.retrieval.hybridSearch?.enabled, queryPrefix: config.embedding.queryPrefix, filter: CODE_SEARCH_FILTER });
67
72
  const maxSize = options.maxSessionCacheSize ?? 50;
68
73
  if (!sessionRetrievalCache.has(sessionID) && sessionRetrievalCache.size >= maxSize) {
69
74
  const oldest = sessionRetrievalCache.keys().next().value;
70
75
  if (oldest !== undefined)
71
76
  sessionRetrievalCache.delete(oldest);
72
77
  }
73
- sessionRetrievalCache.set(sessionID, { messageText, rawResults });
78
+ sessionRetrievalCache.set(sessionID, { messageText: cacheKey, rawResults });
74
79
  }
75
80
  }
76
81
  else {
@@ -80,7 +85,7 @@ export function createRagReadTool(options) {
80
85
  startLine: normalized.startLine,
81
86
  endLine: normalized.endLine,
82
87
  });
83
- rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, hybridEnabled: config.retrieval.hybridSearch?.enabled, queryPrefix: config.embedding.queryPrefix });
88
+ rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, hybridEnabled: config.retrieval.hybridSearch?.enabled, queryPrefix: config.embedding.queryPrefix, filter: CODE_SEARCH_FILTER });
84
89
  }
85
90
  // Collect related files from raw results (before filtering)
86
91
  relatedFiles = collectRelatedFiles(rawResults, resolvedPath, readRelatedFilesMax);
@@ -120,9 +125,16 @@ export function createRagReadTool(options) {
120
125
  }
121
126
  catch (err) {
122
127
  const message = err instanceof Error ? err.message : String(err);
128
+ const code = err?.code;
129
+ // Distinguish path problems (ENOENT / outside workspace) from RAG
130
+ // retrieval failures — "OpenCodeRAG retrieval failed" was misleading
131
+ // for a simple missing file.
132
+ const isPathProblem = code === "ENOENT" || code === "EACCES" || /ENOENT|outside the workspace|does not exist/i.test(message);
123
133
  return {
124
134
  title: "Read",
125
- output: retrievalErrorMessage(message),
135
+ output: isPathProblem
136
+ ? `Could not read file: ${message}`
137
+ : retrievalErrorMessage(message),
126
138
  metadata: {
127
139
  tool: "read",
128
140
  filePath: resolvedPath,
@@ -2,6 +2,7 @@
2
2
  * @fileoverview Normalizes and validates read tool arguments, and resolves file paths relative to the workspace root.
3
3
  */
4
4
  import path from "node:path";
5
+ import { realpathSync } from "node:fs";
5
6
  /**
6
7
  * Normalize raw read tool arguments into a consistent internal shape.
7
8
  *
@@ -58,10 +59,31 @@ export function resolveWorkspacePath(worktree, inputPath) {
58
59
  : path.resolve(worktree, inputPath);
59
60
  const normalizedWorktree = path.resolve(worktree);
60
61
  const normalizedResolved = path.resolve(resolved);
61
- if (!normalizedResolved.startsWith(normalizedWorktree + path.sep) &&
62
+ // Case-insensitive on win32: an absolute path with different casing must
63
+ // not be falsely rejected as "outside the workspace".
64
+ const cmp = (p) => (process.platform === "win32" ? p.toLowerCase() : p);
65
+ if (!cmp(normalizedResolved).startsWith(cmp(normalizedWorktree) + path.sep) &&
62
66
  normalizedResolved !== normalizedWorktree) {
63
67
  throw new Error(`read path "${inputPath}" resolves outside the workspace "${normalizedWorktree}"`);
64
68
  }
69
+ // Resolve symlinks/junctions: a link INSIDE the workspace pointing outside
70
+ // must not let the agent read arbitrary files (node_modules junctions are
71
+ // common on Windows). realpath failure (e.g. missing file) falls back to
72
+ // the lexical check above — the read itself will fail with ENOENT.
73
+ try {
74
+ const realResolved = realpathSync(normalizedResolved);
75
+ const realWorktree = realpathSync(normalizedWorktree);
76
+ if (!cmp(realResolved).startsWith(cmp(realWorktree) + path.sep) &&
77
+ realResolved !== realWorktree) {
78
+ throw new Error(`read path "${inputPath}" resolves outside the workspace "${normalizedWorktree}" (via symlink "${realResolved}")`);
79
+ }
80
+ }
81
+ catch (err) {
82
+ if (err instanceof Error && /outside the workspace/.test(err.message)) {
83
+ throw err;
84
+ }
85
+ // otherwise: realpath unavailable — keep the lexical result
86
+ }
65
87
  return toForwardSlash(normalizedResolved);
66
88
  }
67
89
  /**
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Factory functions for OpenCode autonomous-agent tools (file skeleton, find usages, describe image).
3
3
  */
4
4
  import type { ToolDefinition } from "@opencode-ai/plugin";
5
- import type { EmbeddingProvider, VectorStore, KeywordIndex } from "../core/interfaces.js";
5
+ import { type EmbeddingProvider, type VectorStore, type KeywordIndex } from "../core/interfaces.js";
6
6
  import type { RagConfig } from "../core/config.js";
7
7
  import { type ImageVisionProvider } from "../chunker/image.js";
8
8
  import { retrieve } from "../retriever/retriever.js";
@@ -13,6 +13,7 @@
13
13
  * These complement the general-purpose search_semantic tool.
14
14
  */
15
15
  import { tool } from "@opencode-ai/plugin/tool";
16
+ import { CODE_SEARCH_FILTER } from "../core/interfaces.js";
16
17
  import { SUPPORTED_IMAGE_EXTENSIONS, createImageVisionProvider, getMimeType } from "../chunker/image.js";
17
18
  import { resizeImage } from "../content/image.js";
18
19
  import { retrieve } from "../retriever/retriever.js";
@@ -362,7 +363,7 @@ export function createFindUsagesTool(options) {
362
363
  async function searchViaKeywordIndex(symbol, topK) {
363
364
  if (!keywordIndex)
364
365
  return [];
365
- return keywordIndex.search(symbol, topK);
366
+ return keywordIndex.search(symbol, topK, CODE_SEARCH_FILTER);
366
367
  }
367
368
  async function searchViaVectorStore(symbol, topK) {
368
369
  const count = await store.count();
@@ -373,6 +374,7 @@ export function createFindUsagesTool(options) {
373
374
  minScore: 0,
374
375
  keywordIndex: undefined,
375
376
  queryPrefix: cfg.embedding.queryPrefix,
377
+ filter: CODE_SEARCH_FILTER,
376
378
  });
377
379
  }
378
380
  function escapeRegex(s) {
package/dist/plugin.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * automatic context injection and documentation mode.
5
5
  */
6
6
  import type { Plugin, Hooks } from "@opencode-ai/plugin";
7
- import type { EmbeddingProvider, DescriptionProvider, KeywordIndex, VectorStore } from "./core/interfaces.js";
7
+ import { type EmbeddingProvider, type DescriptionProvider, type KeywordIndex, type VectorStore } from "./core/interfaces.js";
8
8
  import { type RagConfig } from "./core/config.js";
9
9
  import { createEmbedder } from "./embedder/factory.js";
10
10
  import { retrieve } from "./retriever/retriever.js";