Skip to content

Commit cb204f3

Browse files
committed
feat: add support for Agent Rules standard via AGENTS.md (#5966)
- Add AGENTS.md detection and loading in custom-instructions.ts - Add VS Code setting "roo-cline.useAgentRules" (default: true) - Integrate with existing custom instructions system - Add comprehensive test coverage for AGENTS.md functionality - Update README to mention Agent Rules support This enables unified natural language guidelines across different AI coding tools following the agent-rules.org standard.
1 parent 1b12108 commit cb204f3

File tree

8 files changed

+172
-4
lines changed

8 files changed

+172
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Make Roo Code work your way with:
104104
- [Custom Modes](https://docs.roocode.com/advanced-usage/custom-modes) for specialized tasks
105105
- [Local Models](https://docs.roocode.com/advanced-usage/local-models) for offline use
106106
- [Auto-Approval Settings](https://docs.roocode.com/advanced-usage/auto-approving-actions) for faster workflows
107+
- **Agent Rules Standard** - Roo Code supports the [Agent Rules](https://agent-rules.org/) standard via `AGENTS.md` files, enabling unified natural language guidelines across different AI coding tools
107108

108109
## Resources
109110

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,128 @@ describe("addCustomInstructions", () => {
505505
expect(result).toContain("Rules from .roorules-test-mode:\nmode specific rules")
506506
})
507507

508+
it("should load AGENTS.md when useAgentRules is true", async () => {
509+
// Simulate no .roo/rules-test-mode directory
510+
statMock.mockRejectedValueOnce({ code: "ENOENT" })
511+
512+
readFileMock.mockImplementation((filePath: PathLike) => {
513+
const pathStr = filePath.toString()
514+
if (pathStr.endsWith("AGENTS.md")) {
515+
return Promise.resolve("Agent rules from AGENTS.md file")
516+
}
517+
return Promise.reject({ code: "ENOENT" })
518+
})
519+
520+
const result = await addCustomInstructions(
521+
"mode instructions",
522+
"global instructions",
523+
"/fake/path",
524+
"test-mode",
525+
{ useAgentRules: true },
526+
)
527+
528+
expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
529+
expect(result).toContain("Agent rules from AGENTS.md file")
530+
expect(readFileMock).toHaveBeenCalledWith(expect.stringContaining("AGENTS.md"), "utf-8")
531+
})
532+
533+
it("should not load AGENTS.md when useAgentRules is false", async () => {
534+
// Simulate no .roo/rules-test-mode directory
535+
statMock.mockRejectedValueOnce({ code: "ENOENT" })
536+
537+
readFileMock.mockImplementation((filePath: PathLike) => {
538+
const pathStr = filePath.toString()
539+
if (pathStr.endsWith("AGENTS.md")) {
540+
return Promise.resolve("Agent rules from AGENTS.md file")
541+
}
542+
return Promise.reject({ code: "ENOENT" })
543+
})
544+
545+
const result = await addCustomInstructions(
546+
"mode instructions",
547+
"global instructions",
548+
"/fake/path",
549+
"test-mode",
550+
{ useAgentRules: false },
551+
)
552+
553+
expect(result).not.toContain("# Agent Rules Standard (AGENTS.md):")
554+
expect(result).not.toContain("Agent rules from AGENTS.md file")
555+
})
556+
557+
it("should load AGENTS.md by default when useAgentRules is undefined", async () => {
558+
// Simulate no .roo/rules-test-mode directory
559+
statMock.mockRejectedValueOnce({ code: "ENOENT" })
560+
561+
readFileMock.mockImplementation((filePath: PathLike) => {
562+
const pathStr = filePath.toString()
563+
if (pathStr.endsWith("AGENTS.md")) {
564+
return Promise.resolve("Agent rules from AGENTS.md file")
565+
}
566+
return Promise.reject({ code: "ENOENT" })
567+
})
568+
569+
const result = await addCustomInstructions(
570+
"mode instructions",
571+
"global instructions",
572+
"/fake/path",
573+
"test-mode",
574+
{}, // No useAgentRules specified
575+
)
576+
577+
expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
578+
expect(result).toContain("Agent rules from AGENTS.md file")
579+
})
580+
581+
it("should handle missing AGENTS.md gracefully", async () => {
582+
// Simulate no .roo/rules-test-mode directory
583+
statMock.mockRejectedValueOnce({ code: "ENOENT" })
584+
585+
readFileMock.mockRejectedValue({ code: "ENOENT" })
586+
587+
const result = await addCustomInstructions(
588+
"mode instructions",
589+
"global instructions",
590+
"/fake/path",
591+
"test-mode",
592+
{ useAgentRules: true },
593+
)
594+
595+
expect(result).toContain("Global Instructions:\nglobal instructions")
596+
expect(result).toContain("Mode-specific Instructions:\nmode instructions")
597+
expect(result).not.toContain("# Agent Rules Standard (AGENTS.md):")
598+
})
599+
600+
it("should include AGENTS.md content along with other rules", async () => {
601+
// Simulate no .roo/rules-test-mode directory
602+
statMock.mockRejectedValueOnce({ code: "ENOENT" })
603+
604+
readFileMock.mockImplementation((filePath: PathLike) => {
605+
const pathStr = filePath.toString()
606+
if (pathStr.endsWith("AGENTS.md")) {
607+
return Promise.resolve("Agent rules content")
608+
}
609+
if (pathStr.endsWith(".roorules")) {
610+
return Promise.resolve("Roo rules content")
611+
}
612+
return Promise.reject({ code: "ENOENT" })
613+
})
614+
615+
const result = await addCustomInstructions(
616+
"mode instructions",
617+
"global instructions",
618+
"/fake/path",
619+
"test-mode",
620+
{ useAgentRules: true },
621+
)
622+
623+
// Should contain both AGENTS.md and .roorules content
624+
expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
625+
expect(result).toContain("Agent rules content")
626+
expect(result).toContain("# Rules from .roorules:")
627+
expect(result).toContain("Roo rules content")
628+
})
629+
508630
it("should return empty string when no instructions provided", async () => {
509631
// Simulate no .roo/rules directory
510632
statMock.mockRejectedValueOnce({ code: "ENOENT" })

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,28 @@ export async function loadRuleFiles(cwd: string): Promise<string> {
195195
return ""
196196
}
197197

198+
/**
199+
* Load AGENTS.md file from the project root if it exists
200+
*/
201+
async function loadAgentRulesFile(cwd: string): Promise<string> {
202+
try {
203+
const agentsPath = path.join(cwd, "AGENTS.md")
204+
const content = await safeReadFile(agentsPath)
205+
if (content) {
206+
return `# Agent Rules Standard (AGENTS.md):\n${content}`
207+
}
208+
} catch (err) {
209+
// Silently ignore errors - AGENTS.md is optional
210+
}
211+
return ""
212+
}
213+
198214
export async function addCustomInstructions(
199215
modeCustomInstructions: string,
200216
globalCustomInstructions: string,
201217
cwd: string,
202218
mode: string,
203-
options: { language?: string; rooIgnoreInstructions?: string } = {},
219+
options: { language?: string; rooIgnoreInstructions?: string; useAgentRules?: boolean } = {},
204220
): Promise<string> {
205221
const sections = []
206222

@@ -278,6 +294,14 @@ export async function addCustomInstructions(
278294
rules.push(options.rooIgnoreInstructions)
279295
}
280296

297+
// Add AGENTS.md content if enabled (default: true)
298+
if (options.useAgentRules !== false) {
299+
const agentRulesContent = await loadAgentRulesFile(cwd)
300+
if (agentRulesContent && agentRulesContent.trim()) {
301+
rules.push(agentRulesContent.trim())
302+
}
303+
}
304+
281305
// Add generic rules
282306
const genericRuleContent = await loadRuleFiles(cwd)
283307
if (genericRuleContent && genericRuleContent.trim()) {

src/core/prompts/system.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ ${getSystemInfoSection(cwd)}
114114
115115
${getObjectiveSection(codeIndexManager, experiments)}
116116
117-
${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions })}`
117+
${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, {
118+
language: language ?? formatLanguage(vscode.env.language),
119+
rooIgnoreInstructions,
120+
useAgentRules: settings?.useAgentRules,
121+
})}`
118122

119123
return basePrompt
120124
}
@@ -172,7 +176,11 @@ export const SYSTEM_PROMPT = async (
172176
globalCustomInstructions || "",
173177
cwd,
174178
mode,
175-
{ language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions },
179+
{
180+
language: language ?? formatLanguage(vscode.env.language),
181+
rooIgnoreInstructions,
182+
useAgentRules: settings?.useAgentRules,
183+
},
176184
)
177185

178186
// For file-based prompts, don't include the tool sections

src/core/task/Task.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as path from "path"
2+
import * as vscode from "vscode"
23
import os from "os"
34
import crypto from "crypto"
45
import EventEmitter from "events"
@@ -1652,6 +1653,7 @@ export class Task extends EventEmitter<ClineEvents> {
16521653
maxReadFileLine !== -1,
16531654
{
16541655
maxConcurrentFileReads,
1656+
useAgentRules: vscode.workspace.getConfiguration("roo-cline").get<boolean>("useAgentRules") ?? true,
16551657
},
16561658
)
16571659
})()

src/core/webview/generateSystemPrompt.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as vscode from "vscode"
12
import { WebviewMessage } from "../../shared/WebviewMessage"
23
import { defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes"
34
import { buildApiHandler } from "../../api"
@@ -82,6 +83,10 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
8283
maxReadFileLine !== -1,
8384
{
8485
maxConcurrentFileReads,
86+
useAgentRules:
87+
provider.context.globalState.get("useAgentRules") ??
88+
vscode.workspace.getConfiguration("roo-cline").get<boolean>("useAgentRules") ??
89+
true,
8590
},
8691
)
8792

src/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,11 @@
386386
"type": "string",
387387
"default": "",
388388
"description": "%settings.autoImportSettingsPath.description%"
389+
},
390+
"roo-cline.useAgentRules": {
391+
"type": "boolean",
392+
"default": true,
393+
"description": "%settings.useAgentRules.description%"
389394
}
390395
}
391396
}

src/package.nls.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@
3636
"settings.vsCodeLmModelSelector.family.description": "The family of the language model (e.g. gpt-4)",
3737
"settings.customStoragePath.description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')",
3838
"settings.enableCodeActions.description": "Enable Roo Code quick fixes",
39-
"settings.autoImportSettingsPath.description": "Path to a RooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/roo-code-settings.json'). Leave empty to disable auto-import."
39+
"settings.autoImportSettingsPath.description": "Path to a RooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/roo-code-settings.json'). Leave empty to disable auto-import.",
40+
"settings.useAgentRules.description": "Enable support for Agent Rules standard via AGENTS.md file in project root. When enabled, natural language guidelines from AGENTS.md will be included in agent prompts for improved interoperability with other AI coding tools."
4041
}

0 commit comments

Comments
 (0)