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
package/dist/mcp/filesystem.js
CHANGED
|
@@ -6,6 +6,11 @@ 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';
|
|
13
|
+
import { parseEditBySearchParams, parseEditByLineParams, executeBatchOperation, } from './utils/filesystem/batch-operations.utils.js';
|
|
9
14
|
const { resolve, dirname, isAbsolute } = path;
|
|
10
15
|
const execAsync = promisify(exec);
|
|
11
16
|
/**
|
|
@@ -47,328 +52,11 @@ export class FilesystemMCPService {
|
|
|
47
52
|
});
|
|
48
53
|
this.basePath = resolve(basePath);
|
|
49
54
|
}
|
|
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
55
|
/**
|
|
368
56
|
* Get the content of a file with optional line range
|
|
369
|
-
* @param filePath - Path to the file (relative to base path or absolute) or array of file paths
|
|
370
|
-
* @param startLine - Starting line number (1-indexed, inclusive, optional - defaults to 1)
|
|
371
|
-
* @param endLine - Ending line number (1-indexed, inclusive, optional - defaults to
|
|
57
|
+
* @param filePath - Path to the file (relative to base path or absolute) or array of file paths or array of file config objects
|
|
58
|
+
* @param startLine - Starting line number (1-indexed, inclusive, optional - defaults to 1). Used for single file or as default for array of strings
|
|
59
|
+
* @param endLine - Ending line number (1-indexed, inclusive, optional - defaults to file end). Used for single file or as default for array of strings
|
|
372
60
|
* @returns Object containing the requested content with line numbers and metadata
|
|
373
61
|
* @throws Error if file doesn't exist or cannot be read
|
|
374
62
|
*/
|
|
@@ -378,8 +66,24 @@ export class FilesystemMCPService {
|
|
|
378
66
|
if (Array.isArray(filePath)) {
|
|
379
67
|
const filesData = [];
|
|
380
68
|
const allContents = [];
|
|
381
|
-
for (const
|
|
69
|
+
for (const fileItem of filePath) {
|
|
382
70
|
try {
|
|
71
|
+
// Support both string format and object format
|
|
72
|
+
let file;
|
|
73
|
+
let fileStartLine;
|
|
74
|
+
let fileEndLine;
|
|
75
|
+
if (typeof fileItem === 'string') {
|
|
76
|
+
// String format: use global startLine/endLine
|
|
77
|
+
file = fileItem;
|
|
78
|
+
fileStartLine = startLine;
|
|
79
|
+
fileEndLine = endLine;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Object format: use per-file startLine/endLine
|
|
83
|
+
file = fileItem.path;
|
|
84
|
+
fileStartLine = fileItem.startLine ?? startLine;
|
|
85
|
+
fileEndLine = fileItem.endLine ?? endLine;
|
|
86
|
+
}
|
|
383
87
|
const fullPath = this.resolvePath(file);
|
|
384
88
|
// For absolute paths, skip validation to allow access outside base path
|
|
385
89
|
if (!isAbsolute(file)) {
|
|
@@ -402,9 +106,9 @@ export class FilesystemMCPService {
|
|
|
402
106
|
const content = await fs.readFile(fullPath, 'utf-8');
|
|
403
107
|
const lines = content.split('\n');
|
|
404
108
|
const totalLines = lines.length;
|
|
405
|
-
// Default values and logic
|
|
406
|
-
const actualStartLine =
|
|
407
|
-
const actualEndLine =
|
|
109
|
+
// Default values and logic (use file-specific values)
|
|
110
|
+
const actualStartLine = fileStartLine ?? 1;
|
|
111
|
+
const actualEndLine = fileEndLine ?? totalLines;
|
|
408
112
|
// Validate and adjust line numbers
|
|
409
113
|
if (actualStartLine < 1) {
|
|
410
114
|
throw new Error(`Start line must be greater than 0 for ${file}`);
|
|
@@ -434,7 +138,9 @@ export class FilesystemMCPService {
|
|
|
434
138
|
}
|
|
435
139
|
catch (error) {
|
|
436
140
|
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
437
|
-
|
|
141
|
+
// Extract file path for error message
|
|
142
|
+
const filePath = typeof fileItem === 'string' ? fileItem : fileItem.path;
|
|
143
|
+
allContents.push(`❌ ${filePath}: ${errorMsg}`);
|
|
438
144
|
}
|
|
439
145
|
}
|
|
440
146
|
return {
|
|
@@ -644,18 +350,35 @@ export class FilesystemMCPService {
|
|
|
644
350
|
}
|
|
645
351
|
}
|
|
646
352
|
/**
|
|
647
|
-
* Edit
|
|
353
|
+
* Edit file(s) by searching for exact content and replacing it
|
|
648
354
|
* This method uses SMART MATCHING to handle whitespace differences automatically.
|
|
649
355
|
*
|
|
650
|
-
* @param filePath - Path to the file to edit
|
|
651
|
-
* @param searchContent - Content to search for (
|
|
652
|
-
* @param replaceContent - New content to replace
|
|
356
|
+
* @param filePath - Path to the file to edit, or array of file paths, or array of edit config objects
|
|
357
|
+
* @param searchContent - Content to search for (for single file or unified mode)
|
|
358
|
+
* @param replaceContent - New content to replace (for single file or unified mode)
|
|
653
359
|
* @param occurrence - Which occurrence to replace (1-indexed, default: 1, use -1 for all)
|
|
654
360
|
* @param contextLines - Number of context lines to return before and after the edit (default: 8)
|
|
655
361
|
* @returns Object containing success message, before/after comparison, and diagnostics from IDE (VSCode or JetBrains)
|
|
656
362
|
* @throws Error if search content is not found or multiple matches exist
|
|
657
363
|
*/
|
|
658
364
|
async editFileBySearch(filePath, searchContent, replaceContent, occurrence = 1, contextLines = 8) {
|
|
365
|
+
// Handle array of files
|
|
366
|
+
if (Array.isArray(filePath)) {
|
|
367
|
+
return await executeBatchOperation(filePath, fileItem => parseEditBySearchParams(fileItem, searchContent, replaceContent, occurrence), (path, search, replace, occ) => this.editFileBySearchSingle(path, search, replace, occ, contextLines), (path, result) => {
|
|
368
|
+
return { path, ...result };
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
// Single file mode
|
|
372
|
+
if (!searchContent || !replaceContent) {
|
|
373
|
+
throw new Error('searchContent and replaceContent are required for single file mode');
|
|
374
|
+
}
|
|
375
|
+
return await this.editFileBySearchSingle(filePath, searchContent, replaceContent, occurrence, contextLines);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Internal method: Edit a single file by search-replace
|
|
379
|
+
* @private
|
|
380
|
+
*/
|
|
381
|
+
async editFileBySearchSingle(filePath, searchContent, replaceContent, occurrence, contextLines) {
|
|
659
382
|
try {
|
|
660
383
|
const fullPath = this.resolvePath(filePath);
|
|
661
384
|
// For absolute paths, skip validation to allow access outside base path
|
|
@@ -693,7 +416,7 @@ export class FilesystemMCPService {
|
|
|
693
416
|
// Quick pre-filter: check first line similarity (only for multi-line searches)
|
|
694
417
|
if (usePreFilter) {
|
|
695
418
|
const firstLineCandidate = contentLines[i]?.replace(/\s+/g, ' ').trim() || '';
|
|
696
|
-
const firstLineSimilarity =
|
|
419
|
+
const firstLineSimilarity = calculateSimilarity(searchFirstLine, firstLineCandidate, preFilterThreshold);
|
|
697
420
|
// Skip only if first line is very different (< 30% match)
|
|
698
421
|
// This is safe because if first line differs this much, full match unlikely
|
|
699
422
|
if (firstLineSimilarity < preFilterThreshold) {
|
|
@@ -703,7 +426,7 @@ export class FilesystemMCPService {
|
|
|
703
426
|
// Full candidate check
|
|
704
427
|
const candidateLines = contentLines.slice(i, i + searchLines.length);
|
|
705
428
|
const candidateContent = candidateLines.join('\n');
|
|
706
|
-
const similarity =
|
|
429
|
+
const similarity = calculateSimilarity(normalizedSearch, candidateContent, threshold);
|
|
707
430
|
// Accept matches above threshold
|
|
708
431
|
if (similarity >= threshold) {
|
|
709
432
|
matches.push({
|
|
@@ -737,7 +460,7 @@ export class FilesystemMCPService {
|
|
|
737
460
|
}
|
|
738
461
|
const candidateLines = contentLines.slice(i, i + correctedSearchLines.length);
|
|
739
462
|
const candidateContent = candidateLines.join('\n');
|
|
740
|
-
const similarity =
|
|
463
|
+
const similarity = calculateSimilarity(unescapeFix.correctedString, candidateContent);
|
|
741
464
|
if (similarity >= threshold) {
|
|
742
465
|
matches.push({
|
|
743
466
|
startLine: i + 1,
|
|
@@ -760,7 +483,7 @@ export class FilesystemMCPService {
|
|
|
760
483
|
// If still no matches after unescape, provide detailed error
|
|
761
484
|
if (matches.length === 0) {
|
|
762
485
|
// Find closest matches for suggestions
|
|
763
|
-
const closestMatches = await
|
|
486
|
+
const closestMatches = await findClosestMatches(normalizedSearch, normalizedContent.split('\n'), 3);
|
|
764
487
|
let errorMessage = `❌ Search content not found in file: ${filePath}\n\n`;
|
|
765
488
|
errorMessage += `🔍 Using smart fuzzy matching (threshold: 60%)\n`;
|
|
766
489
|
if (isOverEscaped(searchContent)) {
|
|
@@ -778,23 +501,22 @@ export class FilesystemMCPService {
|
|
|
778
501
|
if (bestMatch) {
|
|
779
502
|
const bestMatchLines = lines.slice(bestMatch.startLine - 1, bestMatch.endLine);
|
|
780
503
|
const bestMatchContent = bestMatchLines.join('\n');
|
|
781
|
-
const diffMsg =
|
|
504
|
+
const diffMsg = generateDiffMessage(normalizedSearch, bestMatchContent, 5);
|
|
782
505
|
if (diffMsg) {
|
|
783
506
|
errorMessage += `📊 Difference with closest match:\n${diffMsg}\n\n`;
|
|
784
507
|
}
|
|
785
508
|
}
|
|
786
509
|
errorMessage += `💡 Suggestions:\n`;
|
|
787
|
-
errorMessage += ` • Make sure you copied content from
|
|
510
|
+
errorMessage += ` • Make sure you copied content from filesystem-read (without "123→")\n`;
|
|
788
511
|
errorMessage += ` • Whitespace differences are automatically handled\n`;
|
|
789
512
|
errorMessage += ` • Try copying a larger or smaller code block\n`;
|
|
790
|
-
errorMessage += ` • If multiple
|
|
513
|
+
errorMessage += ` • If multiple filesystem-edit_search attempts fail, use terminal-execute to edit via command line (e.g. sed, printf)\n`;
|
|
791
514
|
errorMessage += `⚠️ No similar content found in the file.\n\n`;
|
|
792
515
|
errorMessage += `📝 What you searched for (first 5 lines, formatted):\n`;
|
|
793
|
-
const normalizeForDisplay = (line) => line.replace(/\s+/g, ' ').trim();
|
|
794
516
|
searchLines.slice(0, 5).forEach((line, idx) => {
|
|
795
517
|
errorMessage += `${idx + 1}. ${JSON.stringify(normalizeForDisplay(line))}\n`;
|
|
796
518
|
});
|
|
797
|
-
errorMessage += `\n💡 Copy exact content from
|
|
519
|
+
errorMessage += `\n💡 Copy exact content from filesystem-read (without line numbers)\n`;
|
|
798
520
|
}
|
|
799
521
|
throw new Error(errorMessage);
|
|
800
522
|
}
|
|
@@ -829,7 +551,6 @@ export class FilesystemMCPService {
|
|
|
829
551
|
const modifiedLines = [...beforeLines, ...replaceLines, ...afterLines];
|
|
830
552
|
const modifiedContent = modifiedLines.join('\n');
|
|
831
553
|
// Calculate replaced content for display (compress whitespace for readability)
|
|
832
|
-
const normalizeForDisplay = (line) => line.replace(/\s+/g, ' ');
|
|
833
554
|
const replacedLines = lines.slice(startLine - 1, endLine);
|
|
834
555
|
const replacedContent = replacedLines
|
|
835
556
|
.map((line, idx) => {
|
|
@@ -839,7 +560,7 @@ export class FilesystemMCPService {
|
|
|
839
560
|
.join('\n');
|
|
840
561
|
// Calculate context boundaries
|
|
841
562
|
const lineDifference = replaceLines.length - (endLine - startLine + 1);
|
|
842
|
-
const smartBoundaries =
|
|
563
|
+
const smartBoundaries = findSmartContextBoundaries(lines, startLine, endLine, contextLines);
|
|
843
564
|
const contextStart = smartBoundaries.start;
|
|
844
565
|
const contextEnd = smartBoundaries.end;
|
|
845
566
|
// Extract old content for context (compress whitespace for readability)
|
|
@@ -885,7 +606,7 @@ export class FilesystemMCPService {
|
|
|
885
606
|
.join('\n');
|
|
886
607
|
// Analyze code structure
|
|
887
608
|
const editedContentLines = replaceLines;
|
|
888
|
-
const structureAnalysis =
|
|
609
|
+
const structureAnalysis = analyzeCodeStructure(finalContent, filePath, editedContentLines);
|
|
889
610
|
// Get diagnostics from IDE (VSCode or JetBrains) - non-blocking, fire-and-forget
|
|
890
611
|
let diagnostics = [];
|
|
891
612
|
try {
|
|
@@ -969,7 +690,7 @@ export class FilesystemMCPService {
|
|
|
969
690
|
}
|
|
970
691
|
}
|
|
971
692
|
if (structureAnalysis.indentationWarnings.length > 0) {
|
|
972
|
-
structureWarnings.push(...structureAnalysis.indentationWarnings.map(w => `Indentation: ${w}`));
|
|
693
|
+
structureWarnings.push(...structureAnalysis.indentationWarnings.map((w) => `Indentation: ${w}`));
|
|
973
694
|
}
|
|
974
695
|
// Note: Boundary warnings removed - partial edits are common and expected
|
|
975
696
|
if (structureWarnings.length > 0) {
|
|
@@ -986,19 +707,38 @@ export class FilesystemMCPService {
|
|
|
986
707
|
}
|
|
987
708
|
}
|
|
988
709
|
/**
|
|
989
|
-
* Edit
|
|
710
|
+
* Edit file(s) by replacing lines within a specified range
|
|
990
711
|
* BEST PRACTICE: Keep edits small and focused (≤15 lines recommended) for better accuracy.
|
|
991
712
|
* For larger changes, make multiple parallel edits to non-overlapping sections instead of one large edit.
|
|
992
713
|
*
|
|
993
|
-
* @param filePath - Path to the file to edit
|
|
994
|
-
* @param startLine - Starting line number (
|
|
995
|
-
* @param endLine - Ending line number (
|
|
996
|
-
* @param newContent - New content to replace
|
|
714
|
+
* @param filePath - Path to the file to edit, or array of file paths, or array of edit config objects
|
|
715
|
+
* @param startLine - Starting line number (for single file or unified mode)
|
|
716
|
+
* @param endLine - Ending line number (for single file or unified mode)
|
|
717
|
+
* @param newContent - New content to replace (for single file or unified mode)
|
|
997
718
|
* @param contextLines - Number of context lines to return before and after the edit (default: 8)
|
|
998
719
|
* @returns Object containing success message, precise before/after comparison, and diagnostics from IDE (VSCode or JetBrains)
|
|
999
720
|
* @throws Error if file editing fails
|
|
1000
721
|
*/
|
|
1001
722
|
async editFile(filePath, startLine, endLine, newContent, contextLines = 8) {
|
|
723
|
+
// Handle array of files
|
|
724
|
+
if (Array.isArray(filePath)) {
|
|
725
|
+
return await executeBatchOperation(filePath, fileItem => parseEditByLineParams(fileItem, startLine, endLine, newContent), (path, start, end, content) => this.editFileSingle(path, start, end, content, contextLines), (path, result) => {
|
|
726
|
+
return { path, ...result };
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
// Single file mode
|
|
730
|
+
if (startLine === undefined ||
|
|
731
|
+
endLine === undefined ||
|
|
732
|
+
newContent === undefined) {
|
|
733
|
+
throw new Error('startLine, endLine, and newContent are required for single file mode');
|
|
734
|
+
}
|
|
735
|
+
return await this.editFileSingle(filePath, startLine, endLine, newContent, contextLines);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Internal method: Edit a single file by line range
|
|
739
|
+
* @private
|
|
740
|
+
*/
|
|
741
|
+
async editFileSingle(filePath, startLine, endLine, newContent, contextLines) {
|
|
1002
742
|
try {
|
|
1003
743
|
const fullPath = this.resolvePath(filePath);
|
|
1004
744
|
// For absolute paths, skip validation to allow access outside base path
|
|
@@ -1026,7 +766,6 @@ export class FilesystemMCPService {
|
|
|
1026
766
|
await incrementalSnapshotManager.backupFile(fullPath);
|
|
1027
767
|
// Extract the lines that will be replaced (for comparison)
|
|
1028
768
|
// Compress whitespace for display readability
|
|
1029
|
-
const normalizeForDisplay = (line) => line.replace(/\s+/g, ' ');
|
|
1030
769
|
const replacedLines = lines.slice(startLine - 1, adjustedEndLine);
|
|
1031
770
|
const replacedContent = replacedLines
|
|
1032
771
|
.map((line, idx) => {
|
|
@@ -1035,7 +774,7 @@ export class FilesystemMCPService {
|
|
|
1035
774
|
})
|
|
1036
775
|
.join('\n');
|
|
1037
776
|
// Calculate context range using smart boundary detection
|
|
1038
|
-
const smartBoundaries =
|
|
777
|
+
const smartBoundaries = findSmartContextBoundaries(lines, startLine, adjustedEndLine, contextLines);
|
|
1039
778
|
const contextStart = smartBoundaries.start;
|
|
1040
779
|
const contextEnd = smartBoundaries.end;
|
|
1041
780
|
// Extract old content for context (compress whitespace for readability)
|
|
@@ -1100,7 +839,7 @@ export class FilesystemMCPService {
|
|
|
1100
839
|
}
|
|
1101
840
|
// Analyze code structure of the edited content (using formatted content if available)
|
|
1102
841
|
const editedContentLines = finalLines.slice(startLine - 1, startLine - 1 + newContentLines.length);
|
|
1103
|
-
const structureAnalysis =
|
|
842
|
+
const structureAnalysis = analyzeCodeStructure(finalLines.join('\n'), filePath, editedContentLines);
|
|
1104
843
|
// Try to get diagnostics from IDE (VSCode or JetBrains) after editing (non-blocking)
|
|
1105
844
|
let diagnostics = [];
|
|
1106
845
|
try {
|
|
@@ -1185,7 +924,7 @@ export class FilesystemMCPService {
|
|
|
1185
924
|
}
|
|
1186
925
|
// Check indentation
|
|
1187
926
|
if (structureAnalysis.indentationWarnings.length > 0) {
|
|
1188
|
-
structureWarnings.push(...structureAnalysis.indentationWarnings.map(w => `Indentation: ${w}`));
|
|
927
|
+
structureWarnings.push(...structureAnalysis.indentationWarnings.map((w) => `Indentation: ${w}`));
|
|
1189
928
|
}
|
|
1190
929
|
// Note: Boundary warnings removed - partial edits are common and expected
|
|
1191
930
|
// Format structure warnings
|
|
@@ -1234,8 +973,8 @@ export class FilesystemMCPService {
|
|
|
1234
973
|
export const filesystemService = new FilesystemMCPService();
|
|
1235
974
|
export const mcpTools = [
|
|
1236
975
|
{
|
|
1237
|
-
name: '
|
|
1238
|
-
description: '📖 Read file content with line numbers. **SUPPORTS MULTIPLE FILES**: Pass either a single file path (string)
|
|
976
|
+
name: 'filesystem-read',
|
|
977
|
+
description: '📖 Read file content with line numbers. **SUPPORTS MULTIPLE FILES WITH FLEXIBLE LINE RANGES**: Pass either (1) a single file path (string), (2) array of file paths (strings) with unified startLine/endLine, or (3) array of file config objects with per-file line ranges. ⚠️ **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. **EXAMPLES**: (A) Unified: filePath=["a.ts", "b.ts"], startLine=1, endLine=50 reads lines 1-50 from both. (B) Per-file: filePath=[{path:"a.ts", startLine:1, endLine:30}, {path:"b.ts", startLine:100, endLine:150}] reads different ranges from each file.',
|
|
1239
978
|
inputSchema: {
|
|
1240
979
|
type: 'object',
|
|
1241
980
|
properties: {
|
|
@@ -1250,25 +989,47 @@ export const mcpTools = [
|
|
|
1250
989
|
items: {
|
|
1251
990
|
type: 'string',
|
|
1252
991
|
},
|
|
1253
|
-
description: 'Array of file paths to read in one call',
|
|
992
|
+
description: 'Array of file paths to read in one call (uses unified startLine/endLine from top-level parameters)',
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
type: 'array',
|
|
996
|
+
items: {
|
|
997
|
+
type: 'object',
|
|
998
|
+
properties: {
|
|
999
|
+
path: {
|
|
1000
|
+
type: 'string',
|
|
1001
|
+
description: 'File path',
|
|
1002
|
+
},
|
|
1003
|
+
startLine: {
|
|
1004
|
+
type: 'number',
|
|
1005
|
+
description: 'Optional: Starting line for this file (overrides top-level startLine)',
|
|
1006
|
+
},
|
|
1007
|
+
endLine: {
|
|
1008
|
+
type: 'number',
|
|
1009
|
+
description: 'Optional: Ending line for this file (overrides top-level endLine)',
|
|
1010
|
+
},
|
|
1011
|
+
},
|
|
1012
|
+
required: ['path'],
|
|
1013
|
+
},
|
|
1014
|
+
description: 'Array of file config objects with per-file line ranges. Each file can have its own startLine/endLine.',
|
|
1254
1015
|
},
|
|
1255
1016
|
],
|
|
1256
|
-
description: 'Path to the file(s) to read
|
|
1017
|
+
description: 'Path to the file(s) to read: string, array of strings, or array of {path, startLine?, endLine?} objects',
|
|
1257
1018
|
},
|
|
1258
1019
|
startLine: {
|
|
1259
1020
|
type: 'number',
|
|
1260
|
-
description: 'Optional:
|
|
1021
|
+
description: 'Optional: Default starting line number (1-indexed) for all files. Omit to read from line 1. Can be overridden by per-file startLine in object format.',
|
|
1261
1022
|
},
|
|
1262
1023
|
endLine: {
|
|
1263
1024
|
type: 'number',
|
|
1264
|
-
description: 'Optional:
|
|
1025
|
+
description: 'Optional: Default ending line number (1-indexed) for all files. Omit to read to end of file. Can be overridden by per-file endLine in object format.',
|
|
1265
1026
|
},
|
|
1266
1027
|
},
|
|
1267
1028
|
required: ['filePath'],
|
|
1268
1029
|
},
|
|
1269
1030
|
},
|
|
1270
1031
|
{
|
|
1271
|
-
name: '
|
|
1032
|
+
name: 'filesystem-create',
|
|
1272
1033
|
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
1034
|
inputSchema: {
|
|
1274
1035
|
type: 'object',
|
|
@@ -1291,7 +1052,7 @@ export const mcpTools = [
|
|
|
1291
1052
|
},
|
|
1292
1053
|
},
|
|
1293
1054
|
{
|
|
1294
|
-
name: '
|
|
1055
|
+
name: 'filesystem-delete',
|
|
1295
1056
|
description: 'Delete one or multiple files. Supports both single file and batch deletion.',
|
|
1296
1057
|
inputSchema: {
|
|
1297
1058
|
type: 'object',
|
|
@@ -1321,7 +1082,7 @@ export const mcpTools = [
|
|
|
1321
1082
|
},
|
|
1322
1083
|
},
|
|
1323
1084
|
{
|
|
1324
|
-
name: '
|
|
1085
|
+
name: 'filesystem-list',
|
|
1325
1086
|
description: 'List files in a directory',
|
|
1326
1087
|
inputSchema: {
|
|
1327
1088
|
type: 'object',
|
|
@@ -1335,22 +1096,60 @@ export const mcpTools = [
|
|
|
1335
1096
|
},
|
|
1336
1097
|
},
|
|
1337
1098
|
{
|
|
1338
|
-
name: '
|
|
1339
|
-
description: '🎯 **RECOMMENDED** for most edits: Search-and-replace with SMART FUZZY MATCHING
|
|
1099
|
+
name: 'filesystem-edit_search',
|
|
1100
|
+
description: '🎯 **RECOMMENDED** for most edits: Search-and-replace with SMART FUZZY MATCHING. **SUPPORTS BATCH EDITING**: Pass (1) single file with search/replace, (2) array of file paths with unified search/replace, or (3) array of {path, searchContent, replaceContent, occurrence?} for per-file edits. **WORKFLOW**: (1) Use ace-text_search/ace-search_symbols to locate code, (2) Use filesystem-read to view content, (3) Copy code blocks (without line numbers), (4) Use THIS tool. **WHY**: No line tracking, auto-handles spacing/tabs, finds best match. **BATCH EXAMPLE**: filePath=[{path:"a.ts", searchContent:"old1", replaceContent:"new1"}, {path:"b.ts", searchContent:"old2", replaceContent:"new2"}]',
|
|
1340
1101
|
inputSchema: {
|
|
1341
1102
|
type: 'object',
|
|
1342
1103
|
properties: {
|
|
1343
1104
|
filePath: {
|
|
1344
|
-
|
|
1345
|
-
|
|
1105
|
+
oneOf: [
|
|
1106
|
+
{
|
|
1107
|
+
type: 'string',
|
|
1108
|
+
description: 'Path to a single file to edit',
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
type: 'array',
|
|
1112
|
+
items: {
|
|
1113
|
+
type: 'string',
|
|
1114
|
+
},
|
|
1115
|
+
description: 'Array of file paths (uses unified searchContent/replaceContent from top-level)',
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
type: 'array',
|
|
1119
|
+
items: {
|
|
1120
|
+
type: 'object',
|
|
1121
|
+
properties: {
|
|
1122
|
+
path: {
|
|
1123
|
+
type: 'string',
|
|
1124
|
+
description: 'File path',
|
|
1125
|
+
},
|
|
1126
|
+
searchContent: {
|
|
1127
|
+
type: 'string',
|
|
1128
|
+
description: 'Content to search for in this file',
|
|
1129
|
+
},
|
|
1130
|
+
replaceContent: {
|
|
1131
|
+
type: 'string',
|
|
1132
|
+
description: 'New content to replace with',
|
|
1133
|
+
},
|
|
1134
|
+
occurrence: {
|
|
1135
|
+
type: 'number',
|
|
1136
|
+
description: 'Which match to replace (1-indexed, default: 1)',
|
|
1137
|
+
},
|
|
1138
|
+
},
|
|
1139
|
+
required: ['path', 'searchContent', 'replaceContent'],
|
|
1140
|
+
},
|
|
1141
|
+
description: 'Array of edit config objects for per-file search-replace operations',
|
|
1142
|
+
},
|
|
1143
|
+
],
|
|
1144
|
+
description: 'File path(s) to edit',
|
|
1346
1145
|
},
|
|
1347
1146
|
searchContent: {
|
|
1348
1147
|
type: 'string',
|
|
1349
|
-
description: 'Content to find and replace
|
|
1148
|
+
description: 'Content to find and replace (for single file or unified mode). Copy from filesystem-read WITHOUT line numbers.',
|
|
1350
1149
|
},
|
|
1351
1150
|
replaceContent: {
|
|
1352
1151
|
type: 'string',
|
|
1353
|
-
description: 'New content to replace with
|
|
1152
|
+
description: 'New content to replace with (for single file or unified mode)',
|
|
1354
1153
|
},
|
|
1355
1154
|
occurrence: {
|
|
1356
1155
|
type: 'number',
|
|
@@ -1363,30 +1162,68 @@ export const mcpTools = [
|
|
|
1363
1162
|
default: 8,
|
|
1364
1163
|
},
|
|
1365
1164
|
},
|
|
1366
|
-
required: ['filePath'
|
|
1165
|
+
required: ['filePath'],
|
|
1367
1166
|
},
|
|
1368
1167
|
},
|
|
1369
1168
|
{
|
|
1370
|
-
name: '
|
|
1371
|
-
description:
|
|
1169
|
+
name: 'filesystem-edit',
|
|
1170
|
+
description: '🔧 Line-based editing for precise control. **SUPPORTS BATCH EDITING**: Pass (1) single file with line range, (2) array of file paths with unified line range, or (3) array of {path, startLine, endLine, newContent} for per-file edits. **WHEN TO USE**: (1) Adding new code sections, (2) Deleting specific line ranges, (3) When search-replace not suitable. **WORKFLOW**: (1) Use ace-text_search/ace-file_outline to locate area, (2) Use filesystem-read to get line numbers, (3) Use THIS tool. **RECOMMENDATION**: For modifying existing code, use filesystem-edit_search - safer. **BATCH EXAMPLE**: filePath=[{path:"a.ts", startLine:10, endLine:20, newContent:"..."}, {path:"b.ts", startLine:50, endLine:60, newContent:"..."}]',
|
|
1372
1171
|
inputSchema: {
|
|
1373
1172
|
type: 'object',
|
|
1374
1173
|
properties: {
|
|
1375
1174
|
filePath: {
|
|
1376
|
-
|
|
1377
|
-
|
|
1175
|
+
oneOf: [
|
|
1176
|
+
{
|
|
1177
|
+
type: 'string',
|
|
1178
|
+
description: 'Path to a single file to edit',
|
|
1179
|
+
},
|
|
1180
|
+
{
|
|
1181
|
+
type: 'array',
|
|
1182
|
+
items: {
|
|
1183
|
+
type: 'string',
|
|
1184
|
+
},
|
|
1185
|
+
description: 'Array of file paths (uses unified startLine/endLine/newContent from top-level)',
|
|
1186
|
+
},
|
|
1187
|
+
{
|
|
1188
|
+
type: 'array',
|
|
1189
|
+
items: {
|
|
1190
|
+
type: 'object',
|
|
1191
|
+
properties: {
|
|
1192
|
+
path: {
|
|
1193
|
+
type: 'string',
|
|
1194
|
+
description: 'File path',
|
|
1195
|
+
},
|
|
1196
|
+
startLine: {
|
|
1197
|
+
type: 'number',
|
|
1198
|
+
description: 'Starting line number (1-indexed, inclusive)',
|
|
1199
|
+
},
|
|
1200
|
+
endLine: {
|
|
1201
|
+
type: 'number',
|
|
1202
|
+
description: 'Ending line number (1-indexed, inclusive)',
|
|
1203
|
+
},
|
|
1204
|
+
newContent: {
|
|
1205
|
+
type: 'string',
|
|
1206
|
+
description: 'New content to replace lines (without line numbers)',
|
|
1207
|
+
},
|
|
1208
|
+
},
|
|
1209
|
+
required: ['path', 'startLine', 'endLine', 'newContent'],
|
|
1210
|
+
},
|
|
1211
|
+
description: 'Array of edit config objects for per-file line-based edits',
|
|
1212
|
+
},
|
|
1213
|
+
],
|
|
1214
|
+
description: 'File path(s) to edit',
|
|
1378
1215
|
},
|
|
1379
1216
|
startLine: {
|
|
1380
1217
|
type: 'number',
|
|
1381
|
-
description: '⚠️ CRITICAL: Starting line number (1-indexed, inclusive)
|
|
1218
|
+
description: '⚠️ CRITICAL: Starting line number (1-indexed, inclusive) for single file or unified mode. MUST match filesystem-read output.',
|
|
1382
1219
|
},
|
|
1383
1220
|
endLine: {
|
|
1384
1221
|
type: 'number',
|
|
1385
|
-
description: '⚠️ CRITICAL: Ending line number (1-indexed, inclusive)
|
|
1222
|
+
description: '⚠️ CRITICAL: Ending line number (1-indexed, inclusive) for single file or unified mode. Keep edits small (≤15 lines).',
|
|
1386
1223
|
},
|
|
1387
1224
|
newContent: {
|
|
1388
1225
|
type: 'string',
|
|
1389
|
-
description: 'New content to replace specified lines. ⚠️ Do NOT include line numbers.
|
|
1226
|
+
description: 'New content to replace specified lines (for single file or unified mode). ⚠️ Do NOT include line numbers. Ensure proper indentation.',
|
|
1390
1227
|
},
|
|
1391
1228
|
contextLines: {
|
|
1392
1229
|
type: 'number',
|
|
@@ -1394,7 +1231,7 @@ export const mcpTools = [
|
|
|
1394
1231
|
default: 8,
|
|
1395
1232
|
},
|
|
1396
1233
|
},
|
|
1397
|
-
required: ['filePath'
|
|
1234
|
+
required: ['filePath'],
|
|
1398
1235
|
},
|
|
1399
1236
|
},
|
|
1400
1237
|
];
|