Skip to content
Merged
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: 8 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,18 @@ Testing: logging is a silent no-op until `initLogging` runs, so ordinary tests n

`src/lib/codegraph.ts` integrates the external [CodeGraph](https://www.npmjs.com/package/@colbymchenry/codegraph) tool (a CLI + MCP server). Two surfaces:

1. **Install wiring.** Tools map to CodeGraph `--target` ids via `toolToCodegraphTarget` (the three CLI agents, plus both Claude Code *extension* variants → `claude`; Continue → none). The work is split in two:
- **Install** (`ensureCodegraphInstalled` = `npm i -g @colbymchenry/codegraph`, always) runs *before* finalize, as a visible row in the `Install` `TaskList` (labeled with the npm package name, like the agent rows) (`src/components/Install.tsx`, keyed `CODEGRAPH_TASK_KEY`). In **install mode** it sits alongside the agent rows (parallel install). In **config mode** the agents are already installed, so `Install` is rendered with `includeAgents={false}` — a CodeGraph-only step titled "Installing CodeGraph", shown right after login (only when `codegraphTargets(tools).length > 0`; otherwise config skips straight to the post-login side-effects / key-choice). The `CODEGRAPH_TASK_KEY` sentinel is **not** a `Tool` — in install mode `handleInstallDone` splits it out of the survivor set (Configure/shims would choke on it) and excludes it from the all-failed fail-stop; in config mode the survivor set is just `tools` (the CodeGraph row is best-effort and never gates the agents).
1. **Install wiring.** Tools map to CodeGraph's *built-in* `--target` ids via `toolToCodegraphTarget` (the three CLI agents, plus both Claude Code *extension* variants → `claude`; Continue → none; `codev-code` → null, see below). The work is split in two:
- **Install** (`ensureCodegraphInstalled` = `npm i -g @colbymchenry/codegraph`, always) runs *before* finalize, as a visible row in the `Install` `TaskList` (labeled with the npm package name, like the agent rows) (`src/components/Install.tsx`, keyed `CODEGRAPH_TASK_KEY`). In **install mode** it sits alongside the agent rows (parallel install). In **config mode** the agents are already installed, so `Install` is rendered with `includeAgents={false}` — a CodeGraph-only step titled "Installing CodeGraph", shown right after login (only when `codegraphEligible(tools)`; otherwise config skips straight to the post-login side-effects / key-choice). The `CODEGRAPH_TASK_KEY` sentinel is **not** a `Tool` — in install mode `handleInstallDone` splits it out of the survivor set (Configure/shims would choke on it) and excludes it from the all-failed fail-stop; in config mode the survivor set is just `tools` (the CodeGraph row is best-effort and never gates the agents).
- **MCP wiring** (`setupCodegraph` → `runCodegraphInstall` = `codegraph install --target <csv> --location global --yes`) runs in `runFinalizeSideEffects`. `setupCodegraph` assumes CodeGraph is already installed — it only wires.

The whole thing is **best-effort**: the install row soft-fails as a yellow ▲ (never a ✗, never affects the fail-stop), and a wiring failure becomes a `warning` result rendered as a ▲ row — neither aborts the CoDev flow. An empty target set returns `skipped` and renders nothing. CodeGraph's own `--yes` install skips putting itself on PATH, which is why CoDev installs the package itself (the MCP configs reference a bare `codegraph` command that must resolve at agent-launch time).
The whole thing is **best-effort**: the install row soft-fails as a yellow ▲ (never a ✗, never affects the fail-stop), and a wiring failure becomes a `warning` result rendered as a ▲ row — neither aborts the CoDev flow. An empty-eligibility selection returns `skipped` and renders nothing. CodeGraph's own `--yes` install skips putting itself on PATH, which is why CoDev installs the package itself (the MCP configs reference a bare `codegraph` command that must resolve at agent-launch time).

**CoDev Code wiring is special.** codegraph has no built-in target for the fork, so `setupCodegraph` wires it per run via one of two paths that produce byte-identical config (the `mcp.codegraph` entry in `codevCodeConfigPath()`, i.e. `~/.config/codev/codev.json(c)`): **Path A** — when `supportsCustomTargets()` (probe: `codegraph targets list`, read-only; the capability is codegraph PR #1459) reports support, register `CODEV_TARGET_SPEC` via `codegraph targets add` and append `codev` to the one install CSV; **Path B** — otherwise `wireCodevCodeMcp()` edits the config directly (surgical jsonc-parser `modify`/`applyEdits`; idempotent — an already-correct entry writes nothing; refuses malformed files). A registration failure on a capable binary silently falls back to the shim. Because codev-hub npm-installs the latest codegraph right before wiring, Path A activates machine-by-machine the moment upstream releases custom targets — once the released floor supports them, delete Path B and the probe (pure code removal, no migration). Gating uses `codegraphEligible(tools)` (built-in targets **or** codev-code), never `codegraphTargets(...).length` — codev-code is the always-on flagship tool, and gating on targets alone would write an MCP entry referencing a binary that was never installed.

**Config-rewrite preservation.** `configureOpenCodeKind` (OpenCode + the fork) whole-file-replaces its config, and runs not just at install time but on every gateway-key auto-refresh (`refresh.ts#ensureFreshGatewayKey`) and `codevhub model` switch. It therefore carries the existing top-level `mcp` map (via `readPreservedMcp` — best-effort, only object-valued maps) across the rewrite; without that, every refresh would silently unwire CodeGraph (either path) and any MCP servers the user added. The `mcp` entry never lands in `*.backup` (taken before CoDev's first write), so restore still returns the true pre-CoDev state.

2. **Command passthrough.** `codevhub codegraph <args>` forwards verbatim to `codegraph <args>` via `forwardToCodegraph` (e.g. `codevhub codegraph init -y`). It mirrors `src/lib/run.ts#runAgent` (inherited stdio, SIGINT/SIGTERM swallowing, win32 `shell:true`) minus the shim-dir stripping and upload daemon — CodeGraph isn't a chat agent and isn't shimmed. ENOENT prints an install hint.

3. **Removal.** `codevhub remove` (`src/lib/remove.ts#runRemove`) runs `runCodegraphUninstall` (`codegraph uninstall --location global --yes`) before the config restores, to revert CodeGraph's MCP wiring across agents. It does NOT npm-uninstall the codegraph package (matching how remove leaves the codev-ai package). It's best-effort via a new `"warning"` `StepStatus`: if the codegraph package was already removed the command errors (ENOENT), and the step is a ▲ warning that's excluded from `anyFailed` — so the remove still succeeds. `RemoveApp` renders warning steps in both the success and failure views.
3. **Removal.** `codevhub remove` (`src/lib/remove.ts#runRemove`) runs `runCodegraphUninstall` (`codegraph uninstall --location global --yes`) before the config restores, to revert CodeGraph's MCP wiring across agents, then sweeps CoDev Code's `mcp.codegraph` entry directly via `unwireCodevCodeMcp()` (an older codegraph doesn't know the custom target, and the entry may be shim-written; the sweep no-ops when a custom-target-aware uninstall already removed it, and drops an emptied `mcp` wrapper). It does NOT npm-uninstall the codegraph package (matching how remove leaves the codev-ai package). It's best-effort via a new `"warning"` `StepStatus`: if the codegraph package was already removed the command errors (ENOENT), and the step is a ▲ warning that's excluded from `anyFailed` — so the remove still succeeds. `RemoveApp` renders warning steps in both the success and failure views.

Spawn/exec are routed through stubbable indirections for tests: `codegraphRunner.spawn` (passthrough) and `lib/npm.ts#execAsync` (install). The Install/Config integration tests spy on both `ensureCodegraphInstalled` and `setupCodegraph` so neither the Install step nor finalize shells out.
16 changes: 9 additions & 7 deletions src/SetupApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { smokeTestModel, validateApiKey } from "@/lib/backend.js";
import {
CODEGRAPH_TASK_KEY,
type CodegraphSetupResult,
codegraphEligible,
codegraphTargets,
formatCodegraphTargets,
setupCodegraph,
Expand Down Expand Up @@ -320,11 +321,12 @@ export function SetupApp({ mode }: SetupAppProps) {
}
// Config mode skips the *agent* install (they're treated as already
// installed, so the survivor set equals `tools`), but still installs
// CodeGraph. When any selected agent maps to a CodeGraph target, show
// the CodeGraph-only Install step right after login; otherwise run the
// post-login side-effects directly. authData is passed explicitly
// because the setAuth above hasn't flushed to state yet this tick.
if (codegraphTargets(tools).length > 0) {
// CodeGraph. When any selected agent is CodeGraph-eligible (built-in
// target or CoDev Code), show the CodeGraph-only Install step right
// after login; otherwise run the post-login side-effects directly.
// authData is passed explicitly because the setAuth above hasn't
// flushed to state yet this tick.
if (codegraphEligible(tools)) {
setStep("installing");
} else {
runPostInstallSideEffects(tools, authData);
Expand Down Expand Up @@ -615,7 +617,7 @@ export function SetupApp({ mode }: SetupAppProps) {
</Step>
)}
{(mode === "install" ||
(mode === "config" && codegraphTargets(tools).length > 0)) &&
(mode === "config" && codegraphEligible(tools))) &&
POST_LOGIN.includes(step) && (
<Step
active={step === "installing"}
Expand Down Expand Up @@ -779,7 +781,7 @@ export function SetupApp({ mode }: SetupAppProps) {
</Step>
))}
{(step === "finalizing" || step === "done") &&
codegraphTargets(installedTools).length > 0 &&
codegraphEligible(installedTools) &&
codegraphResult?.status !== "skipped" && (
<Step
active={step === "finalizing"}
Expand Down
16 changes: 9 additions & 7 deletions src/components/Install.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type TaskItem, TaskList } from "@/components/TaskList.js";
import {
CODEGRAPH_PKG,
CODEGRAPH_TASK_KEY,
codegraphTargets,
codegraphEligible,
ensureCodegraphInstalled,
} from "@/lib/codegraph.js";
import type { Tool } from "@/lib/configure.js";
Expand Down Expand Up @@ -102,12 +102,14 @@ export function Install({ tools, onDone, includeAgents = true }: InstallProps) {
};
});

// When any selected agent maps to a CodeGraph target, install CodeGraph
// alongside the agents (it runs in parallel with them). Best-effort: a
// failure is a soft ▲ warning, never a hard ✗ — CodeGraph is an enhancement
// and must never drop an agent or trip the all-failed gate. SetupApp splits
// CODEGRAPH_TASK_KEY out of the survivor set (it isn't a Tool).
if (codegraphTargets(tools).length > 0) {
// When any selected agent is CodeGraph-eligible (maps to a built-in target,
// or is CoDev Code, wired via custom target / config shim), install
// CodeGraph alongside the agents (it runs in parallel with them).
// Best-effort: a failure is a soft ▲ warning, never a hard ✗ — CodeGraph is
// an enhancement and must never drop an agent or trip the all-failed gate.
// SetupApp splits CODEGRAPH_TASK_KEY out of the survivor set (it isn't a
// Tool).
if (codegraphEligible(tools)) {
tasks.push({
key: CODEGRAPH_TASK_KEY,
label: CODEGRAPH_PKG,
Expand Down
Loading
Loading