code-review-graph Documentation

Local-first code intelligence graph for MCP and CLI. Builds a persistent map of your codebase so AI coding tools read only what matters, with benchmarked...

RAW Doc

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

bash
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 codebase

One 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:

bash
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 Agent

Requires 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.

bash
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 only

Then open your project and ask your AI assistant:

text
Build the code review graph for this project

The 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:

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"]

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.

yaml
# .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-v2 embeddings). These numbers are lower than the 2026-05-25 capture they replace: the graph response grew as node embedding text became richer, so avg graph_tokens rose across every repo. fastapi is now measured at its current pin 22381558 rather than the retired 0227991a.

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

bash
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 server

JSON 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.

text
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 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:

bash
# 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 processes

Also 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:

toml
[[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:

text
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:

bash
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 dependencies

Environment 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:

bash
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 pass
provider="voyage" to embed_graph; the default model is voyage-code-3:

bash
export VOYAGE_API_KEY=pa-...
export CRG_ACCEPT_CLOUD_EMBEDDINGS=1
code-review-graph embed --provider voyage --model voyage-code-3

Model selection tip. Avoid -preview / -beta / -exp model 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 as text-embedding-3-small / text-embedding-3-large (OpenAI),
Qwen/Qwen3-Embedding-8B (via self-hosted vLLM / LocalAI), or
gemini-embedding-001 (via the native Gemini provider, which requires
GOOGLE_API_KEY instead of the OpenAI-compatible path).

code-review-graph embeds 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 full code-review-graph build before 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:

bash
# 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 serve

The CLI flag takes precedence over the environment variable. When neither is set,
all tools are available. This is especially useful for MCP client configurations:

json
{
  "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:

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:

  1. Run the same command from macOS Terminal.app (or iTerm) instead of the IDEโ€™s terminal, then retry pipx install . or pipx install "git+https://..." .

  2. Use uv to install the CLI from a checkout (uses different download machinery than pip in many cases):

    bash
    cd /path/to/code-review-graph
    uv tool install . --force
  3. For development in a clone without a global install, use uv sync and uv run code-review-graph โ€ฆ (or activate .venv after uv 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:

json
"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

bash
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]"
pytest

Adding 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.

bash
# Recommended
uv pip install code-review-graph

# Alternatives
pipx install code-review-graph
pip install code-review-graph

Requires 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

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

text
/* Detailed source-code truncated for AI context efficiency. */

Data Flow

Full Build

  1. collect_all_files() gathers tracked files (git ls-files) and applies .code-review-graphignore (gitignored files are skipped automatically when git is available)
  2. For each file, CodeParser.parse_file() uses Tree-sitter to extract AST
  3. AST walker identifies structural nodes (classes, functions, imports) and edges (calls, inheritance)
  4. GraphStore.store_file_nodes_edges() persists to SQLite with file hash for change detection
  5. Metadata updated with timestamp

Incremental Update

  1. get_changed_files() uses VCS metadata to identify changed files (git diff by default, with SVN support in the incremental layer)
  2. find_dependents() queries the graph for files importing the changed files
  3. Changed + dependent files are re-parsed (others skipped via hash comparison)
  4. Only affected rows in SQLite are updated

Review Context Generation

  1. Changed files identified (git diff or explicit list)
  2. 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
  3. Source snippets extracted for changed areas only
  4. Review guidance generated (test coverage gaps, wide blast radius warnings)
  5. Assembled into a structured, token-efficient context for MCP clients and the CLI
  6. Where a cheap baseline can be estimated, compact context_savings metadata 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:

  1. Walks the AST recursively
  2. Pattern-matches on node types (language-specific mappings in _CLASS_TYPES, _FUNCTION_TYPES, etc.)
  3. Extracts names, parameters, return types, base classes
  4. Identifies calls within function bodies
  5. 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):

  1. Seed = all qualified names in changed files
  2. For each node in frontier:
    • Follow forward edges (what this node affects)
    • Follow reverse edges (what depends on this node)
  3. Expand up to max_depth hops (default: 2)
  4. 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`

text
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`

text
flows: bool = True
communities: bool = True
fts: bool = True
repo_root: str | None

`get_minimal_context_tool`

text
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`

text
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`

text
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`

text
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`

text
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`

text
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`

text
repo_root: str | None
model: str | None    # Embedding model name
provider: str | None # local, openai, google, minimax, voyage

Local embeddings require: pip install "code-review-graph[embeddings]". Cloud providers use stdlib HTTP clients and require their provider environment variables.

`list_graph_stats_tool`

text
repo_root: str | None

`find_large_functions_tool`

text
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`

text
section_name: str    # usage, review-delta, review-pr, commands, legal, watch, embeddings, languages, troubleshooting

Flow Tools

`list_flows_tool`

text
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`

text
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`

text
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`

text
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`

text
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`

text
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`

text
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`

text
top_n: int = 10                  # Capped at 100
repo_root: str | None
detail_level: str = "standard"   # "minimal" returns name, kind, betweenness

`get_knowledge_gaps_tool`

text
repo_root: str | None
max_per_category: int = 15       # Entries per gap category (max 50)
detail_level: str = "standard"   # "minimal" drops file paths

summary maps each category to its untruncated count.

`get_surprising_connections_tool`

text
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`

text
repo_root: str | None

Change Analysis and Refactoring Tools

`detect_changes_tool`

text
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, and
affected_flows_total report the untruncated counts.
Relevant responses may include compact estimated context_savings metadata.

`refactor_tool`

text
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 only

Truncating 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`

text
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`

text
repo_root: str | None
force: bool = False          # Regenerate all pages even if unchanged

`get_wiki_page_tool`

text
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`

text
(no parameters)

`cross_repo_search_tool`

text
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 *_total field per list), sets truncated: 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's max_results does.
  • 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.

text
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.

text

`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.

text
base: str = "HEAD~1"

CLI Commands

text
/* 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 the
code-review-graph daemon subcommands:

bash
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.toml

Configuration

The daemon reads its configuration from ~/.code-review-graph/watch.toml:

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:

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:

bash
uv run code-review-graph build

Files 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 a
WARNING 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, ...).
  • grammar must load from tree_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_field must 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:

  1. 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.
  2. 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.

toml
[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:

bash
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)
EOF

Pick 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:

erlang
-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:

  • Function nodes add, helper, scale (from function_clause),
    each with language = "erlang".
  • A Class node point (from record_decl).
  • CALLS edges add โ†’ helper and scale โ†’ add, resolved to their
    same-file qualified names, plus scale โ†’ lists:map for the remote call.
  • An IMPORTS_FROM edge targeting lists (from import_attribute).
  • CONTAINS edges 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's name field
    (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 for languages.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')"
    (raises LookupError if 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-run
    uv run code-review-graph build after 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?

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. One get_impact_radius call returns all three.
  • Callers of callers โ€” transitive tracing via traverse_graph or repeated
    query_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, and detect_changes adds 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 formal token_efficiency benchmark 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
    with CRG_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?

  1. Check the graph exists and has content:

    bash
    code-review-graph status

    You should see node/edge counts and graph statistics. Zero nodes means the build
    didn't run or found nothing to parse.

  2. See the savings on a real change โ€” make any edit, then:

    bash
    code-review-graph detect-changes --brief

    This prints the risk summary and the boxed Token Savings panel against the
    existing graph (read-only). Add --verify to cross-check the estimate against
    OpenAI's cl100k_base tokenizer (requires pip install tiktoken). If you suspect
    the graph is stale, code-review-graph update --brief re-parses changed files
    first and prints the same panel.

  3. Check the MCP wiring โ€” in Claude Code, run /mcp and confirm the
    code-review-graph server is connected with its tools listed. Then ask the
    assistant something structural ("what calls parse_file?") and watch it use
    query_graph instead 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:

bash
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 alias

Once 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:

bash
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_flows returning ~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_communities returned 206k tokens with default arguments, get_community 134k, get_architecture_overview 625k in standard mode. All are now capped with a uniform contract: total (or a per-list *_total) always reports the untruncated count, truncated marks 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.py pins 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.toml into 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.yml builds/restores the graph from CI cache, runs detect-changes against the PR base, and upserts a sticky comment with risk table, affected flows, test gaps, and the Token Savings line. Optional fail-on-risk merge gate. Dogfooded on this repo via .github/workflows/pr-review.yml. See GITHUB_ACTION.md.
  • agent_baseline eval 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.yml runs 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 status no longer crashes with WinError 87 (#511), and CLI detect-changes maps 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<4 cap: the next fastmcp major can no longer silently break the server.
  • Worktree-safe git hooks: install resolves the real hooks directory via git rev-parse --git-path hooks, so linked worktrees and core.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 --brief and the new code-review-graph update --brief print a boxed Token Savings panel โ€” full-context baseline, graph response, saved tokens, percent, and per-category breakdown (Functions / Tests / Risk / Other) that sums exactly to the graph response size.
  • --verify flag: cross-checks the displayed numbers against OpenAI's cl100k_base tokenizer (the GPT-4 family). Adds a second Verified (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 from detect-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 embed CLI 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.py uses full clones with explicit returncode checks, and Leiden community detection uses a fixed seed (CRG_LEIDEN_SEED=42). Two runs on different machines produce identical numbers.
  • multi_hop_retrieval benchmark: 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_text now 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_identifiers pulls dotted / snake_case / CamelCase tokens out of NL queries and boosts matching qualified-names ร—2.0 in hybrid search.
  • Path normalization fix: eval/runner.py now resolves repo paths absolutely before storing, so the eval-built graph matches the CLI/MCP-built graph and update doesn'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_processing after full_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_savings metadata (estimated, saved_tokens, saved_percent) where a baseline can be estimated.
  • Compact architecture overview by default: get_architecture_overview_tool defaults to detail_level="minimal" to avoid huge member lists and per-edge payloads. Use detail_level="standard" for full detail.
  • Bounded change analysis: CRG_MAX_CHANGED_FUNCS, CRG_MAX_TRANSITIVE_FRONTIER, and CRG_TOOL_TIMEOUT help 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 produce Test nodes.
  • Graph lookup correctness: Review, impact, and file-summary tools resolve user-facing paths to stored graph paths; callers_of includes 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 .svn roots pass validation.
  • CLI reliability: build --skip-postprocess and update --skip-flows honor 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 .hcl files 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_check workflow 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_changes maps 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_nodes now 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_of accuracy.
  • Impact radius pagination: get_impact_radius returns truncated flag and total_impacted count; max_results parameter 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 = 180 prevents stack overflow on deeply nested ASTs.
  • Module cache bound: _MODULE_CACHE_MAX = 15,000 with automatic eviction.
  • Embeddings thread safety: check_same_thread=False on 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 except into specific handlers.
  • Git timeout: Configurable via CRG_GIT_TIMEOUT env var.
  • Governance files: CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md.

v1.8.2

  • C# parsing fix: Renamed language identifier from c_sharp to csharp.
  • 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 gitpython dependency.

v1.7.0

  • install command: New primary entry point for setup (code-review-graph install). init remains as an alias.
  • --dry-run flag: Preview what install/init would 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: init now generates uvx-based .mcp.json โ€” no absolute paths, works on any machine with uv installed
  • Removed symlink workaround: The _safe_path helper for spaces-in-paths is no longer needed with uvx

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, watch now 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

  • init command: Automatic .mcp.json setup for Claude Code integration
  • Interactive D3.js graph visualization: code-review-graph visualize generates 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-graph command 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 .gitignore and .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:

  1. Installs code-review-graph from PyPI.
  2. 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.
  3. Runs code-review-graph detect-changes --base origin/ to get
    risk-scored functions, affected execution flows, and test gaps.
  4. 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.
  5. Optionally fails the job when the overall risk score crosses a threshold
    (fail-on-risk).

Quick start (external repositories)

yaml
# .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:

yaml
- uses: tirth8205/[email protected]
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          fail-on-risk: high

Inputs

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 same context_savings
    estimate the CLI's Token Savings panel shows (a chars / 4 approximation
    labelled estimated: true โ€” see REPRODUCING.md for the
    calibration methodology).
  • A Powered by code-review-graph footer.

The comment starts with a hidden HTML marker
(``). The action looks the marker up via
gh 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: schema9 tracks the database schema version
    (LATEST_VERSION in code_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 full build.
  • On cache miss: a full code-review-graph build runs (one-time cost;
    subsequent PR runs are incremental).

Security notes

  • Token scope: direct commenting needs contents: read for checkout and
    pull-requests: write to post the comment. In the split fork-safe setup,
    the analysis workflow needs only contents: read; the trusted commenter
    needs only actions: read and pull-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_request runs from forks receive a read-only
    GITHUB_TOKEN, so they cannot post the comment directly. Use an
    unprivileged pull_request workflow with comment: false, upload the
    comment-file as an artifact, and publish it from a separate trusted
    workflow_run workflow. See
    .github/workflows/pr-review.yml and
    .github/workflows/pr-review-comment.yml.
    GitHub loads the workflow_run workflow 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 under runner.temp, cap and validate the artifact, and add its
    own sticky marker before posting. Avoid pull_request_target with 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 trusted
pr-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 in
scripts/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:

bash
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 breach

File: 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

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=1 is 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.