Allow architect mode to write md files

This commit is contained in:
Matt Rubens
2025-01-23 23:41:47 -05:00
parent 0a32e24c6d
commit 4e77fb93bb
9 changed files with 419 additions and 139 deletions

View File

@@ -5,16 +5,40 @@ import { TOOL_GROUPS, ToolGroup } from "../../shared/tool-groups"
// Create a schema for valid tool groups using the keys of TOOL_GROUPS
const ToolGroupSchema = z.enum(Object.keys(TOOL_GROUPS) as [ToolGroup, ...ToolGroup[]])
// Schema for group options with regex validation
const GroupOptionsSchema = z.object({
fileRegex: z
.string()
.optional()
.refine(
(pattern) => {
if (!pattern) return true // Optional, so empty is valid
try {
new RegExp(pattern)
return true
} catch {
return false
}
},
{ message: "Invalid regular expression pattern" },
),
})
// Schema for a group entry - either a tool group string or a tuple of [group, options]
const GroupEntrySchema = z.union([ToolGroupSchema, z.tuple([ToolGroupSchema, GroupOptionsSchema])])
// Schema for array of groups
const GroupsArraySchema = z
.array(ToolGroupSchema)
.array(GroupEntrySchema)
.min(1, "At least one tool group is required")
.refine(
(groups) => {
const seen = new Set()
return groups.every((group) => {
if (seen.has(group)) return false
seen.add(group)
// For tuples, check the group name (first element)
const groupName = Array.isArray(group) ? group[0] : group
if (seen.has(groupName)) return false
seen.add(groupName)
return true
})
},

View File

@@ -1,122 +1,72 @@
import { validateCustomMode } from "../CustomModesSchema"
import { ModeConfig } from "../../../shared/modes"
import { ZodError } from "zod"
import { CustomModeSchema } from "../CustomModesSchema"
describe("CustomModesSchema", () => {
describe("validateCustomMode", () => {
test("accepts valid mode configuration", () => {
const validMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies ModeConfig
describe("CustomModeSchema", () => {
it("validates a basic mode configuration", () => {
const validMode = {
slug: "test-mode",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read", "browser"],
}
expect(() => validateCustomMode(validMode)).not.toThrow()
})
expect(() => CustomModeSchema.parse(validMode)).not.toThrow()
})
test("accepts mode with multiple groups", () => {
const validMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read", "edit", "browser"] as const,
} satisfies ModeConfig
it("validates a mode with file restrictions", () => {
const modeWithFileRestrictions = {
slug: "markdown-editor",
name: "Markdown Editor",
roleDefinition: "Markdown editing mode",
groups: ["read", ["edit", { fileRegex: "\\.md$" }], "browser"],
}
expect(() => validateCustomMode(validMode)).not.toThrow()
})
expect(() => CustomModeSchema.parse(modeWithFileRestrictions)).not.toThrow()
})
test("accepts mode with optional customInstructions", () => {
const validMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
customInstructions: "Custom instructions",
groups: ["read"] as const,
} satisfies ModeConfig
it("validates file regex patterns", () => {
const validPatterns = ["\\.md$", ".*\\.txt$", "[a-z]+\\.js$"]
const invalidPatterns = ["[", "(unclosed", "\\"]
expect(() => validateCustomMode(validMode)).not.toThrow()
})
test("rejects missing required fields", () => {
const invalidModes = [
{}, // All fields missing
{ name: "Test" }, // Missing most fields
{
name: "Test",
roleDefinition: "Role",
}, // Missing slug and groups
]
invalidModes.forEach((invalidMode) => {
expect(() => validateCustomMode(invalidMode)).toThrow(ZodError)
})
})
test("rejects invalid slug format", () => {
const invalidMode = {
slug: "not@a@valid@slug",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies Omit<ModeConfig, "slug"> & { slug: string }
expect(() => validateCustomMode(invalidMode)).toThrow(ZodError)
expect(() => validateCustomMode(invalidMode)).toThrow("Slug must contain only letters numbers and dashes")
})
test("rejects empty strings in required fields", () => {
const emptyNameMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "",
roleDefinition: "Test role definition",
groups: ["read"] as const,
} satisfies ModeConfig
const emptyRoleMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "",
groups: ["read"] as const,
} satisfies ModeConfig
expect(() => validateCustomMode(emptyNameMode)).toThrow("Name is required")
expect(() => validateCustomMode(emptyRoleMode)).toThrow("Role definition is required")
})
test("rejects invalid group configurations", () => {
const invalidGroupMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: ["not-a-valid-group"] as any,
validPatterns.forEach((pattern) => {
const mode = {
slug: "test",
name: "Test",
roleDefinition: "Test",
groups: ["read", ["edit", { fileRegex: pattern }]],
}
expect(() => validateCustomMode(invalidGroupMode)).toThrow(ZodError)
expect(() => CustomModeSchema.parse(mode)).not.toThrow()
})
test("rejects empty groups array", () => {
const invalidMode = {
slug: "123e4567-e89b-12d3-a456-426614174000",
name: "Test Mode",
roleDefinition: "Test role definition",
groups: [] as const,
} satisfies ModeConfig
expect(() => validateCustomMode(invalidMode)).toThrow("At least one tool group is required")
})
test("handles null and undefined gracefully", () => {
expect(() => validateCustomMode(null)).toThrow(ZodError)
expect(() => validateCustomMode(undefined)).toThrow(ZodError)
})
test("rejects non-object inputs", () => {
const invalidInputs = [42, "string", true, [], () => {}]
invalidInputs.forEach((input) => {
expect(() => validateCustomMode(input)).toThrow(ZodError)
})
invalidPatterns.forEach((pattern) => {
const mode = {
slug: "test",
name: "Test",
roleDefinition: "Test",
groups: ["read", ["edit", { fileRegex: pattern }]],
}
expect(() => CustomModeSchema.parse(mode)).toThrow()
})
})
it("prevents duplicate groups", () => {
const modeWithDuplicates = {
slug: "test",
name: "Test",
roleDefinition: "Test",
groups: ["read", "read", ["edit", { fileRegex: "\\.md$" }], ["edit", { fileRegex: "\\.txt$" }]],
}
expect(() => CustomModeSchema.parse(modeWithDuplicates)).toThrow(/Duplicate groups/)
})
it("requires at least one group", () => {
const modeWithNoGroups = {
slug: "test",
name: "Test",
roleDefinition: "Test",
groups: [],
}
expect(() => CustomModeSchema.parse(modeWithNoGroups)).toThrow(/At least one tool group is required/)
})
})

View File

@@ -232,6 +232,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
@@ -517,6 +520,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
@@ -802,6 +808,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
@@ -1135,6 +1144,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
@@ -1834,6 +1846,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
@@ -2167,6 +2182,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
@@ -2513,6 +2531,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- You should use apply_diff instead of write_to_file when making changes to existing files since it is much faster and easier to apply a diff than to write the entire file again. Only use write_to_file to edit files when apply_diff has failed repeatedly to apply the diff.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
@@ -2798,6 +2819,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
@@ -2909,7 +2933,7 @@ Mock generic rules"
`;
exports[`addCustomInstructions should generate correct prompt for architect mode 1`] = `
"You are Roo, a software architecture expert specializing in analyzing codebases, identifying patterns, and providing high-level technical guidance. You excel at understanding complex systems, evaluating architectural decisions, and suggesting improvements while maintaining a read-only approach to the codebase. Make sure to help the user come up with a solid implementation plan for their project and don't rush to switch to implementing code.
"You are Roo, a software architecture expert specializing in analyzing codebases, identifying patterns, and providing high-level technical guidance. You excel at understanding complex systems, evaluating architectural decisions, and suggesting improvements. You can edit markdown documentation files to help document architectural decisions and patterns.
====
@@ -3002,6 +3026,43 @@ Example: Requesting to list all top level source code definitions in the current
<path>.</path>
</list_code_definition_names>
## write_to_file
Description: Request to write full content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
Parameters:
- path: (required) The path of the file to write to (relative to the current working directory /test/path)
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing.
Usage:
<write_to_file>
<path>File path here</path>
<content>
Your file content here
</content>
<line_count>total number of lines in the file, including empty lines</line_count>
</write_to_file>
Example: Requesting to write to frontend-config.json
<write_to_file>
<path>frontend-config.json</path>
<content>
{
"apiEndpoint": "https://api.example.com",
"theme": {
"primaryColor": "#007bff",
"secondaryColor": "#6c757d",
"fontFamily": "Arial, sans-serif"
},
"features": {
"darkMode": true,
"notifications": true,
"analytics": false
},
"version": "1.0.0"
}
</content>
<line_count>14</line_count>
</write_to_file>
## ask_followup_question
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
Parameters:
@@ -3089,6 +3150,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
@@ -3323,6 +3387,9 @@ RULES
- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes.
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool.
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.

View File

@@ -8,6 +8,7 @@ export function getRulesSection(
supportsComputerUse: boolean,
diffStrategy?: DiffStrategy,
context?: vscode.ExtensionContext,
diffEnabled?: boolean,
): string {
const settingsDir = context ? path.join(context.globalStorageUri.fsPath, "settings") : "<settings directory>"
const customModesPath = path.join(settingsDir, "cline_custom_modes.json")
@@ -26,6 +27,9 @@ ${
? "- You should use apply_diff instead of write_to_file when making changes to existing files since it is much faster and easier to apply a diff than to write the entire file again. Only use write_to_file to edit files when apply_diff has failed repeatedly to apply the diff."
: "- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool."
}
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.

View File

@@ -78,7 +78,7 @@ ${getCapabilitiesSection(cwd, supportsComputerUse, mcpHub, effectiveDiffStrategy
${modesSection}
${getRulesSection(cwd, supportsComputerUse, diffStrategy, context)}
${getRulesSection(cwd, supportsComputerUse, effectiveDiffStrategy, context)}
${getSystemInfoSection(cwd, mode, customModeConfigs)}

View File

@@ -11,7 +11,7 @@ import { getUseMcpToolDescription } from "./use-mcp-tool"
import { getAccessMcpResourceDescription } from "./access-mcp-resource"
import { DiffStrategy } from "../../diff/DiffStrategy"
import { McpHub } from "../../../services/mcp/McpHub"
import { Mode, ModeConfig, getModeConfig, isToolAllowedForMode } from "../../../shared/modes"
import { Mode, ModeConfig, getModeConfig, isToolAllowedForMode, getGroupName } from "../../../shared/modes"
import { ToolName, getToolName, getToolOptions, TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tool-groups"
import { ToolArgs } from "./types"
@@ -53,19 +53,33 @@ export function getToolDescriptionsForMode(
const tools = new Set<string>()
// Add tools from mode's groups
config.groups.forEach((group) => {
TOOL_GROUPS[group].forEach((tool) => {
if (isToolAllowedForMode(tool as ToolName, mode, customModes ?? [])) {
tools.add(tool)
}
})
config.groups.forEach((groupEntry) => {
const groupName = getGroupName(groupEntry)
const toolGroup = TOOL_GROUPS[groupName]
if (toolGroup) {
toolGroup.forEach((tool) => {
if (isToolAllowedForMode(tool as ToolName, mode, customModes ?? [])) {
tools.add(tool)
}
})
}
})
// Filter out apply_diff if diffStrategy is not provided
if (!diffStrategy) {
tools.delete("apply_diff")
}
// Add always available tools
ALWAYS_AVAILABLE_TOOLS.forEach((tool) => tools.add(tool))
// Map tool descriptions for allowed tools
const descriptions = Array.from(tools).map((toolName) => {
// Skip apply_diff tool description if diffStrategy is not provided
if (toolName === "apply_diff" && !diffStrategy) {
return undefined
}
const descriptionFn = toolDescriptionMap[toolName]
if (!descriptionFn) {
return undefined

View File

@@ -0,0 +1,107 @@
import { isToolAllowedForMode, FileRestrictionError, ModeConfig } from "../modes"
describe("isToolAllowedForMode", () => {
const customModes: ModeConfig[] = [
{
slug: "markdown-editor",
name: "Markdown Editor",
roleDefinition: "You are a markdown editor",
groups: ["read", ["edit", { fileRegex: "\\.md$" }], "browser"],
},
{
slug: "css-editor",
name: "CSS Editor",
roleDefinition: "You are a CSS editor",
groups: ["read", ["edit", { fileRegex: "\\.css$" }], "browser"],
},
]
it("allows always available tools", () => {
expect(isToolAllowedForMode("ask_followup_question", "markdown-editor", customModes)).toBe(true)
expect(isToolAllowedForMode("attempt_completion", "markdown-editor", customModes)).toBe(true)
})
it("allows unrestricted tools", () => {
expect(isToolAllowedForMode("read_file", "markdown-editor", customModes)).toBe(true)
expect(isToolAllowedForMode("browser_action", "markdown-editor", customModes)).toBe(true)
})
describe("file restrictions", () => {
it("allows editing matching files", () => {
// Test markdown editor mode
const mdResult = isToolAllowedForMode("write_to_file", "markdown-editor", customModes, undefined, "test.md")
expect(mdResult).toBe(true)
// Test CSS editor mode
const cssResult = isToolAllowedForMode("write_to_file", "css-editor", customModes, undefined, "styles.css")
expect(cssResult).toBe(true)
})
it("rejects editing non-matching files", () => {
// Test markdown editor mode with non-markdown file
const mdError = isToolAllowedForMode("write_to_file", "markdown-editor", customModes, undefined, "test.js")
expect(mdError).toBeInstanceOf(FileRestrictionError)
expect((mdError as FileRestrictionError).message).toContain("\\.md$")
// Test CSS editor mode with non-CSS file
const cssError = isToolAllowedForMode("write_to_file", "css-editor", customModes, undefined, "test.js")
expect(cssError).toBeInstanceOf(FileRestrictionError)
expect((cssError as FileRestrictionError).message).toContain("\\.css$")
})
it("requires file path for restricted edit operations", () => {
const result = isToolAllowedForMode("write_to_file", "markdown-editor", customModes)
expect(result).toBeInstanceOf(FileRestrictionError)
expect((result as FileRestrictionError).message).toContain("\\.md$")
})
it("applies restrictions to both write_to_file and apply_diff", () => {
// Test write_to_file
const writeResult = isToolAllowedForMode(
"write_to_file",
"markdown-editor",
customModes,
undefined,
"test.md",
)
expect(writeResult).toBe(true)
// Test apply_diff
const diffResult = isToolAllowedForMode("apply_diff", "markdown-editor", customModes, undefined, "test.md")
expect(diffResult).toBe(true)
// Test both with non-matching file
const writeError = isToolAllowedForMode(
"write_to_file",
"markdown-editor",
customModes,
undefined,
"test.js",
)
expect(writeError).toBeInstanceOf(FileRestrictionError)
const diffError = isToolAllowedForMode("apply_diff", "markdown-editor", customModes, undefined, "test.js")
expect(diffError).toBeInstanceOf(FileRestrictionError)
})
})
it("handles non-existent modes", () => {
expect(isToolAllowedForMode("write_to_file", "non-existent", customModes)).toBe(false)
})
it("respects tool requirements", () => {
const toolRequirements = {
write_to_file: false,
}
expect(isToolAllowedForMode("write_to_file", "markdown-editor", customModes, toolRequirements)).toBe(false)
})
})
describe("FileRestrictionError", () => {
it("formats error message correctly", () => {
const error = new FileRestrictionError("Markdown Editor", "\\.md$")
expect(error.message).toBe("This mode (Markdown Editor) can only edit files matching the pattern: \\.md$")
expect(error.name).toBe("FileRestrictionError")
})
})

View File

@@ -3,13 +3,22 @@ import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tool-groups"
// Mode types
export type Mode = string
// Group options type
export type GroupOptions = {
fileRegex?: string // Regular expression pattern
description?: string // Human-readable description of the pattern
}
// Group entry can be either a string or tuple with options
export type GroupEntry = ToolGroup | readonly [ToolGroup, GroupOptions]
// Mode configuration type
export type ModeConfig = {
slug: string
name: string
roleDefinition: string
customInstructions?: string
groups: readonly ToolGroup[] // Now uses groups instead of tools array
groups: readonly GroupEntry[] // Now supports both simple strings and tuples with options
}
// Mode-specific prompts only
@@ -22,13 +31,35 @@ export type CustomModePrompts = {
[key: string]: PromptComponent | undefined
}
// Helper to extract group name regardless of format
export function getGroupName(group: GroupEntry): ToolGroup {
return Array.isArray(group) ? group[0] : group
}
// Helper to get group options if they exist
function getGroupOptions(group: GroupEntry): GroupOptions | undefined {
return Array.isArray(group) ? group[1] : undefined
}
// Helper to check if a file path matches a regex pattern
export function doesFileMatchRegex(filePath: string, pattern: string): boolean {
try {
const regex = new RegExp(pattern)
return regex.test(filePath)
} catch (error) {
console.error(`Invalid regex pattern: ${pattern}`, error)
return false
}
}
// Helper to get all tools for a mode
export function getToolsForMode(groups: readonly ToolGroup[]): string[] {
export function getToolsForMode(groups: readonly GroupEntry[]): string[] {
const tools = new Set<string>()
// Add tools from each group
groups.forEach((group) => {
TOOL_GROUPS[group].forEach((tool) => tools.add(tool))
const groupName = getGroupName(group)
TOOL_GROUPS[groupName].forEach((tool) => tools.add(tool))
})
// Always add required tools
@@ -50,8 +81,8 @@ export const modes: readonly ModeConfig[] = [
slug: "architect",
name: "Architect",
roleDefinition:
"You are Roo, a software architecture expert specializing in analyzing codebases, identifying patterns, and providing high-level technical guidance. You excel at understanding complex systems, evaluating architectural decisions, and suggesting improvements while maintaining a read-only approach to the codebase. Make sure to help the user come up with a solid implementation plan for their project and don't rush to switch to implementing code.",
groups: ["read", "browser", "mcp"],
"You are Roo, a software architecture expert specializing in analyzing codebases, identifying patterns, and providing high-level technical guidance. You excel at understanding complex systems, evaluating architectural decisions, and suggesting improvements. You can edit markdown documentation files to help document architectural decisions and patterns.",
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
},
{
slug: "ask",
@@ -113,12 +144,21 @@ export function isCustomMode(slug: string, customModes?: ModeConfig[]): boolean
return !!customModes?.some((mode) => mode.slug === slug)
}
// Custom error class for file restrictions
export class FileRestrictionError extends Error {
constructor(mode: string, pattern: string) {
super(`This mode (${mode}) can only edit files matching the pattern: ${pattern}`)
this.name = "FileRestrictionError"
}
}
export function isToolAllowedForMode(
tool: string,
modeSlug: string,
customModes: ModeConfig[],
toolRequirements?: Record<string, boolean>,
): boolean {
filePath?: string, // Optional file path for checking regex patterns
): boolean | FileRestrictionError {
// Always allow these tools
if (ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) {
return true
@@ -136,8 +176,33 @@ export function isToolAllowedForMode(
return false
}
// Check if tool is in any of the mode's groups
return mode.groups.some((group) => TOOL_GROUPS[group].includes(tool as string))
// Check if tool is in any of the mode's groups and respects any group options
for (const group of mode.groups) {
const groupName = getGroupName(group)
const options = getGroupOptions(group)
// If the tool isn't in this group, continue to next group
if (!TOOL_GROUPS[groupName].includes(tool)) {
continue
}
// If there are no options, allow the tool
if (!options) {
return true
}
// For the edit group, check file regex if specified
if (groupName === "edit" && options.fileRegex) {
if (!filePath || !doesFileMatchRegex(filePath, options.fileRegex)) {
return new FileRestrictionError(mode.name, options.fileRegex)
}
return true
}
return true
}
return false
}
// Create the mode-specific default prompts

View File

@@ -8,7 +8,14 @@ import {
VSCodeCheckbox,
} from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { Mode, PromptComponent, getRoleDefinition, getAllModes, ModeConfig } from "../../../../src/shared/modes"
import {
Mode,
PromptComponent,
getRoleDefinition,
getAllModes,
ModeConfig,
GroupEntry,
} from "../../../../src/shared/modes"
import {
supportPrompt,
SupportPromptType,
@@ -26,6 +33,11 @@ type PromptsViewProps = {
onDone: () => void
}
// Helper to get group name regardless of format
function getGroupName(group: GroupEntry): ToolGroup {
return Array.isArray(group) ? group[0] : group
}
const PromptsView = ({ onDone }: PromptsViewProps) => {
const {
customModePrompts,
@@ -131,7 +143,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
const [newModeSlug, setNewModeSlug] = useState("")
const [newModeRoleDefinition, setNewModeRoleDefinition] = useState("")
const [newModeCustomInstructions, setNewModeCustomInstructions] = useState("")
const [newModeGroups, setNewModeGroups] = useState<readonly ToolGroup[]>(availableGroups)
const [newModeGroups, setNewModeGroups] = useState<GroupEntry[]>(availableGroups)
// Reset form fields when dialog opens
useEffect(() => {
@@ -219,11 +231,11 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
const target = (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement)
const checked = target.checked
const oldGroups = customMode?.groups || []
let newGroups: readonly ToolGroup[]
let newGroups: GroupEntry[]
if (checked) {
newGroups = [...oldGroups, group]
} else {
newGroups = oldGroups.filter((g) => g !== group)
newGroups = oldGroups.filter((g) => getGroupName(g) !== group)
}
if (customMode) {
updateCustomMode(customMode.slug, {
@@ -639,8 +651,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
const isCustomMode = findModeBySlug(mode, customModes)
const customMode = isCustomMode
const isGroupEnabled = isCustomMode
? customMode?.groups?.includes(group)
: currentMode?.groups?.includes(group)
? customMode?.groups?.some((g) => getGroupName(g) === group)
: currentMode?.groups?.some((g) => getGroupName(g) === group)
return (
<VSCodeCheckbox
@@ -649,6 +661,30 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
onChange={handleGroupChange(group, Boolean(isCustomMode), customMode)}
disabled={!isCustomMode}>
{GROUP_DISPLAY_NAMES[group]}
{group === "edit" && (
<div
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
marginTop: "2px",
}}>
Allowed files:{" "}
{(() => {
const currentMode = getCurrentMode()
const editGroup = currentMode?.groups?.find(
(g) =>
Array.isArray(g) &&
g[0] === "edit" &&
g[1]?.fileRegex,
)
if (!Array.isArray(editGroup)) return "all files"
return (
editGroup[1].description ||
`/${editGroup[1].fileRegex}/`
)
})()}
</div>
)}
</VSCodeCheckbox>
)
})}
@@ -664,7 +700,18 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{(() => {
const currentMode = getCurrentMode()
const enabledGroups = currentMode?.groups || []
return enabledGroups.map((group) => GROUP_DISPLAY_NAMES[group]).join(", ")
return enabledGroups
.map((group) => {
const groupName = getGroupName(group)
const displayName = GROUP_DISPLAY_NAMES[groupName]
if (Array.isArray(group) && group[1]?.fileRegex) {
const description =
group[1].description || `/${group[1].fileRegex}/`
return `${displayName} (${description})`
}
return displayName
})
.join(", ")
})()}
</div>
)}
@@ -1050,7 +1097,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{availableGroups.map((group) => (
<VSCodeCheckbox
key={group}
checked={newModeGroups.includes(group)}
checked={newModeGroups.some((g) => getGroupName(g) === group)}
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
const target =
(e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement)
@@ -1058,7 +1105,9 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
if (checked) {
setNewModeGroups([...newModeGroups, group])
} else {
setNewModeGroups(newModeGroups.filter((g) => g !== group))
setNewModeGroups(
newModeGroups.filter((g) => getGroupName(g) !== group),
)
}
}}>
{GROUP_DISPLAY_NAMES[group]}