Guidance for sessions, streaming queries, tool wrappers, and review-oriented agent loops.
Use Codegraph for structural repo questions: architecture, dependency direction, symbol definitions, semantic references, hotspots, cycles, public API surface, and PR impact. Use plain text search alongside it for raw strings, logs, config keys, and non-symbol patterns.
For an unfamiliar repo, the shortest useful loop is:
codegraph doctor
codegraph inspect ./src --limit 20
codegraph search "auth user" --json
codegraph explain src/auth.ts --json
codegraph artifact build --root . --out codegraph-out --json
codegraph doctor codegraph-out
codegraph mcp serve --root . --stdio
codegraph mcp serve --root . --port 7331Then use the recommended commands from inspect, or the stable handles and follow-up commands from search, to narrow the next graph, navigation, or impact pass.
For durable repo-local scan scope, add codegraph.config.json at the project root. discovery.ignoreGlobs keeps large fixture, generated, or vendored folders out of agent search, MCP sessions, graphing, unresolved-import checks, impact, and review unless a command explicitly changes scan scope.
For the raw CLI command reference, see docs/cli.md.
Use search when an agent needs a compact starting point before calling goto, refs, deps, rdeps, chunk, or later explanation tooling:
codegraph search "validate user" --json
codegraph search "public users" --mode sql --json
codegraph search "handle login" --mode graph --from src/auth.ts --depth 1 --json
codegraph explain "<handle-from-search>" --jsonSearch results include project-relative handle, rankReasons, evidence, neighbors, followUps, resultCount, totalCandidates, limits, and omittedCounts. explain accepts those handles plus file paths, symbol names, and SQL object names, then returns bounded symbols, dependencies, reverse dependencies, references, snippets, SQL relation facts, optional changed-context review tasks/candidate tests, explicit limits, omission counts, and follow-ups. Generated command strings POSIX-shell-quote dynamic arguments when needed. SQL object handles or schema-qualified names avoid ambiguous unqualified basenames. Reference and snippet omission counts are bounded lower bounds once navigation limits are reached, so small explain packets stay cheap on high-fan-in symbols. Both commands are deterministic and do not require embeddings or prebuilt artifacts.
Use artifact build when the agent needs a durable handoff directory. The default bundle writes SQLite, self-describing project-relative graph JSON with symbols, a concise Markdown report, suggested questions, and a manifest. Suggested questions command stable handles, not ambiguous bare names, and use unique IDs even when display labels collide. In-repo artifact output directories and linked outside-root files are excluded from the emitted artifacts so stale handoff files do not feed back into the graph. With --force, Codegraph removes recognizable stale artifact files while preserving unrelated operator files and refusing unrecognized reserved-name collisions. codegraph doctor <artifact-dir> recognizes manifest-backed bundle directories and reports which expected artifacts are present.
Use codegraph mcp serve --root . --stdio when an agent can spawn a stdio MCP server, or codegraph mcp serve --root . --port 7331 for Streamable HTTP at /mcp. HTTP binds to 127.0.0.1 by default; pass --host <host> only when the server must be reachable elsewhere. MCP reuses one in-process Codegraph session and exposes the same deterministic primitives as compact tools: search, get_file, get_symbol, goto, refs, deps, rdeps, path, impact, review, query_sqlite, and artifact_build.
MCP is an ergonomics and performance layer, not a separate analysis engine. It gives agents stable handles from search and explain, avoids rebuilding the project for each follow-up call, and returns bounded snippets/resources. File and artifact paths are confined to the project root after realpath resolution. Tools are read-only by default; query_sqlite rejects mutating SQL, recursive queries, and synthetic payload functions while capping returned rows and bytes. artifact_build is available only when the server is started with --allow-build.
For agents performing code reviews or making multiple queries, use sessions to maintain warm caches:
import { createCodeReviewSession } from "@lzehrung/codegraph";
const session = await createCodeReviewSession({
root: "/path/to/repo",
buildOptions: {
cache: "disk",
useBloomFilters: true,
},
timeout: 30 * 60 * 1000,
});
const impact = await session.analyzeImpact({
provider: "git",
base: "main",
head: "feature-branch",
});
const refs = await session.findReferences({
file: "/path/to/file.ts",
line: 10,
column: 5,
});
const def = await session.goToDefinition({
file: "/path/to/file.ts",
line: 15,
column: 8,
});
await session.refresh();
const stats = session.getStats();
console.log(`Files: ${stats.fileCount}, Symbols: ${stats.symbolCount}`);
session.dispose();Important session contracts:
- Session impact calls use the same required provider contract as
analyzeImpactFromDiff(). - Session navigation rejects files outside the session root with
{ status: "error", reason: "outside_project_root" }.
import { createCodeReviewSession } from "@lzehrung/codegraph";
const session = await createCodeReviewSession({
root: "/path/to/repo",
preset: "code-review",
});
const customSession = await createCodeReviewSession({
root: "/path/to/repo",
preset: "ci-fast",
buildOptions: {
threads: 16,
},
});Available presets:
code-review: balanced speed and accuracy for PR reviewsci-fast: maximum speed for CI and CDdevelopment: fast feedback for local developmentproduction: maximum accuracy
import { SessionManager } from "@lzehrung/codegraph";
const manager = new SessionManager();
const pr1Session = await manager.getOrCreateSession("pr-123", {
root: "/path/to/repo",
});
const pr2Session = await manager.getOrCreateSession("pr-456", {
root: "/path/to/repo",
});
const sameSession = await manager.getOrCreateSession("pr-123", {
root: "/path/to/repo",
});
manager.cleanupExpired();
const allStats = manager.getAllStats();
console.log(Boolean(pr1Session), Boolean(pr2Session), Boolean(sameSession), allStats);Stream impact results as they are discovered so the agent can start reasoning before the full pass completes:
import { buildProjectIndex, analyzeImpactStreaming } from "@lzehrung/codegraph";
const root = process.cwd();
const index = await buildProjectIndex(root);
for await (const chunk of analyzeImpactStreaming(root, index, {
provider: "git",
base: "main",
head: "feature-branch",
})) {
if (chunk.type === "progress") {
console.log(`${chunk.message}: ${chunk.current}/${chunk.total}`);
} else if (chunk.type === "changedSymbol") {
console.log(`Changed: ${chunk.symbol.name} in ${chunk.symbol.file}`);
} else if (chunk.type === "impactItem") {
console.log(`Impacted: ${chunk.item.file} (${chunk.item.severity})`);
} else if (chunk.type === "complete") {
console.log(`Analysis complete: ${chunk.summary.totalImpacted} files impacted`);
} else if (chunk.type === "error") {
console.error(`Error: ${chunk.error}`);
}
}Use the same pattern through a warm session when repeated review passes matter:
import { createCodeReviewSession } from "@lzehrung/codegraph";
const session = await createCodeReviewSession({ root: "/path/to/repo" });
for await (const chunk of session.analyzeImpactStream({
provider: "git",
base: "main",
head: "feature-branch",
})) {
if (chunk.type === "impactItem") {
await analyzeImpactedFile(chunk.item);
}
}Use partial-result helpers when the agent should keep going even if a subset of files fails:
import { withPartialResults, summarizePartialResult } from "@lzehrung/codegraph";
const files = ["file1.ts", "file2.ts", "file3.ts"];
const result = await withPartialResults(files, async (file) => await analyzeFile(file), {
continueOnError: true,
concurrency: 8,
});
if (result.status === "complete") {
console.log("All files processed successfully");
} else if (result.status === "partial") {
console.log(`Partial success: ${result.coverage * 100}% complete`);
console.log(`Succeeded: ${result.metadata?.succeeded}, Failed: ${result.metadata?.failed}`);
processResults(result.data);
for (const error of result.errors) {
console.error(`${error.target}: ${error.message}`);
}
} else {
console.error("Operation failed completely");
}
console.log(summarizePartialResult(result));Symbol query syntax is a compact key:value format with optional free text:
kind:function name:handler file:src/api
docstring:"rate limit" auth
Supported keys:
kindorkindsnamefiledocordocstring
Programmatic helpers:
import { querySymbols, querySymbolNeighbors } from "@lzehrung/codegraph";
const hits = querySymbols(symbolGraph, {
kinds: ["function"],
nameIncludes: "handler",
fileIncludes: "src/api",
});
const neighbors = querySymbolNeighbors(symbolGraph, {
symbolId: hits[0]?.id ?? "",
direction: "both",
maxDepth: 2,
edgeLabels: ["calls", "instantiates"],
});These wrappers are designed to be imported directly into agent runtimes:
import {
buildProjectIndex,
tool_getFileOverview,
tool_findSymbol,
tool_impactJSON,
tool_getDependencies,
tool_getReverseDependencies,
tool_getHotspots,
tool_goToDefinition,
tool_findReferences,
} from "@lzehrung/codegraph";
const root = process.cwd();
const index = await buildProjectIndex(root);
const overview = await tool_getFileOverview(root, "src/utils.ts", { index });
const matches = await tool_findSymbol(root, "collectGraph", { index });
const deps = await tool_getDependencies(root, "src/main.ts", { depth: 2, limit: 20, index });
const reverseDeps = await tool_getReverseDependencies(root, "src/index.ts", { depth: 2, limit: 20, index });
const hotspots = await tool_getHotspots(root, { limit: 20, index });
const impact = await tool_impactJSON(
root,
{
provider: "git",
base: "HEAD",
head: "WORKTREE",
},
{ index },
);
const definition = await tool_goToDefinition(root, "src/main.ts", 10, 5, index, { native: "on" });
const references = await tool_findReferences(root, "src/main.ts", 10, 5, index);Wrapper notes:
- Import only from
@lzehrung/codegraph. - When the agent runtime calls Codegraph as a TypeScript library, prefer structured fields over rendered CLI text. A deterministic review agent should usually call
buildReviewReport()for changed-file and task metadata, thenanalyzeImpactFromDiff()oranalyzeImpactStreaming()for impact and graph context. Use CLI output only when the agent is operating through a shell tool. - For streaming review packs, keep the default
streamSummary: "full"when the final pack needs suggestions, export summaries, re-export chains, ranked top impacts, graph edges, cycles, clusters, and surface area. Streaming always returnsformat: "stream-summary"; forwardedcompactis accepted only for compatibility and is ignored. UsestreamSummary: "light"when the agent only needs progressive chunks plus final changed/impacted counts and details. - Build one shared index per agent pass when you will call multiple wrappers in sequence.
tool_getFileOverview(),tool_getGraph(), andtool_impactJSON()now acceptindexthrough their runtime-options argument, while the bounded graph wrappers already accept it in their options object. - Native runtime control is not passed uniformly across all wrappers:
tool_goToDefinitionandtool_findReferencesaccept trailing runtime options, whiletool_findSymbol,tool_getDependencies,tool_getReverseDependencies, andtool_getHotspotstakenativeinside their options object. tool_getFileOverviewreturns structuredok,not_found, anderrorvariants so agents can distinguish missing files from invalid inputs cleanly.tool_findSymbolreturns stableidhandles plusrange,exported,exactMatch, andmatchKind.tool_goToDefinitionandtool_findReferencesinclude additiveprovenancemetadata when resolution is not just a local binding lookup.- Prefer
tool_getDependencies,tool_getReverseDependencies, andtool_getHotspotsbeforetool_getGraphwhen the agent only needs a bounded graph slice. - Batch impact wrappers return
schemaVersionandformat: "full" | "compact"so downstream prompts can branch on payload shape directly; streamingcomplete.reportusesformat: "stream-summary".
The codegraph review CLI produces JSON bundles that are intended to be fed directly to agents or downstream scripts:
codegraph review --base origin/main --head HEAD > review.json
codegraph review --base origin/main --head HEAD --include-symbol-details --max-callsites 5 > review.json
codegraph review --base origin/main --head HEAD --review-depth standard > review.jsonFor current local edits, start with a ranked human map, then hand off the compact review summary:
codegraph impact --base HEAD --head WORKTREE --pretty
codegraph review --base HEAD --head WORKTREE --summaryUse --head STAGED instead of WORKTREE when the review should cover only the index. Keep the full JSON review bundle for scripts or agent steps that need projectFiles, graphDelta, or detailed symbol handles.
For function-call integrations, keep the JSON object as the handoff. Do not parse review --summary or impact --pretty text to recover fields that are already present in the TypeScript return values.
In summary mode, high-confidence direct import matches are the first regression targets and medium matches are likely file-level coverage. Low-confidence pattern matches are summarized as breadth hints; use the full JSON bundle only when you need to inspect those fallback candidates.
These bundles highlight:
- symbol-level changes
- updated dependency edges
- likely regression tests
- risk summaries and review tasks
For the exact JSON shape and CLI flags, see docs/cli.md.
These patterns combine Codegraph's core capabilities with backend-review heuristics.
import { analyzeImpactFromDiff, buildProjectIndex } from "@lzehrung/codegraph";
const root = process.cwd();
const index = await buildProjectIndex(root);
const impact = await analyzeImpactFromDiff(root, index, {
provider: "git",
base: "main",
head: "feature-branch",
depth: 2,
compact: true,
});
const apiRoutes = impact.impacted.filter(
(item) => item.file.includes("routes") || item.file.includes("controllers") || item.file.includes("api"),
);
const breakingChanges = impact.changedSymbols.filter(
(symbol) => symbol.exported && symbol.explain?.hints?.includes("signatureChanged"),
);
console.log(`API routes impacted: ${apiRoutes.length}`);
console.log(`Breaking changes: ${breakingChanges.length}`);import { collectImpactContext } from "@lzehrung/codegraph";
const schemaChanges = impact.changedSymbols.filter(
(symbol) => symbol.file.includes("models") || symbol.file.includes("schema") || symbol.file.includes("migrations"),
);
if (schemaChanges.length > 0) {
const context = await collectImpactContext(
index,
impact.impacted.map((item) => item.file),
impact.changedSymbols.map((symbol) => symbol.id),
3,
);
const affectedServices = context.symbolNeighbors.filter(
(neighbor) => neighbor.file.includes("services") || neighbor.file.includes("repositories"),
);
console.log(`Services needing migration review: ${affectedServices.length}`);
}import { listCandidateTestFiles } from "@lzehrung/codegraph";
const candidateTests = listCandidateTestFiles(
index,
impact.changedFiles.map((file) => file.file),
impact.changedSymbols.map((symbol) => symbol.id),
{
testPatterns: ["test", "spec", "__tests__", ".test."],
maxCandidates: 20,
},
);
const highPriorityTests = candidateTests.filter((test) => test.confidence === "high");
const mediumPriorityTests = candidateTests.filter((test) => test.confidence === "medium");
console.log(`High-priority tests to review: ${highPriorityTests.length}`);
console.log(`Medium-priority tests to check: ${mediumPriorityTests.length}`);import { textGrep } from "@lzehrung/codegraph";
const securityPatterns = [
"exec\\(|eval\\(|spawn\\(",
"password|secret|key.*=",
"sql.*\\+|\\$\\{.*\\}",
"innerHTML|outerHTML",
];
const securityFindings: Array<{ file: string; pattern: string; line: number }> = [];
for (const changedFile of impact.changedFiles) {
for (const pattern of securityPatterns) {
try {
const matches = await textGrep(root, pattern, [changedFile.file], {
maxHits: 200,
});
for (const match of matches) {
securityFindings.push({
file: match.file,
pattern,
line: match.line,
});
}
} catch {
// Skip invalid regex patterns
}
}
}
if (securityFindings.length > 0) {
console.log(`Security findings: ${securityFindings.length}`);
}const configChanges = impact.changedFiles.filter(
(file) =>
file.file.includes("config") ||
file.file.endsWith(".env") ||
file.file.includes("docker") ||
file.file.includes("terraform") ||
file.file.includes("package.json"),
);
if (configChanges.length > 0) {
console.log(`Configuration files changed: ${configChanges.length}`);
}const perfHotspots = impact.impacted.filter(
(item) =>
item.file.includes("query") ||
item.file.includes("cache") ||
item.file.includes("index") ||
item.file.includes("perf"),
);
if (perfHotspots.length > 0) {
console.log(`Performance-sensitive files impacted: ${perfHotspots.length}`);
}