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