@j0hanz/filesystem-mcp 2.1.6 → 2.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/search.d.ts +29 -0
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +57 -8
- package/dist/core/search.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +8 -13
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/replace-in-files.d.ts.map +1 -1
- package/dist/tools/replace-in-files.js +21 -24
- package/dist/tools/replace-in-files.js.map +1 -1
- package/package.json +1 -1
package/dist/core/search.d.ts
CHANGED
|
@@ -43,6 +43,35 @@ export declare function compileRegex(pattern: string, options?: RegexCompileOpti
|
|
|
43
43
|
* free only in a `finally` that owns the compile.
|
|
44
44
|
*/
|
|
45
45
|
export declare function freeRegex(regex: Regex | undefined): void;
|
|
46
|
+
export interface RegexMatch {
|
|
47
|
+
/** The matched text. */
|
|
48
|
+
text: string;
|
|
49
|
+
/** Capture groups, 1-indexed in the pattern, 0-indexed here. */
|
|
50
|
+
groups: (string | undefined)[];
|
|
51
|
+
/** Named capture groups, when the pattern declares any. */
|
|
52
|
+
named: Record<string, string> | undefined;
|
|
53
|
+
/** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */
|
|
54
|
+
start: number;
|
|
55
|
+
/** End offset in UTF-16 code units. */
|
|
56
|
+
end: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Iterate a global pattern's non-overlapping matches with offsets JS can use.
|
|
60
|
+
*
|
|
61
|
+
* re2-wasm reports `index` and `lastIndex` in **code points**, while every JS
|
|
62
|
+
* string operation indexes in UTF-16 code units. The two agree only until the
|
|
63
|
+
* first astral character (most emoji), past which every offset is short by one
|
|
64
|
+
* per surrogate pair — slicing on the raw index cuts into the middle of
|
|
65
|
+
* neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the
|
|
66
|
+
* match's UTF-16 length to a code-point index, so an astral character inside a
|
|
67
|
+
* match can skip the next one. This is the single place that corrects both; no
|
|
68
|
+
* caller should read `match.index` or `regex.lastIndex` directly.
|
|
69
|
+
*
|
|
70
|
+
* Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop
|
|
71
|
+
* forever. The regex is global and shared across calls, so `lastIndex` is reset
|
|
72
|
+
* on entry.
|
|
73
|
+
*/
|
|
74
|
+
export declare function execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined>;
|
|
46
75
|
export interface SearchContentOptions {
|
|
47
76
|
caseSensitive?: boolean;
|
|
48
77
|
isRegex?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAIxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAI3C,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,KAAK,GAAG,GAAG,CAAC;AACxB,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAKD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,KAAK,CAWtF;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,IAAI,CAOxD;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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"}
|
package/dist/core/search.js
CHANGED
|
@@ -56,22 +56,71 @@ export function freeRegex(regex) {
|
|
|
56
56
|
// already deleted, or a re2-wasm build without embind disposal
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */
|
|
60
|
+
function codePointLength(text) {
|
|
61
|
+
let length = 0;
|
|
62
|
+
for (let i = 0; i < text.length; i++) {
|
|
63
|
+
if ((text.charCodeAt(i) & 0xfc00) === 0xd800 && (text.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
length++;
|
|
67
|
+
}
|
|
68
|
+
return length;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Iterate a global pattern's non-overlapping matches with offsets JS can use.
|
|
72
|
+
*
|
|
73
|
+
* re2-wasm reports `index` and `lastIndex` in **code points**, while every JS
|
|
74
|
+
* string operation indexes in UTF-16 code units. The two agree only until the
|
|
75
|
+
* first astral character (most emoji), past which every offset is short by one
|
|
76
|
+
* per surrogate pair — slicing on the raw index cuts into the middle of
|
|
77
|
+
* neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the
|
|
78
|
+
* match's UTF-16 length to a code-point index, so an astral character inside a
|
|
79
|
+
* match can skip the next one. This is the single place that corrects both; no
|
|
80
|
+
* caller should read `match.index` or `regex.lastIndex` directly.
|
|
81
|
+
*
|
|
82
|
+
* Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop
|
|
83
|
+
* forever. The regex is global and shared across calls, so `lastIndex` is reset
|
|
84
|
+
* on entry.
|
|
85
|
+
*/
|
|
86
|
+
export function* execMatches(regex, text) {
|
|
87
|
+
regex.lastIndex = 0;
|
|
88
|
+
// Cursor into `text`, carried forward across matches: RE2 reports them in
|
|
89
|
+
// ascending order, so the walk is O(text) in total rather than per match.
|
|
90
|
+
let cursorCp = 0;
|
|
91
|
+
let cursorU16 = 0;
|
|
92
|
+
let match;
|
|
93
|
+
while ((match = regex.exec(text)) !== null) {
|
|
94
|
+
while (cursorCp < match.index && cursorU16 < text.length) {
|
|
95
|
+
const isPair = (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&
|
|
96
|
+
(text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;
|
|
97
|
+
cursorU16 += isPair ? 2 : 1;
|
|
98
|
+
cursorCp++;
|
|
99
|
+
}
|
|
100
|
+
const matched = match[0] ?? '';
|
|
101
|
+
const start = cursorU16;
|
|
102
|
+
yield {
|
|
103
|
+
text: matched,
|
|
104
|
+
groups: match.slice(1),
|
|
105
|
+
named: match.groups,
|
|
106
|
+
start,
|
|
107
|
+
end: start + matched.length,
|
|
108
|
+
};
|
|
109
|
+
// Own the advance rather than trusting the wrapper's mixed-unit arithmetic.
|
|
110
|
+
regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
59
113
|
/**
|
|
60
114
|
* Find non-overlapping occurrences of a global regex in a single line, reporting
|
|
61
|
-
* the first match's column alongside the count.
|
|
62
|
-
* (e.g. `a*`) so they cannot loop forever.
|
|
115
|
+
* the first match's column alongside the count.
|
|
63
116
|
*/
|
|
64
117
|
function findLineMatches(regex, line) {
|
|
65
|
-
regex.lastIndex = 0;
|
|
66
118
|
let count = 0;
|
|
67
119
|
let column = -1;
|
|
68
|
-
|
|
69
|
-
while ((match = regex.exec(line)) !== null) {
|
|
120
|
+
for (const match of execMatches(regex, line)) {
|
|
70
121
|
if (column === -1)
|
|
71
|
-
column = match.
|
|
122
|
+
column = match.start;
|
|
72
123
|
count++;
|
|
73
|
-
if (match.index === regex.lastIndex)
|
|
74
|
-
regex.lastIndex++; // advance past zero-length match
|
|
75
124
|
if (count >= MAX_MATCHES_PER_LINE)
|
|
76
125
|
break;
|
|
77
126
|
}
|
package/dist/core/search.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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","sourcesContent":["import { stat as fsStat, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { StopReasonTracker } from './concurrency.js';\nimport { globEntries, type GlobEntry } from './glob.js';\nimport type { PathGuard } from './path.js';\nimport { escapeRegexLiteral } from './primitives.js';\nimport { getMaxTextFileSize } from './util.js';\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount?: number;\n}\n\nexport type Regex = RE2;\nexport interface RegexCompileOptions {\n caseSensitive?: boolean;\n}\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(pattern: string, options: RegexCompileOptions = {}): Regex {\n const flags = options.caseSensitive ? 'gu' : 'giu';\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count. Guards zero-length matches\n * (e.g. `a*`) so they cannot loop forever.\n */\nfunction findLineMatches(\n regex: Regex,\n line: string,\n): { count: number; column: number } | undefined {\n regex.lastIndex = 0;\n let count = 0;\n let column = -1;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(line)) !== null) {\n if (column === -1) column = match.index;\n count++;\n if (match.index === regex.lastIndex) regex.lastIndex++; // advance past zero-length match\n if (count >= MAX_MATCHES_PER_LINE) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n excludePatterns?: string[];\n respectGitignore?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n maxFileSize?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both call\n * only `hitMaxResults`/`hitAbort` (see concurrency.ts) — never\n * `hitMaxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\n/**\n * Owns the compiled pattern's lifetime. Pass `precompiled` to skip compiling\n * here — the caller compiled the same pattern with the same flags and keeps\n * owning the {@link freeRegex} call in that case.\n */\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n precompiled?: Regex,\n): Promise<SearchContentOutcome> {\n const regex =\n precompiled ??\n compileRegex(options.isRegex ? pattern || '' : escapeRegexLiteral(pattern || ''), {\n caseSensitive: Boolean(options.caseSensitive),\n });\n try {\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = options.maxFileSize ?? getMaxTextFileSize();\n\n const entries = globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n excludePatterns: options.excludePatterns ?? [],\n includeHidden: Boolean(options.includeHidden),\n respectGitignore: Boolean(options.respectGitignore),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (matches.length >= maxResults) break;\n\n // Skip oversized files before reading to avoid unbounded memory use. Count\n // them: \"no matches\" for a reason other than the pattern must be visible.\n try {\n const stats = await fsStat(entry.path);\n if (stats.size > maxFileSize) {\n skippedTooLarge++;\n continue;\n }\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n\n filesScanned++;\n\n try {\n const content = await readFile(entry.path, { encoding: 'utf-8', signal: options.signal });\n const lines = content.split('\\n');\n let matchedFile = false;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n // One scan per line: findLineMatches resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = findLineMatches(regex, line);\n if (found) {\n matchedFile = true;\n matchingLines++;\n matches.push({\n file: entry.path,\n line: i + 1,\n column: found.column,\n content: line,\n matchCount: found.count,\n });\n if (matches.length >= maxResults) break;\n }\n }\n if (matchedFile) filesMatched++;\n } catch {\n // A read failure while the signal is aborted IS the abort, not an\n // unreadable file — stop rather than spend another iteration and then\n // report a cut-short scan as complete.\n if (options.signal?.aborted) {\n counters.stoppedByAbort = true;\n break;\n }\n // ignore read errors (e.g. binary files)\n }\n }\n\n const tracker = new StopReasonTracker();\n if (matches.length >= maxResults) tracker.hitMaxResults();\n if (counters.stoppedByAbort) tracker.hitAbort();\n // resolve() is StoppedReason | undefined, but this scan only records\n // maxResults/timeout stops (see the summary type's narrowing note).\n const stoppedReason = tracker.resolve() as 'maxResults' | 'timeout' | undefined;\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: tracker.truncated,\n skippedInaccessible: counters.skippedInaccessible,\n skippedTooLarge,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n if (precompiled === undefined) freeRegex(regex);\n }\n}\n\nasync function* guardedEntries(\n entries: AsyncIterable<GlobEntry>,\n pathGuard: PathGuard,\n signal: AbortSignal | undefined,\n counters: { skippedInaccessible: number; stoppedByAbort: boolean },\n): AsyncGenerator<GlobEntry> {\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan.\n for await (const entry of entries) {\n if (signal?.aborted) {\n counters.stoppedByAbort = true;\n return;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n yield entry;\n }\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n excludePatterns: string[],\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n respectGitignore?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never hit `hitMaxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const entries = globEntries({\n cwd: directory,\n pattern,\n excludePatterns,\n includeHidden: Boolean(options.includeHidden),\n respectGitignore: Boolean(options.respectGitignore),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n const tracker = new StopReasonTracker();\n if (results.length >= maxResults) tracker.hitMaxResults();\n if (counters.stoppedByAbort) tracker.hitAbort();\n // resolve() is StoppedReason | undefined, but this scan only records\n // maxResults/timeout stops (see the summary type's narrowing note).\n const stoppedReason = tracker.resolve() as 'maxResults' | 'timeout' | undefined;\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: tracker.truncated,\n skippedInaccessible: counters.skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAkB,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAgB/C,+EAA+E;AAC/E,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,UAA+B,EAAE;IAC7E,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IACnD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC;YAAE,MAAM,KAAK,CAAC;QACjD,MAAM,IAAI,WAAW,CACnB,GAAG,KAAK,CAAC,OAAO,iEAAiE,EACjF,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,KAAwB;IAChD,MAAM,MAAM,GAAI,KAAsE,EAAE,OAAO,CAAC;IAChG,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;AACH,CAAC;AAeD,qFAAqF;AACrF,SAAS,eAAe,CAAC,IAAY;IACnC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,EAAE,CAAC;YAC7F,CAAC,EAAE,CAAC;QACN,CAAC;QACD,MAAM,EAAE,CAAC;IACX,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,SAAS,CAAC,CAAC,WAAW,CAAC,KAAY,EAAE,IAAY;IACrD,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;IACpB,0EAA0E;IAC1E,0EAA0E;IAC1E,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,KAA0B,CAAC;IAC/B,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,OAAO,QAAQ,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACzD,MAAM,MAAM,GACV,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM;gBAChD,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;YACvD,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5B,QAAQ,EAAE,CAAC;QACb,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,SAAS,CAAC;QACxB,MAAM;YACJ,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YACtB,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,KAAK;YACL,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM;SAC5B,CAAC;QACF,4EAA4E;QAC5E,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;IACxF,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CACtB,KAAY,EACZ,IAAY;IAEZ,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC;IAChB,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;QAC7C,IAAI,MAAM,KAAK,CAAC,CAAC;YAAE,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;QACxC,KAAK,EAAE,CAAC;QACR,IAAI,KAAK,IAAI,oBAAoB;YAAE,MAAM;IAC3C,CAAC;IACD,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACnD,CAAC;AAyCD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,OAAe,EACf,OAA6B,EAC7B,SAAoB,EACpB,WAAmB;IAEnB,MAAM,KAAK,GACT,WAAW;QACX,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE;YAChF,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;SAC9C,CAAC,CAAC;IACL,IAAI,CAAC;QACH,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;QAC7C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,EAAE,CAAC;QAEhE,MAAM,OAAO,GAAG,WAAW,CAAC;YAC1B,GAAG,EAAE,SAAS;YACd,OAAO,EAAE,OAAO,CAAC,WAAW,IAAI,MAAM;YACtC,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,EAAE;YAC9C,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;YAC7C,gBAAgB,EAAE,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC;YACnD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,GAAG;YACjC,cAAc,EAAE,IAAI;SACrB,CAAC,CAAC;QAEH,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,aAAa,GAAG,CAAC,CAAC;QACtB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,MAAM,QAAQ,GAAG,EAAE,mBAAmB,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;QAEnE,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;YACvF,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;gBAAE,MAAM;YAExC,2EAA2E;YAC3E,0EAA0E;YAC1E,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACvC,IAAI,KAAK,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;oBAC7B,eAAe,EAAE,CAAC;oBAClB,SAAS;gBACX,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ,CAAC,mBAAmB,EAAE,CAAC;gBAC/B,SAAS;YACX,CAAC;YAED,YAAY,EAAE,CAAC;YAEf,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC1F,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAClC,IAAI,WAAW,GAAG,KAAK,CAAC;gBACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACtB,IAAI,IAAI,KAAK,SAAS;wBAAE,SAAS;oBACjC,oEAAoE;oBACpE,8CAA8C;oBAC9C,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;oBAC3C,IAAI,KAAK,EAAE,CAAC;wBACV,WAAW,GAAG,IAAI,CAAC;wBACnB,aAAa,EAAE,CAAC;wBAChB,OAAO,CAAC,IAAI,CAAC;4BACX,IAAI,EAAE,KAAK,CAAC,IAAI;4BAChB,IAAI,EAAE,CAAC,GAAG,CAAC;4BACX,MAAM,EAAE,KAAK,CAAC,MAAM;4BACpB,OAAO,EAAE,IAAI;4BACb,UAAU,EAAE,KAAK,CAAC,KAAK;yBACxB,CAAC,CAAC;wBACH,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;4BAAE,MAAM;oBAC1C,CAAC;gBACH,CAAC;gBACD,IAAI,WAAW;oBAAE,YAAY,EAAE,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,kEAAkE;gBAClE,sEAAsE;gBACtE,uCAAuC;gBACvC,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;oBAC5B,QAAQ,CAAC,cAAc,GAAG,IAAI,CAAC;oBAC/B,MAAM;gBACR,CAAC;gBACD,yCAAyC;YAC3C,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;YAAE,OAAO,CAAC,aAAa,EAAE,CAAC;QAC1D,IAAI,QAAQ,CAAC,cAAc;YAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;QAChD,qEAAqE;QACrE,oEAAoE;QACpE,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,EAA0C,CAAC;QAEhF,OAAO;YACL,QAAQ,EAAE,SAAS;YACnB,OAAO;YACP,OAAO,EAAE;gBACP,aAAa;gBACb,YAAY;gBACZ,YAAY;gBACZ,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;gBACjD,eAAe;gBACf,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC5C;SACF,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,WAAW,KAAK,SAAS;YAAE,SAAS,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,cAAc,CAC5B,OAAiC,EACjC,SAAoB,EACpB,MAA+B,EAC/B,QAAkE;IAElE,6EAA6E;IAC7E,yEAAyE;IACzE,4DAA4D;IAC5D,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAClC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,QAAQ,CAAC,cAAc,GAAG,IAAI,CAAC;YAC/B,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,CAAC,mBAAmB,EAAE,CAAC;YAC/B,SAAS;QACX,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,SAAiB,EACjB,OAAe,EACf,eAAyB,EACzB,OAOC,EACD,SAAoB;IAapB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;IAC7C,MAAM,OAAO,GAAG,WAAW,CAAC;QAC1B,GAAG,EAAE,SAAS;QACd,OAAO;QACP,eAAe;QACf,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QAC7C,gBAAgB,EAAE,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC;QACnD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,GAAG;QACjC,cAAc,EAAE,IAAI;KACrB,CAAC,CAAC;IACH,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,MAAM,QAAQ,GAAG,EAAE,mBAAmB,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;IAEnE,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;QACvF,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;YAAE,MAAM;QACxC,YAAY,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,yEAAyE;IACzE,mEAAmE;IACnE,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAE,CAAC;IACxC,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;QAAE,OAAO,CAAC,aAAa,EAAE,CAAC;IAC1D,IAAI,QAAQ,CAAC,cAAc;QAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;IAChD,qEAAqE;IACrE,oEAAoE;IACpE,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,EAA0C,CAAC;IAEhF,OAAO;QACL,QAAQ,EAAE,SAAS;QACnB,OAAO;QACP,OAAO,EAAE;YACP,OAAO,EAAE,OAAO,CAAC,MAAM;YACvB,YAAY;YACZ,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;YACjD,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5C;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { stat as fsStat, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { StopReasonTracker } from './concurrency.js';\nimport { globEntries, type GlobEntry } from './glob.js';\nimport type { PathGuard } from './path.js';\nimport { escapeRegexLiteral } from './primitives.js';\nimport { getMaxTextFileSize } from './util.js';\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount?: number;\n}\n\nexport type Regex = RE2;\nexport interface RegexCompileOptions {\n caseSensitive?: boolean;\n}\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(pattern: string, options: RegexCompileOptions = {}): Regex {\n const flags = options.caseSensitive ? 'gu' : 'giu';\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\nexport interface RegexMatch {\n /** The matched text. */\n text: string;\n /** Capture groups, 1-indexed in the pattern, 0-indexed here. */\n groups: (string | undefined)[];\n /** Named capture groups, when the pattern declares any. */\n named: Record<string, string> | undefined;\n /** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */\n start: number;\n /** End offset in UTF-16 code units. */\n end: number;\n}\n\n/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */\nfunction codePointLength(text: string): number {\n let length = 0;\n for (let i = 0; i < text.length; i++) {\n if ((text.charCodeAt(i) & 0xfc00) === 0xd800 && (text.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {\n i++;\n }\n length++;\n }\n return length;\n}\n\n/**\n * Iterate a global pattern's non-overlapping matches with offsets JS can use.\n *\n * re2-wasm reports `index` and `lastIndex` in **code points**, while every JS\n * string operation indexes in UTF-16 code units. The two agree only until the\n * first astral character (most emoji), past which every offset is short by one\n * per surrogate pair — slicing on the raw index cuts into the middle of\n * neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the\n * match's UTF-16 length to a code-point index, so an astral character inside a\n * match can skip the next one. This is the single place that corrects both; no\n * caller should read `match.index` or `regex.lastIndex` directly.\n *\n * Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop\n * forever. The regex is global and shared across calls, so `lastIndex` is reset\n * on entry.\n */\nexport function* execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined> {\n regex.lastIndex = 0;\n // Cursor into `text`, carried forward across matches: RE2 reports them in\n // ascending order, so the walk is O(text) in total rather than per match.\n let cursorCp = 0;\n let cursorU16 = 0;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n while (cursorCp < match.index && cursorU16 < text.length) {\n const isPair =\n (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&\n (text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;\n cursorU16 += isPair ? 2 : 1;\n cursorCp++;\n }\n const matched = match[0] ?? '';\n const start = cursorU16;\n yield {\n text: matched,\n groups: match.slice(1),\n named: match.groups,\n start,\n end: start + matched.length,\n };\n // Own the advance rather than trusting the wrapper's mixed-unit arithmetic.\n regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count.\n */\nfunction findLineMatches(\n regex: Regex,\n line: string,\n): { count: number; column: number } | undefined {\n let count = 0;\n let column = -1;\n for (const match of execMatches(regex, line)) {\n if (column === -1) column = match.start;\n count++;\n if (count >= MAX_MATCHES_PER_LINE) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n excludePatterns?: string[];\n respectGitignore?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n maxFileSize?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both call\n * only `hitMaxResults`/`hitAbort` (see concurrency.ts) — never\n * `hitMaxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\n/**\n * Owns the compiled pattern's lifetime. Pass `precompiled` to skip compiling\n * here — the caller compiled the same pattern with the same flags and keeps\n * owning the {@link freeRegex} call in that case.\n */\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n precompiled?: Regex,\n): Promise<SearchContentOutcome> {\n const regex =\n precompiled ??\n compileRegex(options.isRegex ? pattern || '' : escapeRegexLiteral(pattern || ''), {\n caseSensitive: Boolean(options.caseSensitive),\n });\n try {\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = options.maxFileSize ?? getMaxTextFileSize();\n\n const entries = globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n excludePatterns: options.excludePatterns ?? [],\n includeHidden: Boolean(options.includeHidden),\n respectGitignore: Boolean(options.respectGitignore),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (matches.length >= maxResults) break;\n\n // Skip oversized files before reading to avoid unbounded memory use. Count\n // them: \"no matches\" for a reason other than the pattern must be visible.\n try {\n const stats = await fsStat(entry.path);\n if (stats.size > maxFileSize) {\n skippedTooLarge++;\n continue;\n }\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n\n filesScanned++;\n\n try {\n const content = await readFile(entry.path, { encoding: 'utf-8', signal: options.signal });\n const lines = content.split('\\n');\n let matchedFile = false;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n // One scan per line: findLineMatches resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = findLineMatches(regex, line);\n if (found) {\n matchedFile = true;\n matchingLines++;\n matches.push({\n file: entry.path,\n line: i + 1,\n column: found.column,\n content: line,\n matchCount: found.count,\n });\n if (matches.length >= maxResults) break;\n }\n }\n if (matchedFile) filesMatched++;\n } catch {\n // A read failure while the signal is aborted IS the abort, not an\n // unreadable file — stop rather than spend another iteration and then\n // report a cut-short scan as complete.\n if (options.signal?.aborted) {\n counters.stoppedByAbort = true;\n break;\n }\n // ignore read errors (e.g. binary files)\n }\n }\n\n const tracker = new StopReasonTracker();\n if (matches.length >= maxResults) tracker.hitMaxResults();\n if (counters.stoppedByAbort) tracker.hitAbort();\n // resolve() is StoppedReason | undefined, but this scan only records\n // maxResults/timeout stops (see the summary type's narrowing note).\n const stoppedReason = tracker.resolve() as 'maxResults' | 'timeout' | undefined;\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: tracker.truncated,\n skippedInaccessible: counters.skippedInaccessible,\n skippedTooLarge,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n if (precompiled === undefined) freeRegex(regex);\n }\n}\n\nasync function* guardedEntries(\n entries: AsyncIterable<GlobEntry>,\n pathGuard: PathGuard,\n signal: AbortSignal | undefined,\n counters: { skippedInaccessible: number; stoppedByAbort: boolean },\n): AsyncGenerator<GlobEntry> {\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan.\n for await (const entry of entries) {\n if (signal?.aborted) {\n counters.stoppedByAbort = true;\n return;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n yield entry;\n }\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n excludePatterns: string[],\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n respectGitignore?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never hit `hitMaxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const entries = globEntries({\n cwd: directory,\n pattern,\n excludePatterns,\n includeHidden: Boolean(options.includeHidden),\n respectGitignore: Boolean(options.respectGitignore),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n const tracker = new StopReasonTracker();\n if (results.length >= maxResults) tracker.hitMaxResults();\n if (counters.stoppedByAbort) tracker.hitAbort();\n // resolve() is StoppedReason | undefined, but this scan only records\n // maxResults/timeout stops (see the summary type's narrowing note).\n const stoppedReason = tracker.resolve() as 'maxResults' | 'timeout' | undefined;\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: tracker.truncated,\n skippedInaccessible: counters.skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
|
package/dist/tools/edit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"AA2cA,eAAO,MAAM,IAAI,mCAwEf,CAAC"}
|
package/dist/tools/edit.js
CHANGED
|
@@ -5,7 +5,7 @@ import { ErrorCode, FsError } from '../core/errors.js';
|
|
|
5
5
|
import { buildWrittenFileMeta } from '../core/file-uri.js';
|
|
6
6
|
import { escapeRegexLiteral } from '../core/primitives.js';
|
|
7
7
|
import { defaultFalseBoolean, FileKind, isBlank, IsoDateTime, NonNegInt, OperationSummarySchema, PerFileErrorSchema, PositiveInt, RequiredPath, singleOrBatchAccessPaths, } from '../core/schema.js';
|
|
8
|
-
import { compileRegex, freeRegex } from '../core/search.js';
|
|
8
|
+
import { compileRegex, execMatches, freeRegex } from '../core/search.js';
|
|
9
9
|
import { isTotalFailure, runOverPaths } from './batch.js';
|
|
10
10
|
import { defineTool } from './define.js';
|
|
11
11
|
const EditSpecSchema = z
|
|
@@ -137,20 +137,15 @@ function findEditMatch(content, oldText, ignoreWhitespace) {
|
|
|
137
137
|
.replace(/[^\S\n]+/g, '[^\\S\\n]*');
|
|
138
138
|
const regex = compileRegex(pattern, { caseSensitive: true });
|
|
139
139
|
try {
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (match ===
|
|
145
|
-
return undefined;
|
|
146
|
-
// RE2ExecArray types group 0 as optional. A successful match always has it;
|
|
147
|
-
// an empty one would name a zero-length span, which cannot be replaced.
|
|
148
|
-
const matched = match[0];
|
|
149
|
-
if (matched === undefined || matched.length === 0)
|
|
140
|
+
// execMatches resets lastIndex and reports UTF-16 offsets; the raw RE2
|
|
141
|
+
// index counts code points and would splice off-by-one per emoji.
|
|
142
|
+
const { value: match } = execMatches(regex, content).next();
|
|
143
|
+
// A zero-length match names an empty span, which cannot be replaced.
|
|
144
|
+
if (match === undefined || match.text.length === 0)
|
|
150
145
|
return undefined;
|
|
151
146
|
return {
|
|
152
|
-
startIndex: match.
|
|
153
|
-
length:
|
|
147
|
+
startIndex: match.start,
|
|
148
|
+
length: match.text.length,
|
|
154
149
|
};
|
|
155
150
|
}
|
|
156
151
|
finally {
|
package/dist/tools/edit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit.js","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { basename } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { computeDiffStats, unifiedPatch } from '../core/diff.js';\nimport { ErrorCode, FsError } from '../core/errors.js';\nimport { buildWrittenFileMeta, type WrittenFileMeta } from '../core/file-uri.js';\nimport { escapeRegexLiteral } from '../core/primitives.js';\nimport {\n defaultFalseBoolean,\n FileKind,\n isBlank,\n IsoDateTime,\n NonNegInt,\n OperationSummarySchema,\n PerFileErrorSchema,\n PositiveInt,\n RequiredPath,\n singleOrBatchAccessPaths,\n} from '../core/schema.js';\nimport { compileRegex, freeRegex } from '../core/search.js';\nimport type { ResourceStore } from '../core/store.js';\nimport { isTotalFailure, runOverPaths } from './batch.js';\nimport { defineTool, type ToolCtx } from './define.js';\n\nconst EditSpecSchema = z\n .strictObject({\n oldText: z\n .string()\n .min(1, 'oldText required')\n .refine((val) => !isBlank(val), {\n message: 'oldText cannot be empty or whitespace-only',\n })\n .describe(\n 'Exact literal text to locate in the file. Must include 3-5 lines of context to ensure uniqueness and avoid matching the wrong block.',\n )\n .meta({ examples: ['const x = 1;', 'function oldName('] }),\n newText: z\n .string()\n .describe('Replacement text. Use an empty string to delete the matched oldText.')\n .meta({ examples: ['const x = 2;', 'function newName(', ''] }),\n })\n // The one document in this server where a `$ref` pays: this subschema is used\n // at both `edits` and `files[].edits`, so an `id` hoists it into `$defs` once\n // instead of inlining ~1.3 kB twice. Shared schemas used once per document\n // deliberately carry no `id` — there a `$ref` costs more than it saves.\n .meta({ id: 'EditSpec' });\n\nconst MAX_MULTI_FILES = 5;\nconst MAX_EDITS_PER_FILE = 100;\n\nconst EditFileInputSchema = z\n .strictObject({\n path: RequiredPath.optional().describe('Single file path; mutually exclusive with files'),\n edits: z\n .array(EditSpecSchema)\n .min(1)\n .max(MAX_EDITS_PER_FILE)\n .optional()\n .describe('Replacements applied to path; not allowed when using files'),\n files: z\n .array(\n z.strictObject({\n path: RequiredPath,\n edits: z\n .array(EditSpecSchema)\n .min(1)\n .max(MAX_EDITS_PER_FILE)\n .describe('Replacements to apply to this specific file'),\n }),\n )\n .min(1)\n .max(MAX_MULTI_FILES)\n .optional()\n .describe('Per-file entries (batch mode)'),\n dryRun: defaultFalseBoolean(\n 'Preview diffs without writing to disk (default: false = apply edits)',\n ),\n ignoreWhitespace: defaultFalseBoolean(\n 'Ignore leading/trailing whitespace differences when matching oldText',\n ),\n })\n .superRefine((value, ctx) => {\n const hasPath = value.path !== undefined;\n const hasFiles = value.files !== undefined;\n if (hasPath === hasFiles) {\n ctx.addIssue({\n code: 'custom',\n path: ['path'],\n message: \"Provide exactly one of 'path' or 'files'\",\n input: value,\n });\n }\n if (hasPath && value.edits === undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['edits'],\n message: \"'edits' required when using 'path'\",\n input: value,\n });\n }\n if (hasFiles && value.edits !== undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['edits'],\n message: \"'edits' not allowed with 'files'; each file carries its own edits\",\n input: value,\n });\n }\n })\n // Mirror the superRefine on the wire: exactly one input mode, and single-file\n // mode carries its own `edits`. Without `edits` in the first branch, `{ path }`\n // alone validated against the published schema and was then rejected at\n // runtime — the model had no way to see that coming.\n .meta({ oneOf: [{ required: ['path', 'edits'] }, { required: ['files'] }] });\n\nconst PerFileResultSchema = z.strictObject({\n path: z.string().describe('Resolved absolute path of the edited file'),\n size: NonNegInt.describe('File size in bytes after edits'),\n lineCount: NonNegInt.describe('Number of lines in the file after edits'),\n mimeType: z.string().describe('Detected MIME type of the file'),\n kind: FileKind.describe('Broad file kind: text, binary, image, audio, or pdf'),\n resourceUri: z\n .string()\n .optional()\n .describe(\n 'Resource URI pointing to the updated file content; omitted when no edit matched (appliedEdits is 0) and the file was left untouched',\n ),\n modified: IsoDateTime.describe('Last modification timestamp after edits (ISO 8601 UTC)'),\n appliedEdits: NonNegInt.describe('Number of edits successfully applied'),\n linesAdded: NonNegInt.optional().describe('Net lines added by all applied edits'),\n linesRemoved: NonNegInt.optional().describe('Net lines removed by all applied edits'),\n diff: z.string().optional().describe('Unified diff of all changes (present only in dryRun mode)'),\n unmatchedEdits: z\n .array(z.string())\n .optional()\n .describe('oldText values that did not match any content in the file'),\n lineRange: z\n .tuple([PositiveInt, PositiveInt])\n .optional()\n .describe('Line range [firstLine, lastLine] covering all applied edits'),\n});\n\nconst EditPerPathSchema = z.strictObject({\n path: z.string().describe('Requested file path'),\n value: PerFileResultSchema.optional().describe('Edit result; present on success'),\n error: PerFileErrorSchema.optional().describe('Error details; present on failure'),\n});\n\nconst EditFileOutputSchema = z.strictObject({\n results: z\n .array(EditPerPathSchema)\n .describe('Per-path edit results ordered to match the input paths'),\n summary: OperationSummarySchema,\n});\n\ntype EditFileValue = z.infer<typeof PerFileResultSchema>;\n\ninterface TextRange {\n startIndex: number;\n length: number;\n}\n\ninterface EditResult {\n content: string;\n appliedEdits: number;\n unmatchedEdits: string[];\n linesAdded: number;\n linesRemoved: number;\n diff?: string;\n lineRange?: [number, number];\n}\n\nfunction findEditMatch(\n content: string,\n oldText: string,\n ignoreWhitespace: boolean,\n): TextRange | undefined {\n if (ignoreWhitespace) {\n // Make whitespace flexible (tolerate indentation/spacing differences)\n // without letting it cross line boundaries: a whitespace run that contains\n // a newline keeps at least one newline, so a single-line oldText cannot\n // match across a newline and a multi-line oldText cannot collapse onto one\n // line. Horizontal whitespace stays mandatory between word characters so\n // adjacent identifiers are not merged.\n const pattern = escapeRegexLiteral(oldText)\n .replace(/[^\\S\\n]*\\n\\s*/g, '[^\\\\S\\\\n]*\\\\n+[^\\\\S\\\\n]*')\n .replace(/(\\w)[^\\S\\n]+(\\w)/g, '$1[^\\\\S\\\\n]+$2')\n .replace(/[^\\S\\n]+/g, '[^\\\\S\\\\n]*');\n const regex = compileRegex(pattern, { caseSensitive: true });\n try {\n // The compiled regex is global, so lastIndex may point past a previous\n // exec — reset before searching.\n regex.lastIndex = 0;\n const match = regex.exec(content);\n\n if (match === null) return undefined;\n // RE2ExecArray types group 0 as optional. A successful match always has it;\n // an empty one would name a zero-length span, which cannot be replaced.\n const matched = match[0];\n if (matched === undefined || matched.length === 0) return undefined;\n\n return {\n startIndex: match.index,\n length: matched.length,\n };\n } finally {\n // The compiled pattern owns wasm memory re2-wasm never reclaims on its\n // own; its lifetime is this one match.\n freeRegex(regex);\n }\n }\n\n const index = content.indexOf(oldText);\n if (index === -1) {\n return undefined;\n }\n\n return {\n startIndex: index,\n length: oldText.length,\n };\n}\n\nfunction replaceEditMatch(content: string, match: TextRange, newText: string): string {\n return (\n content.slice(0, match.startIndex) + newText + content.slice(match.startIndex + match.length)\n );\n}\n\n/**\n * Compute the 1-indexed line range of changed lines in `modified` relative to\n * `original`, using a common-prefix/suffix line scan. This is a conservative\n * bound (it widens to cover independent changes between matching bookends), but\n * it is computed against the FINAL content so it is not skewed by earlier edits\n * inserting or removing lines — which the per-edit merge could not account for.\n */\nfunction computeChangedLineRange(original: string, modified: string): [number, number] | undefined {\n const origLines = original.split('\\n');\n const modLines = modified.split('\\n');\n const minLen = Math.min(origLines.length, modLines.length);\n\n let firstChanged = -1;\n for (let i = 0; i < minLen; i++) {\n if (origLines[i] !== modLines[i]) {\n firstChanged = i;\n break;\n }\n }\n if (firstChanged === -1 && origLines.length === modLines.length) return undefined;\n if (firstChanged === -1) firstChanged = minLen; // pure append/trim at the tail\n\n let lastChangedMod = modLines.length - 1;\n let lastChangedOrig = origLines.length - 1;\n while (\n lastChangedMod > firstChanged &&\n lastChangedOrig >= 0 &&\n modLines[lastChangedMod] === origLines[lastChangedOrig]\n ) {\n lastChangedMod--;\n lastChangedOrig--;\n }\n\n // When the modified content is a strict prefix of the original (a tail trim on\n // a file with no final newline), the loop above never runs and lastChangedMod\n // lands one below firstChanged. Clamp so the range is never inverted.\n return [firstChanged + 1, Math.max(firstChanged, lastChangedMod) + 1];\n}\n\nfunction buildEditFileValue(\n validPath: string,\n meta: EditFileMetadata,\n modified: string,\n result: EditResult,\n): EditFileValue {\n return {\n path: validPath,\n size: meta.size,\n lineCount: meta.lineCount,\n mimeType: meta.mimeType,\n kind: meta.kind,\n ...(meta.resourceUri !== undefined ? { resourceUri: meta.resourceUri } : {}),\n modified,\n appliedEdits: result.appliedEdits,\n ...(result.appliedEdits > 0\n ? { linesAdded: result.linesAdded, linesRemoved: result.linesRemoved }\n : {}),\n ...(result.unmatchedEdits.length > 0 ? { unmatchedEdits: result.unmatchedEdits } : {}),\n ...(result.diff ? { diff: result.diff } : {}),\n ...(result.lineRange ? { lineRange: result.lineRange } : {}),\n };\n}\n\nfunction finalizeEditResult(\n originalContent: string,\n updatedContent: string,\n appliedEdits: number,\n unmatchedEdits: string[],\n lineRange: EditResult['lineRange'],\n): EditResult {\n const { linesAdded, linesRemoved } =\n appliedEdits > 0\n ? computeDiffStats(originalContent, updatedContent)\n : { linesAdded: 0, linesRemoved: 0 };\n\n return {\n content: updatedContent,\n appliedEdits,\n unmatchedEdits,\n linesAdded,\n linesRemoved,\n ...(lineRange ? { lineRange } : {}),\n };\n}\n\n/** {@link WrittenFileMeta}, with `resourceUri` widened: an edit that matched\n * nothing has no updated content to point at. */\ntype EditFileMetadata = Omit<WrittenFileMeta, 'resourceUri'> & {\n resourceUri: string | undefined;\n};\n\nfunction buildEditFileMetadata(\n content: string,\n validPath: string,\n appliedEdits: number,\n resourceStore: ResourceStore | undefined,\n): EditFileMetadata {\n const meta = buildWrittenFileMeta(validPath, content, resourceStore);\n // Omitted rather than empty-stringed when nothing matched: `\"\"` satisfied the\n // schema's `string` and then failed every resources/read a client tried it on.\n const written = appliedEdits > 0;\n return {\n ...meta,\n resourceUri: written ? meta.resourceUri : undefined,\n resourceLink: written ? meta.resourceLink : undefined,\n };\n}\n\nfunction applyEdits(\n content: string,\n edits: z.infer<typeof EditSpecSchema>[],\n ignoreWhitespace: boolean,\n): EditResult {\n let newContent = content;\n let appliedEdits = 0;\n const unmatchedEdits: string[] = [];\n\n for (const edit of edits) {\n const match = findEditMatch(newContent, edit.oldText, ignoreWhitespace);\n\n if (!match) {\n unmatchedEdits.push(edit.oldText);\n continue;\n }\n\n newContent = replaceEditMatch(newContent, match, edit.newText);\n appliedEdits += 1;\n }\n\n // Compute the line range against the final content so earlier edits whose\n // lines were shifted by later edits are still covered.\n const lineRange = appliedEdits > 0 ? computeChangedLineRange(content, newContent) : undefined;\n\n return finalizeEditResult(content, newContent, appliedEdits, unmatchedEdits, lineRange);\n}\n\ninterface EditFileOptions {\n dryRun: boolean;\n ignoreWhitespace: boolean;\n}\n\nasync function handleEditFile(\n filePath: string,\n edits: z.infer<typeof EditSpecSchema>[],\n options: EditFileOptions,\n ctx: ToolCtx,\n): Promise<{ file: EditFileValue; resourceLink?: ContentBlock }> {\n const { validPath, content } = await ctx.fs.readEditableText(filePath, {\n signal: ctx.signal,\n });\n const editResult = applyEdits(content, edits, options.ignoreWhitespace);\n\n if (options.dryRun) {\n if (editResult.appliedEdits > 0) {\n editResult.diff = unifiedPatch(basename(validPath), content, editResult.content);\n }\n\n // Nothing was written, so there is no updated content to point a\n // resourceUri or a resource_link at — the file on disk is still the one the\n // caller already has. Same rule the appliedEdits-is-0 case follows: no\n // write, no link.\n const meta = buildEditFileMetadata(editResult.content, validPath, 0, ctx.resourceStore);\n return {\n file: buildEditFileValue(validPath, meta, new Date().toISOString(), editResult),\n ...(meta.resourceLink ? { resourceLink: meta.resourceLink } : {}),\n };\n }\n\n if (editResult.unmatchedEdits.length > 0) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n `${editResult.unmatchedEdits.length} edit(s) failed to match. Verify oldText matches exact file content.`,\n filePath,\n );\n }\n\n if (editResult.appliedEdits > 0) {\n await ctx.fs.writeFile(filePath, editResult.content, {\n encoding: 'utf-8',\n signal: ctx.signal,\n });\n ctx.log?.(\n 'info',\n `edit: ${filePath} (${editResult.appliedEdits} edits, +${editResult.linesAdded}/-${editResult.linesRemoved})`,\n 'edit',\n );\n }\n\n // `modified` is read from a post-write stat and is advisory: under a concurrent\n // writer it may reflect that writer's mtime while `size`/content below come from\n // this edit's atomic write. The file content itself is always consistent.\n const { stats: fileStats } = await ctx.fs.stat(filePath, { signal: ctx.signal });\n const meta = buildEditFileMetadata(\n editResult.content,\n validPath,\n editResult.appliedEdits,\n ctx.resourceStore,\n );\n return {\n file: buildEditFileValue(validPath, meta, fileStats.mtime.toISOString(), editResult),\n ...(meta.resourceLink ? { resourceLink: meta.resourceLink } : {}),\n };\n}\n\nfunction formatEditSummary(\n results: readonly z.infer<typeof EditPerPathSchema>[],\n dryRun: boolean,\n): string {\n const tag = dryRun ? ' [dry run]' : '';\n // Single-file failure: say why here rather than making the caller read\n // structuredContent for the one message that can apply.\n const [only] = results;\n if (results.length === 1 && only?.error) {\n return `edit: ${only.path} FAILED — ${only.error.message}${tag}`;\n }\n const tokens = results.map((r) => {\n if (r.error) return `${basename(r.path)} FAILED`;\n const v = r.value;\n if (!v) return `${basename(r.path)} (no result)`;\n if (v.unmatchedEdits && v.unmatchedEdits.length > 0) return `${basename(v.path)} NO MATCH`;\n const added = v.linesAdded ?? 0;\n const removed = v.linesRemoved ?? 0;\n if (added === 0 && removed === 0) return `${basename(v.path)} (no change)`;\n return `${basename(v.path)} +${String(added)} -${String(removed)}`;\n });\n\n const failed = results.filter((r) => r.error !== undefined).length;\n const ok = results.length - failed;\n const ratio = failed > 0 ? ` (${String(ok)}/${String(results.length)} ok)` : '';\n return `edit: ${tokens.join(' · ')}${ratio}${tag}`;\n}\n\nexport const EDIT = defineTool({\n name: 'edit',\n title: 'Edit Files',\n description:\n 'Apply sequential literal string replacements to one or more files (max 5 files per call). ' +\n 'Modes: single-file { path, edits } or per-file { files: [{ path, edits }] }. ' +\n 'oldText must match file content exactly; include 3-5 lines of surrounding context to ensure uniqueness. ' +\n 'Set dryRun=true to preview diffs without writing. ' +\n 'For glob-based bulk regex replacement across many files, use replace_text instead.',\n input: EditFileInputSchema,\n output: EditFileOutputSchema,\n annotations: {\n readOnlyHint: false,\n idempotentHint: false,\n destructiveHint: true,\n openWorldHint: false,\n },\n progress: (args) => {\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n let subject: string;\n if (args.path !== undefined) {\n subject = basename(args.path);\n } else if (args.files !== undefined) {\n subject = `${args.files.length} files`;\n } else {\n subject = 'files';\n }\n return { label: `Edit${dryLabel}`, subject };\n },\n accessPaths: singleOrBatchAccessPaths,\n run: async (args, ctx) => {\n const sharedEdits = args.edits ?? [];\n const batchInput = args.path !== undefined ? { path: args.path } : { files: args.files ?? [] };\n\n const options: EditFileOptions = {\n dryRun: args.dryRun,\n ignoreWhitespace: args.ignoreWhitespace,\n };\n\n const batch = await runOverPaths<\n { edits: z.infer<typeof EditSpecSchema>[] },\n { file: EditFileValue; resourceLink?: ContentBlock }\n >(\n batchInput,\n ctx,\n ({ path, override }) => handleEditFile(path, override?.edits ?? sharedEdits, options, ctx),\n { defaultErrorCode: ErrorCode.UNKNOWN },\n );\n\n const perPathResults: z.infer<typeof EditPerPathSchema>[] = [];\n const resourceLinks: ContentBlock[] = [];\n for (const r of batch.results) {\n if ('error' in r) {\n perPathResults.push({ path: r.path, error: r.error });\n continue;\n }\n perPathResults.push({ path: r.path, value: r.value.file });\n if (r.value.resourceLink) resourceLinks.push(r.value.resourceLink);\n }\n\n const summaryText = formatEditSummary(perPathResults, args.dryRun);\n\n return {\n structured: {\n results: perPathResults,\n summary: batch.summary,\n },\n text: summaryText,\n isError: isTotalFailure(batch.summary),\n ...(resourceLinks.length > 0 ? { resources: resourceLinks } : {}),\n };\n },\n});\n"]}
|
|
1
|
+
{"version":3,"file":"edit.js","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAwB,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EACL,mBAAmB,EACnB,QAAQ,EACR,OAAO,EACP,WAAW,EACX,SAAS,EACT,sBAAsB,EACtB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAgB,MAAM,aAAa,CAAC;AAEvD,MAAM,cAAc,GAAG,CAAC;KACrB,YAAY,CAAC;IACZ,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,EAAE,kBAAkB,CAAC;SAC1B,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QAC9B,OAAO,EAAE,4CAA4C;KACtD,CAAC;SACD,QAAQ,CACP,sIAAsI,CACvI;SACA,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,cAAc,EAAE,mBAAmB,CAAC,EAAE,CAAC;IAC5D,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,QAAQ,CAAC,sEAAsE,CAAC;SAChF,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,cAAc,EAAE,mBAAmB,EAAE,EAAE,CAAC,EAAE,CAAC;CACjE,CAAC;IACF,8EAA8E;IAC9E,8EAA8E;IAC9E,2EAA2E;IAC3E,wEAAwE;KACvE,IAAI,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;AAE5B,MAAM,eAAe,GAAG,CAAC,CAAC;AAC1B,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,mBAAmB,GAAG,CAAC;KAC1B,YAAY,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iDAAiD,CAAC;IACzF,KAAK,EAAE,CAAC;SACL,KAAK,CAAC,cAAc,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,kBAAkB,CAAC;SACvB,QAAQ,EAAE;SACV,QAAQ,CAAC,4DAA4D,CAAC;IACzE,KAAK,EAAE,CAAC;SACL,KAAK,CACJ,CAAC,CAAC,YAAY,CAAC;QACb,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,CAAC;aACL,KAAK,CAAC,cAAc,CAAC;aACrB,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,kBAAkB,CAAC;aACvB,QAAQ,CAAC,6CAA6C,CAAC;KAC3D,CAAC,CACH;SACA,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,eAAe,CAAC;SACpB,QAAQ,EAAE;SACV,QAAQ,CAAC,+BAA+B,CAAC;IAC5C,MAAM,EAAE,mBAAmB,CACzB,sEAAsE,CACvE;IACD,gBAAgB,EAAE,mBAAmB,CACnC,sEAAsE,CACvE;CACF,CAAC;KACD,WAAW,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;IACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC;IAC3C,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,MAAM,CAAC;YACd,OAAO,EAAE,0CAA0C;YACnD,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACzC,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,CAAC;YACf,OAAO,EAAE,oCAAoC;YAC7C,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IACD,IAAI,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,CAAC;YACf,OAAO,EAAE,mEAAmE;YAC5E,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC;IACF,8EAA8E;IAC9E,gFAAgF;IAChF,wEAAwE;IACxE,qDAAqD;KACpD,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AAE/E,MAAM,mBAAmB,GAAG,CAAC,CAAC,YAAY,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;IACtE,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,gCAAgC,CAAC;IAC1D,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,yCAAyC,CAAC;IACxE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;IAC/D,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,qDAAqD,CAAC;IAC9E,WAAW,EAAE,CAAC;SACX,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,qIAAqI,CACtI;IACH,QAAQ,EAAE,WAAW,CAAC,QAAQ,CAAC,wDAAwD,CAAC;IACxF,YAAY,EAAE,SAAS,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IACxE,UAAU,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IACjF,YAAY,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;IACrF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2DAA2D,CAAC;IACjG,cAAc,EAAE,CAAC;SACd,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;SACjB,QAAQ,EAAE;SACV,QAAQ,CAAC,2DAA2D,CAAC;IACxE,SAAS,EAAE,CAAC;SACT,KAAK,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;SACjC,QAAQ,EAAE;SACV,QAAQ,CAAC,6DAA6D,CAAC;CAC3E,CAAC,CAAC;AAEH,MAAM,iBAAiB,GAAG,CAAC,CAAC,YAAY,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;IAChD,KAAK,EAAE,mBAAmB,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IACjF,KAAK,EAAE,kBAAkB,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;CACnF,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG,CAAC,CAAC,YAAY,CAAC;IAC1C,OAAO,EAAE,CAAC;SACP,KAAK,CAAC,iBAAiB,CAAC;SACxB,QAAQ,CAAC,wDAAwD,CAAC;IACrE,OAAO,EAAE,sBAAsB;CAChC,CAAC,CAAC;AAmBH,SAAS,aAAa,CACpB,OAAe,EACf,OAAe,EACf,gBAAyB;IAEzB,IAAI,gBAAgB,EAAE,CAAC;QACrB,sEAAsE;QACtE,2EAA2E;QAC3E,wEAAwE;QACxE,2EAA2E;QAC3E,yEAAyE;QACzE,uCAAuC;QACvC,MAAM,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC;aACxC,OAAO,CAAC,gBAAgB,EAAE,0BAA0B,CAAC;aACrD,OAAO,CAAC,mBAAmB,EAAE,gBAAgB,CAAC;aAC9C,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC;YACH,uEAAuE;YACvE,kEAAkE;YAClE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5D,qEAAqE;YACrE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,SAAS,CAAC;YAErE,OAAO;gBACL,UAAU,EAAE,KAAK,CAAC,KAAK;gBACvB,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM;aAC1B,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,uEAAuE;YACvE,uCAAuC;YACvC,SAAS,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,UAAU,EAAE,KAAK;QACjB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAe,EAAE,KAAgB,EAAE,OAAe;IAC1E,OAAO,CACL,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAC9F,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,uBAAuB,CAAC,QAAgB,EAAE,QAAgB;IACjE,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE3D,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACjC,YAAY,GAAG,CAAC,CAAC;YACjB,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,YAAY,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAClF,IAAI,YAAY,KAAK,CAAC,CAAC;QAAE,YAAY,GAAG,MAAM,CAAC,CAAC,+BAA+B;IAE/E,IAAI,cAAc,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IACzC,IAAI,eAAe,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3C,OACE,cAAc,GAAG,YAAY;QAC7B,eAAe,IAAI,CAAC;QACpB,QAAQ,CAAC,cAAc,CAAC,KAAK,SAAS,CAAC,eAAe,CAAC,EACvD,CAAC;QACD,cAAc,EAAE,CAAC;QACjB,eAAe,EAAE,CAAC;IACpB,CAAC;IAED,+EAA+E;IAC/E,8EAA8E;IAC9E,sEAAsE;IACtE,OAAO,CAAC,YAAY,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,kBAAkB,CACzB,SAAiB,EACjB,IAAsB,EACtB,QAAgB,EAChB,MAAkB;IAElB,OAAO;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,QAAQ;QACR,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,GAAG,CAAC,MAAM,CAAC,YAAY,GAAG,CAAC;YACzB,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE;YACtE,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7D,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CACzB,eAAuB,EACvB,cAAsB,EACtB,YAAoB,EACpB,cAAwB,EACxB,SAAkC;IAElC,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,GAChC,YAAY,GAAG,CAAC;QACd,CAAC,CAAC,gBAAgB,CAAC,eAAe,EAAE,cAAc,CAAC;QACnD,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IAEzC,OAAO;QACL,OAAO,EAAE,cAAc;QACvB,YAAY;QACZ,cAAc;QACd,UAAU;QACV,YAAY;QACZ,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAQD,SAAS,qBAAqB,CAC5B,OAAe,EACf,SAAiB,EACjB,YAAoB,EACpB,aAAwC;IAExC,MAAM,IAAI,GAAG,oBAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IACrE,8EAA8E;IAC9E,+EAA+E;IAC/E,MAAM,OAAO,GAAG,YAAY,GAAG,CAAC,CAAC;IACjC,OAAO;QACL,GAAG,IAAI;QACP,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;QACnD,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;KACtD,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CACjB,OAAe,EACf,KAAuC,EACvC,gBAAyB;IAEzB,IAAI,UAAU,GAAG,OAAO,CAAC;IACzB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAExE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAClC,SAAS;QACX,CAAC;QAED,UAAU,GAAG,gBAAgB,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/D,YAAY,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,0EAA0E;IAC1E,uDAAuD;IACvD,MAAM,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE9F,OAAO,kBAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC;AAC1F,CAAC;AAOD,KAAK,UAAU,cAAc,CAC3B,QAAgB,EAChB,KAAuC,EACvC,OAAwB,EACxB,GAAY;IAEZ,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE;QACrE,MAAM,EAAE,GAAG,CAAC,MAAM;KACnB,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAExE,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,IAAI,UAAU,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;YAChC,UAAU,CAAC,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;QACnF,CAAC;QAED,iEAAiE;QACjE,4EAA4E;QAC5E,uEAAuE;QACvE,kBAAkB;QAClB,MAAM,IAAI,GAAG,qBAAqB,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;QACxF,OAAO;YACL,IAAI,EAAE,kBAAkB,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,UAAU,CAAC;YAC/E,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,OAAO,CACf,SAAS,CAAC,aAAa,EACvB,GAAG,UAAU,CAAC,cAAc,CAAC,MAAM,sEAAsE,EACzG,QAAQ,CACT,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,EAAE;YACnD,QAAQ,EAAE,OAAO;YACjB,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC,CAAC;QACH,GAAG,CAAC,GAAG,EAAE,CACP,MAAM,EACN,SAAS,QAAQ,KAAK,UAAU,CAAC,YAAY,YAAY,UAAU,CAAC,UAAU,KAAK,UAAU,CAAC,YAAY,GAAG,EAC7G,MAAM,CACP,CAAC;IACJ,CAAC;IAED,gFAAgF;IAChF,iFAAiF;IACjF,0EAA0E;IAC1E,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,qBAAqB,CAChC,UAAU,CAAC,OAAO,EAClB,SAAS,EACT,UAAU,CAAC,YAAY,EACvB,GAAG,CAAC,aAAa,CAClB,CAAC;IACF,OAAO;QACL,IAAI,EAAE,kBAAkB,CAAC,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,UAAU,CAAC;QACpF,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClE,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CACxB,OAAqD,EACrD,MAAe;IAEf,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IACvC,uEAAuE;IACvE,wDAAwD;IACxD,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;IACvB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC;QACxC,OAAO,SAAS,IAAI,CAAC,IAAI,aAAa,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC;IACnE,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/B,IAAI,CAAC,CAAC,KAAK;YAAE,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;QAClB,IAAI,CAAC,CAAC;YAAE,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;QACjD,IAAI,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC;QAC3F,MAAM,KAAK,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC;YAAE,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;QAC3E,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;IACnE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IACnC,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,OAAO,SAAS,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,GAAG,EAAE,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,CAAC;IAC7B,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,YAAY;IACnB,WAAW,EACT,4FAA4F;QAC5F,+EAA+E;QAC/E,0GAA0G;QAC1G,oDAAoD;QACpD,oFAAoF;IACtF,KAAK,EAAE,mBAAmB;IAC1B,MAAM,EAAE,oBAAoB;IAC5B,WAAW,EAAE;QACX,YAAY,EAAE,KAAK;QACnB,cAAc,EAAE,KAAK;QACrB,eAAe,EAAE,IAAI;QACrB,aAAa,EAAE,KAAK;KACrB;IACD,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,IAAI,OAAe,CAAC;QACpB,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;aAAM,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YACpC,OAAO,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,OAAO,CAAC;QACpB,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC;IAC/C,CAAC;IACD,WAAW,EAAE,wBAAwB;IACrC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QACrC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;QAE/F,MAAM,OAAO,GAAoB;YAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;SACxC,CAAC;QAEF,MAAM,KAAK,GAAG,MAAM,YAAY,CAI9B,UAAU,EACV,GAAG,EACH,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,IAAI,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,EAC1F,EAAE,gBAAgB,EAAE,SAAS,CAAC,OAAO,EAAE,CACxC,CAAC;QAEF,MAAM,cAAc,GAAwC,EAAE,CAAC;QAC/D,MAAM,aAAa,GAAmB,EAAE,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;gBACjB,cAAc,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;gBACtD,SAAS;YACX,CAAC;YACD,cAAc,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAC3D,IAAI,CAAC,CAAC,KAAK,CAAC,YAAY;gBAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACrE,CAAC;QAED,MAAM,WAAW,GAAG,iBAAiB,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAEnE,OAAO;YACL,UAAU,EAAE;gBACV,OAAO,EAAE,cAAc;gBACvB,OAAO,EAAE,KAAK,CAAC,OAAO;aACvB;YACD,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC;YACtC,GAAG,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { basename } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { computeDiffStats, unifiedPatch } from '../core/diff.js';\nimport { ErrorCode, FsError } from '../core/errors.js';\nimport { buildWrittenFileMeta, type WrittenFileMeta } from '../core/file-uri.js';\nimport { escapeRegexLiteral } from '../core/primitives.js';\nimport {\n defaultFalseBoolean,\n FileKind,\n isBlank,\n IsoDateTime,\n NonNegInt,\n OperationSummarySchema,\n PerFileErrorSchema,\n PositiveInt,\n RequiredPath,\n singleOrBatchAccessPaths,\n} from '../core/schema.js';\nimport { compileRegex, execMatches, freeRegex } from '../core/search.js';\nimport type { ResourceStore } from '../core/store.js';\nimport { isTotalFailure, runOverPaths } from './batch.js';\nimport { defineTool, type ToolCtx } from './define.js';\n\nconst EditSpecSchema = z\n .strictObject({\n oldText: z\n .string()\n .min(1, 'oldText required')\n .refine((val) => !isBlank(val), {\n message: 'oldText cannot be empty or whitespace-only',\n })\n .describe(\n 'Exact literal text to locate in the file. Must include 3-5 lines of context to ensure uniqueness and avoid matching the wrong block.',\n )\n .meta({ examples: ['const x = 1;', 'function oldName('] }),\n newText: z\n .string()\n .describe('Replacement text. Use an empty string to delete the matched oldText.')\n .meta({ examples: ['const x = 2;', 'function newName(', ''] }),\n })\n // The one document in this server where a `$ref` pays: this subschema is used\n // at both `edits` and `files[].edits`, so an `id` hoists it into `$defs` once\n // instead of inlining ~1.3 kB twice. Shared schemas used once per document\n // deliberately carry no `id` — there a `$ref` costs more than it saves.\n .meta({ id: 'EditSpec' });\n\nconst MAX_MULTI_FILES = 5;\nconst MAX_EDITS_PER_FILE = 100;\n\nconst EditFileInputSchema = z\n .strictObject({\n path: RequiredPath.optional().describe('Single file path; mutually exclusive with files'),\n edits: z\n .array(EditSpecSchema)\n .min(1)\n .max(MAX_EDITS_PER_FILE)\n .optional()\n .describe('Replacements applied to path; not allowed when using files'),\n files: z\n .array(\n z.strictObject({\n path: RequiredPath,\n edits: z\n .array(EditSpecSchema)\n .min(1)\n .max(MAX_EDITS_PER_FILE)\n .describe('Replacements to apply to this specific file'),\n }),\n )\n .min(1)\n .max(MAX_MULTI_FILES)\n .optional()\n .describe('Per-file entries (batch mode)'),\n dryRun: defaultFalseBoolean(\n 'Preview diffs without writing to disk (default: false = apply edits)',\n ),\n ignoreWhitespace: defaultFalseBoolean(\n 'Ignore leading/trailing whitespace differences when matching oldText',\n ),\n })\n .superRefine((value, ctx) => {\n const hasPath = value.path !== undefined;\n const hasFiles = value.files !== undefined;\n if (hasPath === hasFiles) {\n ctx.addIssue({\n code: 'custom',\n path: ['path'],\n message: \"Provide exactly one of 'path' or 'files'\",\n input: value,\n });\n }\n if (hasPath && value.edits === undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['edits'],\n message: \"'edits' required when using 'path'\",\n input: value,\n });\n }\n if (hasFiles && value.edits !== undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['edits'],\n message: \"'edits' not allowed with 'files'; each file carries its own edits\",\n input: value,\n });\n }\n })\n // Mirror the superRefine on the wire: exactly one input mode, and single-file\n // mode carries its own `edits`. Without `edits` in the first branch, `{ path }`\n // alone validated against the published schema and was then rejected at\n // runtime — the model had no way to see that coming.\n .meta({ oneOf: [{ required: ['path', 'edits'] }, { required: ['files'] }] });\n\nconst PerFileResultSchema = z.strictObject({\n path: z.string().describe('Resolved absolute path of the edited file'),\n size: NonNegInt.describe('File size in bytes after edits'),\n lineCount: NonNegInt.describe('Number of lines in the file after edits'),\n mimeType: z.string().describe('Detected MIME type of the file'),\n kind: FileKind.describe('Broad file kind: text, binary, image, audio, or pdf'),\n resourceUri: z\n .string()\n .optional()\n .describe(\n 'Resource URI pointing to the updated file content; omitted when no edit matched (appliedEdits is 0) and the file was left untouched',\n ),\n modified: IsoDateTime.describe('Last modification timestamp after edits (ISO 8601 UTC)'),\n appliedEdits: NonNegInt.describe('Number of edits successfully applied'),\n linesAdded: NonNegInt.optional().describe('Net lines added by all applied edits'),\n linesRemoved: NonNegInt.optional().describe('Net lines removed by all applied edits'),\n diff: z.string().optional().describe('Unified diff of all changes (present only in dryRun mode)'),\n unmatchedEdits: z\n .array(z.string())\n .optional()\n .describe('oldText values that did not match any content in the file'),\n lineRange: z\n .tuple([PositiveInt, PositiveInt])\n .optional()\n .describe('Line range [firstLine, lastLine] covering all applied edits'),\n});\n\nconst EditPerPathSchema = z.strictObject({\n path: z.string().describe('Requested file path'),\n value: PerFileResultSchema.optional().describe('Edit result; present on success'),\n error: PerFileErrorSchema.optional().describe('Error details; present on failure'),\n});\n\nconst EditFileOutputSchema = z.strictObject({\n results: z\n .array(EditPerPathSchema)\n .describe('Per-path edit results ordered to match the input paths'),\n summary: OperationSummarySchema,\n});\n\ntype EditFileValue = z.infer<typeof PerFileResultSchema>;\n\ninterface TextRange {\n startIndex: number;\n length: number;\n}\n\ninterface EditResult {\n content: string;\n appliedEdits: number;\n unmatchedEdits: string[];\n linesAdded: number;\n linesRemoved: number;\n diff?: string;\n lineRange?: [number, number];\n}\n\nfunction findEditMatch(\n content: string,\n oldText: string,\n ignoreWhitespace: boolean,\n): TextRange | undefined {\n if (ignoreWhitespace) {\n // Make whitespace flexible (tolerate indentation/spacing differences)\n // without letting it cross line boundaries: a whitespace run that contains\n // a newline keeps at least one newline, so a single-line oldText cannot\n // match across a newline and a multi-line oldText cannot collapse onto one\n // line. Horizontal whitespace stays mandatory between word characters so\n // adjacent identifiers are not merged.\n const pattern = escapeRegexLiteral(oldText)\n .replace(/[^\\S\\n]*\\n\\s*/g, '[^\\\\S\\\\n]*\\\\n+[^\\\\S\\\\n]*')\n .replace(/(\\w)[^\\S\\n]+(\\w)/g, '$1[^\\\\S\\\\n]+$2')\n .replace(/[^\\S\\n]+/g, '[^\\\\S\\\\n]*');\n const regex = compileRegex(pattern, { caseSensitive: true });\n try {\n // execMatches resets lastIndex and reports UTF-16 offsets; the raw RE2\n // index counts code points and would splice off-by-one per emoji.\n const { value: match } = execMatches(regex, content).next();\n // A zero-length match names an empty span, which cannot be replaced.\n if (match === undefined || match.text.length === 0) return undefined;\n\n return {\n startIndex: match.start,\n length: match.text.length,\n };\n } finally {\n // The compiled pattern owns wasm memory re2-wasm never reclaims on its\n // own; its lifetime is this one match.\n freeRegex(regex);\n }\n }\n\n const index = content.indexOf(oldText);\n if (index === -1) {\n return undefined;\n }\n\n return {\n startIndex: index,\n length: oldText.length,\n };\n}\n\nfunction replaceEditMatch(content: string, match: TextRange, newText: string): string {\n return (\n content.slice(0, match.startIndex) + newText + content.slice(match.startIndex + match.length)\n );\n}\n\n/**\n * Compute the 1-indexed line range of changed lines in `modified` relative to\n * `original`, using a common-prefix/suffix line scan. This is a conservative\n * bound (it widens to cover independent changes between matching bookends), but\n * it is computed against the FINAL content so it is not skewed by earlier edits\n * inserting or removing lines — which the per-edit merge could not account for.\n */\nfunction computeChangedLineRange(original: string, modified: string): [number, number] | undefined {\n const origLines = original.split('\\n');\n const modLines = modified.split('\\n');\n const minLen = Math.min(origLines.length, modLines.length);\n\n let firstChanged = -1;\n for (let i = 0; i < minLen; i++) {\n if (origLines[i] !== modLines[i]) {\n firstChanged = i;\n break;\n }\n }\n if (firstChanged === -1 && origLines.length === modLines.length) return undefined;\n if (firstChanged === -1) firstChanged = minLen; // pure append/trim at the tail\n\n let lastChangedMod = modLines.length - 1;\n let lastChangedOrig = origLines.length - 1;\n while (\n lastChangedMod > firstChanged &&\n lastChangedOrig >= 0 &&\n modLines[lastChangedMod] === origLines[lastChangedOrig]\n ) {\n lastChangedMod--;\n lastChangedOrig--;\n }\n\n // When the modified content is a strict prefix of the original (a tail trim on\n // a file with no final newline), the loop above never runs and lastChangedMod\n // lands one below firstChanged. Clamp so the range is never inverted.\n return [firstChanged + 1, Math.max(firstChanged, lastChangedMod) + 1];\n}\n\nfunction buildEditFileValue(\n validPath: string,\n meta: EditFileMetadata,\n modified: string,\n result: EditResult,\n): EditFileValue {\n return {\n path: validPath,\n size: meta.size,\n lineCount: meta.lineCount,\n mimeType: meta.mimeType,\n kind: meta.kind,\n ...(meta.resourceUri !== undefined ? { resourceUri: meta.resourceUri } : {}),\n modified,\n appliedEdits: result.appliedEdits,\n ...(result.appliedEdits > 0\n ? { linesAdded: result.linesAdded, linesRemoved: result.linesRemoved }\n : {}),\n ...(result.unmatchedEdits.length > 0 ? { unmatchedEdits: result.unmatchedEdits } : {}),\n ...(result.diff ? { diff: result.diff } : {}),\n ...(result.lineRange ? { lineRange: result.lineRange } : {}),\n };\n}\n\nfunction finalizeEditResult(\n originalContent: string,\n updatedContent: string,\n appliedEdits: number,\n unmatchedEdits: string[],\n lineRange: EditResult['lineRange'],\n): EditResult {\n const { linesAdded, linesRemoved } =\n appliedEdits > 0\n ? computeDiffStats(originalContent, updatedContent)\n : { linesAdded: 0, linesRemoved: 0 };\n\n return {\n content: updatedContent,\n appliedEdits,\n unmatchedEdits,\n linesAdded,\n linesRemoved,\n ...(lineRange ? { lineRange } : {}),\n };\n}\n\n/** {@link WrittenFileMeta}, with `resourceUri` widened: an edit that matched\n * nothing has no updated content to point at. */\ntype EditFileMetadata = Omit<WrittenFileMeta, 'resourceUri'> & {\n resourceUri: string | undefined;\n};\n\nfunction buildEditFileMetadata(\n content: string,\n validPath: string,\n appliedEdits: number,\n resourceStore: ResourceStore | undefined,\n): EditFileMetadata {\n const meta = buildWrittenFileMeta(validPath, content, resourceStore);\n // Omitted rather than empty-stringed when nothing matched: `\"\"` satisfied the\n // schema's `string` and then failed every resources/read a client tried it on.\n const written = appliedEdits > 0;\n return {\n ...meta,\n resourceUri: written ? meta.resourceUri : undefined,\n resourceLink: written ? meta.resourceLink : undefined,\n };\n}\n\nfunction applyEdits(\n content: string,\n edits: z.infer<typeof EditSpecSchema>[],\n ignoreWhitespace: boolean,\n): EditResult {\n let newContent = content;\n let appliedEdits = 0;\n const unmatchedEdits: string[] = [];\n\n for (const edit of edits) {\n const match = findEditMatch(newContent, edit.oldText, ignoreWhitespace);\n\n if (!match) {\n unmatchedEdits.push(edit.oldText);\n continue;\n }\n\n newContent = replaceEditMatch(newContent, match, edit.newText);\n appliedEdits += 1;\n }\n\n // Compute the line range against the final content so earlier edits whose\n // lines were shifted by later edits are still covered.\n const lineRange = appliedEdits > 0 ? computeChangedLineRange(content, newContent) : undefined;\n\n return finalizeEditResult(content, newContent, appliedEdits, unmatchedEdits, lineRange);\n}\n\ninterface EditFileOptions {\n dryRun: boolean;\n ignoreWhitespace: boolean;\n}\n\nasync function handleEditFile(\n filePath: string,\n edits: z.infer<typeof EditSpecSchema>[],\n options: EditFileOptions,\n ctx: ToolCtx,\n): Promise<{ file: EditFileValue; resourceLink?: ContentBlock }> {\n const { validPath, content } = await ctx.fs.readEditableText(filePath, {\n signal: ctx.signal,\n });\n const editResult = applyEdits(content, edits, options.ignoreWhitespace);\n\n if (options.dryRun) {\n if (editResult.appliedEdits > 0) {\n editResult.diff = unifiedPatch(basename(validPath), content, editResult.content);\n }\n\n // Nothing was written, so there is no updated content to point a\n // resourceUri or a resource_link at — the file on disk is still the one the\n // caller already has. Same rule the appliedEdits-is-0 case follows: no\n // write, no link.\n const meta = buildEditFileMetadata(editResult.content, validPath, 0, ctx.resourceStore);\n return {\n file: buildEditFileValue(validPath, meta, new Date().toISOString(), editResult),\n ...(meta.resourceLink ? { resourceLink: meta.resourceLink } : {}),\n };\n }\n\n if (editResult.unmatchedEdits.length > 0) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n `${editResult.unmatchedEdits.length} edit(s) failed to match. Verify oldText matches exact file content.`,\n filePath,\n );\n }\n\n if (editResult.appliedEdits > 0) {\n await ctx.fs.writeFile(filePath, editResult.content, {\n encoding: 'utf-8',\n signal: ctx.signal,\n });\n ctx.log?.(\n 'info',\n `edit: ${filePath} (${editResult.appliedEdits} edits, +${editResult.linesAdded}/-${editResult.linesRemoved})`,\n 'edit',\n );\n }\n\n // `modified` is read from a post-write stat and is advisory: under a concurrent\n // writer it may reflect that writer's mtime while `size`/content below come from\n // this edit's atomic write. The file content itself is always consistent.\n const { stats: fileStats } = await ctx.fs.stat(filePath, { signal: ctx.signal });\n const meta = buildEditFileMetadata(\n editResult.content,\n validPath,\n editResult.appliedEdits,\n ctx.resourceStore,\n );\n return {\n file: buildEditFileValue(validPath, meta, fileStats.mtime.toISOString(), editResult),\n ...(meta.resourceLink ? { resourceLink: meta.resourceLink } : {}),\n };\n}\n\nfunction formatEditSummary(\n results: readonly z.infer<typeof EditPerPathSchema>[],\n dryRun: boolean,\n): string {\n const tag = dryRun ? ' [dry run]' : '';\n // Single-file failure: say why here rather than making the caller read\n // structuredContent for the one message that can apply.\n const [only] = results;\n if (results.length === 1 && only?.error) {\n return `edit: ${only.path} FAILED — ${only.error.message}${tag}`;\n }\n const tokens = results.map((r) => {\n if (r.error) return `${basename(r.path)} FAILED`;\n const v = r.value;\n if (!v) return `${basename(r.path)} (no result)`;\n if (v.unmatchedEdits && v.unmatchedEdits.length > 0) return `${basename(v.path)} NO MATCH`;\n const added = v.linesAdded ?? 0;\n const removed = v.linesRemoved ?? 0;\n if (added === 0 && removed === 0) return `${basename(v.path)} (no change)`;\n return `${basename(v.path)} +${String(added)} -${String(removed)}`;\n });\n\n const failed = results.filter((r) => r.error !== undefined).length;\n const ok = results.length - failed;\n const ratio = failed > 0 ? ` (${String(ok)}/${String(results.length)} ok)` : '';\n return `edit: ${tokens.join(' · ')}${ratio}${tag}`;\n}\n\nexport const EDIT = defineTool({\n name: 'edit',\n title: 'Edit Files',\n description:\n 'Apply sequential literal string replacements to one or more files (max 5 files per call). ' +\n 'Modes: single-file { path, edits } or per-file { files: [{ path, edits }] }. ' +\n 'oldText must match file content exactly; include 3-5 lines of surrounding context to ensure uniqueness. ' +\n 'Set dryRun=true to preview diffs without writing. ' +\n 'For glob-based bulk regex replacement across many files, use replace_text instead.',\n input: EditFileInputSchema,\n output: EditFileOutputSchema,\n annotations: {\n readOnlyHint: false,\n idempotentHint: false,\n destructiveHint: true,\n openWorldHint: false,\n },\n progress: (args) => {\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n let subject: string;\n if (args.path !== undefined) {\n subject = basename(args.path);\n } else if (args.files !== undefined) {\n subject = `${args.files.length} files`;\n } else {\n subject = 'files';\n }\n return { label: `Edit${dryLabel}`, subject };\n },\n accessPaths: singleOrBatchAccessPaths,\n run: async (args, ctx) => {\n const sharedEdits = args.edits ?? [];\n const batchInput = args.path !== undefined ? { path: args.path } : { files: args.files ?? [] };\n\n const options: EditFileOptions = {\n dryRun: args.dryRun,\n ignoreWhitespace: args.ignoreWhitespace,\n };\n\n const batch = await runOverPaths<\n { edits: z.infer<typeof EditSpecSchema>[] },\n { file: EditFileValue; resourceLink?: ContentBlock }\n >(\n batchInput,\n ctx,\n ({ path, override }) => handleEditFile(path, override?.edits ?? sharedEdits, options, ctx),\n { defaultErrorCode: ErrorCode.UNKNOWN },\n );\n\n const perPathResults: z.infer<typeof EditPerPathSchema>[] = [];\n const resourceLinks: ContentBlock[] = [];\n for (const r of batch.results) {\n if ('error' in r) {\n perPathResults.push({ path: r.path, error: r.error });\n continue;\n }\n perPathResults.push({ path: r.path, value: r.value.file });\n if (r.value.resourceLink) resourceLinks.push(r.value.resourceLink);\n }\n\n const summaryText = formatEditSummary(perPathResults, args.dryRun);\n\n return {\n structured: {\n results: perPathResults,\n summary: batch.summary,\n },\n text: summaryText,\n isError: isTotalFailure(batch.summary),\n ...(resourceLinks.length > 0 ? { resources: resourceLinks } : {}),\n };\n },\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-in-files.d.ts","sourceRoot":"","sources":["../../src/tools/replace-in-files.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replace-in-files.d.ts","sourceRoot":"","sources":["../../src/tools/replace-in-files.ts"],"names":[],"mappings":"AA6mBA,eAAO,MAAM,kBAAkB,mCAyC7B,CAAC"}
|
|
@@ -63,7 +63,7 @@ import { toPosixRelative } from '../core/path.js';
|
|
|
63
63
|
import { escapeRegexLiteral } from '../core/primitives.js';
|
|
64
64
|
import { readFileBufferWithLimit } from '../core/read.js';
|
|
65
65
|
import { defaultFalseBoolean, includeHiddenField, includeIgnoredField, isBlank, maxDepthField, NonNegInt, OperationSummarySchema, OptionalPath, PerFileErrorSchema, SafeGlobPattern, } from '../core/schema.js';
|
|
66
|
-
import { compileRegex, freeRegex } from '../core/search.js';
|
|
66
|
+
import { compileRegex, execMatches, freeRegex } from '../core/search.js';
|
|
67
67
|
import { DEFAULT_SEARCH_RESULTS, DEFAULT_SEARCH_TIMEOUT_MS, getMaxTextFileSize, MAX_SEARCH_RESULTS, PARALLEL_CONCURRENCY, } from '../core/util.js';
|
|
68
68
|
import { isTotalFailure } from './batch.js';
|
|
69
69
|
import { defineTool } from './define.js';
|
|
@@ -167,8 +167,8 @@ const DOLLAR_TOKEN = /\$(\$|&|`|'|<([^>]*)>|\d{1,2})/g;
|
|
|
167
167
|
* `Invalid replacement string` on any `$` not followed by a substitution it
|
|
168
168
|
* recognises (so a replacement of `$100` or a trailing `$` fails outright,
|
|
169
169
|
* where `RegExp` inserts the `$` literally), and it renders an out-of-range
|
|
170
|
-
* `$5` as `$4`.
|
|
171
|
-
*
|
|
170
|
+
* `$5` as `$4`. The matcher splices matches itself, so this is the single owner
|
|
171
|
+
* of the syntax.
|
|
172
172
|
*/
|
|
173
173
|
function expandDollarTokens(template, match, groups, offset, input, named) {
|
|
174
174
|
return template.replace(DOLLAR_TOKEN, (token, kind, name) => {
|
|
@@ -206,29 +206,26 @@ function createRegexReplacementMatcher(regex, expandReplacement) {
|
|
|
206
206
|
return regex.test(buffer.toString('utf-8'));
|
|
207
207
|
},
|
|
208
208
|
replace(content, replacement) {
|
|
209
|
-
regex
|
|
209
|
+
// Splicing by hand rather than through `String.replace(regex, …)`: RE2's
|
|
210
|
+
// own replacer indexes the JS string with the code-point offsets it
|
|
211
|
+
// reports, which lands the replacement short by one per astral character
|
|
212
|
+
// (most emoji) earlier in the file. execMatches reports UTF-16 offsets.
|
|
210
213
|
let matchCount = 0;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (!expandReplacement) {
|
|
215
|
-
const updated = content.replace(regex, () => {
|
|
216
|
-
matchCount++;
|
|
217
|
-
return replacement;
|
|
218
|
-
});
|
|
219
|
-
return { content: updated, matchCount };
|
|
220
|
-
}
|
|
221
|
-
const updated = content.replace(regex, (match, ...rest) => {
|
|
214
|
+
let updated = '';
|
|
215
|
+
let cursor = 0;
|
|
216
|
+
for (const match of execMatches(regex, content)) {
|
|
222
217
|
matchCount++;
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
218
|
+
// Only isRegex=true opts into $1/$& substitution. A literal search
|
|
219
|
+
// reaches this matcher too (case-insensitive and wholeWord both need a
|
|
220
|
+
// regex), and there the replacement must be inserted verbatim.
|
|
221
|
+
updated +=
|
|
222
|
+
content.slice(cursor, match.start) +
|
|
223
|
+
(expandReplacement
|
|
224
|
+
? expandDollarTokens(replacement, match.text, match.groups, match.start, content, match.named)
|
|
225
|
+
: replacement);
|
|
226
|
+
cursor = match.end;
|
|
227
|
+
}
|
|
228
|
+
return { content: updated + content.slice(cursor), matchCount };
|
|
232
229
|
},
|
|
233
230
|
dispose() {
|
|
234
231
|
freeRegex(regex);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-in-files.js","sourceRoot":"","sources":["../../src/tools/replace-in-files.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { Buffer } from 'node:buffer';\nimport { dirname, join } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport type { StoppedReason, StopReasonTracker } from '../core/concurrency.js';\nimport { processEntriesConcurrently, StoppedReasonSchema } from '../core/concurrency.js';\nimport { unifiedPatch } from '../core/diff.js';\nimport {\n ErrorCode,\n formatUnknownErrorMessage,\n FsError,\n Problem,\n rethrowIfAborted,\n} from '../core/errors.js';\nimport { buildFileResourceLink } from '../core/file-uri.js';\nimport { truncateProgressPattern } from '../core/fmt.js';\nimport type { GuardedFileSystem } from '../core/fs.js';\nimport { DEFAULT_EXCLUDE_PATTERNS, globEntries } from '../core/glob.js';\nimport { toPosixRelative } from '../core/path.js';\nimport { escapeRegexLiteral } from '../core/primitives.js';\nimport { readFileBufferWithLimit } from '../core/read.js';\nimport {\n defaultFalseBoolean,\n includeHiddenField,\n includeIgnoredField,\n isBlank,\n maxDepthField,\n NonNegInt,\n OperationSummarySchema,\n OptionalPath,\n PerFileErrorSchema,\n SafeGlobPattern,\n} from '../core/schema.js';\nimport type { Regex } from '../core/search.js';\nimport { compileRegex, freeRegex } from '../core/search.js';\nimport {\n DEFAULT_SEARCH_RESULTS,\n DEFAULT_SEARCH_TIMEOUT_MS,\n getMaxTextFileSize,\n MAX_SEARCH_RESULTS,\n PARALLEL_CONCURRENCY,\n} from '../core/util.js';\nimport { isTotalFailure } from './batch.js';\nimport { defineTool, type ToolCtx } from './define.js';\n\nconst SearchAndReplaceInputSchema = z.strictObject({\n path: OptionalPath.describe(\n 'File to rewrite, or directory to rewrite under. Omitting it targets the ENTIRE first allowed root — ' +\n 'scope it deliberately, and pair a wide scope with dryRun=true first',\n ),\n pattern: SafeGlobPattern.optional().describe(\n 'Glob to restrict replacements to specific file types (e.g. **/*.ts); default: all text files',\n ),\n searchPattern: z\n .string()\n .min(1)\n .max(10000)\n .refine((val) => !isBlank(val), {\n message: 'searchPattern cannot be empty or whitespace-only',\n })\n .describe(\n 'Exact literal text or RE2 regex pattern to search for. When isRegex=true, uses RE2 syntax (no lookahead, lookbehind, or backreferences are supported). Cannot be empty or whitespace-only.',\n )\n .meta({ examples: ['TODO', 'function\\\\s+(\\\\w+)', 'import.*from'] }),\n replacement: z\n .string()\n .max(10000)\n .describe(\n 'Replacement text. Use capture group references ($1, $2, etc.) when isRegex=true. Use an empty string to delete all matches.',\n )\n .meta({ examples: ['$1_renamed', '', 'TODO: fix'] }),\n isRegex: defaultFalseBoolean('Treat searchPattern as a RE2 regex (default: literal text match)'),\n includeHidden: includeHiddenField(),\n includeIgnored: includeIgnoredField(),\n caseSensitive: defaultFalseBoolean('Enable case-sensitive matching (default: case-insensitive)'),\n wholeWord: defaultFalseBoolean('Match whole words only (word boundary anchoring)'),\n dryRun: defaultFalseBoolean(\n 'Preview replacements without writing to disk (default: false = apply changes)',\n ),\n returnDiff: defaultFalseBoolean('Include a unified diff of all changes in the response'),\n maxResults: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .default(DEFAULT_SEARCH_RESULTS)\n .describe('Maximum total match count across all files before stopping'),\n maxFiles: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .describe('Maximum number of files to process'),\n maxDepth: maxDepthField(),\n});\n\nconst ReplacePerPathSchema = z.strictObject({\n path: z.string().describe('File path relative to the search root'),\n value: z\n .strictObject({ matches: NonNegInt.describe('Replacements applied in this file') })\n .optional()\n .describe('Replacement outcome; present on success'),\n error: PerFileErrorSchema.optional().describe('Error details; present on failure'),\n});\n\n// The same `{ results, summary }` envelope `read`, `stat`, and `delete` use, so\n// a caller learns one shape for every tool that spans many paths. The former\n// `filesModified` / `failedFiles` counters are `summary.succeeded` / `.failed`,\n// and the old `primaryFile` block is gone — the resource_link content block\n// already points at the first modified file.\nconst SearchAndReplaceOutputSchema = z.strictObject({\n results: z\n .array(ReplacePerPathSchema)\n .describe('Per-file results: modified files, then any that could not be processed'),\n summary: OperationSummarySchema,\n totalMatches: NonNegInt.describe('Total number of replacements made across all files'),\n filesScanned: NonNegInt.describe('Total number of files examined'),\n resultsTruncated: z\n .boolean()\n .optional()\n .describe(\n 'True when the results list holds fewer entries than summary.total: the changed-file or failed-file cap was hit. Trust summary over results.length.',\n ),\n diff: z\n .string()\n .optional()\n .describe('Unified diff of all changes (present when returnDiff=true or dryRun=true)'),\n diffTruncated: z\n .boolean()\n .optional()\n .describe('True when the diff was cut due to the size limit'),\n stoppedReason: StoppedReasonSchema.describe(\n 'Why enumeration stopped early: maxResults = match cap reached, maxFiles = file cap reached, timeout = time limit hit or cancelled. Absent when every matching file was enumerated. Marks the sweep incomplete, not the writes; files already dispatched still complete.',\n ),\n});\n\nconst MAX_FAILURES = 20;\nconst REPLACE_CONCURRENCY = Math.min(PARALLEL_CONCURRENCY, 8);\nconst MAX_CHANGED_FILES = 100;\nconst MAX_DIFF_SIZE = 20 * 1024; // 20KB limit for diff output\nconst DIFF_APPEND_BUFFER = 1024;\n\ninterface Failure {\n path: string;\n error: NonNullable<z.infer<typeof ReplacePerPathSchema>['error']>;\n}\n\nfunction recordFailure(failures: Failure[], failure: Failure): void {\n if (failures.length >= MAX_FAILURES) return;\n failures.push(failure);\n}\n\nfunction recordChangedFile(summary: ReplaceSummary, filePath: string, matchCount: number): void {\n const relativePath = toPosixRelative(summary.root, filePath);\n if (summary.changedFiles.length < MAX_CHANGED_FILES) {\n summary.changedFiles.push({ path: relativePath, matches: matchCount });\n return;\n }\n summary.changedFilesTruncated = true;\n}\n\ninterface ReplacementMatcher {\n replace(content: string, replacement: string): { content: string; matchCount: number };\n testBuffer(buffer: Buffer): boolean;\n /** Releases any compiled pattern this matcher owns. Idempotent. */\n dispose(): void;\n}\n\nconst DOLLAR_TOKEN = /\\$(\\$|&|`|'|<([^>]*)>|\\d{1,2})/g;\n\n/**\n * Expand `$`-substitutions in a replacement template the way `RegExp` does.\n *\n * We cannot hand the template to RE2's own string replacer: it throws\n * `Invalid replacement string` on any `$` not followed by a substitution it\n * recognises (so a replacement of `$100` or a trailing `$` fails outright,\n * where `RegExp` inserts the `$` literally), and it renders an out-of-range\n * `$5` as `$4`. Passing a function to `String.replace` disables RE2's handling\n * entirely, which leaves this the single owner of the syntax.\n */\nfunction expandDollarTokens(\n template: string,\n match: string,\n groups: (string | undefined)[],\n offset: number,\n input: string,\n named: Record<string, string> | undefined,\n): string {\n return template.replace(DOLLAR_TOKEN, (token: string, kind: string, name?: string) => {\n if (kind === '$') return '$';\n if (kind === '&') return match;\n if (kind === '`') return input.slice(0, offset);\n if (kind === \"'\") return input.slice(offset + match.length);\n if (name !== undefined) {\n if (named && Object.hasOwn(named, name)) return named[name] ?? '';\n return token;\n }\n // `$12` prefers group 12, then falls back to group 1 followed by a literal\n // `2`, and stays literal when neither exists — RegExp's own precedence.\n const two = Number.parseInt(kind, 10);\n if (kind.length === 2 && two >= 1 && two <= groups.length) return groups[two - 1] ?? '';\n const one = Number.parseInt(kind.slice(0, 1), 10);\n if (one >= 1 && one <= groups.length) {\n return (groups[one - 1] ?? '') + (kind.length === 2 ? kind.slice(1) : '');\n }\n return token;\n });\n}\n\nfunction createRegexReplacementMatcher(\n regex: Regex,\n expandReplacement: boolean,\n): ReplacementMatcher {\n return {\n testBuffer(buffer: Buffer): boolean {\n // The regex is global and shared across every file in the batch, so a\n // previous file's match would otherwise start this scan mid-string.\n regex.lastIndex = 0;\n return regex.test(buffer.toString('utf-8'));\n },\n replace(content: string, replacement: string): { content: string; matchCount: number } {\n regex.lastIndex = 0;\n let matchCount = 0;\n // Only isRegex=true opts into $1/$& substitution. A literal search reaches\n // this matcher too (case-insensitive and wholeWord both need a regex), and\n // there the replacement must be inserted verbatim.\n if (!expandReplacement) {\n const updated = content.replace(regex, () => {\n matchCount++;\n return replacement;\n });\n return { content: updated, matchCount };\n }\n const updated = content.replace(regex, (match: string, ...rest: unknown[]): string => {\n matchCount++;\n // In ECMAScript/RE2, trailing arguments are [offset, input] or [offset, input, namedGroups].\n const named =\n rest.length >= 3 && typeof rest[rest.length - 3] === 'number'\n ? (rest.pop() as Record<string, string> | undefined)\n : undefined;\n const input = rest.pop() as string;\n const offset = rest.pop() as number;\n return expandDollarTokens(\n replacement,\n match,\n rest as (string | undefined)[],\n offset,\n input,\n named,\n );\n });\n return { content: updated, matchCount };\n },\n dispose(): void {\n freeRegex(regex);\n },\n };\n}\n\nfunction createCaseSensitiveLiteralMatcher(searchPattern: string): ReplacementMatcher {\n const searchBuffer = Buffer.from(searchPattern, 'utf8');\n\n return {\n testBuffer(buffer: Buffer): boolean {\n return buffer.indexOf(searchBuffer) !== -1;\n },\n replace(content: string, replacement: string): { content: string; matchCount: number } {\n let matchCount = 0;\n const updated = content.replaceAll(searchPattern, () => {\n matchCount++;\n return replacement;\n });\n return { content: updated, matchCount };\n },\n dispose(): void {\n // no compiled pattern to release\n },\n };\n}\n\ntype SearchAndReplaceArgs = z.infer<typeof SearchAndReplaceInputSchema>;\ntype SearchAndReplaceOutput = z.infer<typeof SearchAndReplaceOutputSchema>;\n\ninterface ReplaceContext {\n options: { dryRun: boolean; returnDiff: boolean };\n replacement: string;\n matcher: ReplacementMatcher;\n maxFileSize: number;\n signal: AbortSignal | undefined;\n summary: ReplaceSummary;\n fs: GuardedFileSystem;\n}\n\ninterface ReplacementPlan {\n matchCount: number;\n originalContent: string;\n updatedContent: string;\n}\n\nfunction buildReplacementPlan(\n content: string,\n replacement: string,\n matcher: ReplacementMatcher,\n): ReplacementPlan | undefined {\n const { content: updatedContent, matchCount } = matcher.replace(content, replacement);\n if (matchCount === 0) {\n return undefined;\n }\n\n return {\n matchCount,\n originalContent: content,\n updatedContent,\n };\n}\n\nasync function processEntry(entryPath: string, ctx: ReplaceContext): Promise<void> {\n const { options, signal, summary } = ctx;\n const validPath = entryPath;\n\n try {\n const plan = await readReplacementPlan(validPath, ctx);\n if (!plan) {\n return;\n }\n\n if (!options.dryRun) {\n await ctx.fs.writeFile(entryPath, plan.updatedContent, {\n encoding: 'utf-8',\n signal,\n });\n }\n\n // Bookkeep only after the write succeeds (or in dryRun, where there is no\n // write): a failed write must not count the file as changed.\n summary.totalMatches += plan.matchCount;\n summary.filesChanged++;\n\n recordChangedFile(summary, validPath, plan.matchCount);\n\n maybeAppendPatchDiff(summary, {\n filePath: validPath,\n originalContent: plan.originalContent,\n updatedContent: plan.updatedContent,\n includeDiff: options.dryRun || options.returnDiff,\n });\n } catch (error) {\n summary.failedFiles++;\n recordFailure(summary.failures, {\n path: toPosixRelative(summary.root, validPath),\n error: Problem.fromUnknown(error, ErrorCode.UNKNOWN, validPath),\n });\n }\n}\n\nasync function readReplacementPlan(\n validPath: string,\n ctx: ReplaceContext,\n): Promise<ReplacementPlan | undefined> {\n const { matcher, replacement, maxFileSize, signal } = ctx;\n await using fileHandle = await ctx.fs.open(validPath);\n const stats = await fileHandle.stat();\n if (stats.size > maxFileSize) {\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large: ${validPath} (${String(stats.size)} bytes > ${String(maxFileSize)} bytes)`,\n );\n }\n\n const buffer = await readFileBufferWithLimit(fileHandle, maxFileSize, validPath, signal);\n if (!matcher.testBuffer(buffer)) return undefined;\n\n return buildReplacementPlan(buffer.toString('utf-8'), replacement, matcher);\n}\n\nfunction maybeAppendPatchDiff(\n summary: ReplaceSummary,\n params: {\n filePath: string;\n originalContent: string;\n updatedContent: string;\n includeDiff: boolean;\n },\n): void {\n if (!params.includeDiff) return;\n const header = toPosixRelative(summary.root, params.filePath);\n\n const patch = unifiedPatch(header, params.originalContent, params.updatedContent);\n\n if (summary.diff.length >= MAX_DIFF_SIZE) {\n summary.diffTruncated = true;\n return;\n }\n\n if (summary.diff.length + patch.length <= MAX_DIFF_SIZE + DIFF_APPEND_BUFFER) {\n summary.diff += patch;\n return;\n }\n\n summary.diffTruncated = true;\n}\n\ninterface ReplaceSummary {\n root: string;\n totalMatches: number;\n filesChanged: number;\n failedFiles: number;\n processedFiles: number;\n failures: Failure[];\n changedFiles: { path: string; matches: number }[];\n changedFilesTruncated: boolean;\n diff: string;\n diffTruncated: boolean;\n stoppedReason?: StoppedReason;\n}\n\nfunction createReplaceSummary(root: string): ReplaceSummary {\n return {\n root,\n totalMatches: 0,\n filesChanged: 0,\n failedFiles: 0,\n processedFiles: 0,\n failures: [],\n changedFiles: [],\n changedFilesTruncated: false,\n diff: '',\n diffTruncated: false,\n };\n}\n\nasync function resolveSearchRoot(\n pathValue: string | undefined,\n fs: GuardedFileSystem,\n): Promise<{ root: string; singleFile?: string }> {\n if (!pathValue) {\n return { root: fs.pathGuard.resolvePathOrRoot(undefined) };\n }\n const resolvedPath = await fs.pathGuard.validateExistingPath(pathValue);\n const { stats: fileStats } = await fs.stat(resolvedPath);\n if (fileStats.isFile()) {\n // A single explicit file target bypasses the glob machinery entirely:\n // routing it through globEntries with baseNameMatch would rewrite the\n // escaped basename to `**/${basename}` and match every same-named file\n // under the parent tree, not just this one.\n return {\n root: dirname(resolvedPath),\n singleFile: resolvedPath,\n };\n }\n return { root: resolvedPath };\n}\n\nfunction buildSearchPattern(args: SearchAndReplaceArgs): string {\n if (args.isRegex) {\n return args.wholeWord ? `\\\\b(?:${args.searchPattern})\\\\b` : args.searchPattern;\n }\n const escaped = escapeRegexLiteral(args.searchPattern);\n return args.wholeWord ? `\\\\b(?:${escaped})\\\\b` : escaped;\n}\n\nfunction createReplacementMatcher(args: SearchAndReplaceArgs): ReplacementMatcher {\n // Use regex when isRegex, wholeWord, or case-insensitive (all require RE2)\n if (args.isRegex || args.wholeWord || !args.caseSensitive) {\n const regex = compileRegex(buildSearchPattern(args), { caseSensitive: args.caseSensitive });\n return createRegexReplacementMatcher(regex, args.isRegex);\n }\n return createCaseSensitiveLiteralMatcher(args.searchPattern);\n}\n\nfunction buildSearchAndReplaceStructuredResult(\n summary: ReplaceSummary,\n args: SearchAndReplaceArgs,\n): SearchAndReplaceOutput {\n const results = [\n ...summary.changedFiles.map((f) => ({ path: f.path, value: { matches: f.matches } })),\n ...summary.failures.map((f) => ({ path: f.path, error: f.error })),\n ];\n return {\n results,\n // `failedFiles` counts every failure; `failures` itself is capped at\n // MAX_FAILURES, so the summary must not be derived from the array length.\n summary: {\n total: summary.filesChanged + summary.failedFiles,\n succeeded: summary.filesChanged,\n failed: summary.failedFiles,\n },\n totalMatches: summary.totalMatches,\n filesScanned: summary.processedFiles,\n // Failures are capped at MAX_FAILURES independently of the changed-file\n // cap, so either cap can leave `results` shorter than `summary.total`.\n ...(summary.changedFilesTruncated || summary.failures.length < summary.failedFiles\n ? { resultsTruncated: true }\n : {}),\n ...((args.dryRun || args.returnDiff) && summary.diff ? { diff: summary.diff } : {}),\n ...(summary.diffTruncated ? { diffTruncated: true } : {}),\n ...(summary.stoppedReason ? { stoppedReason: summary.stoppedReason } : {}),\n };\n}\n\nasync function handleSearchAndReplace(\n args: SearchAndReplaceArgs,\n ctx: ToolCtx,\n): Promise<{\n structured: SearchAndReplaceOutput;\n link?: ContentBlock;\n}> {\n const maxFileSize = getMaxTextFileSize();\n const { root, singleFile } = await resolveSearchRoot(args.path, ctx.fs);\n const effectivePattern = args.pattern ?? '**/*';\n\n // An explicit single-file target bypasses baseNameMatch/exclude/hidden/\n // gitignore filtering — it should always be processed as the one file named.\n // The async generator matches globEntries' AsyncIterable contract; it needs\n // no await (single yield), hence the disable.\n const entries: AsyncIterable<{ path: string }> = singleFile\n ? // eslint-disable-next-line @typescript-eslint/require-await\n (async function* singleFileEntry() {\n yield { path: singleFile };\n })()\n : globEntries({\n cwd: root,\n pattern: effectivePattern,\n excludePatterns: args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS,\n includeHidden: args.includeHidden,\n respectGitignore: !args.includeIgnored,\n baseNameMatch: true,\n onlyFiles: true,\n suppressErrors: true,\n ...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}),\n });\n\n const summary = createReplaceSummary(root);\n\n // The matcher may own a compiled RE2 pattern, whose wasm memory re2-wasm\n // never reclaims on its own. Nothing past the scan touches it, so free it the\n // moment the scan is done — success, failure, or abort alike.\n const matcher = createReplacementMatcher(args);\n let scan: StopReasonTracker;\n try {\n const context: ReplaceContext = {\n options: {\n dryRun: args.dryRun,\n returnDiff: args.returnDiff,\n },\n replacement: args.replacement,\n matcher,\n maxFileSize,\n signal: ctx.signal,\n summary,\n fs: ctx.fs,\n };\n\n scan = await processEntriesConcurrently(entries, {\n signal: ctx.signal,\n concurrency: REPLACE_CONCURRENCY,\n ...(args.maxFiles !== undefined ? { maxEntries: args.maxFiles } : {}),\n shouldStop: () => summary.totalMatches >= args.maxResults,\n onEntry: () => {\n summary.processedFiles++;\n ctx.onProgress?.({ current: summary.processedFiles });\n },\n onError: (entryPath, err) => {\n summary.failedFiles++;\n recordFailure(summary.failures, {\n path: toPosixRelative(summary.root, entryPath),\n error: Problem.fromUnknown(err, ErrorCode.UNKNOWN, entryPath),\n });\n },\n runEntry: (entryPath) => processEntry(entryPath, context),\n });\n } finally {\n matcher.dispose();\n }\n const stoppedReason = scan.resolve();\n if (stoppedReason !== undefined) summary.stoppedReason = stoppedReason;\n\n ctx.onProgress?.({ current: summary.processedFiles });\n\n if (!args.dryRun && summary.totalMatches > 0) {\n ctx.log?.(\n 'info',\n `${summary.filesChanged} file(s) changed, ${summary.totalMatches} match(es)`,\n 'replace_text',\n );\n }\n\n const structured = buildSearchAndReplaceStructuredResult(summary, args);\n\n // Store primary file in resource store if available (skip in dryRun to avoid reading stale disk content)\n if (\n !args.dryRun &&\n ctx.resourceStore &&\n summary.changedFiles.length > 0 &&\n summary.filesChanged > 0\n ) {\n const primaryFile = summary.changedFiles[0];\n if (!primaryFile) return { structured };\n\n const primaryFilePath = primaryFile.path;\n const fullPath = join(summary.root, primaryFilePath);\n\n try {\n const { content: rawBuffer, mimeType } = await ctx.fs.readRaw(fullPath, {\n signal: ctx.signal,\n });\n const link = buildFileResourceLink(fullPath, mimeType, rawBuffer.length);\n return { structured, link };\n } catch (error) {\n rethrowIfAborted(error);\n // Gracefully fall back if resource storage fails\n ctx.log?.(\n 'error',\n `Failed to store primary file in resource store: ${formatUnknownErrorMessage(error)}`,\n 'replace_text',\n );\n }\n }\n\n return { structured };\n}\n\nexport const SEARCH_AND_REPLACE = defineTool({\n name: 'replace_text',\n title: 'Search and Replace',\n description:\n 'Bulk search-and-replace across files matching a glob pattern. ' +\n 'Replaces ALL occurrences per file (unlike edit, which replaces only the first match). ' +\n 'Set returnDiff=true to preview changes as a unified diff before or after writing. ' +\n 'Literal matching by default; set isRegex=true to enable RE2 regex with capture groups ($1, $2).',\n input: SearchAndReplaceInputSchema,\n output: SearchAndReplaceOutputSchema,\n annotations: {\n readOnlyHint: false,\n idempotentHint: false,\n destructiveHint: true,\n openWorldHint: false,\n },\n timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,\n defaultErrorCode: ErrorCode.UNKNOWN,\n progress: (args) => {\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n return {\n label: `Replace${dryLabel}`,\n subject: `${truncateProgressPattern(args.searchPattern)} → ${truncateProgressPattern(args.replacement)}`,\n };\n },\n accessPaths: (args) => (args.path ? [args.path] : []),\n run: async (args, ctx) => {\n const truncatedPattern = truncateProgressPattern(args.searchPattern);\n const { structured, link } = await handleSearchAndReplace(args, ctx);\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n const summaryText =\n `replace_text: '${truncatedPattern}'${dryLabel}` +\n ` \\u00b7 ${String(structured.totalMatches)} match(es)` +\n ` in ${String(structured.summary.succeeded)} file(s)` +\n (structured.summary.failed > 0 ? ` \\u00b7 ${String(structured.summary.failed)} failed` : '');\n const isError = isTotalFailure(structured.summary);\n if (link) {\n return { structured, text: summaryText, resources: [link], isError };\n }\n return { structured, text: summaryText, isError };\n },\n});\n"]}
|
|
1
|
+
{"version":3,"file":"replace-in-files.js","sourceRoot":"","sources":["../../src/tools/replace-in-files.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { Buffer } from 'node:buffer';\nimport { dirname, join } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport type { StoppedReason, StopReasonTracker } from '../core/concurrency.js';\nimport { processEntriesConcurrently, StoppedReasonSchema } from '../core/concurrency.js';\nimport { unifiedPatch } from '../core/diff.js';\nimport {\n ErrorCode,\n formatUnknownErrorMessage,\n FsError,\n Problem,\n rethrowIfAborted,\n} from '../core/errors.js';\nimport { buildFileResourceLink } from '../core/file-uri.js';\nimport { truncateProgressPattern } from '../core/fmt.js';\nimport type { GuardedFileSystem } from '../core/fs.js';\nimport { DEFAULT_EXCLUDE_PATTERNS, globEntries } from '../core/glob.js';\nimport { toPosixRelative } from '../core/path.js';\nimport { escapeRegexLiteral } from '../core/primitives.js';\nimport { readFileBufferWithLimit } from '../core/read.js';\nimport {\n defaultFalseBoolean,\n includeHiddenField,\n includeIgnoredField,\n isBlank,\n maxDepthField,\n NonNegInt,\n OperationSummarySchema,\n OptionalPath,\n PerFileErrorSchema,\n SafeGlobPattern,\n} from '../core/schema.js';\nimport type { Regex } from '../core/search.js';\nimport { compileRegex, execMatches, freeRegex } from '../core/search.js';\nimport {\n DEFAULT_SEARCH_RESULTS,\n DEFAULT_SEARCH_TIMEOUT_MS,\n getMaxTextFileSize,\n MAX_SEARCH_RESULTS,\n PARALLEL_CONCURRENCY,\n} from '../core/util.js';\nimport { isTotalFailure } from './batch.js';\nimport { defineTool, type ToolCtx } from './define.js';\n\nconst SearchAndReplaceInputSchema = z.strictObject({\n path: OptionalPath.describe(\n 'File to rewrite, or directory to rewrite under. Omitting it targets the ENTIRE first allowed root — ' +\n 'scope it deliberately, and pair a wide scope with dryRun=true first',\n ),\n pattern: SafeGlobPattern.optional().describe(\n 'Glob to restrict replacements to specific file types (e.g. **/*.ts); default: all text files',\n ),\n searchPattern: z\n .string()\n .min(1)\n .max(10000)\n .refine((val) => !isBlank(val), {\n message: 'searchPattern cannot be empty or whitespace-only',\n })\n .describe(\n 'Exact literal text or RE2 regex pattern to search for. When isRegex=true, uses RE2 syntax (no lookahead, lookbehind, or backreferences are supported). Cannot be empty or whitespace-only.',\n )\n .meta({ examples: ['TODO', 'function\\\\s+(\\\\w+)', 'import.*from'] }),\n replacement: z\n .string()\n .max(10000)\n .describe(\n 'Replacement text. Use capture group references ($1, $2, etc.) when isRegex=true. Use an empty string to delete all matches.',\n )\n .meta({ examples: ['$1_renamed', '', 'TODO: fix'] }),\n isRegex: defaultFalseBoolean('Treat searchPattern as a RE2 regex (default: literal text match)'),\n includeHidden: includeHiddenField(),\n includeIgnored: includeIgnoredField(),\n caseSensitive: defaultFalseBoolean('Enable case-sensitive matching (default: case-insensitive)'),\n wholeWord: defaultFalseBoolean('Match whole words only (word boundary anchoring)'),\n dryRun: defaultFalseBoolean(\n 'Preview replacements without writing to disk (default: false = apply changes)',\n ),\n returnDiff: defaultFalseBoolean('Include a unified diff of all changes in the response'),\n maxResults: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .default(DEFAULT_SEARCH_RESULTS)\n .describe('Maximum total match count across all files before stopping'),\n maxFiles: z\n .uint32()\n .min(1)\n .max(MAX_SEARCH_RESULTS)\n .optional()\n .describe('Maximum number of files to process'),\n maxDepth: maxDepthField(),\n});\n\nconst ReplacePerPathSchema = z.strictObject({\n path: z.string().describe('File path relative to the search root'),\n value: z\n .strictObject({ matches: NonNegInt.describe('Replacements applied in this file') })\n .optional()\n .describe('Replacement outcome; present on success'),\n error: PerFileErrorSchema.optional().describe('Error details; present on failure'),\n});\n\n// The same `{ results, summary }` envelope `read`, `stat`, and `delete` use, so\n// a caller learns one shape for every tool that spans many paths. The former\n// `filesModified` / `failedFiles` counters are `summary.succeeded` / `.failed`,\n// and the old `primaryFile` block is gone — the resource_link content block\n// already points at the first modified file.\nconst SearchAndReplaceOutputSchema = z.strictObject({\n results: z\n .array(ReplacePerPathSchema)\n .describe('Per-file results: modified files, then any that could not be processed'),\n summary: OperationSummarySchema,\n totalMatches: NonNegInt.describe('Total number of replacements made across all files'),\n filesScanned: NonNegInt.describe('Total number of files examined'),\n resultsTruncated: z\n .boolean()\n .optional()\n .describe(\n 'True when the results list holds fewer entries than summary.total: the changed-file or failed-file cap was hit. Trust summary over results.length.',\n ),\n diff: z\n .string()\n .optional()\n .describe('Unified diff of all changes (present when returnDiff=true or dryRun=true)'),\n diffTruncated: z\n .boolean()\n .optional()\n .describe('True when the diff was cut due to the size limit'),\n stoppedReason: StoppedReasonSchema.describe(\n 'Why enumeration stopped early: maxResults = match cap reached, maxFiles = file cap reached, timeout = time limit hit or cancelled. Absent when every matching file was enumerated. Marks the sweep incomplete, not the writes; files already dispatched still complete.',\n ),\n});\n\nconst MAX_FAILURES = 20;\nconst REPLACE_CONCURRENCY = Math.min(PARALLEL_CONCURRENCY, 8);\nconst MAX_CHANGED_FILES = 100;\nconst MAX_DIFF_SIZE = 20 * 1024; // 20KB limit for diff output\nconst DIFF_APPEND_BUFFER = 1024;\n\ninterface Failure {\n path: string;\n error: NonNullable<z.infer<typeof ReplacePerPathSchema>['error']>;\n}\n\nfunction recordFailure(failures: Failure[], failure: Failure): void {\n if (failures.length >= MAX_FAILURES) return;\n failures.push(failure);\n}\n\nfunction recordChangedFile(summary: ReplaceSummary, filePath: string, matchCount: number): void {\n const relativePath = toPosixRelative(summary.root, filePath);\n if (summary.changedFiles.length < MAX_CHANGED_FILES) {\n summary.changedFiles.push({ path: relativePath, matches: matchCount });\n return;\n }\n summary.changedFilesTruncated = true;\n}\n\ninterface ReplacementMatcher {\n replace(content: string, replacement: string): { content: string; matchCount: number };\n testBuffer(buffer: Buffer): boolean;\n /** Releases any compiled pattern this matcher owns. Idempotent. */\n dispose(): void;\n}\n\nconst DOLLAR_TOKEN = /\\$(\\$|&|`|'|<([^>]*)>|\\d{1,2})/g;\n\n/**\n * Expand `$`-substitutions in a replacement template the way `RegExp` does.\n *\n * We cannot hand the template to RE2's own string replacer: it throws\n * `Invalid replacement string` on any `$` not followed by a substitution it\n * recognises (so a replacement of `$100` or a trailing `$` fails outright,\n * where `RegExp` inserts the `$` literally), and it renders an out-of-range\n * `$5` as `$4`. The matcher splices matches itself, so this is the single owner\n * of the syntax.\n */\nfunction expandDollarTokens(\n template: string,\n match: string,\n groups: (string | undefined)[],\n offset: number,\n input: string,\n named: Record<string, string> | undefined,\n): string {\n return template.replace(DOLLAR_TOKEN, (token: string, kind: string, name?: string) => {\n if (kind === '$') return '$';\n if (kind === '&') return match;\n if (kind === '`') return input.slice(0, offset);\n if (kind === \"'\") return input.slice(offset + match.length);\n if (name !== undefined) {\n if (named && Object.hasOwn(named, name)) return named[name] ?? '';\n return token;\n }\n // `$12` prefers group 12, then falls back to group 1 followed by a literal\n // `2`, and stays literal when neither exists — RegExp's own precedence.\n const two = Number.parseInt(kind, 10);\n if (kind.length === 2 && two >= 1 && two <= groups.length) return groups[two - 1] ?? '';\n const one = Number.parseInt(kind.slice(0, 1), 10);\n if (one >= 1 && one <= groups.length) {\n return (groups[one - 1] ?? '') + (kind.length === 2 ? kind.slice(1) : '');\n }\n return token;\n });\n}\n\nfunction createRegexReplacementMatcher(\n regex: Regex,\n expandReplacement: boolean,\n): ReplacementMatcher {\n return {\n testBuffer(buffer: Buffer): boolean {\n // The regex is global and shared across every file in the batch, so a\n // previous file's match would otherwise start this scan mid-string.\n regex.lastIndex = 0;\n return regex.test(buffer.toString('utf-8'));\n },\n replace(content: string, replacement: string): { content: string; matchCount: number } {\n // Splicing by hand rather than through `String.replace(regex, …)`: RE2's\n // own replacer indexes the JS string with the code-point offsets it\n // reports, which lands the replacement short by one per astral character\n // (most emoji) earlier in the file. execMatches reports UTF-16 offsets.\n let matchCount = 0;\n let updated = '';\n let cursor = 0;\n for (const match of execMatches(regex, content)) {\n matchCount++;\n // Only isRegex=true opts into $1/$& substitution. A literal search\n // reaches this matcher too (case-insensitive and wholeWord both need a\n // regex), and there the replacement must be inserted verbatim.\n updated +=\n content.slice(cursor, match.start) +\n (expandReplacement\n ? expandDollarTokens(\n replacement,\n match.text,\n match.groups,\n match.start,\n content,\n match.named,\n )\n : replacement);\n cursor = match.end;\n }\n return { content: updated + content.slice(cursor), matchCount };\n },\n dispose(): void {\n freeRegex(regex);\n },\n };\n}\n\nfunction createCaseSensitiveLiteralMatcher(searchPattern: string): ReplacementMatcher {\n const searchBuffer = Buffer.from(searchPattern, 'utf8');\n\n return {\n testBuffer(buffer: Buffer): boolean {\n return buffer.indexOf(searchBuffer) !== -1;\n },\n replace(content: string, replacement: string): { content: string; matchCount: number } {\n let matchCount = 0;\n const updated = content.replaceAll(searchPattern, () => {\n matchCount++;\n return replacement;\n });\n return { content: updated, matchCount };\n },\n dispose(): void {\n // no compiled pattern to release\n },\n };\n}\n\ntype SearchAndReplaceArgs = z.infer<typeof SearchAndReplaceInputSchema>;\ntype SearchAndReplaceOutput = z.infer<typeof SearchAndReplaceOutputSchema>;\n\ninterface ReplaceContext {\n options: { dryRun: boolean; returnDiff: boolean };\n replacement: string;\n matcher: ReplacementMatcher;\n maxFileSize: number;\n signal: AbortSignal | undefined;\n summary: ReplaceSummary;\n fs: GuardedFileSystem;\n}\n\ninterface ReplacementPlan {\n matchCount: number;\n originalContent: string;\n updatedContent: string;\n}\n\nfunction buildReplacementPlan(\n content: string,\n replacement: string,\n matcher: ReplacementMatcher,\n): ReplacementPlan | undefined {\n const { content: updatedContent, matchCount } = matcher.replace(content, replacement);\n if (matchCount === 0) {\n return undefined;\n }\n\n return {\n matchCount,\n originalContent: content,\n updatedContent,\n };\n}\n\nasync function processEntry(entryPath: string, ctx: ReplaceContext): Promise<void> {\n const { options, signal, summary } = ctx;\n const validPath = entryPath;\n\n try {\n const plan = await readReplacementPlan(validPath, ctx);\n if (!plan) {\n return;\n }\n\n if (!options.dryRun) {\n await ctx.fs.writeFile(entryPath, plan.updatedContent, {\n encoding: 'utf-8',\n signal,\n });\n }\n\n // Bookkeep only after the write succeeds (or in dryRun, where there is no\n // write): a failed write must not count the file as changed.\n summary.totalMatches += plan.matchCount;\n summary.filesChanged++;\n\n recordChangedFile(summary, validPath, plan.matchCount);\n\n maybeAppendPatchDiff(summary, {\n filePath: validPath,\n originalContent: plan.originalContent,\n updatedContent: plan.updatedContent,\n includeDiff: options.dryRun || options.returnDiff,\n });\n } catch (error) {\n summary.failedFiles++;\n recordFailure(summary.failures, {\n path: toPosixRelative(summary.root, validPath),\n error: Problem.fromUnknown(error, ErrorCode.UNKNOWN, validPath),\n });\n }\n}\n\nasync function readReplacementPlan(\n validPath: string,\n ctx: ReplaceContext,\n): Promise<ReplacementPlan | undefined> {\n const { matcher, replacement, maxFileSize, signal } = ctx;\n await using fileHandle = await ctx.fs.open(validPath);\n const stats = await fileHandle.stat();\n if (stats.size > maxFileSize) {\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large: ${validPath} (${String(stats.size)} bytes > ${String(maxFileSize)} bytes)`,\n );\n }\n\n const buffer = await readFileBufferWithLimit(fileHandle, maxFileSize, validPath, signal);\n if (!matcher.testBuffer(buffer)) return undefined;\n\n return buildReplacementPlan(buffer.toString('utf-8'), replacement, matcher);\n}\n\nfunction maybeAppendPatchDiff(\n summary: ReplaceSummary,\n params: {\n filePath: string;\n originalContent: string;\n updatedContent: string;\n includeDiff: boolean;\n },\n): void {\n if (!params.includeDiff) return;\n const header = toPosixRelative(summary.root, params.filePath);\n\n const patch = unifiedPatch(header, params.originalContent, params.updatedContent);\n\n if (summary.diff.length >= MAX_DIFF_SIZE) {\n summary.diffTruncated = true;\n return;\n }\n\n if (summary.diff.length + patch.length <= MAX_DIFF_SIZE + DIFF_APPEND_BUFFER) {\n summary.diff += patch;\n return;\n }\n\n summary.diffTruncated = true;\n}\n\ninterface ReplaceSummary {\n root: string;\n totalMatches: number;\n filesChanged: number;\n failedFiles: number;\n processedFiles: number;\n failures: Failure[];\n changedFiles: { path: string; matches: number }[];\n changedFilesTruncated: boolean;\n diff: string;\n diffTruncated: boolean;\n stoppedReason?: StoppedReason;\n}\n\nfunction createReplaceSummary(root: string): ReplaceSummary {\n return {\n root,\n totalMatches: 0,\n filesChanged: 0,\n failedFiles: 0,\n processedFiles: 0,\n failures: [],\n changedFiles: [],\n changedFilesTruncated: false,\n diff: '',\n diffTruncated: false,\n };\n}\n\nasync function resolveSearchRoot(\n pathValue: string | undefined,\n fs: GuardedFileSystem,\n): Promise<{ root: string; singleFile?: string }> {\n if (!pathValue) {\n return { root: fs.pathGuard.resolvePathOrRoot(undefined) };\n }\n const resolvedPath = await fs.pathGuard.validateExistingPath(pathValue);\n const { stats: fileStats } = await fs.stat(resolvedPath);\n if (fileStats.isFile()) {\n // A single explicit file target bypasses the glob machinery entirely:\n // routing it through globEntries with baseNameMatch would rewrite the\n // escaped basename to `**/${basename}` and match every same-named file\n // under the parent tree, not just this one.\n return {\n root: dirname(resolvedPath),\n singleFile: resolvedPath,\n };\n }\n return { root: resolvedPath };\n}\n\nfunction buildSearchPattern(args: SearchAndReplaceArgs): string {\n if (args.isRegex) {\n return args.wholeWord ? `\\\\b(?:${args.searchPattern})\\\\b` : args.searchPattern;\n }\n const escaped = escapeRegexLiteral(args.searchPattern);\n return args.wholeWord ? `\\\\b(?:${escaped})\\\\b` : escaped;\n}\n\nfunction createReplacementMatcher(args: SearchAndReplaceArgs): ReplacementMatcher {\n // Use regex when isRegex, wholeWord, or case-insensitive (all require RE2)\n if (args.isRegex || args.wholeWord || !args.caseSensitive) {\n const regex = compileRegex(buildSearchPattern(args), { caseSensitive: args.caseSensitive });\n return createRegexReplacementMatcher(regex, args.isRegex);\n }\n return createCaseSensitiveLiteralMatcher(args.searchPattern);\n}\n\nfunction buildSearchAndReplaceStructuredResult(\n summary: ReplaceSummary,\n args: SearchAndReplaceArgs,\n): SearchAndReplaceOutput {\n const results = [\n ...summary.changedFiles.map((f) => ({ path: f.path, value: { matches: f.matches } })),\n ...summary.failures.map((f) => ({ path: f.path, error: f.error })),\n ];\n return {\n results,\n // `failedFiles` counts every failure; `failures` itself is capped at\n // MAX_FAILURES, so the summary must not be derived from the array length.\n summary: {\n total: summary.filesChanged + summary.failedFiles,\n succeeded: summary.filesChanged,\n failed: summary.failedFiles,\n },\n totalMatches: summary.totalMatches,\n filesScanned: summary.processedFiles,\n // Failures are capped at MAX_FAILURES independently of the changed-file\n // cap, so either cap can leave `results` shorter than `summary.total`.\n ...(summary.changedFilesTruncated || summary.failures.length < summary.failedFiles\n ? { resultsTruncated: true }\n : {}),\n ...((args.dryRun || args.returnDiff) && summary.diff ? { diff: summary.diff } : {}),\n ...(summary.diffTruncated ? { diffTruncated: true } : {}),\n ...(summary.stoppedReason ? { stoppedReason: summary.stoppedReason } : {}),\n };\n}\n\nasync function handleSearchAndReplace(\n args: SearchAndReplaceArgs,\n ctx: ToolCtx,\n): Promise<{\n structured: SearchAndReplaceOutput;\n link?: ContentBlock;\n}> {\n const maxFileSize = getMaxTextFileSize();\n const { root, singleFile } = await resolveSearchRoot(args.path, ctx.fs);\n const effectivePattern = args.pattern ?? '**/*';\n\n // An explicit single-file target bypasses baseNameMatch/exclude/hidden/\n // gitignore filtering — it should always be processed as the one file named.\n // The async generator matches globEntries' AsyncIterable contract; it needs\n // no await (single yield), hence the disable.\n const entries: AsyncIterable<{ path: string }> = singleFile\n ? // eslint-disable-next-line @typescript-eslint/require-await\n (async function* singleFileEntry() {\n yield { path: singleFile };\n })()\n : globEntries({\n cwd: root,\n pattern: effectivePattern,\n excludePatterns: args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS,\n includeHidden: args.includeHidden,\n respectGitignore: !args.includeIgnored,\n baseNameMatch: true,\n onlyFiles: true,\n suppressErrors: true,\n ...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}),\n });\n\n const summary = createReplaceSummary(root);\n\n // The matcher may own a compiled RE2 pattern, whose wasm memory re2-wasm\n // never reclaims on its own. Nothing past the scan touches it, so free it the\n // moment the scan is done — success, failure, or abort alike.\n const matcher = createReplacementMatcher(args);\n let scan: StopReasonTracker;\n try {\n const context: ReplaceContext = {\n options: {\n dryRun: args.dryRun,\n returnDiff: args.returnDiff,\n },\n replacement: args.replacement,\n matcher,\n maxFileSize,\n signal: ctx.signal,\n summary,\n fs: ctx.fs,\n };\n\n scan = await processEntriesConcurrently(entries, {\n signal: ctx.signal,\n concurrency: REPLACE_CONCURRENCY,\n ...(args.maxFiles !== undefined ? { maxEntries: args.maxFiles } : {}),\n shouldStop: () => summary.totalMatches >= args.maxResults,\n onEntry: () => {\n summary.processedFiles++;\n ctx.onProgress?.({ current: summary.processedFiles });\n },\n onError: (entryPath, err) => {\n summary.failedFiles++;\n recordFailure(summary.failures, {\n path: toPosixRelative(summary.root, entryPath),\n error: Problem.fromUnknown(err, ErrorCode.UNKNOWN, entryPath),\n });\n },\n runEntry: (entryPath) => processEntry(entryPath, context),\n });\n } finally {\n matcher.dispose();\n }\n const stoppedReason = scan.resolve();\n if (stoppedReason !== undefined) summary.stoppedReason = stoppedReason;\n\n ctx.onProgress?.({ current: summary.processedFiles });\n\n if (!args.dryRun && summary.totalMatches > 0) {\n ctx.log?.(\n 'info',\n `${summary.filesChanged} file(s) changed, ${summary.totalMatches} match(es)`,\n 'replace_text',\n );\n }\n\n const structured = buildSearchAndReplaceStructuredResult(summary, args);\n\n // Store primary file in resource store if available (skip in dryRun to avoid reading stale disk content)\n if (\n !args.dryRun &&\n ctx.resourceStore &&\n summary.changedFiles.length > 0 &&\n summary.filesChanged > 0\n ) {\n const primaryFile = summary.changedFiles[0];\n if (!primaryFile) return { structured };\n\n const primaryFilePath = primaryFile.path;\n const fullPath = join(summary.root, primaryFilePath);\n\n try {\n const { content: rawBuffer, mimeType } = await ctx.fs.readRaw(fullPath, {\n signal: ctx.signal,\n });\n const link = buildFileResourceLink(fullPath, mimeType, rawBuffer.length);\n return { structured, link };\n } catch (error) {\n rethrowIfAborted(error);\n // Gracefully fall back if resource storage fails\n ctx.log?.(\n 'error',\n `Failed to store primary file in resource store: ${formatUnknownErrorMessage(error)}`,\n 'replace_text',\n );\n }\n }\n\n return { structured };\n}\n\nexport const SEARCH_AND_REPLACE = defineTool({\n name: 'replace_text',\n title: 'Search and Replace',\n description:\n 'Bulk search-and-replace across files matching a glob pattern. ' +\n 'Replaces ALL occurrences per file (unlike edit, which replaces only the first match). ' +\n 'Set returnDiff=true to preview changes as a unified diff before or after writing. ' +\n 'Literal matching by default; set isRegex=true to enable RE2 regex with capture groups ($1, $2).',\n input: SearchAndReplaceInputSchema,\n output: SearchAndReplaceOutputSchema,\n annotations: {\n readOnlyHint: false,\n idempotentHint: false,\n destructiveHint: true,\n openWorldHint: false,\n },\n timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,\n defaultErrorCode: ErrorCode.UNKNOWN,\n progress: (args) => {\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n return {\n label: `Replace${dryLabel}`,\n subject: `${truncateProgressPattern(args.searchPattern)} → ${truncateProgressPattern(args.replacement)}`,\n };\n },\n accessPaths: (args) => (args.path ? [args.path] : []),\n run: async (args, ctx) => {\n const truncatedPattern = truncateProgressPattern(args.searchPattern);\n const { structured, link } = await handleSearchAndReplace(args, ctx);\n const dryLabel = args.dryRun ? ' [dry run]' : '';\n const summaryText =\n `replace_text: '${truncatedPattern}'${dryLabel}` +\n ` \\u00b7 ${String(structured.totalMatches)} match(es)` +\n ` in ${String(structured.summary.succeeded)} file(s)` +\n (structured.summary.failed > 0 ? ` \\u00b7 ${String(structured.summary.failed)} failed` : '');\n const isError = isTotalFailure(structured.summary);\n if (link) {\n return { structured, text: summaryText, resources: [link], isError };\n }\n return { structured, text: summaryText, isError };\n },\n});\n"]}
|
package/package.json
CHANGED