@travisennis/acai 0.0.10 → 0.0.12
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/README.md +7 -4
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +29 -27
- package/dist/cli/stdin.d.ts +2 -1
- package/dist/cli/stdin.d.ts.map +1 -1
- package/dist/commands/generate-rules/service.d.ts +3 -2
- package/dist/commands/generate-rules/service.d.ts.map +1 -1
- package/dist/commands/health/utils.d.ts +3 -2
- package/dist/commands/health/utils.d.ts.map +1 -1
- package/dist/commands/init-project/utils.d.ts +2 -1
- package/dist/commands/init-project/utils.d.ts.map +1 -1
- package/dist/commands/init-project/utils.js +0 -11
- package/dist/commands/manager.d.ts.map +1 -1
- package/dist/commands/manager.js +6 -1
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +4 -1
- package/dist/commands/review/utils.d.ts +6 -1
- package/dist/commands/review/utils.d.ts.map +1 -1
- package/dist/commands/session/index.d.ts.map +1 -1
- package/dist/commands/session/index.js +6 -0
- package/dist/commands/session/types.d.ts +1 -0
- package/dist/commands/session/types.d.ts.map +1 -1
- package/dist/commands/tools/index.d.ts +3 -0
- package/dist/commands/tools/index.d.ts.map +1 -0
- package/dist/commands/tools/index.js +190 -0
- package/dist/commands/tools/templates.d.ts +6 -0
- package/dist/commands/tools/templates.d.ts.map +1 -0
- package/dist/commands/tools/templates.js +97 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +41 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -3
- package/dist/models/anthropic-provider.d.ts +1 -1
- package/dist/models/deepseek-provider.d.ts +3 -3
- package/dist/models/deepseek-provider.js +17 -17
- package/dist/models/google-provider.d.ts +2 -4
- package/dist/models/google-provider.d.ts.map +1 -1
- package/dist/models/google-provider.js +2 -17
- package/dist/models/groq-provider.d.ts +2 -4
- package/dist/models/groq-provider.d.ts.map +1 -1
- package/dist/models/groq-provider.js +3 -21
- package/dist/models/opencode-go-provider.d.ts +35 -0
- package/dist/models/opencode-go-provider.d.ts.map +1 -0
- package/dist/models/opencode-go-provider.js +214 -0
- package/dist/models/opencode-zen-provider.d.ts +5 -5
- package/dist/models/opencode-zen-provider.d.ts.map +1 -1
- package/dist/models/opencode-zen-provider.js +41 -47
- package/dist/models/openrouter-provider.d.ts +5 -13
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +34 -138
- package/dist/models/providers.d.ts +3 -3
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +6 -0
- package/dist/models/xai-provider.d.ts +1 -2
- package/dist/models/xai-provider.d.ts.map +1 -1
- package/dist/models/xai-provider.js +0 -13
- package/dist/prompts/manager.d.ts.map +1 -1
- package/dist/prompts/manager.js +5 -1
- package/dist/prompts/mentions.d.ts.map +1 -1
- package/dist/prompts/mentions.js +35 -6
- package/dist/prompts/system-prompt.d.ts +1 -0
- package/dist/prompts/system-prompt.d.ts.map +1 -1
- package/dist/prompts/system-prompt.js +20 -5
- package/dist/repl/index.d.ts +1 -2
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +14 -53
- package/dist/sessions/manager.d.ts +3 -3
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +1 -1
- package/dist/skills/activated-tracker.d.ts +11 -0
- package/dist/skills/activated-tracker.d.ts.map +1 -0
- package/dist/skills/activated-tracker.js +16 -0
- package/dist/skills/index.d.ts +3 -2
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +7 -1
- package/dist/subagents/index.d.ts +2 -1
- package/dist/subagents/index.d.ts.map +1 -1
- package/dist/terminal/table/utils.d.ts +1 -1
- package/dist/terminal/table/utils.d.ts.map +1 -1
- package/dist/terminal/wrap-ansi.js +2 -2
- package/dist/tools/agent.js +1 -1
- package/dist/tools/apply-patch.d.ts +62 -0
- package/dist/tools/apply-patch.d.ts.map +1 -0
- package/dist/tools/apply-patch.js +377 -0
- package/dist/tools/bash.d.ts +4 -4
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +40 -8
- package/dist/tools/directory-tree.d.ts +4 -4
- package/dist/tools/directory-tree.d.ts.map +1 -1
- package/dist/tools/directory-tree.js +3 -1
- package/dist/tools/dynamic-tool-loader.d.ts +12 -3
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
- package/dist/tools/dynamic-tool-loader.js +299 -39
- package/dist/tools/edit-file.d.ts +2 -2
- package/dist/tools/edit-file.d.ts.map +1 -1
- package/dist/tools/edit-file.js +188 -79
- package/dist/tools/glob.d.ts +16 -16
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +30 -15
- package/dist/tools/grep.d.ts +14 -14
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +50 -29
- package/dist/tools/index.d.ts +57 -84
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +20 -5
- package/dist/tools/ls.d.ts +2 -2
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +2 -1
- package/dist/tools/read-file.d.ts +9 -11
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +21 -16
- package/dist/tools/save-file.d.ts +4 -4
- package/dist/tools/save-file.d.ts.map +1 -1
- package/dist/tools/save-file.js +26 -21
- package/dist/tools/skill.d.ts +2 -1
- package/dist/tools/skill.d.ts.map +1 -1
- package/dist/tools/skill.js +55 -12
- package/dist/tools/types.d.ts +8 -2
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/web-fetch.d.ts +6 -18
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +45 -9
- package/dist/tools/web-search.d.ts +4 -22
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +1 -1
- package/dist/tui/autocomplete/file-search-provider.js +1 -1
- package/dist/tui/autocomplete/utils.d.ts +2 -1
- package/dist/tui/autocomplete/utils.d.ts.map +1 -1
- package/dist/tui/autocomplete/utils.js +25 -23
- package/dist/tui/components/editor.d.ts +2 -1
- package/dist/tui/components/editor.d.ts.map +1 -1
- package/dist/tui/components/editor.js +1 -1
- package/dist/tui/components/footer.d.ts +0 -2
- package/dist/tui/components/footer.d.ts.map +1 -1
- package/dist/tui/components/footer.js +1 -17
- package/dist/tui/components/markdown.d.ts +2 -2
- package/dist/tui/components/markdown.d.ts.map +1 -1
- package/dist/tui/components/welcome.d.ts +2 -1
- package/dist/tui/components/welcome.d.ts.map +1 -1
- package/dist/tui/editor-launcher.d.ts +3 -2
- package/dist/tui/editor-launcher.d.ts.map +1 -1
- package/dist/tui/index.d.ts +0 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/tui.d.ts +1 -0
- package/dist/tui/tui.d.ts.map +1 -1
- package/dist/tui/tui.js +9 -0
- package/dist/tui/utils.d.ts +1 -5
- package/dist/tui/utils.d.ts.map +1 -1
- package/dist/tui/utils.js +271 -44
- package/dist/utils/binary-output.d.ts +32 -0
- package/dist/utils/binary-output.d.ts.map +1 -0
- package/dist/utils/binary-output.js +127 -0
- package/dist/utils/command-protection.d.ts.map +1 -1
- package/dist/utils/command-protection.js +92 -9
- package/dist/utils/parsing.d.ts +1 -1
- package/dist/utils/parsing.d.ts.map +1 -1
- package/package.json +28 -26
- package/dist/commands/add-directory/types.d.ts +0 -6
- package/dist/commands/add-directory/types.d.ts.map +0 -1
- package/dist/commands/add-directory/types.js +0 -1
- package/dist/commands/copy/types.d.ts +0 -3
- package/dist/commands/copy/types.d.ts.map +0 -1
- package/dist/commands/copy/types.js +0 -1
- package/dist/commands/review/types.d.ts +0 -12
- package/dist/commands/review/types.d.ts.map +0 -1
- package/dist/commands/review/types.js +0 -1
- package/dist/modes/manager.d.ts +0 -23
- package/dist/modes/manager.d.ts.map +0 -1
- package/dist/modes/manager.js +0 -77
- package/dist/modes/prompts.d.ts +0 -2
- package/dist/modes/prompts.d.ts.map +0 -1
- package/dist/modes/prompts.js +0 -143
- package/dist/tools/code-search.d.ts +0 -41
- package/dist/tools/code-search.d.ts.map +0 -1
- package/dist/tools/code-search.js +0 -195
- package/dist/utils/iterables.d.ts +0 -2
- package/dist/utils/iterables.d.ts.map +0 -1
- package/dist/utils/iterables.js +0 -6
package/dist/tools/edit-file.js
CHANGED
|
@@ -118,25 +118,180 @@ function validateEdits(edits) {
|
|
|
118
118
|
throw new Error("Invalid oldText in edit. The value of oldText must be at least one character");
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Normalize text for fuzzy matching by:
|
|
123
|
+
* - Unicode NFKC normalization (canonical compatibility decomposition)
|
|
124
|
+
* - Converting smart quotes to straight quotes
|
|
125
|
+
* - Unifying various dash characters to hyphen
|
|
126
|
+
* - Normalizing whitespace characters to regular space
|
|
127
|
+
* - Removing trailing whitespace from each line
|
|
128
|
+
*/
|
|
129
|
+
function normalizeForFuzzyMatch(text) {
|
|
130
|
+
return text
|
|
131
|
+
.normalize("NFKC")
|
|
132
|
+
.split("\n")
|
|
133
|
+
.map((line) => line.trimEnd())
|
|
134
|
+
.join("\n")
|
|
135
|
+
.replace(/[\u2018\u2019\u201A\u201B]/g, "'") // curly single quotes → '
|
|
136
|
+
.replace(/[\u201C\u201D\u201E\u201F]/g, '"') // curly double quotes → "
|
|
137
|
+
.replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g, "-") // dashes → -
|
|
138
|
+
.replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " "); // spaces → space
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Find text in content, trying exact match first, then fuzzy match.
|
|
142
|
+
* When fuzzy match is used, positions are in the normalized content.
|
|
143
|
+
* IMPORTANT: When fuzzy matching is needed, the caller must work entirely
|
|
144
|
+
* in normalized space to avoid position mapping issues.
|
|
145
|
+
*/
|
|
146
|
+
function fuzzyFindText(content, searchText) {
|
|
147
|
+
// Try exact match first
|
|
148
|
+
const exactIndex = content.indexOf(searchText);
|
|
149
|
+
if (exactIndex !== -1) {
|
|
150
|
+
return {
|
|
151
|
+
found: true,
|
|
152
|
+
index: exactIndex,
|
|
153
|
+
matchLength: searchText.length,
|
|
154
|
+
usedFuzzyMatch: false,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Fall back to fuzzy matching
|
|
158
|
+
const fuzzyContent = normalizeForFuzzyMatch(content);
|
|
159
|
+
const fuzzySearch = normalizeForFuzzyMatch(searchText);
|
|
160
|
+
const fuzzyIndex = fuzzyContent.indexOf(fuzzySearch);
|
|
161
|
+
if (fuzzyIndex === -1) {
|
|
162
|
+
return { found: false, index: -1, matchLength: 0, usedFuzzyMatch: false };
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
found: true,
|
|
166
|
+
index: fuzzyIndex,
|
|
167
|
+
matchLength: fuzzySearch.length,
|
|
168
|
+
usedFuzzyMatch: true,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Count how many times searchText appears in content (exact or fuzzy).
|
|
173
|
+
* Used to ensure uniqueness.
|
|
174
|
+
*/
|
|
175
|
+
function countMatches(content, searchText) {
|
|
176
|
+
// Count exact matches first
|
|
177
|
+
const exactEscaped = searchText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
178
|
+
const exactRegex = new RegExp(exactEscaped, "g");
|
|
179
|
+
const exactMatches = content.match(exactRegex);
|
|
180
|
+
const exactCount = exactMatches ? exactMatches.length : 0;
|
|
181
|
+
if (exactCount > 0) {
|
|
182
|
+
return exactCount;
|
|
183
|
+
}
|
|
184
|
+
// Count fuzzy matches
|
|
185
|
+
const fuzzyContent = normalizeForFuzzyMatch(content);
|
|
186
|
+
const fuzzySearch = normalizeForFuzzyMatch(searchText);
|
|
187
|
+
const fuzzyEscaped = fuzzySearch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
188
|
+
const fuzzyRegex = new RegExp(fuzzyEscaped, "g");
|
|
189
|
+
const fuzzyMatches = fuzzyContent.match(fuzzyRegex);
|
|
190
|
+
return fuzzyMatches ? fuzzyMatches.length : 0;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Preflight validation: Find all edit positions, validate uniqueness and no overlaps.
|
|
194
|
+
* If any edit requires fuzzy matching, normalize the entire content and work in
|
|
195
|
+
* normalized space. This avoids position mapping issues.
|
|
196
|
+
*/
|
|
197
|
+
function preflightEdits(edits, content) {
|
|
198
|
+
const normalizedEdits = edits.map((edit) => ({
|
|
199
|
+
oldText: normalizeLineEndings(edit.oldText),
|
|
200
|
+
newText: normalizeLineEndings(edit.newText),
|
|
201
|
+
}));
|
|
202
|
+
// Check if any edit requires fuzzy matching
|
|
203
|
+
const needsFuzzyMatching = normalizedEdits.some((edit) => content.indexOf(edit.oldText) === -1);
|
|
204
|
+
// Use normalized content if fuzzy matching is needed
|
|
205
|
+
const baseContent = needsFuzzyMatching
|
|
206
|
+
? normalizeForFuzzyMatch(content)
|
|
207
|
+
: content;
|
|
208
|
+
const matchedEdits = [];
|
|
209
|
+
// First pass: Find all match positions
|
|
210
|
+
for (let i = 0; i < normalizedEdits.length; i++) {
|
|
211
|
+
const edit = normalizedEdits[i];
|
|
212
|
+
// Check uniqueness
|
|
213
|
+
const matchCount = countMatches(baseContent, edit.oldText);
|
|
214
|
+
if (matchCount === 0) {
|
|
215
|
+
return {
|
|
216
|
+
success: false,
|
|
217
|
+
matchedEdits: [],
|
|
218
|
+
errorMessage: `Edit ${i + 1}: Could not find the exact text. ` +
|
|
219
|
+
"The oldText must match exactly including all whitespace and newlines.",
|
|
220
|
+
usedFuzzyMatch: needsFuzzyMatching,
|
|
221
|
+
baseContent,
|
|
222
|
+
};
|
|
126
223
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
224
|
+
if (matchCount > 1) {
|
|
225
|
+
const fuzzyContext = needsFuzzyMatching
|
|
226
|
+
? " (including fuzzy matches)"
|
|
227
|
+
: "";
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
matchedEdits: [],
|
|
231
|
+
errorMessage: `Edit ${i + 1}: oldText matches ${matchCount} locations${fuzzyContext} but should match only 1. ` +
|
|
232
|
+
"Please provide a more specific oldText that includes more surrounding context.",
|
|
233
|
+
usedFuzzyMatch: needsFuzzyMatching,
|
|
234
|
+
baseContent,
|
|
235
|
+
};
|
|
130
236
|
}
|
|
131
|
-
|
|
132
|
-
|
|
237
|
+
// Find the match position
|
|
238
|
+
const matchResult = fuzzyFindText(baseContent, edit.oldText);
|
|
239
|
+
if (!matchResult.found) {
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
matchedEdits: [],
|
|
243
|
+
errorMessage: `Edit ${i + 1}: Could not find the text (unexpected error).`,
|
|
244
|
+
usedFuzzyMatch: needsFuzzyMatching,
|
|
245
|
+
baseContent,
|
|
246
|
+
};
|
|
133
247
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
248
|
+
matchedEdits.push({
|
|
249
|
+
...edit,
|
|
250
|
+
index: matchResult.index,
|
|
251
|
+
matchLength: matchResult.matchLength,
|
|
252
|
+
editIndex: i,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
// Sort by position (ascending) for overlap detection
|
|
256
|
+
matchedEdits.sort((a, b) => a.index - b.index);
|
|
257
|
+
// Check for overlapping edits
|
|
258
|
+
for (let i = 0; i < matchedEdits.length - 1; i++) {
|
|
259
|
+
const current = matchedEdits[i];
|
|
260
|
+
const next = matchedEdits[i + 1];
|
|
261
|
+
// Check if current edit overlaps with next edit
|
|
262
|
+
if (current.index + current.matchLength > next.index) {
|
|
263
|
+
return {
|
|
264
|
+
success: false,
|
|
265
|
+
matchedEdits: [],
|
|
266
|
+
errorMessage: `Edits ${current.editIndex + 1} and ${next.editIndex + 1} overlap in the file. ` +
|
|
267
|
+
"Each edit must target a distinct region. Please combine overlapping edits into a single edit.",
|
|
268
|
+
usedFuzzyMatch: needsFuzzyMatching,
|
|
269
|
+
baseContent,
|
|
270
|
+
};
|
|
137
271
|
}
|
|
138
272
|
}
|
|
139
|
-
return
|
|
273
|
+
return {
|
|
274
|
+
success: true,
|
|
275
|
+
matchedEdits,
|
|
276
|
+
usedFuzzyMatch: needsFuzzyMatching,
|
|
277
|
+
baseContent,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Apply edits in reverse position order (highest index first).
|
|
282
|
+
* This prevents position shifting - earlier edits don't affect later ones.
|
|
283
|
+
* All positions are relative to baseContent (normalized if fuzzy matching).
|
|
284
|
+
*/
|
|
285
|
+
function applyEditsReverseOrder(content, matchedEdits) {
|
|
286
|
+
let result = content;
|
|
287
|
+
// Process in reverse order (highest index first)
|
|
288
|
+
for (let i = matchedEdits.length - 1; i >= 0; i--) {
|
|
289
|
+
const edit = matchedEdits[i];
|
|
290
|
+
const before = result.slice(0, edit.index);
|
|
291
|
+
const after = result.slice(edit.index + edit.matchLength);
|
|
292
|
+
result = before + edit.newText + after;
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
140
295
|
}
|
|
141
296
|
function formatDiff(diff, _filePath) {
|
|
142
297
|
let numBackticks = 3;
|
|
@@ -158,11 +313,27 @@ export async function applyFileEdits(filePath, edits, dryRun = false, abortSigna
|
|
|
158
313
|
const originalLineEnding = detectLineEnding(bomStrippedContent);
|
|
159
314
|
const content = normalizeLineEndings(bomStrippedContent);
|
|
160
315
|
validateEdits(edits);
|
|
161
|
-
|
|
316
|
+
// PREFLIGHT: Find all positions, validate no overlaps
|
|
317
|
+
const preflight = preflightEdits(edits, content);
|
|
318
|
+
if (!preflight.success) {
|
|
319
|
+
throw new Error(`Edit validation failed: ${preflight.errorMessage}`);
|
|
320
|
+
}
|
|
321
|
+
// All edits validated - apply in reverse order
|
|
322
|
+
// Note: baseContent is normalized if fuzzy matching was needed
|
|
323
|
+
const modifiedContent = applyEditsReverseOrder(preflight.baseContent, preflight.matchedEdits);
|
|
324
|
+
// Verify something actually changed
|
|
325
|
+
if (modifiedContent === preflight.baseContent) {
|
|
326
|
+
throw new Error("No changes were made - all edits resulted in identical content");
|
|
327
|
+
}
|
|
162
328
|
const finalContentWithLineEndings = restoreLineEndings(modifiedContent, originalLineEnding);
|
|
163
329
|
const finalContent = originalBom + finalContentWithLineEndings;
|
|
164
|
-
|
|
330
|
+
// Use baseContent for diff (normalized if fuzzy matching)
|
|
331
|
+
const diff = createUnifiedDiff(preflight.baseContent, modifiedContent, filePath);
|
|
165
332
|
const formattedDiff = formatDiff(diff, filePath);
|
|
333
|
+
// Add fuzzy match indicator if applicable
|
|
334
|
+
const result = preflight.usedFuzzyMatch
|
|
335
|
+
? `${formattedDiff}\n\n(Note: Used fuzzy matching - file content has been normalized)`
|
|
336
|
+
: formattedDiff;
|
|
166
337
|
if (!dryRun) {
|
|
167
338
|
if (abortSignal?.aborted) {
|
|
168
339
|
throw new Error("File edit operation aborted before writing");
|
|
@@ -172,67 +343,5 @@ export async function applyFileEdits(filePath, edits, dryRun = false, abortSigna
|
|
|
172
343
|
signal: abortSignal,
|
|
173
344
|
});
|
|
174
345
|
}
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Applies a single edit with normalized line endings
|
|
179
|
-
* Returns an error if oldText matches more than one location in the file
|
|
180
|
-
*/
|
|
181
|
-
async function applyNormalizedEdit(edit, content) {
|
|
182
|
-
// Normalize line endings to match the normalized content
|
|
183
|
-
const normalizedOldText = normalizeLineEndings(edit.oldText);
|
|
184
|
-
const normalizedNewText = normalizeLineEndings(edit.newText);
|
|
185
|
-
// First, check how many matches exist (without replacing)
|
|
186
|
-
const matchCountResult = countMatches(content, normalizedOldText);
|
|
187
|
-
const matchCount = matchCountResult.count;
|
|
188
|
-
// If more than one match, require unique oldText
|
|
189
|
-
if (matchCount > 1) {
|
|
190
|
-
return {
|
|
191
|
-
success: false,
|
|
192
|
-
content,
|
|
193
|
-
matchCount,
|
|
194
|
-
errorMessage: `oldText matches ${matchCount} locations in the file but should match only 1. ` +
|
|
195
|
-
"Please provide a more specific oldText that includes more surrounding context (e.g., 3+ lines, " +
|
|
196
|
-
"function/class names, or unique surrounding code) to uniquely identify the location you want to edit.",
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
// If no matches, return failure
|
|
200
|
-
if (matchCount === 0) {
|
|
201
|
-
return { success: false, content };
|
|
202
|
-
}
|
|
203
|
-
// Exactly one match - apply the edit
|
|
204
|
-
const originalResult = applyLiteralEdit(content, normalizedOldText, normalizedNewText);
|
|
205
|
-
return { success: true, content: originalResult.content };
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Count the number of literal matches in content without replacing
|
|
209
|
-
*/
|
|
210
|
-
function countMatches(content, search) {
|
|
211
|
-
if (search === "") {
|
|
212
|
-
return { count: 0 };
|
|
213
|
-
}
|
|
214
|
-
// Escape special regex characters for literal matching
|
|
215
|
-
const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
216
|
-
const regex = new RegExp(escapedSearch, "g");
|
|
217
|
-
// Count matches without modifying content
|
|
218
|
-
const matches = content.match(regex);
|
|
219
|
-
return { count: matches ? matches.length : 0 };
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Applies a literal search and replace operation
|
|
223
|
-
*/
|
|
224
|
-
function applyLiteralEdit(content, search, replace) {
|
|
225
|
-
if (search === "") {
|
|
226
|
-
return { matchCount: 0, content };
|
|
227
|
-
}
|
|
228
|
-
// Escape special regex characters for literal matching
|
|
229
|
-
const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
230
|
-
const regex = new RegExp(escapedSearch, "g");
|
|
231
|
-
// Use replace with callback to count matches while replacing all occurrences
|
|
232
|
-
let matchCount = 0;
|
|
233
|
-
const modifiedContent = content.replace(regex, () => {
|
|
234
|
-
matchCount++;
|
|
235
|
-
return replace;
|
|
236
|
-
});
|
|
237
|
-
return { matchCount, content: modifiedContent };
|
|
346
|
+
return result;
|
|
238
347
|
}
|
package/dist/tools/glob.d.ts
CHANGED
|
@@ -4,28 +4,28 @@ export declare const GlobTool: {
|
|
|
4
4
|
name: "Glob";
|
|
5
5
|
};
|
|
6
6
|
export declare const inputSchema: z.ZodObject<{
|
|
7
|
-
patterns: z.
|
|
8
|
-
path: z.
|
|
9
|
-
gitignore: z.
|
|
10
|
-
recursive: z.
|
|
11
|
-
expandDirectories: z.
|
|
12
|
-
ignoreFiles: z.
|
|
13
|
-
cwd: z.
|
|
14
|
-
maxResults: z.
|
|
7
|
+
patterns: z.ZodDefault<z.ZodPreprocess<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
|
|
8
|
+
path: z.ZodDefault<z.ZodPreprocess<z.ZodString>>;
|
|
9
|
+
gitignore: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
10
|
+
recursive: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
11
|
+
expandDirectories: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
12
|
+
ignoreFiles: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>>;
|
|
13
|
+
cwd: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedString<unknown>>>>;
|
|
14
|
+
maxResults: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
|
|
15
15
|
}, z.core.$strip>;
|
|
16
16
|
type GlobInputSchema = z.infer<typeof inputSchema>;
|
|
17
17
|
export declare const createGlobTool: () => {
|
|
18
18
|
toolDef: {
|
|
19
19
|
description: string;
|
|
20
20
|
inputSchema: z.ZodObject<{
|
|
21
|
-
patterns: z.
|
|
22
|
-
path: z.
|
|
23
|
-
gitignore: z.
|
|
24
|
-
recursive: z.
|
|
25
|
-
expandDirectories: z.
|
|
26
|
-
ignoreFiles: z.
|
|
27
|
-
cwd: z.
|
|
28
|
-
maxResults: z.
|
|
21
|
+
patterns: z.ZodDefault<z.ZodPreprocess<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
|
|
22
|
+
path: z.ZodDefault<z.ZodPreprocess<z.ZodString>>;
|
|
23
|
+
gitignore: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
24
|
+
recursive: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
25
|
+
expandDirectories: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
26
|
+
ignoreFiles: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>>;
|
|
27
|
+
cwd: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedString<unknown>>>>;
|
|
28
|
+
maxResults: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
|
|
29
29
|
}, z.core.$strip>;
|
|
30
30
|
};
|
|
31
31
|
display({ patterns, path }: GlobInputSchema): string;
|
package/dist/tools/glob.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../source/tools/glob.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../source/tools/glob.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA6EvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,eAAO,MAAM,WAAW;;;;;;;;;iBAkFtB,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;gCAMK,eAAe;wGAmBtC,eAAe,mBACD,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;CAuCrB,CAAC"}
|
package/dist/tools/glob.js
CHANGED
|
@@ -38,6 +38,24 @@ function formatResult(sortedFiles) {
|
|
|
38
38
|
? sortedFiles.join("\n")
|
|
39
39
|
: "No files found matching the specified patterns.";
|
|
40
40
|
}
|
|
41
|
+
function normalizePatternArray(patterns) {
|
|
42
|
+
return Array.isArray(patterns) ? patterns : [patterns];
|
|
43
|
+
}
|
|
44
|
+
function buildGlobOptions(effectivePath, gitignore, recursive, expandDirectories, ignoreFiles, cwd) {
|
|
45
|
+
return {
|
|
46
|
+
cwd: cwd || effectivePath,
|
|
47
|
+
...(gitignore !== null && { gitignore }),
|
|
48
|
+
...(recursive !== null && { recursive }),
|
|
49
|
+
...(expandDirectories !== null && { expandDirectories }),
|
|
50
|
+
...(ignoreFiles !== null && { ignoreFiles }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function limitResults(sortedFiles, effectiveMaxResults) {
|
|
54
|
+
if (effectiveMaxResults > 0 && sortedFiles.length > effectiveMaxResults) {
|
|
55
|
+
return sortedFiles.slice(0, effectiveMaxResults);
|
|
56
|
+
}
|
|
57
|
+
return sortedFiles;
|
|
58
|
+
}
|
|
41
59
|
export const GlobTool = {
|
|
42
60
|
name: "Glob",
|
|
43
61
|
};
|
|
@@ -63,18 +81,23 @@ export const inputSchema = z.object({
|
|
|
63
81
|
}
|
|
64
82
|
return val;
|
|
65
83
|
}, z.union([z.string(), z.array(z.string())]))
|
|
84
|
+
.default("**/*")
|
|
66
85
|
.describe("Glob patterns to search for (e.g., '*.ts', '**/*.test.ts', 'src/**/*.js')"),
|
|
67
86
|
path: z
|
|
68
87
|
.preprocess((val) => (val === null || val === undefined ? process.cwd() : val), z.string())
|
|
88
|
+
.default(process.cwd())
|
|
69
89
|
.describe("Base directory to search in"),
|
|
70
90
|
gitignore: z
|
|
71
91
|
.preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
|
|
92
|
+
.default(null)
|
|
72
93
|
.describe("Respect ignore patterns in .gitignore files. (default: true)"),
|
|
73
94
|
recursive: z
|
|
74
95
|
.preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
|
|
96
|
+
.default(null)
|
|
75
97
|
.describe("Search recursively. (default: true)"),
|
|
76
98
|
expandDirectories: z
|
|
77
99
|
.preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
|
|
100
|
+
.default(null)
|
|
78
101
|
.describe("Automatically expand directories to files. (default: true)"),
|
|
79
102
|
ignoreFiles: z
|
|
80
103
|
.preprocess((val) => {
|
|
@@ -98,12 +121,15 @@ export const inputSchema = z.object({
|
|
|
98
121
|
}
|
|
99
122
|
return converted;
|
|
100
123
|
}, z.union([z.string(), z.array(z.string())]).nullable())
|
|
124
|
+
.default(null)
|
|
101
125
|
.describe("Glob patterns to look for ignore files (e.g., '.gitignore'). Pass null to use default behavior."),
|
|
102
126
|
cwd: z
|
|
103
127
|
.preprocess((val) => convertNullString(val), z.coerce.string().nullable())
|
|
128
|
+
.default(null)
|
|
104
129
|
.describe("Current working directory override. (default: process.cwd())"),
|
|
105
130
|
maxResults: z
|
|
106
131
|
.preprocess((val) => convertNullString(val), z.coerce.number().nullable())
|
|
132
|
+
.default(null)
|
|
107
133
|
.describe("Maximum number of files to return. Set to 0 for no limit. (Default: 100)"),
|
|
108
134
|
});
|
|
109
135
|
export const createGlobTool = () => {
|
|
@@ -125,27 +151,16 @@ export const createGlobTool = () => {
|
|
|
125
151
|
throw new Error("Glob search aborted");
|
|
126
152
|
}
|
|
127
153
|
const effectivePath = typeof path === "string" && path.trim() !== "" ? path : process.cwd();
|
|
128
|
-
const patternArray =
|
|
154
|
+
const patternArray = normalizePatternArray(patterns);
|
|
129
155
|
const effectiveMaxResults = maxResults ?? DEFAULT_MAX_RESULTS;
|
|
130
|
-
const globOptions =
|
|
131
|
-
|
|
132
|
-
...(gitignore !== null && { gitignore }),
|
|
133
|
-
...(recursive !== null && { recursive }),
|
|
134
|
-
...(expandDirectories !== null && { expandDirectories }),
|
|
135
|
-
...(ignoreFiles !== null && { ignoreFiles }),
|
|
136
|
-
};
|
|
137
|
-
const matchingFiles = await glob(patternArray, {
|
|
138
|
-
...globOptions,
|
|
139
|
-
cwd: effectivePath,
|
|
140
|
-
});
|
|
156
|
+
const globOptions = buildGlobOptions(effectivePath, gitignore ?? null, recursive ?? null, expandDirectories ?? null, ignoreFiles ?? null, cwd ?? null);
|
|
157
|
+
const matchingFiles = await glob(patternArray, globOptions);
|
|
141
158
|
const filesToStat = matchingFiles.length > MAX_STAT_FILES
|
|
142
159
|
? matchingFiles.slice(0, MAX_STAT_FILES)
|
|
143
160
|
: matchingFiles;
|
|
144
161
|
const filesWithStats = await Promise.all(filesToStat.map((filePath) => getFileWithStats(filePath, effectivePath)));
|
|
145
162
|
const sortedFiles = sortFilesByMtime(filesWithStats);
|
|
146
|
-
const result =
|
|
147
|
-
? sortedFiles.slice(0, effectiveMaxResults)
|
|
148
|
-
: sortedFiles;
|
|
163
|
+
const result = limitResults(sortedFiles, effectiveMaxResults);
|
|
149
164
|
return formatResult(result);
|
|
150
165
|
},
|
|
151
166
|
};
|
package/dist/tools/grep.d.ts
CHANGED
|
@@ -6,13 +6,13 @@ export declare const GrepTool: {
|
|
|
6
6
|
declare const inputSchema: z.ZodObject<{
|
|
7
7
|
pattern: z.ZodString;
|
|
8
8
|
path: z.ZodString;
|
|
9
|
-
recursive: z.
|
|
10
|
-
ignoreCase: z.
|
|
11
|
-
filePattern: z.
|
|
12
|
-
contextLines: z.
|
|
13
|
-
searchIgnored: z.
|
|
14
|
-
literal: z.
|
|
15
|
-
maxResults: z.
|
|
9
|
+
recursive: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
10
|
+
ignoreCase: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
11
|
+
filePattern: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedString<unknown>>>>;
|
|
12
|
+
contextLines: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
|
|
13
|
+
searchIgnored: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
14
|
+
literal: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
15
|
+
maxResults: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
|
|
16
16
|
}, z.core.$strip>;
|
|
17
17
|
type GrepInputSchema = z.infer<typeof inputSchema>;
|
|
18
18
|
export declare const createGrepTool: () => {
|
|
@@ -21,13 +21,13 @@ export declare const createGrepTool: () => {
|
|
|
21
21
|
inputSchema: z.ZodObject<{
|
|
22
22
|
pattern: z.ZodString;
|
|
23
23
|
path: z.ZodString;
|
|
24
|
-
recursive: z.
|
|
25
|
-
ignoreCase: z.
|
|
26
|
-
filePattern: z.
|
|
27
|
-
contextLines: z.
|
|
28
|
-
searchIgnored: z.
|
|
29
|
-
literal: z.
|
|
30
|
-
maxResults: z.
|
|
24
|
+
recursive: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
25
|
+
ignoreCase: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
26
|
+
filePattern: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedString<unknown>>>>;
|
|
27
|
+
contextLines: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
|
|
28
|
+
searchIgnored: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
29
|
+
literal: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedBoolean<unknown>>>>;
|
|
30
|
+
maxResults: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
|
|
31
31
|
}, z.core.$strip>;
|
|
32
32
|
};
|
|
33
33
|
display({ pattern, path, filePattern, recursive, ignoreCase, contextLines, }: GrepInputSchema): string;
|
package/dist/tools/grep.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../source/tools/grep.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,QAAA,MAAM,WAAW;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../source/tools/grep.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAEF,QAAA,MAAM,WAAW;;;;;;;;;;iBA2Cf,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;kFAapB,eAAe;sHAoCb,eAAe,mBACD,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;CAkErB,CAAC;AAEF,UAAU,WAAW;IACnB,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAuMD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CA8B9D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,EAAE,CAgEV;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,WAAW,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CAqDrE;AA8BD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAEhE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAE/D;AAyBD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACpC;IAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAoDpD;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,EACzB,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,GAC/B,OAAO,CAAC,UAAU,CAAC,CAgGrB"}
|
package/dist/tools/grep.js
CHANGED
|
@@ -16,24 +16,31 @@ const inputSchema = z.object({
|
|
|
16
16
|
path: z.string().describe("The path to search in"),
|
|
17
17
|
recursive: z
|
|
18
18
|
.preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
|
|
19
|
+
.default(null)
|
|
19
20
|
.describe("Search recursively. (default: true))"),
|
|
20
21
|
ignoreCase: z
|
|
21
22
|
.preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
|
|
23
|
+
.default(null)
|
|
22
24
|
.describe("Use case-sensitive search. (default: false)"),
|
|
23
25
|
filePattern: z
|
|
24
26
|
.preprocess((val) => convertNullString(val), z.coerce.string().nullable())
|
|
27
|
+
.default(null)
|
|
25
28
|
.describe("Glob pattern to filter files (e.g., '*.ts', '**/*.test.js'). (Default: no filtering)"),
|
|
26
29
|
contextLines: z
|
|
27
30
|
.preprocess((val) => convertNullString(val), z.coerce.number().nullable())
|
|
31
|
+
.default(null)
|
|
28
32
|
.describe("The number of context lines needed in search results. (Default: 0)"),
|
|
29
33
|
searchIgnored: z
|
|
30
34
|
.preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
|
|
35
|
+
.default(null)
|
|
31
36
|
.describe("Search ignored files. (Default: false)"),
|
|
32
37
|
literal: z
|
|
33
38
|
.preprocess((val) => convertNullString(val), z.coerce.boolean().nullable())
|
|
39
|
+
.default(null)
|
|
34
40
|
.describe("Pass true for fixed-string search (-F), false for regex, (Default: auto-detects unbalanced patterns like mismatched parentheses/brackets.)"),
|
|
35
41
|
maxResults: z
|
|
36
42
|
.preprocess((val) => convertNullString(val), z.coerce.number().nullable())
|
|
43
|
+
.default(null)
|
|
37
44
|
.describe("Maximum number of matches to return. Set to 0 for no limit. (Default: 100)"),
|
|
38
45
|
});
|
|
39
46
|
export const createGrepTool = () => {
|
|
@@ -133,39 +140,47 @@ function skipCharacterClass(pattern, startIndex) {
|
|
|
133
140
|
}
|
|
134
141
|
return i;
|
|
135
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Checks if a character is valid inside braces (digits 0-9 or comma).
|
|
145
|
+
*/
|
|
146
|
+
function isValidBraceChar(c) {
|
|
147
|
+
return (c >= "0" && c <= "9") || c === ",";
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Checks if empty braces have a preceding atom.
|
|
151
|
+
* Empty braces with no preceding atom are treated as literal.
|
|
152
|
+
*/
|
|
153
|
+
function hasPrecedingAtom(pattern, startIndex) {
|
|
154
|
+
if (startIndex === 0)
|
|
155
|
+
return false;
|
|
156
|
+
const prev = pattern[startIndex - 1];
|
|
157
|
+
return /\S/.test(prev);
|
|
158
|
+
}
|
|
136
159
|
/**
|
|
137
160
|
* Parses and validates the content inside braces {..}.
|
|
138
161
|
* Returns true if the brace content is invalid.
|
|
139
162
|
*/
|
|
140
163
|
function isInvalidBraceContent(pattern, startIndex) {
|
|
141
|
-
|
|
164
|
+
const j = startIndex + 1;
|
|
165
|
+
// Find closing brace and check for invalid characters
|
|
142
166
|
let hasDigits = false;
|
|
143
|
-
let
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const c = pattern[j];
|
|
147
|
-
if (c >= "0" && c <= "9") {
|
|
148
|
-
hasDigits = true;
|
|
149
|
-
}
|
|
150
|
-
else if (c === "," && !hasComma) {
|
|
151
|
-
hasComma = true;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
// Invalid character inside braces
|
|
167
|
+
let k = j;
|
|
168
|
+
while (k < pattern.length && pattern[k] !== "}") {
|
|
169
|
+
if (!isValidBraceChar(pattern[k])) {
|
|
155
170
|
return true;
|
|
156
171
|
}
|
|
157
|
-
|
|
172
|
+
if (pattern[k] >= "0" && pattern[k] <= "9") {
|
|
173
|
+
hasDigits = true;
|
|
174
|
+
}
|
|
175
|
+
k++;
|
|
158
176
|
}
|
|
159
177
|
// No closing brace found
|
|
160
|
-
if (
|
|
178
|
+
if (k >= pattern.length) {
|
|
161
179
|
return true;
|
|
162
180
|
}
|
|
163
|
-
// Empty braces {} with
|
|
164
|
-
if (!hasDigits) {
|
|
165
|
-
|
|
166
|
-
if (prev !== undefined && /\S/.test(prev)) {
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
181
|
+
// Empty braces {} with preceding atom are invalid
|
|
182
|
+
if (!hasDigits && hasPrecedingAtom(pattern, startIndex)) {
|
|
183
|
+
return true;
|
|
169
184
|
}
|
|
170
185
|
return false;
|
|
171
186
|
}
|
|
@@ -184,24 +199,31 @@ function hasInvalidRepetition(pattern) {
|
|
|
184
199
|
i = skipCharacterClass(pattern, i);
|
|
185
200
|
continue;
|
|
186
201
|
}
|
|
202
|
+
// Unmatched closing brace is invalid
|
|
187
203
|
if (ch === "}") {
|
|
188
|
-
// Unmatched closing brace is invalid
|
|
189
204
|
return true;
|
|
190
205
|
}
|
|
206
|
+
// Handle opening brace
|
|
191
207
|
if (ch === "{") {
|
|
192
208
|
if (isInvalidBraceContent(pattern, i)) {
|
|
193
209
|
return true;
|
|
194
210
|
}
|
|
195
|
-
|
|
196
|
-
let j = i + 1;
|
|
197
|
-
while (j < pattern.length && pattern[j] !== "}") {
|
|
198
|
-
j++;
|
|
199
|
-
}
|
|
200
|
-
i = j;
|
|
211
|
+
i = findClosingBrace(pattern, i);
|
|
201
212
|
}
|
|
202
213
|
}
|
|
203
214
|
return false;
|
|
204
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Finds the index of the closing brace matching an opening brace.
|
|
218
|
+
* Returns the index of the closing brace, or the last index of pattern if not found.
|
|
219
|
+
*/
|
|
220
|
+
function findClosingBrace(pattern, startIndex) {
|
|
221
|
+
let j = startIndex + 1;
|
|
222
|
+
while (j < pattern.length && pattern[j] !== "}") {
|
|
223
|
+
j++;
|
|
224
|
+
}
|
|
225
|
+
return j;
|
|
226
|
+
}
|
|
205
227
|
/**
|
|
206
228
|
* Count bracket/paren/brace pairs in a regex pattern, excluding character classes.
|
|
207
229
|
*/
|
|
@@ -489,7 +511,6 @@ export function truncateMatches(matches, maxResults) {
|
|
|
489
511
|
matchesKept++;
|
|
490
512
|
}
|
|
491
513
|
else {
|
|
492
|
-
break;
|
|
493
514
|
}
|
|
494
515
|
}
|
|
495
516
|
else if (match.isContext) {
|