feat(cli+installer): codegraph explore/node CLI + instructions-file block — subagent & non-MCP reach (#704) (#819)

Task-tool subagents never see the MCP initialize instructions and hold
the MCP tools only as deferred names they rarely think to load — so
delegated work bypassed codegraph almost entirely (measured ~1 of 9
forced-delegation runs touched it; the rest did 30-50 grep/read calls).
Two additions close the gap:

- CLI: `codegraph explore` and `codegraph node` call the same ToolHandler
  as the MCP tools and print identical output — the graph for any agent
  with a shell (subagents, Gemini CLI, raw Codex, humans).
- Installer: each agent target (claude/codex/gemini/opencode) writes a
  short marker-fenced CodeGraph section into its instructions file —
  the one channel subagents DO receive — naming both surfaces. Upsert
  self-heals the stale pre-#529 long block; uninstall strips it; re-runs
  are byte-equal unchanged. (#529's duplication argument bounded the
  size: four lines, commands only.)

A/B (excalidraw, sonnet/high, forced Explore-agent delegation): without
the block, subagent codegraph usage ~1/9 runs; with it, 4/4 — subagents
ToolSearch-load the MCP tools and run explore 5-7x, best runs with ZERO
Read/grep (80-95s vs 150-197s baseline). The block's mechanism: the
parent relays the note into the task prompt, making the deferred tool
names salient.

Contract tests updated to the new expectations (write + self-heal
replace the #529 strip-only behavior); README install/guidance sections
refreshed (they also still described the pre-#817/#818 tool surface).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-11 21:50:41 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent c450fd95b7
commit 8170d181f2
10 changed files with 226 additions and 70 deletions
+7 -9
View File
@@ -34,6 +34,7 @@ import {
readJsonFile,
removeMarkedSection,
writeJsonFile,
upsertInstructionsEntry,
} from './shared';
import {
CODEGRAPH_SECTION_END,
@@ -120,15 +121,12 @@ class ClaudeCodeTarget implements AgentTarget {
const hookCleanup = cleanupLegacyHooks(loc);
if (hookCleanup.action === 'removed') files.push(hookCleanup);
// 3. CLAUDE.md instructions — no longer written. The codegraph
// usage guidance now ships solely in the MCP server's `initialize`
// response (see `mcp/server-instructions.ts`), which Claude Code
// surfaces in the system prompt automatically. Writing it into
// CLAUDE.md as well meant the agent read the same playbook twice
// every turn (issue #529). Strip any block a previous install left
// behind so an upgrade self-heals — same idiom as the hook cleanup.
const instrCleanup = removeInstructionsEntry(loc);
if (instrCleanup.action === 'removed') files.push(instrCleanup);
// 3. CLAUDE.md instructions — the short marker-fenced CodeGraph
// block (#704). The MCP initialize instructions reach only the main
// agent; CLAUDE.md is what Task-tool subagents (and non-MCP
// harnesses) actually see, so the block carries the codegraph
// pointers there. Upsert self-heals a stale pre-#529 long block.
files.push(upsertInstructionsEntry(instructionsPath(loc)));
return { files };
}
+5 -5
View File
@@ -28,6 +28,7 @@ import {
atomicWriteFileSync,
getMcpServerConfig,
removeMarkedSection,
upsertInstructionsEntry,
} from './shared';
import {
CODEGRAPH_SECTION_END,
@@ -83,11 +84,10 @@ class CodexTarget implements AgentTarget {
files.push(writeMcpEntry());
// AGENTS.md is no longer written — the codegraph usage guidance
// ships in the MCP server's `initialize` response (issue #529).
// Strip a block a previous install left so an upgrade self-heals.
const instrCleanup = removeInstructionsEntry();
if (instrCleanup.action === 'removed') files.push(instrCleanup);
// AGENTS.md gets the short marker-fenced CodeGraph block (#704):
// subagents and non-MCP harnesses read AGENTS.md but never the MCP
// initialize instructions. Upsert self-heals a stale pre-#529 block.
files.push(upsertInstructionsEntry(instructionsPath()));
return { files };
}
+5 -5
View File
@@ -38,6 +38,7 @@ import {
readJsonFile,
removeMarkedSection,
writeJsonFile,
upsertInstructionsEntry,
} from './shared';
import {
CODEGRAPH_SECTION_END,
@@ -84,11 +85,10 @@ class GeminiTarget implements AgentTarget {
const files: WriteResult['files'] = [];
files.push(writeMcpEntry(loc));
// GEMINI.md is no longer written — the codegraph usage guidance
// ships in the MCP server's `initialize` response (issue #529).
// Strip a block a previous install left so an upgrade self-heals.
const instrCleanup = removeInstructionsEntry(loc);
if (instrCleanup.action === 'removed') files.push(instrCleanup);
// GEMINI.md gets the short marker-fenced CodeGraph block (#704):
// subagents and non-MCP harnesses read GEMINI.md but never the MCP
// initialize instructions. Upsert self-heals a stale pre-#529 block.
files.push(upsertInstructionsEntry(instructionsPath(loc)));
return { files };
}
+5 -5
View File
@@ -41,6 +41,7 @@ import {
atomicWriteFileSync,
jsonDeepEqual,
removeMarkedSection,
upsertInstructionsEntry,
} from './shared';
import {
CODEGRAPH_SECTION_END,
@@ -127,11 +128,10 @@ class OpencodeTarget implements AgentTarget {
const files: WriteResult['files'] = [];
files.push(writeMcpEntry(loc));
// AGENTS.md is no longer written — the codegraph usage guidance
// ships in the MCP server's `initialize` response (issue #529).
// Strip a block a previous install left so an upgrade self-heals.
const instrCleanup = removeInstructionsEntry(loc);
if (instrCleanup.action === 'removed') files.push(instrCleanup);
// AGENTS.md gets the short marker-fenced CodeGraph block (#704):
// subagents and non-MCP harnesses read AGENTS.md but never the MCP
// initialize instructions. Upsert self-heals a stale pre-#529 block.
files.push(upsertInstructionsEntry(instructionsPath(loc)));
return { files };
}
+25
View File
@@ -10,6 +10,11 @@
import * as fs from 'fs';
import * as path from 'path';
import {
CODEGRAPH_INSTRUCTIONS_BLOCK,
CODEGRAPH_SECTION_START,
CODEGRAPH_SECTION_END,
} from '../instructions-template';
/**
* The MCP-server config block codegraph injects. Same shape across
@@ -167,6 +172,26 @@ export function replaceOrAppendMarkedSection(
return 'appended';
}
/**
* Upsert the CodeGraph instructions block into an agent instructions
* file (CLAUDE.md / AGENTS.md / GEMINI.md). The one write shared by
* every target: self-heals a stale pre-#529 long block (markers match →
* replaced by the current short one), appends after existing user
* content otherwise, and reports `unchanged` on byte-equal re-runs so
* install stays idempotent. See `instructions-template.ts` for why this
* block exists (#704: subagents + non-MCP harnesses never see the MCP
* initialize instructions).
*/
export function upsertInstructionsEntry(file: string): { path: string; action: 'created' | 'updated' | 'unchanged' } {
const action = replaceOrAppendMarkedSection(
file,
CODEGRAPH_INSTRUCTIONS_BLOCK,
CODEGRAPH_SECTION_START,
CODEGRAPH_SECTION_END,
);
return { path: file, action: action === 'appended' ? 'updated' : action };
}
/**
* Inverse of `replaceOrAppendMarkedSection`. Strips the marker
* block from `filePath` if present. If the file becomes empty after