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.
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.
| Folder | Role | Files |
|---|---|---|
| .github | agent-config | 15 |
| docs | docs | 13 |
| hooks | hooks | 2 |
| scripts | scripts | 7 |
| skills | agent-instructions | 7 |
| tests | tests | 354 |
| .beads | folder | 9 |
| code-review-graph-vscode | folder | 32 |
| code_review_graph | folder | 103 |
| diagrams | folder | 12 |
| evaluate | folder | 24 |
The agent reads
- get_review_context_tool evidence: AGENTS.md
- query_graph_tool evidence: AGENTS.md
- skills/ evidence: README.md
- get_docs_section_tool evidence: README.md
- get_suggested_questions_tool evidence: README.md
The agent writes
- .gitignore evidence: CLAUDE.md
- .mcp.json evidence: README.md
- code_review_graph/cli.py evidence: README.md
- code_review_graph/parser.py evidence: README.md
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
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
- 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/
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&utm_medium=badge&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
installsets 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 codebuddyConfig 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 buildThe 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 buildThe 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.
| Repo | Snapshot SHA | naive_corpus_tokens | avg graph_tokens | Reduction |
|---|---|---|---|---|
| fastapi | 22381558 | 948,793 | 2,653 | 357.6x |
| flask | a29f88ce | 143,594 | 2,196 | 65.4x |
| code-review-graph | 84bde354 | 208,821 | 3,190 | 65.5x |
| gin | 5c00df8a | 166,868 | 2,766 | 60.3x |
| httpx | b55d4635 | 142,356 | 2,661 | 53.5x |
| express | b4ab7d65 | 136,052 | 3,936 | 34.6x |
Captured 2026-08-02 from clean clones at the pinned SHAs (crg 2.3.7, local
all-MiniLM-L6-v2embeddings). These numbers are lower than the 2026-05-25 capture they replace: node embedding text became richer, soavg graph_tokensrose in every repo. fastapi is measured at its current pin22381558rather than the retired0227991a.The Reduction column is
naive_corpus_tokens / avg graph_tokens, so it divides out from the two columns beside it. The benchmark's ownaverage_reduction_ratioaverages the five per-question ratios instead, which always reads higher; those per-question figures are indocs/REPRODUCING.md.The
code-review-graphrow is a snapshot, not a current measurement. The repository has grown since84bde354, 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.
| 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 |
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.
| Repo | Nodes | Edges | Embeddings |
|---|---|---|---|
| fastapi | 6,287 | 32,036 | 5,159 |
| express | 1,990 | 19,492 | 1,849 |
| gin | 1,589 | 17,237 | 1,491 |
| code-review-graph | 1,446 | 9,094 | 1,354 |
| flask | 1,415 | 8,259 | 1,329 |
| httpx | 1,263 | 8,236 | 1,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>
| Feature | Details |
|---|---|
| Incremental updates | Re-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 support | See Language coverage above. |
| Framework-aware PHP parsing | Repository-bounded Composer PSR-4 imports, Blade template references, evidence-gated Laravel Route-to-controller and Eloquent relationship edges |
| Framework-aware Java parsing | Spring 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 analysis | Which functions, classes and files are likely affected by a change |
| Auto-update hooks | Editor hooks, git hooks (before each commit, after checkout, merge and rebase) and watch mode update the graph as you work |
| Semantic search | Optional vector embeddings via sentence-transformers, Google Gemini, MiniMax, Voyage AI, or any OpenAI-compatible endpoint |
| Interactive visualisation | D3.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 detection | Most-connected nodes and chokepoints (betweenness centrality) |
| Surprise scoring | Unexpected coupling: cross-community, cross-language, peripheral-to-hub edges |
| Knowledge gap analysis | Isolated nodes, untested hotspots, thin communities, import cycles |
| Suggested questions | Review questions generated from bridges, hubs and surprises |
| Edge confidence | Four tiers (EXTRACTED, INFERRED, AMBIGUOUS, UNRESOLVED) with float scores on edges, so a call the graph could not bind says so |
| Graph traversal | BFS/DFS from any node with configurable depth and token budget; path_to shows how one symbol reaches another and names the weakest hop |
| Export formats | GraphML, Neo4j Cypher, Obsidian vault, JSON, and SVG (SVG needs matplotlib from the eval extra) |
| Token benchmarking | code_review_graph/token_benchmark.py measures whole-corpus tokens against graph query tokens per question |
| Estimated context savings | context_savings metadata (estimated, saved_tokens, saved_percent) on review, impact, detect-changes and architecture responses |
| Community auto-split | Communities above 25% of the graph are split recursively with Leiden |
| Execution flows | Call chains from entry points, sorted by weighted criticality |
| Community detection | Leiden clustering with resolution scaled to graph size |
| Architecture overview | Community-based architecture map with coupling warnings |
| Risk-scored reviews | detect_changes maps diffs to affected functions, YAML and JSON paths, flows and test gaps |
| Custom languages | New languages via .code-review-graph/languages.toml, no fork 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 | Markdown wiki from community structure |
| Multi-repo registry | Register several repos and search across them |
| Multi-repo daemon | crg-daemon watches several repos as child processes, with health checks and restart |
| MCP prompts | 5 workflow templates: review, architecture, debug, onboard, pre-merge |
| Full-text search | FTS5 hybrid search combining keyword and vector similarity |
| Local storage | One 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.
| Skill | Description |
|---|---|
explore-codebase | Navigate and understand codebase structure using the knowledge graph |
review-changes | Perform a structured code review using change detection and impact |
debug-issue | Systematically debug issues using graph-powered code navigation |
refactor-safely | Plan 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 worktreeThis 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 │
└──────────────────────────────────────────────────────────────┘| Command | What it does | When to use |
|---|---|---|
detect-changes --brief | Read-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 --brief | Re-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 watchersAlso 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.
| 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 | Compact context (~100 tokens); call this first |
get_impact_radius_tool | Blast radius of changed files; detail_level="compact" gives ranked file paths |
get_review_context_tool | Review context with structural summary |
query_graph_tool | Callers, callees, tests, imports, inheritance, and path_to between two symbols |
traverse_graph_tool | BFS/DFS traversal from any node with a 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 | Functions, classes or files above a line-count threshold |
list_flows_tool | Execution flows sorted by criticality |
get_flow_tool | One execution flow |
get_affected_flows_tool | Flows affected by changed files |
list_communities_tool | Detected code communities |
get_community_tool | One community |
get_architecture_overview_tool | Architecture overview from community structure |
detect_changes_tool | Risk-scored change impact analysis |
get_hub_nodes_tool | Most-connected nodes |
get_bridge_nodes_tool | Chokepoints by betweenness centrality |
get_knowledge_gaps_tool | Structural weaknesses, untested hotspots and import cycles |
get_surprising_connections_tool | Unexpected cross-community coupling |
get_suggested_questions_tool | Review questions generated from the analysis |
refactor_tool | Rename preview, dead code detection, suggestions |
apply_refactor_tool | Apply a previously previewed refactoring |
generate_wiki_tool | Markdown wiki from communities |
get_wiki_page_tool | One wiki page |
list_repos_tool | Registered repositories |
cross_repo_search_tool | Search 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 dependenciesEnvironment Variables
| Variable | Description | Default |
|---|---|---|
CRG_GIT_TIMEOUT | Timeout in seconds for Git operations (build, update, watch) | 30 |
CRG_DISCOVERY_TIMEOUT | Timeout 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_DIR | Directory for graph databases and generated artefacts | - |
CRG_HOOK_WORKTREES | Set to 1 to let the generated git hooks run in linked git worktrees | - |
CRG_EMBEDDING_MODEL | Default model for local vector embeddings | all-MiniLM-L6-v2 |
CRG_ACCEPT_CLOUD_EMBEDDINGS | Set to 1 to suppress the cloud embedding egress warning | - |
CRG_ALLOW_REMOTE_CODE | Allow HuggingFace models that require trust_remote_code=True | 0 |
CRG_VECTOR_CACHE | Set 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_NODES | Maximum nodes 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_DELETION_FILES | Maximum files whose old version is read in one change report, to name the functions and classes a diff deleted | 100 |
CRG_MAX_CHANGED_YAML_PATHS | Maximum changed YAML paths analysed in one change report, and separately the same number of JSON paths | 200 |
CRG_MAX_TRANSITIVE_FRONTIER | Maximum 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 answer | 50 |
CRG_CALLER_TEST_ROUTE_DEPTH | Hops to walk up incoming CALLS when looking for a tested caller of an untested changed symbol. Reported as a note, never as coverage | 2 |
CRG_CALLER_TEST_ROUTE_MAX_CALLERS | Maximum callers examined while looking for that tested caller | 500 |
CRG_TOOL_TIMEOUT | Timeout in seconds for read-only MCP tools (0 disables). Does not bound the tools that write: build, postprocess, embed, wiki and apply-refactor | 0 |
CRG_CHURN_WINDOW_DAYS | Window for detect-changes --churn commit counts | 90 |
CRG_CONTEXT_HISTORY | Set 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_SEED | Seed for Leiden community detection | 42 |
CRG_RECURSE_SUBMODULES | Include git submodules when set to 1, true or yes | - |
CRG_FOLLOW_SYMLINKS | Follow 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_FILES | Most files taken through followed symlinked folders | 10000 |
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 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 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 for OpenAI-compatible embeddings | - |
CRG_OPENAI_DIMENSION | Pin the embedding dimension (v3 models support reduction) | - |
CRG_OPENAI_BATCH_SIZE | Batch size for OpenAI-compatible requests | 100 |
NO_COLOR | Disable ANSI colours in the terminal | - |
CRG_SERIAL_PARSE | Set to 1 to disable parallel parsing (for debugging) | - |
CRG_ENABLE_ENRICHMENT | Set 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 --enrich | off |
CRG_DISABLE_ENRICHMENT | Set 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 limitsThe 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-3Model selection. Avoid
-preview,-betaor-expmodel 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 astext-embedding-3-small/text-embedding-3-large(OpenAI) orgemini-embedding-001(native Gemini provider, which needsGOOGLE_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 buildbefore re-embedding. Routine builds never refresh embeddings; to refresh after a build, pass both--embedding-providerand--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 serveThe 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>
vs LSP / language servers: one persistent cross-language graph instead of per-language daemons; LSP stays more precise per symbol.
vs RAG / embeddings: structural edges parsed from the AST, not similarity chunks; embeddings are optional and only assist search.
vs grep / agentic search: grep wins on one-hop lookups; the graph wins on multi-hop questions (impact radius, callers-of-callers, tests-for, affected flows).
When not to use it: small repos, trivial single-file diffs, one-off questions.
Does it phone home?: no telemetry; cloud embeddings are opt-in.
How do I verify it is working?:
status,detect-changes --brief,/mcp.
</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.
Run the same command from a terminal outside the editor, such as Terminal.app.
Install from a checkout with uv, which uses different download machinery:
cd /path/to/code-review-graph uv tool install . --forceFor development in a clone, use
uv syncanduv 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>
CHANGELOG.md
Changelog
[Unreleased]
[2.4.0] - 2026-11-10
Two new platforms, MiMo Code and DeepSeek Harness, so install now supports
18. HTML, XSLT, Liquid, JSON and plain YAML files are indexed, and so are
.cjs and .kts files, which every build used to skip. The graph links calls,
imports and tests more carefully: a call it could not check no longer looks
like a real link, every call edge says how sure it is, calls to inherited
methods and imports of workspace packages are followed, and tests_for finds
Java, Ruby and PHP tests. The graph schema moves from 13 to 15. Several new
settings are off unless you turn them on. Read Upgrade notes at the end of
this section before you upgrade.
Added
DeepSeek Harness is a supported platform (#1100, #1101, thanks @sumingwang233).
install --platform dshadds oneinsertentry for DSH's own MCP client to~/.dsh/cordis.patch.yml(or$DSH_HOME/cordis.patch.yml), which every DSH profile reads, and the graph block toAGENTS.md. Comments,!!jsvalues and other entries in the file are kept byte for byte, anduninstalltakes the entry out again. Plaininstallpicks DSH up when its home folder exists.HTML pages (
.html,.htm) are indexed (#521). They used to give nothing at all. Inline scripts are parsed as JavaScript or TypeScript with the page's own line numbers, and a<script src>becomes an import of the local file, soimporters_oflists the pages that load a script. Anonclickthat calls a function on the page becomes a call. Adiv,sectionorarticlewith an id becomes a newDocSectionnode, which you can find by its anchor or its first heading. The body text is not indexed. Style blocks and heading anchors stay out, and pages over 2 MB keep only their file node. Runbuildto pick up pages in a graph you already have.XSLT stylesheets (
.xsl,.xslt) are indexed (#1052). Each template andxsl:functionbecomes aFunctionnode, named by itsnameor written asmatch=para mode=toc.xsl:importandxsl:includebecome imports, andxsl:call-templatebecomes a call.xsl:apply-templatesis chosen from the input document when the stylesheet runs, so it becomes a call only where itsselectnames a step or itsmodenarrows the choice. Where neither does, no edge is added anddynamic_apply_counton the template says so. A template with amatch, named or not, is never reported as dead code. Runbuildto pick up stylesheets in a graph you already have.CI now installs every direct dependency at its declared minimum, on each supported Python, and runs the test suite there.
tests/test_dependency_floors.pykeepspyproject.tomland the pinned list in step in both directions. (#896)A
Dockerfilethat works with Docker and Podman, so the tool can run from a container. It installs this source, includes Git, and runs as a non-root user. Mount a repository at/workspace; the graph stays in that repository. It stores/workspacepaths, so use the container for later commands or rebuild on the host. No image is published. See the README. (#1037)Liquid templates (
.liquid) are indexed. A template getsVariablenodes forassignandOutputnodes for{{ ... }}, andrenderandincludelink to thesnippets/<name>.liquidfile, soimporters_ofanswers who renders a snippet and it counts towards impact.raw,comment,doc,schema,javascriptandstylesheetblocks,#comments and quoted text are not read as code. Names hold no line number, so an edit above them does not rename them, and a repeated name is one node that lists every line.Outputnodes are found by search only withkind="Output": on Shopify's Dawn theme they otherwise took all ten top hits for "product". (#878, #348)get_impact_radius_tool(detail_level="compact")andcode-review-graph impact --detail-level compactreturn the blast radius as ranked repository-relative file paths and counts, with no node or edge records.max_files(default 25, at most 200) limits each file list without shrinking the traversal, and changed files the graph has no nodes for are listed inunmatched_files. The CLI can also ask for--detail-level minimal.build --enrichandupdate --enrichresolve Python method calls on a local variable with Jedi, from theenrichmentextra (PR #823). Fors = make()thens.authenticate(), the CALLS edge to the bare nameauthenticatethen namessvc.py::OldService.authenticate, socallers_ofand the impact radius find the call. Jedi was a declared extra that no build ever ran. It is off unless you ask for it, with the flag orCRG_ENABLE_ENRICHMENT=1(for hooks, the watcher and MCP builds), because it is slow: on an 88-file Python repository a full build went from about 5 to 32 seconds and resolved 43 more calls. Having the extra installed, for example through[all], changes nothing on its own: the graph and the build time stay the same.CRG_DISABLE_ENRICHMENT=1keeps it off even when asked for. With it on,updatescans only the Python files it re-parses, plus their dependents, whose edges it rewrites, and a target with no node in the graph is never written.get_knowledge_gaps_toolnow reportsimport_cycles: groups of files that import each other, with one loop written out and its import lines.load_timesays whether the loop can fail as the files load;falsemeans every loop has an import that runs late: inside a function, a dynamicimport()or a Rubyautoload. To tell these apart, import edges now recordextra.import_context:type_only,deferredorparent_package. Runcode-review-graph buildonce to get it on an existing graph. Part of #434.query_graph_toolhas a newpath_topattern that answers how one symbol or file reaches another:pattern="path_to", target="A", to="B", orcode-review-graph query path_to A --to B. Each hop says how sure it is, and the answer names the weakest hop. A longer route through bound edges beats a shorter one that guesses at a name.code-review-graph install --steeringadds an opt-in Claude Code hook that puts graph context beside each search and read.enrichhas existed for a long time, butinstallnever wrote the hook that runs it. It is off by default because it changes what the model sees on every such call.--no-steeringremoves only that hook, anduninstallremoves it too. A failed lookup never stops the tool call, and a broken graph or odd input now leavesenrichsilent.serve --http --multi-reporuns one HTTP server for every repository in the registry, so the embedding model is loaded once (#607). Each tool call must name a registered repository inrepo_root. A call with norepo_root, a relative path or a path that is not registered is refused with a clear message. Before, a shared HTTP server quietly answered such a call from--repoor its own working directory, which could be a repository the client never named. The registry is read on every call, soregisterandunregistertake effect without a restart. The flag needs--httpand cannot be combined with--repo,--auto-watch,--multi-worktreeorCRG_DATA_DIR; the daemon still watches several repositories. stdio and plainserve --httpare unchanged.An opt-in local log of context savings, and a
context-historycommand to read it (#813). SetCRG_CONTEXT_HISTORY=1and every response that carries acontext_savingsestimate (the impact, review-context, detect-changes and compact architecture tools, and thedetect-changes,update --brief,impactandarchitecturecommands) adds one JSON line tocontext-history.jsonlnext tograph.db: the time, the tool, the estimated full, graph and saved tokens, and where the call has themdetail_level, the changed-file count and the depth. No file names, paths, symbol names or source text are written, nothing is sent anywhere, and the log rolls over at 1 MiB, so it never takes much more than 2 MiB.code-review-graph context-historyprints it per UTC day or, with--by tool, per tool, with--days Nand--json. Off by default; nothing is written in read-only mode.The GitHub Action has an opt-in
detailsinput that adds graph context to the review comment (#882). It takes a list of sections:riskshows each term of the top symbols' risk scores, so a reader sees why a score moved and not only that it did;communities,hubsandcouplingshow the communities the change touches, its most connected symbols and the links that cross a community or language boundary;impactshows its impact radius and direct callers;gapsranks the test gaps by what depends on them;graphgives the graph's size, community count, the method that found the communities (Leiden or file-based grouping), and versions. The block is folded away, shows at most 5 rows per list, is capped at 12,000 bytes with a note saying what was cut, and never takes room from the report. Left empty, the default, the Action runs the same commands and posts the same comment byte for byte. The data comes from the newcode-review-graph detect-changes --details <sections>option, which adds adetailsobject to the JSON and nothing else. Building communities now also records the method used, in graph metadata.Symlinked folders can be followed, as an opt-in (#269). Pass
--follow-symlinkstobuild,updateorwatch, or setCRG_FOLLOW_SYMLINKS=1; it is off by default, so nothing changes unless you ask. A project folder of links to code kept elsewhere (inputs/app -> ~/src/app) now builds a graph instead of an empty one. Links to folders outside the repository are followed too, because you made the link and asked for it, and their files are stored under the link's path inside the repository, never the outside path. A file inside the repository keeps its own path, so a link to a folder the graph already holds adds no copies. A link to a folder that holds it, or holds the repository, is skipped and logged, and each real folder is read once, so links cannot loop. At mostCRG_MAX_FOLLOWED_FILESfiles (default 10,000) are taken, and a warning says when the cap is reached.updateandwatchadd and drop these files as links and the files behind them come and go, matching a full build;watchalso watches each followed folder outside the repository. Search, review context, flows, rename andforgetwork on these files through their in-repo path, and imports between them point there too. Any other path outside the repository is still refused, and the review context and flow source snippets no longer read one either. Symlinked files are still never followed.Python modules that replace themselves in
sys.modulesare followed to the module they install. A compatibility shim such assys.modules[__name__] = importlib.import_module("app.services.billing")hands importersapp/services/billing.py, but the graph stopped at the shim:import billingtargeted the shim file,billing.charge(...)and theTESTED_BYedge from a test calling it stayed unresolved becausechargeis not defined in the shim, andfrom billing import chargenamed a node that does not exist. The import edge, module-receiver calls andfrom shim import namenow resolve to the real module, through chains of shims and through re-exports to the defining file. Only the literal, module-level form is followed; a computed or relative module name, a swap inside a block, or a target outside the repository leaves the file as the module. Because importers now depend on the real module, editing only the shim does not re-parse them onupdate; runbuildafter re-pointing a shim.semantic_search_nodes_toolcan return the code with the hits.include_source=Trueattaches each hit's source lines, andexpand_neighbors=Truealso attaches the direct callers and callees of the top hits with their source.max_source_linescaps each snippet, one line budget caps the whole response, cut-off snippets are markedsource_truncated, and a stored path that resolves outside the repository is never read. Source is not attached whendetail_level="minimal". Without this an agent got names and line numbers and opened the same files by hand, so the search cost tokens on top of the reads instead of replacing them.The README and
docs/REPRODUCING.mdpublish the first canonicalagent_baselinecapture (2026-09-22): against a grep-and-read-top-3-files baseline, the graph answer is a median 6.65x smaller across 18 questions on 6 repositories (range 4.2x to 72.6x). It predates the test-file ranking below, so a run today gives different numbers until it is redone. The per-repo CSVs are inevaluate/results/; the 2026-05-25impact_accuracyCSVs are removed, as the 2026-08-02 captures replace them.Codex installs a global CRG skill with MCP-first guidance and a read-only CLI fallback (#841).
installalso writespost-checkout,post-mergeandpost-rewritegit hooks that runupdate, so the graph follows a branch switch, a merge or agit pull(merge or rebase mode) instead of waiting for the next commit.post-checkoutruns on branch checkouts only and skips the checkout a rebase makes before replaying local commits;post-rewriteupdates once the rebase has finished and ignores--amend, whichpre-commitalready covers. A rebase with nothing to replay never runspost-rewrite, so it updates at that checkout. An existing hook in a language other than shell is left alone. Each block is marked, appended to an existing hook, skipped in linked worktrees unlessCRG_HOOK_WORKTREES=1, and removed byuninstall.--no-hooksskips them along withpre-commit.Oracle PL/SQL files get structural parsing: packages, procedures, functions and the calls between them (#785).
Robot Framework
.robotand.resourcefiles are indexed (#967, #963).Static Laravel routes produce
Endpointnodes andHANDLESedges at parse time (#1072, part of #851).Spring AOP advice reaches its targets through
@annotationandexecutionpointcuts (#916, #592).servehas an opt-in mode for parallel graph access across Git worktrees (#1063).Read-only MCP tools carry
readOnlyHint, so clients can skip the approval prompt (#1124, #1123).The troubleshooting guide explains the local-checkout workaround for Zed SSH projects (#1130).
.jsonfiles are indexed; before, they gave no nodes at all (part of #436). Each key and array shape becomes aJsonPathnode joined byCONTAINSedges, in the same value-free shape as generic YAML: array items share one[*]path, and values never enter the graph. Comments and trailing commas, as intsconfig.json, are fine. JSON is often data, so a lot stays out: translation files keep only a file node by the rule YAML uses (#1150), and so do test files, files underdocs,examples,samples,demoordatafolders, and files over 256 KB. A file stops at 1,000 paths, and an object with more than 200 keys shares one.*path.package-lock.json,npm-shrinkwrap.json,*.lock.json,project.assets.jsonand*.min.jsonare ignored. On vega, 473 JSON files add 1,228 paths, mostly its 37package.jsonfiles; without the folder rule,docs/alone would have added 12,725. Search ranks JSON below code unless you ask for it withkind="JsonPath", a$path or a.jsonname, and JSON rows no longer take the places code needs: sixtranslation.jsonfiles had pushedappWithTranslationout of the hits for "translation" on next-i18next.detect_changes_toolreportschanged_json_paths. A JSON or YAML key escaped to a lone surrogate, such as"\ud800", is left out; before, a YAML one stopped the whole build. Runbuildto add JSON to a graph you already have.MiMo Code is a supported platform (#593, PR #688 by @Aditya-Tripuraneni).
install --platform mimoadds the server undermcpin.mimocode/mimocode.jsoncif that file exists, otherwise in.mimocode/mimocode.json, and writes the graph instructions toAGENTS.md. Other servers, settings and comments are kept. MiMo reads both files, souninstallremoves the entry from both.
Changed
Dependency minimums are raised to versions that are tested and that install on every supported Python:
mcp>=1.29.0,anyio>=4.8.0,tree-sitter>=0.26.0,tree-sitter-language-pack>=0.13.0andpyyaml>=6.0.2. PyYAML 6.0 fails to build on Python 3.12 and newer, andmcp1.29 andfastmcp3.2.4 already need a neweranyiothan the old minimum of 4.0. Ranges stay ranges; pin exact versions in your own lockfile if you need them. (#896, #265)stagingis no longer promoted totestingautomatically. TheAuto promoteworkflow and its script are gone, so a person reviews and merges every promotion, and a test fails if any workflow merges a pull request.Generic
.ymland.yamlfiles are indexed asYamlPathnodes that hold key paths and value types, never values.detect_changesreports them aschanged_yaml_paths, bounded bymax_results; the analysis behind it takes at mostCRG_MAX_CHANGED_YAML_PATHS(200) YAML paths, and as many JSON paths.pnpm-lock.yamlis ignored at any depth.Search ranks test code below production code unless the query asks about tests (
kind="Test", or a word such as "test", "spec", "fixture" or "mock"). A two-line test stub that names a symbol used to score as well as the implementation and could outrank it.The installed instruction block tells agents to answer from
include_source=True, expand_neighbors=Trueand open a file only when a snippet is cut off. It still says not to change code from graph output alone. Earlier blocks stay recognised for upgrade and uninstall.The MCP server instructions now route by question type instead of introducing the server as a code-review tool. With deferred tool loading an agent sees only the tool names and these instructions, so "for code reviews" kept it on Grep for callers, importers and covering tests everywhere else. The new text maps each relationship question to its tool and leaves plain-text search to Grep. Measured with Claude Code on one TypeScript monorepo: across 8 ordinary tasks that never mention the graph (implement, review, brainstorm, plan), the graph was used in 1 run before and 6 after; plain-text questions stayed on Grep (0 of 4 moved).
tests/test_server_instructions.pykeeps every tool and query pattern the text names registered.Stored vectors are scored with numpy when it is installed, about 20 times faster, and numpy stays optional (#1090, #1089).
CRG_VECTOR_CACHE=1keeps the decoded embedding vectors in memory between searches instead of reading them all back from SQLite on every query. A search over 42,000 vectors of 4,096 dimensions goes from 2.3 s to 0.40 s once the cache is warm, with the same ranking. It is off by default because it costs RAM and holds a read connection open for the life of the process, and on Windows that keepsgraph.dbfrom being deleted or replaced.find_dead_codecollects its edge evidence once, not once per function (#1082, #982).Plain C headers skip the C++ parse through a prefilter (#876, #802).
The dev dependency range allows pytest 9 (#1140).
Builds and updates use less memory and fewer database commits. The MCP server loads its tool modules on first use, so an idle server holds about 89 MB instead of 144 MB; new flows, flow members and summary rows are written in one transaction each; the files that depend on a changed file come from one query; and community detection no longer decodes every edge's metadata. The graph itself is unchanged (#1139).
fastmcp 4 is allowed (
fastmcp>=3.2.4,<5) anduv.lockpins 4.1.6 (#970). fastmcp 4 brings the mcp SDK 2.x, so the server now answersserver/discoverand serves clients on the 2026-07-28 protocol, which issue #919 found it could not do. Older clients still use theinitializehandshake as before. The prompts importMessagefromfastmcp.prompts, which works on fastmcp 3 and 4; the oldfastmcp.prompts.promptpath is gone in 4 and stopped the server from starting.The
build-graph,review-deltaandreview-prskills are written from one source,DOCUMENTARY_SKILLSinskills.py.scripts/generate_documentary_skills.pyrewrites them, and a test fails if a file is edited by hand (#822).install --gitignoreadds the files and folders that run created to a marked block in the repository's.gitignore(#471). It is off by default, because.claude/,.mcp.jsonandCLAUDE.mdcan be team files. Only paths that did not exist before the run and that git does not track are listed, so a file you wrote or committed is never ignored. A path some rule already ignores is not repeated. Running install again merges into the same block,--dry-runshows the lines it would add, anduninstallremoves the block and nothing else, so.gitignorecomes back byte for byte. The.code-review-graph/entry keeps its own handling.
Fixed
A fresh clone, set up as CONTRIBUTING.md says, now passes and stays clean:
Installing for any platform inside this checkout no longer leaves untracked files. The root
.gitignorenow lists what Cursor, Windsurf, Gemini CLI, Kiro, MiMo, OpenCode, CodeBuddy and Copilot write there, andtests/test_install_outputs_are_ignored.pyfails if a platform starts writing a new path.tests/test_token_budget.pyno longer fails when the temp folder has a long path (the macOS default, or a long--basetemp). Answers hold absolute paths, so each one is now measured as if the test repo lived at the path CI gives it. No budget changed.One parser test failed on Python 3.14, which
uv syncpicks when it is the newest Python installed. Python 3.14 parses the long expression the test used to breakast.parse, so the test now forces the failure on any Python that accepts it. The rest of the suite already passed on 3.14.
The release gate's corpus check expects this release's numbers. Its recorded baselines dated from before the new YAML, JSON, HTML and Liquid support, Ruby and Java test finding, and the C# and nested-scope identity changes, so 3 of its 8 repositories reported moves that were on purpose. The time budgets are unchanged.
The determinism check reorders the file list again. Since
buildstarted reading the file list through a new function, the check's reorder never reached it, so the three file-order tests built the same order twice: two passed for no reason and the known node-id bug looked fixed, which failed the release gate. The check now reorders the functionbuilduses, and fails if a reordered build never read the reordered list.The docs match the code again.
docs/, the referenceget_docs_section_toolserves,CONTRIBUTING.md,SECURITY.md,AGENTS.md, the Copilot instructions, two skills and the VS Code README now cover what the last two release trains added: all 19 query patterns, the search source options, the write lock and exit75, the new git and steering hooks, untracked files, Liquid and the other new node kinds, confidence tiers and schema v15. They also drop claims the code no longer backs.The read-only query script in the bundled skill (
crg_readonly.py) now takespath_towith--to, so it offers every pattern the CLIquerycommand accepts. It stops before running the CLI whenpath_tohas no--to. A test now fails if the script's list drifts fromtools/query.pyor the CLI again; the patterns the CLI does not take yet are listed there with the reason.The VS Code
codeReviewGraph.cliPathsetting no longer talks about a bundled version. The extension does not include the CLI; left empty, it runscode-review-graphfrom yourPATH(#369).install --dry-runshows what each file would become, andinstallwrites nothing until you have answered its question (#173). The dry run used to list file names only, and a real install wrote the MCP config before it asked aboutCLAUDE.mdand the other instruction files. Every install write (MCP configs,.gitignore, skills, hooks, git hooks, the OpenCode plugin, the instruction files, the--steeringhook and the--gitignoreblock) now goes into a plan first.--dry-runprints that plan as one unified diff per file, a new file as its full content, cut at 60 lines a file with a note saying how many lines were left out, and writes nothing. A real install lists the files it will change, asks about the instruction files, and only then writes exactly the plan it showed. Ctrl-C at the question stops the install with nothing written, and a file whose content or permissions change while the question is open is left alone and reported. A file reached by two names, such asCLAUDE.mdlinked toAGENTS.md, is planned and written once. Commented JSONC configs and hook settings are previewed and written through the same splice, so their comments are kept. A hook settings file install will leave alone is named in the dry run too. Saying no to the question also keeps the instruction files out of the--gitignoreblock.query_graph_toolno longer answers "ambiguous" when a full name has one real symbol. The name search splits on underscores, sohybrid_searchalso matched everytest_hybrid_search_*and came back with 34 candidates and no answer, at about 5,200 tokens even withdetail_level="minimal". A full name now means that name: a test or test helper with the same name gives way to the real symbol, and the summary says how many it skipped. Two real symbols with the name are still ambiguous. The same query now costs about 450 tokens and answers it. An ambiguous answer atminimalis five short rows sent once.callees_oflists calls bound to a real node before bare-name guesses, so a fewlencalls can no longer fill the window and hide the real callee.installno longer wipes a hook settings file it cannot read as strict JSON. One comment or trailing comma in.claude/settings.json,.qoder/settings.json,.codebuddy/settings.json,.gemini/settings.json,~/.codex/hooks.jsonor~/.cursor/hooks.jsonmade the reader fail, and the file was overwritten with nothing but our hooks and no backup, so every setting in it was lost. These files are now read as JSONC, only what changed is written into a commented file, and every comment stays with the setting it describes. A file that cannot be merged, such as broken JSON or ahookssetting of the wrong shape, is left byte for byte as it was and named in the output, andinstallno longer says it installed those hooks. So is a file with a/*comment that never closes, where the hooks would have landed inside the comment and never run. A refused Gemini CLI install no longer rewrites the hook scripts in.gemini/hooks/. Cursor's file now gets a.baklike the others.Adding a member to a commented JSONC config, as
installdoes for an MCP entry, no longer moves a trailing comment onto the new member or drops a comment from an empty object..cjsfiles are indexed as JavaScript and.ktsfiles as Kotlin, as.mjsand.ktalready were. Every build skipped them, so their functions, imports and calls were missing from the graph and read as if they did not exist.crg-daemon stopclears the health and state files of watchers that were killed outright. A killed watcher cannot clear its own, and on Windows that is every watcher, becausestopends the daemon withTerminateProcessand the Job Object takes the children with it. The file left behind read as a stalled watcher forever.stopalso now stops any live watcher when it finds the daemon already gone. Health files for watchers the daemon never ran are left alone.Graphs no longer hold data nothing reads (#1059). Schema v15 drops
community_summaries,flow_snapshotsandrisk_index, which every full postprocess rewrote and nothing read, and whose rows kept pointing at deleted nodes. It also dropsidx_nodes_qualified,idx_edges_sourceandidx_edges_target, as other indexes cover them. A new graph of this repository is 17% smaller, 252 MB down to 210 MB, with the same answers at the same speed. An upgraded graph keeps its file size and reuses the freed space for later writes; to shrink the file, delete it and rebuild, or runVACUUM. Older releases refuse a v15 graph, as with any schema change. Build results no longer carrysummaries_computedor thesummaries_stiming.find_large_functions_tool,semantic_search_nodes_toolandtraverse_graph_toolnow have hard ceilings. At a hugelimitortoken_budgetthey returned up to 1.8M tokens on this repository, and now return under 25,000.limitandtoken_budgetreject zero, negatives and booleans. The ceilings also bound the work: search ranks three times what it shows, the large-functions query fetches only what it shows and counts the rest in SQL, and the walk stops when the budget ends.find_large_functions_toolgainsdetail_level, andminimalcosts less thanstandard.result_counton these tools now means the count before the cut, as onquery_graph_tool. (#895, #888)CRG_MAX_IMPACT_NODESnow reachesget_impact_radius_tool. A literal default of 500 shadowed it, so the setting did nothing through MCP. (#888)traverse_graph_toolindfsmode no longer misses what lies past a node it first reached at the depth limit, then reached again by a shorter path.Imports through an npm alias resolve to the local package (#343, PR #880). With
"sharedLib": "npm:@scope/shared@^1.0.0"in an app'spackage.json,import x from 'sharedLib/some/module'was stored as a bare string, soimporters_ofand the impact radius found none of the apps that use the shared package. Now, when one package in the repository is named@scope/shared, the import reaches its file. Entry points are read as for a workspace member (exports, thensource,moduleandmain, withdist/tried undersrc/). A wrong edge is worse than a missing one, so the import stays bare when the nearestpackage.jsonthat names the dependency gives it an ordinary version, when the local package'sversionis outside the alias range, when two local packages share the name, when thatpackage.jsoncannot be read, or when the package or file is one the build ignores. tsconfigpathsstill win. An alias also wins over a workspace member with the same name, because npm installs the aliased package under it.updatedoes not re-parse importers when only apackage.jsonchanges; runbuildafter changing an alias.A file added by
updatenow gets a community, as it does afterbuild. It was in no community, so the update saw nothing affected, skipped community detection and left the file's nodes without one until the next full build (#1058, PR #1122 by @jay23606).On Windows,
buildandupdatestarted with stdin on theNULdevice (subprocess.DEVNULL, Node'sstdio: "ignore") parse with threads.NULpassesisatty(), so they used a process pool, and a caller that stopped the run, such as a pre-commit hook timeout, left up to 8 worker processes behind each time. Only a real console now keeps the process pool (#1163, PR #1166 by @xOscarCodes).The impact-accuracy benchmark's co-change mode no longer reports
predicted_files = 0on every commit (PR #823).git diffgives repo-relative paths and the graph stores absolute ones under the resolved root, so the seed never matched a node. The graph-derived ground truth now counts real neighbour files too, andseed_filestays relative to the repository.Members of nested classes keep the full scope chain in every language that did not already (#934): in Java, Kotlin, Swift, Scala and others,
Alpha.Same.runandBeta.Same.runare two nodes instead of one, a nested class is contained by its enclosing class, and three levels keep all three. Local calls stay inside their own class, and a JVM import of a nested class (import app.Outer.Inner) targetsOuter.Inner. On gson this leaves no danglingCONTAINSedges (700 before) and 14 danglingCALLS(154 before). Existing graphs reparse their files with nested classes, and the callers of those members, once on the next update.C# declarations no longer collapse across namespaces, and calls no longer bind to a type the caller cannot name. Qualified names now carry the namespace and the complete containing-type path (
/repo/Handlers.cs::App.Report.ExportHandler.Run), and a dedicated namespace-aware pass binds each call from its own lexical scope:usingdirectives are read at the byte offset of the namespace body that declares them, so a reopened or file-scoped body cannot leak its imports elsewhere;global::and namespace aliases keep their meaning;Alias::Typesearches only namespace aliases; and an unqualified call reaches a static method in an enclosing type without crossing a nearer method group. File co-location and short-name uniqueness are no longer treated as visibility evidence, so an ambiguous or unsupported reference stays unresolved instead of pointing at the wrong declaration. Overload selection, generic binding (#943), inherited members andusing staticremain outside this change (#946, with the nested-type identity needed by #934 and #937).C# bindings are now re-evaluated on every update rather than fixed at first parse. Each call keeps its raw reference, so a binding is revoked when the usings or declarations behind it change,
TESTED_BYmirrors move with their call, and deleting a callee returns incoming calls to raw references so recreating the declaration binds them again. A binding change marks flows dirty and the next full postprocess retraces them, which keeps callers and entry points outside the parsed files correct (#946).C# generic declarations are keyed by arity, so a constructed receiver reaches the declaration it names —
I<int>binds toI<T>,Ibinds to the separateI, andPair<int>binds to neither when onlyPair<K, V>is declared. The receiver's spelling is retained on the call and the arity is read from the syntax, so nested arguments and tuples count correctly. Type argument substitution, constraints and overload selection remain out of scope (#946).Existing C# graphs reparse once on the next incremental update to adopt the new identities; a file that fails to parse is retried on its own instead of holding the version gate open and repeating a full rebuild (#944). Graphs that carry embeddings should re-run
code-review-graph embedafterwards: the renamed C# nodes leave their previous vectors orphaned until a refresh purges them.Unresolved C# member-call targets retain the receiver spelling (for example,
Derived::Sharedinstead ofShared), socallers_ofcan return fewer matches for unsupported bindings, including inherited members and receiver types missing from the index (#946).A file inside a Python package no longer answers an absolute standard library import.
import jsonandimport typingin Flask's own files were linked toflask/json/__init__.pyandflask/typing.py, which made a false dependency and a false import cycle. Python 3 does not look in a package's own folder, so those imports now stay external. Atyping.pybeside a script, or at the top of asrc/folder, can still answer them.A multi-clause Elixir function is now one node that spans all its clauses. The graph keeps one node per name, so the last clause used to overwrite the others: a change to an earlier clause found no function at all.
parse_bytesnow merges them, from the first clause's first line to the last clause's last line, and keeps the first clause's parameters. Clauses of different arities share the node too, as the graph has no arity in its names, so another function can sit inside the span (#979).tests_forfinds Java and Ruby tests. On spring-petclinic, production symbols with a test went from 5 to 61, and on sidekiq from 198 to 443. Run a fullbuildto pick this up.Java: a test in
src/test/javacan now reachsrc/main/java, for an import and for a class in its own package, which needs no import.this.field.x()andvar x = new Cart()now give the type too.Ruby:
it "..." do,describe,before,letandtest "..." doare test nodes in test files. A call is matched to a method only when the file says what class it is made on: a constant, a constructor,described_class, or a name assigned from one of those. A constant is looked up in the modules around it first, as Ruby does, soCartinsidemodule ShopisShop::Cart.A call in a test on something of unknown type no longer matches a method by name alone, in either language. An inherited method is not linked yet, so
owner.setId()does not count forBaseEntity.setId.By name,
CartTests,CartTest,CartITandCartSpecnow count for the classCart. Free text in a test's title no longer counts, soit "tests json output"is not a test ofjson.A Ruby or Java method called only by its tests stays a flow entry point, as it was when those calls did not bind.
forget,embedandwatchnow obey the write lock, asbuild,updateandpostprocessalready did, so none of them can rewrite a graph that a build has only half written.forgetandembedtake it for the whole run and exit75if another writer holds it, saying that nothing was done; both take--wait.embed_graph_toolanswersstatus: contended.watchtakes it for each write and lets go between them, so a running watcher never stops a build. If the lock is held, a watcher keeps its changes and retries, and can still be stopped.A call the graph could not bind no longer looks like one it did (#984). The parser wrote a file path in front of a name it could not check, so
h.hashCode()becameOne.java::Helper.hashCodethoughHelperhas no such method,self.shared()named the subclass whensharedwas inherited, andVec::new()in a Cargo crate becamesrc/lib.rs::new. None of these names a node, yet each looked like a real link. Now no edge keeps a qualified name that has no node: it goes back to the plain name, keeps the qualified one inextra.claimed_endpoint, and returns to it if that node appears. A guess like this only counts for a method of the class the call named. In Rust, a type from outside the crate stays as written (Vec::new), which also letsWalkBuilder::newreach the real method;inner::bar()on an inline module lands on it; and on macOS and Windows the typeGlobis no longer read as the fileglob.rs. On ripgrep, 729 invented call targets and 341 invented test links went to 0, and real call links rose from 4,064 to 4,296 with none lost. spring-petclinic went from 2 to 0 and gin had none; their real links did not change.Every edge claimed confidence 1.0 whatever had happened to it. For
CALLS,REFERENCESandTESTED_BY,confidence_tieris nowEXTRACTED(1.0) when the source names a real node,INFERRED(0.8, or the resolver's own score) when a resolver chose it,AMBIGUOUS(0.4) when several nodes fit, andUNRESOLVED(0.2) when none does. On gin, 14,353 of 16,249 such edges had no node and still said 1.0. No schema change: the next build, or an update that changes a file, puts an existing graph right.callers_of,callees_ofandreferences_tosay when part of an answer is only name matches, in the existingconfidencefield: "3 of 8 call sites match by name only and may be wrong; resolution='direct' keeps the confirmed ones".tests_forno longer counts a test as covering a function because both call the same outside name.String::new()in a function and in a test linked the two, and so did the invented targets above: ripgrep'sdecompress.rs::addclaimed 10 tests, none of which reaches it, which lowered its risk score.A call to an inherited method through a subclass now finds the method (#984, building on the idea in #1036 by Anthony Ala).
this.handle()inLeaf extends Alpha,$this->dispatch()in a PHP test case andpet.getName()on a JavaPetthat inheritsgetNamenamed no node, socallers_ofandtests_foron the real method found nothing. The edge now goes to the ancestor that declares the method, asINFERRED, and itsTESTED_BYmirror goes with it. A class is known by its file, not its short name, and a parent counts only when the file, its imports, its Java package or its PHP namespace says which class it is; where Python could pick another ancestor, or a parent is outside the graph, the call stays unresolved. An update that adds or removes the method checks the edge again, so it matches a rebuild. A Java or TypeScript receiver's declared type now picks the class even when that class needs no import, and a call on another object no longer lands on the caller's own method when nothing says it is that class. On spring-petclinic, calls bound through a subclass rose from 2 to 86, on a TypeScript DDD example from 0 to 18 and on its PHP twin from 97 to 134, with no new invented targets; 28 self-calls the old rule made up were dropped or moved to the right class.The edit hook updates the graph of the repository that holds the edited file, not the one the session started in (#311). From a folder of sibling repos it updated nothing, from an outer repo it updated the outer graph and never the nested one, and from a main checkout it never updated a linked worktree's graph. The Claude Code, Qoder and CodeBuddy hooks (and the plugin
hooks/hooks.json) now pass the event JSON tohook-update, which reads the file path from it. That repository is updated if it already has a graph or is the session's own repository, which is still built on first edit as before; an edit anywhere else on disk changes no graph, and a submodule with no graph of its own passes the edit to its superproject. Without a file path in the event, the hook uses the event'scwdas before. Upgrade the package before runninginstallagain: an older CLI has nohook-update, so its edit hook does nothing (it still never fails the edit).installreplaces the hook earlier releases wrote, anduninstallremoves it, including the v2.3.4 to v2.3.6 form it used to leave behind.detect-changesanddetect_changes_toolnow report what a diff that only deletes code removed, and no longer miss it or blame the code around it. A hunk such as@@ -430,42 +429,0 @@adds no lines, so it named only the line before the gap: deleting a whole function reported 0 changed functions and risk 0.00, with nothing about the callers it left behind, and when the gap sat right after the previous function, that untouched function was listed as changed. The old version of the file is now read: a function or class that lost some lines is changed, and one that is gone is listed inremoved_symbolswith the callers, subclasses, importers and tests that still use it, and named in the summary ("Removed but still used: format_filename (risk 0.65, by FileError.init, File.convert, ...)"). The code around the gap is never marked. A removed symbol that is still used breaks its callers, so it is scored like a change (the flows it was in are traced again) and raises the overall risk, whichfail-on-riskreads; one nothing uses adds nothing. Deleting click'sechowith 20 callers now scores 0.65, where changing it scores 0.40. The PR comment gets a "Removed symbols" table. Up to 100 files are read per report (CRG_MAX_DELETION_FILES), and the summary says when a file could not be read. A deleted file is still not covered. Thanks to @decrypt-mm for reporting the case in #1167.TypeScript and JavaScript imports resolve against
compilerOptions.baseUrlwhen nopathspattern matches them, astscdoes. A tsconfig that setsbaseUrlwithoutpathsresolved no non-relative import at all: on a NestJS monorepo whose services importsrc/..., 1,849 import edges stayed bare strings,importers_offor one service found 14 of its 35 importers, and calls on its injected services fell back to name matching.A seeded
visualizeview (--seed-symbol,--seed-file,--seed-flow) reads only the part of the graph around its seed (#1145). It used to load every node, one query per file, and the whole edge table before cutting the view down to--max-nodes, so a two-node view of a large graph cost as much as the whole graph. It now reads a few columns of each node, which name lookup needs, counts edges from the indexes, and loads only the edges that touch the view. On this repository's graph a one-symbol view went from 380 SQL statements to 20 and from 120 MB to 13 MB at peak, with the same output byte for byte. A path query still reads the whole graph, since a route can run anywhere, and so does a view that reaches most of a large graph.Translation files no longer flood the graph. A translation file repeats the same keys once per language, and each key became a
YamlPathnode: on sidekiq,web/locales/*.ymlgave 3,147 of the 3,336YamlPathnodes. A YAML file in alocale,locales,i18n,l10nortranslationsfolder whose name or sub-folder is a language code, such asconfig/locales/devise.en.ymlorlocales/pt-BR/app.yml, now gets a file node only. Sidekiq's graph falls from 5,085 nodes to 1,935 and its database from 45 MB to 31 MB. A name alone is not enough, soconfig/en.ymland.github/workflows/it.ymlkeep their paths, and so doeslocales/deploy.yml. Runbuildto clear these nodes from a graph you already have;updateonly re-reads files that changed.semantic_search_nodes_toolandcross_repo_search_toolapplykindbefore they cut the hits, not after. Rows of another kind could fill the fetch limit, so asking for functions could return few or none when many existed:deadwithkind="Function"found 1 of them on sidekiq and now finds 9. If the exact reading of a query matches only other kinds, the wider reading is now tried.tests_forgives the same answer every time on the same graph. On a large class it follows at most 50 callees (CRG_MAX_TRANSITIVE_FRONTIER), and it picked them in an order that changed with each new process, so an MCP restart or a new CLI call gave a different list. On koel, 20 runs on one graph gaveSongServiceanything from 43 to 90 tests, and every run gave a different list. The 50 are now the first by name, with named callees before bare ones, which are never followed. Thedetect_changesrisk score counts the same tests, so it is steady too. Two builds of the same code already gave the same graph; only the query moved.Imports of sibling packages in npm, yarn and pnpm workspaces resolve to the package's source files (#1030). A specifier such as
@acme/sharedor@acme/sdk/browsernames a package-manager link, not acompilerOptions.pathsalias, so it was stored bare andimporters_of, the impact radius andreferences_tostopped at every package boundary. Calls through a package's barrel file now reach the defining file as well. The newWorkspaceResolverfinds the nearestpnpm-workspace.yamlorpackage.jsonwithworkspacesinside the repository, maps each member'snameto its directory, and readsexports(subpaths, conditions, fallback arrays and*patterns, with Node's key priority; anullentry or an unlisted subpath stays unresolved), thensource,module,main,typesandtypings. A target underdist/,build/,out/orlib/is tried undersrc/with source extensions first, because compiled output is normally absent from a checkout. tsconfigpathsstill win when both apply. Because a wrong edge is worse than a missing one, an unscoped name resolves only when the importing package declares it as a dependency, Node builtin names such aseventsnever resolve to a workspace member, a name two members share resolves to neither, probes are case-exact on every platform, and a target the build would not index keeps the bare specifier.The Claude Code plugin's hooks load again.
hooks/hooks.jsonheld a bare event map, which current Claude Code rejects with "hooks.json must havehooks(the hook matchers) ormodules(hooks modules), or both", so the SessionStart and PostToolUse hooks never ran. The events now sit under a top-level"hooks"key, the shape the plugins reference documents. The hooks carry the same guards as the onesinstallwrites, so a missingcode-review-graph, a folder outside Git or a failed command shows no hook error, and the update hook matchesEdit|Writeonly, so a Bash command can no longer start a full build in a repository with no graph.The OpenCode installer now detects the installed OpenCode major version and writes the matching plugin API: returned hooks for OpenCode 1.x and the
id+setupdefinition for OpenCode 2.x. Both templates use boundednode:child_processargv execution. The 1.x template runs from the OpenCode worktree/current directory instead of passing that directory as--repo, so starting OpenCode in a repository subdirectory still resolves the graph root. The 2.x template keeps its plugin package import type-only, so OpenCode 2.0.0–2.0.24 can load it without a runtime@opencode/plugindependency. This fixes the V2 loader errorPlugin must export a default definition with an id and an effect or setup functionwithout dropping 1.x support (#1038, #947). Each plugin instance runs one graph update at a time, and edits made during a run share one follow-up run, because an overlapping update reads the first run's in-progress marker as a crashed build and does a full rebuild.Codex hooks act on the repository named by the event's
cwdinstead of the directory Codex was launched from, so they cannot update the wrong graph. The generated hooks now run the newhook-updateandhook-statuscommands, which ignore a missing, relative or non-repositorycwd. Upgrade the package before runninginstall --platform codexagain;installanduninstallboth recognise the hooks earlier releases wrote.**/vendor/**applies only when a root manifest showsvendor/holds dependencies (composer.json,go.mod,go.work,Gemfile,Rakefile,Cargo.tomlor*.gemspec), and a nested one excludes its siblingvendor/. A source package namedvendorin any other repository stays in the graph; addvendor/to.code-review-graphignoreto exclude it (#1056).from module import name as aliasfollowed byalias(...)is recorded as a call tomodule.py::name. It used to qualify the alias,module.py::alias, a target that matches no node.A call through a Python module receiver whose module only re-exports the name (
pkg.helper()wherepkg/__init__.pydoesfrom .impl import helper) resolves to the defining file. The resolver required the name at the top level of the receiver's own module, so the call stayed unresolved._strip_plsql_noisetracked string-literal state per line and stripped/* */comments over the raw text in a separate first pass, so a string literal spanning multiple lines (or containing an unterminated/*) could desynchronize the rest of the file: anEND name;-shaped substring inside a multi-line string terminated a block early, an Oracleq'[...]'/Q'<...>'quoted string (which allows unescaped'inside it) confused the plain'...'state machine the same way, and an unterminated/*inside one string literal could pair up with an unrelated*/inside a later string and blank out everything between them — including a wholeCREATE PROCEDUREstatement that happened to fall in that span, which also regressed plain (non-Oracle) SQL files. It now tracks string and comment state in one pass over the whole file._plsql_block_end's terminator search also ran unbounded to end-of-file, so a package body member using a bareEND;could match an unrelated, later package body's same-named member'sEND name;instead of its own, and the unbounded search was quadratic on a file with many members (~4s for 4,000). It now takes alimit, bounding a member's search to its own enclosing package body (PR #785 review).An incremental update now keeps flows and communities in step with the graph. A new entry point gets its flow, a re-parsed file's flows follow its nodes to their new IDs, and a deleted file leaves no empty community behind. Before, relative changed paths never matched the stored absolute ones, so flows and communities were silently skipped (#569, #1058).
Overlapping graph writes no longer stack their work (#1073).
build,updateandpostprocesstake a per-repositoryflockon<data dir>/write.lockbefore parsing or rewriting derived data, so a second invocation reportsstatus: contended(CLI:Skipped: another code-review-graph ... is already running for this repository (PID n)) and exits75(EX_TEMPFAIL) instead of repeating the work. Commit hooks, editor-save hooks and agent tool hooks fire in bursts, and until now the only cross-process protection was SQLite's busy timeout, which is not reached until the first write — every duplicate run paid for a complete parse first, and apostprocesscould rewrite flows/communities/FTS for a graph another process was still storing. Ten concurrentupdateruns of one repository used to mean ten process pools on the same files.--wait(wait_for_lock) blocks until the in-flight write finishes, for callers that want the work done rather than skipped; two repositories never block each other; and the fixed lock path lets external tooling take the same lock. The lock covers schema initialization, legacy migration and CLI recovery too. Forked parse workers close inherited lock handles, so killing their parent releases the lock even while workers remain alive. A filesystem withoutflocksupport logs a warning and runs unlocked rather than reporting a competing writer. Invoke protected CRG commands directly; externalflockwrappers are for other writers.An explicit embedding refresh (
--embedding-providerwith--embedding-model, or theembedding_providerandembedding_modelarguments ofbuild_or_update_graph_toolandrun_postprocess_tool) on a graph with no embeddings yet now reports that it embedded nothing.build,updateandpostprocessreturnembeddings_refresh_skipped: trueand a warning to runcode-review-graph embedonce, and so does anupdatethat finds no changes.watchlogs the same warning after each update and keeps running; a refresh that fails against an existing index still stops it, as before. The refresh used to returnokwith no embedding field, the same result as a build that never asked for one. It still loads no model and makes no network call in that case (#1070).A rename rewrites the import statements that name the symbol (#1107). The preview looked for import edges that target the symbol, but Python import edges target the imported module's file, so
from pkg.util import helperwas never edited and the applied code raisedImportError. The preview now reads the defining module and every module that imports it, and follows Python's scope rules:from module import namein every spelling (several names on a line, parenthesized over several lines with comments,asaliases, relative imports), packages that re-export the name and their__all__,import modulethenmodule.name, and imports inside functions. Calls that no import statement names are still found through the graph's call edges and resolved the same way:pkg.util.name()afterimport pkg,sub.util.name()afterfrom pkg import sub, and a module bound byimportlib.import_module. JavaScript and TypeScript get the same forimport { name },import { name as x }, local export lists,export { name } fromandexport * frombarrels (also a public barrel nothing in the repository imports from), andns.namethroughimport * as ns,import ns = require(),const ns = require(),await import()andimport().then(). A CommonJSmodule.exports = { name }keeps its key, so its importers keep the old name. An alias keeps its own name; a parameter, local, comprehension variable or class attribute that reuses the name is left alone, and so are comments, strings and longer names that contain it. A call inside a shell"$(...)"is code and is renamed.Rename edits are exact. Each one carries the
columnwhere the old name starts and thesiteit is (definition,call,reference,import,export,attribute,__all__), and applying replaces the name only there. Before, applying replaced the first substring match on the line (helper_countbecamedouble_count) and, when the line had none, the first match anywhere in the file. A method call chained over several lines, or made inside an f-string, is renamed where its name is rather than in a comment or the string's text. An edit that no longer matches is skipped and counted inedits_skipped. CRLF line endings, a byte-order mark and non-ASCII text survive, and a file that is not UTF-8 is no longer rewritten with replacement characters.refactor_tool(mode="rename")resolvesold_nameexactly. It accepts a name,Class.methodor a qualified name such aspkg/util.py::helper. When a bare name belongs to several symbols the first by file and line is renamed, and the preview's newqualified_namesays which; pass a qualified name to pick another. A name that matches no symbol isnot_foundinstead of falling back to a symbol whose name merely contains it.tsconfig and jsconfig path aliases inherited through a relative
extendsresolve against the config that declaresbaseUrl(orpaths, when no config in the chain setsbaseUrl), as tsc does, instead of the nearest config's directory. In an Nx or Turborepo layout every app extending a roottsconfig.base.jsonlost its alias imports, soimporters_ofand the impact radius of a shared library missed those apps (#1110). Also as in tsc, a base config reached through a symlink (to the file or to a directory above it) anchors where the link sits, and a..in itsbaseUrlorpathsclimbs from there;"baseUrl": nullin an extending config unsets the inheritedbaseUrl.Python
@propertyreads produce edges, socallers_ofandfind_dead_codesee them (#1065, #1060).Python and Ruby classes that share a leaf name in one file no longer overwrite each other's methods (#1087, #999).
Terraform provider aliases keep distinct nodes, and references reach the right one (#1083, #985).
PowerShell functions, classes, methods and calls are extracted again (#1079, #1067).
Elixir
defwith awhenguard is indexed and short-form bodies are walked (#980, part of #979).JavaScript and TypeScript generator functions are
Functionnodes that own their calls (#1131).Components wrapped in
forwardRef,memoand similar calls areFunctionnodes (#972, #971).TypeScript abstract classes are indexed, with nodes for their abstract methods (#1033, #1031).
Compact Ruby
class Foo::Bardeclarations emitClassnodes with stable identity, and all nested Ruby methods now carry their full scope, such asAuth.User.to_s. This moves the schema to version 14, and the nextupdatere-keys Ruby files by itself (#933, #932).installno longer appends a shell block to apre-commithook written in another language, which made every commit fail.uninstallremoves the empty Codex skills folder it created.Calls inside Rust
if falseblocks are left out of the graph (#1132).@Overridemethods and JUnit lifecycle methods count as entry points in dead-code analysis (#1035, #1034).Spring calls on a concrete injected type stay on that type instead of moving to its only subclass (#1075, #1074).
tests_forand the test-gap report name tests reached through spec helpers (#1128, #1109).get_impact_radiusandget_review_contextreport unknown risk when a requested path is not in the graph (#1084, #983).The real-absence claim is withdrawn while untracked source exists (#987).
Review guidance applies the lifecycle test-gap exemption (#871, #865).
A registered data directory that has gone missing no longer strands a local graph (#1080, #1057).
Untracked files reach
buildandupdate(#1094, #998).A no-op
updaterepairs stale FTS left by--skip-postprocess(#1105, #1104).MCP auto-watch runs post-processing after updates (#1108, #1103) and stops and joins its watcher before closing the store (#1127, #1121).
Partial graphs recover once and then stay incremental (#881, #803).
Renames through re-exports match a full build, and importers keep their flows and communities (#1062).
Watch mode does not report a refused directory as adopted (#910, #907) and does not watch ignored trees created after startup (#973, #908).
Windows daemon logs accept Unicode and tolerate vanished PIDs (#1129, #866), and a Job Object ends watchers when the daemon dies (#958, part of #925).
tests_fornames PHP tests that reach a class through the Laravel container, and says they are guesses (part of #850). Run a fullbuildto pick this up.A PHP call on an expression, such as
$this->service->run()orapp(Service::class)->run(), was bound to a method of the same name in the calling file. A test named after the method it tests was recorded as calling itself: 113 such edges from tests on koel/koel. It now stays unresolved, as the same call in Python does.When more than one class the test imports defines the called method, the test is listed with
inferred_by: "ambiguous_call", unless the call is made on one of those classes that the test builds, as in(new UpdateBooks)->handle(). Building one class does not settle the test's other calls. On koel, classes with a test file that names them but notests_foranswer went from 17 to 12.The summary says how many rows are guesses, and
detail_level="minimal"keepsinferred_byontests_forrows.A guess is never coverage: test gaps and risk scores do not count it. The
detect_changessummary says PHP gaps are a guess, since container and HTTP calls are not traced (#851 is the full fix).
Migration
upanddownmethods are not test gaps when the file is in amigrationsormigratefolder inside the repository (#850).
Upgrade notes
Schema version: 13 → 15. Two migrations run by themselves the first time any command opens the graph. Both are quick, as neither rewrites a table:
v14 clears the stored hash of every Ruby file, so the next
updateparses those files again and gives Ruby classes and methods their full names, such asAuth.User.to_s. Until that update, Ruby rows keep their old names.v15 drops
community_summaries,flow_snapshotsandrisk_index, which nothing read, and three indexes that other indexes cover. The file keeps its size and reuses the space; to shrink it, delete it and runbuild, or runVACUUM.
Rolling back needs a rebuild. Migrations only go forward. 2.3.9 refuses a v15 graph with "was written by a newer code-review-graph (schema v15; this build understands v13)", so to go back, delete
.code-review-graph/graph.dband runcode-review-graph buildwith the older release.The VS Code extension has to be updated as well. 0.3.0 accepts v13 and answers a v15 database with "Database was created with a newer version (schema v15). Update the extension." This repository now carries 0.4.0, which accepts v15. The extension is published apart from the Python package, so until a 0.4.0
.vsixis built and installed, the Code Graph panel shows that error once the CLI has moved the graph to v15.Run
code-review-graph buildonce.updateonly parses files that changed, so most fixes in this release reach a file only when it is parsed again: the new file types (HTML, XSLT, Liquid, JSON, plain YAML,.cjsand.kts), translation files losing their key nodes,vendor/folders that hold source, workspace and npm alias imports, tsconfigbaseUrlandextends,extra.import_contexton import edges, the call and test fixes (#984, inherited methods,tests_forfor Java, Ruby and PHP) and the confidence tiers. Ruby files (v14 above), files with nested classes and C# files are the exception: the nextupdateparses them again by itself. If the graph has embeddings, runcode-review-graph embedafter that, because renamed C# nodes leave their old vectors behind.Upgrade the package, then run
code-review-graph installagain. The hooks and instruction blocks an earlier release wrote do not update themselves. Install now also writespost-checkout,post-mergeandpost-rewritegit hooks; the edit hook for Claude Code, Qoder and CodeBuddy, and the Codex hooks, call the newhook-updateandhook-statuscommands; and the instruction block has new search guidance. Upgrade first: an older CLI has nohook-update, so the new edit hook would do nothing. Install and uninstall still recognise everything 2.3.9 and earlier releases wrote.New settings are off by default. Nothing changes unless you turn them on:
--enrichorCRG_ENABLE_ENRICHMENT=1(needs theenrichmentextra),install --steering,install --gitignore,--follow-symlinksorCRG_FOLLOW_SYMLINKS=1,CRG_CONTEXT_HISTORY=1,CRG_VECTOR_CACHE=1,serve --http --multi-repo,serve --multi-worktreeand the Action'sdetailsinput.Output and dependency changes. Build results no longer carry
summaries_computedorsummaries_s. Onfind_large_functions_tool,semantic_search_nodes_toolandtraverse_graph_tool,result_countis now the count before the cut. The lowest supported versions ofmcp,anyio,tree-sitter,tree-sitter-language-packandpyyamlare higher, and fastmcp 4 is allowed (see Changed).GitHub Action cache. The cache key segment moves from
schema13toschema15, so the first run after upgrading restores no graph cache and builds from scratch. Self-hosted runners can delete the oldcode-review-graph-schema13-*entries.
[2.3.9] - 2026-09-18
Added
visualizecan draw a neighbourhood instead of the whole repository. The D3 page falls back to one bubble per community past 3000 nodes or 9000 edges, which a shallow clone of django (45,111 nodes, 397,762 edges) passes by 15x and 44x, so every serious codebase got the bubble view. Nine new flags seed a view and bound it:--seed-symbol,--seed-file,--seed-changedwith--seed-changed-base,--seed-flow,--path-fromand--path-tofor a shortest path, and--depth,--render-depthand--max-nodesfor how much is carried and drawn. Nodes outside the neighbourhood are absent from the payload rather than dimmed. A hop is a semantic edge (CALLS, IMPORTS_FROM, INHERITS, IMPLEMENTS, TESTED_BY, DEPENDS_ON); CONTAINS is structural and deliberately not a hop. Search, filters, community colouring, the flow selector and the keyboard shortcuts all keep working, and the default output is still one self-contained HTML file. The whole-repository view is unchanged when no seed flag is passed.cross_repo_search_toolacceptsrepos, a list of registry aliases or folder names, to search only part of the registry. Names that match no entry come back inunknownand names that match several entries inambiguous; both lists are bounded like every other list (#915).query_graph_toolresolves dotted targets such asDetails.QueryHandler.Handlethrough an indexednodes.symbolcolumn, added by migration v10. See Upgrade notes for the full schema move (#942, #934).updateandbuild_or_update_graph_toolreport files that failed to parse: the status ispartial, the summary names the files and the CLI prints a warning on stderr. A failed file keeps its previous graph rows (#952).CRG_HOOK_WORKTREES=1keeps the generated pre-commit hook active inside a linked Git worktree (#953).CRG_DISCOVERY_TIMEOUTbounds each Git command that discovers what changed when a review tool or command was not handed an explicit file list. It defaults to 5 seconds, capped atCRG_GIT_TIMEOUTso an unset-but-lowered general budget still wins; setting it explicitly is an instruction and is used as given, aboveCRG_GIT_TIMEOUTincluded. When it is unset andCRG_GIT_TIMEOUTwas set explicitly, discovery inherits that value, so the documented remedy for slow Git keeps working. Read on every call rather than frozen at import.CRG_GIT_TIMEOUTkeeps its 30-second default and still governs build, update and watch (#262).stagingis promoted totestingautomatically, once a day, when it has commitstestinglacks, every required status check is green on its tip, and the promotion gate has not failed on thetestingtip..github/workflows/auto-promote.ymlopens the promotion pull request and merges it with a merge commit, so contributor authorship survives; the decision lives inscripts/auto_promote.pyand reads the required contexts from thetestingruleset at run time. It merges only a pull request it opened itself — same repository,staging→testing, labelledauto-promotion, and pinned with--match-head-committo the commit whose checks were read — all re-verified immediately before the merge, so a fork branch namedstaging, a base branch changed after the fact, or a promotion pull request opened by hand cannot be merged by it. A hand-started run defaults to a dry run. Requires Allow GitHub Actions to create and approve pull requests under Settings → Actions → General. Promotion tomainis never automatic and the workflow cannot target it.
Changed
Keyword search is rebuilt, and it needs a rebuilt graph.
_fts_searchwrapped the whole query in one pair of double quotes, so every multi-word search was an exact-adjacency phrase match: on a build of this repositorypassword hashingreturned 0 rows. Queries are now built from tokens — each quoted, given a prefix term when it carries at least three alphanumeric characters, ANDed first and ORed only if the AND finds nothing, so recall widens without costing precision. A phrase typed in quotes stays a phrase and suppresses the OR widening, and the expression a prose question builds is bounded. Two new indexed columns feed it:nodes.docstring(2,451 of this repository's 6,517 nodes carried a docstring the FTS index could not see, because an external-content FTS5 table can only index real columns of its content table) andnodes.name_tokens, the camelCase splitsunicode61cannot produce, which raises inner-camel segment recall@20 from 24.7% to 54.7% for a 2.7% larger index. An exact-identifier boost and a symbol-name coverage boost keep BM25 precise against the widened candidate set; the shippedsearch_qualitybenchmark goes from MRR 0.667 to 1.000.A test gap that tests reach only through a caller is reported as its own class rather than as an unqualified gap.
TESTED_BYis a direct edge, so a private helper the suite exercises only through the public function above it carried none and was reported untested — 5 of the 74 gaps on one measured delta, includingmain.py::_offload. Each changed symbol is now classified in one batched pass: directTESTED_BY, else a two-hop upward walk over incomingCALLSlooking for a tested caller, which is the newGraphStore.get_caller_test_routes. Reachability is reported as a note, never as coverage: the gap set is unchanged, gap order stays class-blind so truncation cannot hide a real gap first, and risk scoring is not discounted — 32 of the 504 symbols the walk reaches are never executed by the suite, and no depth, fan-out or same-file rule tried moved that below ~5%. Reached gaps carrycovered_via,covered_depthandcovered_by, and the rendered pull-request comment gives them their own heading with an "indirect" Tested column. Two new environment variables bound the walk:CRG_CALLER_TEST_ROUTE_DEPTH(default 2 — over the measured delta depths 3, 4 and 5 rescue exactly zero) andCRG_CALLER_TEST_ROUTE_MAX_CALLERS(default 500).Hints,
next_tool_suggestionsand the prompt templates name the registered tools (detect_changes_tool, notdetect_changes), so agents are no longer told to call tools that do not exist. The generated instruction blocks already named them and are unchanged from 2.3.8, so reinstalling is not needed for this fix (#821).detect_changes,get_review_context,get_affected_flows,get_impact_radius,get_minimal_contextandupdate --briefresolve a branch ref to its merge base withHEAD, matching GitHub's "Files changed" view on divergent branches. Commit IDs and revision expressions are used as given, and a shallow clone that cannot resolve the merge base falls back to the ref (#847).Per-file node and edge writes, bare-endpoint resolution and signature computation run as batched statements in a single transaction each, instead of one autocommitted row at a time, which cuts build time on large graphs (#858, #721).
The generated pre-commit hook skips automatic checks in a linked worktree, so a commit there cannot silently create a second graph for another branch. Detection uses the git directory's
commondirfile and needs onlygit rev-parse --absolute-git-dir(Git 2.13). Reinstall upgrades the exact hook block written by earlier releases (#953).Every MCP tool that can reach Git discovery, a graph traversal, FTS, an embedding provider or the filesystem now runs its work on a worker thread through one shared helper. A tool that exceeds
CRG_TOOL_TIMEOUTanswers withstatus: errornaming itself and the budget, instead of leaving the client to time the request out itself as MCP error -32001. Onlyget_docs_section_toolandlist_repos_toolstill run inline; they read one small file each (#262, #46, #136).CRG_TOOL_TIMEOUTkeeps its meaning: it bounds read-only tools, and it does not boundbuild_or_update_graph_tool,run_postprocess_tool,embed_graph_tool,generate_wiki_toolorapply_refactor_tool. Those write — tograph.db, to the wiki tree, to your source files — and a timeout cancels the wait, not the worker, so bounding them would report failure to the client while the write went on regardless.Change discovery is bounded honestly. Each Git command in the chain that works out what changed gets
CRG_DISCOVERY_TIMEOUT(5 seconds) rather than the 30-secondCRG_GIT_TIMEOUT, and runs withrequire_vcs, so exhausting that budget raises aChangeDiscoveryErrorand the tool answersstatus: error. A shorter budget on its own would not have been enough: discovery that ran out of time silently is what produced #913's false all-clear, and the same silence at the base resolution degrades a three-dot diff into a two-dot one. RaisingCRG_GIT_TIMEOUTstill raises discovery with it, so the documented remedy for slow Git keeps working (#262).get_minimal_context_toolruns its change discovery on that same budget. It previously spent up to ~130 seconds on five Git subprocesses of its own — the tool agents are told to call first, and the one most likely to hit a client's request ceiling (#262).
Fixed
Ruby compact namespace declarations (
class Foo::Bar) are indexed. The name sits in ascope_resolutionnode rather than a bareconstant, so the class node was dropped entirely and its methods were left orphaned at file scope -- the dominant declaration style in Rails codebases (class Admin::UsersController < ApplicationController) produced no class at all.Ruby classes carry their namespace in
parent_name, and their methods qualify against the class's own key.class A::Sameandclass B::Samein one file both resolved tofile.rb::Same, and becausequalified_nameis UNIQUE one of them was dropped outright while the survivor absorbed both sets of methods. A method'sCONTAINSsource now names the class node that exists rather than a bare-leaf container that was never emitted, sochildren_ofreaches the methods of a class declared inside a compact module. The compact and nested spellings agree at every depth:module A; class B::Candmodule A; module B; class Cboth key tofile.rb::A.B.C. This is schema version 14; existing databases mark their Ruby files for re-parsing on open and pick up the new identities on the next update, and the VS Code extension accepts the new version.Ruby declarations whose scope is not a constant (
class klass::Inner,class self::Foo) no longer emit a file-scope class node. Their scope is resolved at run time, so there is no static namespace to record.PL/SQL identifiers with non-ASCII letters (e.g.
café) were silently truncated at the first accented character (_PLSQL_IDENTwas[A-Za-z_][A-Za-z0-9_$#]*, unlike the\w-based identifier matching used for non-Oracle SQL elsewhere in this file, which is Unicode-aware by default). The leading character class is now[^\W\d]instead of[A-Za-z_], so PL/SQL identifiers are Unicode-aware like the rest of the file while still correctly handling Oracle's$/#mid-identifier characters.Go and Ruby imports resolve into the repository instead of staying bare strings. Go reads the module path from the nearest
go.mod(nested modules win over their ancestors, and localreplacetargets are honoured) and maps an in-repo import to the package DIRECTORY it names; the standard library, undownloaded dependencies and anything the ignore patterns exclude stay unresolved. Ruby resolvesrequire_relativeagainst the requiring file andrequire,load,autoloadandrequire_allagainst the repository's load roots (gemspecrequire_paths,lib,test,spec, the Railsapproots, and$LOAD_PATH.unshiftin a root script); gems stay unresolved.importers_offor cli/cli'spkg/iostreams/color.gogoes from 0 to 424, and for jekyll'stest/helper.rbfrom 0 to 51.A Go import names a package and a Ruby
require_allnames a directory tree, so both emit ONE edge naming that directory, taggedextra.import_scope, and the read path expands a directory to its member files.importers_ofmatches edges targeting a file's own package,imports_ofreportsimport_target_kind, and the impact traversal follows a package target at every hop without spending one. One edge per package keeps the edge count proportional to import statements rather than to imports times package size: kubernetes/kubernetes records 93,650 import edges where an edge per member file would be 588,972 (73,507 of them from one imported package), and builds in about 146s rather than 524.6s. It also keeps an incremental update in agreement with a rebuild, which a per-member fan-out cannot do, because an edge's targets would then depend on which files were in the package when the importing file happened to be parsed.Binding imports that previously stayed bare costs build time and disk, on every repository measured, and never saves either. Measured with the import change off and on against the same parent commit — which already carried the batched writes below, so these figures isolate the import change and are not a comparison against 2.3.8: a kubernetes/kubernetes build goes from 115.1s and 122.8s to 148.7s and 146.5s (about 24% slower) and its database from 4.41 GB to 5.36 GB (21.5% larger), run isolated and alternating on one idle machine; cli/cli goes from a 7.6s build and a 281.1 MB database to 9.3s and 325.9 MB. What that buys is that a bound import also lets the call resolver attribute cross-file calls: on cli/cli the CALLS edges bound to an indexed node go from 5503 to 22564 of 67503, with the edge count unchanged.
The impact traversal's directory branch is pinned to an index seek on
(target_qualified, kind). Left to itself SQLite drove it from the covering index onkindalone and rescanned everyIMPORTS_FROMrow once per frontier directory, which was 18 seconds of a 19-second kubernetes traversal; pinned it is 1.8s, against 6.5s for the same answer under the per-file fan-out.tests/test_import_scope.pyasserts the plan.No import edge names a path the build does not index. 73,512 kubernetes import edges named a path the build never indexed, 73,507 of them files under
vendor/, which**/vendor/**excludes, so every one of them was a confident-looking path that matched no node. Five remain. Such an import keeps its bare module string, which is visibly external.get_impact_radiusbounds every list in its response by a fixed ceiling -- the same 100 nodes, 150 edges and 200 filesget_review_contextapplies to the same radius -- and reportsedges_omitted,changed_nodes_omittedandimpacted_files_omittedalongside the existingnodes_omittedandtotal_impacted.edgesandchanged_nodeshad no ceiling at all andmax_resultswas not exposed on the MCP tool, so the response grew with the repository: 138k tokens on one changed file of cli/cli and 189,163 on one of kubernetes, against a documented 12k budget. That kubernetes response is now 23,669 tokens. Edges that touch the changed code are kept first, andmax_resultsis now an argument ofget_impact_radius_tool.Every specifier in a Go
import ( ... )block carries its own line. 8636 of cli/cli's 8687 import edges (99.4%) were stamped with the line of theimport (token.Ruby import extraction dispatches on the call's method name instead of testing whether the node text contains "require". 31 of jekyll's 227 import edges (14%) were not on a require line at all, with targets such as
Missing --ssl_cert or --ssl_key. Both are required.; those call subtrees were also being dropped whole, so their calls are now extracted.autoload, which is how jekyll declares its entire internal module graph, produced no edge at all.Freshness metadata follows what was stored. A no-op
updatethat confirmsHEADadvances the Git anchor, so queries after a commit no longer carry a stale-graph caveat, and a file that fails to parse no longer holds the anchor back for the files that did parse (#952).updatecompares stored hashes with the files on disk, so a change that was indexed and then reverted is re-parsed even when the Git diff is empty. Each indexed file is still read only once per update, and binary checks read only the first 8 KB (#860).Failed C++ identity migrations are retried file by file instead of rebuilding files that migrated correctly, and a file that failed and was then ignored is dropped from the pending set instead of making every later update report "Identity migration pending for ignored file" and stopping
watchfrom starting (#944).The Spring and Temporal resolvers take interface implementors only from Java, Kotlin and Scala classes, so Go struct embeddings are no longer mistaken for Java implementations while Kotlin and Scala implementors keep resolving (#834).
Go embedded structs and interface composition produce
INHERITSedges again, including groupedtype (...)declarations and generic embedded types (#834).tsconfig path aliases probe
.mtsand.ctstargets,.mtsand.ctsfiles are indexed as TypeScript in full builds, and.mjsand.cjsimport specifiers resolve to.mtsand.ctssources before falling back to.tsand.tsx(#874).forgetanddead-coderesolve the database the way the other read-only commands do, without creating graph state when none exists, and still migrate a legacy top-level.code-review-graph.dbinstead of reporting "No graph found" (#874).C, C++ and Bash grammar probes put the parent interpreter's
tree_sitter_language_packlocation on the child's path, so grammars installed in a venv or withpip install --userare no longer reported missing, and probe failures carry an install hint (#810, #807).Go methods whose receiver type carries a comment or parentheses are attached to their type (#872).
Kotlin import names are read from the AST, including
asaliases, and wildcard imports no longer produce an entry (#873).PHP
include,include_once,requireandrequire_oncewith a literal path create import edges. Dynamic paths are not evaluated, and the empty-result caveat says so (#875, #819).Python
import a.b as cis extracted as an import ofa.b(#955).get_all_filesno longer fails on malformedextraJSON left behind by a killed writer (#870, #864).Dotted Python module imports (
srclayouts,odoo.addons.*) count as evidence when resolving bare call targets, both in post-processing and inquery_graph(#960, #903).Embedding runs enumerate nodes directly instead of through stored file paths, so legacy stores with non-canonical paths are embedded, and
embed_graph_toolno longer reports semantic search as active when nothing was embedded (#962).inheritors_ofmarks results found by bare name withinferred_by: "bare_name"when several classes or types share that name (#966).references_toincludes dependents whose import specifier the parser could not resolve (npm workspace alias, tsconfig path mapping), markedtarget_resolution: "unresolved", when the bare name identifies exactly one node (#969).Source files are read as UTF-8 regardless of the system locale in review and flow snippets and in the eval runner (#928).
Watch mode drops events on paths the OS cannot stat instead of ending the watch loop (#959, #897).
Watch mode reloads ignore rules when
.code-review-graphignoreor a build manifest changes, removes newly ignored files from the graph, indexes newly included ones, and keeps a source file created at a path previously inferred as build output (#909).Incremental reconciliation refuses a graph whose File markers only partly overlap the requested root, so files of a parent or sibling repository are not deleted (#909).
Legacy graph paths are purged by their stored spelling, so normalising them cannot delete a different current row (#911).
When promotion to a recursive watch fails, the existing watches are kept and the health file reports
degraded(#927).A watcher thread that dies before the first liveness tick is detected (#891).
crg-daemon stopkeeps the PID file until the process has exited and exits non-zero if it is still running (#958).The Qoder skills are bundled in the wheel, so
install --platform qoderworks from a pip install and never copies the target project's ownskills/directory.uninstallremoves those bundled workflows from.qoder/skills/too — it derived the list from the target project's own top-levelskills/directory, which a normal repository does not have, so it removed none of them (#909).
Upgrade notes
Schema version: 9 → 13. Four migrations land in this release, and they run automatically the first time any command opens the graph:
v10 adds the indexed
nodes.symbol, back-filled fromqualified_name.v11 adds the indexed
edges.target_resolution, back-filled over everyCALLSandREFERENCESrow. It is what thereferences_toentry above means bytarget_resolution: "unresolved".v12 adds
nodes_fts_state, the mirror of the FTS index.v13 adds
nodes.docstringandnodes.name_tokens, back-fills both —name_tokensin a Python loop over every node — then drops and rebuildsnodes_fts.
v13 is a full-table rewrite plus a complete FTS5 rebuild. On a large graph (this release measures kubernetes/kubernetes at 5.36 GB) the first open after upgrading will take a while and is not resumable. Back the database up first if that matters, or delete it and run
code-review-graph build.Rolling back needs a rebuild. Migrations are forward-only, and 2.3.8 has no newer-database check — it opens a v13 graph without complaining and reads it with a v9 reader. This release adds that check, so a 2.3.9 build reading a future schema says so in one line instead.
The VS Code extension has to be repackaged and republished. Any extension build a user can already have installed rejects a v13 database with "Database was created with a newer version (schema v13). Update the extension." — the only tagged release,
vscode-v0.2.1, accepts v6, and the untagged 0.2.2 in this repository accepted v9. The extension is versioned and published separately from the Python package, so upgrading the CLI does not upgrade it. This repository now carries 0.3.0, which accepts v13; until that.vsixis built and installed, the Code Graph panel will report the error above after the CLI migrates the graph.Run
code-review-graph buildonce to get the parser fixes.updateskips any file whose hash is unchanged, and nothing stamps the parser version into the graph, so a file that has not been edited is never re-parsed after an upgrade. Every parse-time fix above — Go and Ruby import resolution, per-specifier Go import lines, Rubyrequiredispatch, Kotlin imports, PHPinclude/require,import a.b as c, Go receiver comments, Go embeddedINHERITS, tsconfig.mts/.cts— changes a file's rows only when that file is re-parsed.importers_ofon a Go package stays at 0 until then, and theextra.import_scoperead path has no edges to expand. The search changes are the exception: migration v13 back-fillsnodes.docstringfrom what the parser already stored inextra, so keyword search improves without a rebuild.Re-run
code-review-graph installto pick up the hook and config changes. The generated pre-commit hook is not self-updating; reinstall is what replaces an earlier release's block with the one that skips automatic checks in a linked worktree.GitHub Action cache. The cache key segment moves from
schema9toschema13, so the first run after upgrading restores no graph cache and builds from scratch. Self-hosted runners can delete the oldcode-review-graph-schema9-*entries.
[2.3.8] - 2026-08-21
Added
Empty results from
query_graph,get_impact_radius, andsemantic_search_nodesnow carry a one-sentenceconfidencefield saying why the count is zero: the target was never indexed, the graph is behind the working tree, a known static-analysis gap applies to that language and query (PHP container resolution andinclude/require, JS/TS callbacks and route registration, npm-aliased imports, Java AOP and reflection, Go structural interface satisfaction, C# DI, Pythongetattrand registry decorators), or the zero is a verified real absence. The field is capped at 140 characters and is emitted only when the result list is empty, so responses that carry results are byte-identical to before. One honest sentence is far cheaper than the wrong conclusion or the repo-wide grep a bare zero provokes (#314, #819, #850, #851).Added a Voyage AI embedding provider (
--provider voyage, key fromVOYAGE_API_KEY, opt-in request throttling viaCRG_VOYAGE_MIN_INTERVAL_SEC). Embeddings are now persisted after each batch for every provider, so an interrupted cloud run keeps completed batches and re-runs skip up-to-date nodes (#783).Added
code-review-graph forget PATH [PATH ...]to drop already-parsed files from the graph without a full rebuild. Paths may be absolute, relative to the repository root, a directory (every file underneath is dropped), or a glob pattern, and--dry-runpreviews the selection. The result is equivalent to rebuilding the graph without those files: surviving referrers are re-parsed so no edge is left pointing at a deleted node, and flows, communities, the FTS index, and embeddings are all repaired (#678).Added
--platform NAMEtocode-review-graph uninstallto unbind a single platform's MCP registration while preserving the graph data and every other configured integration. Without--platformthe command still performs the full uninstall (#678).Added Oracle PL/SQL structural parsing: packages, package bodies, triggers, and PL/SQL-style functions/procedures (
.sql,.pls,.plb,.pks,.pkb,.pck,.prc,.fnc,.trg), with package bodies qualified distinctly from their spec (pkg_name$body) and nested members qualified by name plus parameter signature (pkg_name$body.member_name(params), so overloaded package members don't collide), plus regex-based CALLS edges between procedures/functions/triggers/package members. The PL/SQL-specific passes (body-span computation, CALLS extraction, packages, triggers) only run for files an Oracle-dialect check confirms are PL/SQL (an Oracle extension, or a content signal likeVARCHAR2/%TYPE/:NEW., checked against comment/string-stripped text) — plain ANSI/T-SQL/MySQL.sqlfiles are unaffected. Comments and string-literal contents are blanked before every PL/SQL regex pass runs, so a;orEND name;inside a trailing comment or a dynamic-SQL string literal can't be mistaken for a real terminator, and Oracle's$/#identifier characters are handled consistently across every pass. EveryCREATE/END-anchored regex requires a leading word boundary (e.g.RECREATE PROCEDUREno longer matches asCREATE PROCEDURE,ENDfoo;no longer matches asEND foo;), and the forward-declaration/signature scanners are independently comment/string-aware rather than only correct because callers happen to pass pre-cleaned text. TheINSERT INTOfilter used when extracting PL/SQL call edges now checks a bounded lookback window instead of re-scanning the whole preceding body on every call site, keeping call extraction roughly linear instead of quadratic on files with many calls.
Fixed
Almost every MCP tool response is now bounded. #849 found
get_affected_flowsreturning roughly 247k tokens inside a workflow documented as "5 tool calls, 800 tokens total"; measuring all 30 registered tools against a real 5.6k-node graph found the same shape in ten more places, several of them on the default path. Each list now carries a hard ceiling, keeps its untruncated total, and reports "showing N of M" in the summary, so a caller can tell a short answer from a truncated one. Four query tools are still unbounded and tracked in #888 (#849, #853, #887).A repository is now one graph however its root is spelled. Every path-valued
--repois canonicalized at the CLI boundary, and the full build, incremental update and watch entry points each resolve their root, so., a relative path, a trailing slash, a symlinked path and an absolute path no longer split the graph or reconcile each other away. An incremental run whose storedFilenodes all belong to a different root is refused with a clear error instead of emptying the graph file by file, while orphan-only cleanup from #861 is unchanged (#889).A repository reached through a symlinked path is watched and indexed instead of silently dropping every filesystem event, both through
code-review-graph watch --repoand throughserve --auto-watch(#892).Watch mode no longer registers OS watches inside ignored trees, and no longer keeps running after its filesystem observer dies. Watches are now planned per directory (ignored trees are skipped, the repo root is watched non-recursively, and directories that appear or disappear later are picked up from a per-tick listing rather than from directory events, which macOS never delivers for a child of a non-recursive watch), nested build output such as
moduleA/target/is ignored when a siblingpom.xmlproves it is build output — reported at info level and overridable per path with!pathin.code-review-graphignore— and a dead watchdog thread makes the watcher log an error and exit non-zero so the daemon restarts it, while a deleted watch root is merely released.crg-daemon statusgained aWatchercolumn (ok/partial/stalled/unknown/dead) and last-event age, and the daemon now backs off exponentially between restarts of a watcher that keeps dying (#811).Deleting and recreating a watched directory (
rm -rf src && mkdir src, or two branch switches in a row) no longer kills the watcher or silently drops the recreated directory from the graph. Watches are tracked by inode rather than by path, so a replacement is released and re-adopted instead of being read as a dead watcher, and a directory adopted after startup is planned the same way startup plans the repository — its ownnode_modulesandtargetstay unwatched (#811).code-review-graph watch --repo .no longer empties the graph on startup. The watcher resolves its repository root before reconciling, so stored absolute paths are no longer all treated as stale, and a graph built under a genuinely different root is now refused with a clear error instead of being reconciled away file by file (#811).The generated instruction sections no longer tell agents to always use the graph before reading source and to fall back to file search only when the graph misses. Every platform instruction file now carries the same short guardrails: narrow scope with the graph, read the implementation and its tests before a non-trivial change, prefer the source when the two disagree, and treat an empty graph result as possibly unindexed rather than absent. CONTRIBUTING.md also states what a new platform target must include before it will be reviewed (#314).
Reinstalling now upgrades an instruction section written by an older release instead of skipping the file because the opening marker was already there, which is what made every wording fix invisible to existing users. Generated sections carry a closing marker so the block has real boundaries; blocks from before that marker existed are matched by their exact recorded text, so anything written around them survives byte for byte. A section someone edited by hand is left alone and named in the install output instead of being overwritten, and
installnow reports created, updated and left-alone files separately (#314, #558).uninstallnow removes an instruction section written by any past release, not only one written by the running version, so asking for the instructions to go no longer leaves orphaned text behind. It matches the same recorded block texts install uses, clears duplicate blocks, keeps the text on both sides of the block, closes the gap without leaving a run of blank lines, and still refuses to touch a section that was edited by hand (#314).C# receiver calls (
Service.StaticCall(),obj.Method(),obj?.Method()) now resolve to canonical method nodes using receiver-type and namespace evidence recorded at parse time, socallers_of,get_impact_radius, andtests_forreturn real results for C# codebases instead of bare unresolved names (#612).C# namespace-targeted
IMPORTS_FROMedges now resolve inget_impact_radius,tests_for, and thedetect_changestest-gap classification, not justimporters_of— well-covered C# code is no longer reported as untested (#310, #792).The path alias resolver now reads
jsconfig.json(withtsconfig.jsontaking precedence), so plain-JS Vue/Nuxt/Vite projects resolve@/...imports instead of silently dropping most of the import graph (#776).Qualified names and
file_pathvalues now always use POSIX forward-slash separators, making graphs separator-stable across operating systems and fixing 15 Windows test failures;GraphStore.get_nodebridges native-spelling lookups. Graphs built on Windows by earlier versions need a rebuild (#774).A file saved while it is being indexed no longer stays permanently under-indexed: language detection and parsing now use the same byte snapshot that produced the stored
file_hash(#746).statusno longer reports stale or phantom languages: the language list is derived from the live indexed-file inventory, and Java-specific resolvers rerun when Java files leave the graph (#474).visualizeauto mode now also switches to an aggregated view when the rendered edge count exceeds its budget (previously node-count-only, which stalled the D3 force layout on edge-heavy graphs), and falls back to file aggregation when no community data exists (#609).visualize --servenow works on offline and filtered networks: the pinned D3 build ships with the package and is loaded same-origin with its SRI hash verified, keeping an SRI-pinned CDN fallback. Placeholder substitution is ordered so repo-derived graph content can never be expanded as template markup (#475).Refreshed
uv.lockwithin existing constraints, clearing all disclosed advisories from #665 (mcp 1.29.0, python-multipart 0.0.32, authlib 1.7.2, pyjwt 2.13.0, starlette 1.3.1, cryptography 49.0.0).Swift initializers, deinitializers and subscripts now emit
Functionnodes (#786).init_declaration,deinit_declarationandsubscript_declarationare node types distinct fromfunction_declaration, so the walker skipped them and attributed the calls in their bodies to the enclosing File node — a change inside one initializer read as file-wide blast radius, andget_impact_radiuson a callee could not trace back to the initializer that calls it. Each is named after its declaration keyword, since the grammar gives none of the three a usable name field (subscript's would be its return type,deinit's is absent).GitHub Copilot auto-detection now requires the Copilot extension and also recognizes the extension bundled with released VS Code installations.
C# methods with a non-generic return type are no longer named after that type:
public async Task Foo()indexed asTask, collapsing every such method in a class onto one qualified name (#791).Java method and constructor names now use the grammar
namefield (same approach as C#), instead of walking for the firstidentifierchild (#804).Added a post-index Python import resolver using unique module suffixes, so
src/layouts produce canonicalCALLSandTESTED_BYedges while duplicate package candidates remain explicitly unresolved (#720).
[2.3.7] - 2026-07-18
Maintainer-reconciliation release. This release packages the verified work merged since v2.3.6: broader language and framework coverage, safer graph and CLI workflows, platform-install hardening, daemon reliability, and the final CodeQL security fixes. The four client-validation drafts remain excluded. No breaking changes.
Added
Expanded CLI-first workflows with CommonJS
require()parsing, ten focused graph commands, quiet and JSON output, bounded enrichment, and dead-code analysis (PRs #95, #340, and #341).Added safe Java and Spring modeling for request endpoints, WebFlux routes, value-redacted application configuration, scheduled triggers, application events, Lombok constructor injection, runtime callbacks, and method references (PRs #462, #577, #589, #590, and #591).
Added repository-bounded PHP/Laravel semantics and Julia qualified-scope parsing, plus evidence-backed typed-member resolution, Python star-import expansion, Python class decorators, and C# inheritance edges (PRs #628, #638, #639, #643, #647, and #649).
Added bounded transitive test coverage, opt-in churn risk, weighted impact-radius ranking, and graph provenance on MCP responses (PRs #636, #640, #644, and #646).
Added CodeBuddy Code MCP configuration and project skills using its official shared project contract (PR #633).
Added Terraform/OpenTofu structural parsing for resources, data sources, modules, variables, outputs, locals, providers, and expression references. References resolve across sibling files in a Terraform module, and local module sources connect to parsed target files (PR #514; Terraform portion of #199).
Added Ansible playbook, role, task, handler, notification, include, and role dependency extraction with qualified graph relationships, duplicate-task disambiguation, and ordinary-YAML false-positive guards (PR #415).
Added bounded VB.NET structural parsing for namespaces, types, generics, multiline members, properties, calls, inheritance, and interfaces. Same-file targets resolve case-insensitively only when scope evidence is unique, and overloads share one stable graph symbol (replacing PR #517).
Expanded SystemVerilog structure with ports, nets, parameters, packages, typedefs, modports, port references, and verification declarations. Function locals are excluded rather than promoted to module globals, and signal nodes no longer pollute function risk, flow, dead-code, or size analyses (PR #522).
Corrected Rust trait and
implidentity, preserving one concrete type across repeated implementation blocks. Nested/aliasedusetrees,Selfand turbofish calls, and bounded cached Cargo path/workspace dependency resolution now retain their original graph targets (replacing PR #526).Added an explicit local JSON visualization export. The output is written atomically inside the ignored graph data directory and is documented as potentially containing absolute paths and code-structure metadata (PR #449).
Functions and classes now retain a bounded, first-paragraph documentation summary in backward-compatible node metadata across Python, JSDoc/Javadoc, C# XML docs, Doxygen, Rust, and Go. Semantic embedding text includes the normalized summary, so behavior-oriented queries no longer depend only on identifier overlap (replacing PR #602, with Stefan Hudici attribution).
Explicit provider-and-model-scoped embedding refresh is available on build, update, postprocess, and watch paths. It is default-off, refuses silent provider/model/endpoint migration, remains fail-soft, and purges vectors for deleted or renamed nodes; manual
embedalso purges orphans (replacing PR #599, with Stefan Hudici attribution).Added
code-review-graph uninstallas a safe, symmetric counterpart toinstall(#482, replacing PR #491). It derives MCP cleanup from the live platform specifications, preserves unrelated shared configuration and JSONC comments, commits shared-file edits with atomic replacement, removes only CRG-owned hook/skill files, requires and normalizes Git/SVN repository roots, enforces repository/home boundaries, and supports dry-run, registered-repository, data-retention, and user-config-retention modes.
Fixed
Made minimal-context and review analysis non-blocking, shared changed-file discovery across review tools, bounded automatic repository detection, and cached graph access so MCP requests avoid repeated scans (PRs #394 and #457).
Corrected semantic-search mode reporting, skipped unavailable native grammar parsers safely, and surfaced graph staleness, omitted counts, and symbol disambiguation in responses (PRs #458, #459, and #538).
Hardened daemon startup and persistence: foreground lifecycle state is ready before serving, generated TOML escapes strings, and Windows PID checks use waitable handles without leaking temporary handles (PRs #630 and #632).
Preserved undecodable subprocess output, expanded trailing-slash ignores, retained nested source directories, and excluded AWS CDK synth output without hiding source trees (PRs #566, #583, and #635).
Reconciled community detection placement/splitting and restored responsive visualization sizing without changing graph data (PRs #641 and #642).
Serialized first-use local embedding dependency imports and model construction across MCP worker threads. POSIX startup remains lazy, failed loads are not cached, and Windows retains its main-thread prewarm (#610, replacing PR #611).
PHP scoped/static calls now resolve during parsing when backed by same-file, enclosing-class, import, qualified-name, or namespace evidence. This keeps incremental work bounded to changed files and leaves unrelated globally unique
Class::methodnames unresolved (safe subset of PR #568).Incremental Git change discovery now reads NUL-delimited byte output, so Unicode, whitespace, newline, and literal arrow paths are preserved while rename/copy records keep destination-only semantics (PR #618).
MCP stdio servers now use thread-based parallel parsing on every platform, preventing inherited transport descriptors from keeping Unix servers and workers alive after host disconnects, while normal non-interactive CLI/CI builds retain the faster process-pool default (PR #615).
Bare CALLS targets and TESTED_BY sources are qualified during postprocessing only when same-file or import evidence identifies exactly one node. Query-time fallbacks apply the same rule, preventing unrelated same-named functions from inheriting tests (replacing the unsound subset of PR #601).
Corrected TESTED_BY edge direction across graph, refactor, and transitive-test consumers, with a parser-to-store-to-query regression (#527/#559/#598 class).
C# receiver calls now capture bare, chained, member, and null-conditional invocations with caller attribution (#612); Kotlin/C# annotations and C# namespace importer resolution—including nested namespaces—are also preserved (#295/#310, PR #353).
Restored advertised Zig structure, calls, imports, and test nodes, including TESTED_BY edges for test blocks embedded in ordinary source files (PR #393).
Hardened generated skills/configuration: uppercase
SKILL.md(PR #563), string-safe JSONC plus top-level and nested-container data-preservation guards (#553, PR #354), and portable PATH-aware hooks (PR #565).Packaged documentation remains reachable through the MCP wrapper (#613), Action comments render repository-relative paths, and both visualization templates select the graph SVG specifically (PR #564).
PHP
useimports now resolve to files (importers_of, impact radius, call disambiguation): PHPusestatements had no branch in the parser's import extraction and fell through to the raw-text fallback, storing the wholeuse ...;statement as theIMPORTS_FROMedge target (e.g."use App\Domain\Entity\Job;"). As a resultimporters_of/tests_for/inheritors_ofand the upstream side ofget_impact_radiusreturned nothing for PHP classes, and the unresolved targets also degraded cross-fileCALLSdisambiguation inresolve_bare_call_targets. PHP imports are now recorded as fully-qualified names (handlingasaliases, groupeduse A\{B, C}, anduse function/use const) and resolved to absolute.phppaths by walking up from the importing file, mirroring the existing Java resolver. Vendor/global classes with no local file stay as the bare FQN.
Changed
Updated supported async, embedding, watchdog, checkout, cache, and Python setup dependencies while keeping the published compatibility bounds explicit (PRs #544, #629, and #631).
Fork pull-request reviews now use a least-privilege two-stage workflow, and evaluation configs use larger pinned commits for reproducible measurements (PRs #468 and #634).
Build output now exposes post-processing stage timings for performance diagnosis (PR #637).
Security
Closed all three open high-severity CodeQL alerts: the PyPI diagnostic now uses the default secure TLS context, URL checks compare parsed hostnames, and visualization tests use structural HTML parsing instead of unsafe regular expressions (PR #657).
[2.3.6] - 2026-06-10
Community-response release. Built from a full audit of every open PR, issue, and discussion: community fixes merged with credit, verified defects fixed (including two open Windows bugs), benchmark claims made independently checkable, and the project's first self-hosted PR review bot — this repo now reviews its own pull requests with its own graph. No breaking changes.
Added
Custom languages without forking (#320): drop a
.code-review-graph/languages.tomlinto your repo to index any grammar shipped by tree-sitter-language-pack (extension map + node-type lists, validated and capped, built-ins always win). See docs/CUSTOM_LANGUAGES.md.GitHub Action for risk-scored PR review comments: composite
action.ymlbuilds/restores the graph from CI cache, runsdetect-changesagainst the PR base, and upserts a sticky comment with risk table, affected flows, test gaps, and the Token Savings line. Dogfooded on this repo via.github/workflows/pr-review.yml. See docs/GITHUB_ACTION.md.agent_baselineeval benchmark: compares graph queries against a realistic grep-and-read-top-k agent baseline instead of the whole-corpus strawman; wired into all six pinned eval configs.Co-change ground truth for
impact_accuracy: predictions are now 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): report-only cron run 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. Linked from the README.
GitHub issue forms (bug/feature/platform), a PR template mirroring the CONTRIBUTING checklist, and dependabot config for pip + GitHub Actions.
Fixed
store_file_batchis now guarded against open transactions like its sibling (#489, merged from community PR #529 by @Devilthelegend — thank you).Windows:
daemon statusno longer crashes with WinError 87 (#511): PID liveness now usesOpenProcess/WaitForSingleObjecton win32 instead ofos.kill(pid, 0).Windows: CLI
detect-changesmapped 0 functions (#528): diff paths are now remapped to absolute native paths before node lookup, matching the MCP tool's behavior; also prevents the misleading "~100% token savings" line on an empty result.Eval benchmarks no longer record failed runs as inflated wins: thrown
get_review_context/analyze_changescalls are markedstatus=errorand excluded from aggregates instead of producing naive/1 ratios or recall=1.0.Unknown embedding provider names now raise a clear error listing valid providers instead of silently falling back to the local model.
The five analysis MCP tools and the wiki-page tool no longer leak SQLite connections (try/finally
store.close()).installgit hooks now resolve the real hooks directory viagit rev-parse --git-path hooks, so linked worktrees andcore.hooksPath(husky) setups get a working pre-commit hook (#313 residue).Shipped
hooks/hooks.jsonandhooks/session-start.shnow drain stdin, matching the generated configs (#493 class).fastmcpis now capped<4so the next major cannot silently break the server (the #488 failure mode).
Changed
README benchmarks section now leads with the ~82x median per-question reduction (528x presented as the best case, not the headline), the limitations block is visible instead of collapsed, and "100% impact recall" is reframed as a graph-derived upper bound alongside the new co-change metric.
Stale translated READMEs (zh-CN, ja-JP, ko-KR, hi-IN) carry a staleness banner; the zh-CN benchmark captions and docs/USAGE.md no longer contradict the English README.
SECURITY.md now points to GitHub private vulnerability reporting as the canonical channel.
[2.3.5] - 2026-05-25
Real-time token savings, visible to humans. The estimated context-savings
metric introduced in 2.3.4 was JSON-only. In 2.3.5 it surfaces as a clean
boxed panel on the CLI and is verifiable against a real tokenizer in one
flag — so when you reach for code-review-graph to review a change, you
can immediately see how much of your context window the graph just kept
out. No breaking changes.
Added — Token Savings (headline feature)
Boxed
Token Savingspanel on every--briefCLI call. Bothcode-review-graph detect-changes --briefand the newcode-review-graph update --briefprint a four-line panel: the full-context baseline, the graph response size, total saved tokens with percent, and a per-category breakdown (Functions / Tests / Risk / Other) that sums exactly to the graph response size — no padding, no rounding magic.┌─────────────────────── 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 │ └──────────────────────────────────────────────────────────────┘--verifyflag cross-checks the displayed numbers against OpenAI'scl100k_basetokenizer (the GPT-4 family). Adds a secondVerified (tiktoken)row to the panel showing the real token counts. Requirespip install tiktoken. A one-time calibration across 222 mixed source files (Python/JS/TS/Go/Rust/RST/MD) committed indocs/REPRODUCING.mdshows thechars/4approximation stays within +0.5% of real tokens in aggregate; per-repo bias is bounded to ±12% and the ratio stays stable because both sides of the divide are equally biased.code-review-graph update --brief— incremental update plus the same risk + Token Savings panel in one command. Distinct fromdetect-changes --brief(which is read-only against the existing graph). Useupdate --briefwhen the graph might be stale (post-rebase, large change set); usedetect-changes --briefwhen hooks/crg-daemonhave already kept the graph fresh.
Added — Reproducible benchmarks
docs/REPRODUCING.md— end-to-end reproduction recipe with canonical numbers, the tiktoken calibration table, and an explicit explanation of the three different "token" benchmarks in the codebase and what each measures. Two people running the recipe on different machines on different days now produce identical numbers, within float rounding.multi_hop_retrievalbenchmark — 11 hand-curated 2-step tool-chain tasks (semantic_search → query_graph) across the 6 test repos. Average score 0.909. Per-task CSV inevaluate/results/.code-review-graph embedCLI subcommand — explicit shell-level access to embedding generation. Previously only reachable via MCP, which made the benchmark recipe awkward.
Changed — Deterministic eval pipeline
Every config under
code_review_graph/eval/configs/*.yamlnow pins an upstream SHA. Previously every config usedcommit: HEAD, which made benchmarks drift whenever upstream pushed. Pinned SHAs: expressb4ab7d65, fastapi0227991a, flaska29f88ce, gin5c00df8a, httpxb55d4635, code-review-graph84bde354.nextjs.yamlrenamed tocode-review-graph.yaml. The historical "nextjs" entry pointed at this repo, not a Next.js codebase. Renamed to match reality.eval/runner.pyuses full clones with explicitreturncodechecks. Previously--depth 50silently fell back toHEAD~1..HEADwhenever a pinned test-commit SHA was past the shallow window, producing benchmark numbers tied to whichever HEAD the clone happened to grab.Leiden community detection seeded (
CRG_LEIDEN_SEED, default42). Previously unseeded — community IDs and sizes drifted run-to-run on the same graph, breaking benchmark comparability.eval/runner.pyresolves repo paths absolutely before storing. The parser previously stored file_path as the path you passed in, so eval builds and CLI/MCP builds could disagree, producing duplicate nodes for the same source location. Fixed by.resolve()in the runner.eval/runner.pycallsrun_post_processingafterfull_build. Previously the eval framework left FTS5 unpopulated (shadow tablesnodes_fts_idxandnodes_fts_docsizeempty), so downstream search and multi-hop benchmarks silently returned no results.
Changed — Search and embeddings
embeddings._node_to_textis richer. Embedded text per node now includes the dotted form (Parent.name, e.g.APIRoute.get_route_handler), the identifier split into words (get route handler), and the enclosing module directory (routing,dependencies). Forces an automatic re-embedding because the text hash changes. Lifts multi-hop benchmark accuracy from 0.545 → 0.818.Identifier-aware search boost (
search.extract_query_identifiers). Natural-language queries like "Who advances the gin middleware chain via Context.Next" now have their dotted / snake_case / CamelCase tokens extracted and used to boost matching qualified-names by 2.0× in hybrid search. Combined with the richer embed text, multi-hop accuracy reaches 0.909 (10 of 11 tasks pass).
Fixed
Test-gap dedup in the brief summary. If duplicate
qualified_namesever slip into the graph (e.g. after a path-normalization mismatch), theUntested:line in the human summary now collapses to unique names. The underlyingtest_gapslist still carries every entry.token_benchmark.pywarns when embeddings are missing. The standalone benchmark's default NL questions need semantic search to match anything; without embeddings the benchmark used to silently report 0× reduction ratios. Now logs an explicit warning pointing users toembed.
Documentation
docs/REPRODUCING.md(new). End-to-end recipe, canonical numbers, the tiktoken calibration table, and a side-by-side explanation of the three "token" benchmarks in the codebase.README.mdToken Savings section (new collapsible block under Usage). Plain-English explanation ofdetect-changes --briefvsupdate --brief— read-only vs re-parses-first — with a side-by-side decision table.docs/COMMANDS.mdlists the new--brief,--verify, andembedforms with an inline "which one?" comment block on the analysis pair.Updated benchmark headline to reflect today's pinned-SHA snapshot: range 38× – 528× (median ~82×) across the 6 repos, 100% impact recall, F1 0.71 across 13 commits. Old numbers (73× – 895×) reflected pre-fix conditions (leftover build artifacts, smaller graph responses) and have been superseded.
9 Excalidraw diagrams regenerated with current canonical numbers (
diagrams/*.excalidraw, source kept locally; PNG re-exports manual).
Demo
diagrams/context-savings-demo.gif— 44 s screencast showing both CLI surfaces and the--verifycross-check. Rendered fromdiagrams/context-savings-demo.tape(regenerable withvhs).
[2.3.4] - 2026-05-25
Focused reliability and token-efficiency release for MCP/CLI review workflows. No breaking changes.
Added
Estimated context savings metadata for graph-filtered review/impact/architecture responses. The new
context_savingsfield is intentionally compact (estimated,saved_tokens,saved_percent) and uses the existing conservative character-count approximation rather than claiming exact tokenization.CLI estimated savings line for
code-review-graph detect-changes --brief; full JSON output includes the same compactcontext_savingsmetadata.
Changed
Architecture overview is compact by default:
get_architecture_overview_toolnow defaults todetail_level="minimal", dropping per-community member lists and aggregating cross-community edges by community pair. Full per-edge output remains available withdetail_level="standard".Bounded change analysis:
detect_changes_toolcan now cap very large changed-function and transitive-test frontiers withCRG_MAX_CHANGED_FUNCSandCRG_MAX_TRANSITIVE_FRONTIER, and can return a structured timeout error viaCRG_TOOL_TIMEOUT.
Fixed
Windows semantic search deadlock (#508/#507): local embedding models are pre-warmed on the main thread on Windows before FastMCP starts worker dispatch.
Rust test detection (#503/#502): Rust
#[test]and common async test attributes now produceTestnodes.Generated hook stdin handling (#494/#493): Codex and Claude hook commands drain stdin to avoid caller-side broken pipes on large hook payloads.
Cross-file callers (#486/#472):
callers_ofnow returns cross-file callers even when same-file callers exist.Graph path lookup (#469): review, impact, and file-summary tools resolve user-facing paths to the path format stored in the graph.
Bundled MCP docs (#485/#480):
get_docs_sectioncan load the packagedLLM-OPTIMIZED-REFERENCE.mdfrom installed wheels.Local embedding provider availability (#484/#448): missing
sentence-transformersnow reports local provider unavailability instead of silently producing zero embeddings.Dead-code response fields (#481/#447): dead-code results now include
file_path,relative_path, andlanguagewhile preserving the legacyfilekey.SVN root validation (#456): MCP/daemon/registry root validation now accepts
.svnworking copies consistently.CLI postprocess flags (#487):
build --skip-postprocessandupdate --skip-flowsno longer run an extra full post-processing pass.
Documentation
Updated stale release-facing version references for 2.3.4.
Replaced fragile language-count wording with current broad language and notebook support wording.
Added the missing VS Code extension
0.2.2changelog entry without changing the extension package version.
Tests
Added regression coverage for compact architecture overview output and #476 mitigation.
Added tests for estimated context savings calculation, compact metadata shape, MCP metadata, CLI brief/JSON output, Rust test parsing, hook stdin draining, graph path resolution, dead-code fields, SVN root validation, CLI postprocess flags, embedding availability, and bounded detect-changes behavior.
[2.3.3] - 2026-05-08
Large additive release accumulated since v2.3.2 — 141 non-merge commits, 8 new languages/extensions, 5 new platform install targets, 6 new framework call resolvers, comprehensive Windows hardening, VS Code accessibility pass, and a full sweep of community PRs.
Added
Languages and extensions
Nix support (flake-aware):
.nixfiles are parsed via thenixtree-sitter grammar shipped withtree-sitter-language-pack. Top-level and nested attrset bindings becomeFunctionnodes with flattened dotted names (e.g.packages.default,devShells.default). Inflake.nix,inputs.<name>.url = "..."strings emitIMPORTS_FROMedges to the URL;import <path>andcallPackage <path> <args>applications in any.nixfile emitIMPORTS_FROMedges (relative paths are resolved against the caller's directory). Adds 7 tests (TestNixParsing) and fixturestests/fixtures/sample.nix,tests/fixtures/sample_module.nix.GDScript support (Godot, PR #316):
.gdfiles are parsed via thegdscripttree-sitter grammar. Extracts inner classes (class Name:), the file-levelclass_nameidentity, functions (includingstatic func),extendsparent class as an IMPORTS_FROM edge, direct calls and method calls. Adds 10 tests andtests/fixtures/sample.gd.Verilog / SystemVerilog support (PR #428):
.v,.sv,.svhfiles parse modules, classes, packages, interfaces, programs, functions, and tasks via theverilogtree-sitter grammar. Per-construct extractors with dedicated unit tests.SQL support (PR #398):
.sqlfiles parseCREATE FUNCTION,CREATE PROCEDURE,CREATE TABLE, andCREATE VIEWstatements; emits CALLS edges for function invocations.ReScript support (PR #309/323):
.res/.resiparsing for modules, let-bindings, and external declarations..hhextension support: C++ header variants now resolve into the C++ parser path..kshextension and shebang-based detection (PR #276):.kshfiles parsed as shell; extension-less scripts detected via#!/usr/bin/env <lang>shebang lines.Julia improvements: parametric constructors,
@enumdeclarations, andpublicmodule exports now produce graph nodes.
Platforms and install targets
GitHub Copilot platform support (PR #445):
code-review-graph install --platform copilotwrites Copilot-CLI-compatible MCP config without generating Claude-specific skill artifacts.Gemini CLI platform support (PR #391):
--platform gemini-cliskips Claude skills and writes Gemini-native MCP config.Qoder platform support (PR #245):
--platform qoderadds MCP server registration for Qoder.OpenCode plugin support (PR #198 via #366):
--platform opencoderegisters the MCP server with the OpenCode plugin manifest.Cursor hooks support (PR #196):
installnow writes Cursor hook entries (gated behind~/.cursordetection so non-Cursor users are not affected).Codex install alignment: native Codex integration path; no Claude skill files generated for Codex targets.
MCP server and CLI features
crg-daemon: new multi-repo watch daemon that supervises per-repo file watchers viasubprocess.Popenchild processes. Documented in README, COMMANDS.md, and ROADMAP.md. 35 dedicated tests.Streamable HTTP transport (PR #277): MCP server can now run over streamable HTTP in addition to stdio.
serve --toolsflag andCRG_TOOLSenv var: MCP tool filtering at startup so callers can expose only the subset they need.--repoprecedence and validation inget_docs_section(PR #378): honorsserve --repoand validates path containment before returning section content.Search enrichment via PreToolUse hooks (PR #248): hook-driven search index enrichment ahead of tool calls.
External database directory support: graph DB can now live on a network filesystem via the existing
CRG_DATA_DIRmechanism, with the file locking path adjusted accordingly.SVN support (PR #255): basic Subversion working-copy detection alongside git for change analysis.
Parser and resolver improvements
Spring DI call resolution (PR #413): receiver method calls (
this.userService.find(...)) resolve through@Autowired/constructor-injected fields to the concreteInjectedType.method. EmitsINJECTSedges and stereotype metadata (@Service,@Component,@Repository,@Controller); writes fully-qualifiedtarget_qualifiedsocallers_ofqueries work.Temporal workflow/activity call resolution:
WorkflowStub.start(...)andActivityStub.execute(...)resolve to their concrete workflow/activity implementations.Kafka consumer/producer detection:
@KafkaListener-annotated methods andKafkaTemplate.send(...)calls emitCONSUMESandPRODUCESedges keyed on topic.Jedi-based Python call resolution (PR #247): improved cross-file Python call resolution using the Jedi static-analysis library.
Python callback REFERENCES edges (PR #363): function names passed as callback arguments (
schedule(my_handler)) now emitREFERENCESedges instead of being dropped.Mocha TDD
suite()recognition (PR #423): files using Mocha's TDD interface now classify as tests.Bun test runtime support (PR #421): files importing
bun:testare detected as tests.__tests__/directory detection (PR #422): all files under__tests__/are classified as test files regardless of name.
Embeddings
OpenAI-compatible embedding provider (PR #321): pluggable provider supporting OpenAI, Azure OpenAI, and any OpenAI-API-compatible endpoint, with configurable batch size.
Localized embedding READMEs: provider docs translated for non-English users.
Visualization, accessibility, and VS Code extension
WCAG 2.1 AA contrast pass: 4.5:1 minimum text contrast across the standalone HTML and VS Code webview.
Distinct
d3.symbolshapes per node kind: colorblind-friendly differentiation in both the standalone visualization and the VS Code webview.Keyboard navigation: tab/arrow/enter/escape navigation across nodes, with focus styles and a skip-link to bypass the legend.
ARIA roles and labels: tooltip, detail panel, legend, search results, communities button, edge-pill keyboard activation, search input label.
Help overlay: interaction guide for both the standalone HTML and the keyboard-help overlay.
Empty-state webview in VS Code with a contextual depth slider and tooltip.
Edge filter popover in the VS Code toolbar — fixes density on narrow panels.
Detail panel relocated to the left so it no longer occludes top controls; close button restyled to match the toolbar.
CONTAINS edge opacity raised from 0.08 → 0.14 for visibility on dense graphs.
GitHub Dark palette unified across the VS Code extension.
IMPLEMENTS,TESTED_BY,DEPENDS_ONedge types rendered in the standalone HTML visualization.
Fixed
__version__ reporting
code_review_graph.__version__now matchespyproject.toml(was2.1.0since the v2.1.0 release). The User-Agent header thatembeddings.pysends on cloud HTTP requests is built from this string, so cloud-embedding traffic was being mis-attributed across all releases between v2.1.0 and v2.3.2.
C++ / Java / PHP parsing
C++ scoped/destructor/operator method names (PR #371, PR #403):
void Foo::bar(),Foo::~Foo(),Foo::operator==(...)now extract the correct member name instead of the qualifier or the operator token.Java method name extraction (PR #275): method names are now read from the
identifierchild ofmethod_declarationrather than the return-type child (which was producing names likeint64).Java superclass / super-interfaces (PR #278):
extends Fooandimplements Bar, Baznow extract bare type names from thesuperclass/super_interfacesAST nodes.Java import resolution to file paths (PR #280):
import com.example.foo.Barresolves throughsrc/main/java/...and configured source roots to the actual file.PHP
CALLextraction (PR #298): method calls ($obj->foo()), static calls (Foo::bar()), and unqualified function calls now produce CALLS edges.Module-scope
CALLSedges (PR #285): top-level executable statements emit CALLS edges (previously only function/method bodies did).
Windows
Windows MCP stdio hang on long-running tools (PR #400, PR #292): thread-pool selection now auto-selects on Windows MCP stdio so build/embed do not deadlock.
Windows MCP stdin hang (PR #425): all
git/svnsubprocesses now run withstdin=DEVNULL, preventing the FastMCP-stdio buffer from filling on Windows.Windows non-UTF-8 locale:
subprocess.runcalls now passencoding="utf-8"so cp1252 hosts no longer mis-decode git output.Windows test failures (PR #274): UTF-8 encoding, CRLF normalization, and
stop_atboundary handling fixes for Windows CI.
Hooks and install
Hooks JSON schema (PR #288):
hooks.jsonvalidation no longer fails on the wrapper layout —matcheris required and the wrapper is removed.Hooks merge instead of overwrite (PR #114, PR #145, PR #203):
install_hooksnow merges into existing hook arrays and creates asettings.json.bakbackup before modifying user config.Pre-commit hook adds
updatecommand (PR #315): generated pre-commit hook runscode-review-graph updaterather than the obsolete subcommand.Skip hooks gracefully outside git (PR #293):
installno longer fails when invoked from a non-git directory.Poetry / uv environment detection (PR #287):
installnow generates the correct MCP serve command for projects using Poetry or uv.Hook quoting and
docsrepo_root (PR #192): hook commands now quote repo paths with spaces, and the docs repo path is restored on install.
MCP server
fastmcp 3.x compatibility:
_apply_tool_filterrestored on fastmcp ≥3, dependency floor bumped tofastmcp>=3.2.4to pick up the upstream Windows stdio EOF fixes.FastMCP banner suppressed for stdio transport (PR #290): the startup banner no longer corrupts the stdio handshake.
MCP config
cwd, skills path, and JSONC parsing: install now writescwdinto MCP config, points skills at the correct project path, and tolerates JSONC (comments + trailing commas) in existing config files.
SQLite and post-processing
SQLite transaction safety, FTS5 sync, and atomic operations (PR #94, PR #279): nested-transaction handling, FTS5 content-table synchronization, and resource cleanup on error paths.
CLI build/update/watch run post-processing (PR #98): signatures, FTS, flows, and communities are now refreshed after every CLI graph mutation (was previously only refreshed by the MCP server).
reconcile()auto-builds graphs and registers new repos: cold-start path no longer requires a manualbuildbeforereconcile.Flow trace adjacency in-memory (PR #296):
trace_flowsloads adjacency once instead of querying SQLite per hop.
Other
UnicodeDecodeErrorinread_text(PR #303): all text reads now useerrors="replace".Dead-code callback references (PR #424): functions referenced as callbacks no longer mis-classify as dead code.
Skills.py table formatting (PR #302).
Search.py duplicate logger removed.
statuscommand reports alive/dead from the persisted state file.
Security
Embeddings RCE hardening (PR #397): remote code execution paths in the embedding provider are gated behind an explicit env var; cloud HTTP requests now send a versioned User-Agent (PR #390) and refuse to mix indexes built with different providers.
Documentation
MCP tools documentation (PR #306): catalog of all MCP tools with usage examples.
venv usage guide (PR #307).
Windows setup guide for Claude Code MCP integration.
pipx / PyPI failure troubleshooting with a
diagnose_pypi_connectivity.pydiagnostic script.MseeP.ai badge added to README (PR #399).
Maintenance
Beads (
bd) issue tracking initialized for the project (bd primefor workflow context).iCloud sync duplicate files removed from the working tree.
Working spec docs moved out of git (already in
.gitignore).CI lint and test failures swept across multiple merged PRs.
Upgrade notes
uvx --reinstall code-review-graphorpip install -U code-review-graph.Re-run
code-review-graph installonce after upgrading to pick up the JSONC-tolerant config writer and the correctedcwd/ skills path in.mcp.json.The
__version__fix changes the User-Agent string emitted by cloud embedding providers fromcode-review-graph/2.1.0tocode-review-graph/2.3.3. Anyone allow-listing the old User-Agent on a proxy needs to update their rule.VS Code extension still ships separately — repackage and republish the
.vsixif you want the v2.3.3 a11y improvements in the Marketplace build.
[2.3.2] - 2026-04-14
Major feature release — 15 new capabilities, 6 community PRs merged, 6 new MCP tools, 4 new languages, multi-format export, and graph analysis suite.
Added
Hub node detection (
get_hub_nodes_tool): find the most-connected nodes in the codebase (architectural hotspots) by in+out degree, excluding File nodes.Bridge node detection (
get_bridge_nodes_tool): find architectural chokepoints via betweenness centrality with sampling approximation for graphs >5000 nodes.Knowledge gap analysis (
get_knowledge_gaps_tool): identify structural weaknesses — isolated nodes, thin communities (<3 members), untested hotspots, and single-file communities.Surprise scoring (
get_surprising_connections_tool): composite scoring for unexpected architectural coupling (cross-community, cross-language, peripheral-to-hub, cross-test-boundary).Suggested questions (
get_suggested_questions_tool): auto-generate prioritized review questions from graph analysis (bridge nodes, untested hubs, surprising connections, thin communities).BFS/DFS traversal (
traverse_graph_tool): free-form graph exploration from any node with configurable depth (1-6) and token budget.Edge confidence scoring: three-tier system (EXTRACTED/INFERRED/AMBIGUOUS) with float confidence scores on all edges. Schema migration v9.
Export formats: GraphML (Gephi/yEd/Cytoscape), Neo4j Cypher statements, Obsidian vault (wikilinks + YAML frontmatter + community pages), SVG static graph. CLI:
visualize --format graphml|cypher|obsidian|svg.Graph diff: snapshot/compare graph state over time — new/removed nodes, edges, community membership changes.
Token reduction benchmark: measure naive full-corpus tokens vs graph query tokens with per-question reduction ratios.
Memory/feedback loop: persist Q&A results as markdown for re-ingestion via
save_result/list_memories/clear_memories.Oversized community auto-splitting: communities exceeding 25% of graph are recursively split via Leiden algorithm.
4 new languages: Zig, PowerShell, Julia, Svelte SFC (23 total).
Visualization enhancements: node size scaled by degree, community legend with toggle visibility, improved interactivity.
README translations: Simplified Chinese, Japanese, Korean, Hindi.
Merged community PRs
#127 (xtfer): SQLite compound edge indexes for query performance.
#184 (realkotob): batch
_compute_summaries— fixes build hangs on large repos.#202 (lngyeen): Swift extension detection, inheritance edges, type kind metadata.
#249 (gzenz): community detection resolution scaling (21x speedup), expanded framework patterns, framework-aware dead code detection (56 new tests).
#253 (cwoolum): automatic graph build for new worktrees in Claude Code.
#267 (jindalarpit): Kiro platform support with 9 tests.
Changed
MCP tool count: 22 → 28.
Schema version: 8 → 9 (edge confidence columns).
Community detection uses resolution scaling for large graphs.
Risk scoring uses weighted flow criticality and graduated test coverage.
Dead code detection is framework-aware (ORM models, Pydantic, CDK constructs filtered).
Flow entry points expanded with 30+ framework decorator patterns.
[2.3.1] - 2026-04-11
Hotfix for the Windows long-running-MCP-tool hang that v2.2.4 only partially fixed.
Fixed
Windows MCP hang on long-running tools (PR #231, fixes #46, #136): follow-up to v2.2.4. @dev-limucc reported on #136 that the
WindowsSelectorEventLoopPolicyfix from v2.2.4 was necessary but not sufficient — read-only tools worked, butbuild_or_update_graph_tool(full_rebuild=True)andembed_graph_toolstill hung indefinitely on Windows 11 / Python 3.14. Root cause: FastMCP 2.x dispatches sync handlers inline on the only event-loop thread, so handlers that run for more than a few seconds (especially those that spawn subprocesses or do CPU-bound inference) stop the loop from pumping stdin/stdout. Fix: converted the five heavy tools (build_or_update_graph_tool,run_postprocess_tool,embed_graph_tool,detect_changes_tool,generate_wiki_tool) toasync defand offloaded the blocking work viaasyncio.to_thread. The other 19 tools are fast SQLite-read paths and stay sync. Zero config, works on every platform. New regression tests assert the five tools are registered as coroutines AND that each one's source literally containsasyncio.to_threadas a defense-in-depth lock-in.
[2.3.0] - 2026-04-11
Additive feature release — new language parsers, new platform install target, MCP tool UX improvements, and out-of-tree graph storage. No breaking changes from v2.2.4.
Added
Elixir parser (PR #228, closes #112):
.exand.exsfiles now produce modules as Class nodes,def/defp/defmacro/defmacropas Function/Test nodes attached to their enclosing module,alias/import/require/useasIMPORTS_FROMedges, and everything else asCALLSedges. Internal call resolution walks intodo_blockbodies soMathHelpers.doublecorrectly resolves its call toCalculator.compute.Objective-C parser (PR #227, closes #88):
.mfiles parse classes (@interface,@implementation,@protocol), instance and class methods,[receiver message:args]message expressions, C-stylemain(), and#import/#include. Multi-part selectors likeadd:to:keepaddas the canonical method name.Bash/Shell parser (PR #227, closes #197):
.sh,.bash, and.zshfiles parse functions,commandinvocations asCALLS, andsource path/. pathasIMPORTS_FROMedges with path resolution when the target file exists.Qwen Code as a supported MCP install platform (PR #227, closes #83):
code-review-graph install --platform qwenwrites a merged~/.qwen/settings.jsonusing the samemcpServersschema as Cursor/Windsurf — it does not clobber existing Qwen config.apply_refactor_tooldry-run mode (PR #228, closes #176): newdry_run: bool = Falseparameter on the MCP tool and underlyingapply_refactor()function. When true, returns a unified diff per file without touching disk and leaves therefactor_idvalid for a follow-up real apply. Multi-edit files now apply sequentially against updated content in both modes (fixes a subtle bug where separate edits on the same file could stomp each other).CRG_DATA_DIRenvironment variable (PR #228, closes #155): when set, replaces the default<repo>/.code-review-graphdirectory verbatim. Useful for ephemeral workspaces, Docker volumes, shared CI caches, and multi-repo orchestrators. Supported by the CLI, MCP tools, and the registry.CRG_REPO_ROOTenvironment variable (PR #228, closes #155):find_project_root()now checksCRG_REPO_ROOTbefore the usual git-root walk — useful for anyone scripting the CLI from a cwd outside the target repo.install --no-instructionsand-y/--yesflags (PR #228, closes #173): new flags oncode-review-graph installto opt out of theCLAUDE.md/AGENTS.md/.cursorrules/.windsurfrulesinjection entirely (--no-instructions) or auto-confirm it without an interactive prompt (-y/--yes). The CLI also now prints the list of files it will touch before writing, so even without--dry-runusers see what's coming.Cloud embeddings stderr warning (PR #228, closes #174):
get_provider()now prints an explicit warning to stderr before returning a Google Gemini or MiniMax provider, explaining that source code will be sent to an external API.CRG_ACCEPT_CLOUD_EMBEDDINGS=1suppresses the warning for scripted workflows. The warning is on stderr only — it never writes to stdout or reads from stdin, so the MCP stdio transport remains uncorrupted.TROUBLESHOOTING quick-reference (PR #228): new top section in
docs/TROUBLESHOOTING.mdcovering the four most common support questions — hook schema errors,command not foundafter pip install, project-vs-user scoping, and "built the graph but Claude Code doesn't see it".
Fixed
Multi-edit refactor correctness (PR #228): when a single
apply_refactorcall had multiple edits targeting the same file, the previous implementation re-read the file once per edit and could silently stomp earlier changes. The plan-computation step now groups edits by file and applies them sequentially against the updated content; this fix applies to both the real-write and the new dry-run path.
Changed
installandinitcommands now preview instruction-file targets before writing (no-op if nothing would change). This is always-on and does not require--dry-run.Default embedding path remains fully local (
sentence-transformers); no behavior change unless you explicitly opt in to a cloud provider.
Deprecated
Nothing.
Security
The cloud-embedding stderr warning (#174) is a privacy improvement; it does not change the behavior of offline local embeddings, which remain the default.
Upgrade notes
Nothing to do beyond
uvx --reinstall code-review-graphorpip install -U code-review-graph. If you're coming from v2.2.2 or earlier, re-runcode-review-graph installonce to pick up the v2.2.3 hook schema rewrite.CRG_DATA_DIRis optional — if you don't set it, graphs continue to live at<repo>/.code-review-graphas before.VS Code extension v0.2.2 (from v2.2.4) still needs to be repackaged and republished separately; the PyPI
publish.ymlworkflow does not cover it.
Superseded PRs
PR #204 (install preview, @lngyeen) — reimplemented cleanly in #228 with
isatty()-guarded confirmation.PR #207 (
CRG_DATA_DIR/CRG_REPO_ROOT, @yashmewada9618) — reimplemented cleanly in #228 withoutinput()-on-stdio andmcp._local_onlyfragility.PR #179 (cloud embeddings warning, @Bakul2006) — reimplemented cleanly in #228 with stderr-only messaging and no stdio reads.
Credit to @lngyeen, @yashmewada9618, and @Bakul2006 for the original designs.
[2.2.4] - 2026-04-11
Ships the 11 bugs from PR #222 plus the v2.2.3.1 smoke-test hotfixes, for users upgrading directly from v2.2.3 or earlier.
Security
fastmcp bumped from 1.0 → ≥2.14.0 (PR #222, fixes #139, #195): closes CVE-2025-62800 (XSS), CVE-2025-62801 (command injection via server_name), CVE-2025-66416 (Confused Deputy). Transitively drops the
docket → fakeredischain that was broken by aFakeConnection→FakeRedisConnectionrename in recent fakeredis releases (#195). The FastMCP public API (FastMCP(name, instructions=...),@mcp.tool(),@mcp.prompt(),mcp.run(transport="stdio")) is unchanged across the 1 → 2 bump, so no source changes were needed beyond the pin. All 24 tools verified to register on fastmcp 2.14.6 and round-trip real per-repo data via stdio MCP in a 6-repo smoke test.
Fixed
Windows build/embed hangs (PR #222, fixes #46, #136):
main()now setsWindowsSelectorEventLoopPolicybeforemcp.run()onsys.platform == "win32". The defaultProactorEventLoopon Windows Python 3.8+ deadlocks withProcessPoolExecutor(used byfull_build) over a stdio MCP transport — producing the silent "Synthesizing…" hangs onbuildandembed_graph_tool. This is a no-op on macOS/Linux. Note: the fix was applied blind; maintainer could not verify on Windows. Please open a fresh issue if you still see a hang on v2.2.4 Windows with eithersentence-transformersor Gemini providers.Go method receivers (PR #222, fixes #190):
func (s *T) Foo()now attachesFootoTas a member (parent_name="T") with the usualCONTAINSedge instead of appearing as a top-level function. New_get_go_receiver_type()helper walks the method_declaration's first parameter_list to extract the receiver type name.Dart parser — three bugs (PR #222, fixes #87):
Dart
CALLSedges (_extract_dart_calls_from_children()) — tree-sitter-dart doesn't wrap calls in a singlecall_expressionnode; the pattern isidentifier + selector > argument_part. New walker handles both direct (print('x')) and method-chained (obj.foo()) shapes.Dart
package:URI resolution in_do_resolve_module()—package:<pkgname>/<sub_path>now walks up to apubspec.yamlwhosename:declaration matches<pkgname>and resolves to<root>/lib/<sub_path>.inheritors_ofbare-vs-qualified name mismatch intools/query.py— falls back tosearch_edges_by_target_name(node.name, kind=...)forINHERITS/IMPLEMENTSwhen the qualified-name lookup returns nothing. Affects all languages (INHERITS targets are stored as bare strings for every language), not just Dart.
Nested
node_modulesand framework ignore defaults (PR #222, fixes #91):_should_ignore()now treats single-segment<dir>/**patterns as "this directory at any depth", sonode_modules/**also matchespackages/app/node_modules/react/index.jsinside monorepos. ExtendedDEFAULT_IGNORE_PATTERNSwith Laravel/Composer (vendor/**,bootstrap/cache/**,public/build/**), Ruby (.bundle/**), Gradle (.gradle/**,*.jar), Flutter/Dart (.dart_tool/**,.pub-cache/**), and genericcoverage/**,.cache/**. Deliberately did not addpackages/**orbin/**/obj/**— those are false positives in yarn/pnpm workspace monorepos and .NET source trees respectively.Bare
except Exceptioncleanup (PR #222, fixes #194): Replaced with specific exception classes +logger.debug(...)in 11 files (cli.py,graph.py,migrations.py,parser.py,registry.py,tools/context.py,tsconfig_resolver.py,visualization.py,wiki.py,eval/benchmarks/search_quality.py). No behavioral change; debuggability improvement.Visualization auto-collapse hiding all edges (PR #222, fixes #132):
visualization.pyno longer unconditionally auto-collapses every File node on page load. Auto-collapse now only kicks in above 2000 nodes — previously any graph above ~300 nodes would silently hide every CALLS/IMPORTS/INHERITS edge because they connect Functions/Classes nested inside the collapsed Files.evalcommand crashes onyaml.safe_load(PR #222, fixes #212):eval.runner.load_all_configs()now calls_require_yaml()before reading YAML, so users withoutcode-review-graph[eval]installed getImportError: pyyaml is required: pip install code-review-graph[eval]instead ofAttributeError: 'NoneType' object has no attribute 'safe_load'.
VS Code extension (0.2.2)
better-sqlite3bumped 11.x → 12.x (PR #222, fixes #218): VS Code 1.115 ships Electron 39 / V8 14.2 which removedv8::Context::GetIsolate(), the C++ API used bybetter-sqlite3@11. The extension couldn't activate at all — every command was undefined.[email protected]+(installs 12.8.0) uses the new V8 API and ships Electron 39 prebuilds.@types/better-sqlite3: ^7.6.8 → ^7.6.13, plus type-import adjustments insrc/backend/sqlite.tsfor theNode16module resolution and the new CJSexport =types. Extension version bumped to 0.2.2. Remember to repackage and republish the.vsix— the existingpublish.ymlworkflow only covers PyPI.
Carried forward from 2.2.3.1
serve --repo <X>is now honored by all 24 MCP tools (was only read byget_docs_section_tool). See #223.Wiki slug collisions no longer silently overwrite pages (~70% data loss on real repos). See #223.
Upgrade notes
uvx --reinstall code-review-graphorpip install -U code-review-graph, then re-runcode-review-graph install(the 2.2.3 hook-schema rewrite is still a requirement if you're coming from 2.2.2 or earlier).VS Code extension needs to be repackaged + republished separately; the Python release does not include it.
[2.2.3.1] - 2026-04-11
Hotfix on top of 2.2.3 for two bugs surfaced by a full first-time-user smoke test against six real OSS repos (express, fastapi, flask, gin, httpx, next.js).
Fixed
serve --repo <X>was ignored by 21 of 24 MCP tools (PR #223):main.pycaptured the--repoCLI flag into_default_repo_root, but onlyget_docs_section_toolread it. The other 21@mcp.tool()wrappers all tookrepo_root: Optional[str] = Noneand passed that straight through to the impl, which fell back tofind_repo_root()from cwd. The real-world blast radius is small — theinstallcommand writes.mcp.jsonwithout a--repoflag and Claude Code launches the server withcwd=<repo>— but anyone scriptingservemanually or running a multi-repo orchestrator would silently get the wrong graph. Added a single_resolve_repo_root()helper with explicit precedence (client arg >--repoflag >None → cwd) and threaded it through all 24 wrappers. New unit tests cover the precedence rules.Wiki slug collisions silently overwrote pages (PR #223):
_slugify()folds non-alphanumerics to dashes and truncates to 80 chars, so similar community names collided ("Data Processing","data processing","Data Processing"all →data-processing.md).generate_wiki()wrote each community to<slug>.mdregardless, so later iterations overwrote earlier files while the counter reported them as "updated". On the express smoke test this was ~70% silent data loss (32 real files vs 107 claimed pages). Fixed by tracking used slugs per-run and appending-2,-3, … until unique. Every community now gets its own page; the counter matches the physical file count;get_wiki_page()still resolves by name via the existing partial-match fallback. New regression test monkey-patches three colliding names and asserts no content loss.
[2.2.3] - 2026-04-11
Fixed
Claude Code hook schema (PR #208, fixes #97, #138, #163, #168, #172, #182, #188, #191, #201):
generate_hooks_config()now emits the valid v1.x+ Claude Code schema — every hook entry hasmatcher+ a nestedhooks: [{type, command, timeout}]array, and timeouts are in seconds. The invalidPreCommitevent has been removed; pre-commit checks are now installed as a real git hook viainstall_git_hook(). Users upgrading from 2.2.2 must re-runcode-review-graph installto rewrite.claude/settings.json.SQLite transaction nesting (PR #205, fixes #110, #135, #181):
GraphStore.__init__now connects withisolation_level=None, disabling Python's implicit transactions that were the root cause ofsqlite3.OperationalError: cannot start a transaction within a transactiononupdate.store_file_nodes_edgesadds a defensivein_transactionflush beforeBEGIN IMMEDIATE.Go method receivers (PR #166):
_extract_name_from_nodenow resolves Go method names fromfield_identifierinsidemethod_declaration, fixing method names that were previously picked up as the result type (e.g.int64) instead of the method name.UTF-8 decode errors in
detect_changes(PR #170, fixes #169): Diff parsing now useserrors="replace"so diffs containing binary files no longer crash the tool.--platformtarget scope (PR #142, fixes #133):code-review-graph install --platform <target>now correctly filters skills, hooks, and instruction files so you only get configuration for the requested platform.Large-repo community detection hangs (PR #213, PR #183): Removed recursive sub-community splitting, capped Leiden at
n_iterations=2, and batchedstore_communitieswrites. 100k+ node graphs no longer hang in_compute_summaries.CI: ruff lint +
tomllibon Python 3.10 (PR #220) —tests/test_skills.pynow uses a conditionaltomlibackport on 3.10,N806/E501/W291fixes inskills.py/communities.py/parser.py, and the embeddednoqareference invisualization.pywas rephrased so ruff stops parsing it as a directive.Missing dev dependencies (PR #159):
pytest-covadded to dev extras, 50 ruff errors swept, one failing test fixed.JSX component CALLS edges (PR #154): JSX component usage now produces CALLS edges so component-to-component relationships appear in the graph.
Added
Codex platform install support (PR #177):
code-review-graph install --platform codexappends amcp_servers.code-review-graphsection to~/.codex/config.tomlwithout overwriting existing Codex settings.Luau language support (PR #165, closes #153): Roblox Luau (
.luau) parsing — functions, classes, local functions, requires, tests.REFERENCES edge type (PR #217): New edge kind for symbol references that aren't direct calls (map/dispatch lookups, string-keyed handlers), including Python and TypeScript patterns.
recurse_submodulesbuild option (PR #215): Build/update can now optionally recurse into git submodules..gitignoredefault for.code-review-graph/(PR #185): Fresh installs automatically add the SQLite DB directory to.gitignoreso the database isn't accidentally committed.Clearer gitignore docs (PR #171, closes #157): Documentation now spells out that
code-review-graphalready respects.gitignoreviagit ls-files.
Changed
Community detection is now bounded — large repos complete in reasonable time instead of hanging indefinitely.
Fixed
install_hooksnow merges instead of overwriting (PR #203, fixes #114):install_hooks()previously useddict.update()which clobbered any user-defined hooks in.claude/settings.json. Now merges new entries into existing hook arrays, preserving user hooks. Creates a backup (settings.json.bak) before modification.
[2.2.2] - 2026-04-08
Added
Kotlin call extraction:
simple_identifier+navigation_expressionsupport for Kotlin method calls (PR #107)JUnit/Kotlin test detection: Annotation-based test classification (
@Test,@ParameterizedTest, etc.) for Java/Kotlin/C# (PR #107)
Fixed
Windows encoding crash: All
write_text/read_textcalls inskills.pynow useencoding='utf-8'explicitly (PR #152, fixes #147, #148)Invalid
--quietflag in hooks: Removed non-existent--quietand--jsonflags from generated hook commands (PR #152, fixes #149)
Housekeeping
Untracked
.claude-plugin/directory and added to.gitignoreGitHub issue triage: responded to 30+ issues, closed 14, reviewed 24 PRs
[2.2.1] - 2026-04-07
Added
Parallel parsing:
ProcessPoolExecutorfor 3-5x faster builds (CRG_PARSE_WORKERS,CRG_SERIAL_PARSE)Lazy post-processing:
postprocess="full"|"minimal"|"none"parameter,run_postprocessMCP tool + CLI commandSQLite-native BFS: Recursive CTE replaces NetworkX for impact analysis (
CRG_BFS_ENGINE)Configurable limits:
CRG_MAX_IMPACT_NODES,CRG_MAX_IMPACT_DEPTH,CRG_MAX_BFS_DEPTH,CRG_MAX_SEARCH_RESULTSMulti-hop dependents: N-hop
find_dependents()withCRG_DEPENDENT_HOPS(default 2) and 500-file capToken-efficient output:
detail_level="minimal"on 8 tools for 40-60% token reductionget_minimal_contexttool: Ultra-compact entry point (~100 tokens) with task-based tool routingToken-efficient prompts: All 5 MCP prompts rewritten with minimal-first workflows
Incremental flow/community updates:
incremental_trace_flows(),incremental_detect_communities()Visualization aggregation: Community/file/auto modes with drill-down for large graphs (
--mode)Token-efficiency benchmarks: 5 workflow benchmarks in
eval/token_benchmark.pyDB schema v6: Pre-computed
community_summaries,flow_snapshots,risk_indextablesToken Efficiency Rules in all skill templates and CLAUDE.md
Changed
CLI
build/updatesupport--skip-flows,--skip-postprocessflagsPostToolUse hook uses
--skip-flowsfor faster incremental updatesVS Code extension schema version bumped to v6
Fixed
mypy type errors in parallel parsing and context tool
Bandit false positive on prompt preamble string
Import sorting in graph.py, main.py, tools/init.py
Unused imports cleaned up in cli.py
Housekeeping
Gitignore: untrack
marketing-diagram.excalidraw,evaluate/results/,evaluate/reports/Updated FEATURES.md, LLM-OPTIMIZED-REFERENCE.md, CHANGELOG.md for v2.2.1
[2.1.0] - 2026-04-03
Added
Jupyter notebook parsing: Parse
.ipynbfiles — extract functions, classes, imports across Python, R, and SQL cellsDatabricks notebook parsing: Parse Databricks
.pynotebook exports with# COMMAND ----------cell boundariesLua language support: Full parsing for
.luafiles (functions, local functions, method calls, requires) — 20th languagePerl XS support: Parse
.xsfiles with improved Perl call detection and test coverageZero-config onboarding:
installnow sets up skills, hooks, and CLAUDE.md by default so the graph is used automaticallyPlatform rule injection: Graph instructions injected into all platform rule files (CLAUDE.md, .cursorrules, etc.) on install
Smart install detection: Auto-detects whether installed via uvx or pip and generates correct
.mcp.json--platform claude-codealias: Accepts bothclaudeandclaude-codeas platform names
Fixed
JS/TS arrow functions indexed:
const foo = () => {}andconst bar = function() {}now correctly appear as nodes (#66)importers_ofpath resolution: Normalized withresolve()to match stored edge targets (#65)Custom embedding models: Support for custom model architectures and restored model param wiring in search (#79)
[2.0.0] - 2026-03-27
Added
12 new features: flows, communities, hybrid search, change analysis, refactoring, hints, prompts, skills, wiki, multi-repo registry, migrations, eval framework
14 new modules (~10,000 lines):
flows.py,communities.py,search.py,changes.py,refactor.py,hints.py,prompts.py,skills.py,wiki.py,registry.py,migrations.py,eval/15 new MCP tools:
list_flows,get_flow,get_affected_flows,list_communities,get_community,get_architecture_overview,detect_changes,refactor,apply_refactor,generate_wiki,get_wiki_page,list_repos,cross_repo_search,find_large_functions,semantic_search_nodes5 MCP prompts:
review_changes,architecture_map,debug_issue,onboard_developer,pre_merge_check7 new CLI commands:
detect-changes,wiki,eval,register,unregister,repos,install --skills/--hooks/--allInteractive visualization upgrade: Detail panel, community coloring, flow path highlighting, search-to-zoom, kind filters
Security
Fix path traversal in wiki page reader
Add regex allowlist for git ref validation
Add explicit SSL context for MiniMax API
Fixed
Fix git diff argument ordering (broke incremental updates)
Fix
node_qualified_nameschema mismatch in wiki flow queryBatch N+1 queries in
get_impact_radiusand risk scoring
Architecture
Decompose
_extract_from_treeinto 6 focused methodsAdd 17 public query methods to
GraphStoreSplit
tools.pyinto 10 themed sub-modules
[1.8.4] - 2026-03-20
Added
Vue SFC parsing: Parse
.vueSingle File Components by extracting<script>blocks with automaticlang="ts"detectionSolidity support: Full parsing for
.solfiles (functions, events, modifiers, inheritance)find_large_functions_tool: New MCP tool to find functions, classes, or files exceeding a line-count thresholdCall target resolution: Bare call targets resolved to qualified names using same-file definitions (
_resolve_call_targets)Multi-word AND search:
search_nodesnow requires all words to match (case-insensitive)Impact radius pagination:
get_impact_radiusreturnstruncatedflag,total_impactedcount, and acceptsmax_resultsparameter
Changed
Language count updated from 12 to 14 across all documentation
MCP tool count updated from 8 to 9 across all documentation
VS Code extension updated to v0.2.0 with 5 new commands documented
Fixed
Test assertions updated to handle qualified call targets from
_resolve_call_targets
[1.8.3] - 2026-03-20
Fixed
Parser recursion guard: Added
_MAX_AST_DEPTH = 180limit to_extract_from_tree()preventing stack overflow on deeply nested ASTsModule cache bound: Added
_MODULE_CACHE_MAX = 15_000with automatic eviction to prevent unbounded memory growth in_module_file_cacheEmbeddings thread safety: Added
check_same_thread=FalsetoEmbeddingStoreSQLite connectionEmbeddings retry logic: Added
_call_with_retry()with exponential backoff for Google Gemini API callsVisualization XSS hardening: Added
</to<\/replacement in JSON serialization to prevent script injectionCLI error handling: Split broad
exceptinto specificjson.JSONDecodeErrorand(KeyError, TypeError)handlersGit timeout: Made configurable via
CRG_GIT_TIMEOUTenvironment variable (default 30s)
Added
Governance files: Added CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md
Project URLs: Added Homepage, Repository, Issues, Changelog URLs to pyproject.toml metadata
[1.8.2] - 2026-03-17
Fixed
C# parsing broken: Renamed language identifier from
c_sharptocsharpto matchtree-sitter-language-pack's actual identifier. Previously, all C# files were silently skipped because_get_parser()swallowed theLookupError.
[1.8.1] - 2026-03-17
Fixed
Add missing
max_nodesparameter toget_impact_radiusmethod signature (causedNameErrorat runtime)Fix
.gitignoretest assertion to match expanded comment format
[1.8.0] - 2026-03-17
Security
Prompt injection mitigation: Node names are now sanitized (control characters stripped, length capped at 256) before appearing in MCP tool responses, preventing graph-laundered prompt injection attacks
Path traversal protection:
repo_rootparameter now validates that the target directory contains a.gitor.code-review-graphdirectory, preventing arbitrary file exfiltration via MCP toolsVSCode RCE fix:
cliPathsetting is now scoped tomachinelevel only, preventing malicious workspace settings from pointing to attacker-controlled binariesXSS fix in visualization:
escH()now escapes quotes and backticks in addition to angle brackets, closing stored XSS via crafted node names in generated HTMLSRI for CDN assets: D3.js script tag now includes
integrityandcrossoriginattributes to prevent CDN compromiseSecure nonce generation: VSCode webview CSP nonces now use
crypto.randomBytes()instead ofMath.random()Symlink protection: Build, watch mode, and file collection now skip symbolic links to prevent parsing files outside the repository
TOCTOU elimination: File bytes are now read once, then hashed and parsed from the same buffer, closing the time-of-check-to-time-of-use gap
Fixed
Thread-safe NetworkX cache: Added
threading.Lockaround graph cache reads/writes to prevent race conditions between watch mode and MCP request handlingBFS resource limits: Impact radius traversal now caps at 500 nodes to prevent memory exhaustion on dense graphs
SQL parameter batching:
get_edges_amongnow batches queries to stay under SQLite's variable limit on large node setsDatabase path leakage: Improved
.gitignoreinside.code-review-graph/with explicit warnings about absolute paths in the database
Changed
Pinned dependency bounds: All dependencies now have upper-bound version constraints to mitigate supply-chain risks
[1.7.2] - 2026-03-09
Fixed
Watch mode thread safety: SQLite connections now use
check_same_thread=Falsefor Python 3.10/3.11 compatibility with watchdog's background threadsFull rebuild stale data:
full_buildnow purges nodes/edges from files deleted since last buildRemoved unused dependency:
gitpythonwas listed in dependencies but never imported — removed to shrink install footprintStale Docker reference: Removed non-existent Docker image suggestion from Python version check
[1.7.0] - 2026-03-09
Added
installcommand — primary entry point for new users (code-review-graph install).initremains as an alias for backwards compatibility.--dry-runflag oninstall/init— shows what would be written without modifying filesPyPI publish workflow — GitHub releases now automatically publish to PyPI via API token
Professional README — complete rewrite with real benchmark data:
Code reviews: 6.8x average token reduction (tested on httpx, FastAPI, Next.js)
Live coding tasks: 14.1x average, up to 49.1x on large repos
Changed
README restructured around the install-and-forget user experience
CLI banner now shows
installas the primary command
[1.6.4] - 2026-03-06
Changed
Portable MCP config:
initnow generatesuvx-based.mcp.jsoninstead of absolute Python paths — works on any machine withuvinstalledRemoved
_safe_pathsymlink workaround (no longer needed withuvx)
[1.6.3] - 2026-03-06
Added
SessionStart hook — Claude Code now automatically prefers graph MCP tools over full codebase scans at the start of every session, saving tokens on general queries
homepageandauthor.urlfields in plugin.json for marketplace discoverability
Fixed
plugin.json schema: renamed
tagstokeywords, removed invalidskillspath (auto-discovered from default location)Removed screenshot placeholder section from README
[1.6.2] - 2026-02-27
Fixed
Critical: Incremental hash comparison bug —
file_hashread from wrong field, causing every file to re-parseWatch mode
on_deletedhandler now filters by ignore patternsRemoved dead code in
full_buildand duplicatemain()inincremental.pyget_staged_and_unstagedhandles git renamed files (R old -> new)TROUBLESHOOTING.md hook config path corrected
Added
Parser: C/C++ support — full node extraction (structs, classes, functions, includes, calls, inheritance)
Parser: name extraction fixes for Kotlin/Swift (
simple_identifier), Ruby (constant), C/C++ nestedfunction_declaratorGraphStorecontext manager (__enter__/__exit__)get_all_edges()andget_edges_among()public methods onGraphStoreNetworkX graph caching with automatic invalidation on writes
Subprocess timeout (30s) on all git calls
Progress logging every 50 files in full build
SHA-256 hashing in embeddings (replaced MD5)
Chunked embedding search (
fetchmany(500))Batch edge collection in
get_impact_radius(single SQL query)ARIA labels throughout D3.js visualization
CI: Coverage enforcement (
--cov-fail-under=50), bandit security scanning, mypy type checkingTests:
test_incremental.py(24 tests),test_embeddings.py(16 tests)Test fixtures: C, C++, C#, Ruby, PHP, Kotlin, Swift with multilang test classes
Docs: API response schemas in COMMANDS.md, ignore patterns in USAGE.md
[1.5.3] - 2026-02-27
Fixed
initnow auto-creates symlinks when paths contain spaces (macOS iCloud, OneDrive, etc.)build,status,visualize,watchwork without a git repository (falls back to cwd)Skills discoverable via plugin.json (
namefield added to SKILL.md frontmatter)
[1.5.0] - 2026-02-26
Added
File organization: All generated files now live in
.code-review-graph/directory instead of repo rootAuto-created
.gitignoreinside the directory prevents accidental commitsAutomatic migration from legacy
.code-review-graph.dbat repo root
Visualization: start collapsed: Only File nodes visible on load; click to expand children
Visualization: search bar: Filter nodes by name or qualified name in real-time
Visualization: edge type toggles: Click legend items to show/hide edge types (Calls, Imports, Inherits, Contains)
Visualization: scale-aware layout: Force simulation adapts charge, distance, and decay for large graphs (300+ nodes)
Changed
Database path:
.code-review-graph.db→.code-review-graph/graph.dbHTML visualization path:
.code-review-graph.html→.code-review-graph/graph.html.code-review-graph/**added to default ignore patterns (prevents self-indexing)
Removed
references/directory (duplicate ofdocs/, caused stale path references)agents/directory (unused, not wired into any code)settings.jsonat repo root (decorative, not loaded by code)
[1.4.0] - 2026-02-26
Added
initcommand: automatic.mcp.jsonsetup for Claude Code integrationvisualizecommand: interactive D3.js force-directed graph visualizationservecommand: start MCP server directly from CLI
Changed
Comprehensive documentation overhaul across all reference files
[1.3.0] - 2026-02-26
Added
Universal installation: now works with
pip install code-review-graph[embeddings]on Python 3.10+CLI entry point (
code-review-graphcommand works after normal pip install)Clear Python version check with helpful Docker fallback for older Python users
Improved README installation section with one-command + Docker option
Changed
Minimum Python requirement lowered from 3.11 → 3.10 (covers ~90% of users)
Fixed
Installation friction for most developers
512 files · 480 not served here
- .beads/.gitignore (not served here)
- .beads/README.md (not served here)
- .beads/config.yaml (not served here)
- .beads/hooks/post-checkout (not served here)
- .beads/hooks/post-merge (not served here)
- .beads/hooks/pre-commit (not served here)
- .beads/hooks/pre-push (not served here)
- .beads/hooks/prepare-commit-msg (not served here)
- .beads/metadata.json (not served here)
- .gitattributes (not served here)
- .github/ISSUE_TEMPLATE/bug_report.yml (not served here)
- .github/ISSUE_TEMPLATE/config.yml (not served here)
- .github/ISSUE_TEMPLATE/feature_request.yml (not served here)
- .github/ISSUE_TEMPLATE/platform_request.yml (not served here)
- .github/PULL_REQUEST_TEMPLATE.md (not served here)
- .github/copilot-instructions.md
- .github/dependabot.yml (not served here)
- .github/requirements-minimum.txt (not served here)
- .github/workflows/ci.yml (not served here)
- .github/workflows/eval.yml (not served here)
- .github/workflows/pr-review-comment.yml (not served here)
- .github/workflows/pr-review.yml (not served here)
- .github/workflows/promote.yml (not served here)
- .github/workflows/promotion-gate.yml (not served here)
- .github/workflows/publish.yml (not served here)
- .gitignore (not served here)
- .mcp.json
- AGENTS.md
- ATTRIBUTION.md
- AUTOGINEER-RUNS.md
- CHANGELOG.md
- CLAUDE.md
- CODE_OF_CONDUCT.md
- CONTRIBUTING.md
- Dockerfile (not served here)
- GEMINI.md
- LICENSE
- README.hi-IN.md
- README.ja-JP.md
- README.ko-KR.md
- README.md
- README.zh-CN.md
- SECURITY.md
- action.yml (not served here)
- code-review-graph-vscode/.gitignore (not served here)
- code-review-graph-vscode/.vscodeignore (not served here)
- code-review-graph-vscode/CHANGELOG.md (not served here)
- code-review-graph-vscode/LICENSE
- code-review-graph-vscode/README.md (not served here)
- code-review-graph-vscode/esbuild.mjs (not served here)
- code-review-graph-vscode/media/icons/graph.svg (not served here)
- code-review-graph-vscode/media/icons/icon.png (not served here)
- code-review-graph-vscode/media/walkthrough/build.md (not served here)
- code-review-graph-vscode/media/walkthrough/explore.md (not served here)
- code-review-graph-vscode/media/walkthrough/install.md (not served here)
- code-review-graph-vscode/package-lock.json (not served here)
- code-review-graph-vscode/package.json (not served here)
- code-review-graph-vscode/src/backend/cli.ts (not served here)
- code-review-graph-vscode/src/backend/sqlite.ts (not served here)
- code-review-graph-vscode/src/backend/watcher.ts (not served here)
- code-review-graph-vscode/src/extension.ts (not served here)
- code-review-graph-vscode/src/features/blastRadius.ts (not served here)
- code-review-graph-vscode/src/features/cursorResolver.ts (not served here)
- code-review-graph-vscode/src/features/navigation.ts (not served here)
- code-review-graph-vscode/src/features/reviewAssistant.ts (not served here)
- code-review-graph-vscode/src/features/scmDecorations.ts (not served here)
- code-review-graph-vscode/src/features/search.ts (not served here)
- code-review-graph-vscode/src/onboarding/installer.ts (not served here)
- code-review-graph-vscode/src/onboarding/welcome.ts (not served here)
- code-review-graph-vscode/src/views/graphWebview.ts (not served here)
- code-review-graph-vscode/src/views/statusBar.ts (not served here)
- code-review-graph-vscode/src/views/treeItems.ts (not served here)
- code-review-graph-vscode/src/views/treeView.ts (not served here)
- code-review-graph-vscode/src/webview/graph.ts (not served here)
- code-review-graph-vscode/test/sqlite.test.ts (not served here)
- code-review-graph-vscode/tsconfig.json (not served here)
- code_review_graph/__init__.py (not served here)
- code_review_graph/__main__.py (not served here)
- code_review_graph/_legacy_instructions.py (not served here)
- code_review_graph/analysis.py (not served here)
- code_review_graph/aop_resolver.py (not served here)
- code_review_graph/assets/code-review-graph/SKILL.md (not served here)
- code_review_graph/assets/code-review-graph/agents/openai.yaml (not served here)
- code_review_graph/assets/code-review-graph/scripts/crg_readonly.py (not served here)
- code_review_graph/assets/d3.v7.min.js (not served here)
- code_review_graph/build_state.py (not served here)
- code_review_graph/change_details.py (not served here)
- code_review_graph/changes.py (not served here)
- code_review_graph/cli.py (not served here)
- code_review_graph/codex_skill.py (not served here)
- code_review_graph/communities.py (not served here)
- code_review_graph/config_keys.py (not served here)
- code_review_graph/constants.py (not served here)
- code_review_graph/context_history.py (not served here)
- code_review_graph/context_savings.py (not served here)
- code_review_graph/csharp_resolver.py (not served here)
- code_review_graph/custom_languages.py (not served here)
- code_review_graph/daemon.py (not served here)
- code_review_graph/daemon_cli.py (not served here)
- code_review_graph/docs/LLM-OPTIMIZED-REFERENCE.md (not served here)
- code_review_graph/dsh.py (not served here)
- code_review_graph/embeddings.py (not served here)
- code_review_graph/enrich.py (not served here)
- code_review_graph/errors.py (not served here)
- code_review_graph/eval/__init__.py (not served here)
- code_review_graph/eval/benchmarks/__init__.py (not served here)
- code_review_graph/eval/benchmarks/agent_baseline.py (not served here)
- code_review_graph/eval/benchmarks/build_performance.py (not served here)
- code_review_graph/eval/benchmarks/flow_completeness.py (not served here)
- code_review_graph/eval/benchmarks/impact_accuracy.py (not served here)
- code_review_graph/eval/benchmarks/incremental_fidelity.py (not served here)
- code_review_graph/eval/benchmarks/multi_hop_retrieval.py (not served here)
- code_review_graph/eval/benchmarks/search_quality.py (not served here)
- code_review_graph/eval/benchmarks/token_efficiency.py (not served here)
- code_review_graph/eval/configs/code-review-graph.yaml (not served here)
- code_review_graph/eval/configs/express.yaml (not served here)
- code_review_graph/eval/configs/fastapi.yaml (not served here)
- code_review_graph/eval/configs/flask.yaml (not served here)
- code_review_graph/eval/configs/gin.yaml (not served here)
- code_review_graph/eval/configs/httpx.yaml (not served here)
- code_review_graph/eval/reporter.py (not served here)
- code_review_graph/eval/runner.py (not served here)
- code_review_graph/eval/scorer.py (not served here)
- code_review_graph/eval/token_benchmark.py (not served here)
- code_review_graph/event_resolver.py (not served here)
- code_review_graph/exports.py (not served here)
- code_review_graph/flows.py (not served here)
- code_review_graph/forget.py (not served here)
- code_review_graph/graph.py (not served here)
- code_review_graph/graph_diff.py (not served here)
- code_review_graph/hcl_resolver.py (not served here)
- code_review_graph/hints.py (not served here)
- code_review_graph/hook_update.py (not served here)
- code_review_graph/http_origin_guard.py (not served here)
- code_review_graph/incremental.py (not served here)
- code_review_graph/inheritance.py (not served here)
- code_review_graph/install_gitignore.py (not served here)
- code_review_graph/install_plan.py (not served here)
- code_review_graph/jedi_resolver.py (not served here)
- code_review_graph/jsonc.py (not served here)
- code_review_graph/locking.py (not served here)
- code_review_graph/main.py (not served here)
- code_review_graph/memory.py (not served here)
- code_review_graph/migrations.py (not served here)
- code_review_graph/multi_worktree.py (not served here)
- code_review_graph/neighbourhood.py (not served here)
- code_review_graph/npm_alias_resolver.py (not served here)
- code_review_graph/parser.py (not served here)
- code_review_graph/postprocessing.py (not served here)
- code_review_graph/prompts.py (not served here)
- code_review_graph/python_resolver.py (not served here)
- code_review_graph/refactor.py (not served here)
- code_review_graph/registry.py (not served here)
- code_review_graph/rescript_resolver.py (not served here)
- code_review_graph/scoped_resolver.py (not served here)
- code_review_graph/search.py (not served here)
- code_review_graph/skills.py (not served here)
- code_review_graph/spring_resolver.py (not served here)
- code_review_graph/stdio.py (not served here)
- code_review_graph/temporal_resolver.py (not served here)
- code_review_graph/token_benchmark.py (not served here)
- code_review_graph/tools/__init__.py (not served here)
- code_review_graph/tools/_common.py (not served here)
- code_review_graph/tools/analysis_tools.py (not served here)
- code_review_graph/tools/build.py (not served here)
- code_review_graph/tools/community_tools.py (not served here)
- code_review_graph/tools/context.py (not served here)
- code_review_graph/tools/docs.py (not served here)
- code_review_graph/tools/flows_tools.py (not served here)
- code_review_graph/tools/query.py (not served here)
- code_review_graph/tools/refactor_tools.py (not served here)
- code_review_graph/tools/registry_tools.py (not served here)
- code_review_graph/tools/review.py (not served here)
- code_review_graph/tsconfig_resolver.py (not served here)
- code_review_graph/uncertainty.py (not served here)
- code_review_graph/uninstall.py (not served here)
- code_review_graph/visualization.py (not served here)
- code_review_graph/wiki.py (not served here)
- code_review_graph/workspace_resolver.py (not served here)
- diagrams/context-savings-demo.gif (not served here)
- diagrams/context-savings-demo.tape (not served here)
- diagrams/diagram1_before_vs_after.png (not served here)
- diagrams/diagram2_architecture_pipeline.png (not served here)
- diagrams/diagram3_blast_radius.png (not served here)
- diagrams/diagram4_incremental_update.png (not served here)
- diagrams/diagram5_benchmark_board.png (not served here)
- diagrams/diagram6_monorepo_funnel.png (not served here)
- diagrams/diagram7_mcp_integration_flow.png (not served here)
- diagrams/diagram8_supported_platforms.png (not served here)
- diagrams/diagram9_language_coverage.png (not served here)
- diagrams/generate_diagrams.py (not served here)
- docs/COMMANDS.md
- docs/CUSTOM_LANGUAGES.md
- docs/FAQ.md
- docs/FEATURES.md
- docs/GITHUB_ACTION.md
- docs/INDEX.md
- docs/LEGAL.md
- docs/REPRODUCING.md
- docs/ROADMAP.md
- docs/TROUBLESHOOTING.md
- docs/USAGE.md
- docs/architecture.md
- docs/schema.md
- evaluate/results/code-review-graph_agent_baseline_2026-09-22.csv (not served here)
- evaluate/results/code-review-graph_impact_accuracy_2026-08-02.csv (not served here)
- evaluate/results/code-review-graph_multi_hop_retrieval_2026-05-25.csv (not served here)
- evaluate/results/code-review-graph_token_efficiency_2026-05-25.csv (not served here)
- evaluate/results/express_agent_baseline_2026-09-22.csv (not served here)
- evaluate/results/express_impact_accuracy_2026-08-02.csv (not served here)
- evaluate/results/express_multi_hop_retrieval_2026-05-25.csv (not served here)
- evaluate/results/express_token_efficiency_2026-05-25.csv (not served here)
- evaluate/results/fastapi_agent_baseline_2026-09-22.csv (not served here)
- evaluate/results/fastapi_impact_accuracy_2026-08-02.csv (not served here)
- evaluate/results/fastapi_multi_hop_retrieval_2026-05-25.csv (not served here)
- evaluate/results/fastapi_token_efficiency_2026-05-25.csv (not served here)
- evaluate/results/flask_agent_baseline_2026-09-22.csv (not served here)
- evaluate/results/flask_impact_accuracy_2026-08-02.csv (not served here)
- evaluate/results/flask_multi_hop_retrieval_2026-05-25.csv (not served here)
- evaluate/results/flask_token_efficiency_2026-05-25.csv (not served here)
- evaluate/results/gin_agent_baseline_2026-09-22.csv (not served here)
- evaluate/results/gin_impact_accuracy_2026-08-02.csv (not served here)
- evaluate/results/gin_multi_hop_retrieval_2026-05-25.csv (not served here)
- evaluate/results/gin_token_efficiency_2026-05-25.csv (not served here)
- evaluate/results/httpx_agent_baseline_2026-09-22.csv (not served here)
- evaluate/results/httpx_impact_accuracy_2026-08-02.csv (not served here)
- evaluate/results/httpx_multi_hop_retrieval_2026-05-25.csv (not served here)
- evaluate/results/httpx_token_efficiency_2026-05-25.csv (not served here)
- hooks/hooks.json
- hooks/session-start.sh (not served here)
- pyproject.toml (not served here)
- scripts/audit_python_imports.py (not served here)
- scripts/diagnose_pypi_connectivity.py (not served here)
- scripts/generate_documentary_skills.py (not served here)
- scripts/print_minimum_dependencies.py (not served here)
- scripts/promotion_gate.py (not served here)
- scripts/render_pr_comment.py (not served here)
- scripts/smoke_google_embeddings.py (not served here)
- skills/build-graph/SKILL.md (not served here)
- skills/debug-issue/SKILL.md (not served here)
- skills/explore-codebase/SKILL.md (not served here)
- skills/refactor-safely/SKILL.md (not served here)
- skills/review-changes/SKILL.md (not served here)
- skills/review-delta/SKILL.md (not served here)
- skills/review-pr/SKILL.md (not served here)
- tests/__init__.py (not served here)
- tests/conftest.py (not served here)
- tests/corpus_baselines.json (not served here)
- tests/determinism_build_runner.py (not served here)
- tests/determinism_dump.py (not served here)
- tests/fixtures/KafkaPatterns.java (not served here)
- tests/fixtures/MarkdownMsg.tsx (not served here)
- tests/fixtures/Sample.cs (not served here)
- tests/fixtures/SampleJava.java (not served here)
- tests/fixtures/SpringDI.java (not served here)
- tests/fixtures/TemporalWorkflow.java (not served here)
- tests/fixtures/__tests__/UserService.ts (not served here)
- tests/fixtures/alias_importer.ts (not served here)
- tests/fixtures/caller_example.py (not served here)
- tests/fixtures/cpp_qt_headers/MyWidget.cpp (not served here)
- tests/fixtures/cpp_qt_headers/MyWidget.h (not served here)
- tests/fixtures/cpp_qt_headers/MyWidgetPlain.cpp (not served here)
- tests/fixtures/cpp_qt_headers/MyWidgetPlain.h (not served here)
- tests/fixtures/detect_changes_sample.json (not served here)
- tests/fixtures/graphml_xsd/SOURCE.txt (not served here)
- tests/fixtures/graphml_xsd/graphml-attributes.xsd (not served here)
- tests/fixtures/graphml_xsd/graphml-parseinfo.xsd (not served here)
- tests/fixtures/graphml_xsd/graphml-structure.xsd (not served here)
- tests/fixtures/graphml_xsd/graphml.xsd (not served here)
- tests/fixtures/graphml_xsd/xlink.xsd (not served here)
- tests/fixtures/multi_call_example.py (not served here)
- tests/fixtures/playbooks/sample_ansible_playbook.yml (not served here)
- tests/fixtures/roles/myrole/meta/main.yml (not served here)
- tests/fixtures/sample.R (not served here)
- tests/fixtures/sample.c (not served here)
- tests/fixtures/sample.cpp (not served here)
- tests/fixtures/sample.dart (not served here)
- tests/fixtures/sample.ex (not served here)
- tests/fixtures/sample.gd (not served here)
- tests/fixtures/sample.hh (not served here)
- tests/fixtures/sample.jl (not served here)
- tests/fixtures/sample.kt (not served here)
- tests/fixtures/sample.liquid (not served here)
- tests/fixtures/sample.lua (not served here)
- tests/fixtures/sample.luau (not served here)
- tests/fixtures/sample.m (not served here)
- tests/fixtures/sample.nix (not served here)
- tests/fixtures/sample.php (not served here)
- tests/fixtures/sample.pl (not served here)
- tests/fixtures/sample.rb (not served here)
- tests/fixtures/sample.res (not served here)
- tests/fixtures/sample.resi (not served here)
- tests/fixtures/sample.scala (not served here)
- tests/fixtures/sample.sh (not served here)
- tests/fixtures/sample.sol (not served here)
- tests/fixtures/sample.sql (not served here)
- tests/fixtures/sample.sv (not served here)
- tests/fixtures/sample.swift (not served here)
- tests/fixtures/sample.tf (not served here)
- tests/fixtures/sample.xs (not served here)
- tests/fixtures/sample_bun.test.ts (not served here)
- tests/fixtures/sample_callback_refs.py (not served here)
- tests/fixtures/sample_config.json (not served here)
- tests/fixtures/sample_databricks_export.py (not served here)
- tests/fixtures/sample_databricks_notebook.ipynb (not served here)
- tests/fixtures/sample_dead_guard.c (not served here)
- tests/fixtures/sample_dead_guard.go (not served here)
- tests/fixtures/sample_dead_guard.rs (not served here)
- tests/fixtures/sample_dead_guard.ts (not served here)
- tests/fixtures/sample_dead_guard_impl.rs (not served here)
- tests/fixtures/sample_go.go (not served here)
- tests/fixtures/sample_html.html (not served here)
- tests/fixtures/sample_lib.sh (not served here)
- tests/fixtures/sample_map_dispatch.py (not served here)
- tests/fixtures/sample_map_dispatch.ts (not served here)
- tests/fixtures/sample_mocha.test.ts (not served here)
- tests/fixtures/sample_module.nix (not served here)
- tests/fixtures/sample_notebook.ipynb (not served here)
- tests/fixtures/sample_plsql.sql (not served here)
- tests/fixtures/sample_python.py (not served here)
- tests/fixtures/sample_rust.rs (not served here)
- tests/fixtures/sample_typescript.ts (not served here)
- tests/fixtures/sample_vitest.test.ts (not served here)
- tests/fixtures/sample_vue.vue (not served here)
- tests/fixtures/sample_xslt.xsl (not served here)
- tests/fixtures/sample_xslt_common.xsl (not served here)
- tests/fixtures/sample_zig.zig (not served here)
- tests/fixtures/sample_zig_util.zig (not served here)
- tests/fixtures/src/lib/utils.ts (not served here)
- tests/fixtures/tasks/sample_ansible_tasks.yml (not served here)
- tests/fixtures/test_sample.R (not served here)
- tests/fixtures/test_sample.py (not served here)
- tests/fixtures/tsconfig.json (not served here)
- tests/import_audit.py (not served here)
- tests/mcp_client_compat.py (not served here)
- tests/real_repo_corpus.py (not served here)
- tests/test_action_e2e.py (not served here)
- tests/test_action_render.py (not served here)
- tests/test_affected_flows_limits.py (not served here)
- tests/test_agent_transparency.py (not served here)
- tests/test_ansible_parser.py (not served here)
- tests/test_aop_resolution_edge_cases.py (not served here)
- tests/test_aop_resolver.py (not served here)
- tests/test_build_tool_errors.py (not served here)
- tests/test_call_site_provenance.py (not served here)
- tests/test_callback_context_preservation.py (not served here)
- tests/test_change_details.py (not served here)
- tests/test_changes.py (not served here)
- tests/test_changes_path_normalization.py (not served here)
- tests/test_churn_runtime.py (not served here)
- tests/test_cjs_and_kts_extensions.py (not served here)
- tests/test_cli.py (not served here)
- tests/test_cli_install.py (not served here)
- tests/test_cli_reconciliation.py (not served here)
- tests/test_cli_stdio_encoding.py (not served here)
- tests/test_cli_surface.py (not served here)
- tests/test_cli_tool_commands.py (not served here)
- tests/test_codeql_url_substring.py (not served here)
- tests/test_codex_skill_wrapper.py (not served here)
- tests/test_commonjs_imports.py (not served here)
- tests/test_communities.py (not served here)
- tests/test_compact_impact.py (not served here)
- tests/test_composer_vendor_ignore.py (not served here)
- tests/test_concurrency_and_daemon.py (not served here)
- tests/test_conftest_gates.py (not served here)
- tests/test_context_history.py (not served here)
- tests/test_context_savings.py (not served here)
- tests/test_cpp_header_prefilter.py (not served here)
- tests/test_cpp_overload_identity.py (not served here)
- tests/test_cpp_qt_headers.py (not served here)
- tests/test_csharp_dead_code_overrides.py (not served here)
- tests/test_csharp_dotted_query_targets.py (not served here)
- tests/test_csharp_namespace_binding_edges.py (not served here)
- tests/test_csharp_namespaces.py (not served here)
- tests/test_custom_languages.py (not served here)
- tests/test_d3_asset_line_endings.py (not served here)
- tests/test_daemon.py (not served here)
- tests/test_daemon_cli_followups.py (not served here)
- tests/test_daemon_stop_confirmation.py (not served here)
- tests/test_daemon_stop_escalation.py (not served here)
- tests/test_dbt_parser.py (not served here)
- tests/test_dead_code_edge_evidence.py (not served here)
- tests/test_dead_code_query_scaling.py (not served here)
- tests/test_dependency_floors.py (not served here)
- tests/test_derived_data_fallbacks.py (not served here)
- tests/test_determinism.py (not served here)
- tests/test_diagnose_pypi_connectivity.py (not served here)
- tests/test_docstring_embeddings.py (not served here)
- tests/test_document_skills.py (not served here)
- tests/test_documentation.py (not served here)
- tests/test_dsh.py (not served here)
- tests/test_e2e_mcp_client.py (not served here)
- tests/test_e2e_real_repo.py (not served here)
- tests/test_edit_hook_follows_the_edited_file.py (not served here)
- tests/test_elixir_multi_clause.py (not served here)
- tests/test_embedding_initialization.py (not served here)
- tests/test_embedding_refresh.py (not served here)
- tests/test_embedding_refresh_skip_notice.py (not served here)
- tests/test_embeddings.py (not served here)
- tests/test_enrich.py (not served here)
- tests/test_env_config.py (not served here)
- tests/test_eval.py (not served here)
- tests/test_export_formats.py (not served here)
- tests/test_failure_reporting.py (not served here)
- tests/test_flow_source_stays_in_repo.py (not served here)
- tests/test_flows.py (not served here)
- tests/test_follow_symlinks.py (not served here)
- tests/test_forget.py (not served here)
- tests/test_forget_legacy_db.py (not served here)
- tests/test_forget_parity.py (not served here)
- tests/test_freshness_metadata.py (not served here)
- tests/test_fts_delta.py (not served here)
- tests/test_fts_sync.py (not served here)
- tests/test_genai_packaging.py (not served here)
- tests/test_generic_yaml_edge_cases.py (not served here)
- tests/test_git_hook_block_helpers.py (not served here)
- tests/test_git_hook_worktree.py (not served here)
- tests/test_git_sync_hooks.py (not served here)
- tests/test_go_embeddings.py (not served here)
- tests/test_go_imports.py (not served here)
- tests/test_go_receiver_resolution.py (not served here)
- tests/test_graph.py (not served here)
- tests/test_graph_update_contracts.py (not served here)
- tests/test_hcl_parser.py (not served here)
- tests/test_hcl_provider_aliases.py (not served here)
- tests/test_header_dead_branch_scan.py (not served here)
- tests/test_hermes_install.py (not served here)
- tests/test_hermes_yaml_preservation.py (not served here)
- tests/test_hints.py (not served here)
- tests/test_hook_update.py (not served here)
- tests/test_html_parser.py (not served here)
- tests/test_http_multi_repo.py (not served here)
- tests/test_http_origin_guard.py (not served here)
- tests/test_identity_pending_ignored.py (not served here)
- tests/test_identity_retry.py (not served here)
- tests/test_import_cycles.py (not served here)
- tests/test_import_scope.py (not served here)
- tests/test_import_target_integrity.py (not served here)
- tests/test_incremental.py (not served here)
- tests/test_incremental_dependent_derived_state.py (not served here)
- tests/test_incremental_fidelity.py (not served here)
- tests/test_incremental_flow_lifecycle.py (not served here)
- tests/test_incremental_reexport_regression.py (not served here)
- tests/test_incremental_unindexed_change_memory.py (not served here)
- tests/test_inherited_calls.py (not served here)
- tests/test_install_gitignore_block.py (not served here)
- tests/test_install_outputs_are_ignored.py (not served here)
- tests/test_install_preview.py (not served here)
- tests/test_installer_ownership.py (not served here)
- tests/test_integration_git.py (not served here)
- tests/test_integration_v2.py (not served here)
- tests/test_java_call_references.py (not served here)
- tests/test_java_name_extraction.py (not served here)
- tests/test_js_module_resolution.py (not served here)
- tests/test_js_wrapped_functions.py (not served here)
- tests/test_json_parser.py (not served here)
- tests/test_jsonc_removals.py (not served here)
- tests/test_julia_reconciliation.py (not served here)
- tests/test_jvm_implementors.py (not served here)
- tests/test_keyword_argument_callbacks.py (not served here)
- tests/test_kotlin_import_comments.py (not served here)
- tests/test_kotlin_imports.py (not served here)
- tests/test_language_reconciliation.py (not served here)
- tests/test_legacy_graph_paths.py (not served here)
- tests/test_liquid.py (not served here)
- tests/test_main.py (not served here)
- tests/test_main_lazy_tool_imports.py (not served here)
- tests/test_mcp_auto_watch.py (not served here)
- tests/test_mcp_auto_watch_startup_window.py (not served here)
- tests/test_mcp_stdio_shutdown.py (not served here)
- tests/test_mcp_surface.py (not served here)
- tests/test_mcp_watch_store_shutdown.py (not served here)
- tests/test_memory.py (not served here)
- tests/test_migrations.py (not served here)
- tests/test_missing_registered_data_dir.py (not served here)
- tests/test_multi_worktree.py (not served here)
- tests/test_multilang.py (not served here)
- tests/test_neighbourhood.py (not served here)
- tests/test_neighbourhood_bounded_reads.py (not served here)
- tests/test_neighbourhood_browser.py (not served here)
- tests/test_nested_method_identities.py (not served here)
- tests/test_nested_scope_upgrade.py (not served here)
- tests/test_notebook.py (not served here)
- tests/test_npm_alias_resolver.py (not served here)
- tests/test_opencode_v1_plugin_contract.py (not served here)
- tests/test_opencode_v2_plugin_contract.py (not served here)
- tests/test_optional_dependencies.py (not served here)
- tests/test_packaging.py (not served here)
- tests/test_parser.py (not served here)
- tests/test_parser_grammar_contract.py (not served here)
- tests/test_parser_load_probe.py (not served here)
- tests/test_php_guessed_test_links.py (not served here)
- tests/test_php_includes.py (not served here)
- tests/test_php_laravel.py (not served here)
- tests/test_php_scoped_calls.py (not served here)
- tests/test_platform_lifecycle.py (not served here)
- tests/test_postprocessing.py (not served here)
- tests/test_powershell_extraction.py (not served here)
- tests/test_pr803_poisoned_graph_recovery.py (not served here)
- tests/test_pr_review_workflows.py (not served here)
- tests/test_promotion_gate.py (not served here)
- tests/test_promotion_is_manual.py (not served here)
- tests/test_prompts.py (not served here)
- tests/test_public_tool_hints.py (not served here)
- tests/test_pure_deletion_review.py (not served here)
- tests/test_python_module_alias_shims.py (not served here)
- tests/test_python_property_reads.py (not served here)
- tests/test_python_reachability.py (not served here)
- tests/test_python_receiver_evidence.py (not served here)
- tests/test_python_receiver_shapes.py (not served here)
- tests/test_python_relative_imports.py (not served here)
- tests/test_python_star_imports.py (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
- The graph may become stale if the background update process is interrupted or fails to detect changes. evidence: .github/copilot-instructions.md
- Requires Python 3.10+ and may need manual configuration for environments where MCP is not natively supported. evidence: README.md
Security
Risk level: Medium risk
This brain runs
MCP servers (2)
.mcp.json(code-review-graph):uvx code-review-graph serveREADME.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 networkCLAUDE.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 -qCLAUDE.md:uv run ruff check code_review_graph/CLAUDE.md:uv run mypy code_review_graph/ --ignore-missing-imports --no-strict-optionalCLAUDE.md:uv run code-review-graph build # full buildCLAUDE.md:uv run code-review-graph update # incremental updateCLAUDE.md:uv run code-review-graph status # graph statisticsCLAUDE.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 graphCLAUDE.md:uv run code-review-graph serve # MCP server (stdio; --http for localhost)CLAUDE.md:uv run code-review-graph wiki # Markdown wikiCLAUDE.md:uv run code-review-graph register <path> # add a repo to the multi-repo registryCLAUDE.md:uv run code-review-graph eval --all # benchmarksCLAUDE.md:uv run code-review-graph --help # full command listCLAUDE.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 networkskills/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