Skip to content
Open
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
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**

Expand All @@ -24,6 +24,7 @@
[![Gemini](https://img.shields.io/badge/Gemini-supported-blueviolet.svg)](#supported-agents)
[![Antigravity](https://img.shields.io/badge/Antigravity-supported-blueviolet.svg)](#supported-agents)
[![Kiro](https://img.shields.io/badge/Kiro-supported-blueviolet.svg)](#supported-agents)
[![Rovo Dev](https://img.shields.io/badge/Rovo_Dev-supported-blueviolet.svg)](#supported-agents)

</div>

Expand All @@ -46,7 +47,7 @@ npx @colbymchenry/codegraph # zero-install, or:
npm i -g @colbymchenry/codegraph
```

<sub>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.</sub>
<sub>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.</sub>

### Initialize Projects

Expand Down Expand Up @@ -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`)
Expand All @@ -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

Expand Down Expand Up @@ -527,6 +528,7 @@ the MCP server and writing its instructions file:
- **Gemini CLI**
- **Antigravity IDE**
- **Kiro**
- **Rovo Dev**

## Supported Languages

Expand Down Expand Up @@ -588,7 +590,7 @@ MIT

<div align="center">

**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)

Expand Down
72 changes: 72 additions & 0 deletions __tests__/installer-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
2 changes: 2 additions & 0 deletions src/installer/targets/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -26,6 +27,7 @@ export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
geminiTarget,
antigravityTarget,
kiroTarget,
rovodevTarget,
]);

export function getTarget(id: string): AgentTarget | undefined {
Expand Down
203 changes: 203 additions & 0 deletions src/installer/targets/rovodev.ts
Original file line number Diff line number Diff line change
@@ -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": {
* "<name>": {
* "command": "<executable>",
* "args": ["<arg>", ...],
* "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();
2 changes: 1 addition & 1 deletion src/installer/targets/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)`.
Expand Down