Skip to content

Commit 2953bae

Browse files
cdlliuyYing Liumrubens
authored
[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]>
1 parent fdcc679 commit 2953bae

File tree

4 files changed

+83
-9
lines changed

4 files changed

+83
-9
lines changed

src/core/prompts/sections/__tests__/custom-instructions.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,15 @@ describe("addCustomInstructions", () => {
106106
})
107107

108108
it("should combine all instruction types when provided", async () => {
109-
mockedFs.readFile.mockResolvedValue("mode specific rules")
109+
// Mock implementation to return different values based on the file path
110+
mockedFs.readFile.mockImplementation(((filePath: any) => {
111+
// For .clinerules-test-mode, return mode-specific rules
112+
if (filePath.toString().includes(".clinerules-test-mode")) {
113+
return Promise.resolve("mode specific rules")
114+
}
115+
// For any other read operation, return empty
116+
return Promise.reject({ code: "ENOENT" })
117+
}) as any)
110118

111119
const result = await addCustomInstructions(
112120
"mode instructions",

src/core/prompts/sections/custom-instructions.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,30 @@ async function safeReadFile(filePath: string): Promise<string> {
1616
}
1717
}
1818

19+
async function findRuleInDirectory(dir: string, ruleFile: string): Promise<string> {
20+
const filePath = path.join(dir, ruleFile)
21+
const content = await safeReadFile(filePath)
22+
23+
if (content) {
24+
return content
25+
}
26+
27+
// Check if we've reached the root directory
28+
const parentDir = path.dirname(dir)
29+
if (parentDir === dir) {
30+
return ""
31+
}
32+
33+
// Recursively check parent directory
34+
return findRuleInDirectory(parentDir, ruleFile)
35+
}
36+
1937
export async function loadRuleFiles(cwd: string): Promise<string> {
2038
const ruleFiles = [".clinerules", ".cursorrules", ".windsurfrules"]
2139
let combinedRules = ""
2240

2341
for (const file of ruleFiles) {
24-
const content = await safeReadFile(path.join(cwd, file))
42+
const content = await findRuleInDirectory(cwd, file)
2543
if (content) {
2644
combinedRules += `\n# Rules from ${file}:\n${content}\n`
2745
}
@@ -30,6 +48,17 @@ export async function loadRuleFiles(cwd: string): Promise<string> {
3048
return combinedRules
3149
}
3250

51+
async function findCustomInstructionsFile(dir: string, filePattern: string): Promise<string> {
52+
// First try to find as a direct file
53+
const content = await findRuleInDirectory(dir, filePattern)
54+
if (content) {
55+
return content
56+
}
57+
58+
// If not found as a file, check if it's raw content
59+
return filePattern.trim()
60+
}
61+
3362
export async function addCustomInstructions(
3463
modeCustomInstructions: string,
3564
globalCustomInstructions: string,
@@ -54,14 +83,16 @@ export async function addCustomInstructions(
5483
)
5584
}
5685

57-
// Add global instructions first
58-
if (typeof globalCustomInstructions === "string" && globalCustomInstructions.trim()) {
59-
sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`)
86+
// Add global instructions first - try to find as file or use raw content
87+
const globalContent = await findCustomInstructionsFile(cwd, globalCustomInstructions)
88+
if (globalContent) {
89+
sections.push(`Global Instructions:\n${globalContent}`)
6090
}
6191

62-
// Add mode-specific instructions after
63-
if (typeof modeCustomInstructions === "string" && modeCustomInstructions.trim()) {
64-
sections.push(`Mode-specific Instructions:\n${modeCustomInstructions.trim()}`)
92+
// Add mode-specific instructions - try to find as file or use raw content
93+
const modeContent = await findCustomInstructionsFile(cwd, modeCustomInstructions)
94+
if (modeContent) {
95+
sections.push(`Mode-specific Instructions:\n${modeContent}`)
6596
}
6697

6798
// Add rules - include both mode-specific and generic rules if they exist

src/integrations/misc/open-file.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ export async function openImage(dataUri: string) {
2323
interface OpenFileOptions {
2424
create?: boolean
2525
content?: string
26+
searchParents?: boolean
27+
startFromWorkspace?: boolean
28+
}
29+
30+
async function findFileInParentDirs(searchPath: string, fileName: string): Promise<string | null> {
31+
try {
32+
const fullPath = path.join(searchPath, fileName)
33+
await vscode.workspace.fs.stat(vscode.Uri.file(fullPath))
34+
return fullPath
35+
} catch {
36+
const parentDir = path.dirname(searchPath)
37+
if (parentDir === searchPath) {
38+
// Hit root
39+
return null
40+
}
41+
return findFileInParentDirs(parentDir, fileName)
42+
}
2643
}
2744

2845
export async function openFile(filePath: string, options: OpenFileOptions = {}) {
@@ -34,7 +51,17 @@ export async function openFile(filePath: string, options: OpenFileOptions = {})
3451
}
3552

3653
// If path starts with ./, resolve it relative to workspace root
37-
const fullPath = filePath.startsWith("./") ? path.join(workspaceRoot, filePath.slice(2)) : filePath
54+
let fullPath = filePath.startsWith("./") ? path.join(workspaceRoot, filePath.slice(2)) : filePath
55+
56+
// Handle recursive search
57+
if (options.searchParents) {
58+
const startDir = options.startFromWorkspace ? workspaceRoot : path.dirname(fullPath)
59+
const fileName = path.basename(filePath)
60+
const foundPath = await findFileInParentDirs(startDir, fileName)
61+
if (foundPath) {
62+
fullPath = foundPath
63+
}
64+
}
3865

3966
const uri = vscode.Uri.file(fullPath)
4067

webview-ui/src/components/prompts/PromptsView.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
462462
values: {
463463
create: true,
464464
content: JSON.stringify({ customModes: [] }, null, 2),
465+
searchParents: true,
466+
startFromWorkspace: true,
465467
},
466468
})
467469
setShowConfigMenu(false)
@@ -808,6 +810,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
808810
values: {
809811
create: true,
810812
content: "",
813+
searchParents: true,
814+
startFromWorkspace: true,
811815
},
812816
})
813817
}}
@@ -915,6 +919,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
915919
values: {
916920
create: true,
917921
content: "",
922+
searchParents: true,
923+
startFromWorkspace: true,
918924
},
919925
})
920926
}}
@@ -970,6 +976,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
970976
values: {
971977
create: true,
972978
content: "",
979+
searchParents: true,
980+
startFromWorkspace: true,
973981
},
974982
})
975983
}

0 commit comments

Comments
 (0)