Skip to content

Commit 9fffe16

Browse files
committed
fix: Update global import to preserve rules file structure
- Modified importModeWithRules to treat global imports same as project-level - Rules files now created in global .roo/rules-{slug}/ folder - Updated all translation files to reflect new behavior - Fixes issue where rules were being squashed into custom instructions
1 parent c74bd6b commit 9fffe16

File tree

6 files changed

+44
-22
lines changed

6 files changed

+44
-22
lines changed

src/core/config/CustomModesManager.ts

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { type ModeConfig, customModesSettingsSchema, modeConfigSchema } from "@r
99

1010
import { fileExistsAtPath } from "../../utils/fs"
1111
import { getWorkspacePath } from "../../utils/path"
12+
import { getGlobalRooDirectory } from "../../services/roo-config"
1213
import { logger } from "../../utils/logging"
1314
import { GlobalFileNames } from "../../shared/globalFileNames"
1415
import { ensureSettingsDirectoryExists } from "../../utils/globalContext"
@@ -834,29 +835,50 @@ export class CustomModesManager {
834835
}
835836
}
836837
} else if (source === "global" && rulesFiles && Array.isArray(rulesFiles)) {
837-
// For global imports, we need to merge the rules content into the mode's customInstructions
838-
let mergedInstructions = modeConfig.customInstructions || ""
838+
// For global imports, preserve the rules files structure in the global .roo directory
839+
const globalRooDir = getGlobalRooDirectory()
839840

840-
// Add a separator if there are existing instructions
841-
if (mergedInstructions) {
842-
mergedInstructions += "\n\n"
841+
// Always remove the existing rules folder for this mode if it exists
842+
// This ensures that if the imported mode has no rules, the folder is cleaned up
843+
const rulesFolderPath = path.join(globalRooDir, `rules-${importMode.slug}`)
844+
try {
845+
await fs.rm(rulesFolderPath, { recursive: true, force: true })
846+
logger.info(`Removed existing global rules folder for mode ${importMode.slug}`)
847+
} catch (error) {
848+
// It's okay if the folder doesn't exist
849+
logger.debug(`No existing global rules folder to remove for mode ${importMode.slug}`)
843850
}
844851

845-
// Add the rules content
846-
mergedInstructions += "# Imported Rules\n\n"
847-
852+
// Import the new rules files with path validation
848853
for (const ruleFile of rulesFiles) {
849-
if (ruleFile.content) {
850-
mergedInstructions += `# Rules from ${ruleFile.relativePath}:\n${ruleFile.content}\n\n`
854+
if (ruleFile.relativePath && ruleFile.content) {
855+
// Validate the relative path to prevent path traversal attacks
856+
const normalizedRelativePath = path.normalize(ruleFile.relativePath)
857+
858+
// Ensure the path doesn't contain traversal sequences
859+
if (normalizedRelativePath.includes("..") || path.isAbsolute(normalizedRelativePath)) {
860+
logger.error(`Invalid file path detected: ${ruleFile.relativePath}`)
861+
continue // Skip this file but continue with others
862+
}
863+
864+
const targetPath = path.join(globalRooDir, normalizedRelativePath)
865+
const normalizedTargetPath = path.normalize(targetPath)
866+
const expectedBasePath = path.normalize(globalRooDir)
867+
868+
// Ensure the resolved path stays within the global .roo directory
869+
if (!normalizedTargetPath.startsWith(expectedBasePath)) {
870+
logger.error(`Path traversal attempt detected: ${ruleFile.relativePath}`)
871+
continue // Skip this file but continue with others
872+
}
873+
874+
// Ensure directory exists
875+
const targetDir = path.dirname(targetPath)
876+
await fs.mkdir(targetDir, { recursive: true })
877+
878+
// Write the file
879+
await fs.writeFile(targetPath, ruleFile.content, "utf-8")
851880
}
852881
}
853-
854-
// Update the mode with merged instructions
855-
await this.updateCustomMode(importMode.slug, {
856-
...modeConfig,
857-
source: source,
858-
customInstructions: mergedInstructions.trim(),
859-
})
860882
}
861883
}
862884

webview-ui/src/i18n/locales/de/prompts.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"importing": "Importiere...",
6565
"global": {
6666
"label": "Globale Ebene",
67-
"description": "Verfügbar in allen Projekten. Regeln werden in benutzerdefinierte Anweisungen zusammengeführt."
67+
"description": "Verfügbar in allen Projekten. Wenn der exportierte Modus Regeldateien enthielt, werden diese im globalen Ordner .roo/rules-{slug}/ neu erstellt."
6868
},
6969
"project": {
7070
"label": "Projektebene",

webview-ui/src/i18n/locales/en/prompts.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"importing": "Importing...",
6464
"global": {
6565
"label": "Global Level",
66-
"description": "Available across all projects. Rules will be merged into custom instructions."
66+
"description": "Available across all projects. If the exported mode contained rules files, they will be recreated in the global .roo/rules-{slug}/ folder."
6767
},
6868
"project": {
6969
"label": "Project Level",

webview-ui/src/i18n/locales/es/prompts.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"importing": "Importando...",
6565
"global": {
6666
"label": "Nivel global",
67-
"description": "Disponible en todos los proyectos. Las reglas se fusionarán con las instrucciones personalizadas."
67+
"description": "Disponible en todos los proyectos. Si el modo exportado contenía archivos de reglas, se volverán a crear en la carpeta global .roo/rules-{slug}/."
6868
},
6969
"project": {
7070
"label": "Nivel de proyecto",

webview-ui/src/i18n/locales/fr/prompts.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"importing": "Importation...",
6565
"global": {
6666
"label": "Niveau global",
67-
"description": "Disponible dans tous les projets. Les règles seront fusionnées dans les instructions personnalisées."
67+
"description": "Disponible dans tous les projets. Si le mode exporté contenait des fichiers de règles, ils seront recréés dans le dossier global .roo/rules-{slug}/."
6868
},
6969
"project": {
7070
"label": "Niveau projet",

webview-ui/src/i18n/locales/zh-CN/prompts.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"importing": "导入中...",
6565
"global": {
6666
"label": "全局",
67-
"description": "适用于所有项目。规则将合并到自定义指令中"
67+
"description": "适用于所有项目。如果导出的模式包含规则文件,则将在全局 .roo/rules-{slug}/ 文件夹中重新创建这些文件"
6868
},
6969
"project": {
7070
"label": "项目级",

0 commit comments

Comments
 (0)