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.
Files changed (127) hide show
  1. package/dist/agents/compactAgent.js +7 -3
  2. package/dist/agents/reviewAgent.d.ts +50 -0
  3. package/dist/agents/reviewAgent.js +264 -0
  4. package/dist/agents/summaryAgent.d.ts +34 -8
  5. package/dist/agents/summaryAgent.js +167 -164
  6. package/dist/api/anthropic.d.ts +1 -0
  7. package/dist/api/anthropic.js +118 -78
  8. package/dist/api/chat.d.ts +2 -1
  9. package/dist/api/chat.js +82 -52
  10. package/dist/api/gemini.d.ts +1 -0
  11. package/dist/api/gemini.js +110 -64
  12. package/dist/api/responses.d.ts +10 -1
  13. package/dist/api/responses.js +127 -79
  14. package/dist/api/systemPrompt.d.ts +1 -1
  15. package/dist/api/systemPrompt.js +36 -7
  16. package/dist/api/types.d.ts +8 -0
  17. package/dist/app.js +15 -2
  18. package/dist/hooks/useCommandHandler.d.ts +1 -0
  19. package/dist/hooks/useCommandHandler.js +102 -1
  20. package/dist/hooks/useCommandPanel.d.ts +2 -1
  21. package/dist/hooks/useCommandPanel.js +19 -1
  22. package/dist/hooks/useConversation.d.ts +4 -1
  23. package/dist/hooks/useConversation.js +91 -29
  24. package/dist/hooks/useKeyboardInput.js +19 -0
  25. package/dist/hooks/useSnapshotState.d.ts +2 -0
  26. package/dist/hooks/useTerminalFocus.js +13 -3
  27. package/dist/mcp/aceCodeSearch.d.ts +2 -76
  28. package/dist/mcp/aceCodeSearch.js +31 -467
  29. package/dist/mcp/bash.d.ts +1 -8
  30. package/dist/mcp/bash.js +20 -40
  31. package/dist/mcp/filesystem.d.ts +131 -111
  32. package/dist/mcp/filesystem.js +212 -375
  33. package/dist/mcp/ideDiagnostics.js +2 -4
  34. package/dist/mcp/todo.d.ts +1 -17
  35. package/dist/mcp/todo.js +11 -15
  36. package/dist/mcp/types/aceCodeSearch.types.d.ts +92 -0
  37. package/dist/mcp/types/aceCodeSearch.types.js +4 -0
  38. package/dist/mcp/types/bash.types.d.ts +13 -0
  39. package/dist/mcp/types/bash.types.js +4 -0
  40. package/dist/mcp/types/filesystem.types.d.ts +135 -0
  41. package/dist/mcp/types/filesystem.types.js +4 -0
  42. package/dist/mcp/types/todo.types.d.ts +27 -0
  43. package/dist/mcp/types/todo.types.js +4 -0
  44. package/dist/mcp/types/websearch.types.d.ts +30 -0
  45. package/dist/mcp/types/websearch.types.js +4 -0
  46. package/dist/mcp/utils/aceCodeSearch/filesystem.utils.d.ts +34 -0
  47. package/dist/mcp/utils/aceCodeSearch/filesystem.utils.js +146 -0
  48. package/dist/mcp/utils/aceCodeSearch/language.utils.d.ts +14 -0
  49. package/dist/mcp/utils/aceCodeSearch/language.utils.js +99 -0
  50. package/dist/mcp/utils/aceCodeSearch/search.utils.d.ts +31 -0
  51. package/dist/mcp/utils/aceCodeSearch/search.utils.js +136 -0
  52. package/dist/mcp/utils/aceCodeSearch/symbol.utils.d.ts +20 -0
  53. package/dist/mcp/utils/aceCodeSearch/symbol.utils.js +141 -0
  54. package/dist/mcp/utils/bash/security.utils.d.ts +20 -0
  55. package/dist/mcp/utils/bash/security.utils.js +34 -0
  56. package/dist/mcp/utils/filesystem/batch-operations.utils.d.ts +39 -0
  57. package/dist/mcp/utils/filesystem/batch-operations.utils.js +182 -0
  58. package/dist/mcp/utils/filesystem/code-analysis.utils.d.ts +18 -0
  59. package/dist/mcp/utils/filesystem/code-analysis.utils.js +165 -0
  60. package/dist/mcp/utils/filesystem/match-finder.utils.d.ts +16 -0
  61. package/dist/mcp/utils/filesystem/match-finder.utils.js +85 -0
  62. package/dist/mcp/utils/filesystem/similarity.utils.d.ts +22 -0
  63. package/dist/mcp/utils/filesystem/similarity.utils.js +75 -0
  64. package/dist/mcp/utils/todo/date.utils.d.ts +9 -0
  65. package/dist/mcp/utils/todo/date.utils.js +14 -0
  66. package/dist/mcp/utils/websearch/browser.utils.d.ts +8 -0
  67. package/dist/mcp/utils/websearch/browser.utils.js +58 -0
  68. package/dist/mcp/utils/websearch/text.utils.d.ts +16 -0
  69. package/dist/mcp/utils/websearch/text.utils.js +39 -0
  70. package/dist/mcp/websearch.d.ts +1 -31
  71. package/dist/mcp/websearch.js +21 -97
  72. package/dist/ui/components/ChatInput.d.ts +3 -1
  73. package/dist/ui/components/ChatInput.js +12 -5
  74. package/dist/ui/components/CommandPanel.d.ts +2 -1
  75. package/dist/ui/components/CommandPanel.js +18 -3
  76. package/dist/ui/components/MarkdownRenderer.d.ts +1 -2
  77. package/dist/ui/components/MarkdownRenderer.js +25 -153
  78. package/dist/ui/components/MessageList.js +5 -5
  79. package/dist/ui/components/PendingMessages.js +1 -1
  80. package/dist/ui/components/PendingToolCalls.d.ts +11 -0
  81. package/dist/ui/components/PendingToolCalls.js +35 -0
  82. package/dist/ui/components/SessionListScreen.js +37 -17
  83. package/dist/ui/components/ToolResultPreview.d.ts +1 -1
  84. package/dist/ui/components/ToolResultPreview.js +119 -155
  85. package/dist/ui/components/UsagePanel.d.ts +2 -0
  86. package/dist/ui/components/UsagePanel.js +360 -0
  87. package/dist/ui/pages/ChatScreen.d.ts +5 -0
  88. package/dist/ui/pages/ChatScreen.js +164 -85
  89. package/dist/ui/pages/ConfigScreen.js +23 -19
  90. package/dist/ui/pages/HeadlessModeScreen.js +2 -4
  91. package/dist/ui/pages/SubAgentConfigScreen.js +17 -17
  92. package/dist/ui/pages/SystemPromptConfigScreen.js +7 -6
  93. package/dist/utils/chatExporter.d.ts +9 -0
  94. package/dist/utils/chatExporter.js +126 -0
  95. package/dist/utils/commandExecutor.d.ts +3 -3
  96. package/dist/utils/commandExecutor.js +4 -4
  97. package/dist/utils/commands/export.d.ts +2 -0
  98. package/dist/utils/commands/export.js +12 -0
  99. package/dist/utils/commands/home.d.ts +2 -0
  100. package/dist/utils/commands/home.js +12 -0
  101. package/dist/utils/commands/init.js +3 -3
  102. package/dist/utils/commands/review.d.ts +2 -0
  103. package/dist/utils/commands/review.js +81 -0
  104. package/dist/utils/commands/role.d.ts +2 -0
  105. package/dist/utils/commands/role.js +37 -0
  106. package/dist/utils/commands/usage.d.ts +2 -0
  107. package/dist/utils/commands/usage.js +12 -0
  108. package/dist/utils/contextCompressor.js +99 -367
  109. package/dist/utils/fileDialog.d.ts +9 -0
  110. package/dist/utils/fileDialog.js +74 -0
  111. package/dist/utils/incrementalSnapshot.d.ts +7 -0
  112. package/dist/utils/incrementalSnapshot.js +35 -0
  113. package/dist/utils/mcpToolsManager.js +12 -12
  114. package/dist/utils/messageFormatter.js +89 -6
  115. package/dist/utils/proxyUtils.d.ts +15 -0
  116. package/dist/utils/proxyUtils.js +50 -0
  117. package/dist/utils/retryUtils.d.ts +27 -0
  118. package/dist/utils/retryUtils.js +114 -2
  119. package/dist/utils/sessionConverter.js +11 -0
  120. package/dist/utils/sessionManager.d.ts +7 -5
  121. package/dist/utils/sessionManager.js +60 -82
  122. package/dist/utils/terminal.js +4 -3
  123. package/dist/utils/toolDisplayConfig.d.ts +16 -0
  124. package/dist/utils/toolDisplayConfig.js +42 -0
  125. package/dist/utils/usageLogger.d.ts +11 -0
  126. package/dist/utils/usageLogger.js +99 -0
  127. 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
- * Language-specific parsers configuration
8
- */
9
- const LANGUAGE_CONFIG = {
10
- typescript: {
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
- Object.defineProperty(this, "FILE_CONTENT_CACHE_SIZE", {
159
- enumerable: true,
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
- const patterns = [];
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 (this.shouldExcludeDirectory(entry.name, fullPath)) {
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 = this.detectLanguage(fullPath);
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 this.readFileWithCache(fullPath);
591
- const symbols = await this.parseFileSymbols(fullPath, content);
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 = this.detectLanguage(fullPath);
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: this.getContext(lines, i, 1),
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 = this.parseGrepOutput(stdoutData, this.basePath);
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 this.isCommandAvailable('rg')) ? 'rg' : 'grep';
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 = this.parseGrepOutput(stdoutData, this.basePath);
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 ? this.globToRegex(fileGlob) : null;
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 this.isCommandAvailable('git');
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 this.isCommandAvailable('rg')) ||
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 this.parseFileSymbols(fullPath, content);
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: 'ace_search_symbols',
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: 'ace_find_definition',
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: 'ace_find_references',
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: 'ace_semantic_search',
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: 'ace_file_outline',
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: 'ace_text_search',
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: 'ace_index_stats',
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: 'ace_clear_cache',
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',