@aexol/spectral 0.9.126 → 0.9.128

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 (89) hide show
  1. package/dist/agent/index.d.ts.map +1 -1
  2. package/dist/agent/index.js +5 -0
  3. package/dist/extensions/seo/analyzers/content.d.ts +29 -6
  4. package/dist/extensions/seo/analyzers/content.d.ts.map +1 -1
  5. package/dist/extensions/seo/analyzers/content.js +243 -8
  6. package/dist/extensions/seo/analyzers/crawler.d.ts.map +1 -1
  7. package/dist/extensions/seo/analyzers/crawler.js +133 -7
  8. package/dist/extensions/seo/analyzers/drift.d.ts.map +1 -1
  9. package/dist/extensions/seo/analyzers/drift.js +86 -29
  10. package/dist/extensions/seo/analyzers/images.d.ts +10 -4
  11. package/dist/extensions/seo/analyzers/images.d.ts.map +1 -1
  12. package/dist/extensions/seo/analyzers/images.js +153 -5
  13. package/dist/extensions/seo/analyzers/performance.d.ts +3 -3
  14. package/dist/extensions/seo/analyzers/performance.d.ts.map +1 -1
  15. package/dist/extensions/seo/analyzers/performance.js +77 -6
  16. package/dist/extensions/seo/analyzers/readability.d.ts.map +1 -1
  17. package/dist/extensions/seo/analyzers/readability.js +17 -9
  18. package/dist/extensions/seo/analyzers/schema.d.ts.map +1 -1
  19. package/dist/extensions/seo/analyzers/schema.js +99 -10
  20. package/dist/extensions/seo/analyzers/sitemap.d.ts.map +1 -1
  21. package/dist/extensions/seo/analyzers/sitemap.js +57 -16
  22. package/dist/extensions/seo/analyzers/technical.d.ts +0 -7
  23. package/dist/extensions/seo/analyzers/technical.d.ts.map +1 -1
  24. package/dist/extensions/seo/analyzers/technical.js +408 -37
  25. package/dist/extensions/seo/config.d.ts +2 -0
  26. package/dist/extensions/seo/config.d.ts.map +1 -1
  27. package/dist/extensions/seo/config.js +2 -0
  28. package/dist/extensions/seo/tools/batch.js +1 -1
  29. package/dist/extensions/seo/tools/content.d.ts.map +1 -1
  30. package/dist/extensions/seo/tools/content.js +5 -3
  31. package/dist/extensions/seo/tools/full-audit.d.ts +1 -1
  32. package/dist/extensions/seo/tools/full-audit.d.ts.map +1 -1
  33. package/dist/extensions/seo/tools/full-audit.js +391 -66
  34. package/dist/extensions/seo/tools/helpers.d.ts +15 -4
  35. package/dist/extensions/seo/tools/helpers.d.ts.map +1 -1
  36. package/dist/extensions/seo/tools/helpers.js +57 -6
  37. package/dist/extensions/seo/tools/performance.d.ts.map +1 -1
  38. package/dist/extensions/seo/tools/performance.js +17 -11
  39. package/dist/extensions/seo/tools/report.d.ts.map +1 -1
  40. package/dist/extensions/seo/tools/report.js +513 -47
  41. package/dist/extensions/seo/types.d.ts +295 -3
  42. package/dist/extensions/seo/types.d.ts.map +1 -1
  43. package/dist/extensions/seo/utils/fetcher.d.ts +4 -0
  44. package/dist/extensions/seo/utils/fetcher.d.ts.map +1 -1
  45. package/dist/extensions/seo/utils/fetcher.js +77 -11
  46. package/dist/extensions/seo/utils/parser.d.ts +3 -1
  47. package/dist/extensions/seo/utils/parser.d.ts.map +1 -1
  48. package/dist/extensions/seo/utils/parser.js +105 -25
  49. package/dist/extensions/seo/utils/renderer.d.ts +6 -1
  50. package/dist/extensions/seo/utils/renderer.d.ts.map +1 -1
  51. package/dist/extensions/seo/utils/renderer.js +207 -1
  52. package/dist/extensions/seo/utils/url-safety.d.ts.map +1 -1
  53. package/dist/extensions/seo/utils/url-safety.js +19 -3
  54. package/dist/extensions/web/tools/fetch.js +2 -2
  55. package/dist/memory/tools/compact-context.d.ts.map +1 -1
  56. package/dist/memory/tools/compact-context.js +10 -3
  57. package/dist/sdk/agent-core/agent-loop.d.ts.map +1 -1
  58. package/dist/sdk/agent-core/agent-loop.js +87 -4
  59. package/dist/sdk/agent-core/types.d.ts +1 -0
  60. package/dist/sdk/agent-core/types.d.ts.map +1 -1
  61. package/dist/sdk/ai/utils/validation.d.ts +0 -7
  62. package/dist/sdk/ai/utils/validation.d.ts.map +1 -1
  63. package/dist/sdk/ai/utils/validation.js +21 -1
  64. package/dist/sdk/coding-agent/core/extensions/runner.d.ts.map +1 -1
  65. package/dist/sdk/coding-agent/core/extensions/runner.js +15 -1
  66. package/dist/sdk/coding-agent/core/tools/apply-patch.d.ts +7 -2
  67. package/dist/sdk/coding-agent/core/tools/apply-patch.d.ts.map +1 -1
  68. package/dist/sdk/coding-agent/core/tools/apply-patch.js +219 -41
  69. package/dist/sdk/coding-agent/core/tools/atomic-write.d.ts +30 -0
  70. package/dist/sdk/coding-agent/core/tools/atomic-write.d.ts.map +1 -0
  71. package/dist/sdk/coding-agent/core/tools/atomic-write.js +42 -0
  72. package/dist/sdk/coding-agent/core/tools/edit-diff.d.ts +10 -4
  73. package/dist/sdk/coding-agent/core/tools/edit-diff.d.ts.map +1 -1
  74. package/dist/sdk/coding-agent/core/tools/edit-diff.js +44 -24
  75. package/dist/sdk/coding-agent/core/tools/edit.d.ts +6 -0
  76. package/dist/sdk/coding-agent/core/tools/edit.d.ts.map +1 -1
  77. package/dist/sdk/coding-agent/core/tools/edit.js +31 -5
  78. package/dist/sdk/coding-agent/core/tools/file-mutation-queue.d.ts +9 -3
  79. package/dist/sdk/coding-agent/core/tools/file-mutation-queue.d.ts.map +1 -1
  80. package/dist/sdk/coding-agent/core/tools/file-mutation-queue.js +42 -3
  81. package/dist/sdk/coding-agent/core/tools/read.d.ts.map +1 -1
  82. package/dist/sdk/coding-agent/core/tools/read.js +79 -47
  83. package/dist/sdk/coding-agent/core/tools/truncate.d.ts +4 -2
  84. package/dist/sdk/coding-agent/core/tools/truncate.d.ts.map +1 -1
  85. package/dist/sdk/coding-agent/core/tools/truncate.js +43 -23
  86. package/dist/sdk/coding-agent/core/tools/write.d.ts +5 -8
  87. package/dist/sdk/coding-agent/core/tools/write.d.ts.map +1 -1
  88. package/dist/sdk/coding-agent/core/tools/write.js +29 -11
  89. package/package.json +1 -1
@@ -1,11 +1,12 @@
1
1
  import { constants } from "fs";
2
- import { access as fsAccess, mkdir as fsMkdir, readFile as fsReadFile, rm as fsRm, writeFile as fsWriteFile } from "fs/promises";
2
+ import { access as fsAccess, mkdir as fsMkdir, readFile as fsReadFile, rm as fsRm, stat as fsStat, writeFile as fsWriteFile, } from "fs/promises";
3
3
  import { dirname } from "path";
4
4
  import { Type } from "typebox";
5
5
  import { detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeForFuzzyMatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
6
6
  import { withFileMutationQueue } from "./file-mutation-queue.js";
7
7
  import { resolveToCwd } from "./path-utils.js";
8
8
  import { wrapToolDefinition } from "./tool-definition-wrapper.js";
9
+ import { atomicWriteFile, defaultAtomicWriteOperations } from "./atomic-write.js";
9
10
  const applyPatchSchema = Type.Object({
10
11
  patchText: Type.String({ description: "The full patch text that describes all changes to be made." }),
11
12
  }, { additionalProperties: false });
@@ -15,11 +16,9 @@ const defaultApplyPatchOperations = {
15
16
  removeFile: (filePath) => fsRm(filePath, { force: false }),
16
17
  mkdir: (filePath) => fsMkdir(filePath, { recursive: true }).then(() => { }),
17
18
  access: (filePath) => fsAccess(filePath, constants.R_OK | constants.W_OK),
19
+ stat: (filePath) => fsStat(filePath).then((s) => ({ isDirectory: s.isDirectory() })),
18
20
  };
19
- function stripHeredoc(input) {
20
- const heredocMatch = input.match(/^(?:cat\s+)?<<['"]?(\w+)['"]?\s*\n([\s\S]*?)\n\1\s*$/);
21
- return heredocMatch ? heredocMatch[2] : input;
22
- }
21
+ export { defaultApplyPatchOperations };
23
22
  function parsePatchHeader(lines, startIndex) {
24
23
  const line = lines[startIndex];
25
24
  if (line.startsWith("*** Add File:")) {
@@ -46,8 +45,11 @@ function parseAddFileContent(lines, startIndex) {
46
45
  let content = "";
47
46
  let index = startIndex;
48
47
  while (index < lines.length && !lines[index].startsWith("***")) {
49
- if (lines[index].startsWith("+"))
50
- content += `${lines[index].slice(1)}\n`;
48
+ const line = lines[index];
49
+ if (!line.startsWith("+")) {
50
+ throw new Error(`Add File content line missing '+' prefix: ${line}`);
51
+ }
52
+ content += `${line.slice(1)}\n`;
51
53
  index++;
52
54
  }
53
55
  return { content: content.endsWith("\n") ? content.slice(0, -1) : content, nextIndex: index };
@@ -107,7 +109,7 @@ function parseUpdateHeader(line) {
107
109
  }
108
110
  function parsePatch(patchText) {
109
111
  const normalizedPatchText = patchText.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
110
- const lines = stripHeredoc(normalizedPatchText.trim()).split("\n");
112
+ const lines = normalizedPatchText.trim().split("\n");
111
113
  const beginIndex = lines.findIndex((line) => line.trim() === "*** Begin Patch");
112
114
  const endIndex = lines.findIndex((line) => line.trim() === "*** End Patch");
113
115
  if (beginIndex === -1 || endIndex === -1 || beginIndex >= endIndex) {
@@ -161,7 +163,38 @@ function seekSequence(lines, pattern, startIndex, eof = false) {
161
163
  (a, b) => a.trimEnd() === b.trimEnd(),
162
164
  (a, b) => a.trim() === b.trim(),
163
165
  compareNormalized,
164
- ].reduce((found, compare) => found !== -1 ? found : tryMatch(lines, pattern, startIndex, compare, eof), -1);
166
+ ].reduce((found, compare) => (found !== -1 ? found : tryMatch(lines, pattern, startIndex, compare, eof)), -1);
167
+ }
168
+ /**
169
+ * Searches for a pattern starting from `startIndex` (forward), and if not found,
170
+ * falls back to a whole-file search preferring the match nearest to `startIndex`.
171
+ * This allows context that appears before the cursor to still resolve (A7).
172
+ */
173
+ function seekSequenceBidirectional(lines, pattern, startIndex, eof = false) {
174
+ const forward = seekSequence(lines, pattern, startIndex, eof);
175
+ if (forward !== -1)
176
+ return forward;
177
+ if (pattern.length === 0)
178
+ return -1;
179
+ const comparators = [
180
+ (a, b) => a === b,
181
+ (a, b) => a.trimEnd() === b.trimEnd(),
182
+ (a, b) => a.trim() === b.trim(),
183
+ compareNormalized,
184
+ ];
185
+ for (const compare of comparators) {
186
+ let bestIndex = -1;
187
+ for (let index = 0; index <= lines.length - pattern.length; index++) {
188
+ if (pattern.every((line, offset) => compare(lines[index + offset], line))) {
189
+ if (bestIndex === -1 || Math.abs(index - startIndex) < Math.abs(bestIndex - startIndex)) {
190
+ bestIndex = index;
191
+ }
192
+ }
193
+ }
194
+ if (bestIndex !== -1)
195
+ return bestIndex;
196
+ }
197
+ return -1;
165
198
  }
166
199
  function seekSequenceNearHint(lines, pattern, startIndex, hintLine, eof = false) {
167
200
  if (!hintLine || hintLine < 1 || pattern.length === 0)
@@ -216,7 +249,7 @@ function computeReplacements(originalLines, filePath, chunks) {
216
249
  let lineIndex = 0;
217
250
  for (const chunk of chunks) {
218
251
  if (chunk.changeContext) {
219
- const contextIndex = seekSequence(originalLines, [chunk.changeContext], lineIndex);
252
+ const contextIndex = seekSequenceBidirectional(originalLines, [chunk.changeContext], lineIndex);
220
253
  if (contextIndex === -1)
221
254
  throw new Error(`Failed to find context '${chunk.changeContext}' in ${filePath}.`);
222
255
  lineIndex = contextIndex + 1;
@@ -263,8 +296,112 @@ function deriveUpdatedContent(filePath, rawContent, chunks) {
263
296
  newLines.push("");
264
297
  return { bom, oldContent: normalized, newContent: newLines.join("\n"), lineEnding };
265
298
  }
299
+ /**
300
+ * Groups update hunks by target path (A2). When multiple hunks target the
301
+ * same file, their chunks are applied sequentially to a single evolving buffer
302
+ * so earlier hunks' changes are not lost.
303
+ */
304
+ function groupUpdateHunksByPath(hunks) {
305
+ const groups = new Map();
306
+ for (const hunk of hunks) {
307
+ if (hunk.type !== "update")
308
+ continue;
309
+ let group = groups.get(hunk.path);
310
+ if (!group) {
311
+ group = { path: hunk.path, hunks: [] };
312
+ groups.set(hunk.path, group);
313
+ }
314
+ if (hunk.movePath && !group.movePath) {
315
+ group.movePath = hunk.movePath;
316
+ }
317
+ group.hunks.push(hunk.chunks);
318
+ }
319
+ return groups;
320
+ }
321
+ /**
322
+ * Builds backup entries for all changes BEFORE any mutation. On rollback,
323
+ * entries are restored in reverse order.
324
+ */
325
+ function buildBackups(changes) {
326
+ const backups = [];
327
+ for (const change of changes) {
328
+ switch (change.type) {
329
+ case "add":
330
+ backups.push({ absolutePath: change.absolutePath, existed: false });
331
+ break;
332
+ case "update":
333
+ if (change.movePath && change.absoluteMovePath) {
334
+ backups.push({ absolutePath: change.absolutePath, existed: true, content: change.rawOriginalContent ?? undefined });
335
+ backups.push({ absolutePath: change.absoluteMovePath, existed: false });
336
+ }
337
+ else {
338
+ backups.push({ absolutePath: change.absolutePath, existed: true, content: change.rawOriginalContent ?? undefined });
339
+ }
340
+ break;
341
+ case "delete":
342
+ backups.push({ absolutePath: change.absolutePath, existed: true, content: change.rawOriginalContent ?? undefined });
343
+ break;
344
+ }
345
+ }
346
+ return backups;
347
+ }
348
+ async function rollbackChanges(backups, ops) {
349
+ for (let i = backups.length - 1; i >= 0; i--) {
350
+ const backup = backups[i];
351
+ try {
352
+ if (backup.existed && backup.content !== undefined) {
353
+ await ops.writeFile(backup.absolutePath, backup.content);
354
+ }
355
+ else if (!backup.existed) {
356
+ await ops.removeFile(backup.absolutePath);
357
+ }
358
+ }
359
+ catch {
360
+ // Best-effort rollback; individual failures are tolerated
361
+ }
362
+ }
363
+ }
364
+ /**
365
+ * Applies all changes transactionally (A1, A4). If any change fails, all
366
+ * prior mutations are rolled back so the repository returns to its original
367
+ * state.
368
+ */
369
+ async function applyChangesTransactionally(changes, ops, atomicOps, signal) {
370
+ const backups = buildBackups(changes);
371
+ try {
372
+ for (const change of changes) {
373
+ if (signal?.aborted)
374
+ throw new Error("Operation aborted");
375
+ if (change.type === "delete") {
376
+ try {
377
+ await ops.removeFile(change.absolutePath);
378
+ }
379
+ catch (error) {
380
+ // A8: handle ENOENT gracefully (file already gone — no-op)
381
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
382
+ continue;
383
+ }
384
+ throw error;
385
+ }
386
+ }
387
+ else {
388
+ const targetPath = change.absoluteMovePath ?? change.absolutePath;
389
+ await ops.mkdir(dirname(targetPath));
390
+ const writeFn = ops.atomicWrite ?? ((p, c) => atomicWriteFile(p, c, "utf-8", atomicOps));
391
+ await writeFn(targetPath, change.bom + restoreLineEndings(change.newContent, change.lineEnding));
392
+ if (change.movePath)
393
+ await ops.removeFile(change.absolutePath);
394
+ }
395
+ }
396
+ }
397
+ catch (error) {
398
+ await rollbackChanges(backups, ops);
399
+ throw error;
400
+ }
401
+ }
266
402
  export function createApplyPatchToolDefinition(cwd, options) {
267
403
  const ops = options?.operations ?? defaultApplyPatchOperations;
404
+ const atomicOps = defaultAtomicWriteOperations;
268
405
  return {
269
406
  name: "apply_patch",
270
407
  label: "Apply Patch",
@@ -284,6 +421,10 @@ export function createApplyPatchToolDefinition(cwd, options) {
284
421
  const hunks = parsePatch(input.patchText);
285
422
  if (hunks.length === 0)
286
423
  throw new Error("apply_patch verification failed: no hunks found.");
424
+ // A2: group update hunks by path to avoid data loss from duplicate-file patches
425
+ const updateGroups = groupUpdateHunksByPath(hunks);
426
+ const processedUpdatePaths = new Set();
427
+ // Parse / derive phase — no mutations; failures here never partially mutate the repo
287
428
  const changes = [];
288
429
  for (const hunk of hunks) {
289
430
  if (signal?.aborted)
@@ -302,62 +443,99 @@ export function createApplyPatchToolDefinition(cwd, options) {
302
443
  type: "add",
303
444
  bom,
304
445
  lineEnding: "\n",
446
+ rawOriginalContent: null,
305
447
  });
306
448
  continue;
307
449
  }
308
- await ops.access(absolutePath).catch((error) => {
309
- const errorMessage = error instanceof Error && "code" in error ? `Error code: ${error.code}` : String(error);
310
- throw new Error(`Could not patch file: ${hunk.path}. ${errorMessage}.`);
311
- });
312
- const rawContent = (await ops.readFile(absolutePath)).toString("utf-8");
313
450
  if (hunk.type === "delete") {
314
- const { bom, text } = stripBom(rawContent);
451
+ // A8: directory check
452
+ if (ops.stat) {
453
+ const statResult = await ops.stat(absolutePath).catch(() => null);
454
+ if (statResult?.isDirectory) {
455
+ throw new Error(`Cannot delete directory: ${hunk.path}`);
456
+ }
457
+ }
458
+ // A8: no pre-access TOCTOU check; read for backup, handle missing gracefully
459
+ let rawContent = null;
460
+ try {
461
+ rawContent = (await ops.readFile(absolutePath)).toString("utf-8");
462
+ }
463
+ catch {
464
+ // File doesn't exist — delete is a no-op
465
+ }
466
+ const { bom, text } = rawContent !== null ? stripBom(rawContent) : { bom: "", text: "" };
315
467
  changes.push({
316
468
  path: hunk.path,
317
469
  absolutePath,
318
- oldContent: normalizeToLF(text),
470
+ oldContent: rawContent !== null ? normalizeToLF(text) : "",
319
471
  newContent: "",
320
472
  type: "delete",
321
473
  bom,
322
- lineEnding: detectLineEnding(text),
474
+ lineEnding: rawContent !== null ? detectLineEnding(text) : "\n",
475
+ rawOriginalContent: rawContent,
323
476
  });
324
477
  continue;
325
478
  }
326
- const updated = deriveUpdatedContent(hunk.path, rawContent, hunk.chunks);
327
- const absoluteMovePath = hunk.movePath ? resolveToCwd(hunk.movePath, cwd) : undefined;
479
+ // hunk.type === "update"
480
+ // A2: process grouped updates only once per path
481
+ if (processedUpdatePaths.has(hunk.path))
482
+ continue;
483
+ processedUpdatePaths.add(hunk.path);
484
+ const group = updateGroups.get(hunk.path);
485
+ if (!group)
486
+ continue;
487
+ let rawContent;
488
+ try {
489
+ rawContent = (await ops.readFile(absolutePath)).toString("utf-8");
490
+ }
491
+ catch (error) {
492
+ const errorMessage = error instanceof Error && "code" in error ? `Error code: ${error.code}` : String(error);
493
+ throw new Error(`Could not patch file: ${hunk.path}. ${errorMessage}.`);
494
+ }
495
+ // A2: apply each hunk's chunks sequentially to the evolving buffer
496
+ let currentContent = rawContent;
497
+ let firstBom = "";
498
+ let firstLineEnding = "\n";
499
+ let firstOldContent = "";
500
+ for (let i = 0; i < group.hunks.length; i++) {
501
+ const result = deriveUpdatedContent(hunk.path, currentContent, group.hunks[i]);
502
+ if (i === 0) {
503
+ firstBom = result.bom;
504
+ firstLineEnding = result.lineEnding;
505
+ firstOldContent = result.oldContent;
506
+ }
507
+ currentContent = result.newContent;
508
+ }
509
+ const absoluteMovePath = group.movePath ? resolveToCwd(group.movePath, cwd) : undefined;
328
510
  changes.push({
329
511
  path: hunk.path,
330
512
  absolutePath,
331
- oldContent: updated.oldContent,
332
- newContent: updated.newContent,
513
+ oldContent: firstOldContent,
514
+ newContent: currentContent,
333
515
  type: "update",
334
- movePath: hunk.movePath,
516
+ movePath: group.movePath,
335
517
  absoluteMovePath,
336
- bom: updated.bom,
337
- lineEnding: updated.lineEnding,
518
+ bom: firstBom,
519
+ lineEnding: firstLineEnding,
520
+ rawOriginalContent: rawContent,
338
521
  });
339
522
  }
523
+ // Validate before any mutations
524
+ for (const change of changes) {
525
+ if (change.oldContent === change.newContent && !change.movePath && change.type !== "delete") {
526
+ throw new Error(`No changes made to ${change.path}. The patch produced identical content.`);
527
+ }
528
+ }
529
+ // A3: pass mutated files as array for per-file serialization
340
530
  const mutatedFiles = changes.map((change) => change.absoluteMovePath ?? change.absolutePath);
341
- return withFileMutationQueue(mutatedFiles.join("\0"), async () => {
531
+ return withFileMutationQueue(mutatedFiles, async () => {
532
+ // A1: transactional apply with rollback
533
+ await applyChangesTransactionally(changes, ops, atomicOps, signal);
534
+ // Build diff output (only after successful apply)
342
535
  let combinedDiff = "";
343
536
  let combinedPatch = "";
344
537
  let firstChangedLine;
345
538
  for (const change of changes) {
346
- if (signal?.aborted)
347
- throw new Error("Operation aborted");
348
- if (change.oldContent === change.newContent && !change.movePath) {
349
- throw new Error(`No changes made to ${change.path}. The patch produced identical content.`);
350
- }
351
- if (change.type === "delete") {
352
- await ops.removeFile(change.absolutePath);
353
- }
354
- else {
355
- const targetPath = change.absoluteMovePath ?? change.absolutePath;
356
- await ops.mkdir(dirname(targetPath));
357
- await ops.writeFile(targetPath, change.bom + restoreLineEndings(change.newContent, change.lineEnding));
358
- if (change.movePath)
359
- await ops.removeFile(change.absolutePath);
360
- }
361
539
  const displayPath = change.movePath ? `${change.path} => ${change.movePath}` : change.path;
362
540
  const diffResult = generateDiffString(change.oldContent, change.newContent);
363
541
  combinedDiff += `${displayPath}\n${diffResult.diff}\n`;
@@ -0,0 +1,30 @@
1
+ export type BufferEncoding = "utf-8" | "utf8" | "ascii" | "latin1" | "binary" | "ucs2" | "ucs-2" | "utf16le" | "utf-16le";
2
+ /**
3
+ * Operations surface for atomic writes. Mirrors the subset of
4
+ * {@link WriteOperations} needed so that tests can stub the filesystem.
5
+ */
6
+ export interface AtomicWriteOperations {
7
+ writeFile: (absolutePath: string, data: string) => Promise<void>;
8
+ rename: (oldPath: string, newPath: string) => Promise<void>;
9
+ unlink: (absolutePath: string) => Promise<void>;
10
+ }
11
+ declare const defaultAtomicWriteOperations: AtomicWriteOperations;
12
+ /**
13
+ * Write `content` to `absolutePath` atomically by first writing to a sibling
14
+ * temp file in the same directory and then renaming it into place. `rename`
15
+ * is atomic when source and destination live on the same filesystem, which is
16
+ * guaranteed because the temp file is a sibling of the destination.
17
+ *
18
+ * If the temp write or rename fails, the temp file is best-effort removed so
19
+ * no stale partial temp file is left behind. The original file (if any) is
20
+ * never touched until the rename succeeds, so a failed write leaves the
21
+ * existing file intact.
22
+ *
23
+ * @param absolutePath Final destination file path.
24
+ * @param content Text content to write.
25
+ * @param encoding Encoding for the temp write (defaults to "utf-8").
26
+ * @param operations Optional stub for tests; defaults to real `fs/promises`.
27
+ */
28
+ export declare function atomicWriteFile(absolutePath: string, content: string, encoding?: BufferEncoding, operations?: AtomicWriteOperations): Promise<void>;
29
+ export { defaultAtomicWriteOperations };
30
+ //# sourceMappingURL=atomic-write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic-write.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/tools/atomic-write.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,cAAc,GACvB,OAAO,GACP,MAAM,GACN,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,OAAO,GACP,SAAS,GACT,UAAU,CAAC;AAEd;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACrC,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAED,QAAA,MAAM,4BAA4B,EAAE,qBAInC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CACpC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,cAAwB,EAClC,UAAU,GAAE,qBAAoD,GAC9D,OAAO,CAAC,IAAI,CAAC,CAef;AAED,OAAO,EAAE,4BAA4B,EAAE,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { rename as fsRename, unlink as fsUnlink, writeFile as fsWriteFile, } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ const defaultAtomicWriteOperations = {
5
+ writeFile: (path, data) => fsWriteFile(path, data, "utf-8"),
6
+ rename: (oldPath, newPath) => fsRename(oldPath, newPath),
7
+ unlink: (path) => fsUnlink(path),
8
+ };
9
+ /**
10
+ * Write `content` to `absolutePath` atomically by first writing to a sibling
11
+ * temp file in the same directory and then renaming it into place. `rename`
12
+ * is atomic when source and destination live on the same filesystem, which is
13
+ * guaranteed because the temp file is a sibling of the destination.
14
+ *
15
+ * If the temp write or rename fails, the temp file is best-effort removed so
16
+ * no stale partial temp file is left behind. The original file (if any) is
17
+ * never touched until the rename succeeds, so a failed write leaves the
18
+ * existing file intact.
19
+ *
20
+ * @param absolutePath Final destination file path.
21
+ * @param content Text content to write.
22
+ * @param encoding Encoding for the temp write (defaults to "utf-8").
23
+ * @param operations Optional stub for tests; defaults to real `fs/promises`.
24
+ */
25
+ export async function atomicWriteFile(absolutePath, content, encoding = "utf-8", operations = defaultAtomicWriteOperations) {
26
+ const dir = dirname(absolutePath);
27
+ const base = absolutePath.split(/[\\/]/).pop() ?? absolutePath;
28
+ // Sibling temp file: same dir so rename is atomic on the same filesystem.
29
+ // The leading dot + random suffix keeps it out of the way and unique.
30
+ const tempPath = join(dir, `.${base}.tmp-${randomBytes(6).toString("hex")}`);
31
+ try {
32
+ await operations.writeFile(tempPath, content);
33
+ await operations.rename(tempPath, absolutePath);
34
+ }
35
+ catch (error) {
36
+ // Best-effort cleanup of the temp file on failure; never let cleanup
37
+ // errors mask the original write/rename failure.
38
+ await operations.unlink(tempPath).catch(() => { });
39
+ throw error;
40
+ }
41
+ }
42
+ export { defaultAtomicWriteOperations };
@@ -6,11 +6,15 @@ export declare function detectLineEnding(content: string): "\r\n" | "\n";
6
6
  export declare function normalizeToLF(text: string): string;
7
7
  export declare function restoreLineEndings(text: string, ending: "\r\n" | "\n"): string;
8
8
  /**
9
- * Normalize text for fuzzy matching. Applies progressive transformations:
9
+ * Normalize text for fuzzy matching. Applies only semantically-safe
10
+ * whitespace/visibility transformations:
10
11
  * - Strip trailing whitespace from each line
11
- * - Normalize smart quotes to ASCII equivalents
12
- * - Normalize Unicode dashes/hyphens to ASCII hyphen
13
- * - Normalize special Unicode spaces to regular space
12
+ * - Normalize special Unicode spaces to a regular space
13
+ *
14
+ * Intentionally does NOT perform NFKC compatibility decomposition, smart-quote
15
+ * → straight-quote, or dash/hyphen substitutions: those transformations make
16
+ * semantically different code match silently. Matching across those characters
17
+ * requires the oldText to use the exact same characters as the file.
14
18
  */
15
19
  export declare function normalizeForFuzzyMatch(text: string): string;
16
20
  export interface FuzzyMatchResult {
@@ -32,6 +36,8 @@ export interface Edit {
32
36
  export interface AppliedEditsResult {
33
37
  baseContent: string;
34
38
  newContent: string;
39
+ /** True if any edit was matched via a non-exact (line-trimmed or normalized) strategy. */
40
+ usedFuzzyMatch: boolean;
35
41
  }
36
42
  export declare function fuzzyFindText(content: string, oldText: string): FuzzyMatchResult;
37
43
  /** Strip UTF-8 BOM if present, return both the BOM (if any) and the text without it */
@@ -1 +1 @@
1
- {"version":3,"file":"edit-diff.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/tools/edit-diff.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE9E;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB3D;AAED,MAAM,WAAW,gBAAgB;IAChC,gCAAgC;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,cAAc,EAAE,OAAO,CAAC;IACxB,8DAA8D;IAC9D,qBAAqB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,IAAI;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAwBD,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACnB;AA8KD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAkBhF;AA6CD,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvE;AA0CD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC5C,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,EAAE,MAAM,GACV,kBAAkB,CA0DpB;AAED,yCAAyC;AACzC,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,SAAI,GAAG,MAAM,CAKnH;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,SAAI,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAuHxD;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,IAAI,EAAE,EACb,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,cAAc,GAAG,aAAa,CAAC,CAyBzC;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,cAAc,GAAG,aAAa,CAAC,CAEzC"}
1
+ {"version":3,"file":"edit-diff.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/tools/edit-diff.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAa/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE9E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAU3D;AAED,MAAM,WAAW,gBAAgB;IAChC,gCAAgC;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,cAAc,EAAE,OAAO,CAAC;IACxB,8DAA8D;IAC9D,qBAAqB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,IAAI;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAwBD,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,cAAc,EAAE,OAAO,CAAC;CACxB;AAsLD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAkBhF;AAkDD,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvE;AA0CD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC5C,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,EAAE,MAAM,GACV,kBAAkB,CA4DpB;AAED,yCAAyC;AACzC,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,SAAI,GAAG,MAAM,CAKnH;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,SAAI,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAuHxD;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACrC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,IAAI,EAAE,EACb,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,cAAc,GAAG,aAAa,CAAC,CAyBzC;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,cAAc,GAAG,aAAa,CAAC,CAEzC"}
@@ -7,13 +7,21 @@ import { constants } from "fs";
7
7
  import { access, readFile } from "fs/promises";
8
8
  import { resolveToCwd } from "./path-utils.js";
9
9
  export function detectLineEnding(content) {
10
- const crlfIdx = content.indexOf("\r\n");
11
- const lfIdx = content.indexOf("\n");
12
- if (lfIdx === -1)
13
- return "\n";
14
- if (crlfIdx === -1)
10
+ let crlfCount = 0;
11
+ let lfCount = 0;
12
+ for (let i = 0; i < content.length; i++) {
13
+ if (content[i] !== "\n")
14
+ continue;
15
+ if (i > 0 && content[i - 1] === "\r") {
16
+ crlfCount++;
17
+ }
18
+ else {
19
+ lfCount++;
20
+ }
21
+ }
22
+ if (crlfCount === 0 && lfCount === 0)
15
23
  return "\n";
16
- return crlfIdx < lfIdx ? "\r\n" : "\n";
24
+ return crlfCount > lfCount ? "\r\n" : "\n";
17
25
  }
18
26
  export function normalizeToLF(text) {
19
27
  return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -22,31 +30,26 @@ export function restoreLineEndings(text, ending) {
22
30
  return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
23
31
  }
24
32
  /**
25
- * Normalize text for fuzzy matching. Applies progressive transformations:
33
+ * Normalize text for fuzzy matching. Applies only semantically-safe
34
+ * whitespace/visibility transformations:
26
35
  * - Strip trailing whitespace from each line
27
- * - Normalize smart quotes to ASCII equivalents
28
- * - Normalize Unicode dashes/hyphens to ASCII hyphen
29
- * - Normalize special Unicode spaces to regular space
36
+ * - Normalize special Unicode spaces to a regular space
37
+ *
38
+ * Intentionally does NOT perform NFKC compatibility decomposition, smart-quote
39
+ * → straight-quote, or dash/hyphen substitutions: those transformations make
40
+ * semantically different code match silently. Matching across those characters
41
+ * requires the oldText to use the exact same characters as the file.
30
42
  */
31
43
  export function normalizeForFuzzyMatch(text) {
32
- return (text
33
- .normalize("NFKC")
34
- // Strip trailing whitespace per line
44
+ return text
45
+ // Strip trailing whitespace per line (tab/space visibility tolerance)
35
46
  .split("\n")
36
47
  .map((line) => line.trimEnd())
37
48
  .join("\n")
38
- // Smart single quotes '
39
- .replace(/[\u2018\u2019\u201A\u201B]/g, "'")
40
- // Smart double quotes → "
41
- .replace(/[\u201C\u201D\u201E\u201F]/g, '"')
42
- // Various dashes/hyphens → -
43
- // U+2010 hyphen, U+2011 non-breaking hyphen, U+2012 figure dash,
44
- // U+2013 en-dash, U+2014 em-dash, U+2015 horizontal bar, U+2212 minus
45
- .replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g, "-")
46
- // Special spaces → regular space
49
+ // Special spacesregular space (whitespace-equivalence only)
47
50
  // U+00A0 NBSP, U+2002-U+200A various spaces, U+202F narrow NBSP,
48
51
  // U+205F medium math space, U+3000 ideographic space
49
- .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " "));
52
+ .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " ");
50
53
  }
51
54
  function findExactMatches(content, oldText) {
52
55
  const matches = [];
@@ -194,6 +197,12 @@ function findLineTrimmedMatches(content, oldText) {
194
197
  }
195
198
  return dedupeMatches(matches);
196
199
  }
200
+ /**
201
+ * Files at or above this size skip the O(n) normalized index map. Line-trimmed
202
+ * matching (which already covers tab/space/CRLF tolerance) is still attempted;
203
+ * only the costly normalized pass is skipped for large files.
204
+ */
205
+ const NORMALIZED_MATCH_MAX_BYTES = 256 * 1024; // 256KB
197
206
  function findTextMatches(content, oldText) {
198
207
  const exactMatches = findExactMatches(content, oldText);
199
208
  if (exactMatches.length > 0)
@@ -201,6 +210,8 @@ function findTextMatches(content, oldText) {
201
210
  const lineTrimmedMatches = findLineTrimmedMatches(content, oldText);
202
211
  if (lineTrimmedMatches.length > 0)
203
212
  return lineTrimmedMatches;
213
+ if (content.length >= NORMALIZED_MATCH_MAX_BYTES)
214
+ return [];
204
215
  return findNormalizedTextMatches(content, oldText);
205
216
  }
206
217
  export function fuzzyFindText(content, oldText) {
@@ -240,8 +251,14 @@ function adaptIndentationFlexibleReplacement(oldText, newText, matchedText) {
240
251
  const matchedBaseIndent = leadingWhitespace(matchedBaseLine);
241
252
  if (oldBaseIndent === matchedBaseIndent)
242
253
  return newText;
254
+ // When oldText/matchedText/newText have differing line counts, only adapt the
255
+ // common-length prefix; leave the remaining newText lines untouched so a
256
+ // divergent tail is not corrupted by fabricated base-indent prefixes.
257
+ const commonLength = Math.min(oldLines.length, matchedLines.length, newLines.length);
243
258
  return newLines
244
259
  .map((line, index) => {
260
+ if (index >= commonLength)
261
+ return line;
245
262
  if (line.trim().length === 0)
246
263
  return line;
247
264
  const oldLine = oldLines[index];
@@ -312,6 +329,7 @@ export function applyEditsToNormalizedContent(normalizedContent, edits, path) {
312
329
  }
313
330
  const baseContent = normalizedContent;
314
331
  const matchedEdits = [];
332
+ let usedFuzzyMatch = false;
315
333
  for (let i = 0; i < normalizedEdits.length; i++) {
316
334
  const edit = normalizedEdits[i];
317
335
  const matches = findTextMatches(baseContent, edit.oldText);
@@ -322,6 +340,8 @@ export function applyEditsToNormalizedContent(normalizedContent, edits, path) {
322
340
  throw getDuplicateError(path, i, normalizedEdits.length, matches.length);
323
341
  }
324
342
  const match = matches[0];
343
+ if (match.strategy !== "exact")
344
+ usedFuzzyMatch = true;
325
345
  matchedEdits.push({
326
346
  editIndex: i,
327
347
  matchIndex: match.index,
@@ -348,7 +368,7 @@ export function applyEditsToNormalizedContent(normalizedContent, edits, path) {
348
368
  if (baseContent === newContent) {
349
369
  throw getNoChangeError(path, normalizedEdits.length);
350
370
  }
351
- return { baseContent, newContent };
371
+ return { baseContent, newContent, usedFuzzyMatch };
352
372
  }
353
373
  /** Generate a standard unified patch. */
354
374
  export function generateUnifiedPatch(path, oldContent, newContent, contextLines = 4) {
@@ -1,4 +1,5 @@
1
1
  import type { AgentTool } from "../../../agent-core/index.js";
2
+ import type { Stats } from "fs";
2
3
  import { type Static, Type } from "typebox";
3
4
  import type { ToolDefinition } from "../extensions/types.js";
4
5
  declare const editSchema: Type.TObject<{
@@ -28,6 +29,11 @@ export interface EditOperations {
28
29
  writeFile: (absolutePath: string, content: string) => Promise<void>;
29
30
  /** Check if file is readable and writable (throw if not) */
30
31
  access: (absolutePath: string) => Promise<void>;
32
+ /**
33
+ * Stat a file to obtain a freshness token (mtime). Used to detect stale reads
34
+ * between read and write. Optional — defaults to local fs.stat.
35
+ */
36
+ stat?: (absolutePath: string) => Promise<Stats>;
31
37
  }
32
38
  export interface EditToolOptions {
33
39
  /** Custom operations for file editing. Default: local filesystem */
@@ -1 +1 @@
1
- {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAG9D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AA6B7D,QAAA,MAAM,UAAU;;;;;;EASf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAMtD,MAAM,WAAW,eAAe;IAC/B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,qCAAqC;IACrC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,8BAA8B;IAC9B,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,4DAA4D;IAC5D,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAoFD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAsIhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG"}
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAG9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AA8B7D,QAAA,MAAM,UAAU;;;;;;EASf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAMtD,MAAM,WAAW,eAAe;IAC/B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,qCAAqC;IACrC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,8BAA8B;IAC9B,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,4DAA4D;IAC5D,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;CAChD;AASD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAqGD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAyJhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG"}