Skip to content

Commit eaae319

Browse files
committed
fix: config tool reading and merging issue
1 parent e71f0a4 commit eaae319

File tree

5 files changed

+201
-123
lines changed

5 files changed

+201
-123
lines changed

native/src/parser.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,17 @@ fn extract_semantic_nodes(
103103
#[cfg(debug_assertions)]
104104
{
105105
PERF_STATS.lock().unwrap().extract_semantic_nodes_calls += 1;
106+
PERF_STATS.lock().unwrap().max_depth_reached =
107+
PERF_STATS.lock().unwrap().max_depth_reached.max(depth);
106108
}
107109

108110
const MAX_RECURSION_DEPTH: usize = 1024;
109111
let skip_children = depth > MAX_RECURSION_DEPTH;
110112
if skip_children {
111-
eprintln!(
112-
"WARNING: Maximum recursion depth {} exceeded in extract_semantic_nodes, skipping further recursion",
113-
MAX_RECURSION_DEPTH
114-
);
113+
#[cfg(debug_assertions)]
114+
{
115+
PERF_STATS.lock().unwrap().recursion_depth_exceeded_count += 1;
116+
}
115117
}
116118
loop {
117119
let node = cursor.node();
@@ -256,6 +258,8 @@ struct PerfStats {
256258
extract_name_time: u128,
257259
is_semantic_node_calls: usize,
258260
is_semantic_node_time: u128,
261+
recursion_depth_exceeded_count: usize,
262+
max_depth_reached: usize,
259263
}
260264

261265
impl PerfStats {
@@ -269,6 +273,8 @@ impl PerfStats {
269273
extract_name_time: 0,
270274
is_semantic_node_calls: 0,
271275
is_semantic_node_time: 0,
276+
recursion_depth_exceeded_count: 0,
277+
max_depth_reached: 0,
272278
}
273279
}
274280

@@ -290,6 +296,11 @@ impl PerfStats {
290296
"is_semantic_node: {} calls, {} us",
291297
self.is_semantic_node_calls, self.is_semantic_node_time
292298
);
299+
eprintln!(
300+
"recursion_depth_exceeded: {} times",
301+
self.recursion_depth_exceeded_count
302+
);
303+
eprintln!("max_depth_reached: {}", self.max_depth_reached);
293304
}
294305
}
295306

src/cli.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,11 @@
11
#!/usr/bin/env node
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3-
import { existsSync, readFileSync } from "fs";
43
import * as path from "path";
5-
import * as os from "os";
64

75
import { parseConfig } from "./config/schema.js";
86
import { handleEvalCommand } from "./eval/cli.js";
97
import { createMcpServer } from "./mcp-server.js";
10-
11-
function loadJsonFile(filePath: string): unknown {
12-
try {
13-
if (existsSync(filePath)) {
14-
const content = readFileSync(filePath, "utf-8");
15-
return JSON.parse(content);
16-
}
17-
} catch { /* ignore */ }
18-
return null;
19-
}
20-
21-
function loadPluginConfig(projectRoot: string, configPath?: string): unknown {
22-
if (configPath) {
23-
const config = loadJsonFile(configPath);
24-
if (config) return config;
25-
}
26-
27-
const projectConfig = loadJsonFile(path.join(projectRoot, ".opencode", "codebase-index.json"));
28-
if (projectConfig) return projectConfig;
29-
30-
const globalConfig = loadJsonFile(path.join(os.homedir(), ".config", "opencode", "codebase-index.json"));
31-
if (globalConfig) return globalConfig;
32-
33-
return {};
34-
}
8+
import { loadMergedConfig } from "./config/merger.js";
359

3610
function parseArgs(argv: string[]): { project: string; config?: string } {
3711
let project = process.cwd();
@@ -55,7 +29,7 @@ async function main(): Promise<void> {
5529
}
5630

5731
const args = parseArgs(process.argv);
58-
const rawConfig = loadPluginConfig(args.project, args.config);
32+
const rawConfig = loadMergedConfig(args.project);
5933
const config = parseConfig(rawConfig);
6034

6135
const server = createMcpServer(args.project, config);

src/config/merger.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { existsSync, readFileSync } from "fs";
2+
import * as path from "path";
3+
import * as os from "os";
4+
5+
function loadJsonFile(filePath: string): unknown {
6+
try {
7+
if (existsSync(filePath)) {
8+
const content = readFileSync(filePath, "utf-8");
9+
return JSON.parse(content);
10+
}
11+
} catch { /* ignore */ }
12+
return null;
13+
}
14+
15+
/**
16+
* Loads and merges global and project configs.
17+
*
18+
* Merge rules:
19+
* - Global config is the base
20+
* - For most fields: project overrides global if set, otherwise load global (fallback)
21+
* - For knowledgeBases: merge arrays (union, deduplicated)
22+
* - For additionalInclude: merge arrays (union, deduplicated)
23+
* - For include/exclude: project overrides global if set, otherwise load global
24+
*/
25+
export function loadMergedConfig(projectRoot: string): unknown {
26+
const globalConfigPath = path.join(os.homedir(), ".config", "opencode", "codebase-index.json");
27+
const globalConfig = loadJsonFile(globalConfigPath) as Record<string, unknown> | null;
28+
const projectConfigPath = path.join(projectRoot, ".opencode", "codebase-index.json");
29+
const projectConfig = loadJsonFile(projectConfigPath) as Record<string, unknown> | null;
30+
31+
// If neither exists, return empty
32+
if (!globalConfig && !projectConfig) {
33+
return {};
34+
}
35+
36+
// If only global exists, return it
37+
if (!projectConfig && globalConfig) {
38+
return globalConfig;
39+
}
40+
41+
// If only project exists, return it
42+
if (!globalConfig && projectConfig) {
43+
return projectConfig;
44+
}
45+
46+
// Both exist - start with global config as base
47+
const merged: Record<string, unknown> = { ...globalConfig };
48+
49+
// For embeddingProvider: project overrides if set, otherwise use global
50+
if (projectConfig && "embeddingProvider" in projectConfig) {
51+
merged.embeddingProvider = projectConfig.embeddingProvider;
52+
} else if (globalConfig && globalConfig.embeddingProvider) {
53+
merged.embeddingProvider = globalConfig.embeddingProvider;
54+
}
55+
56+
// For customProvider: project overrides if set, otherwise use global
57+
if (projectConfig && "customProvider" in projectConfig) {
58+
merged.customProvider = projectConfig.customProvider;
59+
} else if (globalConfig && globalConfig.customProvider) {
60+
merged.customProvider = globalConfig.customProvider;
61+
}
62+
63+
// For embeddingModel: project overrides if set, otherwise use global
64+
if (projectConfig && "embeddingModel" in projectConfig) {
65+
merged.embeddingModel = projectConfig.embeddingModel;
66+
} else if (globalConfig && globalConfig.embeddingModel) {
67+
merged.embeddingModel = globalConfig.embeddingModel;
68+
}
69+
70+
// For reranker: project overrides if set, otherwise use global
71+
if (projectConfig && "reranker" in projectConfig) {
72+
merged.reranker = projectConfig.reranker;
73+
} else if (globalConfig && globalConfig.reranker) {
74+
merged.reranker = globalConfig.reranker;
75+
}
76+
77+
// For include: project overrides if set, otherwise use global
78+
if (projectConfig && "include" in projectConfig) {
79+
merged.include = projectConfig.include;
80+
} else if (globalConfig && globalConfig.include) {
81+
merged.include = globalConfig.include;
82+
}
83+
84+
// For exclude: project overrides if set, otherwise use global
85+
if (projectConfig && "exclude" in projectConfig) {
86+
merged.exclude = projectConfig.exclude;
87+
} else if (globalConfig && globalConfig.exclude) {
88+
merged.exclude = globalConfig.exclude;
89+
}
90+
91+
// For indexing: project overrides if set, otherwise use global
92+
if (projectConfig && "indexing" in projectConfig) {
93+
merged.indexing = projectConfig.indexing;
94+
} else if (globalConfig && globalConfig.indexing) {
95+
merged.indexing = globalConfig.indexing;
96+
}
97+
98+
// For search: project overrides if set, otherwise use global
99+
if (projectConfig && "search" in projectConfig) {
100+
merged.search = projectConfig.search;
101+
} else if (globalConfig && globalConfig.search) {
102+
merged.search = globalConfig.search;
103+
}
104+
105+
// For debug: project overrides if set, otherwise use global
106+
if (projectConfig && "debug" in projectConfig) {
107+
merged.debug = projectConfig.debug;
108+
} else if (globalConfig && globalConfig.debug) {
109+
merged.debug = globalConfig.debug;
110+
}
111+
112+
// For scope: project overrides if set, otherwise use global
113+
if (projectConfig && "scope" in projectConfig) {
114+
merged.scope = projectConfig.scope;
115+
} else if (globalConfig && "scope" in globalConfig) {
116+
merged.scope = globalConfig.scope;
117+
}
118+
119+
// For other config sections: project overrides if set, otherwise use global
120+
if (projectConfig) {
121+
for (const key of Object.keys(projectConfig)) {
122+
if (
123+
key === "embeddingProvider" ||
124+
key === "customProvider" ||
125+
key === "embeddingModel" ||
126+
key === "reranker" ||
127+
key === "include" ||
128+
key === "exclude" ||
129+
key === "indexing" ||
130+
key === "search" ||
131+
key === "debug" ||
132+
key === "scope" ||
133+
key === "knowledgeBases" ||
134+
key === "additionalInclude"
135+
) {
136+
continue; // Already handled above
137+
}
138+
merged[key] = projectConfig[key];
139+
}
140+
}
141+
142+
// For knowledgeBases: merge arrays (union, deduplicated)
143+
const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
144+
const projectKbs = projectConfig && Array.isArray(projectConfig.knowledgeBases) ? projectConfig.knowledgeBases : [];
145+
const allKbs = [...globalKbs, ...projectKbs];
146+
const uniqueKbs = [...new Set(allKbs.map(p => String(p).trim()))];
147+
merged.knowledgeBases = uniqueKbs;
148+
149+
// For additionalInclude: merge arrays (union, deduplicated)
150+
const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
151+
const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
152+
const allAdditional = [...globalAdditional, ...projectAdditional];
153+
const uniqueAdditional = [...new Set(allAdditional.map(p => String(p).trim()))];
154+
merged.additionalInclude = uniqueAdditional;
155+
156+
return merged;
157+
}

src/index.ts

Lines changed: 2 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import type { Plugin } from "@opencode-ai/plugin";
2-
import { existsSync, readFileSync } from "fs";
32
import * as path from "path";
4-
import * as os from "os";
53
import { fileURLToPath } from "url";
64

75
import { parseConfig } from "./config/schema.js";
6+
import { loadMergedConfig } from "./config/merger.js";
87
import { Indexer } from "./indexer/index.js";
98
import { createWatcherWithIndexer } from "./watcher/index.js";
109
import {
@@ -36,90 +35,10 @@ function getCommandsDir(): string {
3635
return path.join(currentDir, "..", "commands");
3736
}
3837

39-
function loadJsonFile(filePath: string): unknown {
40-
try {
41-
if (existsSync(filePath)) {
42-
const content = readFileSync(filePath, "utf-8");
43-
return JSON.parse(content);
44-
}
45-
} catch { /* ignore */ }
46-
return null;
47-
}
48-
49-
/**
50-
* Loads and merges global and project configs.
51-
*
52-
* Merge rules:
53-
* - Global config is the base, project config overrides
54-
* - For embeddingProvider/customProvider: project overrides global, fallback to global if not set in project
55-
* - For knowledgeBases: merge arrays (union, deduplicated)
56-
* - For other fields: project overrides global
57-
*/
58-
function loadPluginConfig(projectRoot: string): unknown {
59-
const globalConfigPath = path.join(os.homedir(), ".config", "opencode", "codebase-index.json");
60-
const globalConfig = loadJsonFile(globalConfigPath) as Record<string, unknown> | null;
61-
const projectConfig = loadJsonFile(path.join(projectRoot, ".opencode", "codebase-index.json")) as Record<string, unknown> | null;
62-
63-
// If neither exists, return empty
64-
if (!globalConfig && !projectConfig) {
65-
return {};
66-
}
67-
68-
// If only global exists, return it
69-
if (!projectConfig && globalConfig) {
70-
return globalConfig;
71-
}
72-
73-
// If only project exists, return it
74-
if (!globalConfig && projectConfig) {
75-
return projectConfig;
76-
}
77-
78-
// Both exist - merge them
79-
const merged: Record<string, unknown> = { ...globalConfig };
80-
81-
// For embedding provider: project overrides global if set, otherwise use global
82-
if (projectConfig) {
83-
if (projectConfig.embeddingProvider) {
84-
merged.embeddingProvider = projectConfig.embeddingProvider;
85-
}
86-
if (projectConfig.customProvider) {
87-
merged.customProvider = projectConfig.customProvider;
88-
}
89-
if (projectConfig.embeddingModel) {
90-
merged.embeddingModel = projectConfig.embeddingModel;
91-
}
92-
93-
// For other config sections: project overrides global
94-
for (const key of Object.keys(projectConfig)) {
95-
if (key === "embeddingProvider" || key === "customProvider" || key === "embeddingModel" || key === "knowledgeBases" || key === "additionalInclude") {
96-
continue; // Already handled above or below
97-
}
98-
merged[key] = projectConfig[key];
99-
}
100-
101-
// For knowledgeBases: merge arrays (union, deduplicated)
102-
const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
103-
const projectKbs = Array.isArray(projectConfig.knowledgeBases) ? projectConfig.knowledgeBases : [];
104-
const allKbs = [...globalKbs, ...projectKbs];
105-
const uniqueKbs = [...new Set(allKbs.map(p => String(p).trim()))];
106-
merged.knowledgeBases = uniqueKbs;
107-
108-
// For additionalInclude: merge arrays (union, deduplicated)
109-
const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
110-
const projectAdditional = Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
111-
const allAdditional = [...globalAdditional, ...projectAdditional];
112-
const uniqueAdditional = [...new Set(allAdditional.map(p => String(p).trim()))];
113-
merged.additionalInclude = uniqueAdditional;
114-
}
115-
116-
return merged;
117-
}
118-
11938
const plugin: Plugin = async ({ directory }) => {
12039
try {
12140
const projectRoot = directory;
122-
const rawConfig = loadPluginConfig(projectRoot);
41+
const rawConfig = loadMergedConfig(projectRoot);
12342
const config = parseConfig(rawConfig);
12443

12544
initializeTools(projectRoot, config);

0 commit comments

Comments
 (0)