@llmist/cli 16.0.1 → 16.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +253 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.js +248 -14
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/cli.js
CHANGED
|
@@ -110,7 +110,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
|
|
|
110
110
|
// package.json
|
|
111
111
|
var package_default = {
|
|
112
112
|
name: "@llmist/cli",
|
|
113
|
-
version: "16.0.
|
|
113
|
+
version: "16.0.3",
|
|
114
114
|
description: "CLI for llmist - run LLM agents from the command line",
|
|
115
115
|
type: "module",
|
|
116
116
|
main: "dist/cli.js",
|
|
@@ -167,8 +167,9 @@ var package_default = {
|
|
|
167
167
|
node: ">=22.0.0"
|
|
168
168
|
},
|
|
169
169
|
dependencies: {
|
|
170
|
-
llmist: "^16.0.
|
|
170
|
+
llmist: "^16.0.3",
|
|
171
171
|
"@unblessed/node": "^1.0.0-alpha.23",
|
|
172
|
+
"diff-match-patch": "^1.0.5",
|
|
172
173
|
chalk: "^5.6.2",
|
|
173
174
|
commander: "^12.1.0",
|
|
174
175
|
diff: "^8.0.2",
|
|
@@ -181,8 +182,9 @@ var package_default = {
|
|
|
181
182
|
zod: "^4.1.12"
|
|
182
183
|
},
|
|
183
184
|
devDependencies: {
|
|
184
|
-
"@llmist/testing": "^16.0.
|
|
185
|
+
"@llmist/testing": "^16.0.3",
|
|
185
186
|
"@types/diff": "^8.0.0",
|
|
187
|
+
"@types/diff-match-patch": "^1.0.36",
|
|
186
188
|
"@types/js-yaml": "^4.0.9",
|
|
187
189
|
"@types/marked-terminal": "^6.1.1",
|
|
188
190
|
"@types/node": "^20.12.7",
|
|
@@ -1719,18 +1721,22 @@ import { createGadget as createGadget3 } from "llmist";
|
|
|
1719
1721
|
import { z as z3 } from "zod";
|
|
1720
1722
|
|
|
1721
1723
|
// src/builtins/filesystem/editfile/matcher.ts
|
|
1724
|
+
import DiffMatchPatch from "diff-match-patch";
|
|
1725
|
+
var dmp = new DiffMatchPatch();
|
|
1722
1726
|
var DEFAULT_OPTIONS = {
|
|
1723
1727
|
fuzzyThreshold: 0.8,
|
|
1724
1728
|
maxSuggestions: 3,
|
|
1725
1729
|
contextLines: 5
|
|
1726
1730
|
};
|
|
1727
1731
|
function findMatch(content, search, options = {}) {
|
|
1732
|
+
if (!search) return null;
|
|
1728
1733
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
1729
1734
|
const strategies = [
|
|
1730
1735
|
{ name: "exact", fn: exactMatch },
|
|
1731
1736
|
{ name: "whitespace", fn: whitespaceMatch },
|
|
1732
1737
|
{ name: "indentation", fn: indentationMatch },
|
|
1733
|
-
{ name: "fuzzy", fn: (c, s) => fuzzyMatch(c, s, opts.fuzzyThreshold) }
|
|
1738
|
+
{ name: "fuzzy", fn: (c, s) => fuzzyMatch(c, s, opts.fuzzyThreshold) },
|
|
1739
|
+
{ name: "dmp", fn: (c, s) => dmpMatch(c, s, opts.fuzzyThreshold) }
|
|
1734
1740
|
];
|
|
1735
1741
|
for (const { name, fn } of strategies) {
|
|
1736
1742
|
const result = fn(content, search);
|
|
@@ -1796,7 +1802,8 @@ function indentationMatch(content, search) {
|
|
|
1796
1802
|
const stripIndent = (s) => s.split("\n").map((line) => line.trimStart()).join("\n");
|
|
1797
1803
|
const strippedSearch = stripIndent(search);
|
|
1798
1804
|
const contentLines = content.split("\n");
|
|
1799
|
-
const
|
|
1805
|
+
const searchLines = search.split("\n");
|
|
1806
|
+
const searchLineCount = searchLines.length;
|
|
1800
1807
|
for (let i = 0; i <= contentLines.length - searchLineCount; i++) {
|
|
1801
1808
|
const windowLines = contentLines.slice(i, i + searchLineCount);
|
|
1802
1809
|
const strippedWindow = stripIndent(windowLines.join("\n"));
|
|
@@ -1805,6 +1812,7 @@ function indentationMatch(content, search) {
|
|
|
1805
1812
|
const matchedContent = windowLines.join("\n");
|
|
1806
1813
|
const endIndex = startIndex + matchedContent.length;
|
|
1807
1814
|
const { startLine, endLine } = getLineNumbers(content, startIndex, endIndex);
|
|
1815
|
+
const indentationDelta = computeIndentationDelta(searchLines, windowLines);
|
|
1808
1816
|
return {
|
|
1809
1817
|
found: true,
|
|
1810
1818
|
strategy: "indentation",
|
|
@@ -1813,7 +1821,8 @@ function indentationMatch(content, search) {
|
|
|
1813
1821
|
startIndex,
|
|
1814
1822
|
endIndex,
|
|
1815
1823
|
startLine,
|
|
1816
|
-
endLine
|
|
1824
|
+
endLine,
|
|
1825
|
+
indentationDelta
|
|
1817
1826
|
};
|
|
1818
1827
|
}
|
|
1819
1828
|
}
|
|
@@ -1851,6 +1860,94 @@ function fuzzyMatch(content, search, threshold) {
|
|
|
1851
1860
|
endLine
|
|
1852
1861
|
};
|
|
1853
1862
|
}
|
|
1863
|
+
function dmpMatch(content, search, threshold) {
|
|
1864
|
+
if (!search || !content) return null;
|
|
1865
|
+
if (search.length > 1e3) return null;
|
|
1866
|
+
if (search.split("\n").length > 20) return null;
|
|
1867
|
+
const matchIndex = search.length <= 32 ? dmpMatchShortPattern(content, search, threshold) : dmpMatchLongPattern(content, search, threshold);
|
|
1868
|
+
if (matchIndex === -1) return null;
|
|
1869
|
+
const matchedContent = findBestMatchExtent(content, matchIndex, search, threshold);
|
|
1870
|
+
if (!matchedContent) return null;
|
|
1871
|
+
const startIndex = matchIndex;
|
|
1872
|
+
const endIndex = matchIndex + matchedContent.length;
|
|
1873
|
+
const { startLine, endLine } = getLineNumbers(content, startIndex, endIndex);
|
|
1874
|
+
const similarity = stringSimilarity(search, matchedContent);
|
|
1875
|
+
return {
|
|
1876
|
+
found: true,
|
|
1877
|
+
strategy: "dmp",
|
|
1878
|
+
confidence: similarity,
|
|
1879
|
+
matchedContent,
|
|
1880
|
+
startIndex,
|
|
1881
|
+
endIndex,
|
|
1882
|
+
startLine,
|
|
1883
|
+
endLine
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
function dmpMatchShortPattern(content, search, threshold) {
|
|
1887
|
+
dmp.Match_Threshold = 1 - threshold;
|
|
1888
|
+
dmp.Match_Distance = 1e3;
|
|
1889
|
+
const index = dmp.match_main(content, search, 0);
|
|
1890
|
+
return index;
|
|
1891
|
+
}
|
|
1892
|
+
function dmpMatchLongPattern(content, search, threshold) {
|
|
1893
|
+
const prefix = search.slice(0, 32);
|
|
1894
|
+
dmp.Match_Threshold = 1 - threshold;
|
|
1895
|
+
dmp.Match_Distance = 1e3;
|
|
1896
|
+
const prefixIndex = dmp.match_main(content, prefix, 0);
|
|
1897
|
+
if (prefixIndex === -1) return -1;
|
|
1898
|
+
const windowPadding = Math.max(50, Math.floor(search.length / 2));
|
|
1899
|
+
const windowStart = Math.max(0, prefixIndex - windowPadding);
|
|
1900
|
+
const windowEnd = Math.min(content.length, prefixIndex + search.length + windowPadding);
|
|
1901
|
+
const window = content.slice(windowStart, windowEnd);
|
|
1902
|
+
let bestIndex = -1;
|
|
1903
|
+
let bestSimilarity = 0;
|
|
1904
|
+
for (let i = 0; i <= window.length - search.length; i++) {
|
|
1905
|
+
const candidate = window.slice(i, i + search.length);
|
|
1906
|
+
const similarity = stringSimilarity(search, candidate);
|
|
1907
|
+
if (similarity >= threshold && similarity > bestSimilarity) {
|
|
1908
|
+
bestSimilarity = similarity;
|
|
1909
|
+
bestIndex = windowStart + i;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
return bestIndex;
|
|
1913
|
+
}
|
|
1914
|
+
function findBestMatchExtent(content, matchIndex, search, threshold) {
|
|
1915
|
+
const exactLength = content.slice(matchIndex, matchIndex + search.length);
|
|
1916
|
+
if (stringSimilarity(search, exactLength) >= threshold) {
|
|
1917
|
+
return exactLength;
|
|
1918
|
+
}
|
|
1919
|
+
const searchLines = search.split("\n").length;
|
|
1920
|
+
const contentFromMatch = content.slice(matchIndex);
|
|
1921
|
+
const contentLines = contentFromMatch.split("\n");
|
|
1922
|
+
if (contentLines.length >= searchLines) {
|
|
1923
|
+
const lineBasedMatch = contentLines.slice(0, searchLines).join("\n");
|
|
1924
|
+
if (stringSimilarity(search, lineBasedMatch) >= threshold) {
|
|
1925
|
+
return lineBasedMatch;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
return null;
|
|
1929
|
+
}
|
|
1930
|
+
function findAllMatches(content, search, options = {}) {
|
|
1931
|
+
const results = [];
|
|
1932
|
+
let searchStart = 0;
|
|
1933
|
+
while (searchStart < content.length) {
|
|
1934
|
+
const remainingContent = content.slice(searchStart);
|
|
1935
|
+
const match = findMatch(remainingContent, search, options);
|
|
1936
|
+
if (!match) break;
|
|
1937
|
+
results.push({
|
|
1938
|
+
...match,
|
|
1939
|
+
startIndex: searchStart + match.startIndex,
|
|
1940
|
+
endIndex: searchStart + match.endIndex,
|
|
1941
|
+
// Recalculate line numbers for original content
|
|
1942
|
+
...getLineNumbers(content, searchStart + match.startIndex, searchStart + match.endIndex)
|
|
1943
|
+
});
|
|
1944
|
+
searchStart = searchStart + match.endIndex;
|
|
1945
|
+
if (match.endIndex === 0) {
|
|
1946
|
+
searchStart++;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
return results;
|
|
1950
|
+
}
|
|
1854
1951
|
function findSuggestions(content, search, maxSuggestions, minSimilarity) {
|
|
1855
1952
|
const searchLines = search.split("\n");
|
|
1856
1953
|
const contentLines = content.split("\n");
|
|
@@ -1967,6 +2064,78 @@ function getContext(content, lineNumber, contextLines) {
|
|
|
1967
2064
|
});
|
|
1968
2065
|
return contextWithNumbers.join("\n");
|
|
1969
2066
|
}
|
|
2067
|
+
function getLeadingWhitespace(line) {
|
|
2068
|
+
const match = line.match(/^[ \t]*/);
|
|
2069
|
+
return match ? match[0] : "";
|
|
2070
|
+
}
|
|
2071
|
+
function computeIndentationDelta(searchLines, matchedLines) {
|
|
2072
|
+
for (let i = 0; i < Math.min(searchLines.length, matchedLines.length); i++) {
|
|
2073
|
+
const searchLine = searchLines[i];
|
|
2074
|
+
const matchedLine = matchedLines[i];
|
|
2075
|
+
if (searchLine.trim() === "" && matchedLine.trim() === "") continue;
|
|
2076
|
+
const searchIndent = getLeadingWhitespace(searchLine);
|
|
2077
|
+
const matchedIndent = getLeadingWhitespace(matchedLine);
|
|
2078
|
+
if (matchedIndent.length > searchIndent.length) {
|
|
2079
|
+
return matchedIndent.slice(searchIndent.length);
|
|
2080
|
+
}
|
|
2081
|
+
return "";
|
|
2082
|
+
}
|
|
2083
|
+
return "";
|
|
2084
|
+
}
|
|
2085
|
+
function adjustIndentation(replacement, delta) {
|
|
2086
|
+
if (!delta) return replacement;
|
|
2087
|
+
return replacement.split("\n").map((line, index) => {
|
|
2088
|
+
if (line.trim() === "") return line;
|
|
2089
|
+
return delta + line;
|
|
2090
|
+
}).join("\n");
|
|
2091
|
+
}
|
|
2092
|
+
function formatEditContext(originalContent, match, replacement, contextLines = 5) {
|
|
2093
|
+
const lines = originalContent.split("\n");
|
|
2094
|
+
const startLine = match.startLine - 1;
|
|
2095
|
+
const endLine = match.endLine;
|
|
2096
|
+
const contextStart = Math.max(0, startLine - contextLines);
|
|
2097
|
+
const contextEnd = Math.min(lines.length, endLine + contextLines);
|
|
2098
|
+
const output = [];
|
|
2099
|
+
output.push(`=== Edit (lines ${match.startLine}-${match.endLine}) ===`);
|
|
2100
|
+
output.push("");
|
|
2101
|
+
for (let i = contextStart; i < startLine; i++) {
|
|
2102
|
+
output.push(` ${String(i + 1).padStart(4)} | ${lines[i]}`);
|
|
2103
|
+
}
|
|
2104
|
+
for (let i = startLine; i < endLine; i++) {
|
|
2105
|
+
output.push(`< ${String(i + 1).padStart(4)} | ${lines[i]}`);
|
|
2106
|
+
}
|
|
2107
|
+
const replacementLines = replacement.split("\n");
|
|
2108
|
+
for (let i = 0; i < replacementLines.length; i++) {
|
|
2109
|
+
const lineNum = startLine + i + 1;
|
|
2110
|
+
output.push(`> ${String(lineNum).padStart(4)} | ${replacementLines[i]}`);
|
|
2111
|
+
}
|
|
2112
|
+
for (let i = endLine; i < contextEnd; i++) {
|
|
2113
|
+
output.push(` ${String(i + 1).padStart(4)} | ${lines[i]}`);
|
|
2114
|
+
}
|
|
2115
|
+
return output.join("\n");
|
|
2116
|
+
}
|
|
2117
|
+
function formatMultipleMatches(content, matches, maxMatches = 5) {
|
|
2118
|
+
const lines = content.split("\n");
|
|
2119
|
+
const output = [];
|
|
2120
|
+
output.push(`Found ${matches.length} matches:`);
|
|
2121
|
+
output.push("");
|
|
2122
|
+
const displayMatches = matches.slice(0, maxMatches);
|
|
2123
|
+
for (let i = 0; i < displayMatches.length; i++) {
|
|
2124
|
+
const match = displayMatches[i];
|
|
2125
|
+
output.push(`Match ${i + 1} (lines ${match.startLine}-${match.endLine}):`);
|
|
2126
|
+
const contextStart = Math.max(0, match.startLine - 2);
|
|
2127
|
+
const contextEnd = Math.min(lines.length, match.endLine + 1);
|
|
2128
|
+
for (let j = contextStart; j < contextEnd; j++) {
|
|
2129
|
+
const marker = j >= match.startLine - 1 && j < match.endLine ? ">" : " ";
|
|
2130
|
+
output.push(`${marker}${String(j + 1).padStart(4)} | ${lines[j]}`);
|
|
2131
|
+
}
|
|
2132
|
+
output.push("");
|
|
2133
|
+
}
|
|
2134
|
+
if (matches.length > maxMatches) {
|
|
2135
|
+
output.push(`... and ${matches.length - maxMatches} more matches`);
|
|
2136
|
+
}
|
|
2137
|
+
return output.join("\n");
|
|
2138
|
+
}
|
|
1970
2139
|
|
|
1971
2140
|
// src/builtins/filesystem/utils.ts
|
|
1972
2141
|
import fs from "fs";
|
|
@@ -2038,22 +2207,30 @@ Uses layered matching strategies (in order):
|
|
|
2038
2207
|
2. Whitespace-insensitive - ignores differences in spaces/tabs
|
|
2039
2208
|
3. Indentation-preserving - matches structure ignoring leading whitespace
|
|
2040
2209
|
4. Fuzzy match - similarity-based matching (80% threshold)
|
|
2210
|
+
5. DMP (diff-match-patch) - handles heavily refactored code
|
|
2041
2211
|
|
|
2042
2212
|
For multiple edits to the same file, call this gadget multiple times.
|
|
2043
|
-
Each call provides immediate feedback, allowing you to adjust subsequent edits
|
|
2213
|
+
Each call provides immediate feedback, allowing you to adjust subsequent edits.
|
|
2214
|
+
|
|
2215
|
+
Options:
|
|
2216
|
+
- replaceAll: Replace all occurrences instead of just the first
|
|
2217
|
+
- expectedCount: Validate exact number of matches before applying`,
|
|
2044
2218
|
maxConcurrent: 1,
|
|
2045
2219
|
// Sequential execution to prevent race conditions
|
|
2046
2220
|
schema: z3.object({
|
|
2047
2221
|
filePath: z3.string().describe("Path to the file to edit (relative or absolute)"),
|
|
2048
2222
|
search: z3.string().describe("The content to search for in the file"),
|
|
2049
|
-
replace: z3.string().describe("The content to replace it with (empty string to delete)")
|
|
2223
|
+
replace: z3.string().describe("The content to replace it with (empty string to delete)"),
|
|
2224
|
+
replaceAll: z3.boolean().optional().default(false).describe("Replace all occurrences instead of just the first match"),
|
|
2225
|
+
expectedCount: z3.number().int().positive().optional().describe("Expected number of matches. Edit fails if actual count differs")
|
|
2050
2226
|
}),
|
|
2051
2227
|
examples: [
|
|
2052
2228
|
{
|
|
2053
2229
|
params: {
|
|
2054
2230
|
filePath: "src/config.ts",
|
|
2055
2231
|
search: "const DEBUG = false;",
|
|
2056
|
-
replace: "const DEBUG = true;"
|
|
2232
|
+
replace: "const DEBUG = true;",
|
|
2233
|
+
replaceAll: false
|
|
2057
2234
|
},
|
|
2058
2235
|
output: "path=src/config.ts status=success strategy=exact lines=5-5\n\nReplaced content successfully.\n\nUPDATED FILE CONTENT:\n```\n// config.ts\nconst DEBUG = true;\nexport default { DEBUG };\n```",
|
|
2059
2236
|
comment: "Simple single-line edit"
|
|
@@ -2066,7 +2243,8 @@ Each call provides immediate feedback, allowing you to adjust subsequent edits.`
|
|
|
2066
2243
|
}`,
|
|
2067
2244
|
replace: `function newHelper() {
|
|
2068
2245
|
return 2;
|
|
2069
|
-
}
|
|
2246
|
+
}`,
|
|
2247
|
+
replaceAll: false
|
|
2070
2248
|
},
|
|
2071
2249
|
output: "path=src/utils.ts status=success strategy=exact lines=10-12\n\nReplaced content successfully.\n\nUPDATED FILE CONTENT:\n```\n// utils.ts\nfunction newHelper() {\n return 2;\n}\n```",
|
|
2072
2250
|
comment: "Multi-line replacement"
|
|
@@ -2075,14 +2253,25 @@ Each call provides immediate feedback, allowing you to adjust subsequent edits.`
|
|
|
2075
2253
|
params: {
|
|
2076
2254
|
filePath: "src/app.ts",
|
|
2077
2255
|
search: "unusedImport",
|
|
2078
|
-
replace: ""
|
|
2256
|
+
replace: "",
|
|
2257
|
+
replaceAll: false
|
|
2079
2258
|
},
|
|
2080
2259
|
output: 'path=src/app.ts status=success strategy=exact lines=3-3\n\nReplaced content successfully.\n\nUPDATED FILE CONTENT:\n```\n// app.ts\nimport { usedImport } from "./lib";\n```',
|
|
2081
2260
|
comment: "Delete content by replacing with empty string"
|
|
2261
|
+
},
|
|
2262
|
+
{
|
|
2263
|
+
params: {
|
|
2264
|
+
filePath: "src/constants.ts",
|
|
2265
|
+
search: "OLD_VALUE",
|
|
2266
|
+
replace: "NEW_VALUE",
|
|
2267
|
+
replaceAll: true
|
|
2268
|
+
},
|
|
2269
|
+
output: "path=src/constants.ts status=success matches=3 lines=[2-2, 5-5, 8-8]\n\nReplaced 3 occurrences\n\nUPDATED FILE CONTENT:\n```\n// constants.ts\nexport const A = NEW_VALUE;\nexport const B = NEW_VALUE;\nexport const C = NEW_VALUE;\n```",
|
|
2270
|
+
comment: "Replace all occurrences with replaceAll=true"
|
|
2082
2271
|
}
|
|
2083
2272
|
],
|
|
2084
2273
|
timeoutMs: 3e4,
|
|
2085
|
-
execute: ({ filePath, search, replace }) => {
|
|
2274
|
+
execute: ({ filePath, search, replace, replaceAll, expectedCount }) => {
|
|
2086
2275
|
if (search.trim() === "") {
|
|
2087
2276
|
return `path=${filePath} status=error
|
|
2088
2277
|
|
|
@@ -2112,12 +2301,43 @@ Error: File not found: ${filePath}`;
|
|
|
2112
2301
|
|
|
2113
2302
|
Error reading file: ${message}`;
|
|
2114
2303
|
}
|
|
2115
|
-
const
|
|
2116
|
-
if (
|
|
2304
|
+
const allMatches = findAllMatches(content, search);
|
|
2305
|
+
if (allMatches.length === 0) {
|
|
2117
2306
|
const failure = getMatchFailure(content, search);
|
|
2118
2307
|
return formatFailure(filePath, search, failure, content);
|
|
2119
2308
|
}
|
|
2120
|
-
|
|
2309
|
+
if (expectedCount !== void 0 && allMatches.length !== expectedCount) {
|
|
2310
|
+
return `path=${filePath} status=error
|
|
2311
|
+
|
|
2312
|
+
Error: Expected ${expectedCount} match(es) but found ${allMatches.length}.
|
|
2313
|
+
|
|
2314
|
+
${formatMultipleMatches(content, allMatches)}`;
|
|
2315
|
+
}
|
|
2316
|
+
if (allMatches.length > 1 && !replaceAll) {
|
|
2317
|
+
const matchSummary = formatMultipleMatches(content, allMatches);
|
|
2318
|
+
return `path=${filePath} status=error
|
|
2319
|
+
|
|
2320
|
+
Error: Found ${allMatches.length} matches. Please either:
|
|
2321
|
+
1. Add more context to your search to make it unique
|
|
2322
|
+
2. Use replaceAll=true to replace all occurrences
|
|
2323
|
+
|
|
2324
|
+
${matchSummary}`;
|
|
2325
|
+
}
|
|
2326
|
+
let newContent;
|
|
2327
|
+
let editSummary;
|
|
2328
|
+
if (replaceAll && allMatches.length > 1) {
|
|
2329
|
+
newContent = executeReplaceAll(content, allMatches, replace);
|
|
2330
|
+
const MAX_DISPLAYED_RANGES = 5;
|
|
2331
|
+
const displayMatches = allMatches.slice(0, MAX_DISPLAYED_RANGES);
|
|
2332
|
+
const lineRanges = displayMatches.map((m) => `${m.startLine}-${m.endLine}`).join(", ");
|
|
2333
|
+
const suffix = allMatches.length > MAX_DISPLAYED_RANGES ? `, +${allMatches.length - MAX_DISPLAYED_RANGES} more` : "";
|
|
2334
|
+
editSummary = `matches=${allMatches.length} lines=[${lineRanges}${suffix}]`;
|
|
2335
|
+
} else {
|
|
2336
|
+
const match = allMatches[0];
|
|
2337
|
+
const finalReplace = prepareReplacement(match, replace);
|
|
2338
|
+
newContent = applyReplacement(content, match, finalReplace);
|
|
2339
|
+
editSummary = `strategy=${match.strategy} lines=${match.startLine}-${match.endLine}`;
|
|
2340
|
+
}
|
|
2121
2341
|
try {
|
|
2122
2342
|
writeFileSync(validatedPath, newContent, "utf-8");
|
|
2123
2343
|
} catch (error) {
|
|
@@ -2126,9 +2346,10 @@ Error reading file: ${message}`;
|
|
|
2126
2346
|
|
|
2127
2347
|
Error writing file: ${message}`;
|
|
2128
2348
|
}
|
|
2129
|
-
|
|
2349
|
+
const diffContext = allMatches.length === 1 ? formatEditContext(content, allMatches[0], prepareReplacement(allMatches[0], replace)) : `Replaced ${allMatches.length} occurrences`;
|
|
2350
|
+
return `path=${filePath} status=success ${editSummary}
|
|
2130
2351
|
|
|
2131
|
-
|
|
2352
|
+
${diffContext}
|
|
2132
2353
|
|
|
2133
2354
|
UPDATED FILE CONTENT:
|
|
2134
2355
|
\`\`\`
|
|
@@ -2136,6 +2357,21 @@ ${newContent}
|
|
|
2136
2357
|
\`\`\``;
|
|
2137
2358
|
}
|
|
2138
2359
|
});
|
|
2360
|
+
function prepareReplacement(match, replace) {
|
|
2361
|
+
if (match.strategy === "indentation" && match.indentationDelta) {
|
|
2362
|
+
return adjustIndentation(replace, match.indentationDelta);
|
|
2363
|
+
}
|
|
2364
|
+
return replace;
|
|
2365
|
+
}
|
|
2366
|
+
function executeReplaceAll(content, matches, replace) {
|
|
2367
|
+
const sortedMatches = [...matches].sort((a, b) => b.startIndex - a.startIndex);
|
|
2368
|
+
let result = content;
|
|
2369
|
+
for (const match of sortedMatches) {
|
|
2370
|
+
const finalReplace = prepareReplacement(match, replace);
|
|
2371
|
+
result = applyReplacement(result, match, finalReplace);
|
|
2372
|
+
}
|
|
2373
|
+
return result;
|
|
2374
|
+
}
|
|
2139
2375
|
|
|
2140
2376
|
// src/builtins/filesystem/list-directory.ts
|
|
2141
2377
|
import fs2 from "fs";
|