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

- GitHub Copilot is now a supported agent: `codegraph install` can configure Copilot Chat in VS Code (`copilot-vscode`), the GitHub Copilot CLI (`copilot-cli`), and the Copilot plugin in JetBrains IDEs (`copilot-jetbrains`). Installed Copilot surfaces are auto-detected like every other agent, existing MCP server entries in their config files are preserved, and `codegraph uninstall` reverses the setup cleanly. Restart VS Code or your JetBrains IDE after installing so Copilot picks up the server.

- DSH (DeepSeek Harness) is now a supported agent: `codegraph install` can configure it with `-t dsh`. DSH has no MCP client, so this target is instructions-only — it writes the marker-fenced CodeGraph usage block into `<project>/AGENTS.md` (local) or `$DSH_HOME/AGENTS.md` (global, defaulting to `~/.dsh/AGENTS.md`) and nothing else; the agent drives the `codegraph` CLI through its shell tool, which prints the same output as the MCP tools. Running sessions re-read their instructions file every turn, so no restart is needed — install/uninstall summaries say so instead of asking for a restart (`AgentTarget` grew an optional `requiresRestart` flag, defaulting to true for every existing target). Uninstall reverses the setup cleanly and preserves surrounding user content.

- Codex CLI can now be set up per project instead of only user-wide: `codegraph install --location=local` writes `./.codex/config.toml` and the CodeGraph block in your project's `AGENTS.md`, so CodeGraph is wired into that repo only rather than every Codex session on the machine. `codegraph uninstall --location=local` reverses it, and the global install is untouched either way. Codex only applies a project's config once you've marked the project trusted, so the installer says so after a local install. Thanks @maxmilian. (#1531)

- A new `deprioritize` setting in `codegraph.json` keeps the paths you name from outranking your product code in search and `codegraph_explore` answers, without removing anything from the index. It takes gitignore-style patterns just like `exclude`, but is ranking-only: helper-script trees, generated output, or optional add-on directories whose generic symbol names (`usage`, `run`, `status`) would otherwise crowd out the code that actually answers a query stay fully indexed and findable — and a query that genuinely targets such a tree still returns it. Thanks @maxmilian. (#982)
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Already installed? Run `codegraph upgrade`

Follow [@getcodegraph](https://x.com/getcodegraph) on X for updates.

### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, Kiro, and GitHub Copilot with Semantic Code Intelligence
### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, Kiro, GitHub Copilot, and DSH with Semantic Code Intelligence

**The fastest complete code graph · surgical context · built for how agents actually work · 100% local**

Expand Down Expand Up @@ -36,6 +36,7 @@ Follow [@getcodegraph](https://x.com/getcodegraph) on X for updates.
[![Antigravity](https://img.shields.io/badge/Antigravity-supported-blueviolet.svg)](#supported-agents)
[![Kiro](https://img.shields.io/badge/Kiro-supported-blueviolet.svg)](#supported-agents)
[![GitHub Copilot](https://img.shields.io/badge/GitHub_Copilot-supported-blueviolet.svg)](#supported-agents)
[![DSH](https://img.shields.io/badge/DSH-supported-blueviolet.svg)](#supported-agents)

<br>

Expand Down Expand Up @@ -105,7 +106,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, Kiro, and GitHub Copilot (VS Code, Copilot CLI, JetBrains IDEs) — 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, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, GitHub Copilot (VS Code, Copilot CLI, JetBrains IDEs), and DSH — 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 @@ -376,7 +377,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**, **GitHub Copilot** (VS Code, Copilot CLI, JetBrains IDEs)
- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, **Kiro**, **GitHub Copilot** (VS Code, Copilot CLI, JetBrains IDEs), **DSH** (DeepSeek Harness — instructions-only: writes the AGENTS.md block; no MCP client)
- 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 Down Expand Up @@ -784,6 +785,7 @@ is written):
- **Antigravity IDE**
- **Kiro**
- **GitHub Copilot** — Copilot Chat in VS Code (`copilot-vscode`), the Copilot CLI (`copilot-cli`), and the Copilot plugin in JetBrains IDEs (`copilot-jetbrains`)
- **DSH (DeepSeek Harness)** — instructions-only (`dsh`): no MCP client, so the installer writes only the marker-fenced CodeGraph block into `<project>/AGENTS.md` (local) or `$DSH_HOME/AGENTS.md` (global, default `~/.dsh/AGENTS.md`); the agent drives the `codegraph` CLI through its shell tool. Running sessions re-read AGENTS.md automatically — no restart needed.

## Supported Languages

Expand Down Expand Up @@ -882,7 +884,7 @@ MIT

<div align="center">

**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, and GitHub Copilot**
**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, GitHub Copilot, and DSH**

[Report Bug](https://github.com/colbymchenry/codegraph/issues) · [Request Feature](https://github.com/colbymchenry/codegraph/issues)

Expand Down
93 changes: 93 additions & 0 deletions __tests__/installer-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,18 @@ function setHome(dir: string): { restore: () => void } {
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
HERMES_HOME: process.env.HERMES_HOME,
COPILOT_HOME: process.env.COPILOT_HOME,
DSH_HOME: process.env.DSH_HOME,
};
process.env.HOME = dir;
process.env.USERPROFILE = dir;
process.env.APPDATA = path.join(dir, '.config');
process.env.XDG_CONFIG_HOME = path.join(dir, '.config');
delete process.env.HERMES_HOME;
delete process.env.COPILOT_HOME;
// DSH resolves its global instructions via $DSH_HOME before falling
// back to ~, so an ambient value would leak the real ~/.dsh into
// every redirect.
delete process.env.DSH_HOME;
return {
restore() {
if (prev.HOME === undefined) delete process.env.HOME; else process.env.HOME = prev.HOME;
Expand All @@ -55,6 +60,7 @@ function setHome(dir: string): { restore: () => void } {
if (prev.XDG_CONFIG_HOME === undefined) delete process.env.XDG_CONFIG_HOME; else process.env.XDG_CONFIG_HOME = prev.XDG_CONFIG_HOME;
if (prev.HERMES_HOME === undefined) delete process.env.HERMES_HOME; else process.env.HERMES_HOME = prev.HERMES_HOME;
if (prev.COPILOT_HOME === undefined) delete process.env.COPILOT_HOME; else process.env.COPILOT_HOME = prev.COPILOT_HOME;
if (prev.DSH_HOME === undefined) delete process.env.DSH_HOME; else process.env.DSH_HOME = prev.DSH_HOME;
},
};
}
Expand Down Expand Up @@ -2481,3 +2487,90 @@ describe('Installer targets — Copilot family', () => {
expect(jetbrains.detect('global').alreadyConfigured).toBe(true);
});
});

describe('Installer targets — DSH (instructions-only)', () => {
let tmpHome: string;
let tmpCwd: string;
let origCwd: string;
let homeRestore: { restore: () => void };

beforeEach(() => {
tmpHome = mkTmpDir('home');
tmpCwd = mkTmpDir('cwd');
origCwd = process.cwd();
process.chdir(tmpCwd);
homeRestore = setHome(tmpHome);
});

afterEach(() => {
homeRestore.restore();
process.chdir(origCwd);
fs.rmSync(tmpHome, { recursive: true, force: true });
fs.rmSync(tmpCwd, { recursive: true, force: true });
delete process.env.DSH_HOME;
});

it('local install writes only the AGENTS.md block — no MCP config anywhere', () => {
const dsh = getTarget('dsh')!;
const result = dsh.install('local', { autoAllow: true });

// Instructions-only: the one file written IS the workspace AGENTS.md.
expect(result.files).toHaveLength(1);
expect(fs.realpathSync(result.files[0].path)).toBe(
fs.realpathSync(path.join(tmpCwd, 'AGENTS.md')),
);
expect(fs.readFileSync(result.files[0].path, 'utf-8')).toContain('## CodeGraph');
// No MCP config surface exists for DSH.
for (const p of dsh.describePaths('local')) {
expect(p).not.toMatch(/mcp|\.json|\.toml/i);
}
expect(dsh.detect('local').alreadyConfigured).toBe(true);
});

it('global install honors $DSH_HOME and defaults to ~/.dsh', () => {
const dsh = getTarget('dsh')!;

// Default: ~/.dsh/AGENTS.md under the redirected HOME.
const result = dsh.install('global', { autoAllow: false });
expect(fs.realpathSync(result.files[0].path)).toBe(
fs.realpathSync(path.join(tmpHome, '.dsh', 'AGENTS.md')),
);
expect(fs.existsSync(path.join(tmpHome, '.dsh', 'AGENTS.md'))).toBe(true);

// $DSH_HOME override wins over the default.
const alt = path.join(tmpHome, 'alt-dsh-home');
fs.mkdirSync(alt, { recursive: true });
process.env.DSH_HOME = alt;
expect(dsh.describePaths('global')[0]).toBe(path.join(alt, 'AGENTS.md'));
dsh.install('global', { autoAllow: false });
expect(fs.existsSync(path.join(alt, 'AGENTS.md'))).toBe(true);
expect(dsh.detect('global').alreadyConfigured).toBe(true);
});

it('install preserves user content around the marker block; uninstall removes only the block', () => {
const dsh = getTarget('dsh')!;
fs.writeFileSync(
path.join(tmpCwd, 'AGENTS.md'),
'# My repo rules\n\nBe terse.\n',
);

dsh.install('local', { autoAllow: false });
let body = fs.readFileSync(path.join(tmpCwd, 'AGENTS.md'), 'utf-8');
expect(body).toContain('# My repo rules');
expect(body).toContain('## CodeGraph');

dsh.uninstall('local');
body = fs.readFileSync(path.join(tmpCwd, 'AGENTS.md'), 'utf-8');
expect(body).toContain('# My repo rules');
expect(body).not.toContain('CODEGRAPH_START');
expect(dsh.detect('local').alreadyConfigured).toBe(false);
});

it('printConfig explains the no-MCP CLI wiring without touching disk', () => {
const dsh = getTarget('dsh')!;
const out = dsh.printConfig('global');
expect(out).toContain('no MCP config');
expect(out).toContain('.dsh');
expect(out).toContain('codegraph explore');
});
});
4 changes: 2 additions & 2 deletions src/bin/codegraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2264,7 +2264,7 @@ program
*/
program
.command('install')
.description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, GitHub Copilot)')
.description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, GitHub Copilot, DSH)')
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
.option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
Expand Down Expand Up @@ -2364,7 +2364,7 @@ program
*/
program
.command('uninstall')
.description('Remove codegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, GitHub Copilot)')
.description('Remove codegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, GitHub Copilot, DSH)')
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "all". Default: all')
.option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt')
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=all')
Expand Down
7 changes: 5 additions & 2 deletions src/installer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,9 @@ export async function runInstallerWithOptions(opts: RunInstallerOptions): Promis
await getTelemetry().flushNow();

const finalNote = targets.length > 0
? `Done! Restart your agent${targets.length > 1 ? 's' : ''} to use CodeGraph.`
? targets.every((t) => t.requiresRestart === false)
? 'Done! CodeGraph is wired — instructions-file targets pick it up on the agent’s next turn, no restart needed.'
: `Done! Restart your agent${targets.length > 1 ? 's' : ''} to use CodeGraph.`
: 'Done!';
clack.outro(finalNote);
}
Expand Down Expand Up @@ -577,9 +579,10 @@ export async function runUninstaller(opts: RunUninstallerOptions): Promise<void>
const cliNote = cliRemoved ? ' The CLI is removed too — this was its last run.' : '';
if (removed.length > 0) {
const names = removed.map((r) => r.displayName).join(', ');
const allNoRestart = targets.every((t) => t.requiresRestart === false);
clack.outro(
`Removed CodeGraph from ${removed.length} agent${removed.length > 1 ? 's' : ''}: ${names}. ` +
`Restart ${removed.length > 1 ? 'them' : 'it'} to apply.` + cliNote,
(allNoRestart ? 'No restart needed.' : `Restart ${removed.length > 1 ? 'them' : 'it'} to apply.`) + cliNote,
);
} else if (cliRemoved) {
clack.outro(`No ${location} agent had CodeGraph configured.` + cliNote);
Expand Down
133 changes: 133 additions & 0 deletions src/installer/targets/dsh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* DSH (DeepSeek Harness) target.
*
* The first instructions-only target: DSH has no MCP client, so there
* is no server config to write. Its agents pick up instructions from
* plain markdown instead:
*
* - local → `<project-root>/AGENTS.md` (workspace instruction
* candidates are AGENTS.md / CLAUDE.md; we write only AGENTS.md so
* the same marker block never lands in two files)
* - global → `<DSH_HOME>/AGENTS.md` (a single user-global
* instruction file; DSH_HOME defaults to `~/.dsh`)
*
* Both surfaces are re-read by running sessions (content-digested each
* turn), so unlike MCP-wired targets there is nothing to restart.
* Agents drive CodeGraph through its CLI (`codegraph explore …`),
* which prints the same output as the codegraph_explore MCP tool —
* exactly the "non-MCP harness" audience the shared instructions
* block was reintroduced for (#704).
*
* No permissions concept — there are no MCP tools to auto-allow, and
* shell invocations follow whatever permission policy the harness
* already runs under. `autoAllow` is silently ignored.
*
* Docs: https://colbymchenry.github.io/codegraph/
*/

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import {
AgentTarget,
DetectionResult,
InstallOptions,
Location,
WriteResult,
} from './types';
import { CODEGRAPH_SECTION_START, CODEGRAPH_SECTION_END } from '../instructions-template';
import {
upsertInstructionsEntry,
removeMarkedSection,
} from './shared';

/** `$DSH_HOME` when set, else `~/.dsh`. Mirrors DSH's own resolution order. */
function dshHome(): string {
return process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
}

function instructionsPath(loc: Location): string {
return loc === 'global'
? path.join(dshHome(), 'AGENTS.md')
: path.join(process.cwd(), 'AGENTS.md');
}

class DshTarget implements AgentTarget {
readonly id = 'dsh' as const;
readonly displayName = 'DSH (DeepSeek Harness)';
// AGENTS.md is content-digested every turn by running sessions, so a
// restart adds nothing.
readonly requiresRestart = false;

supportsLocation(_loc: Location): boolean {
return true;
}

detect(loc: Location): DetectionResult {
const file = instructionsPath(loc);
let alreadyConfigured = false;
try {
alreadyConfigured =
fs.existsSync(file) &&
fs.readFileSync(file, 'utf-8').includes(CODEGRAPH_SECTION_START);
} catch { /* unreadable → treat as unconfigured */ }
// Best-effort "is DSH present here" heuristic, mirroring the other
// targets: the harness home (global) or an existing workspace
// instructions file / git root (local). False negatives just mean
// the user opts in manually.
const installed =
loc === 'global'
? fs.existsSync(dshHome())
: fs.existsSync(file) || fs.existsSync(path.join(process.cwd(), '.git'));
return { installed, alreadyConfigured, configPath: file };
}

install(loc: Location, _opts: InstallOptions): WriteResult {
const files: WriteResult['files'] = [upsertInstructionsEntry(instructionsPath(loc))];
return {
files,
notes: [
'No MCP config written — DSH has no MCP client; its agent calls the `codegraph` CLI via its shell tool.',
'Running DSH sessions pick up AGENTS.md changes automatically; no restart needed.',
],
};
}

uninstall(loc: Location): WriteResult {
return {
files: [
{
path: instructionsPath(loc),
action: removeMarkedSection(
instructionsPath(loc),
CODEGRAPH_SECTION_START,
CODEGRAPH_SECTION_END,
),
},
],
};
}

printConfig(loc: Location): string {
return [
`# DSH (${loc}) — no MCP config; CodeGraph is wired through the`,
`# ${instructionsPath(loc)} instructions block (DSH loads AGENTS.md`,
`# every turn; its agent runs \`codegraph\` commands via its shell tool):`,
'',
'<!-- CODEGRAPH_START -->',
'## CodeGraph',
'',
'In repositories indexed by CodeGraph (a `.codegraph/` directory exists at the repo root), reach for it BEFORE grep/find or reading files:',
'',
'- Shell: `codegraph explore "<symbol names or question>"` answers most code questions in one call.',
'- If there is no `.codegraph/` directory, skip CodeGraph entirely.',
'<!-- CODEGRAPH_END -->',
].join('\n');
}

describePaths(loc: Location): string[] {
return [instructionsPath(loc)];
}
}

export const dshTarget = new DshTarget();
2 changes: 2 additions & 0 deletions src/installer/targets/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { kiroTarget } from './kiro';
import { copilotVscodeTarget } from './copilot-vscode';
import { copilotCliTarget } from './copilot-cli';
import { copilotJetbrainsTarget } from './copilot-jetbrains';
import { dshTarget } from './dsh';

export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
claudeTarget,
Expand All @@ -32,6 +33,7 @@ export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
copilotVscodeTarget,
copilotCliTarget,
copilotJetbrainsTarget,
dshTarget,
]);

export function getTarget(id: string): AgentTarget | undefined {
Expand Down
Loading