diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b2d07fb3..78b33075a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `/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) diff --git a/README.md b/README.md index 4f89a40e6..9e8ee9043 100644 --- a/README.md +++ b/README.md @@ -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** @@ -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)
@@ -105,7 +106,7 @@ In a **new terminal**, run the installer to connect CodeGraph to the agents you codegraph install ``` -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.) +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.) ### 3. Initialize each project @@ -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`. @@ -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 `/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 @@ -882,7 +884,7 @@ MIT
-**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) diff --git a/__tests__/installer-targets.test.ts b/__tests__/installer-targets.test.ts index 4ec3e5903..148da9674 100644 --- a/__tests__/installer-targets.test.ts +++ b/__tests__/installer-targets.test.ts @@ -40,6 +40,7 @@ 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; @@ -47,6 +48,10 @@ function setHome(dir: string): { restore: () => void } { 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; @@ -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; }, }; } @@ -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'); + }); +}); diff --git a/src/bin/codegraph.ts b/src/bin/codegraph.ts index e4038200d..aa4c12868 100644 --- a/src/bin/codegraph.ts +++ b/src/bin/codegraph.ts @@ -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 ', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt') .option('-l, --location ', 'Install location: "global" or "local". Default: prompt') .option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on') @@ -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 ', 'Target agent(s): comma-separated ids, or "all". Default: all') .option('-l, --location ', 'Uninstall location: "global" or "local". Default: prompt') .option('-y, --yes', 'Non-interactive: defaults to --location=global --target=all') diff --git a/src/installer/index.ts b/src/installer/index.ts index 9a99ffe34..77289e8e9 100644 --- a/src/installer/index.ts +++ b/src/installer/index.ts @@ -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); } @@ -577,9 +579,10 @@ export async function runUninstaller(opts: RunUninstallerOptions): Promise 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); diff --git a/src/installer/targets/dsh.ts b/src/installer/targets/dsh.ts new file mode 100644 index 000000000..599d4fea8 --- /dev/null +++ b/src/installer/targets/dsh.ts @@ -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 → `/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 → `/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', + '', + '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 ""` answers most code questions in one call.', + '- If there is no `.codegraph/` directory, skip CodeGraph entirely.', + '', + ].join('\n'); + } + + describePaths(loc: Location): string[] { + return [instructionsPath(loc)]; + } +} + +export const dshTarget = new DshTarget(); diff --git a/src/installer/targets/registry.ts b/src/installer/targets/registry.ts index 3798b39ad..a58930cda 100644 --- a/src/installer/targets/registry.ts +++ b/src/installer/targets/registry.ts @@ -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, @@ -32,6 +33,7 @@ export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([ copilotVscodeTarget, copilotCliTarget, copilotJetbrainsTarget, + dshTarget, ]); export function getTarget(id: string): AgentTarget | undefined { diff --git a/src/installer/targets/types.ts b/src/installer/targets/types.ts index d93680573..0b3b61a52 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' | 'copilot-vscode' | 'copilot-cli' | 'copilot-jetbrains'; +export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'gemini' | 'antigravity' | 'kiro' | 'copilot-vscode' | 'copilot-cli' | 'copilot-jetbrains' | 'dsh'; /** * Result of `target.detect(location)`. @@ -84,6 +84,14 @@ export interface AgentTarget { readonly displayName: string; /** Optional URL for "where do I learn more about this agent." */ readonly docsUrl?: string; + /** + * Whether the agent must be restarted after install/uninstall for + * the wiring change to apply (MCP-server configs load at startup). + * Defaults to true. Instructions-only targets whose harness re-reads + * its instructions file every turn set false so the final summary + * doesn't tell users to restart for nothing. + */ + readonly requiresRestart?: boolean; /** * Whether this target supports the given install location. *