Generated banner for code-review-graph
Generated logo for code-review-graph

code-review-graph

Memory server

Created by Tirth

Published in tirth8205/code-review-graph. Publisher page

32,711 GitHub stars

What it is

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 context reductions on reviews and large-repo workflows.
— the description by Tirth

AutoGineer overview, from the brain’s own files

code-review-graph is a local-first memory system designed to solve the token-heavy problem of AI code review. By parsing a repository into a structural map of functions, classes, imports, and tests using Tree-sitter, it stores the codebase as a graph in a local SQLite database. When an AI agent needs to review a change, the system calculates the blast radius—identifying only the relevant callers, dependents, and tests—and serves this compact context over the Model Context Protocol (MCP). This approach significantly reduces the token count required for agents to understand code impact, often by orders of magnitude. The system supports incremental updates via file system hooks and watch mode, ensuring the graph remains accurate without full re-parsing. It is designed for local execution, keeping data on the user's machine, and integrates directly with various AI coding tools and CLI environments.

How it works

Memory model: Hybrid

AutoGineer overview, from the brain’s own files

The system uses Tree-sitter to parse source code into an Abstract Syntax Tree (AST), which is then stored as nodes and edges in a local SQLite database. When a user requests a review or code analysis, the system queries this graph to determine the blast radius of a change. It then provides the AI agent with a minimal, relevant subset of the codebase rather than the entire repository. The graph is kept up-to-date incrementally; hooks and watch mode detect file changes, diff them, and re-parse only the affected files and their immediate dependents.

Folders
FolderRoleFiles
.githubagent-config15
docsdocs13
hookshooks2
scriptsscripts7
skillsagent-instructions7
teststests354
.beadsfolder9
code-review-graph-vscodefolder32
code_review_graphfolder103
diagramsfolder12
evaluatefolder24

The agent reads

The agent writes

Requirements

Apps: Docker

Runtimes: Python >=3.10

Databases: Neo4j, SQLite

API keys (by name): CRG_OPENAI_API_KEY, GITHUB_TOKEN, GOOGLE_API_KEY, MINIMAX_API_KEY, VOYAGE_API_KEY

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Google Antigravity, Kiro, OpenCode, Amp, Roo Code, Cline, JetBrains Junie, Devin (ex-Windsurf)

Set it up

  1. This brain runs code automatically: 3 hooks (claude-code). Review hooks/hooks.json before opening the folder in Claude Code. No step below turns a hook on for you.

    • hooks/hooks.json: cat >/dev/null || true; command -v code-review-graph >/dev/null 2>&1 || exit 0; git rev-parse --git-dir >/dev/null 2>&1 && code-review-graph status --repo "$(git rev-parse --show-toplevel 2>/dev/null)" || echo 'Not a git repo, skipping'
    • hooks/hooks.json: cat >/dev/null || true; code-review-graph build >/dev/null 2>&1 &
    • hooks/hooks.json: command -v code-review-graph >/dev/null 2>&1 && code-review-graph hook-update; cat >/dev/null || true
  2. Not scanned by AutoGineerIts dependencies are installed by pip or uv when you run it and were not scanned by AutoGineer (9 declared). https://pypi.org/project/code-review-graph/
  3. Created by Tirth. Published in tirth8205/code-review-graph. Licence: MIT License.

Usage

AutoGineer overview, from the brain’s own files

  • Install the tool via pip and run the install command to configure your AI coding tools.
  • Build the initial knowledge graph for your project.
  • Use MCP-enabled AI agents to query the graph for impact analysis, code reviews, or architectural questions.
  • Rely on automatic incremental updates triggered by file changes to keep the graph in sync.
“Build the code review graph for this project”
The system parses the repository and initializes the SQLite graph database for use by AI agents.
“What is the impact of changing this function?”
The agent uses the graph to identify and present the callers, dependents, and tests affected by the change.

Documentation by Tirth

<h1 align="center">code-review-graph</h1>

<p align="center"> <a href="https://trendshift.io/repositories/23329?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-23329" target="_blank" rel="noopener noreferrer"> <img src="https://trendshift.io/api/badge/repositories/23329" alt="tirth8205%2Fcode-review-graph | Trendshift" width="250" height="55" /> </a> </p>

<p align="center"> <strong>A local code knowledge graph that gives AI coding tools precise review context over MCP.</strong> </p> <p align="center"> <a href="README.md">English</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.ja-JP.md">日本語</a> | <a href="README.ko-KR.md">한국어</a> | <a href="README.hi-IN.md">हिन्दी</a> </p>

<p align="center"> <a href="https://pypi.org/project/code-review-graph/"><img src="https://img.shields.io/pypi/v/code-review-graph?style=flat-square&color=blue" alt="PyPI"></a> <a href="https://pepy.tech/project/code-review-graph"><img src="https://img.shields.io/pepy/dt/code-review-graph?style=flat-square" alt="Downloads"></a> <a href="https://github.com/tirth8205/code-review-graph/stargazers"><img src="https://img.shields.io/github/stars/tirth8205/code-review-graph?style=flat-square" alt="Stars"></a> <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square" alt="MIT Licence"></a> <a href="https://github.com/tirth8205/code-review-graph/actions/workflows/ci.yml"><img src="https://github.com/tirth8205/code-review-graph/actions/workflows/ci.yml/badge.svg?branch=staging" alt="CI"></a> <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.10%2B-blue.svg?style=flat-square" alt="Python 3.10+"></a> <a href="https://modelcontextprotocol.io/"><img src="https://img.shields.io/badge/MCP-compatible-green.svg?style=flat-square" alt="MCP"></a> <a href="https://code-review-graph.com"><img src="https://img.shields.io/badge/website-code--review--graph.com-blue?style=flat-square" alt="Website"></a> <a href="https://discord.gg/3p58KXqGFN"><img src="https://img.shields.io/badge/discord-join-5865F2?style=flat-square&logo=discord&logoColor=white" alt="Discord"></a> </p>

<p align="center"> <a href="docs/USAGE.md">Usage</a> · <a href="docs/COMMANDS.md">Commands</a> · <a href="docs/FAQ.md">FAQ</a> · <a href="docs/TROUBLESHOOTING.md">Troubleshooting</a> · <a href="docs/GITHUB_ACTION.md">GitHub Action</a> · <a href="docs/REPRODUCING.md">Reproducing the benchmarks</a> · <a href="docs/ROADMAP.md">Roadmap</a> </p>

<br>

AI coding tools often re-read large parts of a codebase to review a change. code-review-graph builds a structural map of the code with Tree-sitter, keeps it updated incrementally, and serves compact context over MCP, so the assistant reads only the files a change touches.

<p align="center"> <img src="diagrams/diagram1_before_vs_after.png" alt="The Token Problem: reading flask's whole corpus costs 143,594 tokens, a graph answer costs 2,196 (65x fewer)" width="85%" /> </p>

What it does

  • Maps your code. Functions, classes, imports, calls and tests become one graph.

  • Shows what a change can break. The callers, dependents and tests it can reach, with a risk score.

  • Stays up to date. Hooks and watch mode parse again only the files that changed.

  • Works with your AI tools. One install sets up each tool it finds.

  • Stays on your machine. One SQLite file, no cloud service; cloud embeddings are opt-in.


Quick Start

Requires Python 3.10+.

pip install code-review-graph          # or: pipx install code-review-graph
code-review-graph install              # detect installed AI coding tools and configure each one
code-review-graph build                # parse the codebase

<details> <summary><strong>What <code>install</code> changes, and how to preview it first</strong></summary> <br>

install detects which AI coding tools you have, writes an MCP server entry for each, installs hooks and skills where the platform supports them, and adds graph instructions to the platform's rules file. The MCP entry uses poetry run or uv run inside a Poetry or uv project environment, uvx code-review-graph serve when uvx is on PATH, and otherwise the current Python interpreter. Nothing is written until install has listed the files it will change and you have answered its question about instruction files; install --dry-run shows each change as a diff and writes nothing. Restart the editor or tool afterwards.

The files install writes, such as .claude/ and .mcp.json, stay visible to git in case your team commits them. install --gitignore adds the ones that run created to a marked block in .gitignore; uninstall removes the block.

For Codex, the installer also places a global code-review-graph Skill under $CODEX_HOME/skills/code-review-graph/ (default: ~/.codex/skills/code-review-graph/). It prefers CRG MCP tools and falls back to the bundled read-only CLI when MCP is unavailable; a missing, empty, or stale graph never triggers an implicit build or update. WSL and Windows Codex runtimes have separate CODEX_HOME directories and tool inventories, so install and refresh the runtime that will execute the task.

</details>

<details> <summary><strong>Every supported platform, and how to set up just one</strong></summary> <br>

<p align="center"> <img src="diagrams/diagram8_supported_platforms.png" alt="One install, every platform: detects Codex, Claude Code, CodeBuddy Code, Cursor, Windsurf, Zed, Continue, OpenCode, MiMo Code, Antigravity, Gemini CLI, Qwen Code, Qoder, Kiro, GitHub Copilot, GitHub Copilot CLI, Hermes Agent and DeepSeek Harness" width="85%" /> </p>

To configure one platform, pass --platform with one of codex, claude-code, cursor, windsurf, zed, continue, opencode, mimo, antigravity, gemini-cli, qwen, kiro, qoder, copilot, copilot-cli, codebuddy, hermes, or dsh:

code-review-graph install --platform cursor
code-review-graph install --platform codebuddy

Config file locations are listed in docs/USAGE.md. Requires Python 3.10+.

</details>

<details> <summary><strong>Remove it again with <code>uninstall</code></strong></summary> <br>

uninstall removes CRG-owned files and entries from a Git or SVN working tree and leaves other MCP servers, hooks, skills and JSONC comments alone. Run it from anywhere inside the tree. Shared config files are replaced atomically, so a failed write leaves the original intact.

code-review-graph uninstall --dry-run    # preview only
code-review-graph uninstall              # preview, confirm, 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, keep graph databases
code-review-graph uninstall --keep-user-configs --repo .  # this project only

</details>

Restart the editor or tool after install. Then open the project and ask the assistant:

Build the code review graph for this project

<details> <summary><strong>How long a build takes, and what a partial build means</strong></summary> <br>

Build time scales with repository size; a cold build of a ~3,000-file repository took about 40 seconds (measured). After that, hooks and watch mode keep the graph updated. If some files fail to parse, the result has status partial and names them in its summary; the CLI also prints a Warning: line on stderr, and those files keep their previous graph rows.

</details>

Run with Docker or Podman

<details> <summary><strong>Build the image, then run it on your repository</strong></summary> <br>

Build the image from the repository root:

docker build -t code-review-graph:local .

From the repository you want to analyse, run:

docker run --rm \
  --user "$(id -u):$(id -g)" \
  --env HOME=/tmp \
  --volume "$PWD:/workspace" \
  code-review-graph:local build

The mounted repository must be writable. The command above uses your user and group IDs so the generated .code-review-graph/ directory remains owned by your user. Run these commands as a non-root host user.

For Podman, build the same Dockerfile and preserve your user mapping:

podman build -t code-review-graph:local .
podman run --rm \
  --userns=keep-id \
  --env HOME=/tmp \
  --volume "$PWD:/workspace" \
  code-review-graph:local build

The graph persists in .code-review-graph/ in the mounted repository and stores absolute container paths such as /workspace/app.py. Run update and detect-changes through Docker or Podman with the same /workspace mount. A host update on this graph can fail with "built with a different repository root". To switch to host commands, run code-review-graph build on the host to rebuild the graph with host paths.

Replace build with status to inspect the saved graph. The image includes Git and defaults to a non-root user. No graph is generated during image construction.

These examples use macOS/Linux shell syntax. On macOS, start your Docker-compatible engine or Podman machine before running them. On a host with SELinux on, such as Fedora, add :z to the mount ("$PWD:/workspace:z"), or the container is refused access to the repository.

</details>

How It Works

The repository is parsed into ASTs with Tree-sitter and stored as a graph of nodes (functions, classes, imports) and edges (calls, inheritance, test coverage). At review time the graph is queried for the smallest set of files the assistant needs to read.

<details> <summary><strong>Two diagrams: how the assistant uses the graph, and the build pipeline</strong></summary> <br>

<p align="center"> <img src="diagrams/diagram7_mcp_integration_flow.png" alt="How the assistant uses the graph: the user asks for a review, the assistant calls MCP tools, the graph returns blast radius and risk scores, the assistant reads only the affected files" width="80%" /> </p>

<p align="center"> <img src="diagrams/diagram2_architecture_pipeline.png" alt="Architecture pipeline: Repository to Tree-sitter parser to SQLite graph to blast radius to minimal review set" width="100%" /> </p>

</details>

Blast-radius analysis

<details> <summary><strong>Every caller, dependent and test a change can affect</strong></summary> <br>

When a file changes, the graph traces every caller, dependent and test that could be affected. The assistant reads those files instead of scanning the whole project.

<p align="center"> <img src="diagrams/diagram3_blast_radius.png" alt="Blast radius: a change to login() propagates to callers, dependents, and tests" width="70%" /> </p>

</details>

Incremental updates

<details> <summary><strong>Only changed files are parsed again (about 2.5 s for a two-file edit)</strong></summary> <br>

Editor hooks, git hooks (pre-commit, post-checkout, post-merge, post-rewrite) and watch mode trigger incremental updates. The update diffs changed files, finds their dependents through the graph's import and call edges, and re-parses only the files whose SHA-256 hash 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.

<p align="center"> <img src="diagrams/diagram4_incremental_update.png" alt="Incremental update flow: a hook or watch update triggers a git diff, dependents are found through graph edges, and only files whose SHA-256 hash changed are re-parsed" width="90%" /> </p>

</details>

Whole codebase or targeted answer?

<details> <summary><strong>A slice of the graph shaped to the question, not the whole codebase</strong></summary> <br>

Instead of feeding a whole corpus to the model, the graph returns a slice shaped to the question. In the 2026-08-02 capture of this repository at 84bde354, 208,821 source tokens became ~3,190 tokens per question. The repository has grown a lot since that snapshot, so both numbers are larger today.

<p align="center"> <img src="diagrams/diagram6_monorepo_funnel.png" alt="code-review-graph at the 84bde354 snapshot: 208,821 source tokens funnel down to ~3,190 token graph responses, about 65x fewer tokens per question" width="80%" /> </p>

</details>

Language coverage and notebooks

<details> <summary><strong>Python, JavaScript, TypeScript, Go, Rust, Java, C/C++, C# and many more, plus notebooks: the full list</strong></summary> <br>

<p align="center"> <img src="diagrams/diagram9_language_coverage.png" alt="Language coverage by category: Web, Backend, Systems, Mobile, Scripting, Shells, Domain, Config and Other, plus Jupyter and Databricks notebooks" width="90%" /> </p>

The parser extracts functions, classes, imports, call sites, inheritance and tests, using Tree-sitter where a grammar exists and targeted fallbacks elsewhere. Supported: Python, JavaScript/TypeScript/TSX (including .mjs, .cjs, .mts and .cts), Go, Rust, Java, C/C++, C#, VB.NET, Ruby, Kotlin (.kt, .kts), Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL/PL/SQL (Oracle) and dbt models, Terraform/OpenTofu (.tf, and Terraform blocks in .hcl files; an .hcl file with no Terraform blocks keeps only a file node), Ansible YAML (playbooks, roles, tasks), Spring Boot application config (application.properties, application.yml, application.yaml and their application-<profile> variants; key names and value types only, never values), value-free generic YAML structure (translation files in folders such as locales/ become file nodes only), XSLT stylesheets (.xsl, .xslt), value-free JSON structure (.json; translation, test, docs, example and data files keep only a file node, and lock files are skipped), Vue/Svelte SFCs, HTML pages (.html, .htm; inline scripts, plus div, section and article blocks with an id as DocSection nodes), Astro files (parsed with the TypeScript grammar), Jupyter and Databricks notebooks (.ipynb, plus Databricks .py exports), Perl XS files (.xs), Robot Framework files (.robot, .resource), and Liquid templates (.liquid). Other .properties files are not treated as source code.

PHP projects also get repository-bounded Composer PSR-4 resolution, Blade template references, and Laravel Route and Eloquent edges when the source shows explicit framework imports, model inheritance and receiver evidence. Static Laravel routes in routes/*.php emit Endpoint nodes and controller HANDLES edges, including literal group prefixes.

Java projects get Spring dependency-injection call resolution, request endpoints and WebFlux routes, scheduled triggers, application-event publisher-to-listener edges, Spring AOP advice-to-target edges, and Temporal workflow and activity edges. Each resolver runs after the parse and needs the injected field, published event or workflow stub to be visible in the repository.

</details>

Add your own language

<details> <summary><strong>Map a file extension to a bundled Tree-sitter grammar in <code>languages.toml</code></strong></summary> <br>

If your repository uses a language the parser does not cover, add a languages.toml to .code-review-graph/ that maps file extensions to any grammar bundled in tree_sitter_language_pack, plus the node types for functions, classes, imports and calls:

[languages.erlang]
extensions = [".erl"]
grammar = "erlang"
function_node_types = ["function_clause"]
class_node_types = ["record_decl"]
import_node_types = ["import_attribute"]
call_node_types = ["call"]

The generic tree-sitter walker does the extraction. Built-in languages cannot be overridden. See docs/CUSTOM_LANGUAGES.md for the schema, validation rules and a worked example.

</details>

Risk-scored PR reviews in CI (GitHub Action)

<details> <summary><strong>One pull request comment with risk scores, affected flows and test gaps; see the sample workflow</strong></summary> <br>

The same analysis runs as a composite GitHub Action. The graph is built and queried on your CI runner; no source code is sent to an external service. On each pull request the action posts one sticky comment with risk-scored functions, affected execution flows and test gaps, updated in place on every push. The optional fail-on-risk input turns it into a merge gate. The optional details input adds more graph context, folded away and capped in size: why each top symbol scored what it did, the communities, hubs and cross-boundary links the change touches, its impact radius, its untested hotspots, and where the graph came from.

# .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, or the workflow this repository runs on itself in .github/workflows/pr-review.yml.

</details>


Benchmarks

The median per-question token reduction across the 6 repositories is about 63x (whole-corpus baseline vs graph query). The 358x maximum is one repository (fastapi, the largest corpus), not the typical result.

<details> <summary><strong>The benchmark board, and how the numbers were made</strong></summary> <br>

<p align="center"> <img src="diagrams/diagram5_benchmark_board.png" alt="Benchmarks across 6 repositories: ~63x median per-question token reduction (358x max), 0.69 average impact F1 against graph-derived ground truth" width="85%" /> </p>

All numbers come from the evaluation runner against 6 open-source repositories (13 commits). Every config pins an upstream SHA, Leiden runs with a fixed seed, and embeddings are deterministic on CPU, so two runs on different machines produce the same numbers. The reproduction recipe is in docs/REPRODUCING.md. A weekly report-only run on the two smallest configs lives in .github/workflows/eval.yml.

</details>

<details> <summary><strong>Token efficiency: ~63x median per-question reduction (range 35x to 358x; whole-corpus vs graph query)</strong></summary> <br>

For a typical agent question ("how does authentication work", "what is the main entry point", and so on), the graph returns ~2,200 to 3,900 tokens of search hits plus neighbour edges instead of every source file. The table averages the 5 sample questions defined in code_review_graph/token_benchmark.py.

RepoSnapshot SHAnaive_corpus_tokensavg graph_tokensReduction
fastapi22381558948,7932,653357.6x
flaska29f88ce143,5942,19665.4x
code-review-graph84bde354208,8213,19065.5x
gin5c00df8a166,8682,76660.3x
httpxb55d4635142,3562,66153.5x
expressb4ab7d65136,0523,93634.6x

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: node embedding text became richer, so avg graph_tokens rose in every repo. fastapi is measured at its current pin 22381558 rather than the retired 0227991a.

The Reduction column is naive_corpus_tokens / avg graph_tokens, so it divides out from the two columns beside it. The benchmark's own average_reduction_ratio averages the five per-question ratios instead, which always reads higher; those per-question figures are in docs/REPRODUCING.md.

The code-review-graph row is a snapshot, not a current measurement. The repository has grown since 84bde354, so its corpus and graph are both much larger today.

The whole-corpus baseline is an upper bound no real agent pays; an agent greps for identifiers and reads the best-matching files. The agent_baseline eval benchmark measures that case (a pure-Python grep over the corpus, top-3 files by match count, token-counted against the graph query cost). Against that realistic baseline the median across 18 questions on 6 repositories is 6.65x (mean 14.8x, range 4.2x to 72.6x), captured 2026-09-22, before search and this baseline began ranking test files lower, so code-review-graph eval --benchmark agent_baseline --embed now gives different numbers until the capture is redone. The full table is in docs/REPRODUCING.md, under "Canonical capture, 2026-09-22".

The ratio tracks repository size, because the graph answer stays flat (2,432 to 4,021 tokens across every repo measured) while the grep baseline grows with the corpus: 4.5x on express, 52x on fastapi. Both figures measure retrieval cost, the tokens needed to locate an answer. An agent that then opens the files the graph named pays the graph on top of its usual reads rather than instead of them; semantic_search_nodes_tool(include_source=True) returns the code with the locations so it does not have to, and measuring that path end-to-end is open work.

The formal token_efficiency benchmark measures a different scenario, the full get_review_context() JSON against only the changed-file content of a commit, and reports ratios below 1 for small commits because the response carries impact-radius edges and source snippets. The two benchmarks answer different questions; see docs/REPRODUCING.md.

Review and impact tools attach a compact context_savings estimate to their responses. The CLI shows the same figures in the Token Savings panel (see Usage below) and --verify compares them with OpenAI's cl100k_base tokenizer. Calibration across 222 sample files puts the estimate within about 1% of real tokens in aggregate (data).

</details>

<details> <summary><strong>Impact accuracy: 0.69 average F1 against graph-derived ground truth (recall 1.0 is a circular upper bound)</strong></summary> <br>

Blast-radius analysis recovers every file in the ground truth on all 13 evaluation commits. Read that as an upper bound, not as "100% recall": the ground truth (changed files plus files with call or import edges into them) comes from the same graph the predictor traverses. The lower precision is deliberate; flagging an extra file costs less than missing a broken dependency.

RepoCommitsAvg F1Avg PrecisionRecall (graph-derived upper bound)
httpx20.8630.7851.0
code-review-graph20.7340.5841.0
fastapi20.6970.5391.0
express20.6670.5001.0
flask20.6330.4851.0
gin30.6090.4391.0
Average130.6930.5461.000

This table is from the 2026-08-02 capture, taken before the path fix described below. That bug also kept real neighbour files out of the graph-derived ground truth, so these numbers will change when the benchmark is re-run.

The benchmark also runs a co-change mode: the predictor is seeded with one changed file and graded against the other files the author touched in the same commit, which is evidence from git history rather than from the graph. Both modes appear in the result CSVs (ground_truth_mode column). In the 2026-08-02 capture co-change mode returned predicted_files = 0 on every graded commit, code seeds included (for example code_review_graph/cli.py and gin's context.go). The cause was a path bug in the benchmark: git diff gives repo-relative paths, the graph stores absolute ones, so the seed never matched a node. That bug is now fixed, but the benchmark has not been re-run yet, so no co-change number is quoted.

</details>

<details> <summary><strong>Build stats</strong></summary> <br>

From the same 2026-08-02 clean-room build at the pinned SHAs above. Embedding counts are lower than node counts because File nodes are not embedded. The code-review-graph row is that snapshot, not the repository as it stands now.

RepoNodesEdgesEmbeddings
fastapi6,28732,0365,159
express1,99019,4921,849
gin1,58917,2371,491
code-review-graph1,4469,0941,354
flask1,4158,2591,329
httpx1,2638,2361,193

</details>

Limitations

<details> <summary><strong>Five known limits: circular recall, small edits, search ranking, flow detection, false positives</strong></summary> <br>

  • Impact "recall 1.0" is circular. The historical ground truth comes from the same graph edges the predictor walks, so it is an upper bound by construction. Co-change numbers are not quoted until the benchmark is re-run with the path fix.

  • Small single-file changes. Graph context can exceed a plain file read for trivial edits. The overhead is the structural metadata that makes multi-file analysis possible.

  • Search ranking. Keyword search usually finds the right result near the top, but ranking needs work. Express queries can return no hits because of module-pattern naming.

  • Flow detection. Entry-point detection is strongest for Python and PHP/Laravel. JavaScript and Go flow detection needs work.

  • Precision vs recall. Impact analysis is conservative. It flags files that might be affected, which means false positives in large dependency graphs.

</details>


Features

Updates, impact analysis, search, visualisation, CI reviews, refactoring and multi-repo support, all kept on your machine.

<details> <summary><strong>The full feature table</strong></summary> <br>

FeatureDetails
Incremental updatesRe-parses only files whose hash changed. On a ~3,000-file repo a two-file edit takes ~2.5 s on the hook path (measured).
Language and notebook supportSee Language coverage above.
Framework-aware PHP parsingRepository-bounded Composer PSR-4 imports, Blade template references, evidence-gated Laravel Route-to-controller and Eloquent relationship edges
Framework-aware Java parsingSpring dependency-injection call resolution, request endpoints and WebFlux routes, scheduled triggers, application-event publisher-to-listener edges, Spring AOP advice edges, Temporal workflow and activity edges, and Spring Boot config keys indexed without their values
Blast-radius analysisWhich functions, classes and files are likely affected by a change
Auto-update hooksEditor hooks, git hooks (before each commit, after checkout, merge and rebase) and watch mode update the graph as you work
Semantic searchOptional vector embeddings via sentence-transformers, Google Gemini, MiniMax, Voyage AI, or any OpenAI-compatible endpoint
Interactive visualisationD3.js force-directed graph with search, community legend toggles and degree-scaled nodes, plus a neighbourhood view seeded from a symbol, file, flow or change
Hub and bridge detectionMost-connected nodes and chokepoints (betweenness centrality)
Surprise scoringUnexpected coupling: cross-community, cross-language, peripheral-to-hub edges
Knowledge gap analysisIsolated nodes, untested hotspots, thin communities, import cycles
Suggested questionsReview questions generated from bridges, hubs and surprises
Edge confidenceFour tiers (EXTRACTED, INFERRED, AMBIGUOUS, UNRESOLVED) with float scores on edges, so a call the graph could not bind says so
Graph traversalBFS/DFS from any node with configurable depth and token budget; path_to shows how one symbol reaches another and names the weakest hop
Export formatsGraphML, Neo4j Cypher, Obsidian vault, JSON, and SVG (SVG needs matplotlib from the eval extra)
Token benchmarkingcode_review_graph/token_benchmark.py measures whole-corpus tokens against graph query tokens per question
Estimated context savingscontext_savings metadata (estimated, saved_tokens, saved_percent) on review, impact, detect-changes and architecture responses
Community auto-splitCommunities above 25% of the graph are split recursively with Leiden
Execution flowsCall chains from entry points, sorted by weighted criticality
Community detectionLeiden clustering with resolution scaled to graph size
Architecture overviewCommunity-based architecture map with coupling warnings
Risk-scored reviewsdetect_changes maps diffs to affected functions, YAML and JSON paths, flows and test gaps
Custom languagesNew languages via .code-review-graph/languages.toml, no fork needed
GitHub ActionSticky risk-scored PR review comments in CI, with an optional fail-on-risk merge gate
Refactoring toolsRename preview, framework-aware dead code detection, community-driven suggestions
Wiki generationMarkdown wiki from community structure
Multi-repo registryRegister several repos and search across them
Multi-repo daemoncrg-daemon watches several repos as child processes, with health checks and restart
MCP prompts5 workflow templates: review, architecture, debug, onboard, pre-merge
Full-text searchFTS5 hybrid search combining keyword and vector similarity
Local storageOne SQLite file in .code-review-graph/; no external database or cloud service

</details>


Usage

Your assistant uses the graph by itself once it is built. Open a part below for skills, commands, MCP tools and settings.

<details> <summary><strong>Skills</strong></summary> <br>

install writes these four skills for the platforms that support them (Claude Code, Gemini CLI, CodeBuddy Code, Hermes Agent and Qoder). Ask for one by name.

SkillDescription
explore-codebaseNavigate and understand codebase structure using the knowledge graph
review-changesPerform a structured code review using change detection and impact
debug-issueSystematically debug issues using graph-powered code navigation
refactor-safelyPlan and execute safe refactoring using dependency analysis

Qoder also gets build-graph, review-delta and review-pr from the repository's skills/ directory.

</details>

<details> <summary><strong>CLI reference</strong></summary> <br>

code-review-graph install          # Detect and configure all platforms
code-review-graph install --platform <name>  # One platform
code-review-graph install --steering  # Claude Code: graph context beside each search and read
code-review-graph uninstall --dry-run  # Preview removal of installed artifacts
code-review-graph build            # Parse the whole codebase
code-review-graph build --follow-symlinks  # Also index symlinked folders
code-review-graph update           # Incremental update (changed files only)
code-review-graph status           # Graph statistics
code-review-graph watch            # Update on file changes
code-review-graph forget <path>    # Drop already-parsed files from the graph
code-review-graph dead-code        # Functions and classes with no callers or tests
code-review-graph query path_to <from> --to <to>  # How one symbol reaches another
code-review-graph impact --detail-level compact  # Blast radius as ranked file paths
code-review-graph visualize        # Interactive HTML graph
code-review-graph visualize --seed-symbol <name>  # Neighbourhood of one symbol
code-review-graph visualize --format json      # Export graph data as JSON
code-review-graph visualize --format graphml   # Export as GraphML
code-review-graph visualize --format svg       # Export as SVG (needs matplotlib)
code-review-graph visualize --format obsidian  # Export as Obsidian vault
code-review-graph visualize --format cypher    # Export as Neo4j Cypher
code-review-graph wiki             # Markdown wiki from communities
code-review-graph detect-changes --brief         # Risk panel + token savings (read-only)
code-review-graph detect-changes --brief --base main  # Against the merge base of main and HEAD
code-review-graph update --brief                 # Refresh graph + same panel
code-review-graph detect-changes --brief --verify  # Cross-check against tiktoken
code-review-graph detect-changes --details all  # Add graph context to the JSON
code-review-graph context-history  # Trend of the opt-in savings log (CRG_CONTEXT_HISTORY=1)
code-review-graph register <path>  # Register repo in the multi-repo registry
code-review-graph unregister <path|alias>  # Remove repo from the registry
code-review-graph repos            # List registered repositories
code-review-graph daemon start     # Start the multi-repo watch daemon
code-review-graph daemon stop      # Stop the daemon
code-review-graph daemon status    # Daemon status and repos
code-review-graph eval --all       # Run every evaluation benchmark
code-review-graph serve            # Start the MCP server (stdio)
code-review-graph serve --http     # MCP over Streamable HTTP on localhost:5555
code-review-graph serve --http --multi-repo  # One HTTP server for every registered repo
code-review-graph serve --multi-worktree  # One child server per linked Git worktree

This is a selection. code-review-graph --help lists every command, and docs/COMMANDS.md documents their flags.

When detect-changes --base names a branch, the diff runs against the merge base of that branch and HEAD. Commit hashes and other revisions are used as given.

visualize --format svg needs matplotlib, which ships in the eval extra (pip install "code-review-graph[eval]"). The other export formats need no extra install.

JSON exports are written inside the local graph data directory, which Git ignores by default. They can contain absolute paths and code-structure metadata, so inspect an export before publishing it.

</details>

<details> <summary><strong>Token Savings panel: <code>detect-changes --brief</code> vs <code>update --brief</code></strong></summary> <br>

Both commands print the same panel showing how many tokens the graph saved compared with handing the changed files to an agent raw. They differ in one thing: whether the graph is refreshed first.

┌─────────────────────── Token Savings ────────────────────────┐
│ Full context would be:     12,921 tokens                     │
│ Graph context used:           762 tokens                     │
│ Saved:                     12,159 tokens (~94%)              │
│ Breakdown: Functions 244 · Tests 191 · Risk 244 · Other 83   │
└──────────────────────────────────────────────────────────────┘
CommandWhat it doesWhen to use
detect-changes --briefRead-only. Queries the existing graph for the current changes and prints the panel.Most of the time; hooks or crg-daemon keep the graph fresh.
update --briefRe-parses the changed files into the graph first, then prints the same panel.After a rebase, a large change set, or whenever the graph may be stale.

Add --verify to either command to compare the figures with OpenAI's cl100k_base tokenizer (needs pip install tiktoken). The estimate is within about 1% of real tokens in aggregate; see docs/REPRODUCING.md.

The same context_savings metadata is attached to the JSON responses of the get_impact_radius, get_review_context, detect_changes and get_architecture_overview MCP tools.

To see these estimates over time, set CRG_CONTEXT_HISTORY=1 (off by default). Each estimate then adds one line to a local log next to graph.db, holding token counts, tool names, a timestamp, a detail level and file and depth counts, never file names, paths or source text, and code-review-graph context-history prints it per day or per tool. See docs/USAGE.md.

</details>

<details> <summary><strong>Multi-repo daemon</strong></summary> <br>

If install gives your editor no hooks (for example Windsurf or Zed), or you want the graph kept fresh without editor integration, the daemon watches your repositories and updates their graphs. It ships with code-review-graph; no separate install.

# 1. Register the repos 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. Check on it
crg-daemon status                 # daemon and per-repo watcher status
crg-daemon logs --repo proj-a -f  # tail logs for one repo
crg-daemon stop                   # stop the daemon and all watchers

Also available as code-review-graph daemon start|stop|status|....

crg-daemon add writes to ~/.code-review-graph/watch.toml, which you can also edit directly:

[[repos]]
path = "/home/user/project-a"
alias = "proj-a"

[[repos]]
path = "/home/user/project-b"
alias = "project-b"

The daemon watches this file and starts or stops watcher processes as repos are added or removed. A health check every 30 seconds restarts dead watchers.

See docs/COMMANDS.md for the full config reference.

</details>

<details> <summary><strong>30 MCP tools</strong></summary> <br>

The assistant uses these once the graph is built. The 25 tools that only read carry readOnlyHint: true, so a client can run them without asking; the five that write (build_or_update_graph_tool, run_postprocess_tool, embed_graph_tool, apply_refactor_tool, generate_wiki_tool) do not.

ToolDescription
build_or_update_graph_toolBuild or incrementally update the graph
run_postprocess_toolRe-run flow detection, community detection and FTS indexing
get_minimal_context_toolCompact context (~100 tokens); call this first
get_impact_radius_toolBlast radius of changed files; detail_level="compact" gives ranked file paths
get_review_context_toolReview context with structural summary
query_graph_toolCallers, callees, tests, imports, inheritance, and path_to between two symbols
traverse_graph_toolBFS/DFS traversal from any node with a token budget
semantic_search_nodes_toolSearch code entities by name or meaning
embed_graph_toolCompute vector embeddings for semantic search
list_graph_stats_toolGraph size and health
get_docs_section_toolRetrieve documentation sections
find_large_functions_toolFunctions, classes or files above a line-count threshold
list_flows_toolExecution flows sorted by criticality
get_flow_toolOne execution flow
get_affected_flows_toolFlows affected by changed files
list_communities_toolDetected code communities
get_community_toolOne community
get_architecture_overview_toolArchitecture overview from community structure
detect_changes_toolRisk-scored change impact analysis
get_hub_nodes_toolMost-connected nodes
get_bridge_nodes_toolChokepoints by betweenness centrality
get_knowledge_gaps_toolStructural weaknesses, untested hotspots and import cycles
get_surprising_connections_toolUnexpected cross-community coupling
get_suggested_questions_toolReview questions generated from the analysis
refactor_toolRename preview, dead code detection, suggestions
apply_refactor_toolApply a previously previewed refactoring
generate_wiki_toolMarkdown wiki from communities
get_wiki_page_toolOne wiki page
list_repos_toolRegistered repositories
cross_repo_search_toolSearch registered repositories; repos limits the search to a subset

MCP prompts (5 workflow templates): review_changes, architecture_map, debug_issue, onboard_developer, pre_merge_check

</details>

<details> <summary><strong>Configuration</strong></summary> <br>

To exclude paths from indexing, create a .code-review-graphignore file in the repository root:

generated/**
*.generated.ts
vendor/**
node_modules/**

In git repositories a build reads tracked files plus untracked files git does not ignore (git ls-files and git ls-files --others --exclude-standard), so gitignored files are skipped. Use .code-review-graphignore to exclude other files or when git is not available. The default ignore list is in docs/USAGE.md.

Optional dependency groups:

pip install "code-review-graph[embeddings]"          # Local vector embeddings (sentence-transformers)
pip install "code-review-graph[google-embeddings]"   # Google Gemini embeddings
pip install "code-review-graph[communities]"         # Community detection (igraph)
pip install "code-review-graph[enrichment]"          # Python call resolution with Jedi (then build --enrich)
pip install "code-review-graph[eval]"                # Evaluation benchmarks and SVG export (matplotlib)
pip install "code-review-graph[wiki]"                # ollama client (not used by the current wiki generator)
pip install "code-review-graph[all]"                 # All optional dependencies
Environment Variables
VariableDescriptionDefault
CRG_GIT_TIMEOUTTimeout in seconds for Git operations (build, update, watch)30
CRG_DISCOVERY_TIMEOUTTimeout in seconds for each Git command that discovers what changed, when a review tool or command was not given an explicit file list. Running out reports an error, never "no changes"5, or CRG_GIT_TIMEOUT when you set that explicitly
CRG_DATA_DIRDirectory for graph databases and generated artefacts-
CRG_HOOK_WORKTREESSet to 1 to let the generated git hooks run in linked git worktrees-
CRG_EMBEDDING_MODELDefault model for local vector embeddingsall-MiniLM-L6-v2
CRG_ACCEPT_CLOUD_EMBEDDINGSSet to 1 to suppress the cloud embedding egress warning-
CRG_ALLOW_REMOTE_CODEAllow HuggingFace models that require trust_remote_code=True0
CRG_VECTOR_CACHESet to 1 to keep the decoded embedding vectors in memory between semantic searches instead of reading them all back from SQLite on every query. Costs RAM (vectors × dimensions × 4 bytes per cached index) and one open read connection per cached database for the life of the process: on Windows, graph.db cannot be deleted or replaced while it is open-
CRG_MAX_IMPACT_NODESMaximum nodes in impact analysis500
CRG_MAX_IMPACT_DEPTHSearch depth for blast-radius analysis2
CRG_MAX_BFS_DEPTHMaximum depth for graph traversal15
CRG_MAX_CHANGED_FUNCSMaximum changed functions analysed in one change report500
CRG_MAX_DELETION_FILESMaximum files whose old version is read in one change report, to name the functions and classes a diff deleted100
CRG_MAX_CHANGED_YAML_PATHSMaximum changed YAML paths analysed in one change report, and separately the same number of JSON paths200
CRG_MAX_TRANSITIVE_FRONTIERMaximum callees followed per hop when tests_for and detect-changes look for tests. The first by name are kept, so one graph always gives one answer50
CRG_CALLER_TEST_ROUTE_DEPTHHops to walk up incoming CALLS when looking for a tested caller of an untested changed symbol. Reported as a note, never as coverage2
CRG_CALLER_TEST_ROUTE_MAX_CALLERSMaximum callers examined while looking for that tested caller500
CRG_TOOL_TIMEOUTTimeout in seconds for read-only MCP tools (0 disables). Does not bound the tools that write: build, postprocess, embed, wiki and apply-refactor0
CRG_CHURN_WINDOW_DAYSWindow for detect-changes --churn commit counts90
CRG_CONTEXT_HISTORYSet to 1 to keep a local log of each context_savings estimate in .code-review-graph/context-history.jsonl; read it with code-review-graph context-history. Holds token counts, tool names, a timestamp, a detail level and file and depth counts, never file names, paths or source text, and is never sent anywhere-
CRG_LEIDEN_SEEDSeed for Leiden community detection42
CRG_RECURSE_SUBMODULESInclude git submodules when set to 1, true or yes-
CRG_FOLLOW_SYMLINKSFollow symlinked folders, inside or outside the repository, when set to 1, true, yes or on; same as --follow-symlinks. See Symlinked folders-
CRG_MAX_FOLLOWED_FILESMost files taken through followed symlinked folders10000
CRG_TOOLSComma-separated allowlist of MCP tools to expose when serving-
GOOGLE_API_KEYAPI key for Google Gemini embeddings-
MINIMAX_API_KEYAPI key for MiniMax embeddings-
VOYAGE_API_KEYAPI key for Voyage embeddings-
CRG_VOYAGE_MODELModel for Voyage embeddingsvoyage-code-3
CRG_VOYAGE_OUTPUT_DIMENSIONOutput dimension for Voyage embeddings1024
CRG_VOYAGE_OUTPUT_DTYPEOutput dtype for Voyage embeddingsfloat
CRG_VOYAGE_BASE_URLVoyage embeddings endpointhttps://api.voyageai.com/v1
CRG_VOYAGE_BATCH_SIZEBatch size for Voyage requests100
CRG_VOYAGE_MIN_INTERVAL_SECMinimum delay between Voyage requests0
CRG_OPENAI_BASE_URLOpenAI-compatible embeddings endpoint-
CRG_OPENAI_API_KEYAPI key for OpenAI-compatible embeddings-
CRG_OPENAI_MODELModel for OpenAI-compatible embeddings-
CRG_OPENAI_DIMENSIONPin the embedding dimension (v3 models support reduction)-
CRG_OPENAI_BATCH_SIZEBatch size for OpenAI-compatible requests100
NO_COLORDisable ANSI colours in the terminal-
CRG_SERIAL_PARSESet to 1 to disable parallel parsing (for debugging)-
CRG_ENABLE_ENRICHMENTSet to 1 to resolve Python method calls with Jedi on every build and update (needs the enrichment extra; about a second more per Python file). Same as build --enrichoff
CRG_DISABLE_ENRICHMENTSet to 1 to never run Jedi, even when asked for-

OpenAI-compatible embeddings need no extra install. Set the variables and pass provider="openai" to embed_graph:

export CRG_OPENAI_BASE_URL=http://127.0.0.1:3000/v1     # or https://api.openai.com/v1
export CRG_OPENAI_API_KEY=sk-...
export CRG_OPENAI_MODEL=text-embedding-3-small          # whatever your gateway serves
# optional:
export CRG_OPENAI_DIMENSION=1536                        # pin dim (v3 models support reduction)
export CRG_OPENAI_BATCH_SIZE=100                        # lower for gateways with tight limits

The cloud-egress warning is skipped when the base URL points at 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:

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

Model selection. Avoid -preview, -beta or -exp model IDs for an index you plan to keep; preview models can change weights (a different dimension forces a full re-embed) or be withdrawn. Prefer GA releases such as text-embedding-3-small / text-embedding-3-large (OpenAI) or gemini-embedding-001 (native Gemini provider, which needs GOOGLE_API_KEY).

The embedding text is identifiers, signatures, structural context, and a bounded first-paragraph docstring or doc-comment summary. Function bodies are not sent. Graphs created before documentation extraction was added need one full code-review-graph build before re-embedding. Routine builds never refresh embeddings; to refresh after a build, pass both --embedding-provider and --embedding-model. Cloud providers receive this source-derived text and may charge for it.

Tool Filtering

CRG exposes 30 MCP tools by default. To limit the server to a subset, use --tools or the CRG_TOOLS environment variable:

# CLI flag
code-review-graph serve --tools query_graph_tool,semantic_search_nodes_tool,detect_changes_tool

# Environment variable
CRG_TOOLS=query_graph_tool,semantic_search_nodes_tool code-review-graph serve

The flag takes precedence over the variable. When neither is set, all tools are available. In an MCP client config:

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

</details>


FAQ and comparisons

Answers in docs/FAQ.md:

<details> <summary><strong>How it compares with LSP, RAG and grep, when not to use it, and more</strong></summary> <br>

</details>

Troubleshooting

More cases, including Windows/WSL, are in docs/TROUBLESHOOTING.md.

pip / pipx cannot download hatchling (or Errno 9 / Bad file descriptor to PyPI)

<details> <summary><strong>How to fix it</strong></summary> <br>

Installing from a source tree (for example pipx install .) needs build dependencies from PyPI. If you see Could not find a version that satisfies the requirement hatchling after connection warnings, the Python in that terminal may not be able to open an HTTPS connection to pypi.org. This is seen most often in an editor's integrated terminal, and sometimes with a VPN, firewall or proxy.

  1. Run the same command from a terminal outside the editor, such as Terminal.app.

  2. Install from a checkout with uv, which uses different download machinery:

    cd /path/to/code-review-graph
    uv tool install . --force
  3. For development in a clone, use uv sync and uv run code-review-graph ....

To diagnose: python3 scripts/diagnose_pypi_connectivity.py. If it prints FAILED, the problem is the network environment, not the package name.

</details>

Windows: Invalid JSON: EOF while parsing or MCP error -32000: Connection closed

<details> <summary><strong>How to fix it</strong></summary> <br>

Do not use a cmd /c wrapper in the Claude Code config. Point ~/.claude.json at the .exe directly and set UTF-8 through the config:

"code-review-graph": {
  "command": "C:\\path\\to\\your\\venv\\Scripts\\code-review-graph.exe",
  "args": ["serve", "--repo", "C:\\path\\to\\your\\project"],
  "env": { "PYTHONUTF8": "1" }
}

</details>

Contributing

Pull requests target staging (the default branch). Changes are promoted staging → testing → main, and releases are tagged from main. No promotion is automatic: a person reviews and merges each one. See CONTRIBUTING.md for the full flow.

<details> <summary><strong>Set up a development copy and run the tests</strong></summary> <br>

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

</details>

<details> <summary><strong>Add a built-in language</strong></summary> <br>

To add a built-in language, edit code_review_graph/parser.py: add the extension to EXTENSION_TO_LANGUAGE and node type mappings to _CLASS_TYPES, _FUNCTION_TYPES, _IMPORT_TYPES and _CALL_TYPES. Include a test fixture and open a PR. For a language you only need in one repository, use languages.toml instead.

</details>

Licence

MIT. See LICENSE.

<p align="center"> <br> <a href="https://code-review-graph.com">code-review-graph.com</a><br><br> <code>pip install code-review-graph && code-review-graph install</code> </p>

docs/REPRODUCING.md

Reproducing the Benchmarks

This document gives the commands behind every benchmark number in the README and in diagrams/. The pipeline is built to be deterministic: pinned upstream SHAs, a seeded community detector and CPU embeddings, so two runs on different machines should agree within float rounding. If your numbers differ by more than that, file an issue.

Verifying the "saved tokens" number

The CLI's Token Savings panel uses a chars / 4 estimate, labelled estimated: true, not a model tokenizer. To check it against a real tokenizer:

pip install tiktoken
code-review-graph detect-changes --brief --verify    # also: update --brief --verify

The panel gains a Verified (tiktoken) row computed with OpenAI's cl100k_base tokenizer (verify_with_tiktoken() in code_review_graph/context_savings.py). Example output:

┌───────────────────────── Token Savings ─────────────────────────┐
│ Full context would be:     12,921 tokens                        │
│ Graph context used:           762 tokens                        │
│ Saved:                     12,159 tokens (~94%)                 │
│ Verified (tiktoken):       10,835 tokens (~93%)  [11,611 → 776] │
│ Breakdown: Functions 244 · Tests 191 · Risk 244 · Other 83      │
└─────────────────────────────────────────────────────────────────┘
Calibration table

A one-off calibration over 222 files (2.2 MB of Python, JS, TS, Go, Rust, RST and Markdown) sampled from the six eval repositories:

Reposample filesbyteschars/4 estimatetiktoken realratio est/real
flask46470,179117,559109,9691.069
fastapi38156,22439,07234,8971.120
gin30471,793117,962132,2960.892
express23296,80574,20783,5750.888
httpx38254,18463,55662,9091.010
code-review-graph47539,206134,820120,7601.116
Overall2222,188,391547,176544,4061.005

In aggregate chars / 4 is within +0.5% of cl100k_base. Per repo it ranges from -11% (gin: short Go identifiers) to +12% (fastapi: long docstrings and type hints). The saved ratio moves less than the absolute counts because both sides of the division carry the same bias.

The sample list and raw counts behind this table are not stored in the repository. --verify reproduces the comparison on any checkout.

What is deterministic

ReproducibleReason
Tree-sitter parsingPure function of the input bytes
Node and edge countsUpserts keyed by qualified_name
FTS5 BM25 scoresDeterministic
Embeddings via all-MiniLM-L6-v2 on CPUModel weights pinned by hash in the Hugging Face cache
Leiden community IDsSeeded: _LEIDEN_SEED = 42 in communities.py; override with CRG_LEIDEN_SEED
naive_corpus_tokensFixed for a given checkout
Upstream repositoriesCloned in full and checked out at the SHA pinned in each config

Prerequisites

  • Python 3.10 or newer

  • git on PATH

  • Network access to clone the six upstream repositories

  • For embeddings, torch and sentence-transformers, installed by the embeddings extra

Step 1: install with the eval, embeddings and communities extras

git clone https://github.com/tirth8205/code-review-graph
cd code-review-graph
uv sync --extra eval --extra embeddings --extra communities     # or: pip install -e ".[eval,embeddings,communities]"

eval is pyyaml and matplotlib (the eval itself does not use matplotlib; only visualize --format svg does); embeddings is sentence-transformers and numpy; communities is igraph, which Leiden needs. Without it the build groups communities by file, so the community numbers will not match.

Step 2: run the eval

This clones the six repositories at the SHAs pinned in code_review_graph/eval/configs/*.yaml, builds a full graph for each (parser, resolvers, signatures, FTS5, flows, Leiden communities), embeds it, and runs the benchmarks.

uv run code-review-graph eval --embed \
  --benchmark token_efficiency,impact_accuracy,agent_baseline,multi_hop_retrieval

--embed is required for agent_baseline, multi_hop_retrieval and search_quality; without it their natural-language questions hit FTS5 only and return nothing. code-review-graph eval --help lists the remaining benchmarks (flow_completeness, search_quality, build_performance, incremental_fidelity) and flags.

A thrown tool call is not a measurement. The row stays in the CSV with status=error and is excluded from every aggregate. Regression tests for this are in tests/test_eval.py.

Outputs:

  • evaluate/test_repos/<name>/, each with its own .code-review-graph/graph.db

  • evaluate/results/<name>_<benchmark>_<date>.csv

Step 3: embeddings for the standalone benchmark

The standalone token benchmark asks five natural-language questions and needs vector embeddings; without them hybrid search matches nothing, the benchmark prints a warning and reports 0x. If Step 2 ran with --embed, the graphs already have them. Otherwise:

for repo in express fastapi flask gin httpx code-review-graph; do
  uv run code-review-graph embed --repo "evaluate/test_repos/$repo"
done

Vectors are stored in the same graph.db.

Step 4: run the standalone token benchmark

This benchmark compares the tokens of all source files in the repository against the tokens of 5 search hits plus up to 5 neighbour edges per hit, for each of the 5 sample questions.

uv run python <<'PY'
import json
from pathlib import Path
from code_review_graph.graph import GraphStore
from code_review_graph.token_benchmark import run_token_benchmark

results = {}
for repo in sorted(Path("evaluate/test_repos").iterdir()):
    db = repo / ".code-review-graph" / "graph.db"
    if not db.exists():
        continue
    store = GraphStore(str(db))
    try:
        results[repo.name] = run_token_benchmark(store, repo)
    finally:
        store.close()

print(f"{'Repo':<22}{'naive_tokens':>16}{'avg_graph_tokens':>20}{'avg_ratio':>14}")
print("-" * 72)
for name, out in sorted(results.items(), key=lambda x: -x[1]["average_reduction_ratio"]):
    pq = out["per_question"]
    avg_graph = int(sum(r["graph_tokens"] for r in pq) / max(len(pq), 1))
    print(f"{name:<22}{out['naive_corpus_tokens']:>16,}"
          f"{avg_graph:>20,}{out['average_reduction_ratio']:>13.1f}x")

Path("evaluate/standalone_token_benchmark.json").write_text(json.dumps(results, indent=2))
PY

Canonical numbers

<!-- BEGIN canonical-stats -->

Captured 2026-08-02 on macOS arm64 (Apple M4 Pro, 14 cores, 24 GB), Python 3.13.12, code-review-graph 2.3.7, sentence-transformers 5.6.1, all-MiniLM-L6-v2, CRG_LEIDEN_SEED=42, from clean clones at the pinned SHAs.

These numbers predate 2.4.0, which also indexes HTML, JSON, YAML and Liquid files and ranks test code below production code in search, so a 2.4.0 run gives different node, edge, embedding and token counts until the capture is redone.

Standalone token benchmark (code_review_graph/token_benchmark.py)

Each row averages the 5 sample questions (how does authentication work, what is the main entry point, how are database connections managed, what error handling patterns are used, how do tests verify core functionality).

Reposnapshot SHAnaive_corpus_tokensavg graph_tokensavg ratio
fastapi22381558948,7932,653375.6x
flaska29f88ce143,5942,19671.0x
code-review-graph84bde354208,8213,19068.1x
gin5c00df8a166,8682,76661.9x
httpxb55d4635142,3562,66160.6x
expressb4ab7d65136,0523,93636.0x

avg ratio is the benchmark's average_reduction_ratio: the mean of the five per-question naive_total / graph_tokens ratios. It is not naive_corpus_tokens / avg graph_tokens, and it always reads higher than that division, so the three columns do not divide out. Range across the 6 repos on this measure: 36x to 376x, median about 65x. The README divides the two token columns instead and reports 35x to 358x, median about 63x.

The JSON written by Step 4 for this capture is not checked in; the same naive_corpus_tokens and avg graph_tokens figures appear in the README. They replace the 2026-05-25 capture and every ratio is lower, for two reasons confirmed by re-running from clean clones: avg graph_tokens rose in every repo because the per-node embedding text grew, so a 5-hit response carries more text; and fastapi is measured at its current pin 22381558 instead of the retired 0227991a. naive_corpus_tokens is unchanged for code-review-graph and gin, so the movement is on the graph-response side.

Formal token_efficiency benchmark (code_review_graph/eval/benchmarks/token_efficiency.py)

A different denominator: the changed-file content of each commit against the full get_review_context() JSON. For small commits the response is larger than the input (it carries impact-radius edges and source snippets), so ratios below 1.0 are expected here. Per-commit rows are in evaluate/results/<repo>_token_efficiency_*.csv.

Impact accuracy (code_review_graph/eval/benchmarks/impact_accuracy.py)

13 commits across the 6 repos, in evaluate/results/<repo>_impact_accuracy_2026-08-02.csv. Each commit is graded in two ground-truth modes, told apart by the ground_truth_mode column:

ModeGround truthMeaning
graph-derived (circular — upper bound)changed files plus files with CALLS or IMPORTS_FROM edges into them, derived from the same graph the predictor traversesAn upper bound. Recall 1.0 is partly true by construction.
co-change (same commit, seed excluded)the other files the author touched in the same commit, given one seed fileIndependent evidence from git history. Expect much lower recall.

The graph-derived rows give:

Metric (graph-derived mode, circular upper bound)Value
Recall (mean across 13 commits)1.000
F1 (mean)0.693
F1 (median)0.667
F1 (min / max)0.465 / 1.000

The predictor over-predicts on some commits. The worst case is flask fbb6f0bc: 33 files flagged for a 10-file change, precision 0.303. That trade-off is deliberate: a missed dependency costs more than an extra reviewed file.

The co-change rows in this capture are not usable: all 11 graded commits came back with predicted_files = 0, so their F1 of 0.000 measures a fault in the benchmark, not the predictor. git diff gives repo-relative paths and the graph stores absolute ones, so the seed never matched a node. PR #823 fixed that, and the graph-derived ground truth now counts real neighbour files too, so both tables will change when the benchmark is run again. No co-change number is quoted until then. The two single-file commits (express) are recorded with status=skipped because there is nothing independent to grade against.

Multi-hop retrieval (code_review_graph/eval/benchmarks/multi_hop_retrieval.py)

11 hand-written tasks across the 6 repos, in evaluate/results/<repo>_multi_hop_retrieval_2026-05-25.csv. Each task is a two-step chain:

  1. hybrid_search(nl_query, limit=k) looks for an anchor node (default k = 10).

  2. query_graph(<traversal_pattern>, target=<anchor>) walks one hop (callers_of, callees_of, tests_for, ...).

A task scores int(anchor_found) * neighbor_recall, so 1.0 only when the anchor is in the top-k and every expected neighbour name comes back. Inspect anchor_found and neighbor_recall in the CSV to tell a search miss from a traversal miss.

RepoTaskAnchor foundRankNeighbour recallScore
code-review-graphcrg-parse-file-callersyes01.001.00
code-review-graphcrg-upsert-node-callersyes41.001.00
expressexpress-create-application-calleesyes11.001.00
fastapifastapi-route-handler-callersyes61.001.00
fastapifastapi-get-dependant-callersno-0.000.00
flaskflask-dispatch-callersyes31.001.00
flaskflask-exception-callersyes51.001.00
gingin-serve-http-calleesyes51.001.00
gingin-context-next-callersyes01.001.00
httpxhttpx-client-request-callersyes01.001.00
httpxhttpx-async-request-testsyes71.001.00

Average score across 11 tasks: 0.909 (10 of 11). The miss, fastapi-get-dependant-callers, targets get_dependant ("dependant" with an a) from the query "dependency declarations into a tree"; there is no shared identifier or substring for the search heuristics to use. It is left as a miss; a fix would need query rewriting or a richer embedding model.

The first version of this benchmark scored 0.545 (6 of 11). Two changes took it to 0.909:

  1. embeddings.py: _node_to_text() embeds, per node, the dotted form (APIRoute.get_route_handler), the identifier split into words (get route handler, see _split_identifier()) and the enclosing module directory, instead of only "{name} {kind} in {parent}". Re-embedding is automatic because the text hash changes.

  2. search.py: extract_query_identifiers() pulls dotted, snake_case and CamelCase tokens out of the query; hits whose qualified_name contains one are boosted 2.0x. This moved Context.Next from rank 11 to rank 0.

To add tasks, append multi_hop_tasks: entries to a config under code_review_graph/eval/configs/:

multi_hop_tasks:
  - id: my-task-id                 # required, unique
    nl_query: "natural language"   # required: what an agent would ask
    anchor_qualified_suffix:       # required: lower-cased suffix of the expected
      "rel/path.py::owner.symbol"  #   qualified_name (case-insensitive endswith)
    traversal_pattern: callers_of  # any query_graph pattern; default callers_of
    expected_neighbor_names:       # required: bare names that must appear
      - "expected_one"
    k: 10                          # optional: top-k depth for the search step
Build stats
RepoNodesEdgesEmbeddings
fastapi6,28732,0365,159
express1,99019,4921,849
gin1,58917,2371,491
code-review-graph1,4469,0941,354
flask1,4158,2591,329
httpx1,2638,2361,193

From the same 2026-08-02 build; the raw output is not checked in. Flow, community and FTS counts were not captured in that run. Embeddings are fewer than nodes because File nodes are not embedded.

<!-- END canonical-stats -->

Incremental update latency

The README and diagram 4 quote an incremental-update time. There is no runner for it; it is a stopwatch on the CLI, so the recipe is written out in full. The timings below are not stored in the repository.

Corpus: a shallow clone of django/django (2,927 .py files; the graph indexed 2,998 files, 46,683 nodes, 392,758 edges). Machine: Apple M4 Pro (14 cores, 24 GB), macOS 26.5.2, Python 3.13.12, code-review-graph 2.3.7.

git clone --depth 1 https://github.com/django/django.git
cd django
/usr/bin/time -p code-review-graph build                 # cold build

/usr/bin/time -p code-review-graph update                # no-op: nothing changed
echo "# edit" >> django/db/models/query.py
echo "# edit" >> django/http/response.py
/usr/bin/time -p code-review-graph update --skip-flows   # the path the hooks run
/usr/bin/time -p code-review-graph update                # full post-processing
ScenarioWall clockFiles re-parsed
Cold full build40.3 s2,998
update, nothing changed1.4 s0
update --skip-flows, 2 files edited (hook path)2.4 to 2.9 s2
update, 2 files edited (full post-processing)9.8 s2

About 1.4 s of every figure is process start-up (the no-op cost), so a two-file edit on the hook path costs about 1 s on top. Only the 2 edited files are re-parsed: dependents are found through import and call edges, but any dependent whose SHA-256 is unchanged is skipped before parsing (incremental.py), so the re-parse count tracks what you edited, not the size of the dependency cascade.

Earlier versions quoted "under 2 seconds on a ~2,900-file repo". At that size it holds only for the no-op case; an edit on the hook path is about 2.5 s, and about 10 s with flow and community detection.

Agent baseline benchmark (code_review_graph/eval/benchmarks/agent_baseline.py)

The whole-corpus baseline in the standalone benchmark is an upper bound no real agent pays. This benchmark simulates an agent without the graph:

  1. Derive search terms from each question in the config's agent_questions: list (identifier-shaped tokens via search.extract_query_identifiers() plus plain keywords; falls back to the search_queries strings when absent).

  2. Grep the corpus in pure Python (no external rg or grep), ranking files by total case-insensitive match count; ties break on path.

  3. Read the top 3 files (agent_baseline_top_k in the config) and count their tokens (chars / 4) as baseline_tokens.

  4. Compare with the graph-query cost for the same question: 5 hybrid search hits plus up to 5 neighbour edges per hit, the same accounting as the standalone benchmark.

Output: evaluate/results/<repo>_agent_baseline_<date>.csv with a baseline_to_graph_ratio per question. Rows where either side is zero get status=no_graph_results or status=no_baseline_match and are excluded from agent_baseline.aggregate().

Canonical capture, 2026-09-22

18 questions, 3 per repo, from the agent_questions: lists. Local all-MiniLM-L6-v2 embeddings, clean clones at the pinned SHAs, every row status=ok.

RepoQuestionsavg grep top-3 tokensavg graph tokensmedian ratio
fastapi3158,5603,43952.1x
flask327,6112,43211.5x
gin325,2573,1106.6x
httpx326,1744,0216.5x
code-review-graph326,1343,7736.4x
express317,0863,7264.5x

Across all 18 questions: median 6.65x, mean 14.81x, range 4.2x to 72.6x.

Run it with:

code-review-graph eval --benchmark agent_baseline --embed

This capture predates test-file ranking in hybrid_search and grep_rank, so a run today gives different numbers until the capture is redone. At the code-review-graph pin the grep baseline alone moves from 41,674 / 15,028 / 21,701 tokens to 41,586 / 27,764 / 29,207.

Two things this number is not:

  • It is not the whole-corpus ratio. The standalone benchmark's ~63x compares a graph query against reading every source file, which no agent does. Against a grep-and-read agent the honest figure is the 6.65x above. Quote whichever you mean, and say which.

  • It is not an end-to-end agent saving. Both sides here are measured as retrieval cost: the tokens needed to locate the answer. A real coding agent may still open the files the graph named, in which case the graph cost is additive rather than a substitute. semantic_search_nodes_tool(include_source=True) exists so the agent can answer from the response instead; measuring that path end-to-end is open work.

The ratio tracks repo size, because the graph answer stays flat (2,432 to 4,021 tokens here) while the grep baseline grows with the corpus. On a small repo the graph saves little; on fastapi it saves a lot.

Incremental fidelity (code_review_graph/eval/benchmarks/incremental_fidelity.py)

A persistent graph rots invisibly. Nothing errors, caller lists quietly get shorter, and a reviewer gets a confidently incomplete answer. A clean rebuild of the same tree is a free and perfect oracle for that drift, so this benchmark uses it.

For each of seven edit kinds the benchmark copies the repository's tracked files into a throwaway git tree, builds a clean graph, applies the edit, commits it, runs incremental_update plus the post-processing build_or_update_graph runs, builds a second clean graph from the same edited tree, and compares the two databases table by table.

uv run code-review-graph eval --repo code-review-graph --benchmark incremental_fidelity

Row ids are autoincrement and differ between two builds of the same tree, so every projection is keyed on something stable (a qualified name, a community name, a flow's path resolved to qualified names) and excludes ids and wall-clock columns. nodes_fts is compared through fts5vocab, which reads the FTS index itself rather than the nodes content table behind it.

Results on this repository

323 files, 6,509 nodes, 57,042 edges, 186 flows. Differing rows per table, incremental update against clean rebuild. Measured at schema v14: community_summaries, flow_snapshots and risk_index were removed in v15, as nothing read them, and the benchmark no longer compares them.

EditStatusTotalnodesnode_communityedgescommunitiesflowsflow_membershipsnodes_ftscommunity_summariesflow_snapshotsrisk_indexmetadataSeconds
trailing_commentknown_failure7,237041260586,70201580011.0
rename_functionknown_failure7,233041260586,6980158009.4
delete_fileknown_failure10,506003,3772726,9790272209.8
add_fileknown_failure3010100010006.7
move_functionknown_failure8,391051961937,67801930012.5
change_importknown_failure7,237041260586,70201580010.5
revertknown_failure7,237041260586,7020158009.8

Benchmark wall time 79.1 s (8 clean builds plus 8 incremental updates).

nodes and nodes_fts match everywhere: node rows and the search index are faithful. Every other divergence traces to one of three defects. The table was measured before #673, which fixes defects 1 and 2 below (flows and community assignment now follow re-parsed nodes), so expect smaller numbers until it is captured again.

1. flow_memberships is orphaned by every re-parse. flow_memberships.node_id is a bare integer with no foreign key and no cascade, and GraphStore._replace_file_data deletes and re-inserts a re-parsed file's nodes with fresh ids. After appending one comment to code_review_graph/parser.py, 1,595 of the incremental graph's 4,494 membership rows point at node ids that no longer exist; the rebuild has 0. Because incremental_trace_flows finds affected flows by joining flow_memberships to nodes, the dangling rows are invisible to the repair path as well, so a full rebuild is the only way back.

2. Community assignment is never restored, and the repair path cannot fire. The same edit leaves 718 nodes with community_id IS NULL against the rebuild's 306. incremental_detect_communities and incremental_trace_flows both match nodes.file_path (absolute) against incremental_update's changed_files (repo-relative), so both always count zero affected rows and skip.

3. Deleting a file silently shortens other files' caller lists. Deleting code_review_graph/graph.py leaves the incremental graph with 52,913 edges against the rebuild's 56,272, and 3,377 edge rows differ. code_review_graph/analysis.py::find_bridge_nodes has no outgoing CALLS edge at all at lines 73 and 100 in the incremental graph; the rebuild has two (_build_networkx_graph and _sanitize_name). Those edges had been resolved into graph.py, were removed with it, and analysis.py was never re-parsed to restore them as bare calls. Two edges still target graph.py:: names that have no node.

The six edges rows that differ on every edit are a separate, smaller issue: the ambiguous_targets list inside an edge's extra JSON is ordered by node id, so re-parsing a file reorders it. Same set, different order, so the graph is not byte-reproducible.

Every edit kind is listed in incremental_fidelity.KNOWN_FAILURES, which keeps the run from reporting them as unexpected. tests/test_incremental_fidelity.py guards against a kind that currently passes starting to diverge, and pins defects 1 and 2 at the level of the defect. Defect 2 has plain tests, one of them for a file that an update adds (#1122). Defect 1's test is still marked xfail but now passes.

Weekly CI run (report-only)

.github/workflows/eval.yml runs every Monday at 06:23 UTC, and on workflow_dispatch, against the two smallest pinned configs (httpx, flask) with token_efficiency, impact_accuracy and agent_baseline. It uploads the CSVs as an artifact and writes a job-summary table. Regressions do not fail the default branch.

Which benchmark measures what

BenchmarkBaselineGraph costQuestion
code_review_graph/eval/benchmarks/token_efficiency.pychanged-file content of one commitfull get_review_context() JSONIs the graph cheaper than reading the diffed files?
code_review_graph/eval/benchmarks/agent_baseline.pygrep, top 3 files for the question's identifiers5 search hits + 5 neighbour edges per hitIs the graph cheaper than a grep-and-read agent?
code_review_graph/eval/token_benchmark.pynone; absolute costsum of the tool responses in simulated review, architecture, debug, onboarding and pre-merge workflowsWhat does a complete agent workflow cost?
code_review_graph/token_benchmark.py (standalone)all source files in the repo5 search hits + 5 neighbour edges per hitIs the graph cheaper than reading the whole repo?
code_review_graph/eval/benchmarks/incremental_fidelity.pya clean rebuild of the same treethe same graph after incremental_updateDoes an incremental update equal a rebuild?

token_efficiency can be below 1.0x for small commits. The standalone numbers are always large because the baseline is the whole repo, which is why the README leads with the median (about 63x), treats 358x as the maximum, and points to agent_baseline as the realistic middle ground. Quote the one that matches the scenario.

Generating diagrams

The 9 PNGs in diagrams/ are produced from diagrams/generate_diagrams.py. The .excalidraw sources are gitignored (*.excalidraw in .gitignore); only the PNGs are tracked. After a benchmark refresh:

uv run python diagrams/generate_diagrams.py         # write all 9 .excalidraw files
uv run python diagrams/generate_diagrams.py 1 5 6   # write only diagrams 1, 5 and 6

Then open the files in an Excalidraw editor and export each one as a PNG.

Troubleshooting

git clone failed: network or upstream rate limit. Retry; the eval does not retry on its own.

git checkout <sha> failed: upstream rewrote history or removed the SHA. File an issue with the failing config so it can be re-pinned.

No embeddings found in this graph during the standalone benchmark: run Step 3.

Different community IDs between runs: check grep _LEIDEN_SEED code_review_graph/communities.py and that everyone uses the same CRG_LEIDEN_SEED.

Different naive_corpus_tokens from the canonical table: git rev-parse HEAD inside evaluate/test_repos/<name> must match the commit: field of the config. If not, delete the clone and let Step 2 re-clone at the pinned SHA.

The real-repository parser corpus

Every other parser test writes a small fixture that exercises one construct. A regression that only shows on real code — a resolver that stops resolving, an ignore rule that swallows a source tree, a grammar that raises on a real file — passes all of them. pytest -m corpus builds the graph over eight projects pinned to exact commits and compares twelve measured properties against tests/corpus_baselines.json.

pytest -m corpus -q                         # run the check
CRG_CORPUS_CACHE=~/.cache/crg-corpus \
  pytest -m corpus -q                       # keep the clones between runs
python -m tests.real_repo_corpus            # list the pins
python -m tests.real_repo_corpus --record   # re-record after an intentional change

The normal suite skips it: tests/conftest.py skips every corpus-marked item unless the run's -m expression names the marker, so neither pytest tests/ nor CI's -m "not browser and not upgrade" job pays for it. Clones are shallow single-commit fetches (~115 MB total). A cold run takes about 1 minute on a warm network and roughly 40 seconds once the clones are cached; budget a few minutes on CI hardware.

fastapi and gin reuse the pins in code_review_graph/eval/configs/, so the project keeps one answer to "which commit of fastapi do we measure". tests/test_real_repo_corpus_guard.py fails if those two ever drift apart.

Recorded baseline (2.4.0)

Measured on macOS 15 (Apple silicon), CPython 3.13.

RepoLanguageCommitFilesNodesEdgesResolved edgesResolved importsParse errorsFiles with no nodeDangling CONTAINSBuild
fastapipython223815581182108043696956.8%46.9%0005.4s
gingo5c00df8a10918701753052.2%0.0%0012.5s
zodtypescript59bbc03e57786299992353.7%65.5%00013.9s
gsonjava854c825527347364783462.2%34.1%00010.7s
newtonsoft-jsoncsharp09bb545d977104917692052.9%0.0%00020.6s
ripgreprust3fce3b5b12537552943147.1%22.8%00173.5s
sinatrarubycb22afd715932682470155.8%31.4%0002.1s
guzzlephp9393947014534514365857.7%45.1%0006.5s

Counts are deterministic: two recording runs on the same machine produced identical numbers for every column except Build.

How the bands were chosen

A pinned commit parsed by a fixed build has no measurement noise, so the bands do not model noise. They model how much intentional change the project may make before someone has to re-record. The tolerances live in the bands block of tests/corpus_baselines.json so a widened band shows up in a diff like any other change.

PropertyBandWhy
files_parsed, file_nodes-2% / +25%The inventory of a fixed commit moves only when the ignore rules or the supported-extension list change. -2% is "must not lose files"; +25% leaves room for a newly supported extension.
total_nodes-8% / +40%-8% is several hundred nodes on every repo here, far outside any legitimate tidy-up. +40% admits a whole new node kind without a forced re-record while still catching runaway duplication.
total_edges-10% / +50%Wider in both directions because resolvers routinely trade edges for precision. -10% still means one relationship in ten stopped being recorded.
resolved_edge_share, imports_resolved_sharefloor at baseline -5ppA floor in percentage points, not a ratio, so a repo near 0.20 and one near 0.60 get the same absolute protection. 5pp is roughly a thousand edges on the larger repos.
parse_errors, files_without_nodes, dangling_contains_edgesceiling at the recorded valueThese are all "the build silently lost something". They may fall, never rise.
control_char_namesmust be 0A security invariant, not a trend.
build_secondsmax(3x, +20s)Wall clock varies with the machine. 3x catches a quadratic resolver; the +20s term keeps the sub-2-second repos from failing on scheduler noise.
primary_language_presentmust holdA dropped extension mapping otherwise looks like a small node-count change.
Reading a failure

Each failure names the property, the recorded value, the measured value, the delta and the band. Dropping ".go": "go" from the parser's extension map produces:

gin (go) @ 5c00df8afadd: 5 of 12 properties moved out of band
  - files_parsed fell below its band: baseline=109 measured=11 delta=-98 (-89.9%), allowed [106.8, 136.2] (x0.98..x1.25)
  - total_nodes fell below its band: baseline=1870 measured=275 delta=-1595 (-85.3%), allowed [1720.4, 2618.0] (x0.92..x1.4)
  - primary_language_present dropped: baseline=1 measured=0 delta=-1. gin is a go project but the graph holds languages ['yaml']
512 files · 480 not served here

Advantages

  • Drastically reduces token usage by providing only the relevant code context for a specific change. evidence: README.md
  • Maintains data privacy by storing the knowledge graph locally in a SQLite file without requiring cloud services. evidence: README.md

Limitations

Security

Risk level: Medium risk

This brain runs

MCP servers (2)

  • .mcp.json (code-review-graph): uvx code-review-graph serve
  • README.md (code-review-graph): code-review-graph serve --tools query_graph_tool,semantic_search_nodes_tool,detect_changes_tool,get_review_context_tool

Agent instructions that run or fetch something (38)

  • AGENTS.md: Other commands that may prompt: `scp` and `ssh` (`-o BatchMode=yes`), `apt-get` (`-y`), `brew` (`HOMEBREW_NO_AUTO_UPDATE=1`). · uses the network
  • CLAUDE.md: `locking.py`: the cross-process lock that lets one graph write run at a time.
  • CLAUDE.md: `skills.py`: `install` (platform MCP configs, hooks, skills, instruction blocks).
  • CLAUDE.md: `install_plan.py`: every install read and write goes through it, so `install --dry-run` can show each change as a diff and a real install writes nothing before its question is answered.
  • CLAUDE.md: `jsonc.py`: comment-preserving JSONC tokenising and splices, shared by install and uninstall so neither flattens a commented config.
  • CLAUDE.md: `install_gitignore.py`: the opt-in `install --gitignore` block listing the files a run created.
  • CLAUDE.md: `uninstall.py`: reverses `install`.
  • CLAUDE.md: uv run pytest tests/ --tb=short -q
  • CLAUDE.md: uv run ruff check code_review_graph/
  • CLAUDE.md: uv run mypy code_review_graph/ --ignore-missing-imports --no-strict-optional
  • CLAUDE.md: uv run code-review-graph build # full build
  • CLAUDE.md: uv run code-review-graph update # incremental update
  • CLAUDE.md: uv run code-review-graph status # graph statistics
  • CLAUDE.md: uv run code-review-graph detect-changes # risk-scored change analysis (read-only)
  • CLAUDE.md: uv run code-review-graph forget PATH # drop files from the graph
  • CLAUDE.md: uv run code-review-graph serve # MCP server (stdio; --http for localhost)
  • CLAUDE.md: uv run code-review-graph wiki # Markdown wiki
  • CLAUDE.md: uv run code-review-graph register <path> # add a repo to the multi-repo registry
  • CLAUDE.md: uv run code-review-graph eval --all # benchmarks
  • CLAUDE.md: uv run code-review-graph --help # full command list
  • CLAUDE.md: Install and platforms: `test_skills.py`, `test_cli_install.py`, `test_uninstall.py`, `test_git_hook_worktree.py`, `test_hermes_install.py`, `test_qoder_bundled_skills.py`, `test_installer_ownership.py` (what install may rewrite and what belongs to the user), `test_install_gitignore_blo …[+283 chars]
  • CLAUDE.md: Builds a wheel and an sdist with `python -m build`, installs each into its own virtual environment, and drives the installed program with the checkout out of reach.
  • CLAUDE.md: Run it before a release with `uv run --python 3.13 python -m pytest tests/test_packaging.py -m packaging`.
  • CLAUDE.md: upgrade-path: manual run only.
  • code_review_graph/assets/code-review-graph/SKILL.md: Resolve the active repository root from the workspace or `git rev-parse --show-toplevel`.
  • code_review_graph/assets/code-review-graph/SKILL.md: Without MCP, run the bundled helper’s `status` command (it forwards `--json` to the CLI).
  • code_review_graph/assets/code-review-graph/SKILL.md: python3 <skill-root>/scripts/crg_readonly.py status --repo "<repo-root>"
  • code_review_graph/assets/code-review-graph/SKILL.md: python3 <skill-root>/scripts/crg_readonly.py architecture --repo "<repo-root>"
  • code_review_graph/assets/code-review-graph/SKILL.md: python3 <skill-root>/scripts/crg_readonly.py search "<query>" --repo "<repo-root>"
  • code_review_graph/assets/code-review-graph/SKILL.md: python3 <skill-root>/scripts/crg_readonly.py query callers_of "<symbol>" --repo "<repo-root>"
  • code_review_graph/assets/code-review-graph/SKILL.md: python3 <skill-root>/scripts/crg_readonly.py query path_to "<symbol>" --to "<other>" --repo "<repo-root>"
  • code_review_graph/assets/code-review-graph/SKILL.md: python3 <skill-root>/scripts/crg_readonly.py impact --repo "<repo-root>"
  • code_review_graph/assets/code-review-graph/SKILL.md: python3 <skill-root>/scripts/crg_readonly.py detect-changes --brief --repo "<repo-root>"
  • code_review_graph/assets/code-review-graph/SKILL.md: A Windows-mounted path such as `/mnt/e/...` is not by itself a failure: verify that the selected runtime can execute the CLI and access that path.
  • skills/build-graph/SKILL.md: Hooks installed by `code-review-graph install` run an update after each edit, before each commit and after a branch switch, merge or rebase, so manual builds are rarely needed.
  • skills/review-delta/SKILL.md: Changes come from `git diff` against `HEAD~1`;
  • skills/review-pr/SKILL.md: The graph tools read the files on disk, so check out what you review first: `git switch <branch>`, or for a PR `git fetch origin pull/<n>/head:pr-<n>` then `git switch pr-<n>`, then call `build_or_update_graph_tool()`. · uses the network
  • skills/review-pr/SKILL.md: Diff it against `main` (or `master`) with `git diff --name-only main...HEAD`, and use that ref as `base` below.

Hooks (3)

  • hooks/hooks.json (SessionStart): cat >/dev/null || true; command -v code-review-graph >/dev/null 2>&1 || exit 0; git rev-parse --git-dir >/dev/null 2>&1 && code-review-graph status --repo "$(git rev-parse --show-toplevel 2>/dev/null)" || echo 'Not a git repo, skipping'
  • hooks/hooks.json (PostToolUse): cat >/dev/null || true; code-review-graph build >/dev/null 2>&1 &
  • hooks/hooks.json (PostToolUse): command -v code-review-graph >/dev/null 2>&1 && code-review-graph hook-update; cat >/dev/null || true
  • Medium risk: runs-mcp-server-unpinned-runner (.mcp.json)
  • Medium risk: runs-instruction-instruction-network (AGENTS.md:17)
  • Medium risk: runs-instruction-instruction-network (skills/review-pr/SKILL.md:13)

Attribution and licence

Created by
Tirth
Declared authors
  • Tirth (declared in pyproject.toml)
Publisher
Tirth Kanani (Community)
Source
tirth8205/code-review-graph · commit e7f2fb2
Licence
MIT License (files served here)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Tirth.

Install from source

480 of its files are not served here, so there is no ZIP. tirth8205/code-review-graph at e7f2fb2