snow-ai 0.3.6 → 0.3.8
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/dist/agents/compactAgent.js +7 -3
- package/dist/agents/reviewAgent.d.ts +50 -0
- package/dist/agents/reviewAgent.js +264 -0
- package/dist/agents/summaryAgent.d.ts +34 -8
- package/dist/agents/summaryAgent.js +167 -164
- package/dist/api/anthropic.d.ts +1 -0
- package/dist/api/anthropic.js +118 -78
- package/dist/api/chat.d.ts +2 -1
- package/dist/api/chat.js +82 -52
- package/dist/api/gemini.d.ts +1 -0
- package/dist/api/gemini.js +110 -64
- package/dist/api/responses.d.ts +10 -1
- package/dist/api/responses.js +127 -79
- package/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +36 -7
- package/dist/api/types.d.ts +8 -0
- package/dist/app.js +15 -2
- package/dist/hooks/useCommandHandler.d.ts +1 -0
- package/dist/hooks/useCommandHandler.js +102 -1
- package/dist/hooks/useCommandPanel.d.ts +2 -1
- package/dist/hooks/useCommandPanel.js +19 -1
- package/dist/hooks/useConversation.d.ts +4 -1
- package/dist/hooks/useConversation.js +91 -29
- package/dist/hooks/useKeyboardInput.js +19 -0
- package/dist/hooks/useSnapshotState.d.ts +2 -0
- package/dist/hooks/useTerminalFocus.js +13 -3
- package/dist/mcp/aceCodeSearch.d.ts +2 -76
- package/dist/mcp/aceCodeSearch.js +31 -467
- package/dist/mcp/bash.d.ts +1 -8
- package/dist/mcp/bash.js +20 -40
- package/dist/mcp/filesystem.d.ts +131 -111
- package/dist/mcp/filesystem.js +212 -375
- package/dist/mcp/ideDiagnostics.js +2 -4
- package/dist/mcp/todo.d.ts +1 -17
- package/dist/mcp/todo.js +11 -15
- package/dist/mcp/types/aceCodeSearch.types.d.ts +92 -0
- package/dist/mcp/types/aceCodeSearch.types.js +4 -0
- package/dist/mcp/types/bash.types.d.ts +13 -0
- package/dist/mcp/types/bash.types.js +4 -0
- package/dist/mcp/types/filesystem.types.d.ts +135 -0
- package/dist/mcp/types/filesystem.types.js +4 -0
- package/dist/mcp/types/todo.types.d.ts +27 -0
- package/dist/mcp/types/todo.types.js +4 -0
- package/dist/mcp/types/websearch.types.d.ts +30 -0
- package/dist/mcp/types/websearch.types.js +4 -0
- package/dist/mcp/utils/aceCodeSearch/filesystem.utils.d.ts +34 -0
- package/dist/mcp/utils/aceCodeSearch/filesystem.utils.js +146 -0
- package/dist/mcp/utils/aceCodeSearch/language.utils.d.ts +14 -0
- package/dist/mcp/utils/aceCodeSearch/language.utils.js +99 -0
- package/dist/mcp/utils/aceCodeSearch/search.utils.d.ts +31 -0
- package/dist/mcp/utils/aceCodeSearch/search.utils.js +136 -0
- package/dist/mcp/utils/aceCodeSearch/symbol.utils.d.ts +20 -0
- package/dist/mcp/utils/aceCodeSearch/symbol.utils.js +141 -0
- package/dist/mcp/utils/bash/security.utils.d.ts +20 -0
- package/dist/mcp/utils/bash/security.utils.js +34 -0
- package/dist/mcp/utils/filesystem/batch-operations.utils.d.ts +39 -0
- package/dist/mcp/utils/filesystem/batch-operations.utils.js +182 -0
- package/dist/mcp/utils/filesystem/code-analysis.utils.d.ts +18 -0
- package/dist/mcp/utils/filesystem/code-analysis.utils.js +165 -0
- package/dist/mcp/utils/filesystem/match-finder.utils.d.ts +16 -0
- package/dist/mcp/utils/filesystem/match-finder.utils.js +85 -0
- package/dist/mcp/utils/filesystem/similarity.utils.d.ts +22 -0
- package/dist/mcp/utils/filesystem/similarity.utils.js +75 -0
- package/dist/mcp/utils/todo/date.utils.d.ts +9 -0
- package/dist/mcp/utils/todo/date.utils.js +14 -0
- package/dist/mcp/utils/websearch/browser.utils.d.ts +8 -0
- package/dist/mcp/utils/websearch/browser.utils.js +58 -0
- package/dist/mcp/utils/websearch/text.utils.d.ts +16 -0
- package/dist/mcp/utils/websearch/text.utils.js +39 -0
- package/dist/mcp/websearch.d.ts +1 -31
- package/dist/mcp/websearch.js +21 -97
- package/dist/ui/components/ChatInput.d.ts +3 -1
- package/dist/ui/components/ChatInput.js +12 -5
- package/dist/ui/components/CommandPanel.d.ts +2 -1
- package/dist/ui/components/CommandPanel.js +18 -3
- package/dist/ui/components/MarkdownRenderer.d.ts +1 -2
- package/dist/ui/components/MarkdownRenderer.js +25 -153
- package/dist/ui/components/MessageList.js +5 -5
- package/dist/ui/components/PendingMessages.js +1 -1
- package/dist/ui/components/PendingToolCalls.d.ts +11 -0
- package/dist/ui/components/PendingToolCalls.js +35 -0
- package/dist/ui/components/SessionListScreen.js +37 -17
- package/dist/ui/components/ToolResultPreview.d.ts +1 -1
- package/dist/ui/components/ToolResultPreview.js +119 -155
- package/dist/ui/components/UsagePanel.d.ts +2 -0
- package/dist/ui/components/UsagePanel.js +360 -0
- package/dist/ui/pages/ChatScreen.d.ts +5 -0
- package/dist/ui/pages/ChatScreen.js +164 -85
- package/dist/ui/pages/ConfigScreen.js +23 -19
- package/dist/ui/pages/HeadlessModeScreen.js +2 -4
- package/dist/ui/pages/SubAgentConfigScreen.js +17 -17
- package/dist/ui/pages/SystemPromptConfigScreen.js +7 -6
- package/dist/utils/chatExporter.d.ts +9 -0
- package/dist/utils/chatExporter.js +126 -0
- package/dist/utils/commandExecutor.d.ts +3 -3
- package/dist/utils/commandExecutor.js +4 -4
- package/dist/utils/commands/export.d.ts +2 -0
- package/dist/utils/commands/export.js +12 -0
- package/dist/utils/commands/home.d.ts +2 -0
- package/dist/utils/commands/home.js +12 -0
- package/dist/utils/commands/init.js +3 -3
- package/dist/utils/commands/review.d.ts +2 -0
- package/dist/utils/commands/review.js +81 -0
- package/dist/utils/commands/role.d.ts +2 -0
- package/dist/utils/commands/role.js +37 -0
- package/dist/utils/commands/usage.d.ts +2 -0
- package/dist/utils/commands/usage.js +12 -0
- package/dist/utils/contextCompressor.js +99 -367
- package/dist/utils/fileDialog.d.ts +9 -0
- package/dist/utils/fileDialog.js +74 -0
- package/dist/utils/incrementalSnapshot.d.ts +7 -0
- package/dist/utils/incrementalSnapshot.js +35 -0
- package/dist/utils/mcpToolsManager.js +12 -12
- package/dist/utils/messageFormatter.js +89 -6
- package/dist/utils/proxyUtils.d.ts +15 -0
- package/dist/utils/proxyUtils.js +50 -0
- package/dist/utils/retryUtils.d.ts +27 -0
- package/dist/utils/retryUtils.js +114 -2
- package/dist/utils/sessionConverter.js +11 -0
- package/dist/utils/sessionManager.d.ts +7 -5
- package/dist/utils/sessionManager.js +60 -82
- package/dist/utils/terminal.js +4 -3
- package/dist/utils/toolDisplayConfig.d.ts +16 -0
- package/dist/utils/toolDisplayConfig.js +42 -0
- package/dist/utils/usageLogger.d.ts +11 -0
- package/dist/utils/usageLogger.js +99 -0
- package/package.json +3 -7
|
@@ -1,90 +1,12 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
|
-
import { EOL } from 'os';
|
|
5
4
|
import { AsyncFzf } from 'fzf';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
extensions: ['.ts', '.tsx'],
|
|
12
|
-
parser: 'typescript',
|
|
13
|
-
symbolPatterns: {
|
|
14
|
-
function: /(?:export\s+)?(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
|
|
15
|
-
class: /(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/,
|
|
16
|
-
variable: /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/,
|
|
17
|
-
import: /import\s+(?:{[^}]+}|\w+)\s+from\s+['"]([^'"]+)['"]/,
|
|
18
|
-
export: /export\s+(?:default\s+)?(?:class|function|const|let|var|interface|type|enum)\s+(\w+)/,
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
javascript: {
|
|
22
|
-
extensions: ['.js', '.jsx', '.mjs', '.cjs'],
|
|
23
|
-
parser: 'javascript',
|
|
24
|
-
symbolPatterns: {
|
|
25
|
-
function: /(?:export\s+)?(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
|
|
26
|
-
class: /(?:export\s+)?class\s+(\w+)/,
|
|
27
|
-
variable: /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/,
|
|
28
|
-
import: /import\s+(?:{[^}]+}|\w+)\s+from\s+['"]([^'"]+)['"]/,
|
|
29
|
-
export: /export\s+(?:default\s+)?(?:class|function|const|let|var)\s+(\w+)/,
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
python: {
|
|
33
|
-
extensions: ['.py', '.pyx', '.pyi'],
|
|
34
|
-
parser: 'python',
|
|
35
|
-
symbolPatterns: {
|
|
36
|
-
function: /def\s+(\w+)\s*\(/,
|
|
37
|
-
class: /class\s+(\w+)\s*[(:]/,
|
|
38
|
-
variable: /(\w+)\s*=\s*[^=]/,
|
|
39
|
-
import: /(?:from\s+[\w.]+\s+)?import\s+([\w, ]+)/,
|
|
40
|
-
export: /^(\w+)\s*=\s*/, // Python doesn't have explicit exports
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
go: {
|
|
44
|
-
extensions: ['.go'],
|
|
45
|
-
parser: 'go',
|
|
46
|
-
symbolPatterns: {
|
|
47
|
-
function: /func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/,
|
|
48
|
-
class: /type\s+(\w+)\s+struct/,
|
|
49
|
-
variable: /(?:var|const)\s+(\w+)\s+/,
|
|
50
|
-
import: /import\s+(?:"([^"]+)"|[(]([^)]+)[)])/,
|
|
51
|
-
export: /^(?:func|type|var|const)\s+([A-Z]\w+)/, // Go exports start with capital letter
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
rust: {
|
|
55
|
-
extensions: ['.rs'],
|
|
56
|
-
parser: 'rust',
|
|
57
|
-
symbolPatterns: {
|
|
58
|
-
function: /(?:pub\s+)?(?:async\s+)?fn\s+(\w+)\s*[<(]/,
|
|
59
|
-
class: /(?:pub\s+)?struct\s+(\w+)|(?:pub\s+)?enum\s+(\w+)|(?:pub\s+)?trait\s+(\w+)/,
|
|
60
|
-
variable: /(?:pub\s+)?(?:static|const)\s+(\w+)\s*:/,
|
|
61
|
-
import: /use\s+([^;]+);/,
|
|
62
|
-
export: /pub\s+(?:fn|struct|enum|trait|const|static)\s+(\w+)/,
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
java: {
|
|
66
|
-
extensions: ['.java'],
|
|
67
|
-
parser: 'java',
|
|
68
|
-
symbolPatterns: {
|
|
69
|
-
function: /(?:public|private|protected|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/,
|
|
70
|
-
class: /(?:public|private|protected)?\s*(?:abstract|final)?\s*class\s+(\w+)/,
|
|
71
|
-
variable: /(?:public|private|protected|static|final|\s)+[\w<>\[\]]+\s+(\w+)\s*[=;]/,
|
|
72
|
-
import: /import\s+([\w.]+);/,
|
|
73
|
-
export: /public\s+(?:class|interface|enum)\s+(\w+)/,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
csharp: {
|
|
77
|
-
extensions: ['.cs'],
|
|
78
|
-
parser: 'csharp',
|
|
79
|
-
symbolPatterns: {
|
|
80
|
-
function: /(?:public|private|protected|internal|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/,
|
|
81
|
-
class: /(?:public|private|protected|internal)?\s*(?:abstract|sealed|static)?\s*class\s+(\w+)/,
|
|
82
|
-
variable: /(?:public|private|protected|internal|static|readonly|\s)+[\w<>\[\]]+\s+(\w+)\s*[=;]/,
|
|
83
|
-
import: /using\s+([\w.]+);/,
|
|
84
|
-
export: /public\s+(?:class|interface|enum|struct)\s+(\w+)/,
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
};
|
|
5
|
+
// Utility functions
|
|
6
|
+
import { detectLanguage } from './utils/aceCodeSearch/language.utils.js';
|
|
7
|
+
import { loadExclusionPatterns, shouldExcludeDirectory, readFileWithCache, } from './utils/aceCodeSearch/filesystem.utils.js';
|
|
8
|
+
import { parseFileSymbols, getContext, } from './utils/aceCodeSearch/symbol.utils.js';
|
|
9
|
+
import { isCommandAvailable, parseGrepOutput, globToRegex, } from './utils/aceCodeSearch/search.utils.js';
|
|
88
10
|
export class ACECodeSearchService {
|
|
89
11
|
constructor(basePath = process.cwd()) {
|
|
90
12
|
Object.defineProperty(this, "basePath", {
|
|
@@ -141,13 +63,6 @@ export class ACECodeSearchService {
|
|
|
141
63
|
writable: true,
|
|
142
64
|
value: false
|
|
143
65
|
}); // Track if exclusions have been loaded
|
|
144
|
-
// 预编译的正则表达式缓存
|
|
145
|
-
Object.defineProperty(this, "regexCache", {
|
|
146
|
-
enumerable: true,
|
|
147
|
-
configurable: true,
|
|
148
|
-
writable: true,
|
|
149
|
-
value: new Map()
|
|
150
|
-
});
|
|
151
66
|
// 文件内容缓存(用于减少重复读取)
|
|
152
67
|
Object.defineProperty(this, "fileContentCache", {
|
|
153
68
|
enumerable: true,
|
|
@@ -155,31 +70,12 @@ export class ACECodeSearchService {
|
|
|
155
70
|
writable: true,
|
|
156
71
|
value: new Map()
|
|
157
72
|
});
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
configurable: true,
|
|
161
|
-
writable: true,
|
|
162
|
-
value: 50
|
|
163
|
-
}); // 限制缓存大小
|
|
164
|
-
// Default exclusion directories
|
|
165
|
-
Object.defineProperty(this, "DEFAULT_EXCLUDES", {
|
|
73
|
+
// 正则表达式缓存(用于 shouldExcludeDirectory)
|
|
74
|
+
Object.defineProperty(this, "regexCache", {
|
|
166
75
|
enumerable: true,
|
|
167
76
|
configurable: true,
|
|
168
77
|
writable: true,
|
|
169
|
-
value:
|
|
170
|
-
'node_modules',
|
|
171
|
-
'.git',
|
|
172
|
-
'dist',
|
|
173
|
-
'build',
|
|
174
|
-
'__pycache__',
|
|
175
|
-
'target',
|
|
176
|
-
'.next',
|
|
177
|
-
'.nuxt',
|
|
178
|
-
'coverage',
|
|
179
|
-
'out',
|
|
180
|
-
'.cache',
|
|
181
|
-
'vendor',
|
|
182
|
-
]
|
|
78
|
+
value: new Map()
|
|
183
79
|
});
|
|
184
80
|
this.basePath = path.resolve(basePath);
|
|
185
81
|
}
|
|
@@ -189,255 +85,9 @@ export class ACECodeSearchService {
|
|
|
189
85
|
async loadExclusionPatterns() {
|
|
190
86
|
if (this.excludesLoaded)
|
|
191
87
|
return;
|
|
192
|
-
|
|
193
|
-
// Load .gitignore if exists
|
|
194
|
-
const gitignorePath = path.join(this.basePath, '.gitignore');
|
|
195
|
-
try {
|
|
196
|
-
const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
|
|
197
|
-
const lines = gitignoreContent.split('\n');
|
|
198
|
-
for (const line of lines) {
|
|
199
|
-
const trimmed = line.trim();
|
|
200
|
-
// Skip empty lines and comments
|
|
201
|
-
if (trimmed && !trimmed.startsWith('#')) {
|
|
202
|
-
// Remove leading slash and trailing slash
|
|
203
|
-
const pattern = trimmed.replace(/^\//, '').replace(/\/$/, '');
|
|
204
|
-
if (pattern) {
|
|
205
|
-
patterns.push(pattern);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
// .gitignore doesn't exist or cannot be read, skip
|
|
212
|
-
}
|
|
213
|
-
// Load .snowignore if exists
|
|
214
|
-
const snowignorePath = path.join(this.basePath, '.snowignore');
|
|
215
|
-
try {
|
|
216
|
-
const snowignoreContent = await fs.readFile(snowignorePath, 'utf-8');
|
|
217
|
-
const lines = snowignoreContent.split('\n');
|
|
218
|
-
for (const line of lines) {
|
|
219
|
-
const trimmed = line.trim();
|
|
220
|
-
// Skip empty lines and comments
|
|
221
|
-
if (trimmed && !trimmed.startsWith('#')) {
|
|
222
|
-
// Remove leading slash and trailing slash
|
|
223
|
-
const pattern = trimmed.replace(/^\//, '').replace(/\/$/, '');
|
|
224
|
-
if (pattern) {
|
|
225
|
-
patterns.push(pattern);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
// .snowignore doesn't exist or cannot be read, skip
|
|
232
|
-
}
|
|
233
|
-
this.customExcludes = patterns;
|
|
88
|
+
this.customExcludes = await loadExclusionPatterns(this.basePath);
|
|
234
89
|
this.excludesLoaded = true;
|
|
235
90
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Check if a directory should be excluded based on exclusion patterns
|
|
238
|
-
*/
|
|
239
|
-
shouldExcludeDirectory(dirName, fullPath) {
|
|
240
|
-
// Check default excludes
|
|
241
|
-
if (this.DEFAULT_EXCLUDES.includes(dirName)) {
|
|
242
|
-
return true;
|
|
243
|
-
}
|
|
244
|
-
// Check hidden directories
|
|
245
|
-
if (dirName.startsWith('.')) {
|
|
246
|
-
return true;
|
|
247
|
-
}
|
|
248
|
-
// Check custom exclusion patterns
|
|
249
|
-
const relativePath = path.relative(this.basePath, fullPath);
|
|
250
|
-
for (const pattern of this.customExcludes) {
|
|
251
|
-
// Simple pattern matching: exact match or glob-style wildcards
|
|
252
|
-
if (pattern.includes('*')) {
|
|
253
|
-
// 使用缓存的正则表达式,避免重复编译
|
|
254
|
-
let regex = this.regexCache.get(pattern);
|
|
255
|
-
if (!regex) {
|
|
256
|
-
const regexPattern = pattern
|
|
257
|
-
.replace(/\./g, '\\.')
|
|
258
|
-
.replace(/\*/g, '.*');
|
|
259
|
-
regex = new RegExp(`^${regexPattern}$`);
|
|
260
|
-
this.regexCache.set(pattern, regex);
|
|
261
|
-
}
|
|
262
|
-
if (regex.test(relativePath) || regex.test(dirName)) {
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
// Exact match
|
|
268
|
-
if (relativePath === pattern ||
|
|
269
|
-
dirName === pattern ||
|
|
270
|
-
relativePath.startsWith(pattern + '/')) {
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Detect programming language from file extension
|
|
279
|
-
*/
|
|
280
|
-
detectLanguage(filePath) {
|
|
281
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
282
|
-
for (const [lang, config] of Object.entries(LANGUAGE_CONFIG)) {
|
|
283
|
-
if (config.extensions.includes(ext)) {
|
|
284
|
-
return lang;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return null;
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Read file with LRU cache to reduce repeated file system access
|
|
291
|
-
*/
|
|
292
|
-
async readFileWithCache(filePath) {
|
|
293
|
-
const stats = await fs.stat(filePath);
|
|
294
|
-
const mtime = stats.mtimeMs;
|
|
295
|
-
// Check cache
|
|
296
|
-
const cached = this.fileContentCache.get(filePath);
|
|
297
|
-
if (cached && cached.mtime === mtime) {
|
|
298
|
-
return cached.content;
|
|
299
|
-
}
|
|
300
|
-
// Read file
|
|
301
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
302
|
-
// Manage cache size (simple LRU: remove oldest if over limit)
|
|
303
|
-
if (this.fileContentCache.size >= this.FILE_CONTENT_CACHE_SIZE) {
|
|
304
|
-
const firstKey = this.fileContentCache.keys().next().value;
|
|
305
|
-
if (firstKey) {
|
|
306
|
-
this.fileContentCache.delete(firstKey);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
// Cache the content
|
|
310
|
-
this.fileContentCache.set(filePath, { content, mtime });
|
|
311
|
-
return content;
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Parse file content to extract code symbols using regex patterns
|
|
315
|
-
*/
|
|
316
|
-
async parseFileSymbols(filePath, content) {
|
|
317
|
-
const symbols = [];
|
|
318
|
-
const language = this.detectLanguage(filePath);
|
|
319
|
-
if (!language || !LANGUAGE_CONFIG[language]) {
|
|
320
|
-
return symbols;
|
|
321
|
-
}
|
|
322
|
-
const config = LANGUAGE_CONFIG[language];
|
|
323
|
-
const lines = content.split('\n');
|
|
324
|
-
// Parse each line for symbols
|
|
325
|
-
for (let i = 0; i < lines.length; i++) {
|
|
326
|
-
const line = lines[i];
|
|
327
|
-
if (!line)
|
|
328
|
-
continue;
|
|
329
|
-
const lineNumber = i + 1;
|
|
330
|
-
// Extract functions
|
|
331
|
-
if (config.symbolPatterns.function) {
|
|
332
|
-
const match = line.match(config.symbolPatterns.function);
|
|
333
|
-
if (match) {
|
|
334
|
-
const name = match[1] || match[2] || match[3];
|
|
335
|
-
if (name) {
|
|
336
|
-
// Get function signature (current line + next few lines)
|
|
337
|
-
const contextLines = lines.slice(i, Math.min(i + 3, lines.length));
|
|
338
|
-
const signature = contextLines.join('\n').trim();
|
|
339
|
-
symbols.push({
|
|
340
|
-
name,
|
|
341
|
-
type: 'function',
|
|
342
|
-
filePath: path.relative(this.basePath, filePath),
|
|
343
|
-
line: lineNumber,
|
|
344
|
-
column: line.indexOf(name) + 1,
|
|
345
|
-
signature,
|
|
346
|
-
language,
|
|
347
|
-
context: this.getContext(lines, i, 2),
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
// Extract classes
|
|
353
|
-
if (config.symbolPatterns.class) {
|
|
354
|
-
const match = line.match(config.symbolPatterns.class);
|
|
355
|
-
if (match) {
|
|
356
|
-
const name = match[1] || match[2] || match[3];
|
|
357
|
-
if (name) {
|
|
358
|
-
symbols.push({
|
|
359
|
-
name,
|
|
360
|
-
type: 'class',
|
|
361
|
-
filePath: path.relative(this.basePath, filePath),
|
|
362
|
-
line: lineNumber,
|
|
363
|
-
column: line.indexOf(name) + 1,
|
|
364
|
-
signature: line.trim(),
|
|
365
|
-
language,
|
|
366
|
-
context: this.getContext(lines, i, 2),
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
// Extract variables
|
|
372
|
-
if (config.symbolPatterns.variable) {
|
|
373
|
-
const match = line.match(config.symbolPatterns.variable);
|
|
374
|
-
if (match) {
|
|
375
|
-
const name = match[1];
|
|
376
|
-
if (name) {
|
|
377
|
-
symbols.push({
|
|
378
|
-
name,
|
|
379
|
-
type: 'variable',
|
|
380
|
-
filePath: path.relative(this.basePath, filePath),
|
|
381
|
-
line: lineNumber,
|
|
382
|
-
column: line.indexOf(name) + 1,
|
|
383
|
-
signature: line.trim(),
|
|
384
|
-
language,
|
|
385
|
-
context: this.getContext(lines, i, 1),
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
// Extract imports
|
|
391
|
-
if (config.symbolPatterns.import) {
|
|
392
|
-
const match = line.match(config.symbolPatterns.import);
|
|
393
|
-
if (match) {
|
|
394
|
-
const name = match[1] || match[2];
|
|
395
|
-
if (name) {
|
|
396
|
-
symbols.push({
|
|
397
|
-
name,
|
|
398
|
-
type: 'import',
|
|
399
|
-
filePath: path.relative(this.basePath, filePath),
|
|
400
|
-
line: lineNumber,
|
|
401
|
-
column: line.indexOf(name) + 1,
|
|
402
|
-
signature: line.trim(),
|
|
403
|
-
language,
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
// Extract exports
|
|
409
|
-
if (config.symbolPatterns.export) {
|
|
410
|
-
const match = line.match(config.symbolPatterns.export);
|
|
411
|
-
if (match) {
|
|
412
|
-
const name = match[1];
|
|
413
|
-
if (name) {
|
|
414
|
-
symbols.push({
|
|
415
|
-
name,
|
|
416
|
-
type: 'export',
|
|
417
|
-
filePath: path.relative(this.basePath, filePath),
|
|
418
|
-
line: lineNumber,
|
|
419
|
-
column: line.indexOf(name) + 1,
|
|
420
|
-
signature: line.trim(),
|
|
421
|
-
language,
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return symbols;
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Get context lines around a specific line
|
|
431
|
-
*/
|
|
432
|
-
getContext(lines, lineIndex, contextSize) {
|
|
433
|
-
const start = Math.max(0, lineIndex - contextSize);
|
|
434
|
-
const end = Math.min(lines.length, lineIndex + contextSize + 1);
|
|
435
|
-
return lines
|
|
436
|
-
.slice(start, end)
|
|
437
|
-
.filter(l => l !== undefined)
|
|
438
|
-
.join('\n')
|
|
439
|
-
.trim();
|
|
440
|
-
}
|
|
441
91
|
/**
|
|
442
92
|
* Check if a directory is a Git repository
|
|
443
93
|
*/
|
|
@@ -451,71 +101,6 @@ export class ACECodeSearchService {
|
|
|
451
101
|
return false;
|
|
452
102
|
}
|
|
453
103
|
}
|
|
454
|
-
/**
|
|
455
|
-
* Check if a command is available in the system PATH
|
|
456
|
-
*/
|
|
457
|
-
isCommandAvailable(command) {
|
|
458
|
-
return new Promise(resolve => {
|
|
459
|
-
try {
|
|
460
|
-
let child;
|
|
461
|
-
if (process.platform === 'win32') {
|
|
462
|
-
// Windows: where is an executable, no shell needed
|
|
463
|
-
child = spawn('where', [command], {
|
|
464
|
-
stdio: 'ignore',
|
|
465
|
-
windowsHide: true,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
// Unix/Linux: Use 'which' command instead of 'command -v'
|
|
470
|
-
// 'which' is an external executable, not a shell builtin
|
|
471
|
-
child = spawn('which', [command], {
|
|
472
|
-
stdio: 'ignore',
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
child.on('close', code => resolve(code === 0));
|
|
476
|
-
child.on('error', () => resolve(false));
|
|
477
|
-
}
|
|
478
|
-
catch {
|
|
479
|
-
resolve(false);
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Parse grep output (format: filePath:lineNumber:lineContent)
|
|
485
|
-
*/
|
|
486
|
-
parseGrepOutput(output, basePath) {
|
|
487
|
-
const results = [];
|
|
488
|
-
if (!output)
|
|
489
|
-
return results;
|
|
490
|
-
const lines = output.split(EOL);
|
|
491
|
-
for (const line of lines) {
|
|
492
|
-
if (!line.trim())
|
|
493
|
-
continue;
|
|
494
|
-
// Find first and second colon indices
|
|
495
|
-
const firstColonIndex = line.indexOf(':');
|
|
496
|
-
if (firstColonIndex === -1)
|
|
497
|
-
continue;
|
|
498
|
-
const secondColonIndex = line.indexOf(':', firstColonIndex + 1);
|
|
499
|
-
if (secondColonIndex === -1)
|
|
500
|
-
continue;
|
|
501
|
-
// Extract parts
|
|
502
|
-
const filePathRaw = line.substring(0, firstColonIndex);
|
|
503
|
-
const lineNumberStr = line.substring(firstColonIndex + 1, secondColonIndex);
|
|
504
|
-
const lineContent = line.substring(secondColonIndex + 1);
|
|
505
|
-
const lineNumber = parseInt(lineNumberStr, 10);
|
|
506
|
-
if (isNaN(lineNumber))
|
|
507
|
-
continue;
|
|
508
|
-
const absoluteFilePath = path.resolve(basePath, filePathRaw);
|
|
509
|
-
const relativeFilePath = path.relative(basePath, absoluteFilePath);
|
|
510
|
-
results.push({
|
|
511
|
-
filePath: relativeFilePath || path.basename(absoluteFilePath),
|
|
512
|
-
line: lineNumber,
|
|
513
|
-
column: 1, // grep doesn't provide column info, default to 1
|
|
514
|
-
content: lineContent.trim(),
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
return results;
|
|
518
|
-
}
|
|
519
104
|
/**
|
|
520
105
|
* Build or refresh the code symbol index with incremental updates
|
|
521
106
|
*/
|
|
@@ -544,13 +129,13 @@ export class ACECodeSearchService {
|
|
|
544
129
|
const fullPath = path.join(dirPath, entry.name);
|
|
545
130
|
if (entry.isDirectory()) {
|
|
546
131
|
// Use configurable exclusion check
|
|
547
|
-
if (
|
|
132
|
+
if (shouldExcludeDirectory(entry.name, fullPath, this.basePath, this.customExcludes, this.regexCache)) {
|
|
548
133
|
continue;
|
|
549
134
|
}
|
|
550
135
|
await searchInDirectory(fullPath);
|
|
551
136
|
}
|
|
552
137
|
else if (entry.isFile()) {
|
|
553
|
-
const language =
|
|
138
|
+
const language = detectLanguage(fullPath);
|
|
554
139
|
if (language) {
|
|
555
140
|
// Check if file needs to be re-indexed
|
|
556
141
|
try {
|
|
@@ -587,8 +172,8 @@ export class ACECodeSearchService {
|
|
|
587
172
|
for (const batch of batches) {
|
|
588
173
|
await Promise.all(batch.map(async (fullPath) => {
|
|
589
174
|
try {
|
|
590
|
-
const content = await
|
|
591
|
-
const symbols = await
|
|
175
|
+
const content = await readFileWithCache(fullPath, this.fileContentCache);
|
|
176
|
+
const symbols = await parseFileSymbols(fullPath, content, this.basePath);
|
|
592
177
|
if (symbols.length > 0) {
|
|
593
178
|
this.indexCache.set(fullPath, symbols);
|
|
594
179
|
}
|
|
@@ -804,7 +389,7 @@ export class ACECodeSearchService {
|
|
|
804
389
|
await searchInDirectory(fullPath);
|
|
805
390
|
}
|
|
806
391
|
else if (entry.isFile()) {
|
|
807
|
-
const language =
|
|
392
|
+
const language = detectLanguage(fullPath);
|
|
808
393
|
if (language) {
|
|
809
394
|
try {
|
|
810
395
|
const content = await fs.readFile(fullPath, 'utf-8');
|
|
@@ -836,7 +421,7 @@ export class ACECodeSearchService {
|
|
|
836
421
|
filePath: path.relative(this.basePath, fullPath),
|
|
837
422
|
line: i + 1,
|
|
838
423
|
column: match.index + 1,
|
|
839
|
-
context:
|
|
424
|
+
context: getContext(lines, i, 1),
|
|
840
425
|
referenceType,
|
|
841
426
|
});
|
|
842
427
|
}
|
|
@@ -914,9 +499,9 @@ export class ACECodeSearchService {
|
|
|
914
499
|
});
|
|
915
500
|
child.on('close', code => {
|
|
916
501
|
const stdoutData = Buffer.concat(stdoutChunks).toString('utf8');
|
|
917
|
-
const stderrData = Buffer.concat(stderrChunks).toString('utf8');
|
|
502
|
+
const stderrData = Buffer.concat(stderrChunks).toString('utf8').trim();
|
|
918
503
|
if (code === 0) {
|
|
919
|
-
const results =
|
|
504
|
+
const results = parseGrepOutput(stdoutData, this.basePath);
|
|
920
505
|
resolve(results.slice(0, maxResults));
|
|
921
506
|
}
|
|
922
507
|
else if (code === 1) {
|
|
@@ -934,7 +519,7 @@ export class ACECodeSearchService {
|
|
|
934
519
|
*/
|
|
935
520
|
async systemGrepSearch(pattern, fileGlob, maxResults = 100) {
|
|
936
521
|
// Prefer ripgrep (rg) over grep if available
|
|
937
|
-
const grepCommand = (await
|
|
522
|
+
const grepCommand = (await isCommandAvailable('rg')) ? 'rg' : 'grep';
|
|
938
523
|
const isRipgrep = grepCommand === 'rg';
|
|
939
524
|
return new Promise((resolve, reject) => {
|
|
940
525
|
const args = isRipgrep
|
|
@@ -989,7 +574,7 @@ export class ACECodeSearchService {
|
|
|
989
574
|
const stdoutData = Buffer.concat(stdoutChunks).toString('utf8');
|
|
990
575
|
const stderrData = Buffer.concat(stderrChunks).toString('utf8').trim();
|
|
991
576
|
if (code === 0) {
|
|
992
|
-
const results =
|
|
577
|
+
const results = parseGrepOutput(stdoutData, this.basePath);
|
|
993
578
|
resolve(results.slice(0, maxResults));
|
|
994
579
|
}
|
|
995
580
|
else if (code === 1) {
|
|
@@ -1027,7 +612,7 @@ export class ACECodeSearchService {
|
|
|
1027
612
|
throw new Error(`Invalid regex pattern: ${pattern}`);
|
|
1028
613
|
}
|
|
1029
614
|
// Parse glob pattern if provided
|
|
1030
|
-
const globRegex = fileGlob ?
|
|
615
|
+
const globRegex = fileGlob ? globToRegex(fileGlob) : null;
|
|
1031
616
|
// Search recursively
|
|
1032
617
|
const searchInDirectory = async (dirPath) => {
|
|
1033
618
|
if (results.length >= maxResults)
|
|
@@ -1143,7 +728,7 @@ export class ACECodeSearchService {
|
|
|
1143
728
|
// Strategy 1: Try git grep first
|
|
1144
729
|
if (await this.isGitRepository()) {
|
|
1145
730
|
try {
|
|
1146
|
-
const gitAvailable = await
|
|
731
|
+
const gitAvailable = await isCommandAvailable('git');
|
|
1147
732
|
if (gitAvailable) {
|
|
1148
733
|
const results = await this.gitGrepSearch(pattern, fileGlob, maxResults);
|
|
1149
734
|
if (results.length > 0 || !isRegex) {
|
|
@@ -1160,8 +745,7 @@ export class ACECodeSearchService {
|
|
|
1160
745
|
}
|
|
1161
746
|
// Strategy 2: Try system grep/ripgrep
|
|
1162
747
|
try {
|
|
1163
|
-
const grepAvailable = (await
|
|
1164
|
-
(await this.isCommandAvailable('grep'));
|
|
748
|
+
const grepAvailable = (await isCommandAvailable('rg')) || (await isCommandAvailable('grep'));
|
|
1165
749
|
if (grepAvailable) {
|
|
1166
750
|
const results = await this.systemGrepSearch(pattern, fileGlob, maxResults);
|
|
1167
751
|
return await this.sortResultsByRecency(results);
|
|
@@ -1217,26 +801,6 @@ export class ACECodeSearchService {
|
|
|
1217
801
|
return 0;
|
|
1218
802
|
});
|
|
1219
803
|
}
|
|
1220
|
-
/**
|
|
1221
|
-
* Convert glob pattern to RegExp
|
|
1222
|
-
* Supports: *, **, ?, [abc], {js,ts}
|
|
1223
|
-
*/
|
|
1224
|
-
globToRegex(glob) {
|
|
1225
|
-
// Escape special regex characters except glob wildcards
|
|
1226
|
-
let pattern = glob
|
|
1227
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape regex special chars
|
|
1228
|
-
.replace(/\*\*/g, '<<<DOUBLESTAR>>>') // Temporarily replace **
|
|
1229
|
-
.replace(/\*/g, '[^/]*') // * matches anything except /
|
|
1230
|
-
.replace(/<<<DOUBLESTAR>>>/g, '.*') // ** matches everything
|
|
1231
|
-
.replace(/\?/g, '[^/]'); // ? matches single char except /
|
|
1232
|
-
// Handle {js,ts} alternatives
|
|
1233
|
-
pattern = pattern.replace(/\\\{([^}]+)\\\}/g, (_, alternatives) => {
|
|
1234
|
-
return '(' + alternatives.split(',').join('|') + ')';
|
|
1235
|
-
});
|
|
1236
|
-
// Handle [abc] character classes (already valid regex)
|
|
1237
|
-
pattern = pattern.replace(/\\\[([^\]]+)\\\]/g, '[$1]');
|
|
1238
|
-
return new RegExp(pattern, 'i');
|
|
1239
|
-
}
|
|
1240
804
|
/**
|
|
1241
805
|
* Get code outline for a file (all symbols in the file)
|
|
1242
806
|
*/
|
|
@@ -1244,7 +808,7 @@ export class ACECodeSearchService {
|
|
|
1244
808
|
const fullPath = path.resolve(this.basePath, filePath);
|
|
1245
809
|
try {
|
|
1246
810
|
const content = await fs.readFile(fullPath, 'utf-8');
|
|
1247
|
-
return await
|
|
811
|
+
return await parseFileSymbols(fullPath, content, this.basePath);
|
|
1248
812
|
}
|
|
1249
813
|
catch (error) {
|
|
1250
814
|
throw new Error(`Failed to get outline for ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -1323,7 +887,7 @@ export const aceCodeSearchService = new ACECodeSearchService();
|
|
|
1323
887
|
// MCP Tool definitions for integration
|
|
1324
888
|
export const mcpTools = [
|
|
1325
889
|
{
|
|
1326
|
-
name: '
|
|
890
|
+
name: 'ace-search_symbols',
|
|
1327
891
|
description: 'ACE Code Search: Intelligent symbol search across the codebase. Finds functions, classes, variables, and other code symbols with fuzzy matching. Supports multiple programming languages (TypeScript, JavaScript, Python, Go, Rust, Java, C#). Returns precise file locations with line numbers and context.',
|
|
1328
892
|
inputSchema: {
|
|
1329
893
|
type: 'object',
|
|
@@ -1371,7 +935,7 @@ export const mcpTools = [
|
|
|
1371
935
|
},
|
|
1372
936
|
},
|
|
1373
937
|
{
|
|
1374
|
-
name: '
|
|
938
|
+
name: 'ace-find_definition',
|
|
1375
939
|
description: 'ACE Code Search: Find the definition of a symbol (Go to Definition). Locates where a function, class, or variable is defined in the codebase. Returns precise location with full signature and context.',
|
|
1376
940
|
inputSchema: {
|
|
1377
941
|
type: 'object',
|
|
@@ -1389,7 +953,7 @@ export const mcpTools = [
|
|
|
1389
953
|
},
|
|
1390
954
|
},
|
|
1391
955
|
{
|
|
1392
|
-
name: '
|
|
956
|
+
name: 'ace-find_references',
|
|
1393
957
|
description: 'ACE Code Search: Find all references to a symbol (Find All References). Shows where a function, class, or variable is used throughout the codebase. Categorizes references as definition, usage, import, or type reference.',
|
|
1394
958
|
inputSchema: {
|
|
1395
959
|
type: 'object',
|
|
@@ -1408,7 +972,7 @@ export const mcpTools = [
|
|
|
1408
972
|
},
|
|
1409
973
|
},
|
|
1410
974
|
{
|
|
1411
|
-
name: '
|
|
975
|
+
name: 'ace-semantic_search',
|
|
1412
976
|
description: 'ACE Code Search: Advanced semantic search with context understanding. Searches for symbols with intelligent filtering by search type (definition, usage, implementation, all). Combines symbol search with cross-reference analysis.',
|
|
1413
977
|
inputSchema: {
|
|
1414
978
|
type: 'object',
|
|
@@ -1446,7 +1010,7 @@ export const mcpTools = [
|
|
|
1446
1010
|
},
|
|
1447
1011
|
},
|
|
1448
1012
|
{
|
|
1449
|
-
name: '
|
|
1013
|
+
name: 'ace-file_outline',
|
|
1450
1014
|
description: "ACE Code Search: Get complete code outline for a file. Shows all functions, classes, variables, and other symbols defined in the file with their locations. Similar to VS Code's outline view.",
|
|
1451
1015
|
inputSchema: {
|
|
1452
1016
|
type: 'object',
|
|
@@ -1460,7 +1024,7 @@ export const mcpTools = [
|
|
|
1460
1024
|
},
|
|
1461
1025
|
},
|
|
1462
1026
|
{
|
|
1463
|
-
name: '
|
|
1027
|
+
name: 'ace-text_search',
|
|
1464
1028
|
description: 'ACE Code Search: Fast text search across the entire codebase using Node.js built-in features (no external dependencies required). Search for exact patterns or regex across all files. Useful for finding strings, comments, TODOs, or any text patterns. Supports glob filtering.',
|
|
1465
1029
|
inputSchema: {
|
|
1466
1030
|
type: 'object',
|
|
@@ -1488,7 +1052,7 @@ export const mcpTools = [
|
|
|
1488
1052
|
},
|
|
1489
1053
|
},
|
|
1490
1054
|
{
|
|
1491
|
-
name: '
|
|
1055
|
+
name: 'ace-index_stats',
|
|
1492
1056
|
description: 'ACE Code Search: Get statistics about the code index. Shows number of indexed files, symbols, language breakdown, and cache status. Useful for understanding search coverage.',
|
|
1493
1057
|
inputSchema: {
|
|
1494
1058
|
type: 'object',
|
|
@@ -1496,7 +1060,7 @@ export const mcpTools = [
|
|
|
1496
1060
|
},
|
|
1497
1061
|
},
|
|
1498
1062
|
{
|
|
1499
|
-
name: '
|
|
1063
|
+
name: 'ace-clear_cache',
|
|
1500
1064
|
description: 'ACE Code Search: Clear the symbol index cache and force a full re-index on next search. Use when codebase has changed significantly or search results seem stale.',
|
|
1501
1065
|
inputSchema: {
|
|
1502
1066
|
type: 'object',
|