diff --git a/README.md b/README.md
index 1db02609..8c45c98e 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
# CodeGraph
-### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, and Kiro with Semantic Code Intelligence
+### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, Kiro, and Rovo Dev with Semantic Code Intelligence
**~35% cheaper · ~70% fewer tool calls · 100% local**
@@ -24,6 +24,7 @@
[](#supported-agents)
[](#supported-agents)
[](#supported-agents)
+[](#supported-agents)
@@ -46,7 +47,7 @@ npx @colbymchenry/codegraph # zero-install, or:
npm i -g @colbymchenry/codegraph
```
-CodeGraph bundles its own runtime — nothing to compile, no native build, works the same everywhere. The interactive installer auto-configures your agent(s) — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro.
+CodeGraph bundles its own runtime — nothing to compile, no native build, works the same everywhere. The interactive installer auto-configures your agent(s) — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, Rovo Dev.
### Initialize Projects
@@ -233,7 +234,7 @@ npx @colbymchenry/codegraph
```
The installer will:
-- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, **Kiro**
+- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, **Kiro**, **Rovo Dev**
- Prompt to install `codegraph` on your PATH (so agents can launch the MCP server)
- Ask whether configs apply to all your projects or just this one
- Write each chosen agent's MCP server config + an instructions file (e.g. `CLAUDE.md`, `.cursor/rules/codegraph.mdc`, `~/.codex/AGENTS.md`, `~/.gemini/GEMINI.md`)
@@ -259,7 +260,7 @@ codegraph install --print-config codex # print snippet, no file wr
### 2. Restart Your Agent
-Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro) for the MCP server to load.
+Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro / Rovo Dev) for the MCP server to load.
### 3. Initialize Projects
@@ -527,6 +528,7 @@ the MCP server and writing its instructions file:
- **Gemini CLI**
- **Antigravity IDE**
- **Kiro**
+- **Rovo Dev**
## Supported Languages
@@ -588,7 +590,7 @@ MIT
-**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro**
+**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, and Rovo Dev**
[Report Bug](https://github.com/colbymchenry/codegraph/issues) · [Request Feature](https://github.com/colbymchenry/codegraph/issues)
diff --git a/__tests__/installer-targets.test.ts b/__tests__/installer-targets.test.ts
index 697f8e97..0d86b66c 100644
--- a/__tests__/installer-targets.test.ts
+++ b/__tests__/installer-targets.test.ts
@@ -468,6 +468,78 @@ describe('Installer targets — partial-state idempotency', () => {
expect(paths.some((p) => p.endsWith('/.kiro/steering/codegraph.md'))).toBe(true);
});
+ it('rovodev: install writes mcp.json and AGENTS.md', () => {
+ const rovodev = getTarget('rovodev')!;
+ const result = rovodev.install('global', { autoAllow: true });
+
+ const mcpJson = path.join(tmpHome, '.rovodev', 'mcp.json');
+ const agentsMd = path.join(tmpHome, '.rovodev', 'AGENTS.md');
+
+ expect(result.files.some((f) => f.path === mcpJson)).toBe(true);
+ expect(result.files.some((f) => f.path === agentsMd)).toBe(true);
+
+ const cfg = JSON.parse(fs.readFileSync(mcpJson, 'utf-8'));
+ expect(cfg.mcpServers.codegraph).toEqual({ command: 'codegraph', args: ['serve', '--mcp'] });
+ // No `type` field — Rovo Dev assumes stdio transport.
+ expect(cfg.mcpServers.codegraph.type).toBeUndefined();
+
+ const md = fs.readFileSync(agentsMd, 'utf-8');
+ expect(md).toContain('codegraph_callers');
+ expect(md).toContain('CodeGraph MCP server');
+ });
+
+ it('rovodev: install preserves a pre-existing sibling MCP server in mcp.json', () => {
+ const rovodev = getTarget('rovodev')!;
+ const mcpJson = path.join(tmpHome, '.rovodev', 'mcp.json');
+ fs.mkdirSync(path.dirname(mcpJson), { recursive: true });
+ fs.writeFileSync(mcpJson, JSON.stringify({
+ mcpServers: { 'ops-sherpa': { command: 'npx', args: ['@atlassian/ops-sherpa'] } },
+ }, null, 2) + '\n');
+
+ rovodev.install('global', { autoAllow: true });
+
+ const after = JSON.parse(fs.readFileSync(mcpJson, 'utf-8'));
+ expect(after.mcpServers['ops-sherpa']).toBeDefined();
+ expect(after.mcpServers.codegraph).toBeDefined();
+ });
+
+ it('rovodev: uninstall strips codegraph but leaves sibling MCP server intact', () => {
+ const rovodev = getTarget('rovodev')!;
+ const mcpJson = path.join(tmpHome, '.rovodev', 'mcp.json');
+ fs.mkdirSync(path.dirname(mcpJson), { recursive: true });
+ fs.writeFileSync(mcpJson, JSON.stringify({
+ mcpServers: { 'ops-sherpa': { command: 'npx', args: ['@atlassian/ops-sherpa'] } },
+ }, null, 2) + '\n');
+
+ rovodev.install('global', { autoAllow: true });
+ rovodev.uninstall('global');
+
+ const after = JSON.parse(fs.readFileSync(mcpJson, 'utf-8'));
+ expect(after.mcpServers['ops-sherpa']).toBeDefined();
+ expect(after.mcpServers.codegraph).toBeUndefined();
+ });
+
+ it('rovodev: uninstall strips the CodeGraph section from AGENTS.md but preserves user content', () => {
+ const rovodev = getTarget('rovodev')!;
+ const agentsMd = path.join(tmpHome, '.rovodev', 'AGENTS.md');
+ fs.mkdirSync(path.dirname(agentsMd), { recursive: true });
+ fs.writeFileSync(agentsMd, '# My guidelines\n\nDo not assume.\n');
+
+ rovodev.install('global', { autoAllow: true });
+ expect(fs.readFileSync(agentsMd, 'utf-8')).toContain('CodeGraph MCP server');
+
+ rovodev.uninstall('global');
+ const after = fs.readFileSync(agentsMd, 'utf-8');
+ expect(after).toContain('My guidelines');
+ expect(after).not.toContain('CodeGraph MCP server');
+ });
+
+ it('rovodev: supportsLocation returns false for local', () => {
+ const rovodev = getTarget('rovodev')!;
+ expect(rovodev.supportsLocation('local')).toBe(false);
+ expect(rovodev.supportsLocation('global')).toBe(true);
+ });
+
it('antigravity: install writes to LEGACY ~/.gemini/antigravity/mcp_config.json when no migration marker', () => {
const antigravity = getTarget('antigravity')!;
antigravity.install('global', { autoAllow: true });
diff --git a/src/installer/targets/registry.ts b/src/installer/targets/registry.ts
index 5e929d46..cd1c5e7d 100644
--- a/src/installer/targets/registry.ts
+++ b/src/installer/targets/registry.ts
@@ -16,6 +16,7 @@ import { hermesTarget } from './hermes';
import { geminiTarget } from './gemini';
import { antigravityTarget } from './antigravity';
import { kiroTarget } from './kiro';
+import { rovodevTarget } from './rovodev';
export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
claudeTarget,
@@ -26,6 +27,7 @@ export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
geminiTarget,
antigravityTarget,
kiroTarget,
+ rovodevTarget,
]);
export function getTarget(id: string): AgentTarget | undefined {
diff --git a/src/installer/targets/rovodev.ts b/src/installer/targets/rovodev.ts
new file mode 100644
index 00000000..9b76f7cb
--- /dev/null
+++ b/src/installer/targets/rovodev.ts
@@ -0,0 +1,203 @@
+/**
+ * Rovo Dev target. Writes:
+ *
+ * - MCP server entry to `~/.rovodev/mcp.json` — the file Rovo Dev
+ * CLI reads to discover and launch MCP servers.
+ * - Instructions to `~/.rovodev/AGENTS.md` — Rovo Dev's global
+ * personal memory file, loaded into every session.
+ *
+ * Rovo Dev only supports a global install location (there is no
+ * per-project MCP config concept in the CLI as of 2026-05). The
+ * `local` location is therefore not supported and the installer will
+ * skip it with a clear message.
+ *
+ * MCP config format reference:
+ * {
+ * "mcpServers": {
+ * "
": {
+ * "command": "",
+ * "args": ["", ...],
+ * "env": { "KEY": "value" } // optional
+ * }
+ * }
+ * }
+ *
+ * The `type` field is omitted — Rovo Dev assumes stdio transport.
+ */
+
+import * as fs from 'fs';
+import * as path from 'path';
+import * as os from 'os';
+import {
+ AgentTarget,
+ DetectionResult,
+ InstallOptions,
+ Location,
+ WriteResult,
+} from './types';
+import {
+ jsonDeepEqual,
+ readJsonFile,
+ removeMarkedSection,
+ replaceOrAppendMarkedSection,
+ writeJsonFile,
+} from './shared';
+import {
+ CODEGRAPH_SECTION_END,
+ CODEGRAPH_SECTION_START,
+ INSTRUCTIONS_TEMPLATE,
+} from '../instructions-template';
+
+/** Path to Rovo Dev's MCP server registry. */
+function mcpJsonPath(): string {
+ return path.join(os.homedir(), '.rovodev', 'mcp.json');
+}
+
+/** Path to the Rovo Dev config directory — used for install detection. */
+function rovodevConfigDir(): string {
+ return path.join(os.homedir(), '.rovodev');
+}
+
+/** Path to Rovo Dev's global personal memory/instructions file. */
+function instructionsPath(): string {
+ return path.join(os.homedir(), '.rovodev', 'AGENTS.md');
+}
+
+/**
+ * The MCP server config block for Rovo Dev.
+ *
+ * Rovo Dev does not use the `type` field; the command + args are
+ * sufficient to identify a stdio-based server.
+ */
+function getRovoDevMcpEntry(): { command: string; args: string[] } {
+ return {
+ command: 'codegraph',
+ args: ['serve', '--mcp'],
+ };
+}
+
+class RovoDevTarget implements AgentTarget {
+ readonly id = 'rovodev' as const;
+ readonly displayName = 'Rovo Dev';
+ readonly docsUrl = 'https://www.atlassian.com/software/rovo-dev';
+
+ /** Rovo Dev only has a global config — no per-project MCP concept. */
+ supportsLocation(loc: Location): boolean {
+ return loc === 'global';
+ }
+
+ detect(loc: Location): DetectionResult {
+ if (loc !== 'global') {
+ return { installed: false, alreadyConfigured: false };
+ }
+ const configPath = mcpJsonPath();
+ const installed = fs.existsSync(rovodevConfigDir());
+ const config = readJsonFile(configPath);
+ const alreadyConfigured = !!config.mcpServers?.codegraph;
+ return { installed, alreadyConfigured, configPath };
+ }
+
+ install(loc: Location, _opts: InstallOptions): WriteResult {
+ // Rovo Dev has no per-project MCP concept — only global is supported.
+ // supportsLocation('local') returns false so the orchestrator will
+ // never call install for local, but guard explicitly for safety.
+ if (loc !== 'global') {
+ return {
+ files: [],
+ notes: ['Rovo Dev has no project-local config — re-run with --location=global to install.'],
+ };
+ }
+ const files: WriteResult['files'] = [];
+ files.push(writeMcpEntry());
+ files.push(writeInstructionsEntry());
+ const notes: string[] = ['Restart Rovo Dev (or reload MCP servers) to apply.'];
+ return { files, notes };
+ }
+
+ uninstall(_loc: Location): WriteResult {
+ const files: WriteResult['files'] = [];
+
+ // 1. MCP server entry
+ const configPath = mcpJsonPath();
+ const config = readJsonFile(configPath);
+ if (config.mcpServers?.codegraph) {
+ delete config.mcpServers.codegraph;
+ if (Object.keys(config.mcpServers).length === 0) {
+ delete config.mcpServers;
+ }
+ writeJsonFile(configPath, config);
+ files.push({ path: configPath, action: 'removed' });
+ } else {
+ files.push({ path: configPath, action: 'not-found' });
+ }
+
+ // 2. Instructions
+ const instr = instructionsPath();
+ const action = removeMarkedSection(instr, CODEGRAPH_SECTION_START, CODEGRAPH_SECTION_END);
+ files.push({ path: instr, action });
+
+ return { files };
+ }
+
+ printConfig(_loc: Location): string {
+ const target = mcpJsonPath();
+ const snippet = JSON.stringify(
+ { mcpServers: { codegraph: getRovoDevMcpEntry() } },
+ null,
+ 2,
+ );
+ return `# Add to ${target}\n\n${snippet}\n`;
+ }
+
+ describePaths(_loc: Location): string[] {
+ return [mcpJsonPath(), instructionsPath()];
+ }
+}
+
+/**
+ * Idempotent write of the codegraph instructions block into
+ * ~/.rovodev/AGENTS.md. Uses marker-based section replacement so any
+ * existing user content in the file is preserved verbatim.
+ */
+function writeInstructionsEntry(): WriteResult['files'][number] {
+ const file = instructionsPath();
+ const dir = path.dirname(file);
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
+
+ const action = replaceOrAppendMarkedSection(
+ file,
+ INSTRUCTIONS_TEMPLATE,
+ CODEGRAPH_SECTION_START,
+ CODEGRAPH_SECTION_END,
+ );
+ const mapped: 'created' | 'updated' | 'unchanged' =
+ action === 'created' ? 'created'
+ : action === 'unchanged' ? 'unchanged'
+ : 'updated';
+ return { path: file, action: mapped };
+}
+
+/**
+ * Idempotent write of the codegraph MCP entry into ~/.rovodev/mcp.json.
+ * Preserves any sibling MCP server entries already in the file.
+ */
+function writeMcpEntry(): WriteResult['files'][number] {
+ const file = mcpJsonPath();
+ const existing = readJsonFile(file);
+ const before = existing.mcpServers?.codegraph;
+ const after = getRovoDevMcpEntry();
+
+ if (jsonDeepEqual(before, after)) {
+ return { path: file, action: 'unchanged' };
+ }
+
+ const action: 'created' | 'updated' =
+ before ? 'updated' : fs.existsSync(file) ? 'updated' : 'created';
+
+ if (!existing.mcpServers) existing.mcpServers = {};
+ existing.mcpServers.codegraph = after;
+ writeJsonFile(file, existing);
+ return { path: file, action };
+}
+
+export const rovodevTarget: AgentTarget = new RovoDevTarget();
diff --git a/src/installer/targets/types.ts b/src/installer/targets/types.ts
index 0ded6ce0..810bbd99 100644
--- a/src/installer/targets/types.ts
+++ b/src/installer/targets/types.ts
@@ -19,7 +19,7 @@ export type Location = 'global' | 'local';
* lookup. New targets add a value here when they're added to the
* registry. Keep these short and lowercase.
*/
-export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'gemini' | 'antigravity' | 'kiro';
+export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'gemini' | 'antigravity' | 'kiro' | 'rovodev';
/**
* Result of `target.detect(location)`.