fileditor-mcp 1.0.2 → 1.0.3
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/1781792324448_PixelTunnelSender.exe +0 -0
- package/LICENSE +21 -21
- package/README.md +128 -128
- package/docs/MCP-INTERFACE.cn.md +975 -1061
- package/docs/MCP-INTERFACE.en.md +881 -974
- package/docs/README.cn.md +128 -128
- package/package.json +30 -31
- package/src/handlers/applyDiff.js +298 -324
- package/src/handlers/insertContent.js +179 -153
- package/src/handlers/listFiles.js +65 -54
- package/src/handlers/readFile.js +109 -109
- package/src/handlers/searchAndReplace.js +198 -217
- package/src/handlers/setWorkspace.js +26 -26
- package/src/handlers/writeFile.js +97 -91
- package/src/index.js +22 -22
- package/src/server.js +85 -85
- package/src/tools/toolDefinitions.js +291 -301
- package/src/utils/fileUtils.js +238 -716
- package/test/ApplyDiffHandler.test.js +754 -754
- package/test/InsertContentHandler.test.js +371 -369
- package/test/ListFilesHandler.test.js +302 -300
- package/test/ReadFileHandler.test.js +213 -213
- package/test/SearchAndReplaceHandler.test.js +505 -505
- package/test/SetWorkspaceHandler.test.js +290 -290
- package/test/WriteFileHandler.test.js +289 -289
- package/test/runAllTests.js +233 -233
|
@@ -1,324 +1,298 @@
|
|
|
1
|
-
import { FileUtils } from '../utils/fileUtils.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* apply_diffs tool handler
|
|
5
|
-
*/
|
|
6
|
-
export class ApplyDiffHandler {
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Format content with line numbers for error display
|
|
10
|
-
* @param {string} content - Content to format
|
|
11
|
-
* @param {number} startLine - Starting line number
|
|
12
|
-
* @returns {string} Formatted content with line numbers
|
|
13
|
-
*/
|
|
14
|
-
static formatContentWithLineNumbers(content, startLine) {
|
|
15
|
-
const lines = content.split('\n');
|
|
16
|
-
return lines.map((line, index) => `${startLine + index} | ${line}`).join('\n');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {
|
|
24
|
-
* @
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
message += ` (${failedCount} failed)`;
|
|
300
|
-
}
|
|
301
|
-
message += `. File now has ${lines.length} lines.\n\n`;
|
|
302
|
-
|
|
303
|
-
// Add detailed results for batch mode
|
|
304
|
-
message += "Detailed results:\n";
|
|
305
|
-
results.forEach((result, index) => {
|
|
306
|
-
message += `\nDiff ${index + 1}:\n`;
|
|
307
|
-
message += ` Status: ${result.status}\n`;
|
|
308
|
-
message += ` Start Line: ${result.start_line}\n`;
|
|
309
|
-
message += ` Message: ${result.message}\n`;
|
|
310
|
-
});
|
|
311
|
-
} else {
|
|
312
|
-
// Single mode - maintain original message format
|
|
313
|
-
const result = results[0];
|
|
314
|
-
if (result.status === "success") {
|
|
315
|
-
message = `Successfully applied diff to ${path}: ${result.message}. File now has ${lines.length} lines.`;
|
|
316
|
-
} else {
|
|
317
|
-
throw new Error(`Single diff failed:\n\n${result.message}`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Create success response using FileUtils
|
|
322
|
-
return FileUtils.createResponse(message);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
1
|
+
import { FileUtils } from '../utils/fileUtils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* apply_diffs tool handler
|
|
5
|
+
*/
|
|
6
|
+
export class ApplyDiffHandler {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format content with line numbers for error display
|
|
10
|
+
* @param {string} content - Content to format
|
|
11
|
+
* @param {number} startLine - Starting line number
|
|
12
|
+
* @returns {string} Formatted content with line numbers
|
|
13
|
+
*/
|
|
14
|
+
static formatContentWithLineNumbers(content, startLine) {
|
|
15
|
+
const lines = content.split('\n');
|
|
16
|
+
return lines.map((line, index) => `${startLine + index} | ${line}`).join('\n');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate a single diff operation
|
|
21
|
+
* @param {Object} diff - Diff operation to validate
|
|
22
|
+
* @param {Array} lines - File lines
|
|
23
|
+
* @param {number} lineOffset - Current line offset
|
|
24
|
+
* @param {boolean} trim - Whether to trim whitespace when comparing
|
|
25
|
+
* @returns {Object} Validation result
|
|
26
|
+
*/
|
|
27
|
+
static validateDiff(diff, lines, lineOffset, trim = false) {
|
|
28
|
+
const { search_content: searchContent, start_line: originalStartLine, originalIndex } = diff;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Calculate actual start line with offset
|
|
32
|
+
const actualStartLine = originalStartLine + lineOffset;
|
|
33
|
+
|
|
34
|
+
// Check if start line number exceeds current file range
|
|
35
|
+
if (actualStartLine > lines.length) {
|
|
36
|
+
throw new Error(`start_line (${originalStartLine} -> ${actualStartLine}) exceeds file length (${lines.length} lines)`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Split search content into line arrays
|
|
40
|
+
const searchLines = searchContent.split('\n');
|
|
41
|
+
|
|
42
|
+
// Determine the end line number for search
|
|
43
|
+
const endLine = actualStartLine + searchLines.length - 1;
|
|
44
|
+
|
|
45
|
+
// Check if search range exceeds file range
|
|
46
|
+
if (endLine > lines.length) {
|
|
47
|
+
throw new Error(`Search content extends beyond file length. Start line: ${originalStartLine} (actual: ${actualStartLine}), search lines: ${searchLines.length}, file lines: ${lines.length}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Extract content to match
|
|
51
|
+
const targetLines = lines.slice(actualStartLine - 1, endLine);
|
|
52
|
+
const targetContent = targetLines.join('\n');
|
|
53
|
+
|
|
54
|
+
// Prepare content for comparison based on trim setting
|
|
55
|
+
let searchForComparison = searchContent;
|
|
56
|
+
let targetForComparison = targetContent;
|
|
57
|
+
|
|
58
|
+
if (trim) {
|
|
59
|
+
// Apply trim to each line and rejoin
|
|
60
|
+
const searchLinesForComparison = searchContent.split('\n').map(line => line.trim());
|
|
61
|
+
const targetLinesForComparison = targetLines.map(line => line.trim());
|
|
62
|
+
|
|
63
|
+
searchForComparison = searchLinesForComparison.join('\n');
|
|
64
|
+
targetForComparison = targetLinesForComparison.join('\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Exact match check (using trimmed or original content based on trim setting)
|
|
68
|
+
if (targetForComparison !== searchForComparison) {
|
|
69
|
+
// Always show original content in error messages for debugging
|
|
70
|
+
const expectedFormatted = this.formatContentWithLineNumbers(searchContent, actualStartLine);
|
|
71
|
+
const actualFormatted = this.formatContentWithLineNumbers(targetContent, actualStartLine);
|
|
72
|
+
const detailedError = `Content mismatch at line ${originalStartLine} (actual: ${actualStartLine}).\n\nExpected content:\n${expectedFormatted}\n\nActual content:\n${actualFormatted}\n\nThis is diff #${originalIndex + 1} in the batch.`;
|
|
73
|
+
throw new Error(detailedError);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
actualStartLine,
|
|
79
|
+
endLine,
|
|
80
|
+
searchLines
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: error.message,
|
|
86
|
+
originalIndex,
|
|
87
|
+
originalStartLine
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Handle apply_diffs request (supports both single diff and batch diffs)
|
|
96
|
+
* @param {Object} args - Request parameters
|
|
97
|
+
* @returns {Promise<Object>} Response result
|
|
98
|
+
*/
|
|
99
|
+
static async handle(args) {
|
|
100
|
+
const { path, search_content, replace_content, start_line, atomic = true, trim = false } = args;
|
|
101
|
+
|
|
102
|
+
// Check if file exists
|
|
103
|
+
if (!FileUtils.fileExists(path)) {
|
|
104
|
+
throw new Error(`File not found: ${path}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Normalize all parameters to arrays
|
|
108
|
+
let searchArray, replaceArray, startLineArray;
|
|
109
|
+
|
|
110
|
+
if (Array.isArray(search_content)) {
|
|
111
|
+
searchArray = search_content;
|
|
112
|
+
replaceArray = Array.isArray(replace_content) ? replace_content : [replace_content];
|
|
113
|
+
startLineArray = Array.isArray(start_line) ? start_line : [start_line];
|
|
114
|
+
} else {
|
|
115
|
+
searchArray = [search_content];
|
|
116
|
+
replaceArray = Array.isArray(replace_content) ? replace_content : [replace_content];
|
|
117
|
+
startLineArray = Array.isArray(start_line) ? start_line : [start_line];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Validate array lengths match
|
|
121
|
+
if (searchArray.length !== replaceArray.length || searchArray.length !== startLineArray.length) {
|
|
122
|
+
throw new Error("Array lengths for search_content, replace_content, and start_line must match");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const diffCount = searchArray.length;
|
|
126
|
+
|
|
127
|
+
// Validate all start_line values
|
|
128
|
+
for (let i = 0; i < diffCount; i++) {
|
|
129
|
+
if (startLineArray[i] < 1) {
|
|
130
|
+
throw new Error(`Invalid start_line: ${startLineArray[i]} at index ${i}. Line numbers start from 1.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Create diff objects and sort by start_line
|
|
135
|
+
const diffs = searchArray.map((searchContent, index) => ({
|
|
136
|
+
search_content: searchContent,
|
|
137
|
+
replace_content: replaceArray[index],
|
|
138
|
+
start_line: startLineArray[index],
|
|
139
|
+
originalIndex: index
|
|
140
|
+
})).sort((a, b) => a.start_line - b.start_line);
|
|
141
|
+
|
|
142
|
+
// Read file content
|
|
143
|
+
const content = await FileUtils.readFile(path);
|
|
144
|
+
let lines = content.split('\n');
|
|
145
|
+
if (atomic && diffCount > 1) {
|
|
146
|
+
// Atomic mode: validate all diffs first
|
|
147
|
+
let lineOffset = 0;
|
|
148
|
+
const validationErrors = [];
|
|
149
|
+
let tempLines = [...lines]; // Work with a copy for validation
|
|
150
|
+
|
|
151
|
+
for (const diff of diffs) {
|
|
152
|
+
const validation = this.validateDiff(diff, tempLines, lineOffset, trim);
|
|
153
|
+
|
|
154
|
+
if (!validation.success) {
|
|
155
|
+
validationErrors.push({
|
|
156
|
+
index: validation.originalIndex,
|
|
157
|
+
start_line: validation.originalStartLine,
|
|
158
|
+
status: "fail",
|
|
159
|
+
message: validation.error
|
|
160
|
+
});
|
|
161
|
+
break; // Stop validation on first error
|
|
162
|
+
} else {
|
|
163
|
+
// Simulate the replacement to update tempLines and lineOffset
|
|
164
|
+
const replaceLines = diff.replace_content.split('\n');
|
|
165
|
+
const actualStartLine = diff.start_line + lineOffset;
|
|
166
|
+
const endLine = actualStartLine + validation.searchLines.length - 1;
|
|
167
|
+
|
|
168
|
+
const beforeLines = tempLines.slice(0, actualStartLine - 1);
|
|
169
|
+
const afterLines = tempLines.slice(endLine);
|
|
170
|
+
tempLines = [...beforeLines, ...replaceLines, ...afterLines];
|
|
171
|
+
|
|
172
|
+
// Update line offset for next validation
|
|
173
|
+
const lineDiff = replaceLines.length - validation.searchLines.length;
|
|
174
|
+
lineOffset += lineDiff;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (validationErrors.length > 0) {
|
|
179
|
+
// In atomic mode, if any diff fails, abort the entire operation
|
|
180
|
+
const results = new Array(diffCount);
|
|
181
|
+
validationErrors.forEach(error => {
|
|
182
|
+
results[error.index] = error;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Fill in success placeholders for non-failed diffs
|
|
186
|
+
for (let i = 0; i < diffCount; i++) {
|
|
187
|
+
if (!results[i]) {
|
|
188
|
+
results[i] = {
|
|
189
|
+
index: i,
|
|
190
|
+
start_line: startLineArray[i],
|
|
191
|
+
status: "aborted",
|
|
192
|
+
message: "Operation aborted due to validation failures in atomic mode"
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Create detailed error message with formatted content
|
|
198
|
+
let detailedMessage = `Atomic operation failed: ${validationErrors.length}/${diffCount} diffs would fail. No changes applied.\n\n`;
|
|
199
|
+
detailedMessage += "Detailed results:\n";
|
|
200
|
+
results.forEach((result, index) => {
|
|
201
|
+
detailedMessage += `\nDiff ${index + 1}:\n`;
|
|
202
|
+
detailedMessage += ` Status: ${result.status}\n`;
|
|
203
|
+
detailedMessage += ` Start Line: ${result.start_line}\n`;
|
|
204
|
+
detailedMessage += ` Message: ${result.message}\n`;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
throw new Error(detailedMessage);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Track results for each diff
|
|
212
|
+
const results = new Array(diffCount);
|
|
213
|
+
let lineOffset = 0; // Track cumulative line offset
|
|
214
|
+
let appliedCount = 0;
|
|
215
|
+
|
|
216
|
+
// Process each diff in order
|
|
217
|
+
for (const diff of diffs) {
|
|
218
|
+
const { search_content: searchContent, replace_content: replaceContent, start_line: originalStartLine, originalIndex } = diff;
|
|
219
|
+
|
|
220
|
+
if (!atomic || diffCount === 1) {
|
|
221
|
+
// Non-atomic mode or single diff: validate and apply one by one
|
|
222
|
+
const validation = this.validateDiff(diff, lines, lineOffset, trim);
|
|
223
|
+
|
|
224
|
+
if (!validation.success) {
|
|
225
|
+
results[originalIndex] = {
|
|
226
|
+
index: originalIndex,
|
|
227
|
+
start_line: originalStartLine,
|
|
228
|
+
status: "fail",
|
|
229
|
+
message: validation.error
|
|
230
|
+
};
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Apply the diff
|
|
236
|
+
const actualStartLine = originalStartLine + lineOffset;
|
|
237
|
+
const searchLines = searchContent.split('\n');
|
|
238
|
+
const replaceLines = replaceContent.split('\n');
|
|
239
|
+
const endLine = actualStartLine + searchLines.length - 1;
|
|
240
|
+
|
|
241
|
+
// Perform replacement
|
|
242
|
+
const beforeLines = lines.slice(0, actualStartLine - 1);
|
|
243
|
+
const afterLines = lines.slice(endLine);
|
|
244
|
+
lines = [...beforeLines, ...replaceLines, ...afterLines];
|
|
245
|
+
|
|
246
|
+
// Update line offset for subsequent diffs
|
|
247
|
+
const lineDiff = replaceLines.length - searchLines.length;
|
|
248
|
+
lineOffset += lineDiff;
|
|
249
|
+
appliedCount++;
|
|
250
|
+
|
|
251
|
+
// Record success
|
|
252
|
+
results[originalIndex] = {
|
|
253
|
+
index: originalIndex,
|
|
254
|
+
start_line: originalStartLine,
|
|
255
|
+
status: "success",
|
|
256
|
+
message: `Replaced ${searchLines.length} line(s) at line ${originalStartLine}${lineDiff !== 0 ? ` (${lineDiff > 0 ? 'added' : 'removed'} ${Math.abs(lineDiff)} line(s))` : ''}`
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Write back to file
|
|
261
|
+
const newContent = lines.join('\n');
|
|
262
|
+
await FileUtils.writeFile(path, newContent);
|
|
263
|
+
|
|
264
|
+
// Build response
|
|
265
|
+
const failedCount = diffCount - appliedCount;
|
|
266
|
+
const isBatchMode = diffCount > 1;
|
|
267
|
+
|
|
268
|
+
let message;
|
|
269
|
+
if (isBatchMode) {
|
|
270
|
+
const mode = atomic ? "atomic" : "non-atomic";
|
|
271
|
+
message = `Batch diff operation (${mode}) completed: ${appliedCount}/${diffCount} diffs applied successfully to ${path}`;
|
|
272
|
+
if (failedCount > 0) {
|
|
273
|
+
message += ` (${failedCount} failed)`;
|
|
274
|
+
}
|
|
275
|
+
message += `. File now has ${lines.length} lines.\n\n`;
|
|
276
|
+
|
|
277
|
+
// Add detailed results for batch mode
|
|
278
|
+
message += "Detailed results:\n";
|
|
279
|
+
results.forEach((result, index) => {
|
|
280
|
+
message += `\nDiff ${index + 1}:\n`;
|
|
281
|
+
message += ` Status: ${result.status}\n`;
|
|
282
|
+
message += ` Start Line: ${result.start_line}\n`;
|
|
283
|
+
message += ` Message: ${result.message}\n`;
|
|
284
|
+
});
|
|
285
|
+
} else {
|
|
286
|
+
// Single mode - maintain original message format
|
|
287
|
+
const result = results[0];
|
|
288
|
+
if (result.status === "success") {
|
|
289
|
+
message = `Successfully applied diff to ${path}: ${result.message}. File now has ${lines.length} lines.`;
|
|
290
|
+
} else {
|
|
291
|
+
throw new Error(`Single diff failed:\n\n${result.message}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Create success response using FileUtils
|
|
296
|
+
return FileUtils.createResponse(message);
|
|
297
|
+
}
|
|
298
|
+
}
|