diff --git a/src/core/prompts/sections/__tests__/custom-instructions.test.ts b/src/core/prompts/sections/__tests__/custom-instructions.test.ts index 4dbb51c845..5c47a48baa 100644 --- a/src/core/prompts/sections/__tests__/custom-instructions.test.ts +++ b/src/core/prompts/sections/__tests__/custom-instructions.test.ts @@ -106,7 +106,15 @@ describe("addCustomInstructions", () => { }) it("should combine all instruction types when provided", async () => { - mockedFs.readFile.mockResolvedValue("mode specific rules") + // Mock implementation to return different values based on the file path + mockedFs.readFile.mockImplementation(((filePath: any) => { + // For .clinerules-test-mode, return mode-specific rules + if (filePath.toString().includes(".clinerules-test-mode")) { + return Promise.resolve("mode specific rules") + } + // For any other read operation, return empty + return Promise.reject({ code: "ENOENT" }) + }) as any) const result = await addCustomInstructions( "mode instructions", diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index f076777585..823a87051e 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -16,12 +16,30 @@ async function safeReadFile(filePath: string): Promise { } } +async function findRuleInDirectory(dir: string, ruleFile: string): Promise { + const filePath = path.join(dir, ruleFile) + const content = await safeReadFile(filePath) + + if (content) { + return content + } + + // Check if we've reached the root directory + const parentDir = path.dirname(dir) + if (parentDir === dir) { + return "" + } + + // Recursively check parent directory + return findRuleInDirectory(parentDir, ruleFile) +} + export async function loadRuleFiles(cwd: string): Promise { const ruleFiles = [".clinerules", ".cursorrules", ".windsurfrules"] let combinedRules = "" for (const file of ruleFiles) { - const content = await safeReadFile(path.join(cwd, file)) + const content = await findRuleInDirectory(cwd, file) if (content) { combinedRules += `\n# Rules from ${file}:\n${content}\n` } @@ -30,6 +48,17 @@ export async function loadRuleFiles(cwd: string): Promise { return combinedRules } +async function findCustomInstructionsFile(dir: string, filePattern: string): Promise { + // First try to find as a direct file + const content = await findRuleInDirectory(dir, filePattern) + if (content) { + return content + } + + // If not found as a file, check if it's raw content + return filePattern.trim() +} + export async function addCustomInstructions( modeCustomInstructions: string, globalCustomInstructions: string, @@ -54,14 +83,16 @@ export async function addCustomInstructions( ) } - // Add global instructions first - if (typeof globalCustomInstructions === "string" && globalCustomInstructions.trim()) { - sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`) + // Add global instructions first - try to find as file or use raw content + const globalContent = await findCustomInstructionsFile(cwd, globalCustomInstructions) + if (globalContent) { + sections.push(`Global Instructions:\n${globalContent}`) } - // Add mode-specific instructions after - if (typeof modeCustomInstructions === "string" && modeCustomInstructions.trim()) { - sections.push(`Mode-specific Instructions:\n${modeCustomInstructions.trim()}`) + // Add mode-specific instructions - try to find as file or use raw content + const modeContent = await findCustomInstructionsFile(cwd, modeCustomInstructions) + if (modeContent) { + sections.push(`Mode-specific Instructions:\n${modeContent}`) } // Add rules - include both mode-specific and generic rules if they exist diff --git a/src/integrations/misc/open-file.ts b/src/integrations/misc/open-file.ts index 5698e919de..572082c251 100644 --- a/src/integrations/misc/open-file.ts +++ b/src/integrations/misc/open-file.ts @@ -23,6 +23,23 @@ export async function openImage(dataUri: string) { interface OpenFileOptions { create?: boolean content?: string + searchParents?: boolean + startFromWorkspace?: boolean +} + +async function findFileInParentDirs(searchPath: string, fileName: string): Promise { + try { + const fullPath = path.join(searchPath, fileName) + await vscode.workspace.fs.stat(vscode.Uri.file(fullPath)) + return fullPath + } catch { + const parentDir = path.dirname(searchPath) + if (parentDir === searchPath) { + // Hit root + return null + } + return findFileInParentDirs(parentDir, fileName) + } } export async function openFile(filePath: string, options: OpenFileOptions = {}) { @@ -34,7 +51,17 @@ export async function openFile(filePath: string, options: OpenFileOptions = {}) } // If path starts with ./, resolve it relative to workspace root - const fullPath = filePath.startsWith("./") ? path.join(workspaceRoot, filePath.slice(2)) : filePath + let fullPath = filePath.startsWith("./") ? path.join(workspaceRoot, filePath.slice(2)) : filePath + + // Handle recursive search + if (options.searchParents) { + const startDir = options.startFromWorkspace ? workspaceRoot : path.dirname(fullPath) + const fileName = path.basename(filePath) + const foundPath = await findFileInParentDirs(startDir, fileName) + if (foundPath) { + fullPath = foundPath + } + } const uri = vscode.Uri.file(fullPath) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 4011e15950..aaa291bc5a 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -462,6 +462,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { values: { create: true, content: JSON.stringify({ customModes: [] }, null, 2), + searchParents: true, + startFromWorkspace: true, }, }) setShowConfigMenu(false) @@ -808,6 +810,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { values: { create: true, content: "", + searchParents: true, + startFromWorkspace: true, }, }) }} @@ -915,6 +919,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { values: { create: true, content: "", + searchParents: true, + startFromWorkspace: true, }, }) }} @@ -970,6 +976,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { values: { create: true, content: "", + searchParents: true, + startFromWorkspace: true, }, }) }