mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-22 21:31:08 -05:00
Allow architect mode to write md files
This commit is contained in:
@@ -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
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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/)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user