Skip to content

Commit d3e62e0

Browse files
committed
rename roo rules (#80)
* rename .roo -> .pearai-agent * pearai-agent-ignore * pearai-agent-modes
1 parent 291a9c0 commit d3e62e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+621
-726
lines changed

.vscodeignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ jest.*
2929
.roomodes
3030
.rooignore
3131
.roo/**
32+
.pearai-agent/**
33+
.pearai-agent-ignore
34+
.pearai-agent-modes
3235
benchmark/**
3336
cline_docs/**
3437
e2e/**

src/core/config/CustomModesManager.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { fileExistsAtPath } from "../../utils/fs"
77
import { arePathsEqual, getWorkspacePath } from "../../utils/path"
88
import { logger } from "../../utils/logging"
99
import { GlobalFileNames } from "../../shared/globalFileNames"
10+
import { AGENT_MODES_FILE_NAME } from "../../shared/constants"
1011

11-
const ROOMODES_FILENAME = ".roomodes"
12-
12+
const ROOMODES_FILENAME = AGENT_MODES_FILE_NAME
1313
export class CustomModesManager {
1414
private static readonly cacheTTL = 10_000
1515

@@ -154,11 +154,11 @@ export class CustomModesManager {
154154
return
155155
}
156156

157-
// Get modes from .roomodes if it exists (takes precedence)
157+
// Get modes from .pearai-agent-modes if it exists (takes precedence)
158158
const roomodesPath = await this.getWorkspaceRoomodes()
159159
const roomodesModes = roomodesPath ? await this.loadModesFromFile(roomodesPath) : []
160160

161-
// Merge modes from both sources (.roomodes takes precedence)
161+
// Merge modes from both sources (.pearai-agent-modes takes precedence)
162162
const mergedModes = await this.mergeCustomModes(roomodesModes, result.data.customModes)
163163
await this.context.globalState.update("customModes", mergedModes)
164164
this.clearCache()
@@ -167,7 +167,7 @@ export class CustomModesManager {
167167
}),
168168
)
169169

170-
// Watch .roomodes file if it exists
170+
// Watch .pearai-agent-modes file if it exists
171171
const roomodesPath = await this.getWorkspaceRoomodes()
172172

173173
if (roomodesPath) {
@@ -176,7 +176,7 @@ export class CustomModesManager {
176176
if (arePathsEqual(document.uri.fsPath, roomodesPath)) {
177177
const settingsModes = await this.loadModesFromFile(settingsPath)
178178
const roomodesModes = await this.loadModesFromFile(roomodesPath)
179-
// .roomodes takes precedence
179+
// .pearai-agent-modes takes precedence
180180
const mergedModes = await this.mergeCustomModes(roomodesModes, settingsModes)
181181
await this.context.globalState.update("customModes", mergedModes)
182182
this.clearCache()
@@ -199,7 +199,7 @@ export class CustomModesManager {
199199
const settingsPath = await this.getCustomModesFilePath()
200200
const settingsModes = await this.loadModesFromFile(settingsPath)
201201

202-
// Get modes from .roomodes if it exists.
202+
// Get modes from .pearai-agent-modes if it exists
203203
const roomodesPath = await this.getWorkspaceRoomodes()
204204
const roomodesModes = roomodesPath ? await this.loadModesFromFile(roomodesPath) : []
205205

src/core/config/__tests__/CustomModesManager.test.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe("CustomModesManager", () => {
2323
// Use path.sep to ensure correct path separators for the current platform
2424
const mockStoragePath = `${path.sep}mock${path.sep}settings`
2525
const mockSettingsPath = path.join(mockStoragePath, "settings", GlobalFileNames.customModes)
26-
const mockRoomodes = `${path.sep}mock${path.sep}workspace${path.sep}.roomodes`
26+
const mockRoomodes = `${path.sep}mock${path.sep}workspace${path.sep}.pearai-agent-ignore`
2727

2828
beforeEach(() => {
2929
mockOnUpdate = jest.fn()
@@ -60,7 +60,7 @@ describe("CustomModesManager", () => {
6060
})
6161

6262
describe("getCustomModes", () => {
63-
it("should merge modes with .roomodes taking precedence", async () => {
63+
it("should merge modes with .pearai-agent-ignore taking precedence", async () => {
6464
const settingsModes = [
6565
{ slug: "mode1", name: "Mode 1", roleDefinition: "Role 1", groups: ["read"] },
6666
{ slug: "mode2", name: "Mode 2", roleDefinition: "Role 2", groups: ["read"] },
@@ -87,13 +87,13 @@ describe("CustomModesManager", () => {
8787
expect(modes).toHaveLength(3)
8888
expect(modes.map((m) => m.slug)).toEqual(["mode2", "mode3", "mode1"])
8989

90-
// mode2 should come from .roomodes since it takes precedence
90+
// mode2 should come from .pearai-agent-ignore since it takes precedence
9191
const mode2 = modes.find((m) => m.slug === "mode2")
9292
expect(mode2?.name).toBe("Mode 2 Override")
9393
expect(mode2?.roleDefinition).toBe("Role 2 Override")
9494
})
9595

96-
it("should handle missing .roomodes file", async () => {
96+
it("should handle missing .pearai-agent-ignore file", async () => {
9797
const settingsModes = [{ slug: "mode1", name: "Mode 1", roleDefinition: "Role 1", groups: ["read"] }]
9898

9999
;(fileExistsAtPath as jest.Mock).mockImplementation(async (path: string) => {
@@ -112,7 +112,7 @@ describe("CustomModesManager", () => {
112112
expect(modes[0].slug).toBe("mode1")
113113
})
114114

115-
it("should handle invalid JSON in .roomodes", async () => {
115+
it("should handle invalid JSON in .pearai-agent-ignore", async () => {
116116
const settingsModes = [{ slug: "mode1", name: "Mode 1", roleDefinition: "Role 1", groups: ["read"] }]
117117

118118
;(fs.readFile as jest.Mock).mockImplementation(async (path: string) => {
@@ -127,7 +127,7 @@ describe("CustomModesManager", () => {
127127

128128
const modes = await manager.getCustomModes()
129129

130-
// Should fall back to settings modes when .roomodes is invalid
130+
// Should fall back to settings modes when .pearai-agent-ignore is invalid
131131
expect(modes).toHaveLength(1)
132132
expect(modes[0].slug).toBe("mode1")
133133
})
@@ -385,7 +385,7 @@ describe("CustomModesManager", () => {
385385
})
386386

387387
describe("updateCustomMode", () => {
388-
it("should update mode in settings file while preserving .roomodes precedence", async () => {
388+
it("should update mode in settings file while preserving .pearai-agent-ignore precedence", async () => {
389389
const newMode: ModeConfig = {
390390
slug: "mode1",
391391
name: "Updated Mode 1",
@@ -449,13 +449,13 @@ describe("CustomModesManager", () => {
449449
}),
450450
)
451451

452-
// Should update global state with merged modes where .roomodes takes precedence
452+
// Should update global state with merged modes where .pearai-agent-ignore takes precedence
453453
expect(mockContext.globalState.update).toHaveBeenCalledWith(
454454
"customModes",
455455
expect.arrayContaining([
456456
expect.objectContaining({
457457
slug: "mode1",
458-
name: "Roomodes Mode 1", // .roomodes version should take precedence
458+
name: "Roomodes Mode 1", // .pearai-agent-ignore version should take precedence
459459
source: "project",
460460
}),
461461
]),
@@ -465,7 +465,7 @@ describe("CustomModesManager", () => {
465465
expect(mockOnUpdate).toHaveBeenCalled()
466466
})
467467

468-
it("creates .roomodes file when adding project-specific mode", async () => {
468+
it("creates .pearai-agent-ignore file when adding project-specific mode", async () => {
469469
const projectMode: ModeConfig = {
470470
slug: "project-mode",
471471
name: "Project Mode",
@@ -474,7 +474,7 @@ describe("CustomModesManager", () => {
474474
source: "project",
475475
}
476476

477-
// Mock .roomodes to not exist initially
477+
// Mock .pearai-agent-ignore to not exist initially
478478
let roomodesContent: any = null
479479
;(fileExistsAtPath as jest.Mock).mockImplementation(async (path: string) => {
480480
return path === mockSettingsPath
@@ -500,7 +500,7 @@ describe("CustomModesManager", () => {
500500

501501
await manager.updateCustomMode("project-mode", projectMode)
502502

503-
// Verify .roomodes was created with the project mode
503+
// Verify .pearai-agent-ignore was created with the project mode
504504
expect(fs.writeFile).toHaveBeenCalledWith(
505505
expect.any(String), // Don't check exact path as it may have different separators on different platforms
506506
expect.stringContaining("project-mode"),
@@ -511,7 +511,7 @@ describe("CustomModesManager", () => {
511511
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
512512
expect(path.normalize(writeCall[0])).toBe(path.normalize(mockRoomodes))
513513

514-
// Verify the content written to .roomodes
514+
// Verify the content written to .pearai-agent-ignore
515515
expect(roomodesContent).toEqual({
516516
customModes: [
517517
expect.objectContaining({

src/core/ignore/RooIgnoreController.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { fileExistsAtPath } from "../../utils/fs"
33
import fs from "fs/promises"
44
import ignore, { Ignore } from "ignore"
55
import * as vscode from "vscode"
6+
import { AGENT_IGNORE_FILE_NAME } from "../../shared/constants"
67

78
export const LOCK_TEXT_SYMBOL = "\u{1F512}"
8-
99
/**
1010
* Controls LLM access to files by enforcing ignore patterns.
1111
* Designed to be instantiated once in Cline.ts and passed to file manipulation services.
12-
* Uses the 'ignore' library to support standard .gitignore syntax in .rooignore files.
12+
* Uses the 'ignore' library to support standard .gitignore syntax in .pearai-agent-ignore files.
1313
*/
1414
export class RooIgnoreController {
1515
private cwd: string
@@ -21,7 +21,7 @@ export class RooIgnoreController {
2121
this.cwd = cwd
2222
this.ignoreInstance = ignore()
2323
this.rooIgnoreContent = undefined
24-
// Set up file watcher for .rooignore
24+
// Set up file watcher for .pearai-agent-ignore
2525
this.setupFileWatcher()
2626
}
2727

@@ -34,10 +34,10 @@ export class RooIgnoreController {
3434
}
3535

3636
/**
37-
* Set up the file watcher for .rooignore changes
37+
* Set up the file watcher for .pearai-agent-ignore changes
3838
*/
3939
private setupFileWatcher(): void {
40-
const rooignorePattern = new vscode.RelativePattern(this.cwd, ".rooignore")
40+
const rooignorePattern = new vscode.RelativePattern(this.cwd, AGENT_IGNORE_FILE_NAME)
4141
const fileWatcher = vscode.workspace.createFileSystemWatcher(rooignorePattern)
4242

4343
// Watch for changes and updates
@@ -58,24 +58,24 @@ export class RooIgnoreController {
5858
}
5959

6060
/**
61-
* Load custom patterns from .rooignore if it exists
61+
* Load custom patterns from .pearai-agent-ignore if it exists
6262
*/
6363
private async loadRooIgnore(): Promise<void> {
6464
try {
6565
// Reset ignore instance to prevent duplicate patterns
6666
this.ignoreInstance = ignore()
67-
const ignorePath = path.join(this.cwd, ".rooignore")
67+
const ignorePath = path.join(this.cwd, AGENT_IGNORE_FILE_NAME)
6868
if (await fileExistsAtPath(ignorePath)) {
6969
const content = await fs.readFile(ignorePath, "utf8")
7070
this.rooIgnoreContent = content
7171
this.ignoreInstance.add(content)
72-
this.ignoreInstance.add(".rooignore")
72+
this.ignoreInstance.add(AGENT_IGNORE_FILE_NAME)
7373
} else {
7474
this.rooIgnoreContent = undefined
7575
}
7676
} catch (error) {
7777
// Should never happen: reading file failed even though it exists
78-
console.error("Unexpected error loading .rooignore:", error)
78+
console.error(`Unexpected error loading ${AGENT_IGNORE_FILE_NAME}:`, error)
7979
}
8080
}
8181

@@ -85,7 +85,7 @@ export class RooIgnoreController {
8585
* @returns true if file is accessible, false if ignored
8686
*/
8787
validateAccess(filePath: string): boolean {
88-
// Always allow access if .rooignore does not exist
88+
// Always allow access if .pearai-agent-ignore does not exist
8989
if (!this.rooIgnoreContent) {
9090
return true
9191
}
@@ -109,7 +109,7 @@ export class RooIgnoreController {
109109
* @returns path of file that is being accessed if it is being accessed, undefined if command is allowed
110110
*/
111111
validateCommand(command: string): string | undefined {
112-
// Always allow if no .rooignore exists
112+
// Always allow if no .pearai-agent-ignore exists
113113
if (!this.rooIgnoreContent) {
114114
return undefined
115115
}
@@ -188,14 +188,14 @@ export class RooIgnoreController {
188188
}
189189

190190
/**
191-
* Get formatted instructions about the .rooignore file for the LLM
192-
* @returns Formatted instructions or undefined if .rooignore doesn't exist
191+
* Get formatted instructions about the .pearai-agent-ignore file for the LLM
192+
* @returns Formatted instructions or undefined if .pearai-agent-ignore doesn't exist
193193
*/
194194
getInstructions(): string | undefined {
195195
if (!this.rooIgnoreContent) {
196196
return undefined
197197
}
198198

199-
return `# .rooignore\n\n(The following is provided by a root-level .rooignore file where the user has specified files and directories that should not be accessed. When using list_files, you'll notice a ${LOCK_TEXT_SYMBOL} next to files that are blocked. Attempting to access the file's contents e.g. through read_file will result in an error.)\n\n${this.rooIgnoreContent}\n.rooignore`
199+
return `# ${AGENT_IGNORE_FILE_NAME}\n\n(The following is provided by a root-level ${AGENT_IGNORE_FILE_NAME} file where the user has specified files and directories that should not be accessed. When using list_files, you'll notice a ${LOCK_TEXT_SYMBOL} next to files that are blocked. Attempting to access the file's contents e.g. through read_file will result in an error.)\n\n${this.rooIgnoreContent}\n${AGENT_IGNORE_FILE_NAME}`
200200
}
201201
}

src/core/ignore/__tests__/RooIgnoreController.security.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe("RooIgnoreController Security Tests", () => {
4141
mockFileExists = fileExistsAtPath as jest.MockedFunction<typeof fileExistsAtPath>
4242
mockReadFile = fs.readFile as jest.MockedFunction<typeof fs.readFile>
4343

44-
// By default, setup .rooignore to exist with some patterns
44+
// By default, setup .pearai-agent-ignore to exist with some patterns
4545
mockFileExists.mockResolvedValue(true)
4646
mockReadFile.mockResolvedValue("node_modules\n.git\nsecrets/**\n*.log\nprivate/")
4747

src/core/prompts/__tests__/custom-system-prompt.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { defaultModeSlug, modes } from "../../../shared/modes"
33
import * as vscode from "vscode"
44
import * as fs from "fs/promises"
55
import { toPosix } from "./utils"
6+
import { AGENT_RULES_DIR } from "../../../shared/constants"
67

78
// Mock the fs/promises module
89
jest.mock("fs/promises", () => ({
@@ -90,7 +91,7 @@ describe("File-Based Custom System Prompt", () => {
9091
const fileCustomSystemPrompt = "Custom system prompt from file"
9192
// When called with utf-8 encoding, return a string
9293
mockedFs.readFile.mockImplementation((filePath, options) => {
93-
if (toPosix(filePath).includes(`.roo/system-prompt-${defaultModeSlug}`) && options === "utf-8") {
94+
if (toPosix(filePath).includes(`${AGENT_RULES_DIR}/system-prompt-${defaultModeSlug}`) && options === "utf-8") {
9495
return Promise.resolve(fileCustomSystemPrompt)
9596
}
9697
return Promise.reject({ code: "ENOENT" })
@@ -125,7 +126,7 @@ describe("File-Based Custom System Prompt", () => {
125126
// Mock the readFile to return content from a file
126127
const fileCustomSystemPrompt = "Custom system prompt from file"
127128
mockedFs.readFile.mockImplementation((filePath, options) => {
128-
if (toPosix(filePath).includes(`.roo/system-prompt-${defaultModeSlug}`) && options === "utf-8") {
129+
if (toPosix(filePath).includes(`${AGENT_RULES_DIR}/system-prompt-${defaultModeSlug}`) && options === "utf-8") {
129130
return Promise.resolve(fileCustomSystemPrompt)
130131
}
131132
return Promise.reject({ code: "ENOENT" })

src/core/prompts/__tests__/responses-rooignore.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ describe("RooIgnore Response Formatting", () => {
5050
const errorMessage = formatResponse.rooIgnoreError("secrets/api-keys.json")
5151

5252
// Verify error message format
53-
expect(errorMessage).toContain("Access to secrets/api-keys.json is blocked by the .rooignore file settings")
53+
expect(errorMessage).toContain("Access to secrets/api-keys.json is blocked by the .pearai-agent-ignore file settings")
5454
expect(errorMessage).toContain("continue in the task without using this file")
55-
expect(errorMessage).toContain("ask the user to update the .rooignore file")
55+
expect(errorMessage).toContain("ask the user to update the .pearai-agent-ignore file")
5656
})
5757

5858
/**
@@ -207,7 +207,7 @@ describe("RooIgnore Response Formatting", () => {
207207
/**
208208
* Tests the instructions format
209209
*/
210-
it("should format .rooignore instructions for the LLM", async () => {
210+
it("should format .pearai-agent-ignore instructions for the LLM", async () => {
211211
// Create controller
212212
const controller = new RooIgnoreController(TEST_CWD)
213213
await controller.initialize()
@@ -216,7 +216,7 @@ describe("RooIgnore Response Formatting", () => {
216216
const instructions = controller.getInstructions()
217217

218218
// Verify format and content
219-
expect(instructions).toContain("# .rooignore")
219+
expect(instructions).toContain("# .pearai-agent-ignore")
220220
expect(instructions).toContain(LOCK_TEXT_SYMBOL)
221221
expect(instructions).toContain("node_modules")
222222
expect(instructions).toContain(".git")
@@ -231,11 +231,11 @@ describe("RooIgnore Response Formatting", () => {
231231
/**
232232
* Tests null/undefined case
233233
*/
234-
it("should return undefined when no .rooignore exists", async () => {
235-
// Set up no .rooignore
234+
it("should return undefined when no .pearai-agent-ignore exists", async () => {
235+
// Set up no .pearai-agent-ignore
236236
mockFileExists.mockResolvedValue(false)
237237

238-
// Create controller without .rooignore
238+
// Create controller without .pearai-agent-ignore
239239
const controller = new RooIgnoreController(TEST_CWD)
240240
await controller.initialize()
241241

src/core/prompts/instructions/create-mode.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as path from "path"
22
import * as vscode from "vscode"
33

44
import { GlobalFileNames } from "../../../shared/globalFileNames"
5+
import { AGENT_MODES_FILE_NAME } from "../../../shared/constants"
56

67
export async function createModeInstructions(context: vscode.ExtensionContext | undefined): Promise<string> {
78
if (!context) throw new Error("Missing VSCode Extension Context")
@@ -12,12 +13,12 @@ export async function createModeInstructions(context: vscode.ExtensionContext |
1213
return `
1314
Custom modes can be configured in two ways:
1415
1. Globally via '${customModesPath}' (created automatically on startup)
15-
2. Per-workspace via '.roomodes' in the workspace root directory
16+
2. Per-workspace via '${AGENT_MODES_FILE_NAME}' in the workspace root directory
1617
17-
When modes with the same slug exist in both files, the workspace-specific .roomodes version takes precedence. This allows projects to override global modes or define project-specific modes.
18+
When modes with the same slug exist in both files, the workspace-specific ${AGENT_MODES_FILE_NAME} version takes precedence. This allows projects to override global modes or define project-specific modes.
1819
1920
20-
If asked to create a project mode, create it in .roomodes in the workspace root. If asked to create a global mode, use the global custom modes file.
21+
If asked to create a project mode, create it in ${AGENT_MODES_FILE_NAME} in the workspace root. If asked to create a global mode, use the global custom modes file.
2122
2223
- The following fields are required and must not be empty:
2324
* slug: A valid slug (lowercase letters, numbers, and hyphens). Must be unique, and shorter is better.

src/core/prompts/responses.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
22
import * as path from "path"
33
import * as diff from "diff"
44
import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/RooIgnoreController"
5+
import { AGENT_IGNORE_FILE_NAME } from "../../shared/constants"
56

67
export const formatResponse = {
78
toolDenied: () => `The user denied this operation.`,
@@ -15,7 +16,7 @@ export const formatResponse = {
1516
toolError: (error?: string) => `The tool execution failed with the following error:\n<error>\n${error}\n</error>`,
1617

1718
rooIgnoreError: (path: string) =>
18-
`Access to ${path} is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.`,
19+
`Access to ${path} is blocked by the ${AGENT_IGNORE_FILE_NAME} file settings. You must try to continue in the task without using this file, or ask the user to update the ${AGENT_IGNORE_FILE_NAME} file.`,
1920

2021
noToolsUsed: () =>
2122
`[ERROR] You did not use a tool in your previous response! Please retry with a tool use.

0 commit comments

Comments
 (0)