diff --git a/README.md b/README.md index 057a67f..80ac38d 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen | Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` | | OpenCode | `AGENTS.md` | `opencode.json`, `~/.config/opencode/opencode.json` | | Goose | `.goosehints` | - | +| Roo | `.roo/rules/ruler_roo_instructions.md` | - | ## Getting Started @@ -164,18 +165,18 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t ### Options -| Option | Description | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--project-root ` | Path to your project's root (default: current directory) | -| `--agents ` | Comma-separated list of agent names to target (amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode) | -| `--config ` | Path to a custom `ruler.toml` configuration file | -| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) | -| `--no-mcp` | Disable applying MCP server configurations | -| `--mcp-overwrite` | Overwrite native MCP config entirely instead of merging | -| `--gitignore` | Enable automatic .gitignore updates (default: true) | -| `--no-gitignore` | Disable automatic .gitignore updates | -| `--local-only` | Do not look for configuration in `$XDG_CONFIG_HOME` | -| `--verbose` / `-v` | Display detailed output during execution | +| Option | Description | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--project-root ` | Path to your project's root (default: current directory) | +| `--agents ` | Comma-separated list of agent names to target (amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode, roo) | +| `--config ` | Path to a custom `ruler.toml` configuration file | +| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) | +| `--no-mcp` | Disable applying MCP server configurations | +| `--mcp-overwrite` | Overwrite native MCP config entirely instead of merging | +| `--gitignore` | Enable automatic .gitignore updates (default: true) | +| `--no-gitignore` | Disable automatic .gitignore updates | +| `--local-only` | Do not look for configuration in `$XDG_CONFIG_HOME` | +| `--verbose` / `-v` | Display detailed output during execution | ### Common Examples @@ -197,6 +198,12 @@ ruler apply --agents copilot,claude ruler apply --agents firebase ``` +**Apply rules only to Roo:** + +```bash +ruler apply --agents roo +``` + **Use a specific configuration file:** ```bash @@ -236,15 +243,15 @@ ruler revert [options] ### Options -| Option | Description | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--project-root ` | Path to your project's root (default: current directory) | -| `--agents ` | Comma-separated list of agent names to revert (amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode) | -| `--config ` | Path to a custom `ruler.toml` configuration file | -| `--keep-backups` | Keep backup files (.bak) after restoration (default: false) | -| `--dry-run` | Preview changes without actually reverting files | -| `--verbose` / `-v` | Display detailed output during execution | -| `--local-only` | Only search for local .ruler directories, ignore global config | +| Option | Description | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--project-root ` | Path to your project's root (default: current directory) | +| `--agents ` | Comma-separated list of agent names to revert (amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode, roo) | +| `--config ` | Path to a custom `ruler.toml` configuration file | +| `--keep-backups` | Keep backup files (.bak) after restoration (default: false) | +| `--dry-run` | Preview changes without actually reverting files | +| `--verbose` / `-v` | Display detailed output during execution | +| `--local-only` | Only search for local .ruler directories, ignore global config | ### Common Examples @@ -354,6 +361,10 @@ enabled = false [agents.kilocode] enabled = true output_path = ".kilocode/rules/ruler_kilocode_instructions.md" + +[agents.roo] +enabled = true +output_path = ".roo/rules/ruler_roo_instructions.md" ``` ### Configuration Precedence @@ -389,10 +400,10 @@ Define your project's MCP servers: } ``` - Ruler uses this file with the `merge` (default) or `overwrite` strategy, controlled by `ruler.toml` or CLI flags. **Note for OpenAI Codex CLI:** To apply the local Codex CLI MCP configuration, set the `CODEX_HOME` environment variable to your project’s `.codex` directory: + ```bash export CODEX_HOME="$(pwd)/.codex" ``` diff --git a/src/agents/RooAgent.ts b/src/agents/RooAgent.ts new file mode 100644 index 0000000..21a85ba --- /dev/null +++ b/src/agents/RooAgent.ts @@ -0,0 +1,37 @@ +import * as path from 'path'; +import { IAgent, IAgentConfig } from './IAgent'; +import { + backupFile, + writeGeneratedFile, + ensureDirExists, +} from '../core/FileSystemUtils'; + +/** + * Roo agent adapter. + */ +export class RooAgent implements IAgent { + getIdentifier(): string { + return 'roo_code'; + } + + getName(): string { + return 'Roo'; + } + + async applyRulerConfig( + concatenatedRules: string, + projectRoot: string, + rulerMcpJson: Record | null, // eslint-disable-line @typescript-eslint/no-unused-vars + agentConfig?: IAgentConfig, + ): Promise { + const outputPath = + agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot); + await ensureDirExists(path.dirname(outputPath)); + await backupFile(outputPath); + await writeGeneratedFile(outputPath, concatenatedRules); + } + + getDefaultOutputPath(projectRoot: string): string { + return path.join(projectRoot, '.roo', 'rules', 'ruler_roo_instructions.md'); + } +} diff --git a/src/agents/index.ts b/src/agents/index.ts index 133c246..5e88f18 100644 --- a/src/agents/index.ts +++ b/src/agents/index.ts @@ -17,6 +17,7 @@ import { OpenCodeAgent } from './OpenCodeAgent'; import { CrushAgent } from './CrushAgent'; import { GooseAgent } from './GooseAgent'; import { AmpAgent } from './AmpAgent'; +import { RooAgent } from './RooAgent'; export const allAgents: IAgent[] = [ new CopilotAgent(), @@ -37,4 +38,5 @@ export const allAgents: IAgent[] = [ new GooseAgent(), new CrushAgent(), new AmpAgent(), + new RooAgent(), ]; diff --git a/tests/unit/agents/RooAgent.test.ts b/tests/unit/agents/RooAgent.test.ts new file mode 100644 index 0000000..96240ac --- /dev/null +++ b/tests/unit/agents/RooAgent.test.ts @@ -0,0 +1,53 @@ +import { promises as fs } from 'fs'; +import * as path from 'path'; +import os from 'os'; + +import { RooAgent } from '../../../src/agents/RooAgent'; + +describe('RooAgent', () => { + let tmpDir: string; + + beforeEach(async () => { + tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ruler-roo-')); + }); + + afterEach(async () => { + await fs.rm(tmpDir, { recursive: true, force: true }); + }); + + it('should have the correct identifier', () => { + const agent = new RooAgent(); + expect(agent.getIdentifier()).toBe('roo_code'); + }); + + it('should have the correct name', () => { + const agent = new RooAgent(); + expect(agent.getName()).toBe('Roo'); + }); + + it('should generate the default output path correctly', () => { + const agent = new RooAgent(); + const expectedPath = path.join( + tmpDir, + '.roo', + 'rules', + 'ruler_roo_instructions.md', + ); + expect(agent.getDefaultOutputPath(tmpDir)).toBe(expectedPath); + }); + + it('should apply ruler config and write to the output path in .roo/rules', async () => { + const agent = new RooAgent(); + const rules = 'test rules'; + const outputPath = path.join( + tmpDir, + '.roo', + 'rules', + 'ruler_roo_instructions.md', + ); + + await agent.applyRulerConfig(rules, tmpDir, null); + + expect(await fs.readFile(outputPath, 'utf8')).toBe(rules); + }); +});