Skip to content

Commit e65c0dd

Browse files
committed
fix: enforce file restrictions for all edit tools in architect mode (#5445)
- Fix isToolAllowedForMode to check search/replace parameters for search_and_replace tool - Add comprehensive test coverage for all edit tools (search_and_replace, insert_content, write_to_file, apply_diff) - Ensure architect mode properly restricts editing to markdown files only - Prevents architect mode from editing Python files and other non-markdown files Fixes #5445
1 parent ad201cc commit e65c0dd

File tree

2 files changed

+61
-5
lines changed

2 files changed

+61
-5
lines changed

src/shared/__tests__/modes.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,58 @@ describe("isToolAllowedForMode", () => {
245245
expect(isToolAllowedForMode("browser_action", "architect", [])).toBe(true)
246246
expect(isToolAllowedForMode("use_mcp_tool", "architect", [])).toBe(true)
247247
})
248+
249+
it("applies restrictions to all edit tools including search_and_replace and insert_content", () => {
250+
// Test search_and_replace with matching file
251+
expect(
252+
isToolAllowedForMode("search_and_replace", "architect", [], undefined, {
253+
path: "test.md",
254+
search: "old text",
255+
replace: "new text",
256+
}),
257+
).toBe(true)
258+
259+
// Test insert_content with matching file
260+
expect(
261+
isToolAllowedForMode("insert_content", "architect", [], undefined, {
262+
path: "test.md",
263+
line: "1",
264+
content: "# New content",
265+
}),
266+
).toBe(true)
267+
268+
// Test search_and_replace with non-matching file - should throw error
269+
expect(() =>
270+
isToolAllowedForMode("search_and_replace", "architect", [], undefined, {
271+
path: "test.py",
272+
search: "old text",
273+
replace: "new text",
274+
}),
275+
).toThrow(FileRestrictionError)
276+
expect(() =>
277+
isToolAllowedForMode("search_and_replace", "architect", [], undefined, {
278+
path: "test.py",
279+
search: "old text",
280+
replace: "new text",
281+
}),
282+
).toThrow(/Markdown files only/)
283+
284+
// Test insert_content with non-matching file - should throw error
285+
expect(() =>
286+
isToolAllowedForMode("insert_content", "architect", [], undefined, {
287+
path: "test.py",
288+
line: "1",
289+
content: "print('hello')",
290+
}),
291+
).toThrow(FileRestrictionError)
292+
expect(() =>
293+
isToolAllowedForMode("insert_content", "architect", [], undefined, {
294+
path: "test.py",
295+
line: "1",
296+
content: "print('hello')",
297+
}),
298+
).toThrow(/Markdown files only/)
299+
})
248300
})
249301

250302
it("handles non-existent modes", () => {

src/shared/modes.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,15 @@ export function isToolAllowedForMode(
266266
// For the edit group, check file regex if specified
267267
if (groupName === "edit" && options.fileRegex) {
268268
const filePath = toolParams?.path
269-
if (
270-
filePath &&
271-
(toolParams.diff || toolParams.content || toolParams.operations) &&
272-
!doesFileMatchRegex(filePath, options.fileRegex)
273-
) {
269+
// Check if this is an actual edit operation (not just path-only for streaming)
270+
const isEditOperation = !!(
271+
toolParams?.diff ||
272+
toolParams?.content ||
273+
toolParams?.operations ||
274+
toolParams?.search ||
275+
toolParams?.replace
276+
)
277+
if (filePath && isEditOperation && !doesFileMatchRegex(filePath, options.fileRegex)) {
274278
throw new FileRestrictionError(mode.name, options.fileRegex, options.description, filePath)
275279
}
276280
}

0 commit comments

Comments
 (0)