@llmist/cli 16.0.0 → 16.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +255 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.js +248 -14
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
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.2",
|
|
114
114
|
description: "CLI for llmist - run LLM agents from the command line",
|
|
115
115
|
type: "module",
|
|
116
116
|
main: "dist/cli.js",
|
|
@@ -136,7 +136,8 @@ var package_default = {
|
|
|
136
136
|
build: "tsup",
|
|
137
137
|
typecheck: "tsc --noEmit",
|
|
138
138
|
clean: "rimraf dist",
|
|
139
|
-
postinstall: "node scripts/postinstall.js"
|
|
139
|
+
postinstall: "node scripts/postinstall.js",
|
|
140
|
+
dev: "npx --silent tsx src/cli.ts"
|
|
140
141
|
},
|
|
141
142
|
homepage: "https://llmist.dev",
|
|
142
143
|
repository: {
|
|
@@ -166,8 +167,9 @@ var package_default = {
|
|
|
166
167
|
node: ">=22.0.0"
|
|
167
168
|
},
|
|
168
169
|
dependencies: {
|
|
169
|
-
llmist: "^16.0.
|
|
170
|
+
llmist: "^16.0.2",
|
|
170
171
|
"@unblessed/node": "^1.0.0-alpha.23",
|
|
172
|
+
"diff-match-patch": "^1.0.5",
|
|
171
173
|
chalk: "^5.6.2",
|
|
172
174
|
commander: "^12.1.0",
|
|
173
175
|
diff: "^8.0.2",
|
|
@@ -180,8 +182,9 @@ var package_default = {
|
|
|
180
182
|
zod: "^4.1.12"
|
|
181
183
|
},
|
|
182
184
|
devDependencies: {
|
|
183
|
-
"@llmist/testing": "^16.0.
|
|
185
|
+
"@llmist/testing": "^16.0.2",
|
|
184
186
|
"@types/diff": "^8.0.0",
|
|
187
|
+
"@types/diff-match-patch": "^1.0.36",
|
|
185
188
|
"@types/js-yaml": "^4.0.9",
|
|
186
189
|
"@types/marked-terminal": "^6.1.1",
|
|
187
190
|
"@types/node": "^20.12.7",
|
|
@@ -1718,18 +1721,22 @@ import { createGadget as createGadget3 } from "llmist";
|
|
|
1718
1721
|
import { z as z3 } from "zod";
|
|
1719
1722
|
|
|
1720
1723
|
// src/builtins/filesystem/editfile/matcher.ts
|
|
1724
|
+
import DiffMatchPatch from "diff-match-patch";
|
|
1725
|
+
var dmp = new DiffMatchPatch();
|
|
1721
1726
|
var DEFAULT_OPTIONS = {
|
|
1722
1727
|
fuzzyThreshold: 0.8,
|
|
1723
1728
|
maxSuggestions: 3,
|
|
1724
1729
|
contextLines: 5
|
|
1725
1730
|
};
|
|
1726
1731
|
function findMatch(content, search, options = {}) {
|
|
1732
|
+
if (!search) return null;
|
|
1727
1733
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
1728
1734
|
const strategies = [
|
|
1729
1735
|
{ name: "exact", fn: exactMatch },
|
|
1730
1736
|
{ name: "whitespace", fn: whitespaceMatch },
|
|
1731
1737
|
{ name: "indentation", fn: indentationMatch },
|
|
1732
|
-
{ 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) }
|
|
1733
1740
|
];
|
|
1734
1741
|
for (const { name, fn } of strategies) {
|
|
1735
1742
|
const result = fn(content, search);
|
|
@@ -1795,7 +1802,8 @@ function indentationMatch(content, search) {
|
|
|
1795
1802
|
const stripIndent = (s) => s.split("\n").map((line) => line.trimStart()).join("\n");
|
|
1796
1803
|
const strippedSearch = stripIndent(search);
|
|
1797
1804
|
const contentLines = content.split("\n");
|
|
1798
|
-
const
|
|
1805
|
+
const searchLines = search.split("\n");
|
|
1806
|
+
const searchLineCount = searchLines.length;
|
|
1799
1807
|
for (let i = 0; i <= contentLines.length - searchLineCount; i++) {
|
|
1800
1808
|
const windowLines = contentLines.slice(i, i + searchLineCount);
|
|
1801
1809
|
const strippedWindow = stripIndent(windowLines.join("\n"));
|
|
@@ -1804,6 +1812,7 @@ function indentationMatch(content, search) {
|
|
|
1804
1812
|
const matchedContent = windowLines.join("\n");
|
|
1805
1813
|
const endIndex = startIndex + matchedContent.length;
|
|
1806
1814
|
const { startLine, endLine } = getLineNumbers(content, startIndex, endIndex);
|
|
1815
|
+
const indentationDelta = computeIndentationDelta(searchLines, windowLines);
|
|
1807
1816
|
return {
|
|
1808
1817
|
found: true,
|
|
1809
1818
|
strategy: "indentation",
|
|
@@ -1812,7 +1821,8 @@ function indentationMatch(content, search) {
|
|
|
1812
1821
|
startIndex,
|
|
1813
1822
|
endIndex,
|
|
1814
1823
|
startLine,
|
|
1815
|
-
endLine
|
|
1824
|
+
endLine,
|
|
1825
|
+
indentationDelta
|
|
1816
1826
|
};
|
|
1817
1827
|
}
|
|
1818
1828
|
}
|
|
@@ -1850,6 +1860,94 @@ function fuzzyMatch(content, search, threshold) {
|
|
|
1850
1860
|
endLine
|
|
1851
1861
|
};
|
|
1852
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
|
+
}
|
|
1853
1951
|
function findSuggestions(content, search, maxSuggestions, minSimilarity) {
|
|
1854
1952
|
const searchLines = search.split("\n");
|
|
1855
1953
|
const contentLines = content.split("\n");
|
|
@@ -1966,6 +2064,78 @@ function getContext(content, lineNumber, contextLines) {
|
|
|
1966
2064
|
});
|
|
1967
2065
|
return contextWithNumbers.join("\n");
|
|
1968
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
|
+
}
|
|
1969
2139
|
|
|
1970
2140
|
// src/builtins/filesystem/utils.ts
|
|
1971
2141
|
import fs from "fs";
|
|
@@ -2037,22 +2207,30 @@ Uses layered matching strategies (in order):
|
|
|
2037
2207
|
2. Whitespace-insensitive - ignores differences in spaces/tabs
|
|
2038
2208
|
3. Indentation-preserving - matches structure ignoring leading whitespace
|
|
2039
2209
|
4. Fuzzy match - similarity-based matching (80% threshold)
|
|
2210
|
+
5. DMP (diff-match-patch) - handles heavily refactored code
|
|
2040
2211
|
|
|
2041
2212
|
For multiple edits to the same file, call this gadget multiple times.
|
|
2042
|
-
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`,
|
|
2043
2218
|
maxConcurrent: 1,
|
|
2044
2219
|
// Sequential execution to prevent race conditions
|
|
2045
2220
|
schema: z3.object({
|
|
2046
2221
|
filePath: z3.string().describe("Path to the file to edit (relative or absolute)"),
|
|
2047
2222
|
search: z3.string().describe("The content to search for in the file"),
|
|
2048
|
-
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")
|
|
2049
2226
|
}),
|
|
2050
2227
|
examples: [
|
|
2051
2228
|
{
|
|
2052
2229
|
params: {
|
|
2053
2230
|
filePath: "src/config.ts",
|
|
2054
2231
|
search: "const DEBUG = false;",
|
|
2055
|
-
replace: "const DEBUG = true;"
|
|
2232
|
+
replace: "const DEBUG = true;",
|
|
2233
|
+
replaceAll: false
|
|
2056
2234
|
},
|
|
2057
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```",
|
|
2058
2236
|
comment: "Simple single-line edit"
|
|
@@ -2065,7 +2243,8 @@ Each call provides immediate feedback, allowing you to adjust subsequent edits.`
|
|
|
2065
2243
|
}`,
|
|
2066
2244
|
replace: `function newHelper() {
|
|
2067
2245
|
return 2;
|
|
2068
|
-
}
|
|
2246
|
+
}`,
|
|
2247
|
+
replaceAll: false
|
|
2069
2248
|
},
|
|
2070
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```",
|
|
2071
2250
|
comment: "Multi-line replacement"
|
|
@@ -2074,14 +2253,25 @@ Each call provides immediate feedback, allowing you to adjust subsequent edits.`
|
|
|
2074
2253
|
params: {
|
|
2075
2254
|
filePath: "src/app.ts",
|
|
2076
2255
|
search: "unusedImport",
|
|
2077
|
-
replace: ""
|
|
2256
|
+
replace: "",
|
|
2257
|
+
replaceAll: false
|
|
2078
2258
|
},
|
|
2079
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```',
|
|
2080
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"
|
|
2081
2271
|
}
|
|
2082
2272
|
],
|
|
2083
2273
|
timeoutMs: 3e4,
|
|
2084
|
-
execute: ({ filePath, search, replace }) => {
|
|
2274
|
+
execute: ({ filePath, search, replace, replaceAll, expectedCount }) => {
|
|
2085
2275
|
if (search.trim() === "") {
|
|
2086
2276
|
return `path=${filePath} status=error
|
|
2087
2277
|
|
|
@@ -2111,12 +2301,43 @@ Error: File not found: ${filePath}`;
|
|
|
2111
2301
|
|
|
2112
2302
|
Error reading file: ${message}`;
|
|
2113
2303
|
}
|
|
2114
|
-
const
|
|
2115
|
-
if (
|
|
2304
|
+
const allMatches = findAllMatches(content, search);
|
|
2305
|
+
if (allMatches.length === 0) {
|
|
2116
2306
|
const failure = getMatchFailure(content, search);
|
|
2117
2307
|
return formatFailure(filePath, search, failure, content);
|
|
2118
2308
|
}
|
|
2119
|
-
|
|
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
|
+
}
|
|
2120
2341
|
try {
|
|
2121
2342
|
writeFileSync(validatedPath, newContent, "utf-8");
|
|
2122
2343
|
} catch (error) {
|
|
@@ -2125,9 +2346,10 @@ Error reading file: ${message}`;
|
|
|
2125
2346
|
|
|
2126
2347
|
Error writing file: ${message}`;
|
|
2127
2348
|
}
|
|
2128
|
-
|
|
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}
|
|
2129
2351
|
|
|
2130
|
-
|
|
2352
|
+
${diffContext}
|
|
2131
2353
|
|
|
2132
2354
|
UPDATED FILE CONTENT:
|
|
2133
2355
|
\`\`\`
|
|
@@ -2135,6 +2357,21 @@ ${newContent}
|
|
|
2135
2357
|
\`\`\``;
|
|
2136
2358
|
}
|
|
2137
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
|
+
}
|
|
2138
2375
|
|
|
2139
2376
|
// src/builtins/filesystem/list-directory.ts
|
|
2140
2377
|
import fs2 from "fs";
|