opencode-rag-plugin 1.15.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReadMe.md +6 -7
- package/dist/api.js +44 -24
- package/dist/chunker/base.d.ts +34 -3
- package/dist/chunker/base.js +70 -21
- package/dist/chunker/factory.d.ts +4 -1
- package/dist/chunker/factory.js +12 -1
- package/dist/chunker/grammar.js +3 -0
- package/dist/chunker/image.js +8 -8
- package/dist/chunker/pdf.js +11 -13
- package/dist/chunker/xml.d.ts +2 -0
- package/dist/chunker/xml.js +2 -0
- package/dist/cli/commands/index-command.js +0 -4
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/init-helpers.d.ts +12 -0
- package/dist/cli/commands/init-helpers.js +84 -2
- package/dist/cli/commands/init.js +20 -2
- package/dist/cli/commands/query.js +1 -0
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.js +113 -0
- package/dist/cli/commands/status.js +64 -2
- package/dist/cli/index.js +2 -2
- package/dist/content/image.js +24 -3
- package/dist/content/reader.d.ts +6 -1
- package/dist/content/reader.js +49 -4
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.js +34 -4
- package/dist/core/desc-cache.d.ts +31 -0
- package/dist/core/desc-cache.js +124 -0
- package/dist/core/interfaces.d.ts +58 -3
- package/dist/core/manifest.d.ts +24 -1
- package/dist/core/manifest.js +34 -3
- package/dist/core/resolve-api-key.js +4 -2
- package/dist/core/runtime-overrides.js +9 -7
- package/dist/core/setup-runtime.d.ts +23 -0
- package/dist/core/setup-runtime.js +183 -0
- package/dist/core/version-check.d.ts +9 -0
- package/dist/core/version-check.js +49 -0
- package/dist/describer/anthropic.d.ts +2 -2
- package/dist/describer/anthropic.js +5 -7
- package/dist/describer/describer.d.ts +2 -2
- package/dist/describer/describer.js +6 -8
- package/dist/describer/gemini.d.ts +2 -2
- package/dist/describer/gemini.js +5 -7
- package/dist/embedder/factory.d.ts +3 -1
- package/dist/embedder/factory.js +7 -1
- package/dist/embedder/health.js +4 -0
- package/dist/embedder/http.d.ts +1 -1
- package/dist/embedder/http.js +74 -43
- package/dist/eval/compare-merge.d.ts +10 -0
- package/dist/eval/compare-merge.js +537 -0
- package/dist/eval/compare-rankings.d.ts +10 -0
- package/dist/eval/compare-rankings.js +245 -0
- package/dist/eval/dump-descriptions.d.ts +7 -0
- package/dist/eval/dump-descriptions.js +58 -0
- package/dist/eval/fast-index.d.ts +8 -0
- package/dist/eval/fast-index.js +283 -0
- package/dist/eval/run-branch-compare.d.ts +7 -0
- package/dist/eval/run-branch-compare.js +220 -0
- package/dist/eval/run-token-test.js +1 -0
- package/dist/eval/test-kw.d.ts +1 -0
- package/dist/eval/test-kw.js +22 -0
- package/dist/eval/update-descriptions.d.ts +7 -0
- package/dist/eval/update-descriptions.js +84 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/indexer/embed-stage.js +2 -1
- package/dist/indexer/git-diff.js +21 -9
- package/dist/indexer/pipeline.d.ts +6 -0
- package/dist/indexer/pipeline.js +290 -37
- package/dist/indexer/watch.js +1 -3
- package/dist/indexer/worker.d.ts +15 -2
- package/dist/indexer/worker.js +25 -12
- package/dist/mcp/handlers.d.ts +9 -0
- package/dist/mcp/handlers.js +23 -6
- package/dist/mcp/server.js +2 -0
- package/dist/opencode/create-read-tool.d.ts +2 -0
- package/dist/opencode/create-read-tool.js +8 -2
- package/dist/opencode/read-fallback.d.ts +1 -5
- package/dist/opencode/read-fallback.js +1 -18
- package/dist/opencode/read-format.js +5 -3
- package/dist/opencode/tools.js +5 -7
- package/dist/plugin.js +196 -81
- package/dist/retriever/keyword-index.d.ts +3 -2
- package/dist/retriever/keyword-index.js +25 -1
- package/dist/retriever/retriever.d.ts +4 -1
- package/dist/retriever/retriever.js +34 -56
- package/dist/vectorstore/lancedb.d.ts +35 -4
- package/dist/vectorstore/lancedb.js +146 -23
- package/dist/vectorstore/memory.d.ts +6 -1
- package/dist/vectorstore/memory.js +58 -0
- package/dist/watcher.js +3 -0
- package/dist/web/api.js +10 -2
- package/dist/web/server.js +18 -3
- package/package.json +8 -9
- package/scripts/postinstall-setup.js +82 -0
- package/dist/api.js.map +0 -1
- package/dist/chunker/base.js.map +0 -1
- package/dist/chunker/bash.js.map +0 -1
- package/dist/chunker/c.js.map +0 -1
- package/dist/chunker/cpp.js.map +0 -1
- package/dist/chunker/csharp.js.map +0 -1
- package/dist/chunker/css.js.map +0 -1
- package/dist/chunker/doc.js.map +0 -1
- package/dist/chunker/dockerfile.js.map +0 -1
- package/dist/chunker/docx.js.map +0 -1
- package/dist/chunker/excel.js.map +0 -1
- package/dist/chunker/factory.js.map +0 -1
- package/dist/chunker/fallback.js.map +0 -1
- package/dist/chunker/go.js.map +0 -1
- package/dist/chunker/grammar.js.map +0 -1
- package/dist/chunker/html.js.map +0 -1
- package/dist/chunker/image.js.map +0 -1
- package/dist/chunker/ini.js.map +0 -1
- package/dist/chunker/java.js.map +0 -1
- package/dist/chunker/javascript.js.map +0 -1
- package/dist/chunker/json.js.map +0 -1
- package/dist/chunker/kotlin.js.map +0 -1
- package/dist/chunker/loader.js.map +0 -1
- package/dist/chunker/markdown.js.map +0 -1
- package/dist/chunker/pdf.js.map +0 -1
- package/dist/chunker/php.js.map +0 -1
- package/dist/chunker/powershell.js.map +0 -1
- package/dist/chunker/python.js.map +0 -1
- package/dist/chunker/razor.js.map +0 -1
- package/dist/chunker/ruby.js.map +0 -1
- package/dist/chunker/rust.js.map +0 -1
- package/dist/chunker/sln.js.map +0 -1
- package/dist/chunker/sql.js.map +0 -1
- package/dist/chunker/ssl.js.map +0 -1
- package/dist/chunker/swift.js.map +0 -1
- package/dist/chunker/tex.js.map +0 -1
- package/dist/chunker/toml.js.map +0 -1
- package/dist/chunker/typescript.js.map +0 -1
- package/dist/chunker/uuid.js.map +0 -1
- package/dist/chunker/xml.js.map +0 -1
- package/dist/chunker/yaml.js.map +0 -1
- package/dist/cli/commands/clear.js.map +0 -1
- package/dist/cli/commands/describe-image.js.map +0 -1
- package/dist/cli/commands/dump.js.map +0 -1
- package/dist/cli/commands/eval.js.map +0 -1
- package/dist/cli/commands/index-command.js.map +0 -1
- package/dist/cli/commands/index.js.map +0 -1
- package/dist/cli/commands/init-helpers.js.map +0 -1
- package/dist/cli/commands/init.js.map +0 -1
- package/dist/cli/commands/list.js.map +0 -1
- package/dist/cli/commands/mcp.js.map +0 -1
- package/dist/cli/commands/query.js.map +0 -1
- package/dist/cli/commands/show.js.map +0 -1
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/ui.js.map +0 -1
- package/dist/cli/commands/update.d.ts +0 -17
- package/dist/cli/commands/update.js +0 -79
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/cli/format.js.map +0 -1
- package/dist/cli/helpers.js.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/progress.d.ts +0 -42
- package/dist/cli/progress.js +0 -137
- package/dist/cli/progress.js.map +0 -1
- package/dist/cli/types.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/content/doc.js.map +0 -1
- package/dist/content/docx.js.map +0 -1
- package/dist/content/excel.js.map +0 -1
- package/dist/content/image.js.map +0 -1
- package/dist/content/pdf.js.map +0 -1
- package/dist/content/reader.js.map +0 -1
- package/dist/content/types.js.map +0 -1
- package/dist/core/bootstrap.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/doc-progress.js.map +0 -1
- package/dist/core/fileLogger.js.map +0 -1
- package/dist/core/interfaces.js.map +0 -1
- package/dist/core/manifest.js.map +0 -1
- package/dist/core/provider-defaults.js.map +0 -1
- package/dist/core/rag-injection-flag.js.map +0 -1
- package/dist/core/resolve-api-key.js.map +0 -1
- package/dist/core/runtime-overrides.js.map +0 -1
- package/dist/describer/anthropic.js.map +0 -1
- package/dist/describer/describer.js.map +0 -1
- package/dist/describer/factory.js.map +0 -1
- package/dist/describer/gemini.js.map +0 -1
- package/dist/describer/shared.js.map +0 -1
- package/dist/embedder/cohere.js.map +0 -1
- package/dist/embedder/factory.js.map +0 -1
- package/dist/embedder/health.js.map +0 -1
- package/dist/embedder/http.js.map +0 -1
- package/dist/embedder/ollama.js.map +0 -1
- package/dist/embedder/openai.js.map +0 -1
- package/dist/eval/index.js.map +0 -1
- package/dist/eval/run-token-test.js.map +0 -1
- package/dist/eval/session-logger.js.map +0 -1
- package/dist/eval/storage.js.map +0 -1
- package/dist/eval/token-analysis.js.map +0 -1
- package/dist/eval/token-counter.js.map +0 -1
- package/dist/eval/types.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/indexer/description-stage.js.map +0 -1
- package/dist/indexer/embed-stage.js.map +0 -1
- package/dist/indexer/git-diff.js.map +0 -1
- package/dist/indexer/metadata.js.map +0 -1
- package/dist/indexer/pipeline.js.map +0 -1
- package/dist/indexer/stats.js.map +0 -1
- package/dist/indexer/watch.js.map +0 -1
- package/dist/indexer/worker.js.map +0 -1
- package/dist/indexer.js.map +0 -1
- package/dist/mcp/cli.js.map +0 -1
- package/dist/mcp/handlers.js.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/opencode/create-read-tool.js.map +0 -1
- package/dist/opencode/read-fallback.js.map +0 -1
- package/dist/opencode/read-format.js.map +0 -1
- package/dist/opencode/read-query.js.map +0 -1
- package/dist/opencode/tool-args.js.map +0 -1
- package/dist/opencode/tools.js.map +0 -1
- package/dist/plugin-entry.js.map +0 -1
- package/dist/plugin.js.map +0 -1
- package/dist/retriever/context-optimizer.js.map +0 -1
- package/dist/retriever/keyword-index.js.map +0 -1
- package/dist/retriever/retriever.js.map +0 -1
- package/dist/tui.js.map +0 -1
- package/dist/updater.d.ts +0 -45
- package/dist/updater.js +0 -175
- package/dist/updater.js.map +0 -1
- package/dist/vectorstore/factory.js.map +0 -1
- package/dist/vectorstore/lancedb.js.map +0 -1
- package/dist/vectorstore/memory.js.map +0 -1
- package/dist/watcher.js.map +0 -1
- package/dist/web/api.js.map +0 -1
- package/dist/web/server.js.map +0 -1
- package/dist/web/static.js.map +0 -1
package/dist/mcp/handlers.js
CHANGED
|
@@ -4,7 +4,26 @@ import { optimizeContext, DEFAULT_CONTEXT_OPTIMIZATION } from "../retriever/cont
|
|
|
4
4
|
import { Parser } from "web-tree-sitter";
|
|
5
5
|
import { initParser, loadLanguage, walkTree } from "../chunker/grammar.js";
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
7
|
-
import { resolve, isAbsolute } from "node:path";
|
|
7
|
+
import { resolve, isAbsolute, relative } from "node:path";
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a user-supplied file path against the worktree root, refusing to
|
|
10
|
+
* escape the worktree. Absolute paths are accepted only if they already reside
|
|
11
|
+
* under the worktree; relative paths are joined to the worktree and any `..`
|
|
12
|
+
* segments that would escape are rejected.
|
|
13
|
+
*
|
|
14
|
+
* @throws {Error} if the resolved path lies outside `worktree`.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveFilePath(filePath, worktree) {
|
|
17
|
+
const root = resolve(worktree);
|
|
18
|
+
const candidate = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);
|
|
19
|
+
const rel = relative(root, candidate);
|
|
20
|
+
if (rel === "")
|
|
21
|
+
return root;
|
|
22
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
23
|
+
throw new Error(`Path "${filePath}" escapes worktree root "${root}" — access denied.`);
|
|
24
|
+
}
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
8
27
|
const SKELETON_CONFIGS = {
|
|
9
28
|
".ts": { grammarName: "typescript", nodeTypes: ["function_declaration", "method_definition", "class_declaration", "interface_declaration", "type_alias_declaration", "enum_declaration"] },
|
|
10
29
|
".tsx": { grammarName: "typescript", nodeTypes: ["function_declaration", "method_definition", "class_declaration", "interface_declaration", "type_alias_declaration", "enum_declaration", "arrow_function"] },
|
|
@@ -45,11 +64,6 @@ function getExtension(filePath) {
|
|
|
45
64
|
const dot = filePath.lastIndexOf(".");
|
|
46
65
|
return dot >= 0 ? filePath.slice(dot).toLowerCase() : "";
|
|
47
66
|
}
|
|
48
|
-
function resolveFilePath(filePath, worktree) {
|
|
49
|
-
if (isAbsolute(filePath))
|
|
50
|
-
return filePath;
|
|
51
|
-
return resolve(worktree, filePath);
|
|
52
|
-
}
|
|
53
67
|
async function extractSkeleton(content, ext) {
|
|
54
68
|
const config = SKELETON_CONFIGS[ext];
|
|
55
69
|
if (!config) {
|
|
@@ -86,6 +100,8 @@ async function extractSkeleton(content, ext) {
|
|
|
86
100
|
return [];
|
|
87
101
|
const nodeTypes = new Set(config.nodeTypes);
|
|
88
102
|
const astNodes = walkTree(tree.rootNode, nodeTypes, content, 15);
|
|
103
|
+
tree.delete();
|
|
104
|
+
parser.delete();
|
|
89
105
|
return astNodes.map((node) => ({
|
|
90
106
|
type: node.type,
|
|
91
107
|
name: extractNodeName(node.text, node.type),
|
|
@@ -160,6 +176,7 @@ export async function handleSearchSemantic(params, embedder, store, cfg, keyword
|
|
|
160
176
|
minScore: cfg.retrieval.minScore,
|
|
161
177
|
keywordIndex,
|
|
162
178
|
keywordWeight: cfg.retrieval.hybridSearch?.keywordWeight,
|
|
179
|
+
hybridEnabled: cfg.retrieval.hybridSearch?.enabled,
|
|
163
180
|
queryPrefix: cfg.embedding.queryPrefix,
|
|
164
181
|
};
|
|
165
182
|
const rawResults = await retrieveFn_(query, embedder, store, retrieveOpts);
|
package/dist/mcp/server.js
CHANGED
|
@@ -24,6 +24,8 @@ export interface RagReadToolOptions {
|
|
|
24
24
|
}>;
|
|
25
25
|
/** Optional keyword index for hybrid search. */
|
|
26
26
|
keywordIndex?: KeywordIndex;
|
|
27
|
+
/** Maximum number of sessions to retain in the retrieval cache. */
|
|
28
|
+
maxSessionCacheSize?: number;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Create the RAG-backed read tool for OpenCode plugin registration.
|
|
@@ -63,7 +63,13 @@ export function createRagReadTool(options) {
|
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
65
|
const retrievalQuery = buildSessionQuery(messageText, resolvedPath, normalized);
|
|
66
|
-
rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, queryPrefix: config.embedding.queryPrefix });
|
|
66
|
+
rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, hybridEnabled: config.retrieval.hybridSearch?.enabled, queryPrefix: config.embedding.queryPrefix });
|
|
67
|
+
const maxSize = options.maxSessionCacheSize ?? 50;
|
|
68
|
+
if (!sessionRetrievalCache.has(sessionID) && sessionRetrievalCache.size >= maxSize) {
|
|
69
|
+
const oldest = sessionRetrievalCache.keys().next().value;
|
|
70
|
+
if (oldest !== undefined)
|
|
71
|
+
sessionRetrievalCache.delete(oldest);
|
|
72
|
+
}
|
|
67
73
|
sessionRetrievalCache.set(sessionID, { messageText, rawResults });
|
|
68
74
|
}
|
|
69
75
|
}
|
|
@@ -74,7 +80,7 @@ export function createRagReadTool(options) {
|
|
|
74
80
|
startLine: normalized.startLine,
|
|
75
81
|
endLine: normalized.endLine,
|
|
76
82
|
});
|
|
77
|
-
rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, queryPrefix: config.embedding.queryPrefix });
|
|
83
|
+
rawResults = await retrieve(retrievalQuery, embedder, store, { topK: retrievalTopK, keywordIndex, hybridEnabled: config.retrieval.hybridSearch?.enabled, queryPrefix: config.embedding.queryPrefix });
|
|
78
84
|
}
|
|
79
85
|
// Collect related files from raw results (before filtering)
|
|
80
86
|
relatedFiles = collectRelatedFiles(rawResults, resolvedPath, readRelatedFilesMax);
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Provides fallback error messages and no-results behavior dispatch for the read tool.
|
|
3
3
|
*/
|
|
4
|
-
import type { ReadNoResultsBehavior } from "../core/config.js";
|
|
5
4
|
/**
|
|
6
5
|
* Error message wrapper for retrieval failures.
|
|
7
6
|
*/
|
|
8
7
|
export declare function retrievalErrorMessage(shortError: string): string;
|
|
9
|
-
/**
|
|
10
|
-
* Dispatch to the correct fallback message based on behavior config.
|
|
11
|
-
*/
|
|
12
|
-
export declare function getNoResultsMessage(behavior: ReadNoResultsBehavior, filePath?: string): string;
|
|
8
|
+
/** No results fallback messages are handled inline in create-read-tool.ts. */
|
|
@@ -12,22 +12,5 @@ export function retrievalErrorMessage(shortError) {
|
|
|
12
12
|
shortError,
|
|
13
13
|
].join("\n");
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Dispatch to the correct fallback message based on behavior config.
|
|
17
|
-
*/
|
|
18
|
-
export function getNoResultsMessage(behavior, filePath) {
|
|
19
|
-
switch (behavior) {
|
|
20
|
-
case "error":
|
|
21
|
-
throw new Error("OpenCodeRAG read: no indexed chunks found." +
|
|
22
|
-
(filePath ? ` File: ${filePath}` : ""));
|
|
23
|
-
case "empty":
|
|
24
|
-
return "No indexed chunks found.";
|
|
25
|
-
case "hint":
|
|
26
|
-
default:
|
|
27
|
-
if (filePath) {
|
|
28
|
-
return `No indexed chunks found for ${filePath}.`;
|
|
29
|
-
}
|
|
30
|
-
return "No relevant chunks found.";
|
|
31
|
-
}
|
|
32
|
-
}
|
|
15
|
+
/** No results fallback messages are handled inline in create-read-tool.ts. */
|
|
33
16
|
//# sourceMappingURL=read-fallback.js.map
|
|
@@ -124,9 +124,11 @@ function formatChunk(index, result) {
|
|
|
124
124
|
lines.push(`Score: ${score.toFixed(4)}`);
|
|
125
125
|
lines.push("");
|
|
126
126
|
lines.push("```" + language);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
if (chunk.content.endsWith("\n")) {
|
|
128
|
+
lines.push(chunk.content.slice(0, -1));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
lines.push(chunk.content);
|
|
130
132
|
}
|
|
131
133
|
lines.push("```");
|
|
132
134
|
return lines.join("\n");
|
package/dist/opencode/tools.js
CHANGED
|
@@ -19,7 +19,7 @@ import { retrieve } from "../retriever/retriever.js";
|
|
|
19
19
|
import { Parser } from "web-tree-sitter";
|
|
20
20
|
import { initParser, loadLanguage, walkTree } from "../chunker/grammar.js";
|
|
21
21
|
import { readFileSync } from "node:fs";
|
|
22
|
-
import {
|
|
22
|
+
import { resolveWorkspacePath } from "./tool-args.js";
|
|
23
23
|
const SKELETON_CONFIGS = {
|
|
24
24
|
".ts": { grammarName: "typescript", nodeTypes: ["function_declaration", "method_definition", "class_declaration", "interface_declaration", "type_alias_declaration", "enum_declaration"] },
|
|
25
25
|
".tsx": { grammarName: "typescript", nodeTypes: ["function_declaration", "method_definition", "class_declaration", "interface_declaration", "type_alias_declaration", "enum_declaration", "arrow_function"] },
|
|
@@ -68,9 +68,7 @@ function getExtension(filePath) {
|
|
|
68
68
|
* Resolve a file path relative to the workspace root.
|
|
69
69
|
*/
|
|
70
70
|
function resolveFilePath(filePath, worktree) {
|
|
71
|
-
|
|
72
|
-
return filePath;
|
|
73
|
-
return resolve(worktree, filePath);
|
|
71
|
+
return resolveWorkspacePath(worktree, filePath);
|
|
74
72
|
}
|
|
75
73
|
/**
|
|
76
74
|
* Extract structural outline from source code using tree-sitter.
|
|
@@ -116,6 +114,8 @@ async function extractSkeleton(content, ext) {
|
|
|
116
114
|
return [];
|
|
117
115
|
const nodeTypes = new Set(config.nodeTypes);
|
|
118
116
|
const astNodes = walkTree(tree.rootNode, nodeTypes, content, 15);
|
|
117
|
+
tree.delete();
|
|
118
|
+
parser.delete();
|
|
119
119
|
return astNodes.map((node) => ({
|
|
120
120
|
type: node.type,
|
|
121
121
|
name: extractNodeName(node.text, node.type),
|
|
@@ -262,9 +262,7 @@ export function createDescribeImageTool(options) {
|
|
|
262
262
|
try {
|
|
263
263
|
const { existsSync, readFileSync } = await import("node:fs");
|
|
264
264
|
const path = await import("node:path");
|
|
265
|
-
const resolvedPath =
|
|
266
|
-
? args.filePath
|
|
267
|
-
: resolve(worktree, args.filePath);
|
|
265
|
+
const resolvedPath = resolveWorkspacePath(worktree, args.filePath);
|
|
268
266
|
if (!existsSync(resolvedPath)) {
|
|
269
267
|
return {
|
|
270
268
|
title: "Describe image",
|
package/dist/plugin.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* automatic context injection and documentation mode.
|
|
5
5
|
*/
|
|
6
6
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
7
|
-
import { loadConfig, findConfigFile, DEFAULT_CONFIG, resolveLogConfig } from "./core/config.js";
|
|
7
|
+
import { loadConfig, findConfigFile, DEFAULT_CONFIG, resolveLogConfig, persistProbedDimension } from "./core/config.js";
|
|
8
8
|
import { createEmbedder } from "./embedder/factory.js";
|
|
9
9
|
import { createDescriptionProvider } from "./describer/factory.js";
|
|
10
10
|
import { createVectorStore } from "./vectorstore/factory.js";
|
|
@@ -18,17 +18,19 @@ import { createRagReadTool } from "./opencode/create-read-tool.js";
|
|
|
18
18
|
import { createFileSkeletonTool, createFindUsagesTool, createDescribeImageTool, } from "./opencode/tools.js";
|
|
19
19
|
import { resolveApiKey } from "./core/resolve-api-key.js";
|
|
20
20
|
import { consumePendingRagInjection } from "./core/rag-injection-flag.js";
|
|
21
|
-
import { uuid } from "./chunker/uuid.js";
|
|
22
21
|
import { loadDocProgress, markSubdirectoryDocumented } from "./core/doc-progress.js";
|
|
23
22
|
import { loadManifest } from "./core/manifest.js";
|
|
24
23
|
import { createSessionLogger } from "./eval/session-logger.js";
|
|
25
24
|
import { countTokens } from "./eval/token-counter.js";
|
|
26
|
-
import { checkForUpdate } from "./
|
|
25
|
+
import { checkForUpdate } from "./core/version-check.js";
|
|
26
|
+
import { destroyAllPooledConnections } from "./embedder/http.js";
|
|
27
27
|
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
28
28
|
import path from "node:path";
|
|
29
29
|
import { fileURLToPath } from "node:url";
|
|
30
30
|
import { spawn, execSync } from "node:child_process";
|
|
31
31
|
import { tmpdir } from "node:os";
|
|
32
|
+
/** Maximum entries per session map before oldest entries are evicted. */
|
|
33
|
+
const MAX_SESSION_MAP_SIZE = 50;
|
|
32
34
|
/** Cache of loaded RAG configurations keyed by workspace directory. */
|
|
33
35
|
const configCache = new Map();
|
|
34
36
|
/** Active background indexer instances keyed by workspace directory. */
|
|
@@ -37,6 +39,52 @@ const backgroundIndexers = new Map();
|
|
|
37
39
|
const mcpServers = new Map();
|
|
38
40
|
/** Pending update notifications keyed by workspace directory. */
|
|
39
41
|
const pendingUpdateInfo = new Map();
|
|
42
|
+
/** Guard flag to prevent re-entrant shutdown. */
|
|
43
|
+
let shutdownRegistered = false;
|
|
44
|
+
/** Close all active background indexers and MCP servers, then destroy idle sockets. */
|
|
45
|
+
async function shutdownPluginResources() {
|
|
46
|
+
for (const [dir, indexer] of backgroundIndexers) {
|
|
47
|
+
try {
|
|
48
|
+
await indexer.close();
|
|
49
|
+
}
|
|
50
|
+
catch { /* best-effort */ }
|
|
51
|
+
backgroundIndexers.delete(dir);
|
|
52
|
+
}
|
|
53
|
+
for (const [dir, server] of mcpServers) {
|
|
54
|
+
try {
|
|
55
|
+
await server.close();
|
|
56
|
+
}
|
|
57
|
+
catch { /* best-effort */ }
|
|
58
|
+
mcpServers.delete(dir);
|
|
59
|
+
}
|
|
60
|
+
configCache.clear();
|
|
61
|
+
pendingUpdateInfo.clear();
|
|
62
|
+
destroyAllPooledConnections();
|
|
63
|
+
}
|
|
64
|
+
/** Register a process.beforeExit handler so that resources are cleaned up
|
|
65
|
+
* when the OpenCode process exits (rather than only on plugin reload). */
|
|
66
|
+
function registerShutdownHandler() {
|
|
67
|
+
if (shutdownRegistered)
|
|
68
|
+
return;
|
|
69
|
+
shutdownRegistered = true;
|
|
70
|
+
process.once("beforeExit", () => {
|
|
71
|
+
void shutdownPluginResources();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
registerShutdownHandler();
|
|
75
|
+
/** Set a bounded Map entry, evicting the oldest key if the map exceeds maximum size. */
|
|
76
|
+
function boundedSet(map, key, value, maxSize) {
|
|
77
|
+
if (map.has(key)) {
|
|
78
|
+
map.set(key, value);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (map.size >= maxSize) {
|
|
82
|
+
const oldest = map.keys().next().value;
|
|
83
|
+
if (oldest !== undefined)
|
|
84
|
+
map.delete(oldest);
|
|
85
|
+
}
|
|
86
|
+
map.set(key, value);
|
|
87
|
+
}
|
|
40
88
|
/** Name of the semantic search tool as exposed to the LLM. */
|
|
41
89
|
const CONTEXT_TOOL_NAME = "search_semantic";
|
|
42
90
|
/** Marker string injected into context output to identify RAG-sourced content. */
|
|
@@ -231,10 +279,10 @@ function buildRetrievalQuery(hints) {
|
|
|
231
279
|
* Perform a retrieval query against the vector store with the given parameters.
|
|
232
280
|
* Returns an empty array for blank queries.
|
|
233
281
|
*/
|
|
234
|
-
async function retrieveContext(query, embedder, store, topK, retrieveFn = retrieve, minScore = 0, keywordIndex, keywordWeight, queryPrefix, explain = false) {
|
|
282
|
+
async function retrieveContext(query, embedder, store, topK, retrieveFn = retrieve, minScore = 0, keywordIndex, keywordWeight, queryPrefix, explain = false, hybridEnabled) {
|
|
235
283
|
if (query.trim().length === 0)
|
|
236
284
|
return [];
|
|
237
|
-
return retrieveFn(query, embedder, store, { topK, minScore, keywordIndex, keywordWeight, queryPrefix, explain });
|
|
285
|
+
return retrieveFn(query, embedder, store, { topK, minScore, keywordIndex, keywordWeight, hybridEnabled, queryPrefix, explain });
|
|
238
286
|
}
|
|
239
287
|
/**
|
|
240
288
|
* Load results from one or two queries (primary + optional extra), optimize
|
|
@@ -244,9 +292,10 @@ async function retrieveContext(query, embedder, store, topK, retrieveFn = retrie
|
|
|
244
292
|
async function loadRetrievedResults(query, embedder, store, cfg, retrieveFn = retrieve, topK = cfg.retrieval.topK, extraQuery, keywordIndex, queryPrefix, explain = false) {
|
|
245
293
|
const minScore = cfg.retrieval.minScore;
|
|
246
294
|
const kw = cfg.retrieval.hybridSearch?.keywordWeight;
|
|
247
|
-
const
|
|
295
|
+
const hybridEnabled = cfg.retrieval.hybridSearch?.enabled;
|
|
296
|
+
const primaryResults = await retrieveContext(query, embedder, store, topK, retrieveFn, minScore, keywordIndex, kw, queryPrefix, explain, hybridEnabled);
|
|
248
297
|
const extraResults = extraQuery
|
|
249
|
-
? await retrieveContext(extraQuery, embedder, store, topK, retrieveFn, minScore, keywordIndex, kw, queryPrefix, explain)
|
|
298
|
+
? await retrieveContext(extraQuery, embedder, store, topK, retrieveFn, minScore, keywordIndex, kw, queryPrefix, explain, hybridEnabled)
|
|
250
299
|
: [];
|
|
251
300
|
const optCfg = cfg.retrieval.contextOptimization ?? DEFAULT_CONTEXT_OPTIMIZATION;
|
|
252
301
|
return optimizeContext([...primaryResults, ...extraResults], { topK, config: optCfg })
|
|
@@ -356,16 +405,27 @@ export function createRagHooks(options) {
|
|
|
356
405
|
let cachedOverrides = loadRuntimeOverrides(options.storePath);
|
|
357
406
|
let overridesLastCheck = 0;
|
|
358
407
|
const OVERRIDES_TTL_MS = Number(process.env.OPENCODE_RAG_OVERRIDES_TTL_MS) || 3600000;
|
|
408
|
+
let lastEffectiveCfg = null;
|
|
409
|
+
let lastEffectiveCfgTime = 0;
|
|
359
410
|
function getEffectiveCfg() {
|
|
360
411
|
if (Date.now() - overridesLastCheck > OVERRIDES_TTL_MS) {
|
|
361
412
|
cachedOverrides = loadRuntimeOverrides(options.storePath);
|
|
362
413
|
overridesLastCheck = Date.now();
|
|
414
|
+
lastEffectiveCfg = null;
|
|
415
|
+
}
|
|
416
|
+
if (lastEffectiveCfg && Date.now() - lastEffectiveCfgTime < 1000) {
|
|
417
|
+
return lastEffectiveCfg;
|
|
363
418
|
}
|
|
364
|
-
|
|
419
|
+
lastEffectiveCfg = applyRuntimeOverrides(options.cfg, cachedOverrides);
|
|
420
|
+
lastEffectiveCfgTime = Date.now();
|
|
421
|
+
return lastEffectiveCfg;
|
|
365
422
|
}
|
|
366
423
|
// Session-level caches for lazy retrieval
|
|
367
424
|
const sessionLastMessage = new Map();
|
|
368
425
|
const sessionRetrievalCache = new Map();
|
|
426
|
+
// Track the current assistant message text per session for 2-message search queries.
|
|
427
|
+
const sessionAssistantMessageId = new Map();
|
|
428
|
+
const sessionAssistantText = new Map();
|
|
369
429
|
// Evaluation session logger — captures OpenCode events for analysis
|
|
370
430
|
const sessionLogger = createSessionLogger(options.storePath);
|
|
371
431
|
appendDebugLog(options.logFilePath, {
|
|
@@ -536,6 +596,7 @@ export function createRagHooks(options) {
|
|
|
536
596
|
sessionLastMessage,
|
|
537
597
|
sessionRetrievalCache,
|
|
538
598
|
keywordIndex,
|
|
599
|
+
maxSessionCacheSize: MAX_SESSION_MAP_SIZE,
|
|
539
600
|
});
|
|
540
601
|
tools["read"] = readTool;
|
|
541
602
|
appendDebugLog(options.logFilePath, {
|
|
@@ -551,6 +612,38 @@ export function createRagHooks(options) {
|
|
|
551
612
|
return {
|
|
552
613
|
async event({ event }) {
|
|
553
614
|
sessionLogger.onEvent(event);
|
|
615
|
+
// Accumulate the most recent assistant message's text via streaming deltas.
|
|
616
|
+
// Used to build the 2-message retrieval query in chat.message.
|
|
617
|
+
try {
|
|
618
|
+
if (event.type === "message.updated") {
|
|
619
|
+
const info = event.properties;
|
|
620
|
+
const msgInfo = info?.info;
|
|
621
|
+
if (msgInfo?.role === "assistant" &&
|
|
622
|
+
typeof msgInfo.sessionID === "string" &&
|
|
623
|
+
typeof msgInfo.id === "string") {
|
|
624
|
+
boundedSet(sessionAssistantMessageId, msgInfo.sessionID, msgInfo.id, MAX_SESSION_MAP_SIZE);
|
|
625
|
+
boundedSet(sessionAssistantText, msgInfo.sessionID, "", MAX_SESSION_MAP_SIZE);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else if (event.type === "message.part.updated") {
|
|
629
|
+
const props = event.properties;
|
|
630
|
+
const part = props?.part;
|
|
631
|
+
const delta = props?.delta;
|
|
632
|
+
if (part?.type === "text" &&
|
|
633
|
+
typeof part.sessionID === "string" &&
|
|
634
|
+
typeof part.messageID === "string" &&
|
|
635
|
+
typeof delta === "string") {
|
|
636
|
+
const assistantMsgId = sessionAssistantMessageId.get(part.sessionID);
|
|
637
|
+
if (assistantMsgId && part.messageID === assistantMsgId) {
|
|
638
|
+
const existing = sessionAssistantText.get(part.sessionID) ?? "";
|
|
639
|
+
boundedSet(sessionAssistantText, part.sessionID, existing + delta, MAX_SESSION_MAP_SIZE);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
// Non-critical — must never throw
|
|
646
|
+
}
|
|
554
647
|
},
|
|
555
648
|
tool: tools,
|
|
556
649
|
async "experimental.chat.system.transform"(_input, output) {
|
|
@@ -558,42 +651,45 @@ export function createRagHooks(options) {
|
|
|
558
651
|
scope: "experimental.chat.system.transform",
|
|
559
652
|
message: "system guidance injected",
|
|
560
653
|
});
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
654
|
+
const cfg = getEffectiveCfg();
|
|
655
|
+
if (cfg.openCode.injectSystemPrompt !== false) {
|
|
656
|
+
const guidance = [
|
|
657
|
+
"MANDATORY: OpenCodeRAG tools MUST be used before any code task:",
|
|
658
|
+
"- `search_semantic(query)`: retrieve relevant code chunks. Call BEFORE planning, editing, or answering. Accepts `pathHints` and `languageHints`.",
|
|
659
|
+
"- `get_file_skeleton(filePath)`: structural overview of a file. Call BEFORE reading any file.",
|
|
660
|
+
"- `find_usages(symbolName)`: find all references. Call BEFORE editing any function, class, or variable.",
|
|
661
|
+
"- `describe_image(filePath)`: describe an image file using a vision model. Call when user refers to a screenshot, diagram, or image.",
|
|
662
|
+
"",
|
|
663
|
+
"Decision tree — ALWAYS follow this order:",
|
|
664
|
+
"1. User mentions code behavior/architecture → `search_semantic(query)`",
|
|
665
|
+
"2. User mentions a file path → `get_file_skeleton(filePath)` THEN `read` on specific lines",
|
|
666
|
+
"3. User mentions a function/class/variable to edit → `find_usages(symbolName)` THEN `search_semantic` THEN `edit`",
|
|
667
|
+
"4. User asks a code question → `search_semantic` to gather context before answering",
|
|
668
|
+
"5. User asks about an image or visual asset → `describe_image(filePath)` to retrieve its generated description, then optionally `search_semantic` for related code",
|
|
669
|
+
"",
|
|
670
|
+
"Proactive triggers — you MUST call these tools when:",
|
|
671
|
+
"- User asks about code behavior, architecture, or implementation details",
|
|
672
|
+
"- User asks to edit, refactor, or fix code — call `find_usages` first",
|
|
673
|
+
"- User references files or functions you haven't read yet",
|
|
674
|
+
"- User says \"find\", \"search\", \"look up\", \"where is\", \"how does\"",
|
|
675
|
+
"- User refers to an image, screenshot, diagram, or visual asset",
|
|
676
|
+
"- Before answering ANY code-related question, retrieve context first",
|
|
677
|
+
"- Before reading ANY file, call `get_file_skeleton` to orient first",
|
|
678
|
+
"",
|
|
679
|
+
"Anti-patterns — NEVER do these:",
|
|
680
|
+
"- Reading full files without calling `get_file_skeleton` first (wastes tokens)",
|
|
681
|
+
"- Editing a function without calling `find_usages` first (breaks call sites)",
|
|
682
|
+
"- Answering code questions without calling `search_semantic` first (you guess at behavior)",
|
|
683
|
+
"- Using `grep`/`glob` when `search_semantic` would find the answer faster",
|
|
684
|
+
"- Treating image files as text — use `describe_image` instead of reading raw bytes",
|
|
685
|
+
];
|
|
686
|
+
output.system.unshift(guidance.join("\n"));
|
|
687
|
+
}
|
|
592
688
|
// Inject update notification if available
|
|
593
689
|
const updateInfo = pendingUpdateInfo.get(options.worktree);
|
|
594
690
|
if (updateInfo) {
|
|
595
691
|
output.system.unshift(`OpenCodeRAG update available: ${updateInfo.currentVersion} → ${updateInfo.latestVersion}. ` +
|
|
596
|
-
`Run \`opencode-rag
|
|
692
|
+
`Run \`npm update -g opencode-rag-plugin && opencode-rag setup\` to install.`);
|
|
597
693
|
}
|
|
598
694
|
// Inject documentation mode system prompt if enabled
|
|
599
695
|
const docMode = getEffectiveCfg().documentationMode;
|
|
@@ -616,7 +712,7 @@ export function createRagHooks(options) {
|
|
|
616
712
|
});
|
|
617
713
|
if (text.length === 0)
|
|
618
714
|
return;
|
|
619
|
-
sessionLastMessage
|
|
715
|
+
boundedSet(sessionLastMessage, input.sessionID, text, MAX_SESSION_MAP_SIZE);
|
|
620
716
|
// Handle /doc slash command
|
|
621
717
|
if (text.startsWith("/doc")) {
|
|
622
718
|
const docMode = getEffectiveCfg().documentationMode;
|
|
@@ -714,7 +810,11 @@ export function createRagHooks(options) {
|
|
|
714
810
|
const effectiveCfg = getEffectiveCfg();
|
|
715
811
|
const hybridCfg = effectiveCfg.retrieval.hybridSearch;
|
|
716
812
|
const retrievalStart = Date.now();
|
|
717
|
-
|
|
813
|
+
// Build 2-message query: previous assistant response + current user prompt.
|
|
814
|
+
// Falls back to user-only text on the first turn (no prior assistant message).
|
|
815
|
+
const assistantText = sessionAssistantText.get(input.sessionID);
|
|
816
|
+
const searchQuery = assistantText ? assistantText + "\n" + text : text;
|
|
817
|
+
const results = await dependencies.retrieve(searchQuery, embedder, store, {
|
|
718
818
|
topK: effectiveCfg.retrieval.topK,
|
|
719
819
|
minScore: 0,
|
|
720
820
|
keywordIndex,
|
|
@@ -734,29 +834,22 @@ export function createRagHooks(options) {
|
|
|
734
834
|
const parts = output?.parts ?? output?.message?.parts;
|
|
735
835
|
if (Array.isArray(parts) && parts.length > 0) {
|
|
736
836
|
const first = parts[0];
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
injectionType: pendingInjection,
|
|
751
|
-
retrievalTimeMs,
|
|
752
|
-
resultCount: results.length,
|
|
753
|
-
timestamp: Date.now(),
|
|
754
|
-
},
|
|
755
|
-
};
|
|
756
|
-
parts.push(ragPart);
|
|
837
|
+
if (typeof first.text === "string") {
|
|
838
|
+
// Append RAG context directly to the user's text part instead of
|
|
839
|
+
// pushing a new part. Matches the /doc handler pattern — avoids the
|
|
840
|
+
// duplicate-output bug caused by parts.push() in some render paths.
|
|
841
|
+
parts[0] = { ...first, text: first.text + "\n\n" + ragContext };
|
|
842
|
+
}
|
|
843
|
+
const msgParts = output?.message?.parts;
|
|
844
|
+
if (Array.isArray(msgParts) && msgParts !== parts) {
|
|
845
|
+
const mfirst = msgParts[0];
|
|
846
|
+
if (typeof mfirst.text === "string") {
|
|
847
|
+
msgParts[0] = { ...mfirst, text: mfirst.text + "\n\n" + ragContext };
|
|
848
|
+
}
|
|
849
|
+
}
|
|
757
850
|
appendDebugLog(options.logFilePath, {
|
|
758
851
|
scope: "chat.message",
|
|
759
|
-
message: `
|
|
852
|
+
message: `injected ${pendingInjection} context into parts[0].text (text.length=${ragContext.length})`,
|
|
760
853
|
});
|
|
761
854
|
}
|
|
762
855
|
}
|
|
@@ -765,10 +858,10 @@ export function createRagHooks(options) {
|
|
|
765
858
|
scope: "chat.message",
|
|
766
859
|
message: `injected ${pendingInjection} context (results=${results.length}, retrieval=${retrievalTimeMs}ms)`,
|
|
767
860
|
});
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
const postPartsText = Array.isArray(
|
|
771
|
-
const postMsgPartsText = Array.isArray(
|
|
861
|
+
const postParts = output?.parts;
|
|
862
|
+
const postMsgParts = output?.message?.parts;
|
|
863
|
+
const postPartsText = Array.isArray(postParts) ? postParts[0]?.text : undefined;
|
|
864
|
+
const postMsgPartsText = Array.isArray(postMsgParts) ? postMsgParts[0]?.text : undefined;
|
|
772
865
|
appendDebugLog(options.logFilePath, {
|
|
773
866
|
scope: "chat.message",
|
|
774
867
|
message: `post-injection parts[0].text="${typeof postPartsText === 'string' ? postPartsText.substring(0, 80) : 'n/a'}" msgParts[0].text="${typeof postMsgPartsText === 'string' ? postMsgPartsText.substring(0, 80) : 'n/a'}"`,
|
|
@@ -994,29 +1087,51 @@ export const ragPlugin = async (input, _options) => {
|
|
|
994
1087
|
}
|
|
995
1088
|
mcpServers.delete(input.directory);
|
|
996
1089
|
}
|
|
1090
|
+
// Clean up stale config cache and pending update info for this directory
|
|
1091
|
+
configCache.delete(input.directory);
|
|
1092
|
+
pendingUpdateInfo.delete(input.directory);
|
|
1093
|
+
// Clean up idle HTTP sockets from previous provider connections
|
|
1094
|
+
destroyAllPooledConnections();
|
|
997
1095
|
appendDebugLog(logFilePath, {
|
|
998
1096
|
scope: "plugin",
|
|
999
1097
|
message: `OpenCode plugin enabled for ${input.directory}`,
|
|
1000
1098
|
}, logLevel);
|
|
1001
|
-
//
|
|
1099
|
+
// Use cached dimension from config if available (avoids blocking startup with an API call)
|
|
1100
|
+
// If not set, probe the embedding provider once and persist the result.
|
|
1002
1101
|
const embedder = createEmbedder(effectiveCfg);
|
|
1003
|
-
let vectorDimension =
|
|
1004
|
-
|
|
1005
|
-
const probe = await embedder.embed(["dimension-probe"], "query");
|
|
1006
|
-
if (probe && probe[0] && probe[0].length > 0 && typeof probe[0][0] === "number") {
|
|
1007
|
-
vectorDimension = probe[0].length;
|
|
1008
|
-
}
|
|
1102
|
+
let vectorDimension = effectiveCfg.embedding.vectorDimension;
|
|
1103
|
+
if (vectorDimension && vectorDimension > 0) {
|
|
1009
1104
|
appendDebugLog(logFilePath, {
|
|
1010
1105
|
scope: "plugin",
|
|
1011
|
-
message: `Vector dimension: ${vectorDimension}`,
|
|
1106
|
+
message: `Vector dimension: ${vectorDimension} (cached in config)`,
|
|
1012
1107
|
}, logLevel);
|
|
1013
1108
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1109
|
+
else {
|
|
1110
|
+
vectorDimension = 384;
|
|
1111
|
+
try {
|
|
1112
|
+
const probe = await embedder.embed(["dimension-probe"], "query");
|
|
1113
|
+
if (probe && probe[0] && probe[0].length > 0 && typeof probe[0][0] === "number") {
|
|
1114
|
+
vectorDimension = probe[0].length;
|
|
1115
|
+
const configPath = findConfigFile(input.directory);
|
|
1116
|
+
if (configPath) {
|
|
1117
|
+
try {
|
|
1118
|
+
persistProbedDimension(configPath, vectorDimension);
|
|
1119
|
+
}
|
|
1120
|
+
catch { /* best-effort */ }
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
appendDebugLog(logFilePath, {
|
|
1124
|
+
scope: "plugin",
|
|
1125
|
+
message: `Vector dimension: ${vectorDimension}`,
|
|
1126
|
+
}, logLevel);
|
|
1127
|
+
}
|
|
1128
|
+
catch (err) {
|
|
1129
|
+
appendDebugLog(logFilePath, {
|
|
1130
|
+
scope: "plugin",
|
|
1131
|
+
message: `Dimension probe failed, falling back to ${vectorDimension}`,
|
|
1132
|
+
error: err,
|
|
1133
|
+
}, logLevel);
|
|
1134
|
+
}
|
|
1020
1135
|
}
|
|
1021
1136
|
const store = createVectorStore(effectiveCfg, storePath, vectorDimension);
|
|
1022
1137
|
// Load or create keyword index for hybrid search
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview In-memory inverted keyword index with stemming, tokenization, and optional serialization.
|
|
3
3
|
*/
|
|
4
|
-
import type { Chunk, SearchResult } from "../core/interfaces.js";
|
|
4
|
+
import type { Chunk, SearchResult, MetadataFilter } from "../core/interfaces.js";
|
|
5
5
|
/**
|
|
6
6
|
* Tokenize text into normalized tokens including stems, camelCase parts, and snake_case parts.
|
|
7
7
|
*
|
|
@@ -22,7 +22,8 @@ export declare class KeywordIndex {
|
|
|
22
22
|
addChunks(chunks: Chunk[]): void;
|
|
23
23
|
getMatchedTerms(query: string, chunkId: string): string[];
|
|
24
24
|
removeByFilePath(filePath: string): void;
|
|
25
|
-
search(query: string, topK: number): SearchResult[];
|
|
25
|
+
search(query: string, topK: number, filter?: MetadataFilter): SearchResult[];
|
|
26
|
+
close(): void;
|
|
26
27
|
clear(): void;
|
|
27
28
|
count(): number;
|
|
28
29
|
save(storePath?: string): Promise<void>;
|