Skip to content
Closed
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### New Features

- CodeGraph now supports CoDev Code, the CoDev fork of opencode. `codegraph install` auto-detects it and wires the MCP server into its own config (`~/.config/codev/codev.jsonc` globally, `./codev.jsonc` per project) — the fork reads renamed paths, so an opencode install was invisible to it. Also available explicitly as `--target=codev`.

### Fixes

- Callers and impact analysis no longer silently under-count a function that calls the same callee many times. When one caller contained several call sites to the same callee and an internal resolution batch boundary happened to split them, cleanup after the first batch removed the later sites' pending rows before they were ever attempted — their edges were never created, deterministically, and which edges went missing shifted with unrelated changes to the project's total reference count. Post-pass cleanup now targets the exact database row each processed reference came from. Found while validating the operator-call fix on nlohmann/json, where `write_cbor`'s 11 calls to `to_char_type` indexed as 10. (#1269)
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ In a **new terminal**, run the installer to connect CodeGraph to the agents you
codegraph install
```

<sub>Detects and auto-configures Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro — wiring the CodeGraph MCP server into each. **This is the step that connects CodeGraph to your agent;** installing the CLI in step 1 does not do it on its own. It only wires up your agent — it does **not** index any code; building each project's graph is the separate `codegraph init` in step 3. (Shortcut: `npx @colbymchenry/codegraph` downloads and runs this in one go.)</sub>
<sub>Detects and auto-configures Claude Code, Cursor, Codex CLI, opencode, CoDev Code, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro — wiring the CodeGraph MCP server into each. **This is the step that connects CodeGraph to your agent;** installing the CLI in step 1 does not do it on its own. It only wires up your agent — it does **not** index any code; building each project's graph is the separate `codegraph init` in step 3. (Shortcut: `npx @colbymchenry/codegraph` downloads and runs this in one go.)</sub>

### 3. Initialize each project

Expand Down Expand Up @@ -412,7 +412,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**, **CoDev Code**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, **Kiro**
- 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, plus a small marker-fenced CodeGraph section in the agent's instructions file (`CLAUDE.md` / `AGENTS.md` / `GEMINI.md`) — that's how subagents and non-MCP agents learn the `codegraph explore` command, since the MCP server's own guidance only reaches the main agent. Removed cleanly by `codegraph uninstall`.
Expand All @@ -439,7 +439,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 / CoDev Code / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro) for the MCP server to load.

### 3. Initialize Projects

Expand Down Expand Up @@ -766,6 +766,7 @@ is written):
- **Cursor**
- **Codex CLI**
- **opencode**
- **CoDev Code**
- **Hermes Agent**
- **Gemini CLI**
- **Antigravity IDE**
Expand Down
122 changes: 119 additions & 3 deletions __tests__/installer-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ describe('Installer targets — contract', () => {
// Seed pre-existing config.
fs.mkdirSync(path.dirname(jsonPath), { recursive: true });
const seed: Record<string, any> = { mcpServers: { other: { command: 'x' } } };
// opencode uses `mcp` not `mcpServers`. Match its shape too.
if (target.id === 'opencode') {
// opencode (and its CoDev fork) uses `mcp` not `mcpServers`.
// Match that shape too.
if (target.id === 'opencode' || target.id === 'codev') {
delete seed.mcpServers;
seed.mcp = { other: { type: 'local', command: ['x'], enabled: true } };
}
Expand All @@ -141,7 +142,7 @@ describe('Installer targets — contract', () => {
target.install(location, { autoAllow: true });

const after = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
if (target.id === 'opencode') {
if (target.id === 'opencode' || target.id === 'codev') {
expect(after.mcp.other).toBeDefined();
expect(after.mcp.codegraph).toBeDefined();
} else {
Expand Down Expand Up @@ -345,6 +346,120 @@ describe('Installer targets — partial-state idempotency', () => {
expect(fs.existsSync(path.join(process.cwd(), 'AGENTS.md'))).toBe(true);
});

// CoDev Code is an opencode fork with renamed paths (~/.config/codev/
// codev.jsonc) but the identical config shape. The shared contract
// suite covers install/idempotency/uninstall; these pin the paths and
// the fork's independence from opencode's files.

it('codev: global install writes ~/.config/codev/codev.jsonc and its AGENTS.md block', () => {
const codev = getTarget('codev')!;
const result = codev.install('global', { autoAllow: true });
const dir = path.join(tmpHome, '.config', 'codev');
expect(result.files.some((f) => f.path === path.join(dir, 'codev.jsonc'))).toBe(true);
const cfg = JSON.parse(fs.readFileSync(path.join(dir, 'codev.jsonc'), 'utf-8'));
expect(cfg.mcp.codegraph).toEqual({ type: 'local', command: ['codegraph', 'serve', '--mcp'], enabled: true });
const agentsMd = path.join(dir, 'AGENTS.md');
expect(fs.existsSync(agentsMd)).toBe(true);
expect(fs.readFileSync(agentsMd, 'utf-8')).toContain('codegraph explore');
});

it('codev: prefers .jsonc when both codev.json and codev.jsonc exist', () => {
const codev = getTarget('codev')!;
const dir = path.join(tmpHome, '.config', 'codev');
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'codev.json'), '{\n "$schema": "https://opencode.ai/config.json"\n}\n');
fs.writeFileSync(path.join(dir, 'codev.jsonc'), '{\n "$schema": "https://opencode.ai/config.json"\n}\n');

const result = codev.install('global', { autoAllow: true });
const written = result.files.find((f) => /codev\.jsonc?$/.test(f.path))!;
expect(written.path).toMatch(/codev\.jsonc$/);
const jsonText = fs.readFileSync(path.join(dir, 'codev.json'), 'utf-8');
expect(jsonText).not.toContain('codegraph');
});

it('codev: uses codev.json when only .json exists (no .jsonc)', () => {
const codev = getTarget('codev')!;
const dir = path.join(tmpHome, '.config', 'codev');
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'codev.json'), '{\n "$schema": "https://opencode.ai/config.json"\n}\n');

const result = codev.install('global', { autoAllow: true });
expect(result.files[0].path).toMatch(/codev\.json$/);
expect(fs.existsSync(path.join(dir, 'codev.jsonc'))).toBe(false);
});

it('codev: local install writes ./codev.jsonc and the ./AGENTS.md block', () => {
const codev = getTarget('codev')!;
const result = codev.install('local', { autoAllow: true });
const paths = result.files.map((f) => f.path.replace(/\\/g, '/'));
// macOS realpath shenanigans (/var vs /private/var) — suffix match.
expect(paths.some((p) => p.endsWith('/codev.jsonc'))).toBe(true);
expect(paths.some((p) => p.endsWith('/AGENTS.md'))).toBe(true);
expect(fs.existsSync(path.join(process.cwd(), 'AGENTS.md'))).toBe(true);
});

it('codev: never writes opencode paths, and vice versa (fork independence)', () => {
const codev = getTarget('codev')!;
const opencode = getTarget('opencode')!;

codev.install('global', { autoAllow: true });
expect(fs.existsSync(path.join(tmpHome, '.config', 'opencode'))).toBe(false);

opencode.install('global', { autoAllow: true });
const opencodeCfg = JSON.parse(
fs.readFileSync(path.join(tmpHome, '.config', 'opencode', 'opencode.jsonc'), 'utf-8'),
);
expect(opencodeCfg.mcp.codegraph).toBeDefined();

// Uninstalling one leaves the other's config wired.
codev.uninstall('global');
expect(opencode.detect('global').alreadyConfigured).toBe(true);
expect(codev.detect('global').alreadyConfigured).toBe(false);
});

it('codev: detect reports installed only when ~/.config/codev exists, ignoring %APPDATA% (no #535 history)', () => {
const codev = getTarget('codev')!;
expect(codev.detect('global').installed).toBe(false);

// A legacy %APPDATA%/codev dir must NOT count — the pre-#535
// misplacement is opencode history that the fork never had.
// (setHome points APPDATA at <home>/.config, so plant the dir via a
// distinct APPDATA to prove it's ignored.)
const appData = mkTmpDir('appdata');
const prevAppData = process.env.APPDATA;
process.env.APPDATA = appData;
try {
fs.mkdirSync(path.join(appData, 'codev'), { recursive: true });
expect(codev.detect('global').installed).toBe(false);

fs.mkdirSync(path.join(tmpHome, '.config', 'codev'), { recursive: true });
expect(codev.detect('global').installed).toBe(true);
} finally {
process.env.APPDATA = prevAppData;
fs.rmSync(appData, { recursive: true, force: true });
}
});

it('codev: global install never touches a legacy %APPDATA%/codev dir', () => {
const appData = mkTmpDir('appdata');
const prevAppData = process.env.APPDATA;
process.env.APPDATA = appData;
try {
const planted = path.join(appData, 'codev', 'codev.jsonc');
fs.mkdirSync(path.dirname(planted), { recursive: true });
const plantedBody = JSON.stringify({ mcp: { codegraph: { type: 'local', command: ['stale'], enabled: true } } }, null, 2);
fs.writeFileSync(planted, plantedBody);

const codev = getTarget('codev')!;
const result = codev.install('global', { autoAllow: true });
expect(result.files.some((f) => f.path.startsWith(appData))).toBe(false);
expect(fs.readFileSync(planted, 'utf-8')).toBe(plantedBody);
} finally {
process.env.APPDATA = prevAppData;
fs.rmSync(appData, { recursive: true, force: true });
}
});

it('gemini: install writes settings.json (mcpServers.codegraph) and the GEMINI.md block (#704)', () => {
const gemini = getTarget('gemini')!;
const result = gemini.install('global', { autoAllow: true });
Expand Down Expand Up @@ -1183,6 +1298,7 @@ describe('Installer targets — registry', () => {
expect(getTarget('cursor')?.id).toBe('cursor');
expect(getTarget('codex')?.id).toBe('codex');
expect(getTarget('opencode')?.id).toBe('opencode');
expect(getTarget('codev')?.id).toBe('codev');
expect(getTarget('hermes')?.id).toBe('hermes');
expect(getTarget('gemini')?.id).toBe('gemini');
expect(getTarget('antigravity')?.id).toBe('antigravity');
Expand Down
4 changes: 2 additions & 2 deletions site/src/content/docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ npx @colbymchenry/codegraph

The installer will:

- Ask which agent(s) to configure — auto-detecting installed ones from **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, and **Kiro**.
- Ask which agent(s) to configure — auto-detecting installed ones from **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **CoDev Code**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, and **Kiro**.
- 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, plus a small marker-fenced CodeGraph section in the agent's instructions file (`CLAUDE.md` / `AGENTS.md` / `GEMINI.md`). Cursor and Kiro get the MCP config only. Removed cleanly by `codegraph uninstall`.
Expand All @@ -38,7 +38,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 / CoDev Code / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro) for the MCP server to load.

## 3. Initialize projects

Expand Down
1 change: 1 addition & 0 deletions site/src/content/docs/reference/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The interactive installer auto-detects and configures each supported agent — w
- **Cursor**
- **Codex CLI**
- **opencode**
- **CoDev Code**
- **Hermes Agent**
- **Gemini CLI**
- **Antigravity IDE**
Expand Down
4 changes: 2 additions & 2 deletions src/installer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,8 @@ export async function runUninstaller(opts: RunUninstallerOptions): Promise<void>
const sel = await clack.select({
message: 'Remove CodeGraph from all your projects, or just this one?',
options: [
{ value: 'global' as const, label: 'All projects (global)', hint: '~/.claude, ~/.cursor, ~/.codex, ~/.config/opencode, ~/.hermes, ~/.gemini, ~/.kiro' },
{ value: 'local' as const, label: 'Just this project (local)', hint: './.claude, ./.cursor, ./opencode.jsonc, ./.gemini, ./.kiro' },
{ value: 'global' as const, label: 'All projects (global)', hint: '~/.claude, ~/.cursor, ~/.codex, ~/.config/opencode, ~/.config/codev, ~/.hermes, ~/.gemini, ~/.kiro' },
{ value: 'local' as const, label: 'Just this project (local)', hint: './.claude, ./.cursor, ./opencode.jsonc, ./codev.jsonc, ./.gemini, ./.kiro' },
],
initialValue: 'global' as const,
});
Expand Down
31 changes: 31 additions & 0 deletions src/installer/targets/codev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* CoDev Code target.
*
* CoDev Code (npm `codev-code`, binary `codev`) is a fork of opencode
* that renames the on-disk app identity but keeps opencode's config
* shape byte-for-byte: same `mcp.<name>` wrapper, same
* `{ type: "local", command: [...], enabled: true }` entry, and the
* same `https://opencode.ai/config.json` `$schema` (CoDev stamps that
* URL into fresh configs itself). Only the paths differ:
*
* - `~/.config/codev/codev.jsonc` instead of
* `~/.config/opencode/opencode.jsonc` (global; XDG on every platform)
* - `./codev.jsonc` instead of `./opencode.jsonc` (local)
* - `~/.config/codev/AGENTS.md` for the instructions block; the
* project-local `./AGENTS.md` convention is unchanged.
*
* So this is a thin spec over the shared opencode-family implementation.
* No `%APPDATA%` sweep: the pre-#535 Windows misplacement is opencode
* install history that CoDev never had.
*/

import { AgentTarget } from './types';
import { createOpencodeFamilyTarget } from './opencode-family';

export const codevTarget: AgentTarget = createOpencodeFamilyTarget({
id: 'codev',
displayName: 'CoDev Code',
docsUrl: 'https://github.com/quickbeard/codev-code',
appName: 'codev',
sweepLegacyWindowsAppData: false,
});
Loading