From ac9393d72a5c36e16e015c184dde83877d397c74 Mon Sep 17 00:00:00 2001 From: "Yu SERIZAWA(@upamune)" Date: Mon, 7 Apr 2025 02:30:46 +0900 Subject: [PATCH 1/6] feat: enhance rule file loading with .roo/rules directory support - Introduced functions to safely read files and check for directory existence. - Added capability to read all text files from a specified directory in alphabetical order. - Updated `loadRuleFiles` to prioritize loading rules from a `.roo/rules/` directory, falling back to existing rule files if necessary. - Enhanced `addCustomInstructions` to support loading mode-specific rules from a `.roo/rules-{mode}/` directory, improving flexibility in rule management. This change improves the organization and retrieval of rule files, allowing for better modularity and maintainability. --- .../prompts/sections/custom-instructions.ts | 113 ++++++++++++++++-- 1 file changed, 104 insertions(+), 9 deletions(-) diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index f6b46764282..02795bb6c50 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -3,6 +3,9 @@ import path from "path" import { LANGUAGES, isLanguage } from "../../../shared/language" +/** + * Safely read a file and return its trimmed content + */ async function safeReadFile(filePath: string): Promise { try { const content = await fs.readFile(filePath, "utf-8") @@ -16,7 +19,81 @@ async function safeReadFile(filePath: string): Promise { } } +/** + * Check if a directory exists + */ +async function directoryExists(dirPath: string): Promise { + try { + const stats = await fs.stat(dirPath) + return stats.isDirectory() + } catch (err) { + return false + } +} + +/** + * Read all text files from a directory in alphabetical order + */ +async function readTextFilesFromDirectory(dirPath: string): Promise> { + try { + const files = await fs + .readdir(dirPath, { withFileTypes: true, recursive: true }) + .then((files) => files.filter((file) => file.isFile())) + .then((files) => files.map((file) => path.resolve(dirPath, file.name))) + + const fileContents = await Promise.all( + files.map(async (file) => { + const filePath = path.join(dirPath, file) + try { + // Check if it's a file (not a directory) + const stats = await fs.stat(filePath) + if (stats.isFile()) { + const content = await safeReadFile(filePath) + return { filename: file, content } + } + return null + } catch (err) { + return null + } + }), + ) + + // Filter out null values (directories or failed reads) + return fileContents.filter((item): item is { filename: string; content: string } => item !== null) + } catch (err) { + return [] + } +} + +/** + * Format content from multiple files with filenames as headers + */ +function formatDirectoryContent(dirPath: string, files: Array<{ filename: string; content: string }>): string { + if (files.length === 0) return "" + + const formattedContent = files + .map((file) => { + return `# Filename: ${file.filename}\n${file.content}` + }) + .join("\n\n") + + return `\n# Rules from ${path.relative(process.cwd(), dirPath)}:\n${formattedContent}\n` +} + +/** + * Load rule files from the specified directory + */ export async function loadRuleFiles(cwd: string): Promise { + // Check for .roo/rules/ directory + const rooRulesDir = path.join(cwd, ".roo", "rules") + if (await directoryExists(rooRulesDir)) { + const files = await readTextFilesFromDirectory(rooRulesDir) + if (files.length > 0) { + return formatDirectoryContent(rooRulesDir, files) + } + } + + // Fall back to existing behavior const ruleFiles = [".roorules", ".clinerules"] for (const file of ruleFiles) { @@ -41,16 +118,30 @@ export async function addCustomInstructions( // Load mode-specific rules if mode is provided let modeRuleContent = "" let usedRuleFile = "" + if (mode) { - const rooModeRuleFile = `.roorules-${mode}` - modeRuleContent = await safeReadFile(path.join(cwd, rooModeRuleFile)) - if (modeRuleContent) { - usedRuleFile = rooModeRuleFile - } else { - const clineModeRuleFile = `.clinerules-${mode}` - modeRuleContent = await safeReadFile(path.join(cwd, clineModeRuleFile)) + // Check for .roo/rules-${mode}/ directory + const modeRulesDir = path.join(cwd, ".roo", `rules-${mode}`) + if (await directoryExists(modeRulesDir)) { + const files = await readTextFilesFromDirectory(modeRulesDir) + if (files.length > 0) { + modeRuleContent = formatDirectoryContent(modeRulesDir, files) + usedRuleFile = modeRulesDir + } + } + + // If no directory exists, fall back to existing behavior + if (!modeRuleContent) { + const rooModeRuleFile = `.roorules-${mode}` + modeRuleContent = await safeReadFile(path.join(cwd, rooModeRuleFile)) if (modeRuleContent) { - usedRuleFile = clineModeRuleFile + usedRuleFile = rooModeRuleFile + } else { + const clineModeRuleFile = `.clinerules-${mode}` + modeRuleContent = await safeReadFile(path.join(cwd, clineModeRuleFile)) + if (modeRuleContent) { + usedRuleFile = clineModeRuleFile + } } } } @@ -78,7 +169,11 @@ export async function addCustomInstructions( // Add mode-specific rules first if they exist if (modeRuleContent && modeRuleContent.trim()) { - rules.push(`# Rules from ${usedRuleFile}:\n${modeRuleContent}`) + if (usedRuleFile.includes(path.join(".roo", `rules-${mode}`))) { + rules.push(modeRuleContent.trim()) + } else { + rules.push(`# Rules from ${usedRuleFile}:\n${modeRuleContent}`) + } } if (options.rooIgnoreInstructions) { From 7eec9ac1eab8bb06f9cb733184ef51968d240269 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 6 Apr 2025 17:40:11 -0400 Subject: [PATCH 2/6] Updated strings and translations --- .../src/components/prompts/PromptsView.tsx | 4 ++-- webview-ui/src/i18n/locales/ca/prompts.json | 6 ++--- webview-ui/src/i18n/locales/de/prompts.json | 6 ++--- webview-ui/src/i18n/locales/en/prompts.json | 4 ++-- webview-ui/src/i18n/locales/es/prompts.json | 6 ++--- webview-ui/src/i18n/locales/fr/prompts.json | 6 ++--- webview-ui/src/i18n/locales/hi/prompts.json | 6 ++--- webview-ui/src/i18n/locales/it/prompts.json | 6 ++--- webview-ui/src/i18n/locales/ja/prompts.json | 6 ++--- webview-ui/src/i18n/locales/ko/prompts.json | 6 ++--- webview-ui/src/i18n/locales/pl/prompts.json | 6 ++--- .../src/i18n/locales/pt-BR/prompts.json | 22 +++++++++---------- webview-ui/src/i18n/locales/tr/prompts.json | 6 ++--- webview-ui/src/i18n/locales/vi/prompts.json | 6 ++--- .../src/i18n/locales/zh-CN/prompts.json | 6 ++--- .../src/i18n/locales/zh-TW/prompts.json | 6 ++--- 16 files changed, 54 insertions(+), 54 deletions(-) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 24ba4ad67c0..95664e266f3 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -798,7 +798,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { // Open or create an empty file vscode.postMessage({ type: "openFile", - text: `./.roorules-${currentMode.slug}`, + text: `./.roo/rules-${currentMode.slug}/rules.md`, values: { create: true, content: "", @@ -935,7 +935,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { onClick={() => vscode.postMessage({ type: "openFile", - text: "./.roorules", + text: "./.roo/rules/rules.md", values: { create: true, content: "", diff --git a/webview-ui/src/i18n/locales/ca/prompts.json b/webview-ui/src/i18n/locales/ca/prompts.json index 656e4efe4ad..16b9d9daeff 100644 --- a/webview-ui/src/i18n/locales/ca/prompts.json +++ b/webview-ui/src/i18n/locales/ca/prompts.json @@ -36,12 +36,12 @@ "title": "Instruccions personalitzades específiques del mode (opcional)", "resetToDefault": "Restablir a valors predeterminats", "description": "Afegiu directrius de comportament específiques per al mode {{modeName}}.", - "loadFromFile": "Les instruccions personalitzades específiques per al mode {{mode}} també es poden carregar des de .roorules-{{slug}} al vostre espai de treball (.clinerules-{{slug}} està obsolet i deixarà de funcionar aviat)." + "loadFromFile": "Les instruccions personalitzades específiques per al mode {{mode}} també es poden carregar des de la carpeta .roo/rules-{{slug}}/ al vostre espai de treball (.roorules-{{slug}} i .clinerules-{{slug}} estan obsolets i deixaran de funcionar aviat)." }, "globalCustomInstructions": { "title": "Instruccions personalitzades per a tots els modes", "description": "Aquestes instruccions s'apliquen a tots els modes. Proporcionen un conjunt bàsic de comportaments que es poden millorar amb instruccions específiques de cada mode a continuació.\nSi voleu que Roo pensi i parli en un idioma diferent al de la visualització del vostre editor ({{language}}), podeu especificar-ho aquí.", - "loadFromFile": "Les instruccions també es poden carregar des de .roorules al vostre espai de treball (.clinerules està obsolet i deixarà de funcionar aviat)." + "loadFromFile": "Les instruccions també es poden carregar des de la carpeta .roo/rules/ al vostre espai de treball (.roorules i .clinerules estan obsolets i deixaran de funcionar aviat)." }, "systemPrompt": { "preview": "Previsualització del prompt del sistema", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avançat: Sobreescriure prompt del sistema", - "description": "Podeu reemplaçar completament el prompt del sistema per a aquest mode (a part de la definició de rol i instruccions personalitzades) creant un fitxer a .roo/system-prompt-{{slug}} al vostre espai de treball. Aquesta és una funcionalitat molt avançada que eludeix les salvaguardes integrades i les comprovacions de consistència (especialment al voltant de l'ús d'eines), així que aneu amb compte!" + "description": "Podeu reemplaçar completament el prompt del sistema per a aquest mode (a part de la definició de rol i instruccions personalitzades) creant un fitxer a la carpeta .roo/system-prompts/{{slug}}/ al vostre espai de treball. Aquesta és una funcionalitat molt avançada que eludeix les salvaguardes integrades i les comprovacions de consistència (especialment al voltant de l'ús d'eines), així que aneu amb compte!" }, "createModeDialog": { "title": "Crear nou mode", diff --git a/webview-ui/src/i18n/locales/de/prompts.json b/webview-ui/src/i18n/locales/de/prompts.json index af3b561e9bd..91b6720d519 100644 --- a/webview-ui/src/i18n/locales/de/prompts.json +++ b/webview-ui/src/i18n/locales/de/prompts.json @@ -36,12 +36,12 @@ "title": "Modusspezifische benutzerdefinierte Anweisungen (optional)", "resetToDefault": "Auf Standardwerte zurücksetzen", "description": "Fügen Sie verhaltensspezifische Richtlinien für den Modus {{modeName}} hinzu.", - "loadFromFile": "Benutzerdefinierte Anweisungen für den Modus {{mode}} können auch aus .roorules-{{slug}} in deinem Arbeitsbereich geladen werden (.clinerules-{{slug}} ist veraltet und wird bald nicht mehr funktionieren)." + "loadFromFile": "Benutzerdefinierte Anweisungen für den Modus {{mode}} können auch aus dem Ordner .roo/rules-{{slug}}/ in deinem Arbeitsbereich geladen werden (.roorules-{{slug}} und .clinerules-{{slug}} sind veraltet und werden bald nicht mehr funktionieren)." }, "globalCustomInstructions": { "title": "Benutzerdefinierte Anweisungen für alle Modi", "description": "Diese Anweisungen gelten für alle Modi. Sie bieten einen grundlegenden Satz von Verhaltensweisen, die durch modusspezifische Anweisungen unten erweitert werden können.\nWenn du möchtest, dass Roo in einer anderen Sprache als deiner Editor-Anzeigesprache ({{language}}) denkt und spricht, kannst du das hier angeben.", - "loadFromFile": "Anweisungen können auch aus .roorules in deinem Arbeitsbereich geladen werden (.clinerules ist veraltet und wird bald nicht mehr funktionieren)." + "loadFromFile": "Anweisungen können auch aus dem Ordner .roo/rules/ in deinem Arbeitsbereich geladen werden (.roorules und .clinerules sind veraltet und werden bald nicht mehr funktionieren)." }, "systemPrompt": { "preview": "System-Prompt Vorschau", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Erweitert: System-Prompt überschreiben", - "description": "Du kannst den System-Prompt für diesen Modus vollständig ersetzen (abgesehen von der Rollendefinition und benutzerdefinierten Anweisungen), indem du eine Datei unter .roo/system-prompt-{{slug}} in deinem Arbeitsbereich erstellst. Dies ist eine sehr fortgeschrittene Funktion, die eingebaute Schutzmaßnahmen und Konsistenzprüfungen umgeht (besonders bei der Werkzeugnutzung), also sei vorsichtig!" + "description": "Du kannst den System-Prompt für diesen Modus vollständig ersetzen (abgesehen von der Rollendefinition und benutzerdefinierten Anweisungen), indem du eine Datei im Ordner .roo/system-prompts/{{slug}}/ in deinem Arbeitsbereich erstellst. Dies ist eine sehr fortgeschrittene Funktion, die eingebaute Schutzmaßnahmen und Konsistenzprüfungen umgeht (besonders bei der Werkzeugnutzung), also sei vorsichtig!" }, "createModeDialog": { "title": "Neuen Modus erstellen", diff --git a/webview-ui/src/i18n/locales/en/prompts.json b/webview-ui/src/i18n/locales/en/prompts.json index 929b13a0bc3..ba836a7012e 100644 --- a/webview-ui/src/i18n/locales/en/prompts.json +++ b/webview-ui/src/i18n/locales/en/prompts.json @@ -36,12 +36,12 @@ "title": "Mode-specific Custom Instructions (optional)", "resetToDefault": "Reset to default", "description": "Add behavioral guidelines specific to {{modeName}} mode.", - "loadFromFile": "Custom instructions specific to {{mode}} mode can also be loaded from .roorules-{{slug}} in your workspace (.clinerules-{{slug}} is deprecated and will stop working soon)." + "loadFromFile": "Custom instructions specific to {{mode}} mode can also be loaded from the .roo/rules-{{slug}}/ folder in your workspace (.roorules-{{slug}} and .clinerules-{{slug}} are deprecated and will stop working soon)." }, "globalCustomInstructions": { "title": "Custom Instructions for All Modes", "description": "These instructions apply to all modes. They provide a base set of behaviors that can be enhanced by mode-specific instructions below.\nIf you would like Roo to think and speak in a different language than your editor display language ({{language}}), you can specify it here.", - "loadFromFile": "Instructions can also be loaded from .roorules in your workspace (.clinerules is deprecated and will stop working soon)." + "loadFromFile": "Instructions can also be loaded from the .roo/rules/ folder in your workspace (.roorules and .clinerules are deprecated and will stop working soon)." }, "systemPrompt": { "preview": "Preview System Prompt", diff --git a/webview-ui/src/i18n/locales/es/prompts.json b/webview-ui/src/i18n/locales/es/prompts.json index 09d48f3398e..2687fdcb47e 100644 --- a/webview-ui/src/i18n/locales/es/prompts.json +++ b/webview-ui/src/i18n/locales/es/prompts.json @@ -36,12 +36,12 @@ "title": "Instrucciones personalizadas para el modo (opcional)", "resetToDefault": "Restablecer a valores predeterminados", "description": "Agrega directrices de comportamiento específicas para el modo {{modeName}}.", - "loadFromFile": "Las instrucciones personalizadas para el modo {{mode}} también se pueden cargar desde .roorules-{{slug}} en tu espacio de trabajo (.clinerules-{{slug}} está obsoleto y dejará de funcionar pronto)." + "loadFromFile": "Las instrucciones personalizadas para el modo {{mode}} también se pueden cargar desde la carpeta .roo/rules-{{slug}}/ en tu espacio de trabajo (.roorules-{{slug}} y .clinerules-{{slug}} están obsoletos y dejarán de funcionar pronto)." }, "globalCustomInstructions": { "title": "Instrucciones personalizadas para todos los modos", "description": "Estas instrucciones se aplican a todos los modos. Proporcionan un conjunto base de comportamientos que pueden ser mejorados por instrucciones específicas de cada modo.\nSi quieres que Roo piense y hable en un idioma diferente al idioma de visualización de tu editor ({{language}}), puedes especificarlo aquí.", - "loadFromFile": "Las instrucciones también se pueden cargar desde .roorules en tu espacio de trabajo (.clinerules está obsoleto y dejará de funcionar pronto)." + "loadFromFile": "Las instrucciones también se pueden cargar desde la carpeta .roo/rules/ en tu espacio de trabajo (.roorules y .clinerules están obsoletos y dejarán de funcionar pronto)." }, "systemPrompt": { "preview": "Vista previa de la solicitud del sistema", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avanzado: Anular solicitud del sistema", - "description": "Puedes reemplazar completamente la solicitud del sistema para este modo (aparte de la definición de rol e instrucciones personalizadas) creando un archivo en .roo/system-prompt-{{slug}} en tu espacio de trabajo. ¡Esta es una función muy avanzada que omite las salvaguardas integradas y las verificaciones de consistencia (especialmente en torno al uso de herramientas), así que ten cuidado!" + "description": "Puedes reemplazar completamente la solicitud del sistema para este modo (aparte de la definición de rol e instrucciones personalizadas) creando un archivo en la carpeta .roo/system-prompts/{{slug}}/ en tu espacio de trabajo. ¡Esta es una función muy avanzada que omite las salvaguardas integradas y las verificaciones de consistencia (especialmente en torno al uso de herramientas), así que ten cuidado!" }, "createModeDialog": { "title": "Crear nuevo modo", diff --git a/webview-ui/src/i18n/locales/fr/prompts.json b/webview-ui/src/i18n/locales/fr/prompts.json index 5009294a8ba..8769fcb0d23 100644 --- a/webview-ui/src/i18n/locales/fr/prompts.json +++ b/webview-ui/src/i18n/locales/fr/prompts.json @@ -36,12 +36,12 @@ "title": "Instructions personnalisées spécifiques au mode (optionnel)", "resetToDefault": "Réinitialiser aux valeurs par défaut", "description": "Ajoutez des directives comportementales spécifiques au mode {{modeName}}.", - "loadFromFile": "Les instructions personnalisées spécifiques au mode {{mode}} peuvent également être chargées depuis .roorules-{{slug}} dans votre espace de travail (.clinerules-{{slug}} est obsolète et cessera de fonctionner bientôt)." + "loadFromFile": "Les instructions personnalisées spécifiques au mode {{mode}} peuvent également être chargées depuis le dossier .roo/rules-{{slug}}/ dans votre espace de travail (.roorules-{{slug}} et .clinerules-{{slug}} sont obsolètes et cesseront de fonctionner bientôt)." }, "globalCustomInstructions": { "title": "Instructions personnalisées pour tous les modes", "description": "Ces instructions s'appliquent à tous les modes. Elles fournissent un ensemble de comportements de base qui peuvent être améliorés par des instructions spécifiques au mode ci-dessous.\nSi vous souhaitez que Roo pense et parle dans une langue différente de celle de votre éditeur ({{language}}), vous pouvez le spécifier ici.", - "loadFromFile": "Les instructions peuvent également être chargées depuis .roorules dans votre espace de travail (.clinerules est obsolète et cessera de fonctionner bientôt)." + "loadFromFile": "Les instructions peuvent également être chargées depuis le dossier .roo/rules/ dans votre espace de travail (.roorules et .clinerules sont obsolètes et cesseront de fonctionner bientôt)." }, "systemPrompt": { "preview": "Aperçu du prompt système", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avancé : Remplacer le prompt système", - "description": "Vous pouvez complètement remplacer le prompt système pour ce mode (en dehors de la définition du rôle et des instructions personnalisées) en créant un fichier à .roo/system-prompt-{{slug}} dans votre espace de travail. Il s'agit d'une fonctionnalité très avancée qui contourne les garanties intégrées et les vérifications de cohérence (notamment concernant l'utilisation des outils), alors soyez prudent !" + "description": "Vous pouvez complètement remplacer le prompt système pour ce mode (en dehors de la définition du rôle et des instructions personnalisées) en créant un fichier dans le dossier .roo/system-prompts/{{slug}}/ dans votre espace de travail. Il s'agit d'une fonctionnalité très avancée qui contourne les garanties intégrées et les vérifications de cohérence (notamment concernant l'utilisation des outils), alors soyez prudent !" }, "createModeDialog": { "title": "Créer un nouveau mode", diff --git a/webview-ui/src/i18n/locales/hi/prompts.json b/webview-ui/src/i18n/locales/hi/prompts.json index 8f5c6bee80a..00c4a730702 100644 --- a/webview-ui/src/i18n/locales/hi/prompts.json +++ b/webview-ui/src/i18n/locales/hi/prompts.json @@ -36,12 +36,12 @@ "title": "मोड-विशिष्ट कस्टम निर्देश (वैकल्पिक)", "resetToDefault": "डिफ़ॉल्ट पर रीसेट करें", "description": "{{modeName}} मोड के लिए विशिष्ट व्यवहार दिशानिर्देश जोड़ें।", - "loadFromFile": "{{mode}} मोड के लिए विशिष्ट कस्टम निर्देश आपके वर्कस्पेस में .roorules-{{slug}} से भी लोड किए जा सकते हैं (.clinerules-{{slug}} पुराना हो गया है और जल्द ही काम करना बंद कर देगा)।" + "loadFromFile": "{{mode}} मोड के लिए विशिष्ट कस्टम निर्देश आपके वर्कस्पेस में .roo/rules-{{slug}}/ फ़ोल्डर से भी लोड किए जा सकते हैं (.roorules-{{slug}} और .clinerules-{{slug}} पुराने हो गए हैं और जल्द ही काम करना बंद कर देंगे)।" }, "globalCustomInstructions": { "title": "सभी मोड्स के लिए कस्टम निर्देश", "description": "ये निर्देश सभी मोड्स पर लागू होते हैं। वे व्यवहारों का एक आधार सेट प्रदान करते हैं जिन्हें नीचे दिए गए मोड-विशिष्ट निर्देशों द्वारा बढ़ाया जा सकता है।\nयदि आप चाहते हैं कि Roo आपके एडिटर की प्रदर्शन भाषा ({{language}}) से अलग भाषा में सोचे और बोले, तो आप यहां इसे निर्दिष्ट कर सकते हैं।", - "loadFromFile": "निर्देश आपके वर्कस्पेस में .roorules से भी लोड किए जा सकते हैं (.clinerules पुराना हो गया है और जल्द ही काम करना बंद कर देगा)।" + "loadFromFile": "निर्देश आपके वर्कस्पेस में .roo/rules/ फ़ोल्डर से भी लोड किए जा सकते हैं (.roorules और .clinerules पुराने हो गए हैं और जल्द ही काम करना बंद कर देंगे)।" }, "systemPrompt": { "preview": "सिस्टम प्रॉम्प्ट का पूर्वावलोकन", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "उन्नत: सिस्टम प्रॉम्प्ट ओवरराइड करें", - "description": "आप अपने वर्कस्पेस में .roo/system-prompt-{{slug}} पर एक फाइल बनाकर इस मोड के लिए सिस्टम प्रॉम्प्ट को पूरी तरह से बदल सकते हैं (भूमिका परिभाषा और कस्टम निर्देशों को छोड़कर)। यह एक बहुत उन्नत सुविधा है जो अंतर्निहित सुरक्षा उपायों और सामंजस्यता जांचों को बायपास करती है (विशेष रूप से टूल उपयोग के आसपास), इसलिए सावधान रहें!" + "description": "आप अपने वर्कस्पेस में .roo/system-prompts/{{slug}}/ फ़ोल्डर में एक फाइल बनाकर इस मोड के लिए सिस्टम प्रॉम्प्ट को पूरी तरह से बदल सकते हैं (भूमिका परिभाषा और कस्टम निर्देशों को छोड़कर)। यह एक बहुत उन्नत सुविधा है जो अंतर्निहित सुरक्षा उपायों और सामंजस्यता जांचों को बायपास करती है (विशेष रूप से टूल उपयोग के आसपास), इसलिए सावधान रहें!" }, "createModeDialog": { "title": "नया मोड बनाएँ", diff --git a/webview-ui/src/i18n/locales/it/prompts.json b/webview-ui/src/i18n/locales/it/prompts.json index dacce0eb782..06bb2d9034e 100644 --- a/webview-ui/src/i18n/locales/it/prompts.json +++ b/webview-ui/src/i18n/locales/it/prompts.json @@ -36,12 +36,12 @@ "title": "Istruzioni personalizzate specifiche per la modalità (opzionale)", "resetToDefault": "Ripristina predefiniti", "description": "Aggiungi linee guida comportamentali specifiche per la modalità {{modeName}}.", - "loadFromFile": "Le istruzioni personalizzate specifiche per la modalità {{mode}} possono essere caricate anche da .roorules-{{slug}} nel tuo spazio di lavoro (.clinerules-{{slug}} è obsoleto e smetterà di funzionare presto)." + "loadFromFile": "Le istruzioni personalizzate specifiche per la modalità {{mode}} possono essere caricate anche dalla cartella .roo/rules-{{slug}}/ nel tuo spazio di lavoro (.roorules-{{slug}} e .clinerules-{{slug}} sono obsoleti e smetteranno di funzionare presto)." }, "globalCustomInstructions": { "title": "Istruzioni personalizzate per tutte le modalità", "description": "Queste istruzioni si applicano a tutte le modalità. Forniscono un insieme base di comportamenti che possono essere migliorati dalle istruzioni specifiche per modalità qui sotto.\nSe desideri che Roo pensi e parli in una lingua diversa dalla lingua di visualizzazione del tuo editor ({{language}}), puoi specificarlo qui.", - "loadFromFile": "Le istruzioni possono essere caricate anche da .roorules nel tuo spazio di lavoro (.clinerules è obsoleto e smetterà di funzionare presto)." + "loadFromFile": "Le istruzioni possono essere caricate anche dalla cartella .roo/rules/ nel tuo spazio di lavoro (.roorules e .clinerules sono obsoleti e smetteranno di funzionare presto)." }, "systemPrompt": { "preview": "Anteprima prompt di sistema", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avanzato: Sovrascrivi prompt di sistema", - "description": "Puoi sostituire completamente il prompt di sistema per questa modalità (a parte la definizione del ruolo e le istruzioni personalizzate) creando un file in .roo/system-prompt-{{slug}} nel tuo spazio di lavoro. Questa è una funzionalità molto avanzata che bypassa le protezioni integrate e i controlli di coerenza (specialmente riguardo all'uso degli strumenti), quindi fai attenzione!" + "description": "Puoi sostituire completamente il prompt di sistema per questa modalità (a parte la definizione del ruolo e le istruzioni personalizzate) creando un file nella cartella .roo/system-prompts/{{slug}}/ nel tuo spazio di lavoro. Questa è una funzionalità molto avanzata che bypassa le protezioni integrate e i controlli di coerenza (specialmente riguardo all'uso degli strumenti), quindi fai attenzione!" }, "createModeDialog": { "title": "Crea nuova modalità", diff --git a/webview-ui/src/i18n/locales/ja/prompts.json b/webview-ui/src/i18n/locales/ja/prompts.json index 7c0d76e33ad..5e90e19c045 100644 --- a/webview-ui/src/i18n/locales/ja/prompts.json +++ b/webview-ui/src/i18n/locales/ja/prompts.json @@ -36,12 +36,12 @@ "title": "モード固有のカスタム指示(オプション)", "resetToDefault": "デフォルトにリセット", "description": "{{modeName}}モードに特化した行動ガイドラインを追加します。", - "loadFromFile": "{{mode}}モード固有のカスタム指示は、ワークスペースの.roorules-{{slug}}からも読み込めます(.clinerules-{{slug}}は非推奨であり、まもなく機能しなくなります)。" + "loadFromFile": "{{mode}}モード固有のカスタム指示は、ワークスペースの.roo/rules-{{slug}}/フォルダからも読み込めます(.roorules-{{slug}}と.clinerules-{{slug}}は非推奨であり、まもなく機能しなくなります)。" }, "globalCustomInstructions": { "title": "すべてのモードのカスタム指示", "description": "これらの指示はすべてのモードに適用されます。モード固有の指示で強化できる基本的な動作セットを提供します。\nRooにエディタの表示言語({{language}})とは異なる言語で考えたり話したりさせたい場合は、ここで指定できます。", - "loadFromFile": "指示はワークスペースの.roorulesからも読み込めます(.clinerules は非推奨であり、まもなく機能しなくなります)。" + "loadFromFile": "指示はワークスペースの.roo/rules/フォルダからも読み込めます(.roorules と .clinerules は非推奨であり、まもなく機能しなくなります)。" }, "systemPrompt": { "preview": "システムプロンプトのプレビュー", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "詳細設定:システムプロンプトの上書き", - "description": "ワークスペースの.roo/system-prompt-{{slug}}にファイルを作成することで、このモードのシステムプロンプト(役割定義とカスタム指示以外)を完全に置き換えることができます。これは組み込みの安全対策と一貫性チェック(特にツールの使用に関して)をバイパスする非常に高度な機能なので、注意して使用してください!" + "description": "ワークスペースの.roo/system-prompts/{{slug}}/フォルダにファイルを作成することで、このモードのシステムプロンプト(役割定義とカスタム指示以外)を完全に置き換えることができます。これは組み込みの安全対策と一貫性チェック(特にツールの使用に関して)をバイパスする非常に高度な機能なので、注意して使用してください!" }, "createModeDialog": { "title": "新しいモードを作成", diff --git a/webview-ui/src/i18n/locales/ko/prompts.json b/webview-ui/src/i18n/locales/ko/prompts.json index 10593364ddc..ba119734c8b 100644 --- a/webview-ui/src/i18n/locales/ko/prompts.json +++ b/webview-ui/src/i18n/locales/ko/prompts.json @@ -36,12 +36,12 @@ "title": "모드별 사용자 지정 지침 (선택 사항)", "resetToDefault": "기본값으로 재설정", "description": "{{modeName}} 모드에 대한 특정 행동 지침을 추가하세요.", - "loadFromFile": "{{mode}} 모드에 대한 사용자 지정 지침은 작업 공간의 .roorules-{{slug}}에서도 로드할 수 있습니다(.clinerules-{{slug}}는 더 이상 사용되지 않으며 곧 작동을 중단합니다)." + "loadFromFile": "{{mode}} 모드에 대한 사용자 지정 지침은 작업 공간의 .roo/rules-{{slug}}/ 폴더에서도 로드할 수 있습니다(.roorules-{{slug}}와 .clinerules-{{slug}}는 더 이상 사용되지 않으며 곧 작동을 중단합니다)." }, "globalCustomInstructions": { "title": "모든 모드에 대한 사용자 지정 지침", "description": "이 지침은 모든 모드에 적용됩니다. 아래의 모드별 지침으로 향상될 수 있는 기본 동작 세트를 제공합니다.\nRoo가 에디터 표시 언어({{language}})와 다른 언어로 생각하고 말하기를 원하시면, 여기에 지정할 수 있습니다.", - "loadFromFile": "지침은 작업 공간의 .roorules에서도 로드할 수 있습니다(.clinerules는 더 이상 사용되지 않으며 곧 작동을 중단합니다)." + "loadFromFile": "지침은 작업 공간의 .roo/rules/ 폴더에서도 로드할 수 있습니다(.roorules와 .clinerules는 더 이상 사용되지 않으며 곧 작동을 중단합니다)." }, "systemPrompt": { "preview": "시스템 프롬프트 미리보기", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "고급: 시스템 프롬프트 재정의", - "description": "작업 공간의 .roo/system-prompt-{{slug}}에 파일을 생성하여 이 모드의 시스템 프롬프트(역할 정의 및 사용자 지정 지침 제외)를 완전히 대체할 수 있습니다. 이는 내장된 안전 장치와 일관성 검사(특히 도구 사용 관련)를 우회하는 매우 고급 기능이므로 주의하세요!" + "description": "작업 공간의 .roo/system-prompts/{{slug}}/ 폴더에 파일을 생성하여 이 모드의 시스템 프롬프트(역할 정의 및 사용자 지정 지침 제외)를 완전히 대체할 수 있습니다. 이는 내장된 안전 장치와 일관성 검사(특히 도구 사용 관련)를 우회하는 매우 고급 기능이므로 주의하세요!" }, "createModeDialog": { "title": "새 모드 만들기", diff --git a/webview-ui/src/i18n/locales/pl/prompts.json b/webview-ui/src/i18n/locales/pl/prompts.json index 5491207f8a1..657af3de335 100644 --- a/webview-ui/src/i18n/locales/pl/prompts.json +++ b/webview-ui/src/i18n/locales/pl/prompts.json @@ -36,12 +36,12 @@ "title": "Niestandardowe instrukcje dla trybu (opcjonalne)", "resetToDefault": "Przywróć domyślne", "description": "Dodaj wytyczne dotyczące zachowania specyficzne dla trybu {{modeName}}.", - "loadFromFile": "Niestandardowe instrukcje dla trybu {{modeName}} mogą być również ładowane z .roorules-{{modeSlug}} w Twoim obszarze roboczym (.clinerules-{{modeSlug}} jest przestarzały i wkrótce przestanie działać)." + "loadFromFile": "Niestandardowe instrukcje dla trybu {{modeName}} mogą być również ładowane z folderu .roo/rules-{{modeSlug}}/ w Twoim obszarze roboczym (.roorules-{{modeSlug}} i .clinerules-{{modeSlug}} są przestarzałe i wkrótce przestaną działać)." }, "globalCustomInstructions": { "title": "Niestandardowe instrukcje dla wszystkich trybów", "description": "Te instrukcje dotyczą wszystkich trybów. Zapewniają podstawowy zestaw zachowań, które mogą być rozszerzone przez instrukcje specyficzne dla trybów poniżej.\nJeśli chcesz, aby Roo myślał i mówił w języku innym niż język wyświetlania Twojego edytora ({{language}}), możesz to określić tutaj.", - "loadFromFile": "Instrukcje mogą być również ładowane z .roorules w Twoim obszarze roboczym (.clinerules jest przestarzały i wkrótce przestanie działać)." + "loadFromFile": "Instrukcje mogą być również ładowane z folderu .roo/rules/ w Twoim obszarze roboczym (.roorules i .clinerules są przestarzałe i wkrótce przestaną działać)." }, "systemPrompt": { "preview": "Podgląd podpowiedzi systemowej", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Zaawansowane: Zastąp podpowiedź systemową", - "description": "Możesz całkowicie zastąpić podpowiedź systemową dla tego trybu (oprócz definicji roli i niestandardowych instrukcji) poprzez utworzenie pliku w .roo/system-prompt-{{modeSlug}} w swoim obszarze roboczym. Jest to bardzo zaawansowana funkcja, która omija wbudowane zabezpieczenia i kontrole spójności (szczególnie wokół używania narzędzi), więc bądź ostrożny!" + "description": "Możesz całkowicie zastąpić podpowiedź systemową dla tego trybu (oprócz definicji roli i niestandardowych instrukcji) poprzez utworzenie pliku w folderze .roo/system-prompts/{{modeSlug}}/ w swoim obszarze roboczym. Jest to bardzo zaawansowana funkcja, która omija wbudowane zabezpieczenia i kontrole spójności (szczególnie wokół używania narzędzi), więc bądź ostrożny!" }, "createModeDialog": { "title": "Utwórz nowy tryb", diff --git a/webview-ui/src/i18n/locales/pt-BR/prompts.json b/webview-ui/src/i18n/locales/pt-BR/prompts.json index 1c2ec0fe86c..dc49bec31c1 100644 --- a/webview-ui/src/i18n/locales/pt-BR/prompts.json +++ b/webview-ui/src/i18n/locales/pt-BR/prompts.json @@ -36,12 +36,12 @@ "title": "Instruções personalizadas específicas do modo (opcional)", "resetToDefault": "Restaurar para padrão", "description": "Adicione diretrizes comportamentais específicas para o modo {{modeName}}.", - "loadFromFile": "Instruções personalizadas específicas para o modo {{modeName}} também podem ser carregadas de .roorules-{{modeSlug}} no seu espaço de trabalho (.clinerules-{{modeSlug}} está obsoleto e deixará de funcionar em breve)." + "loadFromFile": "Instruções personalizadas específicas para o modo {{modeName}} também podem ser carregadas da pasta .roo/rules-{{modeSlug}}/ no seu espaço de trabalho (.roorules-{{modeSlug}} e .clinerules-{{modeSlug}} estão obsoletos e deixarão de funcionar em breve)." }, "globalCustomInstructions": { "title": "Instruções personalizadas para todos os modos", "description": "Estas instruções se aplicam a todos os modos. Elas fornecem um conjunto base de comportamentos que podem ser aprimorados por instruções específicas do modo abaixo.\nSe você desejar que o Roo pense e fale em um idioma diferente do idioma de exibição do seu editor ({{language}}), você pode especificá-lo aqui.", - "loadFromFile": "As instruções também podem ser carregadas de .roorules no seu espaço de trabalho (.clinerules está obsoleto e deixará de funcionar em breve)." + "loadFromFile": "As instruções também podem ser carregadas da pasta .roo/rules/ no seu espaço de trabalho (.roorules e .clinerules estão obsoletos e deixarão de funcionar em breve)." }, "systemPrompt": { "preview": "Visualizar prompt do sistema", @@ -62,35 +62,35 @@ "types": { "ENHANCE": { "label": "Aprimorar Prompt", - "description": "Use prompt enhancement to get tailored suggestions or improvements for your inputs. This ensures Roo understands your intent and provides the best possible responses. Available via the ✨ icon in chat." + "description": "Use o aprimoramento de prompt para obter sugestões ou melhorias personalizadas para suas entradas. Isso garante que o Roo entenda sua intenção e forneça as melhores respostas possíveis. Disponível através do ícone ✨ no chat." }, "EXPLAIN": { "label": "Explicar Código", - "description": "Obtenha explicações detalhadas de trechos de código, funções ou arquivos inteiros. Useful for understanding complex code or learning new patterns. Available in code actions (lightbulb icon in the editor) and the editor context menu (right-click on selected code)." + "description": "Obtenha explicações detalhadas de trechos de código, funções ou arquivos inteiros. Útil para entender código complexo ou aprender novos padrões. Disponível nas ações de código (ícone de lâmpada no editor) e no menu de contexto do editor (clique direito no código selecionado)." }, "FIX": { "label": "Corrigir Problemas", - "description": "Obtenha ajuda para identificar e resolver bugs, erros ou code quality issues. Provides step-by-step guidance for fixing problems. Available in code actions (lightbulb icon in the editor) and the editor context menu (right-click on selected code)." + "description": "Obtenha ajuda para identificar e resolver bugs, erros ou problemas de qualidade de código. Fornece orientação passo a passo para corrigir problemas. Disponível nas ações de código (ícone de lâmpada no editor) e no menu de contexto do editor (clique direito no código selecionado)." }, "IMPROVE": { "label": "Melhorar Código", - "description": "Receba sugestões para code optimization, better practices e architectural improvements while maintaining functionality. Available in code actions (lightbulb icon in the editor) and the editor context menu (right-click on selected code)." + "description": "Receba sugestões para otimização de código, melhores práticas e melhorias arquitetônicas mantendo a funcionalidade. Disponível nas ações de código (ícone de lâmpada no editor) e no menu de contexto do editor (clique direito no código selecionado)." }, "ADD_TO_CONTEXT": { "label": "Adicionar ao Contexto", - "description": "Adicione contexto à sua tarefa ou conversa atual. Useful for providing additional information or clarifications. Available in code actions (lightbulb icon in the editor) and the editor context menu (right-click on selected code)." + "description": "Adicione contexto à sua tarefa ou conversa atual. Útil para fornecer informações adicionais ou esclarecimentos. Disponível nas ações de código (ícone de lâmpada no editor) e no menu de contexto do editor (clique direito no código selecionado)." }, "TERMINAL_ADD_TO_CONTEXT": { "label": "Adicionar Conteúdo do Terminal ao Contexto", - "description": "Adicione a saída do terminal à sua tarefa ou conversa atual. Useful for providing command outputs or logs. Available in the terminal context menu (right-click on selected terminal content)." + "description": "Adicione a saída do terminal à sua tarefa ou conversa atual. Útil para fornecer saídas de comandos ou logs. Disponível no menu de contexto do terminal (clique direito no conteúdo selecionado do terminal)." }, "TERMINAL_FIX": { "label": "Corrigir Comando do Terminal", - "description": "Obtenha ajuda para corrigir comandos de terminal que falharam ou precisam de melhorias. Available in the terminal context menu (right-click on selected terminal content)." + "description": "Obtenha ajuda para corrigir comandos de terminal que falharam ou precisam de melhorias. Disponível no menu de contexto do terminal (clique direito no conteúdo selecionado do terminal)." }, "TERMINAL_EXPLAIN": { "label": "Explicar Comando do Terminal", - "description": "Obtenha explicações detalhadas de comandos de terminal e suas saídas. Available in the terminal context menu (right-click on selected terminal content)." + "description": "Obtenha explicações detalhadas de comandos de terminal e suas saídas. Disponível no menu de contexto do terminal (clique direito no conteúdo selecionado do terminal)." }, "NEW_TASK": { "label": "Iniciar Nova Tarefa", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avançado: Substituir prompt do sistema", - "description": "Você pode substituir completamente o prompt do sistema para este modo (além da definição de função e instruções personalizadas) criando um arquivo em .roo/system-prompt-{{modeSlug}} no seu espaço de trabalho. Esta é uma funcionalidade muito avançada que contorna as salvaguardas integradas e verificações de consistência (especialmente em torno do uso de ferramentas), então tenha cuidado!" + "description": "Você pode substituir completamente o prompt do sistema para este modo (além da definição de função e instruções personalizadas) criando um arquivo na pasta .roo/system-prompts/{{modeSlug}}/ no seu espaço de trabalho. Esta é uma funcionalidade muito avançada que contorna as salvaguardas integradas e verificações de consistência (especialmente em torno do uso de ferramentas), então tenha cuidado!" }, "createModeDialog": { "title": "Criar novo modo", diff --git a/webview-ui/src/i18n/locales/tr/prompts.json b/webview-ui/src/i18n/locales/tr/prompts.json index 7525d284d12..45db7ffe8fe 100644 --- a/webview-ui/src/i18n/locales/tr/prompts.json +++ b/webview-ui/src/i18n/locales/tr/prompts.json @@ -36,12 +36,12 @@ "title": "Moda özgü özel talimatlar (isteğe bağlı)", "resetToDefault": "Varsayılana sıfırla", "description": "{{modeName}} modu için özel davranış yönergeleri ekleyin.", - "loadFromFile": "{{mode}} moduna özgü özel talimatlar ayrıca çalışma alanınızdaki .roorules-{{slug}} adresinden yüklenebilir (.clinerules-{{slug}} kullanımdan kaldırılmıştır ve yakında çalışmayı durduracaktır)." + "loadFromFile": "{{mode}} moduna özgü özel talimatlar ayrıca çalışma alanınızdaki .roo/rules-{{slug}}/ klasöründen yüklenebilir (.roorules-{{slug}} ve .clinerules-{{slug}} kullanımdan kaldırılmıştır ve yakında çalışmayı durduracaklardır)." }, "globalCustomInstructions": { "title": "Tüm Modlar için Özel Talimatlar", "description": "Bu talimatlar tüm modlara uygulanır. Aşağıdaki moda özgü talimatlarla geliştirilebilen temel davranış seti sağlarlar.\nRoo'nun editörünüzün görüntüleme dilinden ({{language}}) farklı bir dilde düşünmesini ve konuşmasını istiyorsanız, burada belirtebilirsiniz.", - "loadFromFile": "Talimatlar ayrıca çalışma alanınızdaki .roorules adresinden de yüklenebilir (.clinerules kullanımdan kaldırılmıştır ve yakında çalışmayı durduracaktır)." + "loadFromFile": "Talimatlar ayrıca çalışma alanınızdaki .roo/rules/ klasöründen de yüklenebilir (.roorules ve .clinerules kullanımdan kaldırılmıştır ve yakında çalışmayı durduracaklardır)." }, "systemPrompt": { "preview": "Sistem promptunu önizle", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Gelişmiş: Sistem Promptunu Geçersiz Kıl", - "description": "Çalışma alanınızda .roo/system-prompt-{{slug}} adresinde bir dosya oluşturarak bu mod için sistem istemini tamamen değiştirebilirsiniz (rol tanımı ve özel talimatlar hariç). Bu, yerleşik güvenlik önlemlerini ve tutarlılık kontrollerini (özellikle araç kullanımıyla ilgili) aşan çok gelişmiş bir özelliktir, bu yüzden dikkatli olun!" + "description": "Çalışma alanınızda .roo/system-prompts/{{slug}}/ klasöründe bir dosya oluşturarak bu mod için sistem istemini tamamen değiştirebilirsiniz (rol tanımı ve özel talimatlar hariç). Bu, yerleşik güvenlik önlemlerini ve tutarlılık kontrollerini (özellikle araç kullanımıyla ilgili) aşan çok gelişmiş bir özelliktir, bu yüzden dikkatli olun!" }, "createModeDialog": { "title": "Yeni Mod Oluştur", diff --git a/webview-ui/src/i18n/locales/vi/prompts.json b/webview-ui/src/i18n/locales/vi/prompts.json index eafbff63dbf..ded3b7a5749 100644 --- a/webview-ui/src/i18n/locales/vi/prompts.json +++ b/webview-ui/src/i18n/locales/vi/prompts.json @@ -36,12 +36,12 @@ "title": "Hướng dẫn tùy chỉnh dành riêng cho chế độ (tùy chọn)", "resetToDefault": "Đặt lại về mặc định", "description": "Thêm hướng dẫn hành vi dành riêng cho chế độ {{modeName}}.", - "loadFromFile": "Hướng dẫn tùy chỉnh dành riêng cho chế độ {{modeName}} cũng có thể được tải từ .roorules-{{modeSlug}} trong không gian làm việc của bạn (.clinerules-{{modeSlug}} đã lỗi thời và sẽ sớm ngừng hoạt động)." + "loadFromFile": "Hướng dẫn tùy chỉnh dành riêng cho chế độ {{modeName}} cũng có thể được tải từ thư mục .roo/rules-{{modeSlug}}/ trong không gian làm việc của bạn (.roorules-{{modeSlug}} và .clinerules-{{modeSlug}} đã lỗi thời và sẽ sớm ngừng hoạt động)." }, "globalCustomInstructions": { "title": "Hướng dẫn tùy chỉnh cho tất cả các chế độ", "description": "Những hướng dẫn này áp dụng cho tất cả các chế độ. Chúng cung cấp một bộ hành vi cơ bản có thể được nâng cao bởi hướng dẫn dành riêng cho chế độ bên dưới.\nNếu bạn muốn Roo suy nghĩ và nói bằng ngôn ngữ khác với ngôn ngữ hiển thị trình soạn thảo của bạn ({{language}}), bạn có thể chỉ định ở đây.", - "loadFromFile": "Hướng dẫn cũng có thể được tải từ .roorules trong không gian làm việc của bạn (.clinerules đã lỗi thời và sẽ sớm ngừng hoạt động)." + "loadFromFile": "Hướng dẫn cũng có thể được tải từ thư mục .roo/rules/ trong không gian làm việc của bạn (.roorules và .clinerules đã lỗi thời và sẽ sớm ngừng hoạt động)." }, "systemPrompt": { "preview": "Xem trước lời nhắc hệ thống", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Nâng cao: Ghi đè lời nhắc hệ thống", - "description": "Bạn có thể hoàn toàn thay thế lời nhắc hệ thống cho chế độ này (ngoài định nghĩa vai trò và hướng dẫn tùy chỉnh) bằng cách tạo một tệp tại .roo/system-prompt-{{modeSlug}} trong không gian làm việc của bạn. Đây là một tính năng rất nâng cao bỏ qua các biện pháp bảo vệ và kiểm tra nhất quán tích hợp sẵn (đặc biệt là xung quanh việc sử dụng công cụ), vì vậy hãy cẩn thận!" + "description": "Bạn có thể hoàn toàn thay thế lời nhắc hệ thống cho chế độ này (ngoài định nghĩa vai trò và hướng dẫn tùy chỉnh) bằng cách tạo một tệp trong thư mục .roo/system-prompts/{{modeSlug}}/ trong không gian làm việc của bạn. Đây là một tính năng rất nâng cao bỏ qua các biện pháp bảo vệ và kiểm tra nhất quán tích hợp sẵn (đặc biệt là xung quanh việc sử dụng công cụ), vì vậy hãy cẩn thận!" }, "createModeDialog": { "title": "Tạo chế độ mới", diff --git a/webview-ui/src/i18n/locales/zh-CN/prompts.json b/webview-ui/src/i18n/locales/zh-CN/prompts.json index aa3b00c3ffd..2a5f99579b4 100644 --- a/webview-ui/src/i18n/locales/zh-CN/prompts.json +++ b/webview-ui/src/i18n/locales/zh-CN/prompts.json @@ -36,12 +36,12 @@ "title": "模式专属规则(可选)", "resetToDefault": "重置为默认值", "description": "{{modeName}}模式的专属规则", - "loadFromFile": "支持从.roorules-{{slug}}文件读取配置(.clinerules-{{slug}}已弃用并将很快停止工作)。" + "loadFromFile": "支持从.roo/rules-{{slug}}/目录读取配置(.roorules-{{slug}}和.clinerules-{{slug}}已弃用并将很快停止工作)。" }, "globalCustomInstructions": { "title": "所有模式的自定义指令", "description": "所有模式通用规则\n当前语言:{{language}}", - "loadFromFile": "支持从.roorules文件读取全局配置(.clinerules已弃用并将很快停止工作)。" + "loadFromFile": "支持从.roo/rules/目录读取全局配置(.roorules和.clinerules已弃用并将很快停止工作)。" }, "systemPrompt": { "preview": "预览系统提示词", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "高级:覆盖系统提示词", - "description": "您可以通过在工作区创建文件 .roo/system-prompt-{{slug}},完全替换此模式的系统提示(角色定义和自定义指令除外)。这是一个非常高级的功能,会绕过内置的安全措施和一致性检查(尤其是与工具使用相关的部分),请谨慎操作!" + "description": "您可以通过在工作区.roo/system-prompts/{{slug}}/目录中创建文件,完全替换此模式的系统提示(角色定义和自定义指令除外)。这是一个非常高级的功能,会绕过内置的安全措施和一致性检查(尤其是与工具使用相关的部分),请谨慎操作!" }, "createModeDialog": { "title": "创建新模式", diff --git a/webview-ui/src/i18n/locales/zh-TW/prompts.json b/webview-ui/src/i18n/locales/zh-TW/prompts.json index 2218b22466c..e24f6b6eb8a 100644 --- a/webview-ui/src/i18n/locales/zh-TW/prompts.json +++ b/webview-ui/src/i18n/locales/zh-TW/prompts.json @@ -36,12 +36,12 @@ "title": "模式專屬自訂指令(選用)", "resetToDefault": "重設為預設值", "description": "為 {{modeName}} 模式新增專屬的行為指南。", - "loadFromFile": "{{mode}} 模式的自訂指令也可以從工作區的 .roorules-{{slug}} 載入(.clinerules-{{slug}} 已棄用並將很快停止運作)。" + "loadFromFile": "{{mode}} 模式的自訂指令也可以從工作區的 .roo/rules-{{slug}}/ 資料夾載入(.roorules-{{slug}} 和 .clinerules-{{slug}} 已棄用並將很快停止運作)。" }, "globalCustomInstructions": { "title": "所有模式的自訂指令", "description": "這些指令適用於所有模式。它們提供了一組基本行為,可以透過下方的模式專屬自訂指令來強化。\n如果您希望 Roo 使用與編輯器顯示語言 ({{language}}) 不同的語言來思考和對話,您可以在這裡指定。", - "loadFromFile": "指令也可以從工作區的 .roorules 載入(.clinerules 已棄用並將很快停止運作)。" + "loadFromFile": "指令也可以從工作區的 .roo/rules/ 資料夾載入(.roorules 和 .clinerules 已棄用並將很快停止運作)。" }, "systemPrompt": { "preview": "預覽系統提示詞", @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "進階:覆寫系統提示詞", - "description": "您可以透過在工作區建立檔案 .roo/system-prompt-{{slug}} 來完全替換此模式的系統提示詞(角色定義和自訂指令除外)。這是一個非常進階的功能,會繞過內建的安全措施和一致性檢查(尤其是與工具使用相關的檢查),請謹慎使用!" + "description": "您可以透過在工作區的 .roo/system-prompts/{{slug}}/ 資料夾中建立檔案來完全替換此模式的系統提示詞(角色定義和自訂指令除外)。這是一個非常進階的功能,會繞過內建的安全措施和一致性檢查(尤其是與工具使用相關的檢查),請謹慎使用!" }, "createModeDialog": { "title": "建立新模式", From 381d526cfad8e2de85c6422b03cd87fa17ba6b23 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 7 Apr 2025 09:27:18 -0400 Subject: [PATCH 3/6] Add tests --- .../__tests__/custom-instructions.test.ts | 440 ++++++++++++++++-- 1 file changed, 406 insertions(+), 34 deletions(-) diff --git a/src/core/prompts/sections/__tests__/custom-instructions.test.ts b/src/core/prompts/sections/__tests__/custom-instructions.test.ts index 80762dcac3f..a864d535302 100644 --- a/src/core/prompts/sections/__tests__/custom-instructions.test.ts +++ b/src/core/prompts/sections/__tests__/custom-instructions.test.ts @@ -1,9 +1,38 @@ import { loadRuleFiles, addCustomInstructions } from "../custom-instructions" import fs from "fs/promises" +import path from "path" +import { PathLike } from "fs" // Mock fs/promises jest.mock("fs/promises") -const mockedFs = jest.mocked(fs) + +// Create mock functions +const readFileMock = jest.fn() +const statMock = jest.fn() +const readdirMock = jest.fn() + +// Replace fs functions with our mocks +fs.readFile = readFileMock as any +fs.stat = statMock as any +fs.readdir = readdirMock as any + +// Mock path.resolve and path.join to be predictable in tests +jest.mock("path", () => ({ + ...jest.requireActual("path"), + resolve: jest.fn().mockImplementation((...args) => args.join("/")), + join: jest.fn().mockImplementation((...args) => args.join("/")), + relative: jest.fn().mockImplementation((from, to) => to), +})) + +// Mock process.cwd +const originalCwd = process.cwd +beforeAll(() => { + process.cwd = jest.fn().mockReturnValue("/fake/cwd") +}) + +afterAll(() => { + process.cwd = originalCwd +}) describe("loadRuleFiles", () => { beforeEach(() => { @@ -11,42 +40,46 @@ describe("loadRuleFiles", () => { }) it("should read and trim file content", async () => { - mockedFs.readFile.mockResolvedValue(" content with spaces ") + // Simulate no .roo/rules directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + readFileMock.mockResolvedValue(" content with spaces ") const result = await loadRuleFiles("/fake/path") - expect(mockedFs.readFile).toHaveBeenCalled() + expect(readFileMock).toHaveBeenCalled() expect(result).toBe("\n# Rules from .roorules:\ncontent with spaces\n") }) it("should handle ENOENT error", async () => { - mockedFs.readFile.mockRejectedValue({ code: "ENOENT" }) + // Simulate no .roo/rules directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + readFileMock.mockRejectedValue({ code: "ENOENT" }) const result = await loadRuleFiles("/fake/path") expect(result).toBe("") }) it("should handle EISDIR error", async () => { - mockedFs.readFile.mockRejectedValue({ code: "EISDIR" }) + // Simulate no .roo/rules directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + readFileMock.mockRejectedValue({ code: "EISDIR" }) const result = await loadRuleFiles("/fake/path") expect(result).toBe("") }) it("should throw on unexpected errors", async () => { + // Simulate no .roo/rules directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) const error = new Error("Permission denied") as NodeJS.ErrnoException error.code = "EPERM" - mockedFs.readFile.mockRejectedValue(error) + readFileMock.mockRejectedValue(error) await expect(async () => { await loadRuleFiles("/fake/path") }).rejects.toThrow() }) -}) - -describe("loadRuleFiles", () => { - beforeEach(() => { - jest.clearAllMocks() - }) it("should not combine content from multiple rule files when they exist", async () => { - mockedFs.readFile.mockImplementation(((filePath: string | Buffer | URL | number) => { + // Simulate no .roo/rules directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + readFileMock.mockImplementation((filePath: PathLike) => { if (filePath.toString().endsWith(".roorules")) { return Promise.resolve("roo rules content") } @@ -54,31 +87,25 @@ describe("loadRuleFiles", () => { return Promise.resolve("cline rules content") } return Promise.reject({ code: "ENOENT" }) - }) as any) + }) const result = await loadRuleFiles("/fake/path") expect(result).toBe("\n# Rules from .roorules:\nroo rules content\n") }) it("should handle when no rule files exist", async () => { - mockedFs.readFile.mockRejectedValue({ code: "ENOENT" }) + // Simulate no .roo/rules directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + readFileMock.mockRejectedValue({ code: "ENOENT" }) const result = await loadRuleFiles("/fake/path") expect(result).toBe("") }) - it("should throw on unexpected errors", async () => { - const error = new Error("Permission denied") as NodeJS.ErrnoException - error.code = "EPERM" - mockedFs.readFile.mockRejectedValue(error) - - await expect(async () => { - await loadRuleFiles("/fake/path") - }).rejects.toThrow() - }) - it("should skip directories with same name as rule files", async () => { - mockedFs.readFile.mockImplementation(((filePath: string | Buffer | URL | number) => { + // Simulate no .roo/rules directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + readFileMock.mockImplementation((filePath: PathLike) => { if (filePath.toString().endsWith(".roorules")) { return Promise.reject({ code: "EISDIR" }) } @@ -86,11 +113,92 @@ describe("loadRuleFiles", () => { return Promise.reject({ code: "EISDIR" }) } return Promise.reject({ code: "ENOENT" }) - }) as any) + }) const result = await loadRuleFiles("/fake/path") expect(result).toBe("") }) + + it("should use .roo/rules/ directory when it exists and has files", async () => { + // Simulate .roo/rules directory exists + statMock.mockResolvedValueOnce({ + isDirectory: jest.fn().mockReturnValue(true), + } as any) + + // Simulate listing files + readdirMock.mockResolvedValueOnce([ + { name: "file1.txt", isFile: () => true }, + { name: "file2.txt", isFile: () => true }, + ] as any) + + // Simulate file stats + statMock.mockImplementation( + () => + ({ + isFile: jest.fn().mockReturnValue(true), + }) as any, + ) + + // Simulate file reading + readFileMock.mockImplementation((filePath: PathLike) => { + if (filePath.toString().includes("file1.txt")) { + return Promise.resolve("content of file1") + } + if (filePath.toString().includes("file2.txt")) { + return Promise.resolve("content of file2") + } + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await loadRuleFiles("/fake/path") + expect(result).toContain("# Rules from /fake/path/.roo/rules") + expect(result).toContain("# Filename: /fake/path/.roo/rules/file1.txt") + expect(result).toContain("content of file1") + expect(result).toContain("# Filename: /fake/path/.roo/rules/file2.txt") + expect(result).toContain("content of file2") + }) + + it("should fall back to .roorules when .roo/rules/ is empty", async () => { + // Simulate .roo/rules directory exists + statMock.mockResolvedValueOnce({ + isDirectory: jest.fn().mockReturnValue(true), + } as any) + + // Simulate empty directory + readdirMock.mockResolvedValueOnce([]) + + // Simulate .roorules exists + readFileMock.mockImplementation((filePath: PathLike) => { + if (filePath.toString().endsWith(".roorules")) { + return Promise.resolve("roo rules content") + } + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await loadRuleFiles("/fake/path") + expect(result).toBe("\n# Rules from .roorules:\nroo rules content\n") + }) + + it("should handle errors when reading directory", async () => { + // Simulate .roo/rules directory exists + statMock.mockResolvedValueOnce({ + isDirectory: jest.fn().mockReturnValue(true), + } as any) + + // Simulate error reading directory + readdirMock.mockRejectedValueOnce(new Error("Failed to read directory")) + + // Simulate .roorules exists + readFileMock.mockImplementation((filePath: PathLike) => { + if (filePath.toString().endsWith(".roorules")) { + return Promise.resolve("roo rules content") + } + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await loadRuleFiles("/fake/path") + expect(result).toBe("\n# Rules from .roorules:\nroo rules content\n") + }) }) describe("addCustomInstructions", () => { @@ -99,7 +207,10 @@ describe("addCustomInstructions", () => { }) it("should combine all instruction types when provided", async () => { - mockedFs.readFile.mockResolvedValue("mode specific rules") + // Simulate no .roo/rules-test-mode directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + + readFileMock.mockResolvedValue("mode specific rules") const result = await addCustomInstructions( "mode instructions", @@ -118,14 +229,20 @@ describe("addCustomInstructions", () => { }) it("should return empty string when no instructions provided", async () => { - mockedFs.readFile.mockRejectedValue({ code: "ENOENT" }) + // Simulate no .roo/rules directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + + readFileMock.mockRejectedValue({ code: "ENOENT" }) const result = await addCustomInstructions("", "", "/fake/path", "", {}) expect(result).toBe("") }) it("should handle missing mode-specific rules file", async () => { - mockedFs.readFile.mockRejectedValue({ code: "ENOENT" }) + // Simulate no .roo/rules-test-mode directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + + readFileMock.mockRejectedValue({ code: "ENOENT" }) const result = await addCustomInstructions( "mode instructions", @@ -140,7 +257,10 @@ describe("addCustomInstructions", () => { }) it("should handle unknown language codes properly", async () => { - mockedFs.readFile.mockRejectedValue({ code: "ENOENT" }) + // Simulate no .roo/rules-test-mode directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + + readFileMock.mockRejectedValue({ code: "ENOENT" }) const result = await addCustomInstructions( "mode instructions", @@ -156,9 +276,12 @@ describe("addCustomInstructions", () => { }) it("should throw on unexpected errors", async () => { + // Simulate no .roo/rules-test-mode directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + const error = new Error("Permission denied") as NodeJS.ErrnoException error.code = "EPERM" - mockedFs.readFile.mockRejectedValue(error) + readFileMock.mockRejectedValue(error) await expect(async () => { await addCustomInstructions("", "", "/fake/path", "test-mode") @@ -166,12 +289,15 @@ describe("addCustomInstructions", () => { }) it("should skip mode-specific rule files that are directories", async () => { - mockedFs.readFile.mockImplementation(((filePath: string | Buffer | URL | number) => { + // Simulate no .roo/rules-test-mode directory + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + + readFileMock.mockImplementation((filePath: PathLike) => { if (filePath.toString().includes(".clinerules-test-mode")) { return Promise.reject({ code: "EISDIR" }) } return Promise.reject({ code: "ENOENT" }) - }) as any) + }) const result = await addCustomInstructions( "mode instructions", @@ -184,4 +310,250 @@ describe("addCustomInstructions", () => { expect(result).toContain("Mode-specific Instructions:\nmode instructions") expect(result).not.toContain("Rules from .clinerules-test-mode") }) + + it("should use .roo/rules-test-mode/ directory when it exists and has files", async () => { + // Simulate .roo/rules-test-mode directory exists + statMock.mockResolvedValueOnce({ + isDirectory: jest.fn().mockReturnValue(true), + } as any) + + // Simulate listing files + readdirMock.mockResolvedValueOnce([ + { name: "rule1.txt", isFile: () => true }, + { name: "rule2.txt", isFile: () => true }, + ] as any) + + // Simulate file stats + statMock.mockImplementation( + () => + ({ + isFile: jest.fn().mockReturnValue(true), + }) as any, + ) + + // Simulate file reading + readFileMock.mockImplementation((filePath: PathLike) => { + if (filePath.toString().includes("rule1.txt")) { + return Promise.resolve("mode specific rule 1") + } + if (filePath.toString().includes("rule2.txt")) { + return Promise.resolve("mode specific rule 2") + } + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await addCustomInstructions( + "mode instructions", + "global instructions", + "/fake/path", + "test-mode", + { language: "es" }, + ) + + expect(result).toContain("# Rules from /fake/path/.roo/rules-test-mode") + expect(result).toContain("# Filename: /fake/path/.roo/rules-test-mode/rule1.txt") + expect(result).toContain("mode specific rule 1") + expect(result).toContain("# Filename: /fake/path/.roo/rules-test-mode/rule2.txt") + expect(result).toContain("mode specific rule 2") + }) + + it("should fall back to .roorules-test-mode when .roo/rules-test-mode/ does not exist", async () => { + // Simulate .roo/rules-test-mode directory does not exist + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + + // Simulate .roorules-test-mode exists + readFileMock.mockImplementation((filePath: PathLike) => { + if (filePath.toString().includes(".roorules-test-mode")) { + return Promise.resolve("mode specific rules from file") + } + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await addCustomInstructions( + "mode instructions", + "global instructions", + "/fake/path", + "test-mode", + ) + + expect(result).toContain("Rules from .roorules-test-mode:\nmode specific rules from file") + }) + + it("should fall back to .clinerules-test-mode when .roo/rules-test-mode/ and .roorules-test-mode do not exist", async () => { + // Simulate .roo/rules-test-mode directory does not exist + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + + // Simulate file reading + readFileMock.mockImplementation((filePath: PathLike) => { + if (filePath.toString().includes(".roorules-test-mode")) { + return Promise.reject({ code: "ENOENT" }) + } + if (filePath.toString().includes(".clinerules-test-mode")) { + return Promise.resolve("mode specific rules from cline file") + } + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await addCustomInstructions( + "mode instructions", + "global instructions", + "/fake/path", + "test-mode", + ) + + expect(result).toContain("Rules from .clinerules-test-mode:\nmode specific rules from cline file") + }) + + it("should correctly format content from directories when using .roo/rules-test-mode/", async () => { + // Need to reset mockImplementation first to avoid interference from previous tests + statMock.mockReset() + readFileMock.mockReset() + + // Simulate .roo/rules-test-mode directory exists + statMock.mockImplementationOnce(() => + Promise.resolve({ + isDirectory: jest.fn().mockReturnValue(true), + } as any), + ) + + // Simulate directory has files + readdirMock.mockResolvedValueOnce([{ name: "rule1.txt", isFile: () => true }] as any) + + // Set up stat mock for checking files + let statCallCount = 0 + statMock.mockImplementation(() => { + statCallCount++ + return Promise.resolve({ + isFile: jest.fn().mockReturnValue(true), + isDirectory: jest.fn().mockReturnValue(false), + } as any) + }) + + // Set up read file mock for both rule files and fallback files + readFileMock.mockImplementation((filePath: PathLike) => { + if (filePath.toString().includes("rule1.txt")) { + return Promise.resolve("mode specific rule content") + } + // Make sure we return ENOENT for all other files to test fallback behavior + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await addCustomInstructions( + "mode instructions", + "global instructions", + "/fake/path", + "test-mode", + ) + + expect(result).toContain("# Rules from /fake/path/.roo/rules-test-mode") + expect(result).toContain("# Filename: /fake/path/.roo/rules-test-mode/rule1.txt") + expect(result).toContain("mode specific rule content") + }) +}) + +// Test directory existence checks through loadRuleFiles +describe("Directory existence checks", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it("should detect when directory exists", async () => { + // Mock the stats to indicate the directory exists + statMock.mockResolvedValueOnce({ + isDirectory: jest.fn().mockReturnValue(true), + } as any) + + // Simulate empty directory to test that stats is called + readdirMock.mockResolvedValueOnce([]) + + // For loadRuleFiles to return something for testing + readFileMock.mockResolvedValueOnce("fallback content") + + await loadRuleFiles("/fake/path") + + // Verify stat was called to check directory existence + expect(statMock).toHaveBeenCalledWith("/fake/path/.roo/rules") + }) + + it("should handle when directory does not exist", async () => { + // Mock the stats to indicate the directory doesn't exist + statMock.mockRejectedValueOnce({ code: "ENOENT" }) + + // Mock file read to verify fallback + readFileMock.mockResolvedValueOnce("fallback content") + + const result = await loadRuleFiles("/fake/path") + + // Verify it fell back to reading rule files directly + expect(result).toBe("\n# Rules from .roorules:\nfallback content\n") + }) +}) + +// Indirectly test readTextFilesFromDirectory and formatDirectoryContent through loadRuleFiles +describe("Rules directory reading", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it("should correctly format multiple files from directory", async () => { + // Simulate .roo/rules directory exists + statMock.mockResolvedValueOnce({ + isDirectory: jest.fn().mockReturnValue(true), + } as any) + + // Simulate listing multiple files and a directory + readdirMock.mockResolvedValueOnce([ + { name: "file1.txt", isFile: () => true }, + { name: "file2.txt", isFile: () => true }, + { name: "subdir", isFile: () => false }, // This should be filtered out + ] as any) + + // Simulate file stats + statMock.mockImplementation((path) => { + if (path.toString().includes("subdir")) { + return Promise.resolve({ + isFile: jest.fn().mockReturnValue(false), + } as any) + } + return Promise.resolve({ + isFile: jest.fn().mockReturnValue(true), + } as any) + }) + + // Simulate file reading + readFileMock.mockImplementation((filePath: PathLike) => { + if (filePath.toString().includes("file1.txt")) { + return Promise.resolve("content of file1") + } + if (filePath.toString().includes("file2.txt")) { + return Promise.resolve("content of file2") + } + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await loadRuleFiles("/fake/path") + expect(result).toContain("# Rules from /fake/path/.roo/rules") + expect(result).toContain("# Filename: /fake/path/.roo/rules/file1.txt") + expect(result).toContain("content of file1") + expect(result).toContain("# Filename: /fake/path/.roo/rules/file2.txt") + expect(result).toContain("content of file2") + // Verify subdirectory was filtered out + expect(result).not.toContain("subdir") + }) + + it("should handle empty file list gracefully", async () => { + // Simulate .roo/rules directory exists + statMock.mockResolvedValueOnce({ + isDirectory: jest.fn().mockReturnValue(true), + } as any) + + // Simulate empty directory + readdirMock.mockResolvedValueOnce([]) + + // For loadRuleFiles to return something for testing + readFileMock.mockResolvedValueOnce("fallback content") + + const result = await loadRuleFiles("/fake/path") + expect(result).toBe("\n# Rules from .roorules:\nfallback content\n") + }) }) From 0d97c2c0c82aa0e58ca645ffbf0f6b2ee772a972 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 7 Apr 2025 09:39:12 -0400 Subject: [PATCH 4/6] Revert changes to system prompt translations --- webview-ui/src/i18n/locales/ca/prompts.json | 2 +- webview-ui/src/i18n/locales/de/prompts.json | 2 +- webview-ui/src/i18n/locales/es/prompts.json | 2 +- webview-ui/src/i18n/locales/fr/prompts.json | 2 +- webview-ui/src/i18n/locales/hi/prompts.json | 2 +- webview-ui/src/i18n/locales/it/prompts.json | 2 +- webview-ui/src/i18n/locales/ja/prompts.json | 2 +- webview-ui/src/i18n/locales/ko/prompts.json | 2 +- webview-ui/src/i18n/locales/pl/prompts.json | 2 +- webview-ui/src/i18n/locales/pt-BR/prompts.json | 2 +- webview-ui/src/i18n/locales/tr/prompts.json | 2 +- webview-ui/src/i18n/locales/vi/prompts.json | 2 +- webview-ui/src/i18n/locales/zh-CN/prompts.json | 2 +- webview-ui/src/i18n/locales/zh-TW/prompts.json | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/webview-ui/src/i18n/locales/ca/prompts.json b/webview-ui/src/i18n/locales/ca/prompts.json index 16b9d9daeff..0acb8d5d067 100644 --- a/webview-ui/src/i18n/locales/ca/prompts.json +++ b/webview-ui/src/i18n/locales/ca/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avançat: Sobreescriure prompt del sistema", - "description": "Podeu reemplaçar completament el prompt del sistema per a aquest mode (a part de la definició de rol i instruccions personalitzades) creant un fitxer a la carpeta .roo/system-prompts/{{slug}}/ al vostre espai de treball. Aquesta és una funcionalitat molt avançada que eludeix les salvaguardes integrades i les comprovacions de consistència (especialment al voltant de l'ús d'eines), així que aneu amb compte!" + "description": "Podeu reemplaçar completament el prompt del sistema per a aquest mode (a part de la definició de rol i instruccions personalitzades) creant un fitxer a .roo/system-prompt-{{slug}} al vostre espai de treball. Aquesta és una funcionalitat molt avançada que eludeix les salvaguardes integrades i les comprovacions de consistència (especialment al voltant de l'ús d'eines), així que aneu amb compte!" }, "createModeDialog": { "title": "Crear nou mode", diff --git a/webview-ui/src/i18n/locales/de/prompts.json b/webview-ui/src/i18n/locales/de/prompts.json index 91b6720d519..55e8440c516 100644 --- a/webview-ui/src/i18n/locales/de/prompts.json +++ b/webview-ui/src/i18n/locales/de/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Erweitert: System-Prompt überschreiben", - "description": "Du kannst den System-Prompt für diesen Modus vollständig ersetzen (abgesehen von der Rollendefinition und benutzerdefinierten Anweisungen), indem du eine Datei im Ordner .roo/system-prompts/{{slug}}/ in deinem Arbeitsbereich erstellst. Dies ist eine sehr fortgeschrittene Funktion, die eingebaute Schutzmaßnahmen und Konsistenzprüfungen umgeht (besonders bei der Werkzeugnutzung), also sei vorsichtig!" + "description": "Du kannst den System-Prompt für diesen Modus vollständig ersetzen (abgesehen von der Rollendefinition und benutzerdefinierten Anweisungen), indem du eine Datei unter .roo/system-prompt-{{slug}} in deinem Arbeitsbereich erstellst. Dies ist eine sehr fortgeschrittene Funktion, die eingebaute Schutzmaßnahmen und Konsistenzprüfungen umgeht (besonders bei der Werkzeugnutzung), also sei vorsichtig!" }, "createModeDialog": { "title": "Neuen Modus erstellen", diff --git a/webview-ui/src/i18n/locales/es/prompts.json b/webview-ui/src/i18n/locales/es/prompts.json index 2687fdcb47e..e93835edf49 100644 --- a/webview-ui/src/i18n/locales/es/prompts.json +++ b/webview-ui/src/i18n/locales/es/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avanzado: Anular solicitud del sistema", - "description": "Puedes reemplazar completamente la solicitud del sistema para este modo (aparte de la definición de rol e instrucciones personalizadas) creando un archivo en la carpeta .roo/system-prompts/{{slug}}/ en tu espacio de trabajo. ¡Esta es una función muy avanzada que omite las salvaguardas integradas y las verificaciones de consistencia (especialmente en torno al uso de herramientas), así que ten cuidado!" + "description": "Puedes reemplazar completamente la solicitud del sistema para este modo (aparte de la definición de rol e instrucciones personalizadas) creando un archivo en .roo/system-prompt-{{slug}} en tu espacio de trabajo. ¡Esta es una función muy avanzada que omite las salvaguardas integradas y las verificaciones de consistencia (especialmente en torno al uso de herramientas), así que ten cuidado!" }, "createModeDialog": { "title": "Crear nuevo modo", diff --git a/webview-ui/src/i18n/locales/fr/prompts.json b/webview-ui/src/i18n/locales/fr/prompts.json index 8769fcb0d23..1d5b871d625 100644 --- a/webview-ui/src/i18n/locales/fr/prompts.json +++ b/webview-ui/src/i18n/locales/fr/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avancé : Remplacer le prompt système", - "description": "Vous pouvez complètement remplacer le prompt système pour ce mode (en dehors de la définition du rôle et des instructions personnalisées) en créant un fichier dans le dossier .roo/system-prompts/{{slug}}/ dans votre espace de travail. Il s'agit d'une fonctionnalité très avancée qui contourne les garanties intégrées et les vérifications de cohérence (notamment concernant l'utilisation des outils), alors soyez prudent !" + "description": "Vous pouvez complètement remplacer le prompt système pour ce mode (en dehors de la définition du rôle et des instructions personnalisées) en créant un fichier à .roo/system-prompt-{{slug}} dans votre espace de travail. Il s'agit d'une fonctionnalité très avancée qui contourne les garanties intégrées et les vérifications de cohérence (notamment concernant l'utilisation des outils), alors soyez prudent !" }, "createModeDialog": { "title": "Créer un nouveau mode", diff --git a/webview-ui/src/i18n/locales/hi/prompts.json b/webview-ui/src/i18n/locales/hi/prompts.json index 00c4a730702..e30f04c664d 100644 --- a/webview-ui/src/i18n/locales/hi/prompts.json +++ b/webview-ui/src/i18n/locales/hi/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "उन्नत: सिस्टम प्रॉम्प्ट ओवरराइड करें", - "description": "आप अपने वर्कस्पेस में .roo/system-prompts/{{slug}}/ फ़ोल्डर में एक फाइल बनाकर इस मोड के लिए सिस्टम प्रॉम्प्ट को पूरी तरह से बदल सकते हैं (भूमिका परिभाषा और कस्टम निर्देशों को छोड़कर)। यह एक बहुत उन्नत सुविधा है जो अंतर्निहित सुरक्षा उपायों और सामंजस्यता जांचों को बायपास करती है (विशेष रूप से टूल उपयोग के आसपास), इसलिए सावधान रहें!" + "description": "आप अपने वर्कस्पेस में .roo/system-prompt-{{slug}} पर एक फाइल बनाकर इस मोड के लिए सिस्टम प्रॉम्प्ट को पूरी तरह से बदल सकते हैं (भूमिका परिभाषा और कस्टम निर्देशों को छोड़कर)। यह एक बहुत उन्नत सुविधा है जो अंतर्निहित सुरक्षा उपायों और सामंजस्यता जांचों को बायपास करती है (विशेष रूप से टूल उपयोग के आसपास), इसलिए सावधान रहें!" }, "createModeDialog": { "title": "नया मोड बनाएँ", diff --git a/webview-ui/src/i18n/locales/it/prompts.json b/webview-ui/src/i18n/locales/it/prompts.json index 06bb2d9034e..cfb35f150c4 100644 --- a/webview-ui/src/i18n/locales/it/prompts.json +++ b/webview-ui/src/i18n/locales/it/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avanzato: Sovrascrivi prompt di sistema", - "description": "Puoi sostituire completamente il prompt di sistema per questa modalità (a parte la definizione del ruolo e le istruzioni personalizzate) creando un file nella cartella .roo/system-prompts/{{slug}}/ nel tuo spazio di lavoro. Questa è una funzionalità molto avanzata che bypassa le protezioni integrate e i controlli di coerenza (specialmente riguardo all'uso degli strumenti), quindi fai attenzione!" + "description": "Puoi sostituire completamente il prompt di sistema per questa modalità (a parte la definizione del ruolo e le istruzioni personalizzate) creando un file in .roo/system-prompt-{{slug}} nel tuo spazio di lavoro. Questa è una funzionalità molto avanzata che bypassa le protezioni integrate e i controlli di coerenza (specialmente riguardo all'uso degli strumenti), quindi fai attenzione!" }, "createModeDialog": { "title": "Crea nuova modalità", diff --git a/webview-ui/src/i18n/locales/ja/prompts.json b/webview-ui/src/i18n/locales/ja/prompts.json index 5e90e19c045..8f18095a121 100644 --- a/webview-ui/src/i18n/locales/ja/prompts.json +++ b/webview-ui/src/i18n/locales/ja/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "詳細設定:システムプロンプトの上書き", - "description": "ワークスペースの.roo/system-prompts/{{slug}}/フォルダにファイルを作成することで、このモードのシステムプロンプト(役割定義とカスタム指示以外)を完全に置き換えることができます。これは組み込みの安全対策と一貫性チェック(特にツールの使用に関して)をバイパスする非常に高度な機能なので、注意して使用してください!" + "description": "ワークスペースの.roo/system-prompt-{{slug}}にファイルを作成することで、このモードのシステムプロンプト(役割定義とカスタム指示以外)を完全に置き換えることができます。これは組み込みの安全対策と一貫性チェック(特にツールの使用に関して)をバイパスする非常に高度な機能なので、注意して使用してください!" }, "createModeDialog": { "title": "新しいモードを作成", diff --git a/webview-ui/src/i18n/locales/ko/prompts.json b/webview-ui/src/i18n/locales/ko/prompts.json index ba119734c8b..7ce93e8be02 100644 --- a/webview-ui/src/i18n/locales/ko/prompts.json +++ b/webview-ui/src/i18n/locales/ko/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "고급: 시스템 프롬프트 재정의", - "description": "작업 공간의 .roo/system-prompts/{{slug}}/ 폴더에 파일을 생성하여 이 모드의 시스템 프롬프트(역할 정의 및 사용자 지정 지침 제외)를 완전히 대체할 수 있습니다. 이는 내장된 안전 장치와 일관성 검사(특히 도구 사용 관련)를 우회하는 매우 고급 기능이므로 주의하세요!" + "description": "작업 공간의 .roo/system-prompt-{{slug}}에 파일을 생성하여 이 모드의 시스템 프롬프트(역할 정의 및 사용자 지정 지침 제외)를 완전히 대체할 수 있습니다. 이는 내장된 안전 장치와 일관성 검사(특히 도구 사용 관련)를 우회하는 매우 고급 기능이므로 주의하세요!" }, "createModeDialog": { "title": "새 모드 만들기", diff --git a/webview-ui/src/i18n/locales/pl/prompts.json b/webview-ui/src/i18n/locales/pl/prompts.json index 657af3de335..459b2069194 100644 --- a/webview-ui/src/i18n/locales/pl/prompts.json +++ b/webview-ui/src/i18n/locales/pl/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Zaawansowane: Zastąp podpowiedź systemową", - "description": "Możesz całkowicie zastąpić podpowiedź systemową dla tego trybu (oprócz definicji roli i niestandardowych instrukcji) poprzez utworzenie pliku w folderze .roo/system-prompts/{{modeSlug}}/ w swoim obszarze roboczym. Jest to bardzo zaawansowana funkcja, która omija wbudowane zabezpieczenia i kontrole spójności (szczególnie wokół używania narzędzi), więc bądź ostrożny!" + "description": "Możesz całkowicie zastąpić podpowiedź systemową dla tego trybu (oprócz definicji roli i niestandardowych instrukcji) poprzez utworzenie pliku w .roo/system-prompt-{{modeSlug}} w swoim obszarze roboczym. Jest to bardzo zaawansowana funkcja, która omija wbudowane zabezpieczenia i kontrole spójności (szczególnie wokół używania narzędzi), więc bądź ostrożny!" }, "createModeDialog": { "title": "Utwórz nowy tryb", diff --git a/webview-ui/src/i18n/locales/pt-BR/prompts.json b/webview-ui/src/i18n/locales/pt-BR/prompts.json index dc49bec31c1..50c3852b15f 100644 --- a/webview-ui/src/i18n/locales/pt-BR/prompts.json +++ b/webview-ui/src/i18n/locales/pt-BR/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Avançado: Substituir prompt do sistema", - "description": "Você pode substituir completamente o prompt do sistema para este modo (além da definição de função e instruções personalizadas) criando um arquivo na pasta .roo/system-prompts/{{modeSlug}}/ no seu espaço de trabalho. Esta é uma funcionalidade muito avançada que contorna as salvaguardas integradas e verificações de consistência (especialmente em torno do uso de ferramentas), então tenha cuidado!" + "description": "Você pode substituir completamente o prompt do sistema para este modo (além da definição de função e instruções personalizadas) criando um arquivo em .roo/system-prompt-{{modeSlug}} no seu espaço de trabalho. Esta é uma funcionalidade muito avançada que contorna as salvaguardas integradas e verificações de consistência (especialmente em torno do uso de ferramentas), então tenha cuidado!" }, "createModeDialog": { "title": "Criar novo modo", diff --git a/webview-ui/src/i18n/locales/tr/prompts.json b/webview-ui/src/i18n/locales/tr/prompts.json index 45db7ffe8fe..40cd10aa895 100644 --- a/webview-ui/src/i18n/locales/tr/prompts.json +++ b/webview-ui/src/i18n/locales/tr/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Gelişmiş: Sistem Promptunu Geçersiz Kıl", - "description": "Çalışma alanınızda .roo/system-prompts/{{slug}}/ klasöründe bir dosya oluşturarak bu mod için sistem istemini tamamen değiştirebilirsiniz (rol tanımı ve özel talimatlar hariç). Bu, yerleşik güvenlik önlemlerini ve tutarlılık kontrollerini (özellikle araç kullanımıyla ilgili) aşan çok gelişmiş bir özelliktir, bu yüzden dikkatli olun!" + "description": "Çalışma alanınızda .roo/system-prompt-{{slug}} adresinde bir dosya oluşturarak bu mod için sistem istemini tamamen değiştirebilirsiniz (rol tanımı ve özel talimatlar hariç). Bu, yerleşik güvenlik önlemlerini ve tutarlılık kontrollerini (özellikle araç kullanımıyla ilgili) aşan çok gelişmiş bir özelliktir, bu yüzden dikkatli olun!" }, "createModeDialog": { "title": "Yeni Mod Oluştur", diff --git a/webview-ui/src/i18n/locales/vi/prompts.json b/webview-ui/src/i18n/locales/vi/prompts.json index ded3b7a5749..3951da818c6 100644 --- a/webview-ui/src/i18n/locales/vi/prompts.json +++ b/webview-ui/src/i18n/locales/vi/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "Nâng cao: Ghi đè lời nhắc hệ thống", - "description": "Bạn có thể hoàn toàn thay thế lời nhắc hệ thống cho chế độ này (ngoài định nghĩa vai trò và hướng dẫn tùy chỉnh) bằng cách tạo một tệp trong thư mục .roo/system-prompts/{{modeSlug}}/ trong không gian làm việc của bạn. Đây là một tính năng rất nâng cao bỏ qua các biện pháp bảo vệ và kiểm tra nhất quán tích hợp sẵn (đặc biệt là xung quanh việc sử dụng công cụ), vì vậy hãy cẩn thận!" + "description": "Bạn có thể hoàn toàn thay thế lời nhắc hệ thống cho chế độ này (ngoài định nghĩa vai trò và hướng dẫn tùy chỉnh) bằng cách tạo một tệp tại .roo/system-prompt-{{modeSlug}} trong không gian làm việc của bạn. Đây là một tính năng rất nâng cao bỏ qua các biện pháp bảo vệ và kiểm tra nhất quán tích hợp sẵn (đặc biệt là xung quanh việc sử dụng công cụ), vì vậy hãy cẩn thận!" }, "createModeDialog": { "title": "Tạo chế độ mới", diff --git a/webview-ui/src/i18n/locales/zh-CN/prompts.json b/webview-ui/src/i18n/locales/zh-CN/prompts.json index 2a5f99579b4..abfd892039f 100644 --- a/webview-ui/src/i18n/locales/zh-CN/prompts.json +++ b/webview-ui/src/i18n/locales/zh-CN/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "高级:覆盖系统提示词", - "description": "您可以通过在工作区.roo/system-prompts/{{slug}}/目录中创建文件,完全替换此模式的系统提示(角色定义和自定义指令除外)。这是一个非常高级的功能,会绕过内置的安全措施和一致性检查(尤其是与工具使用相关的部分),请谨慎操作!" + "description": "您可以通过在工作区创建文件 .roo/system-prompt-{{slug}},完全替换此模式的系统提示(角色定义和自定义指令除外)。这是一个非常高级的功能,会绕过内置的安全措施和一致性检查(尤其是与工具使用相关的部分),请谨慎操作!" }, "createModeDialog": { "title": "创建新模式", diff --git a/webview-ui/src/i18n/locales/zh-TW/prompts.json b/webview-ui/src/i18n/locales/zh-TW/prompts.json index e24f6b6eb8a..874c9361dc0 100644 --- a/webview-ui/src/i18n/locales/zh-TW/prompts.json +++ b/webview-ui/src/i18n/locales/zh-TW/prompts.json @@ -100,7 +100,7 @@ }, "advancedSystemPrompt": { "title": "進階:覆寫系統提示詞", - "description": "您可以透過在工作區的 .roo/system-prompts/{{slug}}/ 資料夾中建立檔案來完全替換此模式的系統提示詞(角色定義和自訂指令除外)。這是一個非常進階的功能,會繞過內建的安全措施和一致性檢查(尤其是與工具使用相關的檢查),請謹慎使用!" + "description": "您可以透過在工作區建立檔案 .roo/system-prompt-{{slug}} 來完全替換此模式的系統提示詞(角色定義和自訂指令除外)。這是一個非常進階的功能,會繞過內建的安全措施和一致性檢查(尤其是與工具使用相關的檢查),請謹慎使用!" }, "createModeDialog": { "title": "建立新模式", From 210a69a635dd9fb8d2bff73a1520bb076b62dbb3 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 7 Apr 2025 10:59:58 -0400 Subject: [PATCH 5/6] Fix path resolution --- .../__tests__/custom-instructions.test.ts | 75 +++++++++++-------- .../prompts/sections/custom-instructions.ts | 5 +- 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/core/prompts/sections/__tests__/custom-instructions.test.ts b/src/core/prompts/sections/__tests__/custom-instructions.test.ts index a864d535302..abdcecc294a 100644 --- a/src/core/prompts/sections/__tests__/custom-instructions.test.ts +++ b/src/core/prompts/sections/__tests__/custom-instructions.test.ts @@ -131,20 +131,18 @@ describe("loadRuleFiles", () => { { name: "file2.txt", isFile: () => true }, ] as any) - // Simulate file stats statMock.mockImplementation( - () => + (path) => ({ isFile: jest.fn().mockReturnValue(true), }) as any, ) - // Simulate file reading readFileMock.mockImplementation((filePath: PathLike) => { - if (filePath.toString().includes("file1.txt")) { + if (filePath.toString() === "/fake/path/.roo/rules/file1.txt") { return Promise.resolve("content of file1") } - if (filePath.toString().includes("file2.txt")) { + if (filePath.toString() === "/fake/path/.roo/rules/file2.txt") { return Promise.resolve("content of file2") } return Promise.reject({ code: "ENOENT" }) @@ -156,6 +154,11 @@ describe("loadRuleFiles", () => { expect(result).toContain("content of file1") expect(result).toContain("# Filename: /fake/path/.roo/rules/file2.txt") expect(result).toContain("content of file2") + + expect(statMock).toHaveBeenCalledWith("/fake/path/.roo/rules/file1.txt") + expect(statMock).toHaveBeenCalledWith("/fake/path/.roo/rules/file2.txt") + expect(readFileMock).toHaveBeenCalledWith("/fake/path/.roo/rules/file1.txt", "utf-8") + expect(readFileMock).toHaveBeenCalledWith("/fake/path/.roo/rules/file2.txt", "utf-8") }) it("should fall back to .roorules when .roo/rules/ is empty", async () => { @@ -323,20 +326,18 @@ describe("addCustomInstructions", () => { { name: "rule2.txt", isFile: () => true }, ] as any) - // Simulate file stats statMock.mockImplementation( - () => + (path) => ({ isFile: jest.fn().mockReturnValue(true), }) as any, ) - // Simulate file reading readFileMock.mockImplementation((filePath: PathLike) => { - if (filePath.toString().includes("rule1.txt")) { + if (filePath.toString() === "/fake/path/.roo/rules-test-mode/rule1.txt") { return Promise.resolve("mode specific rule 1") } - if (filePath.toString().includes("rule2.txt")) { + if (filePath.toString() === "/fake/path/.roo/rules-test-mode/rule2.txt") { return Promise.resolve("mode specific rule 2") } return Promise.reject({ code: "ENOENT" }) @@ -355,6 +356,11 @@ describe("addCustomInstructions", () => { expect(result).toContain("mode specific rule 1") expect(result).toContain("# Filename: /fake/path/.roo/rules-test-mode/rule2.txt") expect(result).toContain("mode specific rule 2") + + expect(statMock).toHaveBeenCalledWith("/fake/path/.roo/rules-test-mode/rule1.txt") + expect(statMock).toHaveBeenCalledWith("/fake/path/.roo/rules-test-mode/rule2.txt") + expect(readFileMock).toHaveBeenCalledWith("/fake/path/.roo/rules-test-mode/rule1.txt", "utf-8") + expect(readFileMock).toHaveBeenCalledWith("/fake/path/.roo/rules-test-mode/rule2.txt", "utf-8") }) it("should fall back to .roorules-test-mode when .roo/rules-test-mode/ does not exist", async () => { @@ -418,23 +424,28 @@ describe("addCustomInstructions", () => { // Simulate directory has files readdirMock.mockResolvedValueOnce([{ name: "rule1.txt", isFile: () => true }] as any) + readFileMock.mockReset() // Set up stat mock for checking files let statCallCount = 0 - statMock.mockImplementation(() => { + statMock.mockImplementation((filePath) => { statCallCount++ + if (filePath === "/fake/path/.roo/rules-test-mode/rule1.txt") { + return Promise.resolve({ + isFile: jest.fn().mockReturnValue(true), + isDirectory: jest.fn().mockReturnValue(false), + } as any) + } return Promise.resolve({ - isFile: jest.fn().mockReturnValue(true), + isFile: jest.fn().mockReturnValue(false), isDirectory: jest.fn().mockReturnValue(false), } as any) }) - // Set up read file mock for both rule files and fallback files readFileMock.mockImplementation((filePath: PathLike) => { - if (filePath.toString().includes("rule1.txt")) { + if (filePath.toString() === "/fake/path/.roo/rules-test-mode/rule1.txt") { return Promise.resolve("mode specific rule content") } - // Make sure we return ENOENT for all other files to test fallback behavior return Promise.reject({ code: "ENOENT" }) }) @@ -448,6 +459,8 @@ describe("addCustomInstructions", () => { expect(result).toContain("# Rules from /fake/path/.roo/rules-test-mode") expect(result).toContain("# Filename: /fake/path/.roo/rules-test-mode/rule1.txt") expect(result).toContain("mode specific rule content") + + expect(statCallCount).toBeGreaterThan(0) }) }) @@ -501,44 +514,47 @@ describe("Rules directory reading", () => { isDirectory: jest.fn().mockReturnValue(true), } as any) - // Simulate listing multiple files and a directory + // Simulate listing files readdirMock.mockResolvedValueOnce([ { name: "file1.txt", isFile: () => true }, { name: "file2.txt", isFile: () => true }, - { name: "subdir", isFile: () => false }, // This should be filtered out + { name: "file3.txt", isFile: () => true }, ] as any) - // Simulate file stats statMock.mockImplementation((path) => { - if (path.toString().includes("subdir")) { - return Promise.resolve({ - isFile: jest.fn().mockReturnValue(false), - } as any) - } + expect([ + "/fake/path/.roo/rules/file1.txt", + "/fake/path/.roo/rules/file2.txt", + "/fake/path/.roo/rules/file3.txt", + ]).toContain(path) + return Promise.resolve({ isFile: jest.fn().mockReturnValue(true), - } as any) + }) as any }) - // Simulate file reading readFileMock.mockImplementation((filePath: PathLike) => { - if (filePath.toString().includes("file1.txt")) { + if (filePath.toString() === "/fake/path/.roo/rules/file1.txt") { return Promise.resolve("content of file1") } - if (filePath.toString().includes("file2.txt")) { + if (filePath.toString() === "/fake/path/.roo/rules/file2.txt") { return Promise.resolve("content of file2") } + if (filePath.toString() === "/fake/path/.roo/rules/file3.txt") { + return Promise.resolve("content of file3") + } return Promise.reject({ code: "ENOENT" }) }) const result = await loadRuleFiles("/fake/path") + expect(result).toContain("# Rules from /fake/path/.roo/rules") expect(result).toContain("# Filename: /fake/path/.roo/rules/file1.txt") expect(result).toContain("content of file1") expect(result).toContain("# Filename: /fake/path/.roo/rules/file2.txt") expect(result).toContain("content of file2") - // Verify subdirectory was filtered out - expect(result).not.toContain("subdir") + expect(result).toContain("# Filename: /fake/path/.roo/rules/file3.txt") + expect(result).toContain("content of file3") }) it("should handle empty file list gracefully", async () => { @@ -550,7 +566,6 @@ describe("Rules directory reading", () => { // Simulate empty directory readdirMock.mockResolvedValueOnce([]) - // For loadRuleFiles to return something for testing readFileMock.mockResolvedValueOnce("fallback content") const result = await loadRuleFiles("/fake/path") diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index 02795bb6c50..8399ea68110 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -43,12 +43,11 @@ async function readTextFilesFromDirectory(dirPath: string): Promise { - const filePath = path.join(dirPath, file) try { // Check if it's a file (not a directory) - const stats = await fs.stat(filePath) + const stats = await fs.stat(file) if (stats.isFile()) { - const content = await safeReadFile(filePath) + const content = await safeReadFile(file) return { filename: file, content } } return null From 06708446683e3af18847d57174b1f111b3b8c6aa Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 7 Apr 2025 11:20:20 -0400 Subject: [PATCH 6/6] Make instruction structure clearer --- .../__tests__/custom-instructions.test.ts | 18 ++++++++---------- .../prompts/sections/custom-instructions.ts | 15 ++++++++------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/core/prompts/sections/__tests__/custom-instructions.test.ts b/src/core/prompts/sections/__tests__/custom-instructions.test.ts index abdcecc294a..1871b4995e5 100644 --- a/src/core/prompts/sections/__tests__/custom-instructions.test.ts +++ b/src/core/prompts/sections/__tests__/custom-instructions.test.ts @@ -149,10 +149,9 @@ describe("loadRuleFiles", () => { }) const result = await loadRuleFiles("/fake/path") - expect(result).toContain("# Rules from /fake/path/.roo/rules") - expect(result).toContain("# Filename: /fake/path/.roo/rules/file1.txt") + expect(result).toContain("# Rules from /fake/path/.roo/rules/file1.txt:") expect(result).toContain("content of file1") - expect(result).toContain("# Filename: /fake/path/.roo/rules/file2.txt") + expect(result).toContain("# Rules from /fake/path/.roo/rules/file2.txt:") expect(result).toContain("content of file2") expect(statMock).toHaveBeenCalledWith("/fake/path/.roo/rules/file1.txt") @@ -352,9 +351,9 @@ describe("addCustomInstructions", () => { ) expect(result).toContain("# Rules from /fake/path/.roo/rules-test-mode") - expect(result).toContain("# Filename: /fake/path/.roo/rules-test-mode/rule1.txt") + expect(result).toContain("# Rules from /fake/path/.roo/rules-test-mode/rule1.txt:") expect(result).toContain("mode specific rule 1") - expect(result).toContain("# Filename: /fake/path/.roo/rules-test-mode/rule2.txt") + expect(result).toContain("# Rules from /fake/path/.roo/rules-test-mode/rule2.txt:") expect(result).toContain("mode specific rule 2") expect(statMock).toHaveBeenCalledWith("/fake/path/.roo/rules-test-mode/rule1.txt") @@ -457,7 +456,7 @@ describe("addCustomInstructions", () => { ) expect(result).toContain("# Rules from /fake/path/.roo/rules-test-mode") - expect(result).toContain("# Filename: /fake/path/.roo/rules-test-mode/rule1.txt") + expect(result).toContain("# Rules from /fake/path/.roo/rules-test-mode/rule1.txt:") expect(result).toContain("mode specific rule content") expect(statCallCount).toBeGreaterThan(0) @@ -548,12 +547,11 @@ describe("Rules directory reading", () => { const result = await loadRuleFiles("/fake/path") - expect(result).toContain("# Rules from /fake/path/.roo/rules") - expect(result).toContain("# Filename: /fake/path/.roo/rules/file1.txt") + expect(result).toContain("# Rules from /fake/path/.roo/rules/file1.txt:") expect(result).toContain("content of file1") - expect(result).toContain("# Filename: /fake/path/.roo/rules/file2.txt") + expect(result).toContain("# Rules from /fake/path/.roo/rules/file2.txt:") expect(result).toContain("content of file2") - expect(result).toContain("# Filename: /fake/path/.roo/rules/file3.txt") + expect(result).toContain("# Rules from /fake/path/.roo/rules/file3.txt:") expect(result).toContain("content of file3") }) diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index 8399ea68110..b17fc1f3194 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -70,13 +70,14 @@ async function readTextFilesFromDirectory(dirPath: string): Promise): string { if (files.length === 0) return "" - const formattedContent = files - .map((file) => { - return `# Filename: ${file.filename}\n${file.content}` - }) - .join("\n\n") - - return `\n# Rules from ${path.relative(process.cwd(), dirPath)}:\n${formattedContent}\n` + return ( + "\n\n" + + files + .map((file) => { + return `# Rules from ${file.filename}:\n${file.content}:` + }) + .join("\n\n") + ) } /**