File: README.md
code-review-graph
AI coding tools can end up re-reading large parts of your codebase on review tasks. code-review-graph fixes that. It builds a structural map of your code with Tree-sitter, tracks changes incrementally, and gives your AI assistant precise context via MCP so it reads only what matters.
Quick Start
pip install code-review-graph # or: pipx install code-review-graph
code-review-graph install # auto-detects and configures all supported platforms
code-review-graph build # parse your codebaseOne command sets up everything. install detects which AI coding tools you have, writes the correct MCP configuration for each one, installs platform-native hooks/skills where supported, and injects graph-aware instructions into your platform rules. It auto-detects whether you installed via uvx or pip/pipx and generates the right config. Restart your editor/tool after installing.
To target a specific platform:
code-review-graph install --platform codex # configure only Codex
code-review-graph install --platform cursor # configure only Cursor
code-review-graph install --platform claude-code # configure only Claude Code
code-review-graph install --platform gemini-cli # configure only Gemini CLI
code-review-graph install --platform antigravity # configure only Antigravity
code-review-graph install --platform windsurf # configure only Windsurf
code-review-graph install --platform zed # configure only Zed
code-review-graph install --platform continue # configure only Continue
code-review-graph install --platform opencode # configure only OpenCode
code-review-graph install --platform qwen # configure only Qwen
code-review-graph install --platform qoder # configure only Qoder
code-review-graph install --platform kiro # configure only Kiro
code-review-graph install --platform copilot # configure only GitHub Copilot (VS Code)
code-review-graph install --platform copilot-cli # configure only GitHub Copilot CLI
code-review-graph install --platform codebuddy # configure only CodeBuddy Code
code-review-graph install --platform hermes # configure only Hermes AgentRequires Python 3.10+. For the best experience, install uv (the MCP config will use uvx if available, otherwise falls back to the code-review-graph command directly).
To remove CRG from a Git or SVN project, use the symmetric uninstall command
from anywhere inside its working tree. The target is normalized to the working
tree root, and non-repository directories are refused. It removes only
CRG-owned files and entries; unrelated MCP servers, hooks, skills, and JSONC
comments remain untouched. Shared configuration changes use atomic replacement
so a failed write leaves the original file intact.
code-review-graph uninstall --dry-run # preview every action; write nothing
code-review-graph uninstall # preview, ask for confirmation, then apply
code-review-graph uninstall --yes # apply without prompting
code-review-graph uninstall --all-repos # also clean every registered repository
code-review-graph uninstall --keep-data # remove integrations but keep graph databases
code-review-graph uninstall --keep-user-configs --repo . # clean this project onlyThen open your project and ask your AI assistant:
Build the code review graph for this projectThe initial build takes ~10 seconds for a 500-file project. After that, watch mode and supported hooks can keep the graph updated automatically.
How It Works
Your repository is parsed into an AST with Tree-sitter, stored as a graph of nodes (functions, classes, imports) and edges (calls, inheritance, test coverage), then queried at review time to compute the minimal set of files your AI assistant needs to read.
Blast-radius analysis
When a file changes, the graph traces every caller, dependent, and test that could be affected. This is the "blast radius" of the change. Your AI reads only these files instead of scanning the whole project.
Incremental updates in seconds
When hooks or watch mode are enabled, file saves and supported commit hooks trigger incremental updates. The graph diffs changed files, finds their dependents through the graph's own import and call edges, and re-parses only the files whose SHA-256 hash actually changed. On a ~3,000-file project (django) a two-file edit re-indexes in about 2.5 seconds on the path the hooks use, of which ~1.4 s is process start-up; a no-op update costs only that start-up. See Incremental update latency for the full measurement.
Whole codebase or targeted answer?
The bigger the repository, the more token waste hurts. Instead of feeding a whole corpus to the model, the graph returns an answer-shaped slice of it: on this repository, 208,821 source tokens become ~3,190 tokens per question.
Broad language coverage + Jupyter notebooks
Parser support covers functions, classes, imports, call sites, inheritance, and test detection across the current parser surface, using Tree-sitter where available and targeted fallbacks where needed. Current support includes Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, VB.NET, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Terraform/OpenTofu structure (.tf; generic .hcl files are recognized as file nodes), Ansible playbooks/roles/tasks, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks (.ipynb), and Perl XS files (.xs). Generic YAML is not treated as source code.
PHP projects additionally get repository-bounded Composer PSR-4 resolution,
Blade template references, and Laravel Route/Eloquent semantic edges when the
source includes explicit framework imports, model inheritance, and receiver
evidence.
Add your own language (no fork needed)
If your repo uses a language the parser does not cover yet, drop a languages.toml into .code-review-graph/ mapping file extensions to any grammar bundled in tree_sitter_language_pack, plus the tree-sitter node types for functions, classes, imports, and calls:
[languages.erlang]
extensions = [".erl"]
grammar = "erlang"
function_node_types = ["function_clause"]
class_node_types = ["record_decl"]
import_node_types = ["import_attribute"]
call_node_types = ["call"]The generic tree-sitter walker handles extraction from there โ no code changes, and built-in languages can never be overridden. See docs/CUSTOM_LANGUAGES.md for the schema reference, validation rules, and a worked end-to-end example.
Risk-scored PR reviews in CI (GitHub Action)
The same analysis runs as a composite GitHub Action โ and it stays local-first: the knowledge graph is built and queried entirely on your CI runner, with no source code sent to any external service. On each pull request the action posts a single sticky comment with risk-scored functions, affected execution flows, and test gaps, updated in place on every push. An optional fail-on-risk input turns the review into a merge gate.
# .github/workflows/code-review-graph.yml
on:
pull_request:
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: tirth8205/[email protected]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}See docs/GITHUB_ACTION.md for inputs, risk levels, and caching details, or the dogfood workflow this repo runs on itself in .github/workflows/pr-review.yml.
Benchmarks
Headline number: the median per-question token reduction across the 6 repos is ~65x (whole-corpus baseline vs graph query). The 376x maximum is a single best-case repo (fastapi, the largest corpus) โ not the typical result.
All numbers come from the automated evaluation runner against 6 real open-source repositories (13 commits total). Every config pins an upstream SHA, the Leiden community detector runs with a fixed seed, and embeddings are deterministic on CPU โ so two runs on different machines produce identical numbers. The full reproduction recipe with expected outputs is in docs/REPRODUCING.md. A weekly report-only run on the two smallest configs lives in .github/workflows/eval.yml.
Token efficiency: ~65x median per-question reduction (range 36x โ 376x; whole-corpus vs graph query)
For a typical agent question ("how does authentication work", "what is the main entry point", etc.), the graph returns ~2,000โ3,500 tokens of targeted search hits + neighbor edges instead of forcing the agent to read every source file. The table below averages over the 5 sample questions defined in code_review_graph/token_benchmark.py.
| Repo | Snapshot SHA | naive_corpus_tokens | avg graph_tokens | Reduction |
|---|---|---|---|---|
| fastapi | 22381558 |
948,793 | 2,653 | 375.6x |
| flask | a29f88ce |
143,594 | 2,196 | 71.0x |
| code-review-graph | 84bde354 |
208,821 | 3,190 | 68.1x |
| gin | 5c00df8a |
166,868 | 2,766 | 61.9x |
| httpx | b55d4635 |
142,356 | 2,661 | 60.6x |
| express | b4ab7d65 |
136,052 | 3,936 | 36.0x |
Median per-question reduction across the 6 repos: ~65x. The range is 36x โ 376x, where 376x is the best case (fastapi, the largest corpus), not the headline.
Re-captured 2026-08-02 from clean clones at the pinned SHAs (crg 2.3.7, local
all-MiniLM-L6-v2embeddings). These numbers are lower than the 2026-05-25 capture they replace: the graph response grew as node embedding text became richer, soavg graph_tokensrose across every repo. fastapi is now measured at its current pin22381558rather than the retired0227991a.
The whole-corpus baseline above is an upper bound no real agent pays: a competent agent greps for identifiers and reads only the best-matching files. The agent_baseline eval benchmark measures that realistic baseline โ a pure-python grep over the corpus, top-3 files by match count, token-counted and compared to the graph query cost (evaluate/results/<repo>_agent_baseline_*.csv).
The formal eval/benchmarks/token_efficiency.py benchmark measures a different scenario โ full get_review_context() JSON versus just the changed-file content of a commit โ and reports ratios below 1 for small commits, because the review-context response carries impact-radius edges plus source snippets that exceed a tiny single-file diff. That is not a bug; the two benchmarks answer different questions. See docs/REPRODUCING.md for the full methodology.
Since v2.3.4, review and impact tools attach a compact context_savings estimate so MCP clients can see the approximate context saved per call. In v2.3.5 the CLI surfaces this as the boxed Token Savings panel shown above (see "Token Savings panel" in the Usage section) and adds --verify to cross-check against OpenAI's cl100k_base tokenizer. Calibration data in docs/REPRODUCING.md shows the estimate is within ~1% of real GPT-4 tokens in aggregate across 222 sample files.
Impact accuracy: 0.69 average F1 against graph-derived ground truth (recall 1.0 is a circular upper bound, not "100% recall")
Blast-radius analysis recovers every file in the ground truth on all 13 evaluation commits โ but read that as an upper bound, not as "100% recall": in this mode the ground truth (changed files + files with call/import edges into them) is derived from the same graph the predictor traverses, so it is circular by construction. The over-prediction visible in the precision column is a deliberate trade-off: better to flag too many files than miss a broken dependency.
| Repo | Commits | Avg F1 | Avg Precision | Recall (graph-derived upper bound) |
|---|---|---|---|---|
| httpx | 2 | 0.863 | 0.785 | 1.0 |
| code-review-graph | 2 | 0.734 | 0.584 | 1.0 |
| fastapi | 2 | 0.697 | 0.539 | 1.0 |
| express | 2 | 0.667 | 0.500 | 1.0 |
| flask | 2 | 0.633 | 0.485 | 1.0 |
| gin | 3 | 0.609 | 0.439 | 1.0 |
| Average | 13 | 0.693 | 0.546 | 1.000 |
The benchmark also runs an honest co-change mode: the predictor is seeded with a single changed file and graded against the other files the author actually touched in the same commit โ independent-ish evidence from git history, not from the graph. Both modes appear side by side in the result CSVs (ground_truth_mode column). As of the 2026-08-02 capture that mode returns predicted_files = 0 on every graded commit, so it is not yet a usable measurement and no co-change number is quoted here โ the harness needs fixing before the mode says anything about accuracy.
Build performance
| Repo | Files | Nodes | Edges | Flow Detection | Search Latency |
|---|---|---|---|---|---|
| express | 141 | 1,910 | 17,553 | 106ms | 0.7ms |
| fastapi | 1,122 | 6,285 | 27,117 | 128ms | 1.5ms |
| flask | 83 | 1,446 | 7,974 | 95ms | 0.7ms |
| gin | 99 | 1,286 | 16,762 | 111ms | 0.5ms |
| httpx | 60 | 1,253 | 7,896 | 96ms | 0.4ms |
Limitations and known weaknesses
- Impact "recall 1.0" is graph-derived and circular: the historical ground truth comes from the same graph edges the predictor walks, so it is an upper bound by construction. The honest co-change mode (grade against files actually co-changed in the same commit) is measured alongside it; expect those numbers to be substantially lower.
- Small single-file changes: Graph context can exceed naive file reads for trivial edits (see express results above). The overhead is the structural metadata that enables multi-file analysis.
- Search quality (MRR 0.35): Keyword search finds the right result in the top-4 for most queries, but ranking needs improvement. Express queries return 0 hits due to module-pattern naming.
- Flow detection (33% recall): Framework and conventional entry patterns are strongest for Python and PHP/Laravel. JavaScript and Go flow detection needs work.
- Precision vs recall trade-off: Impact analysis is deliberately conservative. It flags files that might be affected, which means some false positives in large dependency graphs.
Features
| Feature | Details |
|---|---|
| Incremental updates | Re-parses only the files whose hash changed. On a ~3,000-file repo a two-file edit takes ~2.5s on the hook path (measured). |
| Broad language + notebook support | Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, VB.NET, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Terraform/OpenTofu structure (.tf; generic .hcl files are file-only), Ansible playbooks/roles/tasks, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks (.ipynb), and Perl XS (.xs) |
| Framework-aware PHP parsing | Repository-bounded Composer PSR-4 imports, Blade template references, and evidence-gated Laravel Route-to-controller and Eloquent relationship edges |
| Blast-radius analysis | Shows which functions, classes, and files are likely affected by a change |
| Auto-update hooks | Hooks and watch mode can update the graph on file saves and supported commit hooks |
| Semantic search | Optional vector embeddings via sentence-transformers, Google Gemini, MiniMax, or any OpenAI-compatible endpoint (real OpenAI, Azure, new-api, LiteLLM, vLLM, LocalAI) |
| Interactive visualisation | D3.js force-directed graph with search, community legend toggles, and degree-scaled nodes |
| Hub & bridge detection | Find most-connected nodes and architectural chokepoints via betweenness centrality |
| Surprise scoring | Detect unexpected coupling: cross-community, cross-language, peripheral-to-hub edges |
| Knowledge gap analysis | Identify isolated nodes, untested hotspots, thin communities, and structural weaknesses |
| Suggested questions | Auto-generated review questions from graph analysis (bridges, hubs, surprises) |
| Edge confidence | Three-tier confidence scoring (EXTRACTED/INFERRED/AMBIGUOUS) with float scores on edges |
| Graph traversal | Free-form BFS/DFS exploration from any node with configurable depth and token budget |
| Export formats | GraphML (Gephi/yEd), Neo4j Cypher, Obsidian vault with wikilinks, SVG static graph |
| Graph diff | Compare graph snapshots over time: new/removed nodes, edges, community changes |
| Token benchmarking | Measure naive full-corpus tokens vs graph query tokens with per-question ratios |
| Estimated context savings | Compact context_savings metadata on relevant MCP/CLI review outputs, labelled as estimated and kept to three small fields |
| Memory loop | Persist Q&A results as markdown for re-ingestion, so the graph grows from queries |
| Community auto-split | Oversized communities (>25% of graph) are recursively split via Leiden |
| Execution flows | Trace call chains from entry points, sorted by weighted criticality |
| Community detection | Cluster related code via Leiden algorithm with resolution scaling for large graphs |
| Architecture overview | Auto-generated architecture map with coupling warnings |
| Risk-scored reviews | detect_changes maps diffs to affected functions, flows, and test gaps |
| Custom languages | Add new languages via .code-review-graph/languages.toml โ no fork or code changes needed |
| GitHub Action | Sticky risk-scored PR review comments in CI, with an optional fail-on-risk merge gate |
| Refactoring tools | Rename preview, framework-aware dead code detection, community-driven suggestions |
| Wiki generation | Auto-generate markdown wiki from community structure |
| Multi-repo registry | Register multiple repos, search across all of them |
| Multi-repo daemon | crg-daemon watches multiple repos as child processes, with health checks and auto-restart |
| MCP prompts | 5 workflow templates: review, architecture, debug, onboard, pre-merge |
| Full-text search | FTS5-powered hybrid search combining keyword and vector similarity |
| Local storage | SQLite file in .code-review-graph/. Core graph storage needs no external database or cloud service. |
| Watch mode | Continuous graph updates as you work |
Usage
Slash commands
| Command | Description |
|---|---|
/code-review-graph:build-graph |
Build or rebuild the code graph |
/code-review-graph:review-delta |
Review changes since last commit |
/code-review-graph:review-pr |
Full PR review with blast-radius analysis |
CLI reference
code-review-graph install # Auto-detect and configure all platforms
code-review-graph install --platform <name> # Target a specific platform
code-review-graph uninstall --dry-run # Preview safe removal of installed artifacts
code-review-graph build # Parse entire codebase
code-review-graph update # Incremental update (changed files only)
code-review-graph status # Graph statistics
code-review-graph watch # Auto-update on file changes
code-review-graph visualize # Generate interactive HTML graph
code-review-graph visualize --format json # Export local graph data as JSON
code-review-graph visualize --format graphml # Export as GraphML
code-review-graph visualize --format svg # Export as SVG
code-review-graph visualize --format obsidian # Export as Obsidian vault
code-review-graph visualize --format cypher # Export as Neo4j Cypher
code-review-graph wiki # Generate markdown wiki from communities
code-review-graph detect-changes --brief # Risk panel + token savings (read-only)
code-review-graph update --brief # Refresh graph + same panel
code-review-graph detect-changes --brief --verify # Cross-check vs tiktoken
code-review-graph register # Register repo in multi-repo registry
code-review-graph unregister # Remove repo from registry
code-review-graph repos # List registered repositories
code-review-graph daemon start # Start multi-repo watch daemon
code-review-graph daemon stop # Stop the daemon
code-review-graph daemon status # Show daemon status and repos
code-review-graph eval # Run evaluation benchmarks
code-review-graph serve # Start MCP serverJSON exports stay inside the local graph data directory, which Git ignores by
default. They can contain absolute paths and code-structure metadata, so inspect
and sanitize an export before publishing it outside your machine.
Token Savings panel: detect-changes --brief vs update --brief
Both commands print the same compact panel showing how many tokens the
graph saved you compared to handing the changed files to an agent raw.
They differ in one thing: whether the graph gets refreshed first.
โโโโโโโโโโโโโโโโโโโโโโโโ Token Savings โโโโโโโโโโโโโโโโโโโโโโโโโ
โ Full context would be: 12,921 tokens โ
โ Graph context used: 762 tokens โ
โ Saved: 12,159 tokens (~94%) โ
โ Breakdown: Functions 244 ยท Tests 191 ยท Risk 244 ยท Other 83 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ| Command | What it does | When to use |
|---|---|---|
detect-changes --brief |
Read-only. Looks at your current changes, queries the existing graph, prints the panel. ~1 sec. | Most of the time โ the hooks (or crg-daemon) keep the graph fresh in the background, so this is enough. |
update --brief |
Re-parses your changed files into the graph first, then prints the same panel. ~5 sec. | After a rebase, a large change set, or any time you suspect the graph is stale. |
Both end with the same panel because both call the same analyze_changes() step at the end. The difference is whether the graph itself got refreshed before that analysis ran.
Add --verify to either command to cross-check the displayed numbers against OpenAI's cl100k_base tokenizer (the GPT-4 family). Requires pip install tiktoken. The estimate stays within ~1% of real tokens on a typical change set โ see docs/REPRODUCING.md for the calibration data.
The same context_savings metadata is also attached automatically to the JSON responses of get_impact_radius, get_review_context, detect_changes, and get_architecture_overview MCP tools, so AI agents can surface the savings to humans in chat without any extra prompting.
Multi-repo daemon
If your editor doesn't support hooks (e.g. Cursor, OpenCode), or you just want your
graph to stay fresh in the background without any editor integration, the daemon is
for you. It watches your repos for file changes and automatically rebuilds the graph
โ no manual build or update commands needed.
The daemon is included with code-review-graph โ no separate install required.
Quick setup:
# 1. Register the repos you want to watch
crg-daemon add ~/project-a --alias proj-a
crg-daemon add ~/project-b
# 2. Start the daemon (runs in the background)
crg-daemon start
# 3. That's it โ graphs stay up to date automatically
crg-daemon status # check daemon and per-repo watcher status
crg-daemon logs --repo proj-a -f # tail logs for a specific repo
crg-daemon stop # stop daemon and all watcher processesAlso available as code-review-graph daemon start|stop|status|....
Under the hood, crg-daemon add writes to a TOML config file at~/.code-review-graph/watch.toml. You can also edit this file directly:
[[repos]]
path = "/home/user/project-a"
alias = "proj-a"
[[repos]]
path = "/home/user/project-b"
alias = "project-b"The daemon monitors this config file for changes and automatically starts/stops
watcher processes as repos are added or removed. Health checks every 30 seconds
restart dead watchers. No external dependencies required.
See docs/COMMANDS.md for the
full config reference and all available options.
30 MCP tools
Your AI assistant uses these automatically once the graph is built.
| Tool | Description |
|---|---|
build_or_update_graph_tool |
Build or incrementally update the graph |
run_postprocess_tool |
Re-run flow detection, community detection, and FTS indexing |
get_minimal_context_tool |
Ultra-compact context (~100 tokens) โ call this first |
get_impact_radius_tool |
Blast radius of changed files |
get_review_context_tool |
Token-optimised review context with structural summary |
query_graph_tool |
Callers, callees, tests, imports, inheritance queries |
traverse_graph_tool |
BFS/DFS traversal from any node with token budget |
semantic_search_nodes_tool |
Search code entities by name or meaning |
embed_graph_tool |
Compute vector embeddings for semantic search |
list_graph_stats_tool |
Graph size and health |
get_docs_section_tool |
Retrieve documentation sections |
find_large_functions_tool |
Find functions/classes exceeding a line-count threshold |
list_flows_tool |
List execution flows sorted by criticality |
get_flow_tool |
Get details of a single execution flow |
get_affected_flows_tool |
Find flows affected by changed files |
list_communities_tool |
List detected code communities |
get_community_tool |
Get details of a single community |
get_architecture_overview_tool |
Architecture overview from community structure |
detect_changes_tool |
Risk-scored change impact analysis for code review |
get_hub_nodes_tool |
Find most-connected nodes (architectural hotspots) |
get_bridge_nodes_tool |
Find chokepoints via betweenness centrality |
get_knowledge_gaps_tool |
Identify structural weaknesses and untested hotspots |
get_surprising_connections_tool |
Detect unexpected cross-community coupling |
get_suggested_questions_tool |
Auto-generated review questions from analysis |
refactor_tool |
Rename preview, dead code detection, suggestions |
apply_refactor_tool |
Apply a previously previewed refactoring |
generate_wiki_tool |
Generate markdown wiki from communities |
get_wiki_page_tool |
Retrieve a specific wiki page |
list_repos_tool |
List registered repositories |
cross_repo_search_tool |
Search across all registered repositories |
MCP Prompts (5 workflow templates):review_changes, architecture_map, debug_issue, onboard_developer, pre_merge_check
Configuration
To exclude paths from indexing, create a .code-review-graphignore file in your repository root:
generated/**
*.generated.ts
vendor/**
node_modules/**Note: in git repos, only tracked files are indexed (git ls-files), so gitignored files are skipped automatically. Use .code-review-graphignore to exclude tracked files or when git isn't available.
Optional dependency groups:
pip install "code-review-graph[embeddings]" # Local vector embeddings (sentence-transformers)
pip install "code-review-graph[google-embeddings]" # Google Gemini embeddings
pip install "code-review-graph[communities]" # Community detection (igraph)
pip install "code-review-graph[enrichment]" # Python call-resolution enrichment (Jedi)
pip install "code-review-graph[eval]" # Evaluation benchmarks (matplotlib)
pip install "code-review-graph[wiki]" # Wiki generation with LLM summaries (ollama)
pip install "code-review-graph[all]" # All optional dependenciesEnvironment Variables
| Variable | Description | Default |
|---|---|---|
CRG_GIT_TIMEOUT |
Timeout in seconds for Git operations | 30 |
CRG_DATA_DIR |
Override directory for graph databases and generated graph artefacts | - |
CRG_EMBEDDING_MODEL |
Default model for vector embeddings | all-MiniLM-L6-v2 |
CRG_ACCEPT_CLOUD_EMBEDDINGS |
Suppress the cloud embedding egress warning after explicit acknowledgement | - |
CRG_ALLOW_REMOTE_CODE |
Allow HuggingFace models that require trust_remote_code=True |
0 |
CRG_MAX_IMPACT_NODES |
Maximum nodes to include in impact analysis | 500 |
CRG_MAX_IMPACT_DEPTH |
Search depth for blast-radius analysis | 2 |
CRG_MAX_BFS_DEPTH |
Maximum depth for graph traversal | 15 |
CRG_MAX_CHANGED_FUNCS |
Maximum changed functions analysed in one change report | 500 |
CRG_MAX_TRANSITIVE_FRONTIER |
Maximum frontier size for transitive caller/callee expansion | 50 |
CRG_TOOL_TIMEOUT |
Optional timeout in seconds for bounded MCP tools (0 disables timeout) |
0 |
CRG_RECURSE_SUBMODULES |
Include git submodules in file collection when set to 1, true, or yes |
- |
CRG_TOOLS |
Comma-separated allowlist of MCP tools to expose when serving | - |
GOOGLE_API_KEY |
API key for Google Gemini embeddings | - |
MINIMAX_API_KEY |
API key for MiniMax embeddings | - |
VOYAGE_API_KEY |
API key for Voyage embeddings | - |
CRG_VOYAGE_MODEL |
Model name for Voyage embeddings | voyage-code-3 |
CRG_VOYAGE_OUTPUT_DIMENSION |
Output dimension for Voyage embeddings | 1024 |
CRG_VOYAGE_OUTPUT_DTYPE |
Output dtype for Voyage embeddings | float |
CRG_VOYAGE_BASE_URL |
Voyage embeddings endpoint | https://api.voyageai.com/v1 |
CRG_VOYAGE_BATCH_SIZE |
Batch size for Voyage embedding requests | 100 |
CRG_VOYAGE_MIN_INTERVAL_SEC |
Minimum delay between Voyage requests | 0 |
CRG_OPENAI_BASE_URL |
OpenAI-compatible embeddings endpoint | - |
CRG_OPENAI_API_KEY |
API key for OpenAI-compatible embeddings | - |
CRG_OPENAI_MODEL |
Model name for OpenAI-compatible embeddings | - |
CRG_OPENAI_DIMENSION |
Pin embedding dimension (v3 models support reduction) | - |
NO_COLOR |
If set, disables ANSI colors in terminal | - |
CRG_SERIAL_PARSE |
If 1, disables parallel parsing (use for debugging) |
- |
OpenAI-compatible embeddings (real OpenAI, Azure, or any self-hosted gateway like
new-api / LiteLLM / vLLM / LocalAI / Ollama in openai mode) need no extra install โ
just set the environment variables and pass provider="openai" to embed_graph:
export CRG_OPENAI_BASE_URL=http://127.0.0.1:3000/v1 # or https://api.openai.com/v1
export CRG_OPENAI_API_KEY=sk-...
export CRG_OPENAI_MODEL=text-embedding-3-small # whatever your gateway serves
# optional:
export CRG_OPENAI_DIMENSION=1536 # pin dim (v3 models support reduction)
export CRG_OPENAI_BATCH_SIZE=100 # lower for gateways with tight limits
# (e.g. Qwen text-embedding-v4 caps at 10)The cloud-egress warning is auto-skipped when the base URL points to localhost
(127.0.0.1, localhost, 0.0.0.0, ::1).
Voyage embeddings need no extra install. Set VOYAGE_API_KEY and passprovider="voyage" to embed_graph; the default model is voyage-code-3:
export VOYAGE_API_KEY=pa-...
export CRG_ACCEPT_CLOUD_EMBEDDINGS=1
code-review-graph embed --provider voyage --model voyage-code-3Model selection tip. Avoid
-preview/-beta/-expmodel IDs
(e.g.google/gemini-embedding-2-preview) for anything you plan to keep
long-term โ preview models can change weights (different dimension โ full
re-embed required) or be deprecated without notice. Prefer stable GA
releases such astext-embedding-3-small/text-embedding-3-large(OpenAI),Qwen/Qwen3-Embedding-8B(via self-hosted vLLM / LocalAI), orgemini-embedding-001(via the native Gemini provider, which requiresGOOGLE_API_KEYinstead of the OpenAI-compatible path).
code-review-graphembeds identifiers, signatures, structural context, and a
bounded first-paragraph docstring/doc-comment summary. It does not transmit
function bodies. Graphs created before documentation extraction was added
need one fullcode-review-graph buildbefore re-embedding so every file is
reparsed. Routine builds never refresh embeddings by default. To refresh an
existing index after a build, explicitly pass both--embedding-provider
and--embedding-model; cloud choices may transmit this source-derived text
and incur API cost.
Tool Filtering
CRG exposes 30 MCP tools by default. In token-constrained environments, you can
limit the server to a subset of tools using --tools or the CRG_TOOLS
environment variable:
# Via CLI flag
code-review-graph serve --tools query_graph_tool,semantic_search_nodes_tool,detect_changes_tool
# Via environment variable
CRG_TOOLS=query_graph_tool,semantic_search_nodes_tool code-review-graph serveThe CLI flag takes precedence over the environment variable. When neither is set,
all tools are available. This is especially useful for MCP client configurations:
{
"mcpServers": {
"code-review-graph": {
"command": "code-review-graph",
"args": ["serve", "--tools", "query_graph_tool,semantic_search_nodes_tool,detect_changes_tool,get_review_context_tool"]
}
}
}FAQ & how it compares
Short, honest answers in docs/FAQ.md:
- vs LSP / language servers โ one persistent cross-language graph instead of per-language daemons; LSP stays more precise per symbol.
- vs RAG / embeddings โ structural edges parsed from the AST, not similarity chunks; embeddings are optional and only assist search.
- vs grep / agentic search โ grep wins on one-hop lookups; the graph wins on multi-hop questions (impact radius, callers-of-callers, tests-for, affected flows).
- vs Serena, codegraph, claude-context, repomix โ factual comparison table.
- When NOT to use it โ small repos, trivial single-file diffs, one-off questions.
- Does it phone home? โ no; zero telemetry, cloud embeddings are opt-in.
- How do I verify it is working? โ
status,detect-changes --brief,/mcp.
Troubleshooting
`pip` / `pipx` cannot download `hatchling` (or `Errno 9` / `Bad file descriptor` to PyPI)
Installing from a source tree (for example pipx install .) needs build dependencies from PyPI (for example hatchling). If you see Could not find a version that satisfies the requirement hatchling after connection warnings, the Python/pip in that terminal may not be able to open an HTTPS client to pypi.org (sometimes seen in an integrated editor terminal; less often system-wide with VPN, firewall, or proxy).
Options:
Run the same command from macOS Terminal.app (or iTerm) instead of the IDEโs terminal, then retry
pipx install .orpipx install "git+https://...".Use uv to install the CLI from a checkout (uses different download machinery than
pipin many cases):bashcd /path/to/code-review-graph uv tool install . --forceFor development in a clone without a global install, use
uv syncanduv run code-review-graph โฆ(or activate.venvafteruv sync).
Diagnose (optional): python3 scripts/diagnose_pypi_connectivity.py โ if it prints FAILED, the issue is environment/network, not a wrong package name in this repo.
Windows Configuration Issues (Invalid JSON / Connection Closed)
If you are using Windows and encounter Invalid JSON: EOF while parsing or MCP error -32000: Connection closed when connecting via Claude Code, do not use the cmd /c wrapper in your config.
Ensure fastmcp is updated to at least 3.2.4+. Then, configure your ~/.claude.json to execute the .exe directly and pass the UTF-8 environment variable via the config:
"code-review-graph": {
"command": "C:\\path\\to\\your\\venv\\Scripts\\code-review-graph.exe",
"args": ["serve", "--repo", "C:\\path\\to\\your\\project"],
"env": { "PYTHONUTF8": "1" }
}Contributing
git clone https://github.com/tirth8205/code-review-graph.git
cd code-review-graph
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytestAdding a new language
Edit code_review_graph/parser.py and add your extension to EXTENSION_TO_LANGUAGE along with node type mappings in _CLASS_TYPES, _FUNCTION_TYPES, _IMPORT_TYPES, and _CALL_TYPES. Include a test fixture and open a PR.
Licence
MIT. See LICENSE.
File: code-review-graph-vscode/README.md
Code Review Graph for VS Code
Visualize code dependencies, blast radius, and review context from your code graph -- directly in VS Code.
Features
- Code Graph Explorer -- Browse files, classes, functions, and their relationships in a tree view
- Blast Radius -- See which files and symbols are impacted when you change code
- Review Changes -- Automatically detect git changes and show their blast radius
- Find Callers / Callees -- Trace all callers or callees of any function
- Find Tests -- Locate tests for any symbol
- Query Graph -- Run semantic queries (callers, callees, imports, inheritance, tests) with 8 patterns
- Find Large Functions -- Identify functions or classes exceeding a line-count threshold
- Interactive Graph -- Force-directed D3.js visualization of your code dependencies
- Live Search -- Fuzzy search across your entire code graph with instant results
- Compute Embeddings -- Generate vector embeddings for semantic search
- Watch Mode -- Continuous graph updates as you work
- Auto-Update -- Graph rebuilds in the background when you save files
Quick Start
1. Install the Extension
Install Code Review Graph from the VS Code Marketplace.
2. Install the Backend
The extension requires the code-review-graph Python CLI to parse your codebase.
# Recommended
uv pip install code-review-graph
# Alternatives
pipx install code-review-graph
pip install code-review-graphRequires Python 3.10+.
3. Build Your Graph
Open the Command Palette (Ctrl+Shift+P) and run Code Graph: Build Graph.
The graph database is stored locally at .code-review-graph/graph.db and updates automatically on file save.
Commands
| Command | Description |
|---|---|
Code Graph: Build Graph |
Parse the codebase and create the graph database |
Code Graph: Update Graph |
Incrementally update the graph |
Code Graph: Show Blast Radius |
Show the blast radius for a symbol |
Code Graph: Review Changes |
Analyze git changes and show impacted files |
Code Graph: Find Callers |
Find all callers of a function |
Code Graph: Find Callees |
Find all functions called by a target |
Code Graph: Find Tests |
Find tests for a symbol |
Code Graph: Find Large Functions |
Find functions/classes exceeding a size threshold |
Code Graph: Query Graph |
Run semantic queries (8 patterns: callers_of, callees_of, etc.) |
Code Graph: Search |
Search the code graph |
Code Graph: Show Graph |
Open the interactive graph visualization |
Code Graph: Compute Embeddings |
Generate vector embeddings for semantic search |
Code Graph: Watch Mode |
Run graph in watch mode for continuous updates |
Settings
| Setting | Default | Description |
|---|---|---|
codeReviewGraph.cliPath |
"" |
Path to the CLI binary. Leave empty to use the bundled version or one found on PATH. |
codeReviewGraph.autoUpdate |
true |
Auto-update the graph on file save. |
codeReviewGraph.blastRadiusDepth |
2 |
Max traversal depth for blast radius (1--10). |
codeReviewGraph.graphTheme |
"auto" |
Graph color theme: auto, light, or dark. |
codeReviewGraph.graph.maxNodes |
500 |
Max nodes in the graph visualization (10--5000). |
codeReviewGraph.graph.defaultEdges |
All except CONTAINS | Edge types shown by default. |
codeReviewGraph.treeView.showFiles |
true |
Show file nodes in the tree view. |
codeReviewGraph.treeView.showClasses |
true |
Show class nodes in the tree view. |
codeReviewGraph.treeView.showFunctions |
true |
Show function nodes in the tree view. |
codeReviewGraph.treeView.showTypes |
true |
Show type nodes in the tree view. |
codeReviewGraph.treeView.showTests |
true |
Show test nodes in the tree view. |
Requirements
- VS Code 1.85+
- Python 3.10+ (for the backend CLI)
- A workspace with source code to analyze
Links
License
MIT
File: docs/architecture.md
Architecture
System Overview
code-review-graph is a local-first code intelligence graph exposed through a CLI and MCP server. It maintains a persistent, incrementally updated knowledge graph of a codebase so AI coding tools can review changes with structural context instead of reading broad file dumps. Claude Code is supported, but it is one client among several.
Component Diagram
/* Detailed source-code truncated for AI context efficiency. */Data Flow
Full Build
collect_all_files()gathers tracked files (git ls-files) and applies.code-review-graphignore(gitignored files are skipped automatically when git is available)- For each file,
CodeParser.parse_file()uses Tree-sitter to extract AST - AST walker identifies structural nodes (classes, functions, imports) and edges (calls, inheritance)
GraphStore.store_file_nodes_edges()persists to SQLite with file hash for change detection- Metadata updated with timestamp
Incremental Update
get_changed_files()uses VCS metadata to identify changed files (git diff by default, with SVN support in the incremental layer)find_dependents()queries the graph for files importing the changed files- Changed + dependent files are re-parsed (others skipped via hash comparison)
- Only affected rows in SQLite are updated
Review Context Generation
- Changed files identified (git diff or explicit list)
get_impact_radius()walks outward from the changed nodes, relaxing a weighted
best-path score per node (per-edge-kind weight ร depth decay) rather than a plain BFS- Source snippets extracted for changed areas only
- Review guidance generated (test coverage gaps, wide blast radius warnings)
- Assembled into a structured, token-efficient context for MCP clients and the CLI
- Where a cheap baseline can be estimated, compact
context_savingsmetadata is attached as an estimate rather than an exact tokenisation
Storage
SQLite Schema
- nodes table: id, kind, name, qualified_name, file_path, line_start/end, language, community_id, etc.
- edges table: id, kind, source_qualified, target_qualified, file_path, line
- metadata table: key-value pairs (last_updated, build_type, schema_version)
- flows table: id, name, entry_point_id, depth, node_count, file_count, criticality, path_json
- flow_memberships table: flow_id, node_id, position
- communities table: id, name, level, parent_id, cohesion, size, dominant_language, description
- nodes_fts (FTS5 virtual table): full-text search on name, qualified_name, file_path, signature
- community_summaries, flow_snapshots, risk_index tables: compact precomputed summaries for token-efficient queries
- embeddings table (separate DB): qualified_name, vector, text_hash, provider
Indexes on qualified_name, file_path, edge source/target, criticality, community_id, and cohesion for fast lookups.
WAL mode enabled for concurrent read access during updates.
Qualified Names
Nodes are uniquely identified by qualified names:
- Files: absolute path (e.g.,
/repo/src/auth.py) - Functions:
file_path::function_name(e.g.,/repo/src/auth.py::authenticate) - Methods:
file_path::ClassName.method_name(e.g.,/repo/src/auth.py::AuthService.login)
Parsing Strategy
Tree-sitter provides language-agnostic AST access. The parser:
- Walks the AST recursively
- Pattern-matches on node types (language-specific mappings in
_CLASS_TYPES,_FUNCTION_TYPES, etc.) - Extracts names, parameters, return types, base classes
- Identifies calls within function bodies
- Resolves imports to module paths
This approach is more robust than tree-sitter queries across grammar versions.
Visualization
The visualization.py module generates an interactive D3.js force-directed graph as a self-contained HTML file. It reads all nodes and edges from the SQLite graph store and renders them in the browser, allowing developers to visually explore code relationships, filter by node kind, and inspect dependencies.
Impact Analysis Algorithm
BFS from seed nodes (changed files' contents):
- Seed = all qualified names in changed files
- For each node in frontier:
- Follow forward edges (what this node affects)
- Follow reverse edges (what depends on this node)
- Expand up to
max_depthhops (default: 2) - Collect all reached nodes as "impacted"
This captures both downstream effects (things that call changed code) and upstream context (things that the changed code depends on).
File: docs/COMMANDS.md
All Available Commands
Skills and Slash Commands
These commands are installed for clients that support project skills or slash-command style workflows.
`/code-review-graph:build-graph`
Build or update the knowledge graph.
- First time: performs a full build
- Subsequent: incremental update (only changed files)
`/code-review-graph:review-delta`
Review only changes since last commit.
- Auto-detects changed files via git diff
- Computes blast radius (2-hop default)
- Generates structured review with guidance
`/code-review-graph:review-pr`
Review a PR or branch diff.
- Uses main/master as base
- Full impact analysis across all PR commits
- Structured output with risk assessment
MCP Tools
Core Tools
`build_or_update_graph_tool`
full_rebuild: bool = False # True for full re-parse
repo_root: str | None # Auto-detected
base: str | None = None # Diff base; None auto-resolves to the last-synced commit
postprocess: str = "full" # "full", "minimal", or "none"
recurse_submodules: bool | None # Falls back to CRG_RECURSE_SUBMODULES`run_postprocess_tool`
flows: bool = True
communities: bool = True
fts: bool = True
repo_root: str | None`get_minimal_context_tool`
task: str = "" # What you are doing
changed_files: list[str] | None # Auto-detected from VCS when omitted
repo_root: str | None
base: str = "HEAD~1"`get_impact_radius_tool`
changed_files: list[str] | None # Auto-detected from VCS
max_depth: int = 2 # Hops in graph
repo_root: str | None
base: str = "HEAD~1"
detail_level: str = "standard" # "standard" or "minimal"Relevant responses may include compact estimated context_savings metadata.
`query_graph_tool`
pattern: str # callers_of, references_to, callees_of, imports_of, importers_of,
# children_of, tests_for, inheritors_of, file_summary
target: str # Node name, qualified name, or file path
repo_root: str | None
detail_level: str = "standard" # "standard" or "minimal"
max_results: int = 100 # Minimal mode additionally caps visible results at 5`get_review_context_tool`
changed_files: list[str] | None
max_depth: int = 2
include_source: bool = True
max_lines_per_file: int = 200 # Capped at 500
repo_root: str | None
base: str = "HEAD~1"
detail_level: str = "standard" # "standard" or "minimal"
max_results: int = 50 # Graph nodes per list (max 100) and edges (max 150)
max_files: int = 25 # Files listed and given snippets (max 200)Snippets share an 800-line budget across the whole response. Each list reports
its untruncated *_total, and context.truncated marks any cut.
Relevant responses may include compact estimated context_savings metadata.
`traverse_graph_tool`
query: str
depth: int = 3 # 1-6
mode: str = "bfs" # "bfs" or "dfs"
token_budget: int = 2000
repo_root: str | None`semantic_search_nodes_tool`
query: str # Search string
kind: str | None # File, Class, Function, Type, Test
limit: int = 20
repo_root: str | None
model: str | None # Embedding model (falls back to provider-specific env vars)
provider: str | None # local, openai, google, minimax, voyage
detail_level: str = "standard"`embed_graph_tool`
repo_root: str | None
model: str | None # Embedding model name
provider: str | None # local, openai, google, minimax, voyageLocal embeddings require: pip install "code-review-graph[embeddings]". Cloud providers use stdlib HTTP clients and require their provider environment variables.
`list_graph_stats_tool`
repo_root: str | None`find_large_functions_tool`
min_lines: int = 50 # Minimum line count threshold
kind: str | None # File, Class, Function, or Test
file_path_pattern: str | None # Filter by file path substring
limit: int = 50 # Max results to return
repo_root: str | None`get_docs_section_tool`
section_name: str # usage, review-delta, review-pr, commands, legal, watch, embeddings, languages, troubleshootingFlow Tools
`list_flows_tool`
sort_by: str = "criticality" # criticality, depth, node_count, file_count, name
limit: int = 50
kind: str | None # Filter by entry point kind (e.g. "Test", "Function")
repo_root: str | None
detail_level: str = "standard"`get_flow_tool`
flow_id: int | None # Database ID from list_flows_tool
flow_name: str | None # Name to search (partial match)
include_source: bool = False # Include source snippets for each step
repo_root: str | None
max_steps: int = 50 # Capped at 200; flow.total_steps reports the full count
max_source_lines: int = 400 # Shared across all steps; capped at 2000`get_affected_flows_tool`
changed_files: list[str] | None # Auto-detected from VCS
base: str = "HEAD~1"
repo_root: str | None
detail_level: str = "standard" # "standard" full step details, "minimal" metadata only
max_flows: int = 50 # 0 means "no caller limit"Standard mode carries a full steps list per flow, so it additionally caps
visible flows at 25 and spends a shared 400-step budget across them; minimal
mode caps at 500. total always reports the untruncated flow count. See #849.
Community Tools
`list_communities_tool`
sort_by: str = "size" # size, cohesion, name
min_size: int = 0
repo_root: str | None
detail_level: str = "standard"
max_results: int = 50 # Communities returned (max 200)
max_members: int = 10 # Member names per community in standard mode (max 25)Each community's size still reports its true member count;members_truncated marks a cut member list.
`get_community_tool`
community_name: str | None # Name to search (partial match)
community_id: int | None # Database ID
include_members: bool = False
repo_root: str | None
max_members: int = 25 # Member entries returned (max 25)`get_architecture_overview_tool`
repo_root: str | None
detail_level: str = "minimal" # "minimal" compact default, "standard" full detail
max_results: int = 100 # Cross-community rows and warnings (max 200)
max_members: int = 10 # Member names per community in standard mode (max 25)cross_community_edges_total reports the untruncated row count.
Minimal responses may include compact estimated context_savings metadata.
Graph Health and Architecture Tools
`get_hub_nodes_tool`
top_n: int = 10 # Capped at 100
repo_root: str | None
detail_level: str = "standard" # "minimal" returns name, kind, total_degree`get_bridge_nodes_tool`
top_n: int = 10 # Capped at 100
repo_root: str | None
detail_level: str = "standard" # "minimal" returns name, kind, betweenness`get_knowledge_gaps_tool`
repo_root: str | None
max_per_category: int = 15 # Entries per gap category (max 50)
detail_level: str = "standard" # "minimal" drops file pathssummary maps each category to its untruncated count.
`get_surprising_connections_tool`
top_n: int = 15 # Capped at 100
repo_root: str | None
detail_level: str = "standard" # "minimal" returns source, target, kind, score`get_suggested_questions_tool`
repo_root: str | NoneChange Analysis and Refactoring Tools
`detect_changes_tool`
base: str = "HEAD~1"
changed_files: list[str] | None
include_source: bool = False
max_depth: int = 2
repo_root: str | None
detail_level: str = "standard"
max_results: int = 25 # Changed functions, test gaps, changed files (max 100)
max_flows: int = 20 # Affected flows embedded (max 200)Primary tool for code review. Maps changed files to affected functions, flows, communities, and test coverage gaps. Returns risk scores and prioritized review items.
Embedded flows carry per-flow metadata only โ use get_affected_flows_tool for
step detail. changed_functions_total, test_gaps_total, andaffected_flows_total report the untruncated counts.
Relevant responses may include compact estimated context_savings metadata.
`refactor_tool`
mode: str = "rename" # "rename", "dead_code", or "suggest"
old_name: str | None # (rename) Current symbol name
new_name: str | None # (rename) New name
kind: str | None # (dead_code) Function or Class
file_pattern: str | None # (dead_code) Filter by file path substring
repo_root: str | None
max_results: int = 50 # Edits/symbols/suggestions returned (max 150)
detail_level: str = "standard" # "minimal" keeps identifying fields onlyTruncating a rename preview truncates only the response: the stored preview
keeps every edit, so apply_refactor_tool still applies the full set.
`apply_refactor_tool`
refactor_id: str # ID from prior refactor_tool call
repo_root: str | None
dry_run: bool = False # Return diff without writing files
max_diff_files: int = 25 # Per-file diffs in a dry run (max 150)Wiki Tools
`generate_wiki_tool`
repo_root: str | None
force: bool = False # Regenerate all pages even if unchanged`get_wiki_page_tool`
community_name: str # Community name to look up
repo_root: str | None
max_chars: int = 20000 # Page content returned (max 80000)total_chars reports the real page length; truncated marks a cut.
Multi-Repo Tools
`list_repos_tool`
(no parameters)`cross_repo_search_tool`
query: str
kind: str | None
limit: int = 20 # Results per repo
max_results: int = 50 # Merged results across all repos (max 100)Result Bounds
Every tool that returns a list is bounded, so no single MCP response can blow
a context window (see #849). The contract is uniform:
- Defaults are small. Pass the tool's cap parameter to widen up to its hard
ceiling, or a smaller value to narrow. - Truncation is never silent: the response reports the untruncated count
(total, or a*_totalfield per list), setstruncated: true, and the
summary line says how many of how many are shown. - Cap parameters reject values below 1 and reject booleans, the same way
query_graph_tool'smax_resultsdoes. - Hard ceilings are enforced in code and pinned by
tests/test_token_budget.py,
which records the measured per-tool budget table as reviewable data.
MCP Prompts (5 workflow templates)
`review_changes`
Pre-commit review workflow using detect_changes, affected_flows, and test gaps.
base: str = "HEAD~1"`architecture_map`
Architecture documentation using communities, flows, and Mermaid diagrams.
`debug_issue`
Guided debugging using search, flow tracing, and recent changes.
`onboard_developer`
New developer orientation using stats, architecture, and critical flows.
`pre_merge_check`
PR readiness check with risk scoring, test gaps, and dead code detection.
base: str = "HEAD~1"CLI Commands
/* Detailed source-code truncated for AI context efficiency. */Standalone Daemon CLI (`crg-daemon`)
The crg-daemon command is included with every code-review-graph installation โ no
separate install required. It is also available as a standalone entry point. It mirrors thecode-review-graph daemon subcommands:
crg-daemon start [--foreground] # Start the multi-repo watch daemon
crg-daemon stop # Stop the daemon and all watcher processes
crg-daemon restart [--foreground] # Restart (stop + start)
crg-daemon status # Show daemon status, repos, and process liveness
crg-daemon logs [--repo ALIAS] [-f] [-n N] # Tail daemon or per-repo log files
crg-daemon add [--alias NAME] # Add a repository to watch.toml
crg-daemon remove # Remove a repository from watch.tomlConfiguration
The daemon reads its configuration from ~/.code-review-graph/watch.toml:
session_name = "crg-watch" # logical daemon name
log_dir = "~/.code-review-graph/logs"
poll_interval = 2 # seconds between config file polls
[[repos]]
path = "/home/user/project-a"
alias = "project-a"
[[repos]]
path = "/home/user/project-b"
alias = "project-b"The daemon spawns one code-review-graph watch child process per repo,
managed via subprocess.Popen. It monitors the config file for changes and
automatically reconciles child processes (starting/stopping as repos are
added or removed). Health checks run every 30 seconds and automatically
restart dead watchers. No external dependencies (tmux, screen, etc.) are
required.
File: docs/CUSTOM_LANGUAGES.md
Custom Languages (Bring Your Own Language)
code-review-graph ships parsers for 35+ languages, but the
tree-sitter-language-pack
it depends on bundles many more grammars than the built-in list. If your repo
uses a language the graph does not cover yet โ Erlang, Haskell, OCaml,
Fortran, Ada, Clojure, ... โ you can teach the parser about it with a small
config file. No fork, no code changes.
Quick start
Create <repo_root>/.code-review-graph/languages.toml:
[languages.erlang]
extensions = [".erl"]
grammar = "erlang"
function_node_types = ["function_clause"]
class_node_types = ["record_decl"]
import_node_types = ["import_attribute"]
call_node_types = ["call"]
comment = "Erlang via the bundled tree-sitter-erlang grammar"Then rebuild:
uv run code-review-graph buildFiles matching the configured extensions are now parsed with the named
grammar, and the resulting Function/Class nodes and CALLS/IMPORTS_FROM edges
flow through every downstream feature (impact radius, search, communities,
wiki, MCP tools) exactly like built-in languages. Nodes carry the custom
language name (here erlang) in their language field.
Schema reference
Each custom language is one [languages.<name>] table.
| Key | Type | Required | Meaning |
|---|---|---|---|
<name> |
table key | yes | Language identifier stored on every parsed node. Lowercase letters, digits, _, -; max 32 chars; must start with a letter. |
extensions |
list of strings | yes | File extensions to claim, each starting with a dot (e.g. ".erl"). Matched case-insensitively. |
grammar |
string | yes | A grammar name shipped by tree_sitter_language_pack (probe availability โ see below). |
function_node_types |
list of strings | no* | Tree-sitter node types that define functions/methods. Matching nodes become Function nodes (or Test nodes when the name/file looks like a test). |
class_node_types |
list of strings | no* | Node types that define classes/records/types. Matching nodes become Class nodes. |
import_node_types |
list of strings | no* | Node types for import/include statements. Each yields an IMPORTS_FROM edge. |
call_node_types |
list of strings | no* | Node types for call expressions. Each yields a CALLS edge from the enclosing function. |
name_field |
string or list of strings | no | Ordered candidates for locating a definition's name when it is not a name field or a plain identifier child (see below). |
comment |
string | no | Free-form note for humans; ignored by the parser. |
* At least one of the four node-type lists must be non-empty, otherwise the
entry is skipped (there would be nothing to extract).
Validation rules (safety first)
The loader never crashes a build. Anything invalid is skipped with aWARNING log line:
- Built-ins always win. A custom language cannot claim a built-in
extension (.py,.ts,.ex, ...) and cannot reuse a built-in language
name (python,elixir, ...). grammarmust load fromtree_sitter_language_pack; unknown grammars are
skipped.- Every extension must start with a dot.
- Two custom languages cannot claim the same extension (first one wins).
- At most 20 custom languages are loaded per repo.
- Malformed TOML disables custom languages for that build (with a warning).
name_fieldmust be a string or a list of non-empty strings (max 8
candidates); anything else skips the entry with a warning.
Naming definitions with `name_field`
By default the parser finds a definition's name from an identifier-like child
or a field literally called name. Many grammars keep the name elsewhere โ in
a differently named field, or nested a level or two below it. When that happens
the definition is extracted unnamed and silently dropped. name_field tells
the parser where to look.
Each candidate is tried in order and resolved in two passes:
- Field first โ a child accessed by that tree-sitter field name. Fields are
tried across all candidates before any type search, so a precise field
always wins over a broader match. - Typed descendant โ if no candidate matched a field, the first descendant
whose node type equals a candidate (bounded depth). This covers names that
sit under a fieldless wrapper.
The resolved node is then descended to its first text-bearing leaf and cleaned
(surrounding {}/quotes/whitespace stripped; multi-line or oversized text is
rejected). Because resolution is anchored on your configured candidates, it
never grabs an unrelated inner identifier.
[languages.bibtex]
extensions = [".bib"]
grammar = "bibtex"
class_node_types = ["entry"]
name_field = ["key"] # @article{smith2020,...} -> "smith2020"
[languages.latex]
extensions = [".tex"]
grammar = "latex"
class_node_types = ["section", "chapter", "subsection"]
function_node_types = ["new_command_definition"]
name_field = ["name", "text", "declaration"]
# \section{Introduction} -> "Introduction" (via `text`)
# \newcommand{\foo}{bar} -> "\foo" (via `declaration`)
[languages.markdown]
extensions = [".md"]
grammar = "markdown"
class_node_types = ["section"]
name_field = ["inline"] # "# My Heading" -> "My Heading" (typed descendant)Use a list when a grammar's node types keep their names in different places
(LaTeX section uses text, \newcommand uses declaration): the first
candidate that resolves wins. Omitting name_field preserves the previous
behavior exactly.
Finding the right node type names
Node type names are grammar-specific, so you need to look at the tree the
grammar actually produces. Two easy options:
Option 1 โ tree-sitter playground. Paste a snippet into
https://tree-sitter.github.io/tree-sitter/7-playground.html and read the
node names off the parse tree (select the matching grammar first).
Option 2 โ probe locally with Python. The exact grammar version your
build uses is the one in tree_sitter_language_pack, so probing locally is
the most reliable source of truth:
uv run python - <<'EOF'
import tree_sitter_language_pack as tslp
source = b"""
-module(math_utils).
add(A, B) -> helper(A) + B.
helper(X) -> X * 2.
"""
def dump(node, depth=0):
print(" " * depth + node.type, node.text.decode()[:40].replace("\n", " "))
for child in node.children:
dump(child, depth + 1)
dump(tslp.get_parser("erlang").parse(source).root_node)
EOFPick the node types that wrap whole definitions (function_clause, not the
inner atom) and whole call expressions (call, not the callee identifier).
Worked example: Erlang end to end
src/math_utils.erl:
-module(math_utils).
-export([add/2, scale/2]).
-import(lists, [map/2]).
-record(point, {x, y}).
add(A, B) ->
helper(A) + B.
helper(X) -> X * 2.
scale(Points, F) ->
lists:map(fun(P) -> add(P, F) end, Points).With the [languages.erlang] config from the quick start, a build produces:
Functionnodesadd,helper,scale(fromfunction_clause),
each withlanguage = "erlang".- A
Classnodepoint(fromrecord_decl). CALLSedgesadd โ helperandscale โ add, resolved to their
same-file qualified names, plusscale โ lists:mapfor the remote call.- An
IMPORTS_FROMedge targetinglists(fromimport_attribute). CONTAINSedges from the file to every definition.
How extraction works (and its limits)
Custom languages run through the same generic tree-sitter walker as built-in
languages โ there is no per-language code path to maintain. That keeps the
feature simple, but the generic heuristics have limits:
- Name extraction uses the default name-field heuristics. The walker
looks for a child node of a common identifier type (identifier,name,type_identifier, ...) and falls back to the grammar'snamefield
(node.child_by_field_name("name")). Grammars that store definition names
in another shape (e.g. nested two levels deep with a non-standard field)
will produce unnamed โ and therefore skipped โ definitions. - Callee extraction probes common field names (
function,callee,expr,name) and descends through curried applications. Exotic call
shapes may be missed. - Import targets come from the grammar's
module/name/path/source
field when present, otherwise the raw statement text is recorded. - No cross-file module resolution. Import edges keep the module name as
written (e.g.lists); they are not resolved to file paths the way
built-in languages with dedicated resolvers are. - No language-specific extras: things like decorator-based test
detection, framework annotations (Spring, Temporal), or SFC handling only
exist for built-in languages.
If a language needs deeper support than the generic walker can give, please
open an issue โ config-driven support is the on-ramp, not the ceiling.
Troubleshooting
- Run a build with
-v/logging enabled and look forlanguages.toml
warnings โ every skipped entry says exactly why it was skipped. - Probe grammar availability:
uv run python -c "import tree_sitter_language_pack as t; t.get_language('erlang')"
(raisesLookupErrorif the grammar is not bundled). - The config is read when a parser is constructed (every
build/update),
so config changes take effect on the next build โ re-runuv run code-review-graph buildafter editing.
File: docs/FAQ.md
FAQ โ how code-review-graph compares
Honest answers to the questions we get most often. Where another tool is genuinely
better for a job, this page says so.
- How is this different from LSP and language servers?
- Isn't this just RAG?
- Why not just grep?
- How does it compare to Serena, codegraph, claude-context, and repomix?
- When should I not use it?
- Does it phone home?
- How do I verify it is working?
- How big a codebase justifies it?
- How does it handle monorepos, git worktrees, and multiple repos?
How is this different from LSP and language servers?
Language servers and code-review-graph (CRG) both build a structural model of your
code, but they optimize for different things.
What LSP does better. A language server is backed by a real compiler frontend (or
something close to it), so it gives you type-aware, semantically precise results:
exact go-to-definition through generics and overloads, find-references that
understands scoping, live diagnostics, completions, and renames that are safe by
construction. If you need a provably complete reference list for one symbol in one
language, an LSP server is the gold standard and CRG does not try to replace it.
What CRG does differently:
- One persistent graph instead of per-language daemons. Language servers run one
process per language and (with a few exceptions that cache an index on disk) rebuild
or revalidate state per session. CRG parses once with Tree-sitter, stores nodes and
edges in a single SQLite file (.code-review-graph/graph.db), and answers queries
across roughly 35 languages plus notebooks from one process โ including cross-language
edges that no single LSP server models. - It survives sessions and commits. The graph is updated incrementally (changed
files only, ~2.5 seconds on a ~3,000-file repo) rather than rebuilt per editor
session. - Review-oriented edges.
tests_for, execution flows, community membership,
risk-scored change analysis โ relationships LSP does not model because they are not
needed for editing.
The honest trade-off: CRG's call resolution is AST-level and heuristic, not
compiler-backed. Dynamic dispatch, metaprogramming, and duck typing can produce
inferred or ambiguous edges โ which is exactly why every edge carries a confidence
tier (EXTRACTED / INFERRED / AMBIGUOUS). LSP is more precise per symbol; CRG is
broader, persistent, and cheaper to query across the whole repo.
Isn't this just RAG?
No. RAG splits your code into text chunks, embeds them, and retrieves chunks by
similarity to the query. That answers "find code that talks about X." It cannot
answer "who calls X" โ similarity between two functions tells you nothing about
whether one invokes the other.
CRG stores structural edges parsed from the AST: calls, imports, inheritance,
test coverage. "Who calls login()" is a graph lookup, not a similarity guess.
Embeddings exist in CRG but they are optional and play a supporting role โ one input
to hybrid search (FTS5 BM25 keyword + vector) used to find a starting node, after
which traversal follows real edges. Currently only function signatures are embedded
(~10 tokens per node), not bodies.
The benchmark that captures the difference is multi-hop retrieval: natural-language
query โ anchor node โ one-hop traversal (callers_of, tests_for, ...). CRG scores
0.909 across 11 hand-curated tasks on 6 real repos (see
REPRODUCING.md). Pure similarity retrieval has no equivalent of the
second hop.
Where RAG-style search is better: purely conceptual questions ("where is rate
limiting discussed?") over prose, comments, and docs. CRG's own keyword search
ranking is a documented weakness (MRR 0.35 โ see the limitations section in the
README).
Why not just grep?
Fair question โ Anthropic has been explicit that Claude Code deliberately ships
without a code index. Agentic search (glob, grep, targeted file reads) is always
exactly as fresh as your working tree, has no chunking or staleness failure modes,
and needs zero setup. For one-hop questions โ "where is parse_file defined?" โ
that approach works well, and CRG will not beat it by much.
The gap appears on multi-hop structural questions, where each hop costs the agent
another round of grep + read + reasoning, and token spend compounds:
- Impact radius โ "what could break if I change this file?" requires callers,
dependents, and their tests. Oneget_impact_radiuscall returns all three. - Callers of callers โ transitive tracing via
traverse_graphor repeatedquery_graph(pattern="callers_of"), instead of N rounds of grepping for each
intermediate name (and grep matches text, so overloaded or re-exported names
produce false hits the agent must read to rule out). - Tests for โ
query_graph(pattern="tests_for")maps code to covering tests via
parsed edges plus naming conventions, anddetect_changesadds transitive test
coverage. Grep only finds tests that mention the name literally. - Affected flows โ "which execution paths does this change touch?" has no grep
equivalent at all.
The graph also persists: agentic search re-derives the same structure from scratch
every session, while CRG keeps it in SQLite and updates incrementally.
One honest caveat on the numbers: the whole-corpus token-reduction numbers (~65x median,
36xโ376x range) compare graph responses against reading the whole corpus, not
against a skilled agentic-grep session (see REPRODUCING.md for what
each benchmark measures). For single-hop lookups in a small repo, grep is cheap and
good. The multi-hop review workflow is where the graph earns its keep.
How does it compare to Serena, codegraph, claude-context, and repomix?
These are good tools solving adjacent problems. Short factual comparison, based on
each project's public documentation (check upstream docs for current behavior):
| Tool | Approach | Persistence | External deps | Review focus |
|---|---|---|---|---|
| code-review-graph | Tree-sitter AST โ structural graph (calls, imports, inheritance, tests) over MCP + CLI | SQLite in .code-review-graph/, incremental updates |
None for the core; embeddings optional | Yes โ blast radius, risk-scored change analysis, test-gap detection |
| Serena | LSP-backed symbol retrieval and editing tools over MCP | Language-server state plus per-project memories | A language server per language | General coding-agent toolkit, not review-specific |
| codegraph | AST/call-graph indexing over MCP (several projects share this name; details vary by implementation) | Varies by implementation | Varies by implementation | Generally retrieval-focused |
| claude-context | Chunk + embed semantic code search over MCP | Vector index in a vector database | Embedding provider + vector DB (cloud or self-hosted) | Search-focused, not review-specific |
| repomix | Packs the whole repo into one AI-friendly file | None โ regenerated per run | Node.js | One-shot context packing; no structural queries |
Rough guidance: if you want symbol-precise editing tools, Serena's LSP approach is
a better fit. If you want semantic search and are happy running a vector store,
claude-context covers that. If your repo is small enough to paste wholesale into a
large context window, repomix is the simplest thing that works. CRG's niche is the
persistent structural graph for review: impact analysis, risk scoring, and
test-coverage tracing with no external services.
When should I not use it?
Consistent with the limitations section in the README:
- Repos under a few hundred files. An agent can often just read everything
relevant directly; the graph's structural metadata adds overhead that a small repo
doesn't repay. See How big a codebase justifies it? - Trivial single-file changes. The graph response carries impact-radius edges and
source snippets, which can exceed the raw content of a one-file diff. This is
measured and documented (the formaltoken_efficiencybenchmark reports ratios
below 1.0 for small commits โ by design, see REPRODUCING.md). - One-off questions on a repo you won't revisit. The build is fast (~10 seconds
for a 500-file project) but the payoff comes from reuse across queries and
sessions. For a single question, agentic search is fine. - Flow detection on JS/Go. Entry-point detection is currently reliable mainly for
Python framework patterns; JavaScript and Go flow detection needs work (33% recall,
documented in the README limitations).
Does it phone home?
No. There is zero telemetry. The graph is a SQLite file in .code-review-graph/
inside your repo, and the core build / review / search / MCP workflows run entirely
locally. The streamable-HTTP MCP transport binds to localhost by default.
The only network activity is opt-in:
- Local embeddings (
pip install "code-review-graph[embeddings]") download the
sentence-transformers model from HuggingFace on first use. Your code does not leave
the machine. - Cloud embeddings (OpenAI-compatible, Google Gemini, MiniMax) send the text being
embedded โ currently function signatures โ to the provider you explicitly configure
via environment variables. CRG prints an egress warning unless you acknowledge it
withCRG_ACCEPT_CLOUD_EMBEDDINGS=1; the warning is skipped automatically when the
endpoint is localhost.
See LEGAL.md for the full privacy notes.
How do I verify it is working?
Check the graph exists and has content:
bashcode-review-graph statusYou should see node/edge counts and graph statistics. Zero nodes means the build
didn't run or found nothing to parse.See the savings on a real change โ make any edit, then:
bashcode-review-graph detect-changes --briefThis prints the risk summary and the boxed Token Savings panel against the
existing graph (read-only). Add--verifyto cross-check the estimate against
OpenAI'scl100k_basetokenizer (requirespip install tiktoken). If you suspect
the graph is stale,code-review-graph update --briefre-parses changed files
first and prints the same panel.Check the MCP wiring โ in Claude Code, run
/mcpand confirm thecode-review-graphserver is connected with its tools listed. Then ask the
assistant something structural ("what callsparse_file?") and watch it usequery_graphinstead of grepping.
If any of these fail, see TROUBLESHOOTING.md.
How big a codebase justifies it?
This comes up often (see #414). Honest guidance, tied to the documented small-repo
overhead:
- Below a few hundred files: marginal. The graph builds in seconds and works
fine, but an agent can already hold most of the repo in context, and for trivial
diffs the structural response can cost more tokens than it saves (the documented
overhead regime โ see When should I not use it?). - A few hundred to a few thousand files: this is where the benchmarks live. The
six evaluation repos range from 70 to ~1,100 files and show 36xโ376x reductions on
whole-corpus agent questions, with the caveat noted above about what that baseline
measures. - Multi-thousand-file repos and monorepos: the strongest case. No agent can read
the corpus per question (FastAPI alone is ~950k tokens of source), re-deriving
structure by search every session is the dominant cost, and incremental updates
keep the graph fresh in a couple of seconds.
A second axis matters as much as file count: how often you ask multi-file
questions. A 300-file repo you review daily benefits more than a 3,000-file repo
you touch once.
How does it handle monorepos, git worktrees, and multiple repos?
Monorepos. One graph per repository root by default โ commands auto-detect the
root by walking up to the nearest .git, and in git repos only tracked files are
indexed (git ls-files), so gitignored build artifacts are skipped automatically.
Use a .code-review-graphignore file to exclude tracked paths (e.g. vendor/**,
generated code), or pass --repo to point a command at a specific directory.
Git worktrees. Each worktree is detected as its own root, so each gets its own.code-review-graph/ database matching its checkout. Don't try to share one database
across worktrees at different commits โ the graph reflects one working tree. If you
want the database outside the working tree entirely (ephemeral workspaces, network
shares), use --data-dir on build/update/etc., or set the CRG_DATA_DIR
environment variable.
Multiple repos. A lightweight registry (stored at~/.code-review-graph/registry.json) lets MCP clients search across projects:
code-review-graph register ~/work/api --alias api # add a repo (optional alias)
code-review-graph repos # list registered repos
code-review-graph unregister api # remove by path or aliasOnce registered, the list_repos_tool and cross_repo_search_tool MCP tools work
across all of them. To keep several graphs fresh automatically, the bundled daemon watches
registered repos as child processes:
crg-daemon add ~/work/api --alias api
crg-daemon start
crg-daemon status(Also available as code-review-graph daemon start|stop|status.) See
COMMANDS.md for the full daemon reference.
File: docs/FEATURES.md
Features
v2.3.6 (Current)
- Every MCP tool response is bounded: #849 found
get_affected_flowsreturning ~247k tokens inside a workflow documented as "5 tool calls, 800 tokens total"; a sweep of the other 29 tools found the same class of bug in ten more places โlist_communitiesreturned 206k tokens with default arguments,get_community134k,get_architecture_overview625k in standard mode. All are now capped with a uniform contract:total(or a per-list*_total) always reports the untruncated count,truncatedmarks the cut, and the summary says how many of how many are shown. Cap parameters reject values below 1 and reject booleans.detail_level="minimal"was added to the analysis and refactor tools.tests/test_token_budget.pypins the per-tool budget table and the hard ceilings so a removed cap fails CI. See COMMANDS.md. - Framework-aware PHP parsing: traits, enums, object creation, and base clauses are indexed; Composer PSR-4 resolution is longest-prefix, multi-directory, cached, and repository-bounded; Blade references ignore comments/escaped directives; Laravel Route and Eloquent edges require explicit framework/import/receiver evidence.
- Custom languages without forking: drop a
.code-review-graph/languages.tomlinto your repo to index any grammar shipped by tree-sitter-language-pack โ extension map plus node-type lists, validated and capped, with built-in languages always winning. See CUSTOM_LANGUAGES.md. - GitHub Action for risk-scored PR reviews: composite
action.ymlbuilds/restores the graph from CI cache, runsdetect-changesagainst the PR base, and upserts a sticky comment with risk table, affected flows, test gaps, and the Token Savings line. Optionalfail-on-riskmerge gate. Dogfooded on this repo via.github/workflows/pr-review.yml. See GITHUB_ACTION.md. agent_baselineeval benchmark: compares graph queries against a realistic grep-and-read-top-k agent baseline instead of the whole-corpus strawman; wired into all six pinned eval configs.- Co-change ground truth for
impact_accuracy: predictions are also graded against files actually co-changed in the same commit; the legacy metric is explicitly labelled "graph-derived (circular โ upper bound)". - Weekly eval CI:
.github/workflows/eval.ymlruns a report-only cron of the two smallest pinned configs with CSV artifacts and a job summary. - docs/FAQ.md: how CRG compares to LSP, RAG, grep/agentic search, and adjacent tools; when NOT to use it; verification steps; monorepo/worktree and registry guidance.
- Contribution scaffolding: GitHub issue forms (bug/feature/platform), a PR template mirroring the CONTRIBUTING checklist, and dependabot config for pip + GitHub Actions.
- Windows fixes:
daemon statusno longer crashes with WinError 87 (#511), and CLIdetect-changesmaps diff paths to absolute native paths so it no longer reports 0 functions (#528). - Provider-name validation: unknown embedding provider names raise a clear error listing valid providers instead of silently falling back to the local model.
- Store-leak fixes: the five analysis MCP tools and the wiki-page tool no longer leak SQLite connections (try/finally
store.close()). fastmcp<4cap: the next fastmcp major can no longer silently break the server.- Worktree-safe git hooks:
installresolves the real hooks directory viagit rev-parse --git-path hooks, so linked worktrees andcore.hooksPath(husky) setups get a working pre-commit hook.
v2.3.5
- Token Savings panel on every brief CLI call:
code-review-graph detect-changes --briefand the newcode-review-graph update --briefprint a boxedToken Savingspanel โ full-context baseline, graph response, saved tokens, percent, and per-category breakdown (Functions / Tests / Risk / Other) that sums exactly to the graph response size. --verifyflag: cross-checks the displayed numbers against OpenAI'scl100k_basetokenizer (the GPT-4 family). Adds a secondVerified (tiktoken)row showing real token counts. Calibration across 222 mixed-language files shows the estimate is within ~1% of real tokens in aggregate.update --brief: incremental update + the same risk panel in one command. Distinct fromdetect-changes --brief(which is read-only against the existing graph) โ use update when the graph might be stale (post-rebase, large change set).code-review-graph embedCLI subcommand: explicit shell-level access to embedding generation. Previously only reachable via MCP.- Deterministic eval pipeline: all 6 eval configs pin upstream SHAs,
eval/runner.pyuses full clones with explicitreturncodechecks, and Leiden community detection uses a fixed seed (CRG_LEIDEN_SEED=42). Two runs on different machines produce identical numbers. multi_hop_retrievalbenchmark: 11 hand-curated 2-step tool-chain tasks (hybrid_searchโquery_graph) across the 6 test repos. Average score 0.909.- Richer semantic search:
embeddings._node_to_textnow includes the dotted form (Module.Class.method), word-split identifiers, and enclosing module directory. Search ranking on natural-language queries improved from 0.545 โ 0.909 on the multi-hop benchmark. - Identifier-aware search boost:
extract_query_identifierspulls dotted / snake_case / CamelCase tokens out of NL queries and boosts matching qualified-names ร2.0 in hybrid search. - Path normalization fix:
eval/runner.pynow resolves repo paths absolutely before storing, so the eval-built graph matches the CLI/MCP-built graph andupdatedoesn't create duplicate nodes for the same source location. - Test-gap dedup: the
Untested:line in the brief summary dedupes by bare name (defensive guard if duplicate qualified_names slip in). - FTS5 auto-rebuild in eval: the eval framework now calls
run_post_processingafterfull_build, so FTS5 is populated automatically instead of leaving the index empty.
v2.3.4
- Estimated context savings: Review, impact, detect-changes, and compact architecture responses include tiny
context_savingsmetadata (estimated,saved_tokens,saved_percent) where a baseline can be estimated. - Compact architecture overview by default:
get_architecture_overview_tooldefaults todetail_level="minimal"to avoid huge member lists and per-edge payloads. Usedetail_level="standard"for full detail. - Bounded change analysis:
CRG_MAX_CHANGED_FUNCS,CRG_MAX_TRANSITIVE_FRONTIER, andCRG_TOOL_TIMEOUThelp keep large MCP review calls responsive. - Windows MCP reliability: Local embedding models are pre-warmed on Windows before FastMCP starts worker dispatch to avoid semantic-search deadlocks.
- Parser correctness: Rust
#[test]and common async test attributes now produceTestnodes. - Graph lookup correctness: Review, impact, and file-summary tools resolve user-facing paths to stored graph paths;
callers_ofincludes cross-file callers even when same-file callers exist. - Install/runtime reliability: Generated Codex/Claude hooks drain stdin, bundled docs are available from wheels, missing local embeddings report unavailable status, and
.svnroots pass validation. - CLI reliability:
build --skip-postprocessandupdate --skip-flowshonor the requested post-processing level. - Broad parser surface: Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, VB.NET, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Terraform/OpenTofu structure (
.tf; generic.hclfiles are recognized as file nodes), Ansible playbooks/roles/tasks, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks, and Perl XS files. Generic YAML is not treated as source code. - Local-first by design: SQLite graph storage remains local, with no telemetry and no cloud-default behavior.
v2.0.0
- 22 MCP tools (up from 9): 13 new tools for flows, communities, architecture, refactoring, wiki, multi-repo, and risk-scored change detection.
- 5 MCP prompts:
review_changes,architecture_map,debug_issue,onboard_developer,pre_merge_checkworkflow templates. - 18 languages (up from 15): Added Dart, R, Perl support.
- Execution flows: Trace call chains from entry points (HTTP handlers, CLI commands, tests), sorted by criticality score.
- Community detection: Cluster related code entities via Leiden algorithm (igraph) or file-based grouping.
- Architecture overview: Auto-generated architecture map with module summaries and cross-community coupling warnings.
- Risk-scored change detection:
detect_changesmaps git diffs to affected functions, flows, communities, and test coverage gaps with priority ordering. - Refactoring tools: Rename preview with edit list, dead code detection, community-driven refactoring suggestions.
- Wiki generation: Auto-generate markdown wiki pages for each community with optional LLM summaries (ollama).
- Multi-repo registry: Register multiple repositories, search across all of them with
cross_repo_search. - Full-text search: FTS5 virtual table with porter stemming for hybrid keyword + vector search.
- Database migrations: Versioned schema migrations (v1-v5) with automatic upgrade on startup.
- Optional dependency groups:
[embeddings],[google-embeddings],[communities],[eval],[wiki],[all]. - Evaluation framework: Benchmark suite with matplotlib visualization.
- TypeScript path resolution: tsconfig.json paths/baseUrl alias resolution for imports.
- 486 tests across 22 test files.
v1.8.4
- Multi-word AND search:
search_nodesnow requires all words to match (case-insensitive), producing more precise results. - Call target resolution: Bare call targets are resolved to qualified names using same-file definitions, improving
callers_of/callees_ofaccuracy. - Impact radius pagination:
get_impact_radiusreturnstruncatedflag andtotal_impactedcount;max_resultsparameter controls output size. find_large_functions_tool: New MCP tool to find functions, classes, or files exceeding a line-count threshold.- 15 languages: Added Vue SFC and Solidity support.
- Documentation overhaul: All docs updated with accurate language/tool counts, version references, and VS Code extension parity.
v1.8.3
- Parser recursion guard:
_MAX_AST_DEPTH = 180prevents stack overflow on deeply nested ASTs. - Module cache bound:
_MODULE_CACHE_MAX = 15,000with automatic eviction. - Embeddings thread safety:
check_same_thread=Falseon EmbeddingStore SQLite. - Embeddings retry logic: Exponential backoff for Google Gemini API calls.
- Visualization XSS hardening:
</escaped to<\/in JSON serialization. - CLI error handling: Split broad
exceptinto specific handlers. - Git timeout: Configurable via
CRG_GIT_TIMEOUTenv var. - Governance files: CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md.
v1.8.2
- C# parsing fix: Renamed language identifier from
c_sharptocsharp. - Watch mode thread safety: SQLite connections compatible with Python 3.10/3.11 watchdog threads.
- Full rebuild cleanup: Purges stale data from deleted files during full rebuild.
- Dependency trim: Removed unused
gitpythondependency.
v1.7.0
installcommand: New primary entry point for setup (code-review-graph install).initremains as an alias.--dry-runflag: Preview whatinstall/initwould write without modifying files.- PyPI auto-publish: GitHub releases now automatically publish to PyPI.
- README rewrite: Professional documentation with real benchmark data from httpx, FastAPI, and Next.js.
v1.6.4
- Portable MCP config:
initnow generatesuvx-based.mcp.jsonโ no absolute paths, works on any machine withuvinstalled - Removed symlink workaround: The
_safe_pathhelper for spaces-in-paths is no longer needed withuvx
v1.6.3
- SessionStart hook: Claude Code automatically prefers graph MCP tools over full codebase scans at session start
- Marketplace ready: plugin.json corrected for official Claude Code plugin marketplace submission
- README cleanup: Removed screenshot placeholders
v1.6.2
- 24 audit fixes: Critical bug fixes, performance improvements, parser enhancements, expanded test coverage
- Parser: C/C++ support: Full node extraction for C and C++ (classes, functions, imports, calls, inheritance)
- Parser: name extraction: Fixed for Kotlin, Swift (simple_identifier), Ruby (constant)
- Performance: NetworkX graph caching, batch edge queries, chunked embedding search, git subprocess timeouts
- CI hardening: Coverage enforcement (50%), bandit security scanning, mypy type checking
- Tests: +40 new tests for incremental updates, embeddings, and 7 new language fixtures
- Docs: API response schemas, ignore pattern documentation, fixed hook config reference
- Accessibility: ARIA labels throughout D3.js visualization
v1.5.3
- Spaces-in-path handling: (superseded in v1.6.4 by
uvx-based config) Previously used symlinks for spaces in paths - No git required:
build,status,visualize,watchnow work on any directory without git - Plugin ready: Skills registered in plugin.json, SKILL.md frontmatter fixed
- File organization: Generated files moved into
.code-review-graph/directory (auto-created.gitignore, legacy migration) - Visualization density: Starts collapsed (File nodes only), search bar, clickable edge type toggles, scale-aware layout for large graphs
- Project cleanup: Removed redundant
references/,agents/,settings.json
v1.4.0
initcommand: Automatic.mcp.jsonsetup for Claude Code integration- Interactive D3.js graph visualization:
code-review-graph visualizegenerates an HTML graph you can explore in-browser - Documentation overhaul: Comprehensive docs audit across all reference files
v1.3.0
- Python version check with Docker fallback: Automatically detects Python 3.10+ and suggests Docker if unavailable
- Universal install:
pip install code-review-graphโ no git clone needed - CLI entry point:
code-review-graphcommand available system-wide after pip install
v1.2.0
- Logging improvements: Structured logging throughout the codebase
- Watch debounce: Smarter file-change detection in watch mode
- tools.py fixes: Bug fixes and reliability improvements for MCP tools
- CI coverage: GitHub Actions CI/CD pipeline with test coverage reporting
v1.1.0
- Watch mode:
code-review-graph watchโ auto-rebuilds graph on file changes - Vector embeddings: Optional
pip install .[embeddings]for semantic code search - Go, Rust, Java verified: 12+ languages with dedicated test coverage
- 47 tests passing, 8 MCP tools registered
- README badges and cleaner install flow
v1.0.0 (Foundation)
- Persistent SQLite knowledge graph โ zero external dependencies
- Tree-sitter multi-language parsing โ classes, functions, imports, calls, inheritance
- Incremental updates via
git diff+ automatic dependency cascade - Impact-radius / blast-radius analysis โ BFS through call/import/inheritance graph
- 6 MCP tools for full graph interaction
- 3 review-first skills: build-graph, review-delta, review-pr
- PostToolUse hooks (Write|Edit|Bash) for automatic background updates
- FastMCP 3.0 compatible stdio MCP server
Privacy & Data
- Core graph data is stored locally
- Graph stored in
.code-review-graph/graph.db(SQLite), auto-gitignored - No telemetry; core graph/review workflows do not require network access
- Optional embedding and wiki features may call configured local or remote services when explicitly enabled
- Respects
.gitignoreand.code-review-graphignore
File: docs/GITHUB_ACTION.md
GitHub Action: Risk-Scored PR Review
code-review-graph ships a composite GitHub Action (action.yml at the repo
root) that posts a risk-scored, graph-aware review comment on every pull
request โ think of it as a hosted AI review bot (Greptile-style), except the
analysis is local-first: the knowledge graph is built and queried entirely
on your CI runner, and no source code is sent to any external service.
On each PR run the action:
- Installs
code-review-graphfrom PyPI. - Restores the cached
.code-review-graph/SQLite graph (or builds it from
scratch on a cache miss) and incrementally re-parses the files changed by
the PR. - Runs
code-review-graph detect-changes --base origin/to get
risk-scored functions, affected execution flows, and test gaps. - Renders a markdown report (via
scripts/render_pr_comment.py) and upserts
a single sticky PR comment โ the same comment is updated on every push, so
the PR thread is never spammed. - Optionally fails the job when the overall risk score crosses a threshold
(fail-on-risk).
Quick start (external repositories)
# .github/workflows/code-review-graph.yml
name: code-review-graph
on:
pull_request:
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: tirth8205/[email protected]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}That is the whole setup. The default GITHUB_TOKEN provided by Actions is
sufficient โ no PAT, no API key, no third-party service.
Self-hosted runners must be version 2.327.1 or newer. The composite action
uses Node 24-based GitHub actions, including actions/setup-python@v6,actions/cache@v6, and the recommended actions/checkout@v7 example.
To turn the review into a merge gate:
- uses: tirth8205/[email protected]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fail-on-risk: highInputs
| Input | Required | Default | Description |
|---|---|---|---|
github-token |
yes | โ | Token used to post the sticky PR comment via the GitHub API. The workflow's default GITHUB_TOKEN works when the job has pull-requests: write. |
comment |
no | true |
Post (and keep updated) the sticky PR comment. Set to false to run analysis/gating without commenting. |
fail-on-risk |
no | none |
Fail the job when the overall risk score reaches a level: none (never fail), high (risk โฅ 0.70), critical (risk โฅ 0.85). |
python-version |
no | 3.12 |
Python version used to run code-review-graph (3.10+ supported). |
Outputs
| Output | Description |
|---|---|
comment-file |
Runner-local path to the rendered markdown report. Use with comment: false when a separate trusted workflow will publish it. |
Risk levels
detect-changes produces a 0.0โ1.0 overall risk score (max across changed
functions; see code_review_graph/changes.py:compute_risk_score for the
scoring factors: flow participation, community crossing, test coverage,
security-sensitive names, caller count). The action maps it to levels:
| Level | Score |
|---|---|
| low | < 0.40 |
| medium | 0.40 โ 0.69 |
| high | 0.70 โ 0.84 |
| critical | โฅ 0.85 |
What the comment contains
- Overall risk score and level, with counts of changed functions,
affected flows, and test gaps. - Risk-scored changes โ a table of the top changed symbols ordered by
risk, with file:line locations and test-coverage status. - Affected execution flows โ which entry-point flows the change touches,
ordered by criticality. - Test gaps โ changed functions with no direct test coverage.
- Token savings โ how many tokens the graph-backed report saved versus
reading every changed file in full. This is the samecontext_savings
estimate the CLI's Token Savings panel shows (achars / 4approximation
labelledestimated: trueโ see REPRODUCING.md for the
calibration methodology). - A
Powered by code-review-graphfooter.
The comment starts with a hidden HTML marker
(``). The action looks the marker up viagh api on each run and PATCHes the existing comment instead of creating a
new one (a "sticky" comment).
Cache behavior
The action caches the .code-review-graph/ directory (the SQLite graph
database) with actions/cache:
- Key:
code-review-graph-schema9-<runner.os>-<hashFiles(lockfiles)>,
where the lockfile hash covers common Python/JS/Go/Rust/Ruby/PHP lockfiles
(uv.lock,poetry.lock,requirements*.txt,package-lock.json,go.sum,Cargo.lock, โฆ). - Schema segment:
schema9tracks the database schema version
(LATEST_VERSIONincode_review_graph/migrations.py). It is bumped when
the schema changes so stale caches are never restored across incompatible
versions. - Restore keys: fall back to any cache for the same OS and schema, so a
lockfile change still reuses the previous graph. - On cache hit: the action runs
code-review-graph update --base origin/, which re-parses only the files that differ from the
PR's base ref. If the restored database turns out to be unusable, it falls
back to a fullbuild. - On cache miss: a full
code-review-graph buildruns (one-time cost;
subsequent PR runs are incremental).
Security notes
- Token scope: direct commenting needs
contents: readfor checkout andpull-requests: writeto post the comment. In the split fork-safe setup,
the analysis workflow needs onlycontents: read; the trusted commenter
needs onlyactions: readandpull-requests: write. Grant exactly those
permissions in each workflow. - Local-first: analysis runs entirely on the runner. No code, diff, or
metadata leaves GitHub's infrastructure; there is no external API, account,
or key. - Untrusted input: all dynamic values (
github.base_ref, the PR number,
action inputs) are passed to scripts through environment variables, never
interpolated into shell commands. The markdown renderer escapes
table/markup characters and strips control characters from symbol names
and file paths before they reach the comment body, on top of the
server-side_sanitize_name()sanitization. - Pinning: when consuming the action from another repository, pin
uses:to a release tag or commit SHA rather than@main. - Fork PRs:
pull_requestruns from forks receive a read-onlyGITHUB_TOKEN, so they cannot post the comment directly. Use an
unprivilegedpull_requestworkflow withcomment: false, upload thecomment-fileas an artifact, and publish it from a separate trustedworkflow_runworkflow. See.github/workflows/pr-review.ymland.github/workflows/pr-review-comment.yml.
GitHub loads theworkflow_runworkflow from the default branch, so the
trusted commenting half becomes active only after that workflow is merged.
The privileged workflow must verify the source event and analyzed commit,
extract only underrunner.temp, cap and validate the artifact, and add its
own sticky marker before posting. Avoidpull_request_targetwith a checkout
of PR code because it can execute untrusted code with a privileged token
(details).
Dogfooding
This repository runs the action on its own PRs via.github/workflows/pr-review.yml,
which runs the local action.yml without write permissions and uploads the
rendered report. The trustedpr-review-comment.yml workflow
validates that artifact and posts the sticky comment without checking out or
executing PR-controlled code.
Rendering script
The markdown rendering and risk gating logic lives inscripts/render_pr_comment.py (stdlib
only, unit-tested in tests/test_action_render.py) rather than inline YAML,
so it can be tested and reused:
code-review-graph detect-changes --base origin/main | \
python scripts/render_pr_comment.py # markdown to stdout
python scripts/render_pr_comment.py --input report.json \
--fail-on-risk high --quiet # gate only: exit 3 on breachFile: docs/INDEX.md
Documentation Index
- USAGE.md -- How to install and use
- FAQ.md -- How it compares to LSP, RAG, grep, and similar tools; when not to use it
- FEATURES.md -- What's included, changelog
- COMMANDS.md -- All 30 MCP tools, 5 MCP prompts, skills, and CLI commands
- GITHUB_ACTION.md -- Risk-scored PR review comments via GitHub Actions
- CUSTOM_LANGUAGES.md -- Bring your own language via
.code-review-graph/languages.toml - LLM-OPTIMIZED-REFERENCE.md -- Token-optimized reference for MCP-capable AI coding agents
- architecture.md -- System design and data flow
- schema.md -- Graph node/edge schema, SQLite tables (including flows, communities, FTS5)
- TROUBLESHOOTING.md -- Common issues and fixes (including Windows/WSL)
- REPRODUCING.md -- Reproducing every benchmark number (pinned SHAs, seeded runs, tokenizer calibration)
- ROADMAP.md -- Shipped and planned features
- LEGAL.md -- License and privacy
File: docs/LEGAL.md
Legal & Privacy
License: MIT (see LICENSE in project root)
Privacy:
- Zero telemetry
- All graph data stored locally in
.code-review-graph/graph.db - Core graph build, review, search, and CLI/MCP workflows run locally
- Optional local embeddings may download a sentence-transformers model from HuggingFace when first used
- Optional cloud embedding providers (
openai,google,minimax,voyage) send embedded source snippets to the configured provider only when explicitly selected - Remote embedding providers print an egress warning unless
CRG_ACCEPT_CLOUD_EMBEDDINGS=1is set - Streamable HTTP MCP transport binds to localhost by default
Data: Core graph data stays on your machine. If you opt into a cloud embedding provider, the text being embedded leaves your machine under that provider's terms.
Warranty: Provided as-is, without warranty of any kind.