Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/core/prompts/__tests__/system-prompt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,9 @@ describe("SYSTEM_PROMPT", () => {

it("should exclude update_todo_list tool when todoListEnabled is false", async () => {
const settings = {
maxConcurrentFileReads: 5,
todoListEnabled: false,
useAgentRules: true,
}

const prompt = await SYSTEM_PROMPT(
Expand Down Expand Up @@ -607,7 +609,9 @@ describe("SYSTEM_PROMPT", () => {

it("should include update_todo_list tool when todoListEnabled is true", async () => {
const settings = {
maxConcurrentFileReads: 5,
todoListEnabled: true,
useAgentRules: true,
}

const prompt = await SYSTEM_PROMPT(
Expand Down Expand Up @@ -636,7 +640,9 @@ describe("SYSTEM_PROMPT", () => {

it("should include update_todo_list tool when todoListEnabled is undefined", async () => {
const settings = {
// todoListEnabled not set
maxConcurrentFileReads: 5,
todoListEnabled: true,
useAgentRules: true,
}

const prompt = await SYSTEM_PROMPT(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ describe("custom-instructions global .roo support", () => {
mockReadFile
.mockResolvedValueOnce("global mode rule content")
.mockResolvedValueOnce("project mode rule content")
.mockResolvedValueOnce("") // AGENTS.md file (empty)
.mockResolvedValueOnce("") // .roorules legacy file (empty)
.mockResolvedValueOnce("") // .clinerules legacy file (empty)

Expand All @@ -218,6 +219,7 @@ describe("custom-instructions global .roo support", () => {
// Mock legacy mode file reading
mockReadFile
.mockResolvedValueOnce("legacy mode rule content") // .roorules-code
.mockResolvedValueOnce("") // AGENTS.md file (empty)
.mockResolvedValueOnce("") // generic .roorules (empty)
.mockResolvedValueOnce("") // generic .clinerules (empty)

Expand Down
123 changes: 123 additions & 0 deletions src/core/prompts/sections/__tests__/custom-instructions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,129 @@ describe("addCustomInstructions", () => {
expect(result).toContain("Rules from .roorules-test-mode:\nmode specific rules")
})

it("should load AGENTS.md when settings.useAgentRules is true", async () => {
// Simulate no .roo/rules-test-mode directory
statMock.mockRejectedValueOnce({ code: "ENOENT" })

readFileMock.mockImplementation((filePath: PathLike) => {
const pathStr = filePath.toString()
if (pathStr.endsWith("AGENTS.md")) {
return Promise.resolve("Agent rules from AGENTS.md file")
}
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 (AGENTS.md):")
expect(result).toContain("Agent rules from AGENTS.md file")
expect(readFileMock).toHaveBeenCalledWith(expect.stringContaining("AGENTS.md"), "utf-8")
})

it("should not load AGENTS.md when settings.useAgentRules is false", async () => {
// Simulate no .roo/rules-test-mode directory
statMock.mockRejectedValueOnce({ code: "ENOENT" })

readFileMock.mockImplementation((filePath: PathLike) => {
const pathStr = filePath.toString()
if (pathStr.endsWith("AGENTS.md")) {
return Promise.resolve("Agent rules from AGENTS.md file")
}
return Promise.reject({ code: "ENOENT" })
})

const result = await addCustomInstructions(
"mode instructions",
"global instructions",
"/fake/path",
"test-mode",
{ settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: false } },
)

expect(result).not.toContain("# Agent Rules Standard (AGENTS.md):")
expect(result).not.toContain("Agent rules from AGENTS.md file")
})

it("should load AGENTS.md by default when settings.useAgentRules is undefined", async () => {
// Simulate no .roo/rules-test-mode directory
statMock.mockRejectedValueOnce({ code: "ENOENT" })

readFileMock.mockImplementation((filePath: PathLike) => {
const pathStr = filePath.toString()
if (pathStr.endsWith("AGENTS.md")) {
return Promise.resolve("Agent rules from AGENTS.md file")
}
return Promise.reject({ code: "ENOENT" })
})

const result = await addCustomInstructions(
"mode instructions",
"global instructions",
"/fake/path",
"test-mode",
{}, // No settings.useAgentRules specified
)

expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
expect(result).toContain("Agent rules from AGENTS.md file")
expect(readFileMock).toHaveBeenCalledWith(expect.stringContaining("AGENTS.md"), "utf-8")
})

it("should handle missing AGENTS.md gracefully", async () => {
// Simulate no .roo/rules-test-mode directory
statMock.mockRejectedValueOnce({ code: "ENOENT" })

readFileMock.mockRejectedValue({ code: "ENOENT" })

const result = await addCustomInstructions(
"mode instructions",
"global instructions",
"/fake/path",
"test-mode",
{ settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
)

expect(result).toContain("Global Instructions:\nglobal instructions")
expect(result).toContain("Mode-specific Instructions:\nmode instructions")
expect(result).not.toContain("# Agent Rules Standard (AGENTS.md):")
})

it("should include AGENTS.md content along with other rules", async () => {
// Simulate no .roo/rules-test-mode directory
statMock.mockRejectedValueOnce({ code: "ENOENT" })

readFileMock.mockImplementation((filePath: PathLike) => {
const pathStr = filePath.toString()
if (pathStr.endsWith("AGENTS.md")) {
return Promise.resolve("Agent rules content")
}
if (pathStr.endsWith(".roorules")) {
return Promise.resolve("Roo rules content")
}
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 both AGENTS.md and .roorules content
expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
expect(result).toContain("Agent rules content")
expect(result).toContain("# Rules from .roorules:")
expect(result).toContain("Roo rules content")
})

it("should return empty string when no instructions provided", async () => {
// Simulate no .roo/rules directory
statMock.mockRejectedValueOnce({ code: "ENOENT" })
Expand Down
32 changes: 31 additions & 1 deletion src/core/prompts/sections/custom-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Dirent } from "fs"

import { isLanguage } from "@roo-code/types"

import type { SystemPromptSettings } from "../types"

import { LANGUAGES } from "../../../shared/language"
import { getRooDirectoriesForCwd, getGlobalRooDirectory } from "../../../services/roo-config"

Expand Down Expand Up @@ -214,12 +216,32 @@ export async function loadRuleFiles(cwd: string): Promise<string> {
return ""
}

/**
* Load AGENTS.md file from the project root if it exists
*/
async function loadAgentRulesFile(cwd: string): Promise<string> {
try {
const agentsPath = path.join(cwd, "AGENTS.md")
const content = await safeReadFile(agentsPath)
if (content) {
return `# Agent Rules Standard (AGENTS.md):\n${content}`
}
} catch (err) {
// Silently ignore errors - AGENTS.md is optional
}
return ""
}

export async function addCustomInstructions(
modeCustomInstructions: string,
globalCustomInstructions: string,
cwd: string,
mode: string,
options: { language?: string; rooIgnoreInstructions?: string; settings?: Record<string, any> } = {},
options: {
language?: string
rooIgnoreInstructions?: string
settings?: SystemPromptSettings
} = {},
): Promise<string> {
const sections = []

Expand Down Expand Up @@ -297,6 +319,14 @@ export async function addCustomInstructions(
rules.push(options.rooIgnoreInstructions)
}

// Add AGENTS.md content if enabled (default: true)
if (options.settings?.useAgentRules !== false) {
const agentRulesContent = await loadAgentRulesFile(cwd)
if (agentRulesContent && agentRulesContent.trim()) {
rules.push(agentRulesContent.trim())
}
}

// Add generic rules
const genericRuleContent = await loadRuleFiles(cwd)
if (genericRuleContent && genericRuleContent.trim()) {
Expand Down
18 changes: 14 additions & 4 deletions src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as os from "os"

import type { ModeConfig, PromptComponent, CustomModePrompts, TodoItem } from "@roo-code/types"

import type { SystemPromptSettings } from "./types"

import { Mode, modes, defaultModeSlug, getModeBySlug, getGroupName, getModeSelection } from "../../shared/modes"
import { DiffStrategy } from "../../shared/tools"
import { formatLanguage } from "../../shared/language"
Expand Down Expand Up @@ -57,7 +59,7 @@ async function generatePrompt(
language?: string,
rooIgnoreInstructions?: string,
partialReadsEnabled?: boolean,
settings?: Record<string, any>,
settings?: SystemPromptSettings,
todoList?: TodoItem[],
): Promise<string> {
if (!context) {
Expand Down Expand Up @@ -119,7 +121,11 @@ ${getSystemInfoSection(cwd)}

${getObjectiveSection(codeIndexManager, experiments)}

${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings })}`
${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, {
language: language ?? formatLanguage(vscode.env.language),
rooIgnoreInstructions,
settings,
})}`

return basePrompt
}
Expand All @@ -141,7 +147,7 @@ export const SYSTEM_PROMPT = async (
language?: string,
rooIgnoreInstructions?: string,
partialReadsEnabled?: boolean,
settings?: Record<string, any>,
settings?: SystemPromptSettings,
todoList?: TodoItem[],
): Promise<string> => {
if (!context) {
Expand Down Expand Up @@ -177,7 +183,11 @@ export const SYSTEM_PROMPT = async (
globalCustomInstructions || "",
cwd,
mode,
{ language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings },
{
language: language ?? formatLanguage(vscode.env.language),
rooIgnoreInstructions,
settings,
},
)

// For file-based prompts, don't include the tool sections
Expand Down
8 changes: 8 additions & 0 deletions src/core/prompts/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Settings passed to system prompt generation functions
*/
export interface SystemPromptSettings {
maxConcurrentFileReads: number
todoListEnabled: boolean
useAgentRules: boolean
}
1 change: 1 addition & 0 deletions src/core/protect/RooProtectedController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class RooProtectedController {
".roo/**",
".vscode/**",
".rooprotected", // For future use
"AGENTS.md",
]

constructor(cwd: string) {
Expand Down
5 changes: 5 additions & 0 deletions src/core/protect/__tests__/RooProtectedController.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ describe("RooProtectedController", () => {
expect(controller.isWriteProtected(".vscode/tasks.json")).toBe(true)
})

it("should protect AGENTS.md file", () => {
expect(controller.isWriteProtected("AGENTS.md")).toBe(true)
})

it("should not protect other files starting with .roo", () => {
expect(controller.isWriteProtected(".roosettings")).toBe(false)
expect(controller.isWriteProtected(".rooconfig")).toBe(false)
Expand Down Expand Up @@ -142,6 +146,7 @@ describe("RooProtectedController", () => {
".roo/**",
".vscode/**",
".rooprotected",
"AGENTS.md",
])
})
})
Expand Down
6 changes: 4 additions & 2 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as path from "path"
import * as vscode from "vscode"
import os from "os"
import crypto from "crypto"
import EventEmitter from "events"
Expand Down Expand Up @@ -1660,8 +1661,9 @@ export class Task extends EventEmitter<ClineEvents> {
rooIgnoreInstructions,
maxReadFileLine !== -1,
{
maxConcurrentFileReads,
todoListEnabled: apiConfiguration?.todoListEnabled,
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
useAgentRules: vscode.workspace.getConfiguration("roo-cline").get<boolean>("useAgentRules") ?? true,
},
)
})()
Expand Down
5 changes: 4 additions & 1 deletion src/core/webview/generateSystemPrompt.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as vscode from "vscode"
import { WebviewMessage } from "../../shared/WebviewMessage"
import { defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes"
import { buildApiHandler } from "../../api"
Expand Down Expand Up @@ -81,7 +82,9 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
rooIgnoreInstructions,
maxReadFileLine !== -1,
{
maxConcurrentFileReads,
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
useAgentRules: vscode.workspace.getConfiguration("roo-cline").get<boolean>("useAgentRules") ?? true,
},
)

Expand Down
5 changes: 5 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,11 @@
"type": "string",
"default": "",
"description": "%settings.autoImportSettingsPath.description%"
},
"roo-cline.useAgentRules": {
"type": "boolean",
"default": true,
"description": "%settings.useAgentRules.description%"
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/package.nls.ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@
"settings.vsCodeLmModelSelector.family.description": "La família del model de llenguatge (p. ex. gpt-4)",
"settings.customStoragePath.description": "Ruta d'emmagatzematge personalitzada. Deixeu-la buida per utilitzar la ubicació predeterminada. Admet rutes absolutes (p. ex. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Habilitar correccions ràpides de Roo Code.",
"settings.autoImportSettingsPath.description": "Ruta a un fitxer de configuració de RooCode per importar automàticament en iniciar l'extensió. Admet rutes absolutes i rutes relatives al directori d'inici (per exemple, '~/Documents/roo-code-settings.json'). Deixeu-ho en blanc per desactivar la importació automàtica."
"settings.autoImportSettingsPath.description": "Ruta a un fitxer de configuració de RooCode per importar automàticament en iniciar l'extensió. Admet rutes absolutes i rutes relatives al directori d'inici (per exemple, '~/Documents/roo-code-settings.json'). Deixeu-ho en blanc per desactivar la importació automàtica.",
"settings.useAgentRules.description": "Activa la càrrega de fitxers AGENTS.md per a regles específiques de l'agent (vegeu https://agent-rules.org/)"
}
3 changes: 2 additions & 1 deletion src/package.nls.de.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@
"settings.vsCodeLmModelSelector.family.description": "Die Familie des Sprachmodells (z.B. gpt-4)",
"settings.customStoragePath.description": "Benutzerdefinierter Speicherpfad. Leer lassen, um den Standardspeicherort zu verwenden. Unterstützt absolute Pfade (z.B. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Roo Code Schnelle Problembehebung aktivieren.",
"settings.autoImportSettingsPath.description": "Pfad zu einer RooCode-Konfigurationsdatei, die beim Start der Erweiterung automatisch importiert wird. Unterstützt absolute Pfade und Pfade relativ zum Home-Verzeichnis (z.B. '~/Documents/roo-code-settings.json'). Leer lassen, um den automatischen Import zu deaktivieren."
"settings.autoImportSettingsPath.description": "Pfad zu einer RooCode-Konfigurationsdatei, die beim Start der Erweiterung automatisch importiert wird. Unterstützt absolute Pfade und Pfade relativ zum Home-Verzeichnis (z.B. '~/Documents/roo-code-settings.json'). Leer lassen, um den automatischen Import zu deaktivieren.",
"settings.useAgentRules.description": "Aktiviert das Laden von AGENTS.md-Dateien für agentenspezifische Regeln (siehe https://agent-rules.org/)"
}
3 changes: 2 additions & 1 deletion src/package.nls.es.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@
"settings.vsCodeLmModelSelector.family.description": "La familia del modelo de lenguaje (ej. gpt-4)",
"settings.customStoragePath.description": "Ruta de almacenamiento personalizada. Dejar vacío para usar la ubicación predeterminada. Admite rutas absolutas (ej. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Habilitar correcciones rápidas de Roo Code.",
"settings.autoImportSettingsPath.description": "Ruta a un archivo de configuración de RooCode para importar automáticamente al iniciar la extensión. Admite rutas absolutas y rutas relativas al directorio de inicio (por ejemplo, '~/Documents/roo-code-settings.json'). Dejar vacío para desactivar la importación automática."
"settings.autoImportSettingsPath.description": "Ruta a un archivo de configuración de RooCode para importar automáticamente al iniciar la extensión. Admite rutas absolutas y rutas relativas al directorio de inicio (por ejemplo, '~/Documents/roo-code-settings.json'). Dejar vacío para desactivar la importación automática.",
"settings.useAgentRules.description": "Habilita la carga de archivos AGENTS.md para reglas específicas del agente (ver https://agent-rules.org/)"
}
Loading