Просмотр исходного кода

[clinerules] search clinerule in parent folders which make it easier to share within a github repo (#1832)

* [clinerules] search clinerule in parent folders which make it easier to share common clinerules for a git repo

* fix indent

* Fix test

---------

Co-authored-by: Ying Liu <[email protected]>
Co-authored-by: Matt Rubens <[email protected]>
ying 9 месяцев назад
Родитель
Сommit
2953baef97

+ 9 - 1
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",

+ 38 - 7
src/core/prompts/sections/custom-instructions.ts

@@ -16,12 +16,30 @@ async function safeReadFile(filePath: string): Promise<string> {
 	}
 }
 
+async function findRuleInDirectory(dir: string, ruleFile: string): Promise<string> {
+	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<string> {
 	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<string> {
 	return combinedRules
 }
 
+async function findCustomInstructionsFile(dir: string, filePattern: string): Promise<string> {
+	// 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

+ 28 - 1
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<string | null> {
+	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)
 

+ 8 - 0
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,
 												},
 											})
 										}