snow-ai 0.3.6 → 0.3.7

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 (101) hide show
  1. package/dist/agents/reviewAgent.d.ts +50 -0
  2. package/dist/agents/reviewAgent.js +264 -0
  3. package/dist/api/anthropic.js +104 -71
  4. package/dist/api/chat.d.ts +1 -1
  5. package/dist/api/chat.js +60 -41
  6. package/dist/api/gemini.js +97 -57
  7. package/dist/api/responses.d.ts +9 -1
  8. package/dist/api/responses.js +110 -70
  9. package/dist/api/systemPrompt.d.ts +1 -1
  10. package/dist/api/systemPrompt.js +36 -7
  11. package/dist/api/types.d.ts +8 -0
  12. package/dist/hooks/useCommandHandler.d.ts +1 -0
  13. package/dist/hooks/useCommandHandler.js +44 -1
  14. package/dist/hooks/useCommandPanel.js +13 -0
  15. package/dist/hooks/useConversation.d.ts +4 -1
  16. package/dist/hooks/useConversation.js +48 -6
  17. package/dist/hooks/useKeyboardInput.js +19 -0
  18. package/dist/hooks/useTerminalFocus.js +13 -3
  19. package/dist/mcp/aceCodeSearch.d.ts +2 -76
  20. package/dist/mcp/aceCodeSearch.js +31 -467
  21. package/dist/mcp/bash.d.ts +1 -8
  22. package/dist/mcp/bash.js +20 -40
  23. package/dist/mcp/filesystem.d.ts +3 -68
  24. package/dist/mcp/filesystem.js +32 -348
  25. package/dist/mcp/ideDiagnostics.js +2 -4
  26. package/dist/mcp/todo.d.ts +1 -17
  27. package/dist/mcp/todo.js +11 -15
  28. package/dist/mcp/types/aceCodeSearch.types.d.ts +92 -0
  29. package/dist/mcp/types/aceCodeSearch.types.js +4 -0
  30. package/dist/mcp/types/bash.types.d.ts +13 -0
  31. package/dist/mcp/types/bash.types.js +4 -0
  32. package/dist/mcp/types/filesystem.types.d.ts +44 -0
  33. package/dist/mcp/types/filesystem.types.js +4 -0
  34. package/dist/mcp/types/todo.types.d.ts +27 -0
  35. package/dist/mcp/types/todo.types.js +4 -0
  36. package/dist/mcp/types/websearch.types.d.ts +30 -0
  37. package/dist/mcp/types/websearch.types.js +4 -0
  38. package/dist/mcp/utils/aceCodeSearch/filesystem.utils.d.ts +34 -0
  39. package/dist/mcp/utils/aceCodeSearch/filesystem.utils.js +146 -0
  40. package/dist/mcp/utils/aceCodeSearch/language.utils.d.ts +14 -0
  41. package/dist/mcp/utils/aceCodeSearch/language.utils.js +99 -0
  42. package/dist/mcp/utils/aceCodeSearch/search.utils.d.ts +31 -0
  43. package/dist/mcp/utils/aceCodeSearch/search.utils.js +136 -0
  44. package/dist/mcp/utils/aceCodeSearch/symbol.utils.d.ts +20 -0
  45. package/dist/mcp/utils/aceCodeSearch/symbol.utils.js +141 -0
  46. package/dist/mcp/utils/bash/security.utils.d.ts +20 -0
  47. package/dist/mcp/utils/bash/security.utils.js +34 -0
  48. package/dist/mcp/utils/filesystem/code-analysis.utils.d.ts +18 -0
  49. package/dist/mcp/utils/filesystem/code-analysis.utils.js +165 -0
  50. package/dist/mcp/utils/filesystem/match-finder.utils.d.ts +16 -0
  51. package/dist/mcp/utils/filesystem/match-finder.utils.js +85 -0
  52. package/dist/mcp/utils/filesystem/similarity.utils.d.ts +22 -0
  53. package/dist/mcp/utils/filesystem/similarity.utils.js +75 -0
  54. package/dist/mcp/utils/todo/date.utils.d.ts +9 -0
  55. package/dist/mcp/utils/todo/date.utils.js +14 -0
  56. package/dist/mcp/utils/websearch/browser.utils.d.ts +8 -0
  57. package/dist/mcp/utils/websearch/browser.utils.js +58 -0
  58. package/dist/mcp/utils/websearch/text.utils.d.ts +16 -0
  59. package/dist/mcp/utils/websearch/text.utils.js +39 -0
  60. package/dist/mcp/websearch.d.ts +1 -31
  61. package/dist/mcp/websearch.js +21 -97
  62. package/dist/ui/components/ChatInput.d.ts +2 -1
  63. package/dist/ui/components/ChatInput.js +10 -3
  64. package/dist/ui/components/MarkdownRenderer.d.ts +1 -2
  65. package/dist/ui/components/MarkdownRenderer.js +16 -153
  66. package/dist/ui/components/MessageList.js +4 -4
  67. package/dist/ui/components/SessionListScreen.js +37 -17
  68. package/dist/ui/components/ToolResultPreview.js +6 -6
  69. package/dist/ui/components/UsagePanel.d.ts +2 -0
  70. package/dist/ui/components/UsagePanel.js +360 -0
  71. package/dist/ui/pages/ChatScreen.d.ts +4 -0
  72. package/dist/ui/pages/ChatScreen.js +70 -30
  73. package/dist/ui/pages/ConfigScreen.js +23 -19
  74. package/dist/ui/pages/HeadlessModeScreen.js +2 -4
  75. package/dist/ui/pages/SubAgentConfigScreen.js +17 -17
  76. package/dist/ui/pages/SystemPromptConfigScreen.js +7 -6
  77. package/dist/utils/commandExecutor.d.ts +3 -3
  78. package/dist/utils/commandExecutor.js +4 -4
  79. package/dist/utils/commands/home.d.ts +2 -0
  80. package/dist/utils/commands/home.js +12 -0
  81. package/dist/utils/commands/review.d.ts +2 -0
  82. package/dist/utils/commands/review.js +81 -0
  83. package/dist/utils/commands/role.d.ts +2 -0
  84. package/dist/utils/commands/role.js +37 -0
  85. package/dist/utils/commands/usage.d.ts +2 -0
  86. package/dist/utils/commands/usage.js +12 -0
  87. package/dist/utils/contextCompressor.js +99 -367
  88. package/dist/utils/fileUtils.js +3 -3
  89. package/dist/utils/mcpToolsManager.js +12 -12
  90. package/dist/utils/proxyUtils.d.ts +15 -0
  91. package/dist/utils/proxyUtils.js +50 -0
  92. package/dist/utils/retryUtils.d.ts +27 -0
  93. package/dist/utils/retryUtils.js +114 -2
  94. package/dist/utils/sessionManager.d.ts +2 -5
  95. package/dist/utils/sessionManager.js +16 -83
  96. package/dist/utils/terminal.js +4 -3
  97. package/dist/utils/usageLogger.d.ts +11 -0
  98. package/dist/utils/usageLogger.js +99 -0
  99. package/package.json +3 -7
  100. package/dist/agents/summaryAgent.d.ts +0 -31
  101. package/dist/agents/summaryAgent.js +0 -256
@@ -6,6 +6,10 @@ import { promisify } from 'util';
6
6
  import { vscodeConnection } from '../utils/vscodeConnection.js';
7
7
  import { incrementalSnapshotManager } from '../utils/incrementalSnapshot.js';
8
8
  import { tryUnescapeFix, trimPairIfPossible, isOverEscaped, } from '../utils/escapeHandler.js';
9
+ // Utility functions
10
+ import { calculateSimilarity, normalizeForDisplay, } from './utils/filesystem/similarity.utils.js';
11
+ import { analyzeCodeStructure, findSmartContextBoundaries, } from './utils/filesystem/code-analysis.utils.js';
12
+ import { findClosestMatches, generateDiffMessage, } from './utils/filesystem/match-finder.utils.js';
9
13
  const { resolve, dirname, isAbsolute } = path;
10
14
  const execAsync = promisify(exec);
11
15
  /**
@@ -47,323 +51,6 @@ export class FilesystemMCPService {
47
51
  });
48
52
  this.basePath = resolve(basePath);
49
53
  }
50
- /**
51
- * Calculate similarity between two strings using a smarter algorithm
52
- * This normalizes whitespace first to avoid false negatives from spacing differences
53
- * Returns a value between 0 (completely different) and 1 (identical)
54
- */
55
- calculateSimilarity(str1, str2, threshold = 0) {
56
- // Normalize whitespace for comparison: collapse all whitespace to single spaces
57
- const normalize = (s) => s.replace(/\s+/g, ' ').trim();
58
- const norm1 = normalize(str1);
59
- const norm2 = normalize(str2);
60
- const len1 = norm1.length;
61
- const len2 = norm2.length;
62
- if (len1 === 0)
63
- return len2 === 0 ? 1 : 0;
64
- if (len2 === 0)
65
- return 0;
66
- // Quick length check - if lengths differ too much, similarity can't be above threshold
67
- const maxLen = Math.max(len1, len2);
68
- const minLen = Math.min(len1, len2);
69
- const lengthRatio = minLen / maxLen;
70
- if (threshold > 0 && lengthRatio < threshold) {
71
- return lengthRatio; // Can't possibly meet threshold
72
- }
73
- // Use Levenshtein distance for better similarity calculation
74
- const distance = this.levenshteinDistance(norm1, norm2, Math.ceil(maxLen * (1 - threshold)));
75
- return 1 - distance / maxLen;
76
- }
77
- /**
78
- * Calculate Levenshtein distance between two strings with early termination
79
- * @param str1 First string
80
- * @param str2 Second string
81
- * @param maxDistance Maximum distance to compute (early exit if exceeded)
82
- * @returns Levenshtein distance, or maxDistance+1 if exceeded
83
- */
84
- levenshteinDistance(str1, str2, maxDistance = Infinity) {
85
- const len1 = str1.length;
86
- const len2 = str2.length;
87
- // Quick exit for identical strings
88
- if (str1 === str2)
89
- return 0;
90
- // Quick exit if length difference already exceeds maxDistance
91
- if (Math.abs(len1 - len2) > maxDistance) {
92
- return maxDistance + 1;
93
- }
94
- // Use single-row algorithm to save memory (only need previous row)
95
- let prevRow = Array.from({ length: len2 + 1 }, (_, i) => i);
96
- for (let i = 1; i <= len1; i++) {
97
- const currRow = [i];
98
- let minInRow = i; // Track minimum value in current row
99
- for (let j = 1; j <= len2; j++) {
100
- const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
101
- const val = Math.min(prevRow[j] + 1, // deletion
102
- currRow[j - 1] + 1, // insertion
103
- prevRow[j - 1] + cost // substitution
104
- );
105
- currRow[j] = val;
106
- minInRow = Math.min(minInRow, val);
107
- }
108
- // Early termination: if minimum in this row exceeds maxDistance, we can stop
109
- if (minInRow > maxDistance) {
110
- return maxDistance + 1;
111
- }
112
- prevRow = currRow;
113
- }
114
- return prevRow[len2];
115
- }
116
- /**
117
- * Find the closest matching candidates in the file content
118
- * Returns top N candidates sorted by similarity
119
- * Optimized with safe pre-filtering and early exit
120
- * ASYNC to prevent terminal freeze during search
121
- */
122
- async findClosestMatches(searchContent, fileLines, topN = 3) {
123
- const searchLines = searchContent.split('\n');
124
- const candidates = [];
125
- // Normalize whitespace for display only (makes preview more readable)
126
- const normalizeForDisplay = (line) => line.replace(/\t/g, ' ').replace(/ +/g, ' ');
127
- // Fast pre-filter: use first line as anchor (only for multi-line searches)
128
- const searchFirstLine = searchLines[0]?.replace(/\s+/g, ' ').trim() || '';
129
- const threshold = 0.5;
130
- const usePreFilter = searchLines.length >= 5; // Only for 5+ line searches
131
- const preFilterThreshold = 0.2; // Very conservative - only skip completely unrelated lines
132
- // Try to find candidates by sliding window with optimizations
133
- const maxCandidates = topN * 3; // Collect more candidates, then pick best
134
- const YIELD_INTERVAL = 100; // Yield control every 100 iterations
135
- for (let i = 0; i <= fileLines.length - searchLines.length; i++) {
136
- // Yield control periodically to prevent UI freeze
137
- if (i % YIELD_INTERVAL === 0) {
138
- await new Promise(resolve => setImmediate(resolve));
139
- }
140
- // Quick pre-filter: check first line similarity (only for multi-line)
141
- if (usePreFilter) {
142
- const firstLineCandidate = fileLines[i]?.replace(/\s+/g, ' ').trim() || '';
143
- const firstLineSimilarity = this.calculateSimilarity(searchFirstLine, firstLineCandidate, preFilterThreshold);
144
- // Skip only if first line is very different
145
- if (firstLineSimilarity < preFilterThreshold) {
146
- continue;
147
- }
148
- }
149
- // Full candidate check
150
- const candidateLines = fileLines.slice(i, i + searchLines.length);
151
- const candidateContent = candidateLines.join('\n');
152
- const similarity = this.calculateSimilarity(searchContent, candidateContent, threshold);
153
- // Only consider candidates with >50% similarity
154
- if (similarity > threshold) {
155
- candidates.push({
156
- startLine: i + 1,
157
- endLine: i + searchLines.length,
158
- similarity,
159
- preview: candidateLines
160
- .map((line, idx) => `${i + idx + 1}→${normalizeForDisplay(line)}`)
161
- .join('\n'),
162
- });
163
- // Early exit if we found a nearly perfect match
164
- if (similarity >= 0.95) {
165
- break;
166
- }
167
- // Limit candidates to avoid excessive computation
168
- if (candidates.length >= maxCandidates) {
169
- break;
170
- }
171
- }
172
- }
173
- // Sort by similarity descending and return top N
174
- return candidates
175
- .sort((a, b) => b.similarity - a.similarity)
176
- .slice(0, topN);
177
- }
178
- /**
179
- * Generate a helpful diff message showing differences between search and actual content
180
- * Note: This is ONLY for display purposes. Tabs/spaces are normalized for better readability.
181
- */
182
- generateDiffMessage(searchContent, actualContent, maxLines = 10) {
183
- const searchLines = searchContent.split('\n');
184
- const actualLines = actualContent.split('\n');
185
- const diffLines = [];
186
- const maxLen = Math.max(searchLines.length, actualLines.length);
187
- // Normalize whitespace for display only (makes diff more readable)
188
- const normalizeForDisplay = (line) => line.replace(/\t/g, ' ').replace(/ +/g, ' ');
189
- for (let i = 0; i < Math.min(maxLen, maxLines); i++) {
190
- const searchLine = searchLines[i] || '';
191
- const actualLine = actualLines[i] || '';
192
- if (searchLine !== actualLine) {
193
- diffLines.push(`Line ${i + 1}:`);
194
- diffLines.push(` Search: ${JSON.stringify(normalizeForDisplay(searchLine))}`);
195
- diffLines.push(` Actual: ${JSON.stringify(normalizeForDisplay(actualLine))}`);
196
- }
197
- }
198
- if (maxLen > maxLines) {
199
- diffLines.push(`... (${maxLen - maxLines} more lines)`);
200
- }
201
- return diffLines.join('\n');
202
- }
203
- /**
204
- * Analyze code structure for balance and completeness
205
- * Helps AI identify bracket mismatches, unclosed tags, and boundary issues
206
- */
207
- analyzeCodeStructure(_content, filePath, editedLines) {
208
- const analysis = {
209
- bracketBalance: {
210
- curly: { open: 0, close: 0, balanced: true },
211
- round: { open: 0, close: 0, balanced: true },
212
- square: { open: 0, close: 0, balanced: true },
213
- },
214
- indentationWarnings: [],
215
- };
216
- // Count brackets in the edited content
217
- const editedContent = editedLines.join('\n');
218
- // Remove string literals and comments to avoid false positives
219
- const cleanContent = editedContent
220
- .replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`/g, '""') // Remove strings
221
- .replace(/\/\/.*$/gm, '') // Remove single-line comments
222
- .replace(/\/\*[\s\S]*?\*\//g, ''); // Remove multi-line comments
223
- // Count brackets
224
- analysis.bracketBalance.curly.open = (cleanContent.match(/\{/g) || []).length;
225
- analysis.bracketBalance.curly.close = (cleanContent.match(/\}/g) || []).length;
226
- analysis.bracketBalance.curly.balanced =
227
- analysis.bracketBalance.curly.open ===
228
- analysis.bracketBalance.curly.close;
229
- analysis.bracketBalance.round.open = (cleanContent.match(/\(/g) || []).length;
230
- analysis.bracketBalance.round.close = (cleanContent.match(/\)/g) || []).length;
231
- analysis.bracketBalance.round.balanced =
232
- analysis.bracketBalance.round.open ===
233
- analysis.bracketBalance.round.close;
234
- analysis.bracketBalance.square.open = (cleanContent.match(/\[/g) || []).length;
235
- analysis.bracketBalance.square.close = (cleanContent.match(/\]/g) || []).length;
236
- analysis.bracketBalance.square.balanced =
237
- analysis.bracketBalance.square.open ===
238
- analysis.bracketBalance.square.close;
239
- // HTML/JSX tag analysis (for .html, .jsx, .tsx, .vue files)
240
- const isMarkupFile = /\.(html|jsx|tsx|vue)$/i.test(filePath);
241
- if (isMarkupFile) {
242
- const tagPattern = /<\/?([a-zA-Z][a-zA-Z0-9-]*)[^>]*>/g;
243
- const selfClosingPattern = /<[a-zA-Z][a-zA-Z0-9-]*[^>]*\/>/g;
244
- // Remove self-closing tags
245
- const contentWithoutSelfClosing = cleanContent.replace(selfClosingPattern, '');
246
- const tags = [];
247
- const unclosedTags = [];
248
- const unopenedTags = [];
249
- let match;
250
- while ((match = tagPattern.exec(contentWithoutSelfClosing)) !== null) {
251
- const isClosing = match[0]?.startsWith('</');
252
- const tagName = match[1]?.toLowerCase();
253
- if (!tagName)
254
- continue;
255
- if (isClosing) {
256
- const lastOpenTag = tags.pop();
257
- if (!lastOpenTag || lastOpenTag !== tagName) {
258
- unopenedTags.push(tagName);
259
- if (lastOpenTag)
260
- tags.push(lastOpenTag); // Put it back
261
- }
262
- }
263
- else {
264
- tags.push(tagName);
265
- }
266
- }
267
- unclosedTags.push(...tags);
268
- analysis.htmlTags = {
269
- unclosedTags,
270
- unopenedTags,
271
- balanced: unclosedTags.length === 0 && unopenedTags.length === 0,
272
- };
273
- }
274
- // Check indentation consistency
275
- const lines = editedContent.split('\n');
276
- const indents = lines
277
- .filter(line => line.trim().length > 0)
278
- .map(line => {
279
- const match = line.match(/^(\s*)/);
280
- return match ? match[1] : '';
281
- })
282
- .filter((indent) => indent !== undefined);
283
- // Detect mixed tabs/spaces
284
- const hasTabs = indents.some(indent => indent.includes('\t'));
285
- const hasSpaces = indents.some(indent => indent.includes(' '));
286
- if (hasTabs && hasSpaces) {
287
- analysis.indentationWarnings.push('Mixed tabs and spaces detected');
288
- }
289
- // Detect inconsistent indentation levels (spaces only)
290
- if (!hasTabs && hasSpaces) {
291
- const spaceCounts = indents
292
- .filter(indent => indent.length > 0)
293
- .map(indent => indent.length);
294
- if (spaceCounts.length > 1) {
295
- const gcd = spaceCounts.reduce((a, b) => {
296
- while (b !== 0) {
297
- const temp = b;
298
- b = a % b;
299
- a = temp;
300
- }
301
- return a;
302
- });
303
- const hasInconsistent = spaceCounts.some(count => count % gcd !== 0 && gcd > 1);
304
- if (hasInconsistent) {
305
- analysis.indentationWarnings.push(`Inconsistent indentation (expected multiples of ${gcd} spaces)`);
306
- }
307
- }
308
- }
309
- // Note: Boundary checking removed - AI should be free to edit partial code blocks
310
- // The bracket balance check above is sufficient for detecting real issues
311
- return analysis;
312
- }
313
- /**
314
- * Find smart context boundaries for editing
315
- * Expands context to include complete code blocks when possible
316
- */
317
- findSmartContextBoundaries(lines, startLine, endLine, requestedContext) {
318
- const totalLines = lines.length;
319
- let contextStart = Math.max(1, startLine - requestedContext);
320
- let contextEnd = Math.min(totalLines, endLine + requestedContext);
321
- let extended = false;
322
- // Try to find the start of the enclosing block
323
- let bracketDepth = 0;
324
- for (let i = startLine - 1; i >= Math.max(0, startLine - 50); i--) {
325
- const line = lines[i];
326
- if (!line)
327
- continue;
328
- const trimmed = line.trim();
329
- // Count brackets (simple approach)
330
- const openBrackets = (line.match(/\{/g) || []).length;
331
- const closeBrackets = (line.match(/\}/g) || []).length;
332
- bracketDepth += closeBrackets - openBrackets;
333
- // If we find a function/class/block definition with balanced brackets
334
- if (bracketDepth === 0 &&
335
- (trimmed.match(/^(function|class|const|let|var|if|for|while|async|export)\s/i) ||
336
- trimmed.match(/=>\s*\{/) ||
337
- trimmed.match(/^\w+\s*\(/))) {
338
- if (i + 1 < contextStart) {
339
- contextStart = i + 1;
340
- extended = true;
341
- }
342
- break;
343
- }
344
- }
345
- // Try to find the end of the enclosing block
346
- bracketDepth = 0;
347
- for (let i = endLine - 1; i < Math.min(totalLines, endLine + 50); i++) {
348
- const line = lines[i];
349
- if (!line)
350
- continue;
351
- const trimmed = line.trim();
352
- // Count brackets
353
- const openBrackets = (line.match(/\{/g) || []).length;
354
- const closeBrackets = (line.match(/\}/g) || []).length;
355
- bracketDepth += openBrackets - closeBrackets;
356
- // If we find a closing bracket at depth 0
357
- if (bracketDepth === 0 && trimmed.startsWith('}')) {
358
- if (i + 1 > contextEnd) {
359
- contextEnd = i + 1;
360
- extended = true;
361
- }
362
- break;
363
- }
364
- }
365
- return { start: contextStart, end: contextEnd, extended };
366
- }
367
54
  /**
368
55
  * Get the content of a file with optional line range
369
56
  * @param filePath - Path to the file (relative to base path or absolute) or array of file paths
@@ -693,7 +380,7 @@ export class FilesystemMCPService {
693
380
  // Quick pre-filter: check first line similarity (only for multi-line searches)
694
381
  if (usePreFilter) {
695
382
  const firstLineCandidate = contentLines[i]?.replace(/\s+/g, ' ').trim() || '';
696
- const firstLineSimilarity = this.calculateSimilarity(searchFirstLine, firstLineCandidate, preFilterThreshold);
383
+ const firstLineSimilarity = calculateSimilarity(searchFirstLine, firstLineCandidate, preFilterThreshold);
697
384
  // Skip only if first line is very different (< 30% match)
698
385
  // This is safe because if first line differs this much, full match unlikely
699
386
  if (firstLineSimilarity < preFilterThreshold) {
@@ -703,7 +390,7 @@ export class FilesystemMCPService {
703
390
  // Full candidate check
704
391
  const candidateLines = contentLines.slice(i, i + searchLines.length);
705
392
  const candidateContent = candidateLines.join('\n');
706
- const similarity = this.calculateSimilarity(normalizedSearch, candidateContent, threshold);
393
+ const similarity = calculateSimilarity(normalizedSearch, candidateContent, threshold);
707
394
  // Accept matches above threshold
708
395
  if (similarity >= threshold) {
709
396
  matches.push({
@@ -737,7 +424,7 @@ export class FilesystemMCPService {
737
424
  }
738
425
  const candidateLines = contentLines.slice(i, i + correctedSearchLines.length);
739
426
  const candidateContent = candidateLines.join('\n');
740
- const similarity = this.calculateSimilarity(unescapeFix.correctedString, candidateContent);
427
+ const similarity = calculateSimilarity(unescapeFix.correctedString, candidateContent);
741
428
  if (similarity >= threshold) {
742
429
  matches.push({
743
430
  startLine: i + 1,
@@ -760,7 +447,7 @@ export class FilesystemMCPService {
760
447
  // If still no matches after unescape, provide detailed error
761
448
  if (matches.length === 0) {
762
449
  // Find closest matches for suggestions
763
- const closestMatches = await this.findClosestMatches(normalizedSearch, normalizedContent.split('\n'), 3);
450
+ const closestMatches = await findClosestMatches(normalizedSearch, normalizedContent.split('\n'), 3);
764
451
  let errorMessage = `❌ Search content not found in file: ${filePath}\n\n`;
765
452
  errorMessage += `🔍 Using smart fuzzy matching (threshold: 60%)\n`;
766
453
  if (isOverEscaped(searchContent)) {
@@ -778,23 +465,22 @@ export class FilesystemMCPService {
778
465
  if (bestMatch) {
779
466
  const bestMatchLines = lines.slice(bestMatch.startLine - 1, bestMatch.endLine);
780
467
  const bestMatchContent = bestMatchLines.join('\n');
781
- const diffMsg = this.generateDiffMessage(normalizedSearch, bestMatchContent, 5);
468
+ const diffMsg = generateDiffMessage(normalizedSearch, bestMatchContent, 5);
782
469
  if (diffMsg) {
783
470
  errorMessage += `📊 Difference with closest match:\n${diffMsg}\n\n`;
784
471
  }
785
472
  }
786
473
  errorMessage += `💡 Suggestions:\n`;
787
- errorMessage += ` • Make sure you copied content from filesystem_read (without "123→")\n`;
474
+ errorMessage += ` • Make sure you copied content from filesystem-read (without "123→")\n`;
788
475
  errorMessage += ` • Whitespace differences are automatically handled\n`;
789
476
  errorMessage += ` • Try copying a larger or smaller code block\n`;
790
- errorMessage += ` • If multiple filesystem_edit_search attempts fail, use terminal_execute to edit via command line (e.g. sed, printf)\n`;
477
+ errorMessage += ` • If multiple filesystem-edit_search attempts fail, use terminal-execute to edit via command line (e.g. sed, printf)\n`;
791
478
  errorMessage += `⚠️ No similar content found in the file.\n\n`;
792
479
  errorMessage += `📝 What you searched for (first 5 lines, formatted):\n`;
793
- const normalizeForDisplay = (line) => line.replace(/\s+/g, ' ').trim();
794
480
  searchLines.slice(0, 5).forEach((line, idx) => {
795
481
  errorMessage += `${idx + 1}. ${JSON.stringify(normalizeForDisplay(line))}\n`;
796
482
  });
797
- errorMessage += `\n💡 Copy exact content from filesystem_read (without line numbers)\n`;
483
+ errorMessage += `\n💡 Copy exact content from filesystem-read (without line numbers)\n`;
798
484
  }
799
485
  throw new Error(errorMessage);
800
486
  }
@@ -829,7 +515,6 @@ export class FilesystemMCPService {
829
515
  const modifiedLines = [...beforeLines, ...replaceLines, ...afterLines];
830
516
  const modifiedContent = modifiedLines.join('\n');
831
517
  // Calculate replaced content for display (compress whitespace for readability)
832
- const normalizeForDisplay = (line) => line.replace(/\s+/g, ' ');
833
518
  const replacedLines = lines.slice(startLine - 1, endLine);
834
519
  const replacedContent = replacedLines
835
520
  .map((line, idx) => {
@@ -839,7 +524,7 @@ export class FilesystemMCPService {
839
524
  .join('\n');
840
525
  // Calculate context boundaries
841
526
  const lineDifference = replaceLines.length - (endLine - startLine + 1);
842
- const smartBoundaries = this.findSmartContextBoundaries(lines, startLine, endLine, contextLines);
527
+ const smartBoundaries = findSmartContextBoundaries(lines, startLine, endLine, contextLines);
843
528
  const contextStart = smartBoundaries.start;
844
529
  const contextEnd = smartBoundaries.end;
845
530
  // Extract old content for context (compress whitespace for readability)
@@ -885,7 +570,7 @@ export class FilesystemMCPService {
885
570
  .join('\n');
886
571
  // Analyze code structure
887
572
  const editedContentLines = replaceLines;
888
- const structureAnalysis = this.analyzeCodeStructure(finalContent, filePath, editedContentLines);
573
+ const structureAnalysis = analyzeCodeStructure(finalContent, filePath, editedContentLines);
889
574
  // Get diagnostics from IDE (VSCode or JetBrains) - non-blocking, fire-and-forget
890
575
  let diagnostics = [];
891
576
  try {
@@ -969,7 +654,7 @@ export class FilesystemMCPService {
969
654
  }
970
655
  }
971
656
  if (structureAnalysis.indentationWarnings.length > 0) {
972
- structureWarnings.push(...structureAnalysis.indentationWarnings.map(w => `Indentation: ${w}`));
657
+ structureWarnings.push(...structureAnalysis.indentationWarnings.map((w) => `Indentation: ${w}`));
973
658
  }
974
659
  // Note: Boundary warnings removed - partial edits are common and expected
975
660
  if (structureWarnings.length > 0) {
@@ -991,8 +676,8 @@ export class FilesystemMCPService {
991
676
  * For larger changes, make multiple parallel edits to non-overlapping sections instead of one large edit.
992
677
  *
993
678
  * @param filePath - Path to the file to edit
994
- * @param startLine - Starting line number (1-indexed, inclusive) - get from filesystem_read output
995
- * @param endLine - Ending line number (1-indexed, inclusive) - get from filesystem_read output
679
+ * @param startLine - Starting line number (1-indexed, inclusive) - get from filesystem-read output
680
+ * @param endLine - Ending line number (1-indexed, inclusive) - get from filesystem-read output
996
681
  * @param newContent - New content to replace the specified lines (WITHOUT line numbers)
997
682
  * @param contextLines - Number of context lines to return before and after the edit (default: 8)
998
683
  * @returns Object containing success message, precise before/after comparison, and diagnostics from IDE (VSCode or JetBrains)
@@ -1026,7 +711,6 @@ export class FilesystemMCPService {
1026
711
  await incrementalSnapshotManager.backupFile(fullPath);
1027
712
  // Extract the lines that will be replaced (for comparison)
1028
713
  // Compress whitespace for display readability
1029
- const normalizeForDisplay = (line) => line.replace(/\s+/g, ' ');
1030
714
  const replacedLines = lines.slice(startLine - 1, adjustedEndLine);
1031
715
  const replacedContent = replacedLines
1032
716
  .map((line, idx) => {
@@ -1035,7 +719,7 @@ export class FilesystemMCPService {
1035
719
  })
1036
720
  .join('\n');
1037
721
  // Calculate context range using smart boundary detection
1038
- const smartBoundaries = this.findSmartContextBoundaries(lines, startLine, adjustedEndLine, contextLines);
722
+ const smartBoundaries = findSmartContextBoundaries(lines, startLine, adjustedEndLine, contextLines);
1039
723
  const contextStart = smartBoundaries.start;
1040
724
  const contextEnd = smartBoundaries.end;
1041
725
  // Extract old content for context (compress whitespace for readability)
@@ -1100,7 +784,7 @@ export class FilesystemMCPService {
1100
784
  }
1101
785
  // Analyze code structure of the edited content (using formatted content if available)
1102
786
  const editedContentLines = finalLines.slice(startLine - 1, startLine - 1 + newContentLines.length);
1103
- const structureAnalysis = this.analyzeCodeStructure(finalLines.join('\n'), filePath, editedContentLines);
787
+ const structureAnalysis = analyzeCodeStructure(finalLines.join('\n'), filePath, editedContentLines);
1104
788
  // Try to get diagnostics from IDE (VSCode or JetBrains) after editing (non-blocking)
1105
789
  let diagnostics = [];
1106
790
  try {
@@ -1185,7 +869,7 @@ export class FilesystemMCPService {
1185
869
  }
1186
870
  // Check indentation
1187
871
  if (structureAnalysis.indentationWarnings.length > 0) {
1188
- structureWarnings.push(...structureAnalysis.indentationWarnings.map(w => `Indentation: ${w}`));
872
+ structureWarnings.push(...structureAnalysis.indentationWarnings.map((w) => `Indentation: ${w}`));
1189
873
  }
1190
874
  // Note: Boundary warnings removed - partial edits are common and expected
1191
875
  // Format structure warnings
@@ -1234,8 +918,8 @@ export class FilesystemMCPService {
1234
918
  export const filesystemService = new FilesystemMCPService();
1235
919
  export const mcpTools = [
1236
920
  {
1237
- name: 'filesystem_read',
1238
- description: '📖 Read file content with line numbers. **SUPPORTS MULTIPLE FILES**: Pass either a single file path (string) or multiple file paths (array of strings) to read in one call. ⚠️ **IMPORTANT WORKFLOW**: (1) ALWAYS use ACE search tools FIRST (ace_text_search/ace_search_symbols/ace_file_outline) to locate the relevant code, (2) ONLY use filesystem_read when you know the approximate location and need precise line numbers for editing. **ANTI-PATTERN**: Reading files line-by-line from the top wastes tokens - use search instead! **USAGE**: Call without parameters to read entire file(s), or specify startLine/endLine for partial reads. Returns content with line numbers (format: "123→code") for precise editing. **MULTI-FILE EXAMPLE**: filePath=["src/component.ts", "src/utils.ts"] reads both files together.',
921
+ name: 'filesystem-read',
922
+ description: '📖 Read file content with line numbers. **SUPPORTS MULTIPLE FILES**: Pass either a single file path (string) or multiple file paths (array of strings) to read in one call. ⚠️ **IMPORTANT WORKFLOW**: (1) ALWAYS use ACE search tools FIRST (ace-text_search/ace-search_symbols/ace-file_outline) to locate the relevant code, (2) ONLY use filesystem-read when you know the approximate location and need precise line numbers for editing. **ANTI-PATTERN**: Reading files line-by-line from the top wastes tokens - use search instead! **USAGE**: Call without parameters to read entire file(s), or specify startLine/endLine for partial reads. Returns content with line numbers (format: "123→code") for precise editing. **MULTI-FILE EXAMPLE**: filePath=["src/component.ts", "src/utils.ts"] reads both files together.',
1239
923
  inputSchema: {
1240
924
  type: 'object',
1241
925
  properties: {
@@ -1268,7 +952,7 @@ export const mcpTools = [
1268
952
  },
1269
953
  },
1270
954
  {
1271
- name: 'filesystem_create',
955
+ name: 'filesystem-create',
1272
956
  description: 'PREFERRED tool for file creation: Create a new file with specified content. More reliable than terminal commands like echo/cat with redirects. Automatically creates parent directories if needed. Terminal commands can be used as a fallback if needed.',
1273
957
  inputSchema: {
1274
958
  type: 'object',
@@ -1291,7 +975,7 @@ export const mcpTools = [
1291
975
  },
1292
976
  },
1293
977
  {
1294
- name: 'filesystem_delete',
978
+ name: 'filesystem-delete',
1295
979
  description: 'Delete one or multiple files. Supports both single file and batch deletion.',
1296
980
  inputSchema: {
1297
981
  type: 'object',
@@ -1321,7 +1005,7 @@ export const mcpTools = [
1321
1005
  },
1322
1006
  },
1323
1007
  {
1324
- name: 'filesystem_list',
1008
+ name: 'filesystem-list',
1325
1009
  description: 'List files in a directory',
1326
1010
  inputSchema: {
1327
1011
  type: 'object',
@@ -1335,8 +1019,8 @@ export const mcpTools = [
1335
1019
  },
1336
1020
  },
1337
1021
  {
1338
- name: 'filesystem_edit_search',
1339
- description: '🎯 **RECOMMENDED** for most edits: Search-and-replace with SMART FUZZY MATCHING that automatically handles whitespace differences. **WORKFLOW**: (1) Use ace_text_search/ace_search_symbols to locate code, (2) Use filesystem_read to view content, (3) Copy the code block you want to change (without line numbers), (4) Use THIS tool - whitespace will be normalized automatically. **WHY**: No line tracking, auto-handles spacing/tabs, finds best match. **BEST FOR**: Modifying functions, fixing bugs, updating logic.',
1022
+ name: 'filesystem-edit_search',
1023
+ description: '🎯 **RECOMMENDED** for most edits: Search-and-replace with SMART FUZZY MATCHING that automatically handles whitespace differences. **WORKFLOW**: (1) Use ace-text_search/ace-search_symbols to locate code, (2) Use filesystem-read to view content, (3) Copy the code block you want to change (without line numbers), (4) Use THIS tool - whitespace will be normalized automatically. **WHY**: No line tracking, auto-handles spacing/tabs, finds best match. **BEST FOR**: Modifying functions, fixing bugs, updating logic.',
1340
1024
  inputSchema: {
1341
1025
  type: 'object',
1342
1026
  properties: {
@@ -1346,7 +1030,7 @@ export const mcpTools = [
1346
1030
  },
1347
1031
  searchContent: {
1348
1032
  type: 'string',
1349
- description: 'Content to find and replace. Copy from filesystem_read output WITHOUT line numbers (e.g., "123→"). Whitespace differences are automatically handled - focus on getting the content right.',
1033
+ description: 'Content to find and replace. Copy from filesystem-read output WITHOUT line numbers (e.g., "123→"). Whitespace differences are automatically handled - focus on getting the content right.',
1350
1034
  },
1351
1035
  replaceContent: {
1352
1036
  type: 'string',
@@ -1367,8 +1051,8 @@ export const mcpTools = [
1367
1051
  },
1368
1052
  },
1369
1053
  {
1370
- name: 'filesystem_edit',
1371
- description: "🔧 Line-based editing for precise control. **WHEN TO USE**: (1) Adding completely new code sections, (2) Deleting specific line ranges, (3) When search-replace is not suitable. **WORKFLOW**: (1) Use ace_text_search/ace_file_outline to locate relevant area, (2) Use filesystem_read to get exact line numbers, (3) Use THIS tool with precise line ranges. **RECOMMENDATION**: For modifying existing code, use filesystem_edit_search instead - it's safer. **BEST PRACTICES**: Keep edits small (≤15 lines), double-check line numbers, verify bracket closure.",
1054
+ name: 'filesystem-edit',
1055
+ description: "🔧 Line-based editing for precise control. **WHEN TO USE**: (1) Adding completely new code sections, (2) Deleting specific line ranges, (3) When search-replace is not suitable. **WORKFLOW**: (1) Use ace-text_search/ace-file_outline to locate relevant area, (2) Use filesystem-read to get exact line numbers, (3) Use THIS tool with precise line ranges. **RECOMMENDATION**: For modifying existing code, use filesystem-edit_search instead - it's safer. **BEST PRACTICES**: Keep edits small (≤15 lines), double-check line numbers, verify bracket closure.",
1372
1056
  inputSchema: {
1373
1057
  type: 'object',
1374
1058
  properties: {
@@ -1378,11 +1062,11 @@ export const mcpTools = [
1378
1062
  },
1379
1063
  startLine: {
1380
1064
  type: 'number',
1381
- description: '⚠️ CRITICAL: Starting line number (1-indexed, inclusive). MUST match exact line number from filesystem_read output. Double-check this value!',
1065
+ description: '⚠️ CRITICAL: Starting line number (1-indexed, inclusive). MUST match exact line number from filesystem-read output. Double-check this value!',
1382
1066
  },
1383
1067
  endLine: {
1384
1068
  type: 'number',
1385
- description: '⚠️ CRITICAL: Ending line number (1-indexed, inclusive). MUST match exact line number from filesystem_read output. 💡 TIP: Keep edits small (≤15 lines recommended) for better accuracy.',
1069
+ description: '⚠️ CRITICAL: Ending line number (1-indexed, inclusive). MUST match exact line number from filesystem-read output. 💡 TIP: Keep edits small (≤15 lines recommended) for better accuracy.',
1386
1070
  },
1387
1071
  newContent: {
1388
1072
  type: 'string',
@@ -44,9 +44,7 @@ export class IdeDiagnosticsMCPService {
44
44
  // Add summary
45
45
  const counts = [
46
46
  grouped.error.length > 0 ? `${grouped.error.length} errors` : null,
47
- grouped.warning.length > 0
48
- ? `${grouped.warning.length} warnings`
49
- : null,
47
+ grouped.warning.length > 0 ? `${grouped.warning.length} warnings` : null,
50
48
  grouped.info.length > 0 ? `${grouped.info.length} info` : null,
51
49
  grouped.hint.length > 0 ? `${grouped.hint.length} hints` : null,
52
50
  ].filter(Boolean);
@@ -76,7 +74,7 @@ export const ideDiagnosticsService = new IdeDiagnosticsMCPService();
76
74
  // Export MCP tool definitions
77
75
  export const mcpTools = [
78
76
  {
79
- name: 'ide_get_diagnostics',
77
+ name: 'ide-get_diagnostics',
80
78
  description: '🔍 Get diagnostics (errors, warnings, hints) for a specific file from the connected IDE. Works with both VSCode and JetBrains IDEs. Returns array of diagnostic information including severity, line number, character position, message, and source. Requires IDE plugin to be installed and running.',
81
79
  inputSchema: {
82
80
  type: 'object',
@@ -1,19 +1,5 @@
1
1
  import { Tool, type CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
- interface TodoItem {
3
- id: string;
4
- content: string;
5
- status: 'pending' | 'completed';
6
- createdAt: string;
7
- updatedAt: string;
8
- parentId?: string;
9
- }
10
- interface TodoList {
11
- sessionId: string;
12
- todos: TodoItem[];
13
- createdAt: string;
14
- updatedAt: string;
15
- }
16
- type GetCurrentSessionId = () => string | null;
2
+ import type { TodoItem, TodoList, GetCurrentSessionId } from './types/todo.types.js';
17
3
  /**
18
4
  * TODO 管理服务 - 支持创建、查询、更新 TODO
19
5
  */
@@ -23,7 +9,6 @@ export declare class TodoService {
23
9
  constructor(baseDir: string, getCurrentSessionId: GetCurrentSessionId);
24
10
  initialize(): Promise<void>;
25
11
  private getTodoPath;
26
- private formatDateForFolder;
27
12
  private ensureTodoDir;
28
13
  /**
29
14
  * 创建或更新会话的 TODO List
@@ -59,4 +44,3 @@ export declare class TodoService {
59
44
  */
60
45
  executeTool(toolName: string, args: Record<string, unknown>): Promise<CallToolResult>;
61
46
  }
62
- export {};