mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
[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 <lying@microsoft.com> Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
parent
fdcc679f9f
commit
2953baef97
4 changed files with 83 additions and 9 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue