Skip to content

Commit ace4fb0

Browse files
hannesrudolphdaniel-lxs
authored andcommitted
fix: remove existing rules folder when importing mode without rules
- Always remove the existing rules folder for a mode during project-level import - This ensures that if the imported mode has no rules, the folder is cleaned up - Added tests to verify the behavior works correctly
1 parent 54ad594 commit ace4fb0

File tree

2 files changed

+123
-12
lines changed

2 files changed

+123
-12
lines changed

src/core/config/CustomModesManager.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -716,11 +716,12 @@ export class CustomModesManager {
716716
source: source, // Use the provided source parameter
717717
})
718718

719-
// Only import rules files for project-level imports
720-
if (source === "project" && rulesFiles && Array.isArray(rulesFiles)) {
719+
// Handle project-level imports
720+
if (source === "project") {
721721
const workspacePath = getWorkspacePath()
722722

723-
// First, remove the existing rules folder for this mode if it exists
723+
// Always remove the existing rules folder for this mode if it exists
724+
// This ensures that if the imported mode has no rules, the folder is cleaned up
724725
const rulesFolderPath = path.join(workspacePath, ".roo", `rules-${importMode.slug}`)
725726
try {
726727
await fs.rm(rulesFolderPath, { recursive: true, force: true })
@@ -730,17 +731,20 @@ export class CustomModesManager {
730731
logger.debug(`No existing rules folder to remove for mode ${importMode.slug}`)
731732
}
732733

733-
// Now import the new rules files
734-
for (const ruleFile of rulesFiles) {
735-
if (ruleFile.relativePath && ruleFile.content) {
736-
const targetPath = path.join(workspacePath, ".roo", ruleFile.relativePath)
734+
// Only create new rules files if they exist in the import
735+
if (rulesFiles && Array.isArray(rulesFiles) && rulesFiles.length > 0) {
736+
// Import the new rules files
737+
for (const ruleFile of rulesFiles) {
738+
if (ruleFile.relativePath && ruleFile.content) {
739+
const targetPath = path.join(workspacePath, ".roo", ruleFile.relativePath)
737740

738-
// Ensure directory exists
739-
const targetDir = path.dirname(targetPath)
740-
await fs.mkdir(targetDir, { recursive: true })
741+
// Ensure directory exists
742+
const targetDir = path.dirname(targetPath)
743+
await fs.mkdir(targetDir, { recursive: true })
741744

742-
// Write the file
743-
await fs.writeFile(targetPath, ruleFile.content, "utf-8")
745+
// Write the file
746+
await fs.writeFile(targetPath, ruleFile.content, "utf-8")
747+
}
744748
}
745749
}
746750
} else if (source === "global" && rulesFiles && Array.isArray(rulesFiles)) {

src/core/config/__tests__/CustomModesManager.spec.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,113 @@ describe("CustomModesManager", () => {
10341034
expect(result.success).toBe(false)
10351035
expect(result.error).toContain("Permission denied")
10361036
})
1037+
1038+
it("should remove existing rules folder when importing mode without rules", async () => {
1039+
const importYaml = yaml.stringify({
1040+
customModes: [
1041+
{
1042+
slug: "test-mode",
1043+
name: "Test Mode",
1044+
roleDefinition: "Test Role",
1045+
groups: ["read"],
1046+
// No rulesFiles property - this mode has no rules
1047+
},
1048+
],
1049+
})
1050+
1051+
let roomodesContent: any = null
1052+
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
1053+
if (path === mockSettingsPath) {
1054+
return yaml.stringify({ customModes: [] })
1055+
}
1056+
if (path === mockRoomodes && roomodesContent) {
1057+
return yaml.stringify(roomodesContent)
1058+
}
1059+
throw new Error("File not found")
1060+
})
1061+
;(fs.writeFile as Mock).mockImplementation(async (path: string, content: string) => {
1062+
if (path === mockRoomodes) {
1063+
roomodesContent = yaml.parse(content)
1064+
}
1065+
return Promise.resolve()
1066+
})
1067+
;(fs.rm as Mock).mockResolvedValue(undefined)
1068+
1069+
const result = await manager.importModeWithRules(importYaml)
1070+
1071+
expect(result.success).toBe(true)
1072+
1073+
// Verify that fs.rm was called to remove the existing rules folder
1074+
expect(fs.rm).toHaveBeenCalledWith(expect.stringContaining(path.join(".roo", "rules-test-mode")), {
1075+
recursive: true,
1076+
force: true,
1077+
})
1078+
1079+
// Verify mode was imported
1080+
expect(fs.writeFile).toHaveBeenCalledWith(
1081+
expect.stringContaining(".roomodes"),
1082+
expect.stringContaining("test-mode"),
1083+
"utf-8",
1084+
)
1085+
})
1086+
1087+
it("should remove existing rules folder and create new ones when importing mode with rules", async () => {
1088+
const importYaml = yaml.stringify({
1089+
customModes: [
1090+
{
1091+
slug: "test-mode",
1092+
name: "Test Mode",
1093+
roleDefinition: "Test Role",
1094+
groups: ["read"],
1095+
rulesFiles: [
1096+
{
1097+
relativePath: "rules-test-mode/new-rule.md",
1098+
content: "New rule content",
1099+
},
1100+
],
1101+
},
1102+
],
1103+
})
1104+
1105+
let roomodesContent: any = null
1106+
let writtenFiles: Record<string, string> = {}
1107+
;(fs.readFile as Mock).mockImplementation(async (path: string) => {
1108+
if (path === mockSettingsPath) {
1109+
return yaml.stringify({ customModes: [] })
1110+
}
1111+
if (path === mockRoomodes && roomodesContent) {
1112+
return yaml.stringify(roomodesContent)
1113+
}
1114+
throw new Error("File not found")
1115+
})
1116+
;(fs.writeFile as Mock).mockImplementation(async (path: string, content: string) => {
1117+
if (path === mockRoomodes) {
1118+
roomodesContent = yaml.parse(content)
1119+
} else {
1120+
writtenFiles[path] = content
1121+
}
1122+
return Promise.resolve()
1123+
})
1124+
;(fs.rm as Mock).mockResolvedValue(undefined)
1125+
;(fs.mkdir as Mock).mockResolvedValue(undefined)
1126+
1127+
const result = await manager.importModeWithRules(importYaml)
1128+
1129+
expect(result.success).toBe(true)
1130+
1131+
// Verify that fs.rm was called to remove the existing rules folder
1132+
expect(fs.rm).toHaveBeenCalledWith(expect.stringContaining(path.join(".roo", "rules-test-mode")), {
1133+
recursive: true,
1134+
force: true,
1135+
})
1136+
1137+
// Verify new rules files were created
1138+
expect(fs.mkdir).toHaveBeenCalledWith(expect.stringContaining("rules-test-mode"), { recursive: true })
1139+
1140+
// Verify file contents
1141+
const newRulePath = Object.keys(writtenFiles).find((p) => p.includes("new-rule.md"))
1142+
expect(writtenFiles[newRulePath!]).toBe("New rule content")
1143+
})
10371144
})
10381145
})
10391146

0 commit comments

Comments
 (0)