mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: add support for AGENT.md alongside AGENTS.md (#6913)
Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
parent
7b0f489ea2
commit
8bd8f5dc59
4 changed files with 116 additions and 24 deletions
|
|
@ -775,6 +775,86 @@ describe("addCustomInstructions", () => {
|
|||
expect(readFileMock).toHaveBeenCalledWith(expect.stringContaining("AGENTS.md"), "utf-8")
|
||||
})
|
||||
|
||||
it("should load AGENT.md (singular) when AGENTS.md is not found", async () => {
|
||||
// Simulate no .roo/rules-test-mode directory
|
||||
statMock.mockRejectedValueOnce({ code: "ENOENT" })
|
||||
|
||||
// Mock lstat to indicate AGENTS.md doesn't exist but AGENT.md does
|
||||
lstatMock.mockImplementation((filePath: PathLike) => {
|
||||
const pathStr = filePath.toString()
|
||||
if (pathStr.endsWith("AGENTS.md")) {
|
||||
return Promise.reject({ code: "ENOENT" })
|
||||
}
|
||||
if (pathStr.endsWith("AGENT.md")) {
|
||||
return Promise.resolve({
|
||||
isSymbolicLink: vi.fn().mockReturnValue(false),
|
||||
})
|
||||
}
|
||||
return Promise.reject({ code: "ENOENT" })
|
||||
})
|
||||
|
||||
readFileMock.mockImplementation((filePath: PathLike) => {
|
||||
const pathStr = filePath.toString()
|
||||
if (pathStr.endsWith("AGENT.md")) {
|
||||
return Promise.resolve("Agent rules from AGENT.md file (singular)")
|
||||
}
|
||||
return Promise.reject({ code: "ENOENT" })
|
||||
})
|
||||
|
||||
const result = await addCustomInstructions(
|
||||
"mode instructions",
|
||||
"global instructions",
|
||||
"/fake/path",
|
||||
"test-mode",
|
||||
{ settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
|
||||
)
|
||||
|
||||
expect(result).toContain("# Agent Rules Standard (AGENT.md):")
|
||||
expect(result).toContain("Agent rules from AGENT.md file (singular)")
|
||||
expect(readFileMock).toHaveBeenCalledWith(expect.stringContaining("AGENT.md"), "utf-8")
|
||||
})
|
||||
|
||||
it("should prefer AGENTS.md over AGENT.md when both exist", async () => {
|
||||
// Simulate no .roo/rules-test-mode directory
|
||||
statMock.mockRejectedValueOnce({ code: "ENOENT" })
|
||||
|
||||
// Mock lstat to indicate both files exist
|
||||
lstatMock.mockImplementation((filePath: PathLike) => {
|
||||
const pathStr = filePath.toString()
|
||||
if (pathStr.endsWith("AGENTS.md") || pathStr.endsWith("AGENT.md")) {
|
||||
return Promise.resolve({
|
||||
isSymbolicLink: vi.fn().mockReturnValue(false),
|
||||
})
|
||||
}
|
||||
return Promise.reject({ code: "ENOENT" })
|
||||
})
|
||||
|
||||
readFileMock.mockImplementation((filePath: PathLike) => {
|
||||
const pathStr = filePath.toString()
|
||||
if (pathStr.endsWith("AGENTS.md")) {
|
||||
return Promise.resolve("Agent rules from AGENTS.md file (plural)")
|
||||
}
|
||||
if (pathStr.endsWith("AGENT.md")) {
|
||||
return Promise.resolve("Agent rules from AGENT.md file (singular)")
|
||||
}
|
||||
return Promise.reject({ code: "ENOENT" })
|
||||
})
|
||||
|
||||
const result = await addCustomInstructions(
|
||||
"mode instructions",
|
||||
"global instructions",
|
||||
"/fake/path",
|
||||
"test-mode",
|
||||
{ settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
|
||||
)
|
||||
|
||||
// Should contain AGENTS.md content (preferred) and not AGENT.md
|
||||
expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
|
||||
expect(result).toContain("Agent rules from AGENTS.md file (plural)")
|
||||
expect(result).not.toContain("Agent rules from AGENT.md file (singular)")
|
||||
expect(readFileMock).toHaveBeenCalledWith(expect.stringContaining("AGENTS.md"), "utf-8")
|
||||
})
|
||||
|
||||
it("should return empty string when no instructions provided", async () => {
|
||||
// Simulate no .roo/rules directory
|
||||
statMock.mockRejectedValueOnce({ code: "ENOENT" })
|
||||
|
|
|
|||
|
|
@ -217,40 +217,46 @@ export async function loadRuleFiles(cwd: string): Promise<string> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Load AGENTS.md file from the project root if it exists
|
||||
* Load AGENTS.md or AGENT.md file from the project root if it exists
|
||||
* Checks for both AGENTS.md (standard) and AGENT.md (alternative) for compatibility
|
||||
*/
|
||||
async function loadAgentRulesFile(cwd: string): Promise<string> {
|
||||
try {
|
||||
const agentsPath = path.join(cwd, "AGENTS.md")
|
||||
let resolvedPath = agentsPath
|
||||
// Try both filenames - AGENTS.md (standard) first, then AGENT.md (alternative)
|
||||
const filenames = ["AGENTS.md", "AGENT.md"]
|
||||
|
||||
// Check if AGENTS.md exists and handle symlinks
|
||||
for (const filename of filenames) {
|
||||
try {
|
||||
const stats = await fs.lstat(agentsPath)
|
||||
if (stats.isSymbolicLink()) {
|
||||
// Create a temporary fileInfo array to use with resolveSymLink
|
||||
const fileInfo: Array<{ originalPath: string; resolvedPath: string }> = []
|
||||
const agentPath = path.join(cwd, filename)
|
||||
let resolvedPath = agentPath
|
||||
|
||||
// Use the existing resolveSymLink function to handle symlink resolution
|
||||
await resolveSymLink(agentsPath, fileInfo, 0)
|
||||
// Check if file exists and handle symlinks
|
||||
try {
|
||||
const stats = await fs.lstat(agentPath)
|
||||
if (stats.isSymbolicLink()) {
|
||||
// Create a temporary fileInfo array to use with resolveSymLink
|
||||
const fileInfo: Array<{ originalPath: string; resolvedPath: string }> = []
|
||||
|
||||
// Extract the resolved path from fileInfo
|
||||
if (fileInfo.length > 0) {
|
||||
resolvedPath = fileInfo[0].resolvedPath
|
||||
// Use the existing resolveSymLink function to handle symlink resolution
|
||||
await resolveSymLink(agentPath, fileInfo, 0)
|
||||
|
||||
// Extract the resolved path from fileInfo
|
||||
if (fileInfo.length > 0) {
|
||||
resolvedPath = fileInfo[0].resolvedPath
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// If lstat fails (file doesn't exist), try next filename
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the content from the resolved path
|
||||
const content = await safeReadFile(resolvedPath)
|
||||
if (content) {
|
||||
return `# Agent Rules Standard (${filename}):\n${content}`
|
||||
}
|
||||
} catch (err) {
|
||||
// If lstat fails (file doesn't exist), return empty
|
||||
return ""
|
||||
// Silently ignore errors - agent rules files are optional
|
||||
}
|
||||
|
||||
// Read the content from the resolved path
|
||||
const content = await safeReadFile(resolvedPath)
|
||||
if (content) {
|
||||
return `# Agent Rules Standard (AGENTS.md):\n${content}`
|
||||
}
|
||||
} catch (err) {
|
||||
// Silently ignore errors - AGENTS.md is optional
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export class RooProtectedController {
|
|||
".vscode/**",
|
||||
".rooprotected", // For future use
|
||||
"AGENTS.md",
|
||||
"AGENT.md", // Alternative singular form for compatibility
|
||||
]
|
||||
|
||||
constructor(cwd: string) {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ describe("RooProtectedController", () => {
|
|||
expect(controller.isWriteProtected("AGENTS.md")).toBe(true)
|
||||
})
|
||||
|
||||
it("should protect AGENT.md file", () => {
|
||||
expect(controller.isWriteProtected("AGENT.md")).toBe(true)
|
||||
})
|
||||
|
||||
it("should not protect other files starting with .roo", () => {
|
||||
expect(controller.isWriteProtected(".roosettings")).toBe(false)
|
||||
expect(controller.isWriteProtected(".rooconfig")).toBe(false)
|
||||
|
|
@ -147,6 +151,7 @@ describe("RooProtectedController", () => {
|
|||
".vscode/**",
|
||||
".rooprotected",
|
||||
"AGENTS.md",
|
||||
"AGENT.md",
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue