chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
# All Available Commands
|
||||
|
||||
## Skills and Slash Commands
|
||||
|
||||
These commands are installed for clients that support project skills or slash-command style workflows.
|
||||
|
||||
### `/code-review-graph:build-graph`
|
||||
Build or update the knowledge graph.
|
||||
- First time: performs a full build
|
||||
- Subsequent: incremental update (only changed files)
|
||||
|
||||
### `/code-review-graph:review-delta`
|
||||
Review only changes since last commit.
|
||||
- Auto-detects changed files via git diff
|
||||
- Computes blast radius (2-hop default)
|
||||
- Generates structured review with guidance
|
||||
|
||||
### `/code-review-graph:review-pr`
|
||||
Review a PR or branch diff.
|
||||
- Uses main/master as base
|
||||
- Full impact analysis across all PR commits
|
||||
- Structured output with risk assessment
|
||||
|
||||
## MCP Tools
|
||||
|
||||
### Core Tools
|
||||
|
||||
#### `build_or_update_graph_tool`
|
||||
```
|
||||
full_rebuild: bool = False # True for full re-parse
|
||||
repo_root: str | None # Auto-detected
|
||||
base: str = "HEAD~1" # VCS diff base for incremental updates
|
||||
postprocess: str = "full" # "full", "minimal", or "none"
|
||||
recurse_submodules: bool | None # Falls back to CRG_RECURSE_SUBMODULES
|
||||
```
|
||||
|
||||
#### `run_postprocess_tool`
|
||||
```
|
||||
flows: bool = True
|
||||
communities: bool = True
|
||||
fts: bool = True
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `get_minimal_context_tool`
|
||||
```
|
||||
task: str = "" # What you are doing
|
||||
changed_files: list[str] | None # Auto-detected from VCS when omitted
|
||||
repo_root: str | None
|
||||
base: str = "HEAD~1"
|
||||
```
|
||||
|
||||
#### `get_impact_radius_tool`
|
||||
```
|
||||
changed_files: list[str] | None # Auto-detected from VCS
|
||||
max_depth: int = 2 # Hops in graph
|
||||
repo_root: str | None
|
||||
base: str = "HEAD~1"
|
||||
detail_level: str = "standard" # "standard" or "minimal"
|
||||
```
|
||||
Relevant responses may include compact estimated `context_savings` metadata.
|
||||
|
||||
#### `query_graph_tool`
|
||||
```
|
||||
pattern: str # callers_of, callees_of, imports_of, importers_of,
|
||||
# children_of, tests_for, inheritors_of, file_summary
|
||||
target: str # Node name, qualified name, or file path
|
||||
repo_root: str | None
|
||||
detail_level: str = "standard" # "standard" or "minimal"
|
||||
```
|
||||
|
||||
#### `get_review_context_tool`
|
||||
```
|
||||
changed_files: list[str] | None
|
||||
max_depth: int = 2
|
||||
include_source: bool = True
|
||||
max_lines_per_file: int = 200
|
||||
repo_root: str | None
|
||||
base: str = "HEAD~1"
|
||||
detail_level: str = "standard" # "standard" or "minimal"
|
||||
```
|
||||
Relevant responses may include compact estimated `context_savings` metadata.
|
||||
|
||||
#### `traverse_graph_tool`
|
||||
```
|
||||
query: str
|
||||
depth: int = 3 # 1-6
|
||||
mode: str = "bfs" # "bfs" or "dfs"
|
||||
token_budget: int = 2000
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `semantic_search_nodes_tool`
|
||||
```
|
||||
query: str # Search string
|
||||
kind: str | None # File, Class, Function, Type, Test
|
||||
limit: int = 20
|
||||
repo_root: str | None
|
||||
model: str | None # Embedding model (falls back to CRG_EMBEDDING_MODEL env var)
|
||||
provider: str | None # local, openai, google, minimax
|
||||
detail_level: str = "standard"
|
||||
```
|
||||
|
||||
#### `embed_graph_tool`
|
||||
```
|
||||
repo_root: str | None
|
||||
model: str | None # Embedding model name
|
||||
provider: str | None # local, openai, google, minimax
|
||||
```
|
||||
Local embeddings require: `pip install code-review-graph[embeddings]`. Cloud providers use stdlib HTTP clients and require their provider environment variables.
|
||||
|
||||
#### `list_graph_stats_tool`
|
||||
```
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `find_large_functions_tool`
|
||||
```
|
||||
min_lines: int = 50 # Minimum line count threshold
|
||||
kind: str | None # File, Class, Function, or Test
|
||||
file_path_pattern: str | None # Filter by file path substring
|
||||
limit: int = 50 # Max results to return
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `get_docs_section_tool`
|
||||
```
|
||||
section_name: str # usage, review-delta, review-pr, commands, legal, watch, embeddings, languages, troubleshooting
|
||||
```
|
||||
|
||||
### Flow Tools
|
||||
|
||||
#### `list_flows_tool`
|
||||
```
|
||||
sort_by: str = "criticality" # criticality, depth, node_count, file_count, name
|
||||
limit: int = 50
|
||||
kind: str | None # Filter by entry point kind (e.g. "Test", "Function")
|
||||
repo_root: str | None
|
||||
detail_level: str = "standard"
|
||||
```
|
||||
|
||||
#### `get_flow_tool`
|
||||
```
|
||||
flow_id: int | None # Database ID from list_flows_tool
|
||||
flow_name: str | None # Name to search (partial match)
|
||||
include_source: bool = False # Include source snippets for each step
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `get_affected_flows_tool`
|
||||
```
|
||||
changed_files: list[str] | None # Auto-detected from VCS
|
||||
base: str = "HEAD~1"
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
### Community Tools
|
||||
|
||||
#### `list_communities_tool`
|
||||
```
|
||||
sort_by: str = "size" # size, cohesion, name
|
||||
min_size: int = 0
|
||||
repo_root: str | None
|
||||
detail_level: str = "standard"
|
||||
```
|
||||
|
||||
#### `get_community_tool`
|
||||
```
|
||||
community_name: str | None # Name to search (partial match)
|
||||
community_id: int | None # Database ID
|
||||
include_members: bool = False
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `get_architecture_overview_tool`
|
||||
```
|
||||
repo_root: str | None
|
||||
detail_level: str = "minimal" # "minimal" compact default, "standard" full detail
|
||||
```
|
||||
Minimal responses may include compact estimated `context_savings` metadata.
|
||||
|
||||
### Graph Health and Architecture Tools
|
||||
|
||||
#### `get_hub_nodes_tool`
|
||||
```
|
||||
top_n: int = 10
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `get_bridge_nodes_tool`
|
||||
```
|
||||
top_n: int = 10
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `get_knowledge_gaps_tool`
|
||||
```
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `get_surprising_connections_tool`
|
||||
```
|
||||
top_n: int = 15
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `get_suggested_questions_tool`
|
||||
```
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
### Change Analysis and Refactoring Tools
|
||||
|
||||
#### `detect_changes_tool`
|
||||
```
|
||||
base: str = "HEAD~1"
|
||||
changed_files: list[str] | None
|
||||
include_source: bool = False
|
||||
max_depth: int = 2
|
||||
repo_root: str | None
|
||||
detail_level: str = "standard"
|
||||
```
|
||||
Primary tool for code review. Maps changed files to affected functions, flows, communities, and test coverage gaps. Returns risk scores and prioritized review items.
|
||||
Relevant responses may include compact estimated `context_savings` metadata.
|
||||
|
||||
#### `refactor_tool`
|
||||
```
|
||||
mode: str = "rename" # "rename", "dead_code", or "suggest"
|
||||
old_name: str | None # (rename) Current symbol name
|
||||
new_name: str | None # (rename) New name
|
||||
kind: str | None # (dead_code) Function or Class
|
||||
file_pattern: str | None # (dead_code) Filter by file path substring
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
#### `apply_refactor_tool`
|
||||
```
|
||||
refactor_id: str # ID from prior refactor_tool call
|
||||
repo_root: str | None
|
||||
dry_run: bool = False # Return diff without writing files
|
||||
```
|
||||
|
||||
### Wiki Tools
|
||||
|
||||
#### `generate_wiki_tool`
|
||||
```
|
||||
repo_root: str | None
|
||||
force: bool = False # Regenerate all pages even if unchanged
|
||||
```
|
||||
|
||||
#### `get_wiki_page_tool`
|
||||
```
|
||||
community_name: str # Community name to look up
|
||||
repo_root: str | None
|
||||
```
|
||||
|
||||
### Multi-Repo Tools
|
||||
|
||||
#### `list_repos_tool`
|
||||
```
|
||||
(no parameters)
|
||||
```
|
||||
|
||||
#### `cross_repo_search_tool`
|
||||
```
|
||||
query: str
|
||||
kind: str | None
|
||||
limit: int = 20
|
||||
```
|
||||
|
||||
## MCP Prompts (5 workflow templates)
|
||||
|
||||
### `review_changes`
|
||||
Pre-commit review workflow using detect_changes, affected_flows, and test gaps.
|
||||
```
|
||||
base: str = "HEAD~1"
|
||||
```
|
||||
|
||||
### `architecture_map`
|
||||
Architecture documentation using communities, flows, and Mermaid diagrams.
|
||||
|
||||
### `debug_issue`
|
||||
Guided debugging using search, flow tracing, and recent changes.
|
||||
```
|
||||
description: str = ""
|
||||
```
|
||||
|
||||
### `onboard_developer`
|
||||
New developer orientation using stats, architecture, and critical flows.
|
||||
|
||||
### `pre_merge_check`
|
||||
PR readiness check with risk scoring, test gaps, and dead code detection.
|
||||
```
|
||||
base: str = "HEAD~1"
|
||||
```
|
||||
|
||||
## CLI Commands
|
||||
|
||||
```bash
|
||||
# Setup
|
||||
code-review-graph install # Configure detected AI coding platforms (alias: init)
|
||||
code-review-graph install --dry-run # Preview without writing files
|
||||
code-review-graph install --platform codex # Configure one platform
|
||||
|
||||
# Build and update
|
||||
code-review-graph build # Full build
|
||||
code-review-graph build --skip-flows # Parse + signatures + FTS only
|
||||
code-review-graph build --skip-postprocess # Raw parse only
|
||||
code-review-graph update # Incremental update
|
||||
code-review-graph update --base origin/main # Custom base ref
|
||||
code-review-graph update --brief # Update graph + show risk panel
|
||||
code-review-graph update --brief --verify # ...and cross-check vs tiktoken
|
||||
code-review-graph postprocess # Re-run flows, communities, FTS
|
||||
code-review-graph embed --provider local # Compute vector embeddings for semantic search
|
||||
|
||||
# Monitor and inspect
|
||||
code-review-graph status # Graph statistics
|
||||
code-review-graph watch # Auto-update on file changes
|
||||
code-review-graph visualize # Generate interactive HTML graph
|
||||
code-review-graph visualize --format graphml # Export GraphML
|
||||
code-review-graph visualize --serve # Serve graph.html on localhost:8765
|
||||
|
||||
# Analysis
|
||||
code-review-graph detect-changes # Risk-scored change analysis
|
||||
code-review-graph detect-changes --base HEAD~3 # Custom base ref
|
||||
code-review-graph detect-changes --brief # Compact panel with token-savings estimate
|
||||
code-review-graph detect-changes --brief --verify # ...and cross-check vs tiktoken
|
||||
|
||||
# detect-changes vs update --brief — which one?
|
||||
# • detect-changes --brief: read-only. Asks "what's the impact of my current
|
||||
# changes against the existing graph?" Fast (~1s). Use this when the graph
|
||||
# is already up to date (the default, if you have hooks installed).
|
||||
# • update --brief: re-parses your changed files into the graph FIRST, then
|
||||
# runs the same analysis at the end. Use this after a rebase, a big
|
||||
# change set, or whenever you suspect the graph is stale.
|
||||
# Both end with an identical "Token Savings" panel.
|
||||
|
||||
# Wiki
|
||||
code-review-graph wiki # Generate markdown wiki from communities
|
||||
|
||||
# Multi-repo
|
||||
code-review-graph register <path> [--alias name] # Register a repository
|
||||
code-review-graph unregister <path_or_alias> # Remove from registry
|
||||
code-review-graph repos # List registered repositories
|
||||
|
||||
# Daemon (multi-repo watcher) — included with install, no extra dependencies
|
||||
code-review-graph daemon start [--foreground] # Start the watch daemon
|
||||
code-review-graph daemon stop # Stop the daemon
|
||||
code-review-graph daemon restart [--foreground] # Restart the daemon
|
||||
code-review-graph daemon status # Show daemon status and repos
|
||||
code-review-graph daemon logs [--repo ALIAS] [--follow] # View daemon or per-repo logs
|
||||
code-review-graph daemon add <path> [--alias NAME] # Add a repo to daemon config
|
||||
code-review-graph daemon remove <path_or_alias> # Remove a repo from daemon config
|
||||
|
||||
# Evaluation
|
||||
code-review-graph eval # Run evaluation benchmarks
|
||||
|
||||
# Server
|
||||
code-review-graph serve # Start MCP server (stdio)
|
||||
code-review-graph serve --http # Streamable HTTP on localhost:5555
|
||||
code-review-graph serve --tools query_graph_tool,detect_changes_tool # Tool allowlist
|
||||
code-review-graph mcp # Alias for serve
|
||||
```
|
||||
|
||||
## Standalone Daemon CLI (`crg-daemon`)
|
||||
|
||||
The `crg-daemon` command is included with every `code-review-graph` installation — no
|
||||
separate install required. It is also available as a standalone entry point. It mirrors the
|
||||
`code-review-graph daemon` subcommands:
|
||||
|
||||
```bash
|
||||
crg-daemon start [--foreground] # Start the multi-repo watch daemon
|
||||
crg-daemon stop # Stop the daemon and all watcher processes
|
||||
crg-daemon restart [--foreground] # Restart (stop + start)
|
||||
crg-daemon status # Show daemon status, repos, and process liveness
|
||||
crg-daemon logs [--repo ALIAS] [-f] [-n N] # Tail daemon or per-repo log files
|
||||
crg-daemon add <path> [--alias NAME] # Add a repository to watch.toml
|
||||
crg-daemon remove <path_or_alias> # Remove a repository from watch.toml
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The daemon reads its configuration from `~/.code-review-graph/watch.toml`:
|
||||
|
||||
```toml
|
||||
session_name = "crg-watch" # logical daemon name
|
||||
log_dir = "~/.code-review-graph/logs"
|
||||
poll_interval = 2 # seconds between config file polls
|
||||
|
||||
[[repos]]
|
||||
path = "/home/user/project-a"
|
||||
alias = "project-a"
|
||||
|
||||
[[repos]]
|
||||
path = "/home/user/project-b"
|
||||
alias = "project-b"
|
||||
```
|
||||
|
||||
The daemon spawns one `code-review-graph watch` child process per repo,
|
||||
managed via `subprocess.Popen`. It monitors the config file for changes and
|
||||
automatically reconciles child processes (starting/stopping as repos are
|
||||
added or removed). Health checks run every 30 seconds and automatically
|
||||
restart dead watchers. No external dependencies (tmux, screen, etc.) are
|
||||
required.
|
||||
@@ -0,0 +1,171 @@
|
||||
# Custom Languages (Bring Your Own Language)
|
||||
|
||||
code-review-graph ships parsers for 30+ languages, but the
|
||||
[tree-sitter-language-pack](https://github.com/Goldziher/tree-sitter-language-pack)
|
||||
it depends on bundles many more grammars than the built-in list. If your repo
|
||||
uses a language the graph does not cover yet — Erlang, Haskell, OCaml,
|
||||
Fortran, Ada, Clojure, ... — you can teach the parser about it with a small
|
||||
config file. No fork, no code changes.
|
||||
|
||||
## Quick start
|
||||
|
||||
Create `<repo_root>/.code-review-graph/languages.toml`:
|
||||
|
||||
```toml
|
||||
[languages.erlang]
|
||||
extensions = [".erl"]
|
||||
grammar = "erlang"
|
||||
function_node_types = ["function_clause"]
|
||||
class_node_types = ["record_decl"]
|
||||
import_node_types = ["import_attribute"]
|
||||
call_node_types = ["call"]
|
||||
comment = "Erlang via the bundled tree-sitter-erlang grammar"
|
||||
```
|
||||
|
||||
Then rebuild:
|
||||
|
||||
```bash
|
||||
uv run code-review-graph build
|
||||
```
|
||||
|
||||
Files matching the configured extensions are now parsed with the named
|
||||
grammar, and the resulting Function/Class nodes and CALLS/IMPORTS_FROM edges
|
||||
flow through every downstream feature (impact radius, search, communities,
|
||||
wiki, MCP tools) exactly like built-in languages. Nodes carry the custom
|
||||
language name (here `erlang`) in their `language` field.
|
||||
|
||||
## Schema reference
|
||||
|
||||
Each custom language is one `[languages.<name>]` table.
|
||||
|
||||
| Key | Type | Required | Meaning |
|
||||
|-----|------|----------|---------|
|
||||
| `<name>` | table key | yes | Language identifier stored on every parsed node. Lowercase letters, digits, `_`, `-`; max 32 chars; must start with a letter. |
|
||||
| `extensions` | list of strings | yes | File extensions to claim, each starting with a dot (e.g. `".erl"`). Matched case-insensitively. |
|
||||
| `grammar` | string | yes | A grammar name shipped by `tree_sitter_language_pack` (probe availability — see below). |
|
||||
| `function_node_types` | list of strings | no* | Tree-sitter node types that define functions/methods. Matching nodes become `Function` nodes (or `Test` nodes when the name/file looks like a test). |
|
||||
| `class_node_types` | list of strings | no* | Node types that define classes/records/types. Matching nodes become `Class` nodes. |
|
||||
| `import_node_types` | list of strings | no* | Node types for import/include statements. Each yields an `IMPORTS_FROM` edge. |
|
||||
| `call_node_types` | list of strings | no* | Node types for call expressions. Each yields a `CALLS` edge from the enclosing function. |
|
||||
| `comment` | string | no | Free-form note for humans; ignored by the parser. |
|
||||
|
||||
\* At least one of the four node-type lists must be non-empty, otherwise the
|
||||
entry is skipped (there would be nothing to extract).
|
||||
|
||||
### Validation rules (safety first)
|
||||
|
||||
The loader never crashes a build. Anything invalid is skipped with a
|
||||
`WARNING` log line:
|
||||
|
||||
- **Built-ins always win.** A custom language cannot claim a built-in
|
||||
extension (`.py`, `.ts`, `.ex`, ...) and cannot reuse a built-in language
|
||||
name (`python`, `elixir`, ...).
|
||||
- `grammar` must load from `tree_sitter_language_pack`; unknown grammars are
|
||||
skipped.
|
||||
- Every extension must start with a dot.
|
||||
- Two custom languages cannot claim the same extension (first one wins).
|
||||
- At most **20** custom languages are loaded per repo.
|
||||
- Malformed TOML disables custom languages for that build (with a warning).
|
||||
|
||||
## Finding the right node type names
|
||||
|
||||
Node type names are grammar-specific, so you need to look at the tree the
|
||||
grammar actually produces. Two easy options:
|
||||
|
||||
**Option 1 — tree-sitter playground.** Paste a snippet into
|
||||
<https://tree-sitter.github.io/tree-sitter/7-playground.html> and read the
|
||||
node names off the parse tree (select the matching grammar first).
|
||||
|
||||
**Option 2 — probe locally with Python.** The exact grammar version your
|
||||
build uses is the one in `tree_sitter_language_pack`, so probing locally is
|
||||
the most reliable source of truth:
|
||||
|
||||
```bash
|
||||
uv run python - <<'EOF'
|
||||
import tree_sitter_language_pack as tslp
|
||||
|
||||
source = b"""
|
||||
-module(math_utils).
|
||||
add(A, B) -> helper(A) + B.
|
||||
helper(X) -> X * 2.
|
||||
"""
|
||||
|
||||
def dump(node, depth=0):
|
||||
print(" " * depth + node.type, node.text.decode()[:40].replace("\n", " "))
|
||||
for child in node.children:
|
||||
dump(child, depth + 1)
|
||||
|
||||
dump(tslp.get_parser("erlang").parse(source).root_node)
|
||||
EOF
|
||||
```
|
||||
|
||||
Pick the node types that wrap whole definitions (`function_clause`, not the
|
||||
inner `atom`) and whole call expressions (`call`, not the callee identifier).
|
||||
|
||||
## Worked example: Erlang end to end
|
||||
|
||||
`src/math_utils.erl`:
|
||||
|
||||
```erlang
|
||||
-module(math_utils).
|
||||
-export([add/2, scale/2]).
|
||||
-import(lists, [map/2]).
|
||||
|
||||
-record(point, {x, y}).
|
||||
|
||||
add(A, B) ->
|
||||
helper(A) + B.
|
||||
|
||||
helper(X) -> X * 2.
|
||||
|
||||
scale(Points, F) ->
|
||||
lists:map(fun(P) -> add(P, F) end, Points).
|
||||
```
|
||||
|
||||
With the `[languages.erlang]` config from the quick start, a build produces:
|
||||
|
||||
- `Function` nodes `add`, `helper`, `scale` (from `function_clause`),
|
||||
each with `language = "erlang"`.
|
||||
- A `Class` node `point` (from `record_decl`).
|
||||
- `CALLS` edges `add → helper` and `scale → add`, resolved to their
|
||||
same-file qualified names, plus `scale → lists:map` for the remote call.
|
||||
- An `IMPORTS_FROM` edge targeting `lists` (from `import_attribute`).
|
||||
- `CONTAINS` edges from the file to every definition.
|
||||
|
||||
## How extraction works (and its limits)
|
||||
|
||||
Custom languages run through the same generic tree-sitter walker as built-in
|
||||
languages — there is no per-language code path to maintain. That keeps the
|
||||
feature simple, but the generic heuristics have limits:
|
||||
|
||||
- **Name extraction uses the default name-field heuristics.** The walker
|
||||
looks for a child node of a common identifier type (`identifier`, `name`,
|
||||
`type_identifier`, ...) and falls back to the grammar's `name` field
|
||||
(`node.child_by_field_name("name")`). Grammars that store definition names
|
||||
in another shape (e.g. nested two levels deep with a non-standard field)
|
||||
will produce unnamed — and therefore skipped — definitions.
|
||||
- **Callee extraction probes common field names** (`function`, `callee`,
|
||||
`expr`, `name`) and descends through curried applications. Exotic call
|
||||
shapes may be missed.
|
||||
- **Import targets** come from the grammar's `module`/`name`/`path`/`source`
|
||||
field when present, otherwise the raw statement text is recorded.
|
||||
- **No cross-file module resolution.** Import edges keep the module name as
|
||||
written (e.g. `lists`); they are not resolved to file paths the way
|
||||
built-in languages with dedicated resolvers are.
|
||||
- **No language-specific extras**: things like decorator-based test
|
||||
detection, framework annotations (Spring, Temporal), or SFC handling only
|
||||
exist for built-in languages.
|
||||
|
||||
If a language needs deeper support than the generic walker can give, please
|
||||
open an issue — config-driven support is the on-ramp, not the ceiling.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Run a build with `-v`/logging enabled and look for `languages.toml`
|
||||
warnings — every skipped entry says exactly why it was skipped.
|
||||
- Probe grammar availability:
|
||||
`uv run python -c "import tree_sitter_language_pack as t; t.get_language('erlang')"`
|
||||
(raises `LookupError` if the grammar is not bundled).
|
||||
- The config is read when a parser is constructed (every `build`/`update`),
|
||||
so config changes take effect on the next build — re-run
|
||||
`uv run code-review-graph build` after editing.
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
# FAQ — how code-review-graph compares
|
||||
|
||||
Honest answers to the questions we get most often. Where another tool is genuinely
|
||||
better for a job, this page says so.
|
||||
|
||||
- [How is this different from LSP and language servers?](#how-is-this-different-from-lsp-and-language-servers)
|
||||
- [Isn't this just RAG?](#isnt-this-just-rag)
|
||||
- [Why not just grep?](#why-not-just-grep)
|
||||
- [How does it compare to Serena, codegraph, claude-context, and repomix?](#how-does-it-compare-to-serena-codegraph-claude-context-and-repomix)
|
||||
- [When should I not use it?](#when-should-i-not-use-it)
|
||||
- [Does it phone home?](#does-it-phone-home)
|
||||
- [How do I verify it is working?](#how-do-i-verify-it-is-working)
|
||||
- [How big a codebase justifies it?](#how-big-a-codebase-justifies-it)
|
||||
- [How does it handle monorepos, git worktrees, and multiple repos?](#how-does-it-handle-monorepos-git-worktrees-and-multiple-repos)
|
||||
|
||||
---
|
||||
|
||||
## How is this different from LSP and language servers?
|
||||
|
||||
Language servers and code-review-graph (CRG) both build a structural model of your
|
||||
code, but they optimize for different things.
|
||||
|
||||
**What LSP does better.** A language server is backed by a real compiler frontend (or
|
||||
something close to it), so it gives you type-aware, semantically precise results:
|
||||
exact go-to-definition through generics and overloads, find-references that
|
||||
understands scoping, live diagnostics, completions, and renames that are safe by
|
||||
construction. If you need a *provably complete* reference list for one symbol in one
|
||||
language, an LSP server is the gold standard and CRG does not try to replace it.
|
||||
|
||||
**What CRG does differently:**
|
||||
|
||||
- **One persistent graph instead of per-language daemons.** Language servers run one
|
||||
process per language and (with a few exceptions that cache an index on disk) rebuild
|
||||
or revalidate state per session. CRG parses once with Tree-sitter, stores nodes and
|
||||
edges in a single SQLite file (`.code-review-graph/graph.db`), and answers queries
|
||||
across roughly 35 languages plus notebooks from one process — including cross-language
|
||||
edges that no single LSP server models.
|
||||
- **It survives sessions and commits.** The graph is updated incrementally (changed
|
||||
files only, under 2 seconds on a ~2,900-file repo) rather than rebuilt per editor
|
||||
session.
|
||||
- **Review-oriented edges.** `tests_for`, execution flows, community membership,
|
||||
risk-scored change analysis — relationships LSP does not model because they are not
|
||||
needed for editing.
|
||||
|
||||
**The honest trade-off:** CRG's call resolution is AST-level and heuristic, not
|
||||
compiler-backed. Dynamic dispatch, metaprogramming, and duck typing can produce
|
||||
inferred or ambiguous edges — which is exactly why every edge carries a confidence
|
||||
tier (`EXTRACTED` / `INFERRED` / `AMBIGUOUS`). LSP is more precise per symbol; CRG is
|
||||
broader, persistent, and cheaper to query across the whole repo.
|
||||
|
||||
## Isn't this just RAG?
|
||||
|
||||
No. RAG splits your code into text chunks, embeds them, and retrieves chunks by
|
||||
similarity to the query. That answers "find code that *talks about* X." It cannot
|
||||
answer "who *calls* X" — similarity between two functions tells you nothing about
|
||||
whether one invokes the other.
|
||||
|
||||
CRG stores **structural edges parsed from the AST**: calls, imports, inheritance,
|
||||
test coverage. "Who calls `login()`" is a graph lookup, not a similarity guess.
|
||||
Embeddings exist in CRG but they are optional and play a supporting role — one input
|
||||
to hybrid search (FTS5 BM25 keyword + vector) used to find a *starting node*, after
|
||||
which traversal follows real edges. Currently only function signatures are embedded
|
||||
(~10 tokens per node), not bodies.
|
||||
|
||||
The benchmark that captures the difference is multi-hop retrieval: natural-language
|
||||
query → anchor node → one-hop traversal (`callers_of`, `tests_for`, ...). CRG scores
|
||||
0.909 across 11 hand-curated tasks on 6 real repos (see
|
||||
[REPRODUCING.md](REPRODUCING.md)). Pure similarity retrieval has no equivalent of the
|
||||
second hop.
|
||||
|
||||
**Where RAG-style search is better:** purely conceptual questions ("where is rate
|
||||
limiting discussed?") over prose, comments, and docs. CRG's own keyword search
|
||||
ranking is a documented weakness (MRR 0.35 — see the limitations section in the
|
||||
[README](../README.md#benchmarks)).
|
||||
|
||||
## Why not just grep?
|
||||
|
||||
Fair question — Anthropic has been explicit that Claude Code deliberately ships
|
||||
*without* a code index. Agentic search (glob, grep, targeted file reads) is always
|
||||
exactly as fresh as your working tree, has no chunking or staleness failure modes,
|
||||
and needs zero setup. For one-hop questions — "where is `parse_file` defined?" —
|
||||
that approach works well, and CRG will not beat it by much.
|
||||
|
||||
The gap appears on **multi-hop structural questions**, where each hop costs the agent
|
||||
another round of grep + read + reasoning, and token spend compounds:
|
||||
|
||||
- **Impact radius** — "what could break if I change this file?" requires callers,
|
||||
dependents, *and* their tests. One `get_impact_radius` call returns all three.
|
||||
- **Callers of callers** — transitive tracing via `traverse_graph` or repeated
|
||||
`query_graph(pattern="callers_of")`, instead of N rounds of grepping for each
|
||||
intermediate name (and grep matches *text*, so overloaded or re-exported names
|
||||
produce false hits the agent must read to rule out).
|
||||
- **Tests for** — `query_graph(pattern="tests_for")` maps code to covering tests via
|
||||
parsed edges plus naming conventions, and `detect_changes` adds transitive test
|
||||
coverage. Grep only finds tests that mention the name literally.
|
||||
- **Affected flows** — "which execution paths does this change touch?" has no grep
|
||||
equivalent at all.
|
||||
|
||||
The graph also persists: agentic search re-derives the same structure from scratch
|
||||
every session, while CRG keeps it in SQLite and updates incrementally.
|
||||
|
||||
One honest caveat on the numbers: the whole-corpus token-reduction numbers (~82x median,
|
||||
38x–528x range) compare graph responses against reading the **whole corpus**, not
|
||||
against a skilled agentic-grep session (see [REPRODUCING.md](REPRODUCING.md) for what
|
||||
each benchmark measures). For single-hop lookups in a small repo, grep is cheap and
|
||||
good. The multi-hop review workflow is where the graph earns its keep.
|
||||
|
||||
## How does it compare to Serena, codegraph, claude-context, and repomix?
|
||||
|
||||
These are good tools solving adjacent problems. Short factual comparison, based on
|
||||
each project's public documentation (check upstream docs for current behavior):
|
||||
|
||||
| Tool | Approach | Persistence | External deps | Review focus |
|
||||
|---|---|---|---|---|
|
||||
| **code-review-graph** | Tree-sitter AST → structural graph (calls, imports, inheritance, tests) over MCP + CLI | SQLite in `.code-review-graph/`, incremental updates | None for the core; embeddings optional | Yes — blast radius, risk-scored change analysis, test-gap detection |
|
||||
| **Serena** | LSP-backed symbol retrieval and editing tools over MCP | Language-server state plus per-project memories | A language server per language | General coding-agent toolkit, not review-specific |
|
||||
| **codegraph** | AST/call-graph indexing over MCP (several projects share this name; details vary by implementation) | Varies by implementation | Varies by implementation | Generally retrieval-focused |
|
||||
| **claude-context** | Chunk + embed semantic code search over MCP | Vector index in a vector database | Embedding provider + vector DB (cloud or self-hosted) | Search-focused, not review-specific |
|
||||
| **repomix** | Packs the whole repo into one AI-friendly file | None — regenerated per run | Node.js | One-shot context packing; no structural queries |
|
||||
|
||||
Rough guidance: if you want symbol-precise *editing* tools, Serena's LSP approach is
|
||||
a better fit. If you want semantic *search* and are happy running a vector store,
|
||||
claude-context covers that. If your repo is small enough to paste wholesale into a
|
||||
large context window, repomix is the simplest thing that works. CRG's niche is the
|
||||
persistent structural graph for **review**: impact analysis, risk scoring, and
|
||||
test-coverage tracing with no external services.
|
||||
|
||||
## When should I not use it?
|
||||
|
||||
Consistent with the limitations section in the [README](../README.md#benchmarks):
|
||||
|
||||
- **Repos under a few hundred files.** An agent can often just read everything
|
||||
relevant directly; the graph's structural metadata adds overhead that a small repo
|
||||
doesn't repay. See [How big a codebase justifies it?](#how-big-a-codebase-justifies-it)
|
||||
- **Trivial single-file changes.** The graph response carries impact-radius edges and
|
||||
source snippets, which can exceed the raw content of a one-file diff. This is
|
||||
measured and documented (the formal `token_efficiency` benchmark reports ratios
|
||||
below 1.0 for small commits — by design, see [REPRODUCING.md](REPRODUCING.md)).
|
||||
- **One-off questions on a repo you won't revisit.** The build is fast (~10 seconds
|
||||
for a 500-file project) but the payoff comes from *reuse* across queries and
|
||||
sessions. For a single question, agentic search is fine.
|
||||
- **Flow detection on JS/Go.** Entry-point detection is currently reliable mainly for
|
||||
Python framework patterns; JavaScript and Go flow detection needs work (33% recall,
|
||||
documented in the README limitations).
|
||||
|
||||
## Does it phone home?
|
||||
|
||||
No. There is zero telemetry. The graph is a SQLite file in `.code-review-graph/`
|
||||
inside your repo, and the core build / review / search / MCP workflows run entirely
|
||||
locally. The streamable-HTTP MCP transport binds to localhost by default.
|
||||
|
||||
The only network activity is opt-in:
|
||||
|
||||
- **Local embeddings** (`pip install code-review-graph[embeddings]`) download the
|
||||
sentence-transformers model from HuggingFace on first use. Your code does not leave
|
||||
the machine.
|
||||
- **Cloud embeddings** (OpenAI-compatible, Google Gemini, MiniMax) send the text being
|
||||
embedded — currently function signatures — to the provider you explicitly configure
|
||||
via environment variables. CRG prints an egress warning unless you acknowledge it
|
||||
with `CRG_ACCEPT_CLOUD_EMBEDDINGS=1`; the warning is skipped automatically when the
|
||||
endpoint is localhost.
|
||||
|
||||
See [LEGAL.md](LEGAL.md) for the full privacy notes.
|
||||
|
||||
## How do I verify it is working?
|
||||
|
||||
1. **Check the graph exists and has content:**
|
||||
|
||||
```bash
|
||||
code-review-graph status
|
||||
```
|
||||
|
||||
You should see node/edge counts and graph statistics. Zero nodes means the build
|
||||
didn't run or found nothing to parse.
|
||||
|
||||
2. **See the savings on a real change** — make any edit, then:
|
||||
|
||||
```bash
|
||||
code-review-graph detect-changes --brief
|
||||
```
|
||||
|
||||
This prints the risk summary and the boxed **Token Savings** panel against the
|
||||
existing graph (read-only). Add `--verify` to cross-check the estimate against
|
||||
OpenAI's `cl100k_base` tokenizer (requires `pip install tiktoken`). If you suspect
|
||||
the graph is stale, `code-review-graph update --brief` re-parses changed files
|
||||
first and prints the same panel.
|
||||
|
||||
3. **Check the MCP wiring** — in Claude Code, run `/mcp` and confirm the
|
||||
`code-review-graph` server is connected with its tools listed. Then ask the
|
||||
assistant something structural ("what calls `parse_file`?") and watch it use
|
||||
`query_graph` instead of grepping.
|
||||
|
||||
If any of these fail, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
|
||||
|
||||
## How big a codebase justifies it?
|
||||
|
||||
This comes up often (see #414). Honest guidance, tied to the documented small-repo
|
||||
overhead:
|
||||
|
||||
- **Below a few hundred files:** marginal. The graph builds in seconds and works
|
||||
fine, but an agent can already hold most of the repo in context, and for trivial
|
||||
diffs the structural response can cost more tokens than it saves (the documented
|
||||
overhead regime — see [When should I not use it?](#when-should-i-not-use-it)).
|
||||
- **A few hundred to a few thousand files:** this is where the benchmarks live. The
|
||||
six evaluation repos range from 60 to ~1,100 files and show 38x–528x reductions on
|
||||
whole-corpus agent questions, with the caveat noted above about what that baseline
|
||||
measures.
|
||||
- **Multi-thousand-file repos and monorepos:** the strongest case. No agent can read
|
||||
the corpus per question (FastAPI alone is ~950k tokens of source), re-deriving
|
||||
structure by search every session is the dominant cost, and incremental updates
|
||||
keep the graph fresh in under 2 seconds.
|
||||
|
||||
A second axis matters as much as file count: **how often you ask multi-file
|
||||
questions**. A 300-file repo you review daily benefits more than a 3,000-file repo
|
||||
you touch once.
|
||||
|
||||
## How does it handle monorepos, git worktrees, and multiple repos?
|
||||
|
||||
**Monorepos.** One graph per repository root by default — commands auto-detect the
|
||||
root by walking up to the nearest `.git`, and in git repos only tracked files are
|
||||
indexed (`git ls-files`), so gitignored build artifacts are skipped automatically.
|
||||
Use a `.code-review-graphignore` file to exclude tracked paths (e.g. `vendor/**`,
|
||||
generated code), or pass `--repo <path>` to point a command at a specific directory.
|
||||
|
||||
**Git worktrees.** Each worktree is detected as its own root, so each gets its own
|
||||
`.code-review-graph/` database matching its checkout. Don't try to share one database
|
||||
across worktrees at different commits — the graph reflects one working tree. If you
|
||||
want the database outside the working tree entirely (ephemeral workspaces, network
|
||||
shares), use `--data-dir <path>` on `build`/`update`/etc., or set the `CRG_DATA_DIR`
|
||||
environment variable.
|
||||
|
||||
**Multiple repos.** A lightweight registry (stored at
|
||||
`~/.code-review-graph/registry.json`) lets MCP clients search across projects:
|
||||
|
||||
```bash
|
||||
code-review-graph register ~/work/api --alias api # add a repo (optional alias)
|
||||
code-review-graph repos # list registered repos
|
||||
code-review-graph unregister api # remove by path or alias
|
||||
```
|
||||
|
||||
Once registered, the `list_repos_tool` and `cross_repo_search_tool` MCP tools work
|
||||
across all of them. To keep several graphs fresh automatically, the bundled daemon watches
|
||||
registered repos as child processes:
|
||||
|
||||
```bash
|
||||
crg-daemon add ~/work/api --alias api
|
||||
crg-daemon start
|
||||
crg-daemon status
|
||||
```
|
||||
|
||||
(Also available as `code-review-graph daemon start|stop|status`.) See
|
||||
[COMMANDS.md](COMMANDS.md) for the full daemon reference.
|
||||
@@ -0,0 +1,155 @@
|
||||
# Features
|
||||
|
||||
## v2.3.6 (Current)
|
||||
- **Custom languages without forking**: drop a `.code-review-graph/languages.toml` into your repo to index any grammar shipped by tree-sitter-language-pack — extension map plus node-type lists, validated and capped, with built-in languages always winning. See [CUSTOM_LANGUAGES.md](CUSTOM_LANGUAGES.md).
|
||||
- **GitHub Action for risk-scored PR reviews**: composite `action.yml` builds/restores the graph from CI cache, runs `detect-changes` against the PR base, and upserts a sticky comment with risk table, affected flows, test gaps, and the Token Savings line. Optional `fail-on-risk` merge gate. Dogfooded on this repo via `.github/workflows/pr-review.yml`. See [GITHUB_ACTION.md](GITHUB_ACTION.md).
|
||||
- **`agent_baseline` eval benchmark**: compares graph queries against a realistic grep-and-read-top-k agent baseline instead of the whole-corpus strawman; wired into all six pinned eval configs.
|
||||
- **Co-change ground truth for `impact_accuracy`**: predictions are also graded against files actually co-changed in the same commit; the legacy metric is explicitly labelled "graph-derived (circular — upper bound)".
|
||||
- **Weekly eval CI**: `.github/workflows/eval.yml` runs a report-only cron of the two smallest pinned configs with CSV artifacts and a job summary.
|
||||
- **docs/FAQ.md**: how CRG compares to LSP, RAG, grep/agentic search, and adjacent tools; when NOT to use it; verification steps; monorepo/worktree and registry guidance.
|
||||
- **Contribution scaffolding**: GitHub issue forms (bug/feature/platform), a PR template mirroring the CONTRIBUTING checklist, and dependabot config for pip + GitHub Actions.
|
||||
- **Windows fixes**: `daemon status` no longer crashes with WinError 87 (#511), and CLI `detect-changes` maps diff paths to absolute native paths so it no longer reports 0 functions (#528).
|
||||
- **Provider-name validation**: unknown embedding provider names raise a clear error listing valid providers instead of silently falling back to the local model.
|
||||
- **Store-leak fixes**: the five analysis MCP tools and the wiki-page tool no longer leak SQLite connections (try/finally `store.close()`).
|
||||
- **`fastmcp<4` cap**: the next fastmcp major can no longer silently break the server.
|
||||
- **Worktree-safe git hooks**: `install` resolves the real hooks directory via `git rev-parse --git-path hooks`, so linked worktrees and `core.hooksPath` (husky) setups get a working pre-commit hook.
|
||||
|
||||
## v2.3.5
|
||||
- **Token Savings panel on every brief CLI call**: `code-review-graph detect-changes --brief` and the new `code-review-graph update --brief` print a boxed `Token Savings` panel — full-context baseline, graph response, saved tokens, percent, and per-category breakdown (Functions / Tests / Risk / Other) that sums exactly to the graph response size.
|
||||
- **`--verify` flag**: cross-checks the displayed numbers against OpenAI's `cl100k_base` tokenizer (the GPT-4 family). Adds a second `Verified (tiktoken)` row showing real token counts. Calibration across 222 mixed-language files shows the estimate is within ~1% of real tokens in aggregate.
|
||||
- **`update --brief`**: incremental update + the same risk panel in one command. Distinct from `detect-changes --brief` (which is read-only against the existing graph) — use update when the graph might be stale (post-rebase, large change set).
|
||||
- **`code-review-graph embed` CLI subcommand**: explicit shell-level access to embedding generation. Previously only reachable via MCP.
|
||||
- **Deterministic eval pipeline**: all 6 eval configs pin upstream SHAs, `eval/runner.py` uses full clones with explicit `returncode` checks, and Leiden community detection uses a fixed seed (`CRG_LEIDEN_SEED=42`). Two runs on different machines produce identical numbers.
|
||||
- **`multi_hop_retrieval` benchmark**: 11 hand-curated 2-step tool-chain tasks (`hybrid_search` → `query_graph`) across the 6 test repos. Average score 0.909.
|
||||
- **Richer semantic search**: `embeddings._node_to_text` now includes the dotted form (`Module.Class.method`), word-split identifiers, and enclosing module directory. Search ranking on natural-language queries improved from 0.545 → 0.909 on the multi-hop benchmark.
|
||||
- **Identifier-aware search boost**: `extract_query_identifiers` pulls dotted / snake_case / CamelCase tokens out of NL queries and boosts matching qualified-names ×2.0 in hybrid search.
|
||||
- **Path normalization fix**: `eval/runner.py` now resolves repo paths absolutely before storing, so the eval-built graph matches the CLI/MCP-built graph and `update` doesn't create duplicate nodes for the same source location.
|
||||
- **Test-gap dedup**: the `Untested:` line in the brief summary dedupes by bare name (defensive guard if duplicate qualified_names slip in).
|
||||
- **FTS5 auto-rebuild in eval**: the eval framework now calls `run_post_processing` after `full_build`, so FTS5 is populated automatically instead of leaving the index empty.
|
||||
|
||||
## v2.3.4
|
||||
- **Estimated context savings**: Review, impact, detect-changes, and compact architecture responses include tiny `context_savings` metadata (`estimated`, `saved_tokens`, `saved_percent`) where a baseline can be estimated.
|
||||
- **Compact architecture overview by default**: `get_architecture_overview_tool` defaults to `detail_level="minimal"` to avoid huge member lists and per-edge payloads. Use `detail_level="standard"` for full detail.
|
||||
- **Bounded change analysis**: `CRG_MAX_CHANGED_FUNCS`, `CRG_MAX_TRANSITIVE_FRONTIER`, and `CRG_TOOL_TIMEOUT` help keep large MCP review calls responsive.
|
||||
- **Windows MCP reliability**: Local embedding models are pre-warmed on Windows before FastMCP starts worker dispatch to avoid semantic-search deadlocks.
|
||||
- **Parser correctness**: Rust `#[test]` and common async test attributes now produce `Test` nodes.
|
||||
- **Graph lookup correctness**: Review, impact, and file-summary tools resolve user-facing paths to stored graph paths; `callers_of` includes cross-file callers even when same-file callers exist.
|
||||
- **Install/runtime reliability**: Generated Codex/Claude hooks drain stdin, bundled docs are available from wheels, missing local embeddings report unavailable status, and `.svn` roots pass validation.
|
||||
- **CLI reliability**: `build --skip-postprocess` and `update --skip-flows` honor the requested post-processing level.
|
||||
- **Broad parser surface**: Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks, and Perl XS files.
|
||||
- **Local-first by design**: SQLite graph storage remains local, with no telemetry and no cloud-default behavior.
|
||||
|
||||
## v2.0.0
|
||||
- **22 MCP tools** (up from 9): 13 new tools for flows, communities, architecture, refactoring, wiki, multi-repo, and risk-scored change detection.
|
||||
- **5 MCP prompts**: `review_changes`, `architecture_map`, `debug_issue`, `onboard_developer`, `pre_merge_check` workflow templates.
|
||||
- **18 languages** (up from 15): Added Dart, R, Perl support.
|
||||
- **Execution flows**: Trace call chains from entry points (HTTP handlers, CLI commands, tests), sorted by criticality score.
|
||||
- **Community detection**: Cluster related code entities via Leiden algorithm (igraph) or file-based grouping.
|
||||
- **Architecture overview**: Auto-generated architecture map with module summaries and cross-community coupling warnings.
|
||||
- **Risk-scored change detection**: `detect_changes` maps git diffs to affected functions, flows, communities, and test coverage gaps with priority ordering.
|
||||
- **Refactoring tools**: Rename preview with edit list, dead code detection, community-driven refactoring suggestions.
|
||||
- **Wiki generation**: Auto-generate markdown wiki pages for each community with optional LLM summaries (ollama).
|
||||
- **Multi-repo registry**: Register multiple repositories, search across all of them with `cross_repo_search`.
|
||||
- **Full-text search**: FTS5 virtual table with porter stemming for hybrid keyword + vector search.
|
||||
- **Database migrations**: Versioned schema migrations (v1-v5) with automatic upgrade on startup.
|
||||
- **Optional dependency groups**: `[embeddings]`, `[google-embeddings]`, `[communities]`, `[eval]`, `[wiki]`, `[all]`.
|
||||
- **Evaluation framework**: Benchmark suite with matplotlib visualization.
|
||||
- **TypeScript path resolution**: tsconfig.json paths/baseUrl alias resolution for imports.
|
||||
- **486 tests** across 22 test files.
|
||||
|
||||
## v1.8.4
|
||||
- **Multi-word AND search**: `search_nodes` now requires all words to match (case-insensitive), producing more precise results.
|
||||
- **Call target resolution**: Bare call targets are resolved to qualified names using same-file definitions, improving `callers_of`/`callees_of` accuracy.
|
||||
- **Impact radius pagination**: `get_impact_radius` returns `truncated` flag and `total_impacted` count; `max_results` parameter controls output size.
|
||||
- **`find_large_functions_tool`**: New MCP tool to find functions, classes, or files exceeding a line-count threshold.
|
||||
- **15 languages**: Added Vue SFC and Solidity support.
|
||||
- **Documentation overhaul**: All docs updated with accurate language/tool counts, version references, and VS Code extension parity.
|
||||
|
||||
## v1.8.3
|
||||
- **Parser recursion guard**: `_MAX_AST_DEPTH = 180` prevents stack overflow on deeply nested ASTs.
|
||||
- **Module cache bound**: `_MODULE_CACHE_MAX = 15,000` with automatic eviction.
|
||||
- **Embeddings thread safety**: `check_same_thread=False` on EmbeddingStore SQLite.
|
||||
- **Embeddings retry logic**: Exponential backoff for Google Gemini API calls.
|
||||
- **Visualization XSS hardening**: `</` escaped to `<\/` in JSON serialization.
|
||||
- **CLI error handling**: Split broad `except` into specific handlers.
|
||||
- **Git timeout**: Configurable via `CRG_GIT_TIMEOUT` env var.
|
||||
- **Governance files**: CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md.
|
||||
|
||||
## v1.8.2
|
||||
- **C# parsing fix**: Renamed language identifier from `c_sharp` to `csharp`.
|
||||
- **Watch mode thread safety**: SQLite connections compatible with Python 3.10/3.11 watchdog threads.
|
||||
- **Full rebuild cleanup**: Purges stale data from deleted files during full rebuild.
|
||||
- **Dependency trim**: Removed unused `gitpython` dependency.
|
||||
|
||||
## v1.7.0
|
||||
- **`install` command**: New primary entry point for setup (`code-review-graph install`). `init` remains as an alias.
|
||||
- **`--dry-run` flag**: Preview what `install`/`init` would write without modifying files.
|
||||
- **PyPI auto-publish**: GitHub releases now automatically publish to PyPI.
|
||||
- **README rewrite**: Professional documentation with real benchmark data from httpx, FastAPI, and Next.js.
|
||||
|
||||
## v1.6.4
|
||||
- **Portable MCP config**: `init` now generates `uvx`-based `.mcp.json` — no absolute paths, works on any machine with `uv` installed
|
||||
- **Removed symlink workaround**: The `_safe_path` helper for spaces-in-paths is no longer needed with `uvx`
|
||||
|
||||
## v1.6.3
|
||||
- **SessionStart hook**: Claude Code automatically prefers graph MCP tools over full codebase scans at session start
|
||||
- **Marketplace ready**: plugin.json corrected for official Claude Code plugin marketplace submission
|
||||
- **README cleanup**: Removed screenshot placeholders
|
||||
|
||||
## v1.6.2
|
||||
- **24 audit fixes**: Critical bug fixes, performance improvements, parser enhancements, expanded test coverage
|
||||
- **Parser: C/C++ support**: Full node extraction for C and C++ (classes, functions, imports, calls, inheritance)
|
||||
- **Parser: name extraction**: Fixed for Kotlin, Swift (simple_identifier), Ruby (constant)
|
||||
- **Performance**: NetworkX graph caching, batch edge queries, chunked embedding search, git subprocess timeouts
|
||||
- **CI hardening**: Coverage enforcement (50%), bandit security scanning, mypy type checking
|
||||
- **Tests**: +40 new tests for incremental updates, embeddings, and 7 new language fixtures
|
||||
- **Docs**: API response schemas, ignore pattern documentation, fixed hook config reference
|
||||
- **Accessibility**: ARIA labels throughout D3.js visualization
|
||||
|
||||
## v1.5.3
|
||||
- **Spaces-in-path handling**: *(superseded in v1.6.4 by `uvx`-based config)* Previously used symlinks for spaces in paths
|
||||
- **No git required**: `build`, `status`, `visualize`, `watch` now work on any directory without git
|
||||
- **Plugin ready**: Skills registered in plugin.json, SKILL.md frontmatter fixed
|
||||
- **File organization**: Generated files moved into `.code-review-graph/` directory (auto-created `.gitignore`, legacy migration)
|
||||
- **Visualization density**: Starts collapsed (File nodes only), search bar, clickable edge type toggles, scale-aware layout for large graphs
|
||||
- **Project cleanup**: Removed redundant `references/`, `agents/`, `settings.json`
|
||||
|
||||
## v1.4.0
|
||||
- **`init` command**: Automatic `.mcp.json` setup for Claude Code integration
|
||||
- **Interactive D3.js graph visualization**: `code-review-graph visualize` generates an HTML graph you can explore in-browser
|
||||
- **Documentation overhaul**: Comprehensive docs audit across all reference files
|
||||
|
||||
## v1.3.0
|
||||
- **Python version check with Docker fallback**: Automatically detects Python 3.10+ and suggests Docker if unavailable
|
||||
- **Universal install**: `pip install code-review-graph` — no git clone needed
|
||||
- **CLI entry point**: `code-review-graph` command available system-wide after pip install
|
||||
|
||||
## v1.2.0
|
||||
- **Logging improvements**: Structured logging throughout the codebase
|
||||
- **Watch debounce**: Smarter file-change detection in watch mode
|
||||
- **tools.py fixes**: Bug fixes and reliability improvements for MCP tools
|
||||
- **CI coverage**: GitHub Actions CI/CD pipeline with test coverage reporting
|
||||
|
||||
## v1.1.0
|
||||
- **Watch mode**: `code-review-graph watch` — auto-rebuilds graph on file changes
|
||||
- **Vector embeddings**: Optional `pip install .[embeddings]` for semantic code search
|
||||
- **Go, Rust, Java verified**: 12+ languages with dedicated test coverage
|
||||
- **47 tests passing**, 8 MCP tools registered
|
||||
- README badges and cleaner install flow
|
||||
|
||||
## v1.0.0 (Foundation)
|
||||
- **Persistent SQLite knowledge graph** — zero external dependencies
|
||||
- **Tree-sitter multi-language parsing** — classes, functions, imports, calls, inheritance
|
||||
- **Incremental updates** via `git diff` + automatic dependency cascade
|
||||
- **Impact-radius / blast-radius analysis** — BFS through call/import/inheritance graph
|
||||
- **6 MCP tools** for full graph interaction
|
||||
- **3 review-first skills**: build-graph, review-delta, review-pr
|
||||
- **PostToolUse hooks** (Write|Edit|Bash) for automatic background updates
|
||||
- **FastMCP 3.0 compatible** stdio MCP server
|
||||
|
||||
## Privacy & Data
|
||||
- Core graph data is stored locally
|
||||
- Graph stored in `.code-review-graph/graph.db` (SQLite), auto-gitignored
|
||||
- No telemetry; core graph/review workflows do not require network access
|
||||
- Optional embedding and wiki features may call configured local or remote services when explicitly enabled
|
||||
- Respects `.gitignore` and `.code-review-graphignore`
|
||||
@@ -0,0 +1,166 @@
|
||||
# GitHub Action: Risk-Scored PR Review
|
||||
|
||||
code-review-graph ships a composite GitHub Action (`action.yml` at the repo
|
||||
root) that posts a risk-scored, graph-aware review comment on every pull
|
||||
request — think of it as a hosted AI review bot (Greptile-style), except the
|
||||
analysis is **local-first**: the knowledge graph is built and queried entirely
|
||||
on your CI runner, and no source code is sent to any external service.
|
||||
|
||||
On each PR run the action:
|
||||
|
||||
1. Installs `code-review-graph` from PyPI.
|
||||
2. Restores the cached `.code-review-graph/` SQLite graph (or builds it from
|
||||
scratch on a cache miss) and incrementally re-parses the files changed by
|
||||
the PR.
|
||||
3. Runs `code-review-graph detect-changes --base origin/<base-branch>` to get
|
||||
risk-scored functions, affected execution flows, and test gaps.
|
||||
4. Renders a markdown report (via `scripts/render_pr_comment.py`) and upserts
|
||||
a single sticky PR comment — the same comment is updated on every push, so
|
||||
the PR thread is never spammed.
|
||||
5. Optionally fails the job when the overall risk score crosses a threshold
|
||||
(`fail-on-risk`).
|
||||
|
||||
## Quick start (external repositories)
|
||||
|
||||
```yaml
|
||||
# .github/workflows/code-review-graph.yml
|
||||
name: code-review-graph
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: tirth8205/code-review-graph@v2.3.6
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
That is the whole setup. The default `GITHUB_TOKEN` provided by Actions is
|
||||
sufficient — no PAT, no API key, no third-party service.
|
||||
|
||||
To turn the review into a merge gate:
|
||||
|
||||
```yaml
|
||||
- uses: tirth8205/code-review-graph@v2.3.6
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fail-on-risk: high
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `github-token` | yes | — | Token used to post the sticky PR comment via the GitHub API. The workflow's default `GITHUB_TOKEN` works when the job has `pull-requests: write`. |
|
||||
| `comment` | no | `true` | Post (and keep updated) the sticky PR comment. Set to `false` to run analysis/gating without commenting. |
|
||||
| `fail-on-risk` | no | `none` | Fail the job when the overall risk score reaches a level: `none` (never fail), `high` (risk ≥ 0.70), `critical` (risk ≥ 0.85). |
|
||||
| `python-version` | no | `3.12` | Python version used to run code-review-graph (3.10+ supported). |
|
||||
|
||||
### Risk levels
|
||||
|
||||
`detect-changes` produces a 0.0–1.0 overall risk score (max across changed
|
||||
functions; see `code_review_graph/changes.py:compute_risk_score` for the
|
||||
scoring factors: flow participation, community crossing, test coverage,
|
||||
security-sensitive names, caller count). The action maps it to levels:
|
||||
|
||||
| Level | Score |
|
||||
|-------|-------|
|
||||
| low | < 0.40 |
|
||||
| medium | 0.40 – 0.69 |
|
||||
| high | 0.70 – 0.84 |
|
||||
| critical | ≥ 0.85 |
|
||||
|
||||
## What the comment contains
|
||||
|
||||
- **Overall risk** score and level, with counts of changed functions,
|
||||
affected flows, and test gaps.
|
||||
- **Risk-scored changes** — a table of the top changed symbols ordered by
|
||||
risk, with file:line locations and test-coverage status.
|
||||
- **Affected execution flows** — which entry-point flows the change touches,
|
||||
ordered by criticality.
|
||||
- **Test gaps** — changed functions with no direct test coverage.
|
||||
- **Token savings** — how many tokens the graph-backed report saved versus
|
||||
reading every changed file in full. This is the same `context_savings`
|
||||
estimate the CLI's Token Savings panel shows (a `chars / 4` approximation
|
||||
labelled `estimated: true` — see [REPRODUCING.md](REPRODUCING.md) for the
|
||||
calibration methodology).
|
||||
- A `Powered by code-review-graph` footer.
|
||||
|
||||
The comment starts with a hidden HTML marker
|
||||
(`<!-- code-review-graph-report -->`). The action looks the marker up via
|
||||
`gh api` on each run and PATCHes the existing comment instead of creating a
|
||||
new one (a "sticky" comment).
|
||||
|
||||
## Cache behavior
|
||||
|
||||
The action caches the `.code-review-graph/` directory (the SQLite graph
|
||||
database) with `actions/cache`:
|
||||
|
||||
- **Key**: `code-review-graph-schema9-<runner.os>-<hashFiles(lockfiles)>`,
|
||||
where the lockfile hash covers common Python/JS/Go/Rust/Ruby/PHP lockfiles
|
||||
(`uv.lock`, `poetry.lock`, `requirements*.txt`, `package-lock.json`,
|
||||
`go.sum`, `Cargo.lock`, …).
|
||||
- **Schema segment**: `schema9` tracks the database schema version
|
||||
(`LATEST_VERSION` in `code_review_graph/migrations.py`). It is bumped when
|
||||
the schema changes so stale caches are never restored across incompatible
|
||||
versions.
|
||||
- **Restore keys**: fall back to any cache for the same OS and schema, so a
|
||||
lockfile change still reuses the previous graph.
|
||||
- **On cache hit**: the action runs `code-review-graph update --base
|
||||
origin/<base-branch>`, which re-parses only the files that differ from the
|
||||
PR's base ref. If the restored database turns out to be unusable, it falls
|
||||
back to a full `build`.
|
||||
- **On cache miss**: a full `code-review-graph build` runs (one-time cost;
|
||||
subsequent PR runs are incremental).
|
||||
|
||||
## Security notes
|
||||
|
||||
- **Token scope**: the action needs only `pull-requests: write` (to post the
|
||||
comment) and `contents: read` (for checkout). Grant exactly that in the
|
||||
workflow's `permissions:` block — the examples above do. The token is used
|
||||
for nothing except listing/creating/updating the one PR comment.
|
||||
- **Local-first**: analysis runs entirely on the runner. No code, diff, or
|
||||
metadata leaves GitHub's infrastructure; there is no external API, account,
|
||||
or key.
|
||||
- **Untrusted input**: all dynamic values (`github.base_ref`, the PR number,
|
||||
action inputs) are passed to scripts through environment variables, never
|
||||
interpolated into shell commands. The markdown renderer escapes
|
||||
table/markup characters and strips control characters from symbol names
|
||||
and file paths before they reach the comment body, on top of the
|
||||
server-side `_sanitize_name()` sanitization.
|
||||
- **Pinning**: when consuming the action from another repository, pin
|
||||
`uses:` to a release tag or commit SHA rather than `@main`.
|
||||
- **Fork PRs**: `pull_request` runs from forks receive a read-only
|
||||
`GITHUB_TOKEN`, so the comment step will fail for fork PRs unless you use
|
||||
`pull_request_target` — which checks out trusted base-branch workflow
|
||||
code; understand [the security implications](https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/)
|
||||
before switching, or set `comment: false` for fork PRs.
|
||||
|
||||
## Dogfooding
|
||||
|
||||
This repository runs the action on its own PRs via
|
||||
[`.github/workflows/pr-review.yml`](../.github/workflows/pr-review.yml),
|
||||
which `uses: ./` (the local `action.yml`).
|
||||
|
||||
## Rendering script
|
||||
|
||||
The markdown rendering and risk gating logic lives in
|
||||
[`scripts/render_pr_comment.py`](../scripts/render_pr_comment.py) (stdlib
|
||||
only, unit-tested in `tests/test_action_render.py`) rather than inline YAML,
|
||||
so it can be tested and reused:
|
||||
|
||||
```bash
|
||||
code-review-graph detect-changes --base origin/main | \
|
||||
python scripts/render_pr_comment.py # markdown to stdout
|
||||
|
||||
python scripts/render_pr_comment.py --input report.json \
|
||||
--fail-on-risk high --quiet # gate only: exit 3 on breach
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
# Documentation Index
|
||||
|
||||
- [USAGE.md](USAGE.md) -- How to install and use
|
||||
- [FAQ.md](FAQ.md) -- How it compares to LSP, RAG, grep, and similar tools; when not to use it
|
||||
- [FEATURES.md](FEATURES.md) -- What's included, changelog
|
||||
- [COMMANDS.md](COMMANDS.md) -- All 30 MCP tools, 5 MCP prompts, skills, and CLI commands
|
||||
- [GITHUB_ACTION.md](GITHUB_ACTION.md) -- Risk-scored PR review comments via GitHub Actions
|
||||
- [CUSTOM_LANGUAGES.md](CUSTOM_LANGUAGES.md) -- Bring your own language via `.code-review-graph/languages.toml`
|
||||
- [LLM-OPTIMIZED-REFERENCE.md](LLM-OPTIMIZED-REFERENCE.md) -- Token-optimized reference for MCP-capable AI coding agents
|
||||
- [architecture.md](architecture.md) -- System design and data flow
|
||||
- [schema.md](schema.md) -- Graph node/edge schema, SQLite tables (including flows, communities, FTS5)
|
||||
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) -- Common issues and fixes (including Windows/WSL)
|
||||
- [REPRODUCING.md](REPRODUCING.md) -- Reproducing every benchmark number (pinned SHAs, seeded runs, tokenizer calibration)
|
||||
- [ROADMAP.md](ROADMAP.md) -- Shipped and planned features
|
||||
- [LEGAL.md](LEGAL.md) -- License and privacy
|
||||
@@ -0,0 +1,16 @@
|
||||
# Legal & Privacy
|
||||
|
||||
**License:** MIT (see [LICENSE](../LICENSE) in project root)
|
||||
|
||||
**Privacy:**
|
||||
- Zero telemetry
|
||||
- All graph data stored locally in `.code-review-graph/graph.db`
|
||||
- Core graph build, review, search, and CLI/MCP workflows run locally
|
||||
- Optional local embeddings may download a sentence-transformers model from HuggingFace when first used
|
||||
- Optional cloud embedding providers (`openai`, `google`, `minimax`) send embedded source snippets to the configured provider only when explicitly selected
|
||||
- Remote embedding providers print an egress warning unless `CRG_ACCEPT_CLOUD_EMBEDDINGS=1` is set
|
||||
- Streamable HTTP MCP transport binds to localhost by default
|
||||
|
||||
**Data:** Core graph data stays on your machine. If you opt into a cloud embedding provider, the text being embedded leaves your machine under that provider's terms.
|
||||
|
||||
**Warranty:** Provided as-is, without warranty of any kind.
|
||||
@@ -0,0 +1,71 @@
|
||||
# LLM-OPTIMIZED REFERENCE -- code-review-graph v2.3.6
|
||||
|
||||
AI coding agents: Read ONLY the exact `<section>` you need. Never load the whole file.
|
||||
|
||||
<section name="usage">
|
||||
Quick install: pip install code-review-graph
|
||||
Then: code-review-graph install && code-review-graph build
|
||||
First run: /code-review-graph:build-graph
|
||||
After that use only delta/pr commands.
|
||||
ALWAYS start with get_minimal_context_tool(task="your task") — returns ~100 tokens with risk, communities, flows, and suggested next tools.
|
||||
Use detail_level="minimal" on all subsequent calls unless you need more detail.
|
||||
When present, context_savings is an estimated compact hint, not exact tokenization.
|
||||
</section>
|
||||
|
||||
<section name="review-delta">
|
||||
1. Call get_minimal_context_tool(task="review changes") first.
|
||||
2. If risk is low: detect_changes_tool(detail_level="minimal") → report summary.
|
||||
3. If risk is medium/high: detect_changes_tool(detail_level="standard") → expand on high-risk items.
|
||||
Target: ≤5 tool calls, ≤800 tokens total context.
|
||||
</section>
|
||||
|
||||
<section name="review-pr">
|
||||
Fetch PR diff -> detect_changes_tool -> get_affected_flows_tool -> structured review with blast-radius table and risk scores.
|
||||
Never include full files unless explicitly asked.
|
||||
</section>
|
||||
|
||||
<section name="commands">
|
||||
Core MCP tools: get_minimal_context_tool, detect_changes_tool, get_review_context_tool, get_impact_radius_tool, query_graph_tool, semantic_search_nodes_tool, get_architecture_overview_tool, get_affected_flows_tool, list_flows_tool, list_communities_tool, refactor_tool, build_or_update_graph_tool, run_postprocess_tool, embed_graph_tool, list_graph_stats_tool, get_docs_section_tool
|
||||
MCP prompts (5): review_changes, architecture_map, debug_issue, onboard_developer, pre_merge_check
|
||||
Skills: build-graph, debug-issue, explore-codebase, refactor-safely, review-changes, review-delta, review-pr
|
||||
CLI: code-review-graph [install|init|build|update|status|watch|visualize|serve|mcp|wiki|detect-changes|postprocess|embed|register|unregister|repos|eval|daemon]
|
||||
Token efficiency: Prefer detail_level="minimal" where available. Always call get_minimal_context_tool first. Some review/context tools return compact estimated context_savings metadata.
|
||||
</section>
|
||||
|
||||
<section name="legal">
|
||||
MIT licence. Core graph/review workflows are local and there is no telemetry. DB file: .code-review-graph/graph.db. Optional cloud embeddings send embedded source snippets to the configured provider only when selected.
|
||||
</section>
|
||||
|
||||
<section name="watch">
|
||||
Run: code-review-graph watch (auto-updates graph on file save via watchdog)
|
||||
Or use PostToolUse (Write|Edit|Bash) hooks for automatic background updates.
|
||||
</section>
|
||||
|
||||
<section name="embeddings">
|
||||
Optional: pip install code-review-graph[embeddings]
|
||||
Then call embed_graph_tool to compute vectors.
|
||||
semantic_search_nodes_tool auto-uses vectors when available, falls back to keyword + FTS5.
|
||||
Providers: local sentence-transformers, OpenAI-compatible endpoints, Google Gemini, and MiniMax.
|
||||
Configure via provider/model parameters, CRG_EMBEDDING_MODEL for local, or CRG_OPENAI_* for OpenAI-compatible endpoints.
|
||||
</section>
|
||||
|
||||
<section name="languages">
|
||||
Supported: Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks, and Perl XS files.
|
||||
Parser: Tree-sitter via tree-sitter-language-pack
|
||||
Custom languages: add .code-review-graph/languages.toml (extensions + node types per grammar) — no fork needed, see docs/CUSTOM_LANGUAGES.md. Built-ins cannot be overridden.
|
||||
</section>
|
||||
|
||||
<section name="troubleshooting">
|
||||
DB lock: SQLite WAL mode, auto-recovers. Only one build at a time.
|
||||
Large repos: First build 30-60s. Incremental <2s. Add patterns to .code-review-graphignore.
|
||||
Stale graph: Run /code-review-graph:build-graph manually.
|
||||
Missing nodes: Check language support + ignore patterns. Use full_rebuild=True.
|
||||
Windows/WSL: Use forward slashes in paths. Ensure uv is on PATH in WSL.
|
||||
</section>
|
||||
|
||||
**Instruction to AI coding agents (always follow):**
|
||||
When user asks anything about "code-review-graph", "how to use", "commands", "review-delta", etc.:
|
||||
1. Call get_docs_section_tool with the exact section name.
|
||||
2. Use ONLY that content + current graph state.
|
||||
3. Never include full docs or source code in your reasoning.
|
||||
This keeps documentation lookup compact and avoids loading broad reference files by default.
|
||||
@@ -0,0 +1,429 @@
|
||||
# Reproducing the Benchmarks
|
||||
|
||||
This document gives the exact commands to reproduce every benchmark number
|
||||
shown in the README and the `diagrams/`. Two people running the recipe below
|
||||
on different machines on different days should produce identical numbers,
|
||||
within float rounding.
|
||||
|
||||
If you get different numbers, that's a bug — please file an issue.
|
||||
|
||||
## Verifying the "saved tokens" number
|
||||
|
||||
The CLI's `Token Savings` panel uses a `chars / 4` approximation labelled
|
||||
`estimated: true`, not a model-specific tokenizer. The approximation is
|
||||
designed to be both fast (no model load, no inference) and conservative.
|
||||
|
||||
### How to verify against a real tokenizer
|
||||
|
||||
```bash
|
||||
pip install tiktoken
|
||||
code-review-graph detect-changes --brief --verify
|
||||
```
|
||||
|
||||
The panel grows a `Verified (tiktoken)` row showing the same calculation
|
||||
done with OpenAI's `cl100k_base` tokenizer (the GPT-4 family). If the
|
||||
estimate is significantly off, you'll see it immediately:
|
||||
|
||||
```text
|
||||
┌───────────────────────── Token Savings ─────────────────────────┐
|
||||
│ Full context would be: 12,921 tokens │
|
||||
│ Graph context used: 762 tokens │
|
||||
│ Saved: 12,159 tokens (~94%) │
|
||||
│ Verified (tiktoken): 10,835 tokens (~93%) [11,611 → 776] │
|
||||
│ Breakdown: Functions 244 · Tests 191 · Risk 244 · Other 83 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Calibration result (committed)
|
||||
|
||||
A one-time calibration across 222 files / 2.2 MB of mixed source
|
||||
(Python, JS, TS, Go, Rust, RST, MD) pulled from the 6 test repos:
|
||||
|
||||
| Repo | sample files | bytes | chars/4 estimate | tiktoken real | ratio est/real |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| flask | 46 | 470,179 | 117,559 | 109,969 | 1.069 |
|
||||
| fastapi | 38 | 156,224 | 39,072 | 34,897 | 1.120 |
|
||||
| gin | 30 | 471,793 | 117,962 | 132,296 | 0.892 |
|
||||
| express | 23 | 296,805 | 74,207 | 83,575 | 0.888 |
|
||||
| httpx | 38 | 254,184 | 63,556 | 62,909 | 1.010 |
|
||||
| code-review-graph | 47 | 539,206 | 134,820 | 120,760 | 1.116 |
|
||||
| **OVERALL** | **222** | **2,188,391** | **547,176** | **544,406** | **1.005** |
|
||||
|
||||
`chars / 4` is within **+0.5%** of real GPT-4 tokens in aggregate. Per-repo
|
||||
it swings between **-11%** (gin: lots of short Go identifiers) and **+12%**
|
||||
(fastapi: heavy docstrings and type hints), but the **ratio** stabilizes
|
||||
because both sides of the divide are equally biased.
|
||||
|
||||
Reproduce the calibration with the snippet in this commit's
|
||||
`code_review_graph/context_savings.py:verify_with_tiktoken`, or
|
||||
inline-run the `--verify` flag on any commit.
|
||||
|
||||
## What is and isn't deterministic
|
||||
|
||||
| Reproducible | Reason |
|
||||
|---|---|
|
||||
| Tree-sitter parsing | Pure function of input bytes |
|
||||
| Node / edge counts | Deterministic upserts keyed by `qualified_name` |
|
||||
| FTS5 BM25 scores | Deterministic |
|
||||
| Embeddings via `all-MiniLM-L6-v2` on CPU | Model weights cache-pinned by SHA in HuggingFace cache |
|
||||
| Leiden community IDs | Seeded — `_LEIDEN_SEED=42` in `communities.py`, override with `CRG_LEIDEN_SEED` env var |
|
||||
| `naive_corpus_tokens` | Deterministic for a fixed git checkout |
|
||||
| `git clone` at a pinned SHA | Determines the source-of-truth byte stream |
|
||||
|
||||
What used to make it **non**-reproducible (now fixed):
|
||||
|
||||
- `commit: HEAD` in every `code_review_graph/eval/configs/*.yaml` — replaced with the pinned latest test-commit SHA per repo
|
||||
- `git clone --depth 50` silently fell back to wrong commits when the pinned SHAs were beyond the shallow window — now uses full clones with explicit `returncode` checks
|
||||
- Leiden ran with an unseeded RNG — now seeded
|
||||
- `nextjs.yaml` was a misnamed config evaluating this repo — renamed to `code-review-graph.yaml`
|
||||
- FTS5 was created but never populated by the eval framework's `full_build` call — `code_review_graph/eval/runner.py` now calls `postprocessing.run_post_processing` directly
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10 or newer
|
||||
- `git` on PATH
|
||||
- Network access (~600 MB to clone the 6 upstream repos)
|
||||
- ~3 GB free disk
|
||||
- For the embedding step: roughly 700 MB extra for `torch` + `sentence-transformers`
|
||||
|
||||
## Step 1 — Install with the right extras
|
||||
|
||||
```bash
|
||||
git clone https://github.com/tirth8205/code-review-graph
|
||||
cd code-review-graph
|
||||
|
||||
# eval extras: pyyaml + matplotlib (matplotlib only needed for `--report`)
|
||||
# embeddings extras: sentence-transformers + numpy
|
||||
uv sync --extra eval --extra embeddings # or: pip install -e ".[eval,embeddings]"
|
||||
```
|
||||
|
||||
## Step 2 — Run the formal eval
|
||||
|
||||
This step clones 6 upstream repositories at pinned SHAs, builds a full graph
|
||||
for each (parser + cross-file resolvers + signatures + FTS5 + flows + Leiden
|
||||
communities), then runs the `token_efficiency`, `impact_accuracy`,
|
||||
`agent_baseline`, and `multi_hop_retrieval` benchmarks.
|
||||
|
||||
```bash
|
||||
uv run code-review-graph eval \
|
||||
--benchmark token_efficiency,impact_accuracy,agent_baseline,multi_hop_retrieval
|
||||
```
|
||||
|
||||
Failure semantics (applies to every benchmark): a thrown tool call is **not**
|
||||
a measurement. The row is kept in the CSV with `status=error` for forensics,
|
||||
but excluded from every aggregate. (Two historical bugs made failures look
|
||||
like wins: a thrown `get_review_context` produced `graph_tokens=0` and a
|
||||
ratio of `naive/1`, and a thrown `analyze_changes` silently set
|
||||
`predicted = changed`, guaranteeing recall 1.0. Both are fixed; regression
|
||||
tests live in `tests/test_eval.py`.)
|
||||
|
||||
Expected runtime on an M1/M2 Mac: roughly 8–15 minutes for the build phase,
|
||||
plus seconds per benchmark.
|
||||
|
||||
Outputs:
|
||||
|
||||
- `evaluate/test_repos/{express,fastapi,flask,gin,httpx,code-review-graph}/`
|
||||
- `evaluate/test_repos/<name>/.code-review-graph/graph.db`
|
||||
- `evaluate/results/<name>_<benchmark>_<date>.csv`
|
||||
|
||||
## Step 3 — Generate embeddings (required for the standalone benchmark)
|
||||
|
||||
The standalone token benchmark ships with 5 hardcoded natural-language
|
||||
questions. Without embeddings, hybrid search can't match them and the
|
||||
benchmark silently returns 0× reduction ratios (a loud warning will print).
|
||||
|
||||
```bash
|
||||
for repo in express fastapi flask gin httpx code-review-graph; do
|
||||
uv run code-review-graph embed --repo "evaluate/test_repos/$repo"
|
||||
done
|
||||
```
|
||||
|
||||
Expected runtime: 2–5 minutes total. Vectors live inside the same `graph.db`.
|
||||
|
||||
## Step 4 — Run the standalone token benchmark
|
||||
|
||||
This benchmark compares **all source-file tokens** in the repo against
|
||||
**5 search hits + a few neighbor edges** for each of 5 sample questions. The
|
||||
ratio answers: *how many tokens does the graph let me skip on a typical
|
||||
question?*
|
||||
|
||||
```bash
|
||||
uv run python <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
from code_review_graph.graph import GraphStore
|
||||
from code_review_graph.token_benchmark import run_token_benchmark
|
||||
|
||||
results = {}
|
||||
for repo in sorted(Path("evaluate/test_repos").iterdir()):
|
||||
db = repo / ".code-review-graph" / "graph.db"
|
||||
if not db.exists():
|
||||
continue
|
||||
store = GraphStore(str(db))
|
||||
try:
|
||||
results[repo.name] = run_token_benchmark(store, repo)
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
print(f"{'Repo':<22}{'naive_tokens':>16}{'avg_graph_tokens':>20}{'avg_ratio':>14}")
|
||||
print("-" * 72)
|
||||
for name, out in sorted(results.items(), key=lambda x: -x[1]["average_reduction_ratio"]):
|
||||
pq = out["per_question"]
|
||||
avg_graph = int(sum(r["graph_tokens"] for r in pq) / max(len(pq), 1))
|
||||
print(f"{name:<22}{out['naive_corpus_tokens']:>16,}"
|
||||
f"{avg_graph:>20,}{out['average_reduction_ratio']:>13.1f}×")
|
||||
|
||||
Path("evaluate/standalone_token_benchmark.json").write_text(json.dumps(results, indent=2))
|
||||
PY
|
||||
```
|
||||
|
||||
## Canonical numbers
|
||||
|
||||
<!-- BEGIN canonical-stats -->
|
||||
Captured **2026-05-25** on macOS arm64, Python 3.11, sentence-transformers 5.5.1,
|
||||
`all-MiniLM-L6-v2`, `CRG_LEIDEN_SEED=42`. If your numbers differ by more than
|
||||
rounding, something in the chain has drifted — file an issue.
|
||||
|
||||
### Standalone token benchmark (`code_review_graph/token_benchmark.py`)
|
||||
|
||||
Each row is the average of 5 sample questions (`how does authentication work`,
|
||||
`what is the main entry point`, `how are database connections managed`,
|
||||
`what error handling patterns are used`, `how do tests verify core functionality`).
|
||||
|
||||
| Repo | snapshot SHA | naive_corpus_tokens | avg graph_tokens | avg ratio |
|
||||
|---|---|---:|---:|---:|
|
||||
| fastapi | `0227991a` | 951,071 | 2,169 | **528.4×** |
|
||||
| code-review-graph | `84bde354` | 208,821 | 2,495 | **93.0×** |
|
||||
| gin | `5c00df8a` | 166,868 | 1,990 | **91.8×** |
|
||||
| flask | `a29f88ce` | 125,022 | 1,986 | **71.4×** |
|
||||
| express | `b4ab7d65` | 135,955 | 3,465 | **40.6×** |
|
||||
| httpx | `b55d4635` | 89,492 | 2,438 | **38.0×** |
|
||||
|
||||
Range across 6 repos: **38× – 528×**. The numbers shifted down from a
|
||||
previous capture because (a) the test repos are now wiped/re-cloned from
|
||||
scratch — no leftover build artifacts or local caches inflate the naive
|
||||
baseline; and (b) the embedding text per node became richer in this same
|
||||
release (see `embeddings._node_to_text`), so the graph response itself is
|
||||
slightly bigger. Both are correctness improvements over the prior numbers.
|
||||
|
||||
### Formal `token_efficiency` benchmark (`code_review_graph/eval/benchmarks/token_efficiency.py`)
|
||||
|
||||
A different denominator: just the **changed-file content** for each commit,
|
||||
vs the full `get_review_context()` JSON. For small commits the response is
|
||||
larger than the input (it carries impact-radius edges + source snippets), so
|
||||
ratios here are intentionally < 1.0 — that is not a bug, it measures a
|
||||
different thing than the standalone benchmark.
|
||||
|
||||
Raw per-commit CSVs in `evaluate/results/<repo>_token_efficiency_*.csv`.
|
||||
|
||||
### Impact accuracy (`code_review_graph/eval/benchmarks/impact_accuracy.py`)
|
||||
|
||||
13 commits across 6 repos. The benchmark emits two ground-truth modes side
|
||||
by side, distinguished by the `ground_truth_mode` CSV column:
|
||||
|
||||
| Mode | Ground truth | What it tells you |
|
||||
|---|---|---|
|
||||
| `graph-derived (circular — upper bound)` | changed files + files with CALLS/IMPORTS_FROM edges into them — **derived from the same graph the predictor traverses** | An upper bound. Recall 1.0 here is partly true by construction, not independent evidence. |
|
||||
| `co-change (same commit, seed excluded)` | the *other* files the author actually touched in the same commit, given a single seed file | Independent-ish evidence from git history. Expect substantially lower recall. |
|
||||
|
||||
The canonical numbers below were captured **in graph-derived mode only**
|
||||
(the co-change mode did not exist at capture time). Treat the recall row as
|
||||
a circular upper bound, not as "100% recall":
|
||||
|
||||
| Metric (graph-derived mode — circular upper bound) | Value |
|
||||
|---|---|
|
||||
| Recall (mean across 13 commits) | **1.000** (upper bound on every commit) |
|
||||
| F1 (mean) | **0.714** |
|
||||
| F1 (median) | 0.667 |
|
||||
| F1 (min / max) | 0.455 / 1.000 |
|
||||
|
||||
Canonical co-change numbers will be added after the next full capture — we
|
||||
do not quote them before measuring. Single-file commits are recorded with
|
||||
`status=skipped` in co-change mode (there is nothing independent to grade
|
||||
against).
|
||||
|
||||
The blast-radius analysis over-predicts in some commits (precision ≈ 0.30 in the
|
||||
worst case, where 34 files are flagged for a 10-file change). That is
|
||||
intentional: a missed dependency is worse than an extra reviewed file.
|
||||
|
||||
### Multi-hop retrieval (`code_review_graph/eval/benchmarks/multi_hop_retrieval.py`)
|
||||
|
||||
11 hand-curated tasks across the 6 repos. Each task is a 2-step tool chain:
|
||||
|
||||
1. `hybrid_search(nl_query, limit=10)` looks for a starting anchor node.
|
||||
2. `query_graph(<traversal_pattern>, target=<anchor>)` walks one hop along
|
||||
`callers_of` / `callees_of` / `tests_for` / `imports_of` / etc.
|
||||
|
||||
The task **scores 1.0** only if both the anchor is found in the top-K *and*
|
||||
the expected neighbor names are returned by the traversal. **Scores 0.0**
|
||||
otherwise (which collapses both "search missed the anchor" and "traversal
|
||||
returned the wrong set" — split those by inspecting `anchor_found` and
|
||||
`neighbor_recall` in the per-task CSV row).
|
||||
|
||||
| Repo | Task | Anchor found | Rank | Neighbor recall | Score |
|
||||
|---|---|---|---:|---:|---:|
|
||||
| code-review-graph | crg-parse-file-callers | yes | 0 | 1.00 | **1.00** |
|
||||
| code-review-graph | crg-upsert-node-callers | yes | 4 | 1.00 | **1.00** |
|
||||
| express | express-create-application-callees | yes | 1 | 1.00 | **1.00** |
|
||||
| fastapi | fastapi-route-handler-callers | yes | 6 | 1.00 | **1.00** |
|
||||
| fastapi | fastapi-get-dependant-callers | no | — | 0.00 | **0.00** |
|
||||
| flask | flask-dispatch-callers | yes | 3 | 1.00 | **1.00** |
|
||||
| flask | flask-exception-callers | yes | 5 | 1.00 | **1.00** |
|
||||
| gin | gin-serve-http-callees | yes | 5 | 1.00 | **1.00** |
|
||||
| gin | gin-context-next-callers | yes | 0 | 1.00 | **1.00** |
|
||||
| httpx | httpx-client-request-callers | yes | 0 | 1.00 | **1.00** |
|
||||
| httpx | httpx-async-request-tests | yes | 7 | 1.00 | **1.00** |
|
||||
|
||||
**Average score across 11 tasks: 0.909**. 10/11 tasks pass; the one remaining
|
||||
miss (`fastapi-get-dependant-callers`) targets a function spelled `get_dependant`
|
||||
("dependant" with an `a`) from a query phrased as "dependency declarations into
|
||||
a tree" — there is no lexical overlap and no extractable identifier in the
|
||||
query for the boosting heuristic to lock onto. Left as an honest miss; the
|
||||
fix would be either query rewriting or a richer embedding model.
|
||||
|
||||
#### How the score went from 0.545 to 0.909 (the same-day fix)
|
||||
|
||||
The v1 scaffold first scored **0.545** (6/11). Two changes brought it to
|
||||
**0.909** (10/11), both deterministic, both small, both committed in this
|
||||
same session:
|
||||
|
||||
1. **`embeddings.py:_node_to_text`** — the embedded text per node used to be
|
||||
just `"{name} {kind} in {parent}"`. It now also includes the dotted form
|
||||
(`APIRoute.get_route_handler`), the identifier split into words
|
||||
(`get route handler`), and the enclosing module directory (`routing`,
|
||||
`fastapi`, `dependencies`). All re-embeddings are automatic — the text
|
||||
hash changes, `EmbeddingStore.embed_nodes` re-embeds. See
|
||||
`_split_identifier` for the casing/separator rules.
|
||||
|
||||
2. **`search.py:extract_query_identifiers`** — natural-language queries
|
||||
like "Who advances the gin middleware chain via Context.Next" now have
|
||||
their dotted / snake_case / CamelCase identifier tokens extracted. Search
|
||||
results whose `qualified_name` contains any extracted identifier get a
|
||||
2.0× boost. This pushed `Context.Next` from rank 11 to rank 0.
|
||||
|
||||
The remaining `fastapi-get-dependant-callers` failure cannot be fixed by
|
||||
either change because the query doesn't share any identifier or substring
|
||||
with the target — that's the boundary of the heuristic.
|
||||
|
||||
This benchmark is a v1 scaffold (11 tasks). The intent is to track the
|
||||
**multi-hop tool chain** as the agent's actual usage pattern rather than just
|
||||
single-shot retrieval. Adding more tasks: append `multi_hop_tasks:` entries
|
||||
to any config under `code_review_graph/eval/configs/*.yaml` with the schema:
|
||||
|
||||
```yaml
|
||||
multi_hop_tasks:
|
||||
- id: my-task-id # required, unique
|
||||
nl_query: "natural language" # required, what an agent would ask
|
||||
anchor_qualified_suffix: # required, lowercased suffix of expected
|
||||
"rel/path.py::owner.symbol" # qualified_name (case-insensitive endswith)
|
||||
traversal_pattern: callers_of # one of callers_of|callees_of|imports_of|
|
||||
# importers_of|tests_for|inheritors_of|children_of
|
||||
expected_neighbor_names: # required, list of bare names that should
|
||||
- "expected_one" # appear in the traversal result
|
||||
k: 10 # optional, top-K depth for the search step
|
||||
```
|
||||
|
||||
### Build stats
|
||||
|
||||
| Repo | Nodes | Edges | Flows | Communities | Embeddings | FTS idx rows |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| fastapi | 6,292 | 32,081 | 165 | 85 | 5,164 | 127 |
|
||||
| express | 1,912 | 18,877 | 4 | 7 | 1,771 | 47 |
|
||||
| gin | 1,589 | 17,237 | 114 | 41 | 1,491 | 29 |
|
||||
| code-review-graph | 1,418 | 8,877 | 104 | 11 | 1,326 | 38 |
|
||||
| flask | 1,415 | 8,259 | 78 | 13 | 1,329 | 35 |
|
||||
| httpx | 1,261 | 8,228 | 128 | 5 | 1,193 | 34 |
|
||||
|
||||
Embeddings count is lower than node count because File nodes aren't
|
||||
embedded. FTS idx rows are far lower than node count because FTS5 stores
|
||||
inverted-index segments, not one row per indexed document.
|
||||
<!-- END canonical-stats -->
|
||||
|
||||
## Agent baseline benchmark (`code_review_graph/eval/benchmarks/agent_baseline.py`)
|
||||
|
||||
The whole-corpus baseline in the standalone token benchmark is an upper
|
||||
bound no real agent pays. This benchmark simulates what an agent actually
|
||||
does without the graph:
|
||||
|
||||
1. Derive search terms from each question in the config's `agent_questions:`
|
||||
list (identifier-shaped tokens via `search.extract_query_identifiers`,
|
||||
plus plain keywords; falls back to the `search_queries` query strings
|
||||
when absent).
|
||||
2. Pure-python grep over the corpus (no external `rg`/`grep` binary),
|
||||
ranking source files by total case-insensitive match count
|
||||
(deterministic; ties break on path).
|
||||
3. Read the top-3 files and token-count them (`chars/4`) as
|
||||
`baseline_tokens`.
|
||||
4. Compare against the graph-query cost for the same question (5 hybrid
|
||||
search hits + up to 5 neighbor edges per hit — the same accounting as the
|
||||
standalone benchmark).
|
||||
|
||||
Output: `evaluate/results/<repo>_agent_baseline_<date>.csv` with a
|
||||
`baseline_to_graph_ratio` per question. Rows where either side is zero are
|
||||
marked `status=no_graph_results` / `status=no_baseline_match` and excluded
|
||||
from aggregates (`agent_baseline.aggregate`). No canonical capture exists
|
||||
yet; numbers will be added to the canonical block above once captured —
|
||||
they are not quoted before being measured.
|
||||
|
||||
## Weekly CI run (report-only)
|
||||
|
||||
`.github/workflows/eval.yml` runs every Monday at 06:23 UTC (plus manual
|
||||
`workflow_dispatch`) against the two smallest pinned configs (`httpx`,
|
||||
`flask`) with the `token_efficiency`, `impact_accuracy`, and
|
||||
`agent_baseline` benchmarks. It uploads the CSVs as an artifact and writes
|
||||
a job-summary table. It is deliberately **report-only**: regressions do not
|
||||
fail the default branch yet.
|
||||
|
||||
## Which benchmark measures what
|
||||
|
||||
There are four different "token" benchmarks in the repo. They are all valid
|
||||
but measure different scenarios:
|
||||
|
||||
| Benchmark | Naive baseline | Graph cost | Question answered |
|
||||
|---|---|---|---|
|
||||
| `code_review_graph/eval/benchmarks/token_efficiency.py` | sum of **changed-file content** for a specific commit | full `get_review_context()` JSON | "Is the graph cheaper than just reading the diffed files?" |
|
||||
| `code_review_graph/eval/benchmarks/agent_baseline.py` | **grep top-3 files** for the question's identifiers | 5 search hits + 5 neighbor edges per question | "Is the graph cheaper than a realistic grep-and-read agent?" |
|
||||
| `code_review_graph/eval/token_benchmark.py` | none — absolute per-workflow cost | sum of 5 MCP-tool responses | "How many tokens does a complete agent workflow cost?" |
|
||||
| `code_review_graph/token_benchmark.py` (standalone) | sum of **all source files** in repo | 5 search hits + 5 neighbor edges per question | "Is the graph cheaper than reading the whole repo?" |
|
||||
|
||||
The `code_review_graph/eval/benchmarks/token_efficiency.py` numbers can be **less than 1.0×**
|
||||
for small commits (`get_review_context` carries impact-radius metadata and
|
||||
source snippets, which outweigh a tiny changed-file set). The standalone
|
||||
benchmark numbers are **always large** because the baseline is the entire
|
||||
repo — that is why the README leads with the median (~82×) and treats 528×
|
||||
as the max, and why `agent_baseline` exists as the realistic middle ground.
|
||||
Pick the one that matches the scenario you're talking about.
|
||||
|
||||
## Generating diagrams
|
||||
|
||||
The 9 diagrams in `diagrams/` are produced from `diagrams/generate_diagrams.py`.
|
||||
Excalidraw source files (`.excalidraw`) are gitignored (`*.excalidraw` line in
|
||||
`.gitignore`); only the rendered PNGs are tracked. Regenerate after a
|
||||
benchmark refresh:
|
||||
|
||||
```bash
|
||||
uv run python diagrams/generate_diagrams.py
|
||||
# Open each .excalidraw at https://excalidraw.com to render/export
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`git clone failed`** — Network or upstream rate-limit. The fix is a clean
|
||||
retry; the eval doesn't auto-retry by design (loud failures > silent
|
||||
fallback).
|
||||
|
||||
**`git checkout <sha> failed`** — Upstream rewrote history or removed the
|
||||
SHA. File an issue with the failing config so we can re-pin.
|
||||
|
||||
**`No embeddings found in this graph`** warning during the standalone
|
||||
benchmark — you skipped Step 3. Run it.
|
||||
|
||||
**Different community IDs between runs** — Make sure you're on the seeded
|
||||
`communities.py`. Check `grep _LEIDEN_SEED code_review_graph/communities.py`.
|
||||
You can override the seed via `CRG_LEIDEN_SEED=<int>` but all collaborators
|
||||
must agree on the same value.
|
||||
|
||||
**Different `naive_corpus_tokens` than the canonical table** — Make sure
|
||||
`git rev-parse HEAD` inside each `evaluate/test_repos/<name>` matches the
|
||||
`commit:` field in the corresponding config file. If not, delete the clone
|
||||
and let Step 2 re-clone at the pinned SHA.
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
# Roadmap
|
||||
|
||||
## Shipped
|
||||
|
||||
### v2.3.6
|
||||
- **Custom languages without forking**: `.code-review-graph/languages.toml` maps extensions and node types to any tree-sitter-language-pack grammar (`docs/CUSTOM_LANGUAGES.md`)
|
||||
- **GitHub Action** for risk-scored PR review comments: graph built/restored on the CI runner, sticky comment upserted per push, optional `fail-on-risk` merge gate; dogfooded via `.github/workflows/pr-review.yml` (`docs/GITHUB_ACTION.md`)
|
||||
- **`agent_baseline` benchmark**: graph queries vs a realistic grep-and-read-top-k agent baseline, wired into all six pinned eval configs
|
||||
- **Co-change ground truth** for `impact_accuracy`; the legacy graph-derived metric is labelled as a circular upper bound
|
||||
- **Weekly eval CI**: report-only cron run of the two smallest configs (`.github/workflows/eval.yml`)
|
||||
- **`docs/FAQ.md`**: comparisons with LSP, RAG, grep/agentic search, and adjacent tools, plus when-not-to-use guidance
|
||||
- **Contribution scaffolding**: issue forms, PR template, dependabot config
|
||||
- **Windows fixes** for `daemon status` (#511) and `detect-changes` path mapping (#528)
|
||||
- **Reliability**: embedding provider-name validation, SQLite store-leak fixes in analysis/wiki tools, `fastmcp<4` cap, hooks installed via `git rev-parse --git-path hooks`
|
||||
|
||||
### v2.3.5
|
||||
- **Token Savings panel** on `detect-changes --brief` and the new `update --brief` — boxed CLI output with per-category breakdown that sums exactly to the graph response size
|
||||
- **`--verify` flag** cross-checks the displayed savings against OpenAI's `cl100k_base` tokenizer; calibration data committed in `docs/REPRODUCING.md` shows the estimate is within ~1% of real GPT-4 tokens in aggregate
|
||||
- **`code-review-graph embed`** CLI subcommand for explicit embedding generation
|
||||
- **Deterministic eval pipeline**: pinned upstream SHAs in every config, full clones with `returncode` checks, fixed-seed Leiden community detection (`CRG_LEIDEN_SEED`)
|
||||
- **`multi_hop_retrieval` benchmark**: 11 curated 2-step tool-chain tasks; average score 0.909
|
||||
- **Richer embedding text** and **identifier-aware search boost** lift multi-hop accuracy from 0.545 to 0.909
|
||||
- **Path normalization fix** in the eval pipeline + test-gap dedup in the brief summary
|
||||
- **`docs/REPRODUCING.md`**: end-to-end recipe with canonical numbers and tiktoken calibration table
|
||||
- Demo GIF (`diagrams/context-savings-demo.gif`) showing both CLI surfaces and `--verify`
|
||||
|
||||
### v2.3.4
|
||||
- 30 MCP tools and 5 MCP prompts
|
||||
- Estimated context savings metadata for review, impact, detect-changes, and compact architecture responses
|
||||
- Compact architecture overview by default to reduce large MCP payloads
|
||||
- Bounded change-analysis controls for large diffs (`CRG_MAX_CHANGED_FUNCS`, `CRG_MAX_TRANSITIVE_FRONTIER`, `CRG_TOOL_TIMEOUT`)
|
||||
- Windows FastMCP semantic-search deadlock mitigation
|
||||
- Rust test detection and path lookup correctness fixes
|
||||
- Documentation and release metadata refreshed for the 2.3.4 release
|
||||
|
||||
### v2.3.3
|
||||
- Broad parser surface expansion across source languages, shell scripts, notebooks, and SFC-style files
|
||||
- Additional AI coding platform install targets including Gemini CLI, Qwen, Kiro, Qoder, and GitHub Copilot variants
|
||||
- Streamable HTTP MCP transport on localhost
|
||||
- Parser/resolver, Windows, FastMCP, and daemon reliability fixes
|
||||
- Community PR sweep and VS Code accessibility improvements
|
||||
|
||||
### v2.2.0
|
||||
- Multi-repo watch daemon (`crg-daemon` / `code-review-graph daemon`)
|
||||
- TOML-based daemon configuration (`~/.code-review-graph/watch.toml`)
|
||||
- Child process management: one `code-review-graph watch` process per repo
|
||||
- Config file watching with automatic reconciliation of watcher processes
|
||||
- Daemonization with PID file management
|
||||
- Health checking with automatic restart of dead watchers
|
||||
- Standalone `crg-daemon` CLI entry point (7 subcommands)
|
||||
- Integrated `daemon` subcommand group in main CLI
|
||||
|
||||
### v2.0.0
|
||||
- 22 MCP tools (up from 9) and 5 MCP prompts
|
||||
- 18 languages (added Dart, R, Perl)
|
||||
- Execution flow detection with criticality scoring
|
||||
- Community detection (Leiden algorithm via igraph, file-based fallback)
|
||||
- Architecture overview with coupling warnings
|
||||
- Risk-scored change detection (`detect_changes`)
|
||||
- Refactoring tools (rename preview, dead code, suggestions)
|
||||
- Wiki generation from community structure
|
||||
- Multi-repo registry with cross-repo search
|
||||
- FTS5 full-text search with porter stemming
|
||||
- Database migrations (v1-v5)
|
||||
- Evaluation framework with matplotlib visualization
|
||||
- TypeScript tsconfig path alias resolution
|
||||
- MiniMax embedding provider (embo-01)
|
||||
- Optional dependency groups: `[embeddings]`, `[google-embeddings]`, `[communities]`, `[eval]`, `[wiki]`, `[all]`
|
||||
- 486 tests across 22 test files
|
||||
|
||||
### v1.8.4
|
||||
- Multi-word AND search, call target resolution, impact radius pagination
|
||||
- `find_large_functions_tool`, Vue SFC and Solidity support
|
||||
- Documentation overhaul
|
||||
|
||||
### v1.7.0
|
||||
- `install` command as primary entry point (`init` kept as alias)
|
||||
- `--dry-run` flag for previewing install/init changes
|
||||
- Automatic PyPI publishing via GitHub Actions on release
|
||||
- README rewrite with real benchmark data from httpx, FastAPI, and Next.js
|
||||
|
||||
### v1.6.x
|
||||
- Portable `uvx`-based MCP config
|
||||
- SessionStart hook for automatic graph tool preference
|
||||
- 24 audit fixes: C/C++ support, performance, CI hardening
|
||||
|
||||
### v1.5.x
|
||||
- Generated files in `.code-review-graph/` directory
|
||||
- Visualization density: collapsed start, search, edge toggles
|
||||
- Works without git
|
||||
|
||||
### v1.4.0
|
||||
- `init` command, interactive D3.js visualization, `serve` command
|
||||
|
||||
### v1.3.0
|
||||
- Universal pip install, CLI entry point, Python version check
|
||||
|
||||
### v1.1.0-v1.2.0
|
||||
- Watch mode, vector embeddings, logging, CI coverage
|
||||
|
||||
### v1.0.0 (Foundation)
|
||||
- Persistent SQLite knowledge graph, Tree-sitter parsing, incremental updates
|
||||
- Impact radius analysis, 6 MCP tools, 3 skills
|
||||
|
||||
## Planned
|
||||
|
||||
- GitHub App / bot mode beyond the shipped GitHub Action (org-wide install, check runs)
|
||||
- Team sync (shared graph via git-tracked DB)
|
||||
- Performance optimization for monorepos (>50k files)
|
||||
|
||||
## Ongoing
|
||||
|
||||
- Additional language grammars as requested
|
||||
- Integration updates as AI coding platforms evolve
|
||||
@@ -0,0 +1,190 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Quick reference for common install/setup problems
|
||||
|
||||
Four issues account for most support questions. Check these first:
|
||||
|
||||
### 1. `Hooks use a matcher + hooks array` error in `.claude/settings.json`
|
||||
|
||||
**You're on a pre-v2.2.3 release.** v2.2.1 and v2.2.2 shipped a broken hook schema — flat `{matcher, command, timeout}` entries without the required nested `hooks: []` array, timeouts in milliseconds instead of seconds, and a `PreCommit` event that isn't a real Claude Code event. PR #208 (shipped in v2.2.3) rewrote the generator to emit the correct v1.x+ schema.
|
||||
|
||||
**Fix:**
|
||||
|
||||
```bash
|
||||
pip install --upgrade code-review-graph # → v2.2.4 or later
|
||||
cd /path/to/your/project
|
||||
code-review-graph install # rewrites .claude/settings.json
|
||||
```
|
||||
|
||||
The re-install merge-replaces the entire broken `hooks` block with the new nested format and drops a real git pre-commit hook into the hooks directory resolved via `git rev-parse --git-path hooks` — typically `.git/hooks/pre-commit`, but linked worktrees and `core.hooksPath` (husky) setups are handled too. That's where "check before commit" lives in v2.2.3+, not in Claude Code settings.
|
||||
|
||||
Valid Claude Code hook events are: `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`, `SubagentStop`, `SessionStart`, `SessionEnd`, `PreCompact`, `Notification`. There is no `PreCommit`.
|
||||
|
||||
### 2. `code-review-graph: command not found` after `pip install`
|
||||
|
||||
`pip install` put the console script into a `bin/` directory that isn't on your `$PATH`. Four fixes, in order of recommendation:
|
||||
|
||||
**Option 1 — Use `pipx` (cleanest):**
|
||||
|
||||
```bash
|
||||
pip uninstall code-review-graph
|
||||
pipx install code-review-graph
|
||||
```
|
||||
|
||||
`pipx` installs CLI tools in an isolated venv. If the command is not found afterwards, run `pipx ensurepath` or add `~/.local/bin` to your PATH.
|
||||
|
||||
**Option 2 — Use `uvx` (no install needed):**
|
||||
|
||||
```bash
|
||||
uvx code-review-graph install
|
||||
uvx code-review-graph build
|
||||
```
|
||||
|
||||
**Option 3 — Run it as a Python module (always works):**
|
||||
|
||||
```bash
|
||||
python -m code_review_graph install
|
||||
python -m code_review_graph build
|
||||
```
|
||||
|
||||
**Option 4 — Fix PATH manually:**
|
||||
|
||||
```bash
|
||||
pip show code-review-graph | grep Location
|
||||
# Find the sibling `bin/` directory; on macOS user installs this is
|
||||
# typically ~/Library/Python/3.X/bin. Add it to your shell rc:
|
||||
echo 'export PATH="$HOME/Library/Python/3.12/bin:$PATH"' >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
### 3. Is code-review-graph project-scoped or user-scoped?
|
||||
|
||||
**Both** — four different pieces, each scoped differently:
|
||||
|
||||
| Piece | Scope | Where |
|
||||
|-------------------------------|----------------|------------------------------------------------------------------|
|
||||
| The Python package | User-scoped | Install once via `pip`/`pipx`/`uvx` |
|
||||
| The graph database | Project-scoped | `.code-review-graph/graph.db` inside each project |
|
||||
| MCP server config (`.mcp.json`) | Project-scoped | Claude Code launches one MCP server per project, with `cwd=<project>` |
|
||||
| Multi-repo registry | User-scoped | `~/.code-review-graph/registry.json` (only for `cross_repo_search`) |
|
||||
|
||||
**TL;DR**: install the tool **once**, then run `code-review-graph install && code-review-graph build` inside **each** project you want graph-aware reviews in.
|
||||
|
||||
### 4. Using a venv? You must update `settings.json` manually
|
||||
|
||||
Claude Code hooks and MCP tool paths in `.claude/settings.json` are **hardcoded at install time**. If you switch to (or create) a virtual environment after running `code-review-graph install`, the paths will still point to the old interpreter and the server will silently fail or use the wrong Python.
|
||||
|
||||
**Fix — update the `command`/`args` in `.mcp.json` and any hook commands in `.claude/settings.json` to match your venv:**
|
||||
|
||||
```json
|
||||
// .mcp.json — point to your venv's Python or uvx inside the venv
|
||||
{
|
||||
"mcpServers": {
|
||||
"code-review-graph": {
|
||||
"command": "/path/to/your/venv/bin/uvx",
|
||||
"args": ["code-review-graph", "serve"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or simply re-run `code-review-graph install` **from within the activated venv** so the paths are regenerated correctly:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate # activate your venv first
|
||||
code-review-graph install # rewrites .mcp.json and hook paths
|
||||
```
|
||||
|
||||
Then fully quit and reopen Claude Code so it picks up the new config.
|
||||
|
||||
### 5. "I built the graph but Claude Code doesn't see it in a new session"
|
||||
|
||||
Most likely causes, ranked:
|
||||
|
||||
1. **You didn't restart Claude Code after `install`.** Claude Code reads `.mcp.json` at startup — if you ran `install` in one session, fully quit and reopen Claude Code for the MCP server to register.
|
||||
2. **New session's `cwd` is a different directory.** The MCP server is launched with `cwd=<project>` and it reads `.code-review-graph/graph.db` from there. If your new session opened in a parent folder or a different project, it won't find the graph you built.
|
||||
3. **You ran `build` but not `install`.** `build` creates `graph.db`; `install` is what registers the MCP server with Claude Code via `.mcp.json`. You need both.
|
||||
4. **MCP server is crashing on startup.** Run `/mcp` inside Claude Code to see server status, or check `~/Library/Logs/Claude/mcp*.log` on macOS.
|
||||
|
||||
**Quick checklist:**
|
||||
|
||||
```bash
|
||||
cd /path/to/your/project
|
||||
code-review-graph status # should print Files/Nodes/Edges from the built graph
|
||||
ls .mcp.json # should exist
|
||||
cat .mcp.json # should reference `code-review-graph serve`
|
||||
# then: fully quit Claude Code and reopen it inside this project
|
||||
```
|
||||
|
||||
If `status` shows the graph but `/mcp` in the new session doesn't list `code-review-graph`, the `.mcp.json` isn't in the session's `cwd` — re-run `code-review-graph install` from the correct project root.
|
||||
|
||||
---
|
||||
|
||||
## Database lock errors
|
||||
The graph uses SQLite with WAL mode. If you see lock errors:
|
||||
- Ensure only one build process runs at a time
|
||||
- The database auto-recovers; just retry
|
||||
- Delete `.code-review-graph/graph.db-wal` and `.code-review-graph/graph.db-shm` if corrupt
|
||||
|
||||
## Large repositories (>10k files)
|
||||
- First build may take 30-60 seconds
|
||||
- Subsequent incremental updates are fast (<2s)
|
||||
- Add more ignore patterns to `.code-review-graphignore`:
|
||||
```
|
||||
generated/**
|
||||
vendor/**
|
||||
*.min.js
|
||||
```
|
||||
|
||||
## Missing nodes after build
|
||||
- Check that the file's language is supported (see [FEATURES.md](FEATURES.md))
|
||||
- Check that the file isn't matched by an ignore pattern
|
||||
- Run with `full_rebuild=True` to force a complete re-parse
|
||||
|
||||
## Graph seems stale
|
||||
- Hooks auto-update on edit/commit
|
||||
- If stale, run `/code-review-graph:build-graph` manually
|
||||
- Check that hooks are configured in `.claude/settings.json` (re-run `code-review-graph install` to regenerate)
|
||||
|
||||
## Embeddings not working
|
||||
- Install with: `pip install code-review-graph[embeddings]`
|
||||
- Run `embed_graph_tool` to compute vectors
|
||||
- First embedding run downloads the model (~90MB, one time)
|
||||
|
||||
## MCP server won't start
|
||||
- Verify `uv` is installed (`uv --version`; install with `pip install uv` or `brew install uv`)
|
||||
- Check that `uvx code-review-graph serve` runs without errors
|
||||
- If using a custom `.mcp.json`, ensure it uses `"command": "uvx"` with `"args": ["code-review-graph", "serve"]`
|
||||
- Re-run `code-review-graph install` to regenerate the config
|
||||
|
||||
## Windows / WSL
|
||||
|
||||
- Upgrade to v2.3.6+ if `daemon status` crashes with WinError 87 (#511) or CLI `detect-changes` maps 0 functions on Windows (#528) — both are fixed there
|
||||
- Use forward slashes in paths when passing `repo_root` to MCP tools
|
||||
- In WSL, ensure `uv` is installed inside WSL (not the Windows version): `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
||||
- If `uv` is not found after install, add `~/.cargo/bin` to your PATH
|
||||
- File watching (`code-review-graph watch`) may have delays on WSL1 due to filesystem event limitations; WSL2 is recommended
|
||||
- On Windows native (non-WSL), long path support may need to be enabled: `git config --system core.longpaths true`
|
||||
|
||||
## Community detection requires igraph
|
||||
|
||||
- Install with: `pip install code-review-graph[communities]`
|
||||
- Without igraph, community detection falls back to file-based grouping (less precise but functional)
|
||||
|
||||
## Wiki generation with LLM summaries
|
||||
|
||||
- Install with: `pip install code-review-graph[wiki]`
|
||||
- Requires a running Ollama instance for LLM-powered summaries
|
||||
- Without Ollama, wiki pages are generated with structural information only (no prose summaries)
|
||||
|
||||
## Optional dependency groups
|
||||
|
||||
If a tool returns an ImportError, install the relevant optional group:
|
||||
- `pip install code-review-graph[embeddings]` for semantic search
|
||||
- `pip install code-review-graph[google-embeddings]` for Google Gemini embeddings
|
||||
- OpenAI-compatible and MiniMax embeddings use stdlib HTTP clients and require only their environment variables
|
||||
- `pip install code-review-graph[communities]` for igraph-based community detection
|
||||
- `pip install code-review-graph[enrichment]` for Python call-resolution enrichment via Jedi
|
||||
- `pip install code-review-graph[eval]` for evaluation benchmarks (matplotlib)
|
||||
- `pip install code-review-graph[wiki]` for wiki LLM summaries (ollama)
|
||||
- `pip install code-review-graph[all]` for everything
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
# Code Review Graph — User Guide
|
||||
|
||||
**Applies to:** v2.3.6
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install code-review-graph
|
||||
code-review-graph install # auto-detects and configures all supported platforms
|
||||
code-review-graph build # parse your codebase
|
||||
```
|
||||
|
||||
`install` detects which AI coding tools you have, writes the correct MCP configuration for each one, and installs platform-native hooks where supported. Restart your editor/tool after installing.
|
||||
|
||||
To target a specific platform instead of auto-detecting all:
|
||||
|
||||
```bash
|
||||
code-review-graph install --platform codex
|
||||
code-review-graph install --platform cursor
|
||||
code-review-graph install --platform claude-code
|
||||
```
|
||||
|
||||
### Supported Platforms
|
||||
|
||||
| Platform | Config file |
|
||||
|----------|-------------|
|
||||
| **Codex** | `~/.codex/config.toml` + `~/.codex/hooks.json` |
|
||||
| **Claude Code** | `.mcp.json` + `.claude/settings.json` |
|
||||
| **Cursor** | `.cursor/mcp.json` |
|
||||
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` |
|
||||
| **Zed** | `.zed/settings.json` |
|
||||
| **Continue** | `.continue/config.json` |
|
||||
| **OpenCode** | `.opencode.json` |
|
||||
| **Antigravity** | `~/.gemini/antigravity/mcp_config.json` |
|
||||
| **Gemini CLI** | `.gemini/settings.json` |
|
||||
| **Qwen Code** | `~/.qwen/settings.json` |
|
||||
| **Kiro** | `.kiro/settings/mcp.json` |
|
||||
| **Qoder** | `.qoder/mcp.json` |
|
||||
| **GitHub Copilot** | `.vscode/mcp.json` |
|
||||
| **GitHub Copilot CLI** | `~/.copilot/mcp-config.json` |
|
||||
|
||||
## Core Workflow
|
||||
|
||||
### 1. Build the graph (first time only)
|
||||
```
|
||||
/code-review-graph:build-graph
|
||||
```
|
||||
Parses your entire codebase. Takes ~10s for 500 files.
|
||||
|
||||
### 2. Review changes (daily use)
|
||||
```
|
||||
/code-review-graph:review-delta
|
||||
```
|
||||
Reviews only files changed since last commit plus the graph-derived impact radius. Relevant review and impact responses include compact estimated `context_savings` metadata. Across the 6 benchmark repositories, graph queries use ~82x fewer tokens per question (median; range 38x–528x) than reading the whole corpus — see the [README benchmarks](../README.md#benchmarks) and [REPRODUCING.md](REPRODUCING.md) for the methodology.
|
||||
|
||||
### 3. Review a PR
|
||||
```
|
||||
/code-review-graph:review-pr
|
||||
```
|
||||
Comprehensive structural review of a branch diff with blast-radius analysis.
|
||||
|
||||
### 4. Watch mode (optional)
|
||||
```bash
|
||||
code-review-graph watch
|
||||
```
|
||||
Auto-updates the graph on every file save. Zero manual work.
|
||||
|
||||
### 5. Visualize the graph (optional)
|
||||
```bash
|
||||
code-review-graph visualize
|
||||
open .code-review-graph/graph.html
|
||||
```
|
||||
Interactive D3.js force-directed graph. Starts collapsed (File nodes only) — click a file to expand its children. Use the search bar to filter, and click legend edge types to toggle visibility.
|
||||
|
||||
### 6. Semantic search (optional)
|
||||
```bash
|
||||
pip install "code-review-graph[embeddings]"
|
||||
```
|
||||
Then use `embed_graph_tool` to compute vectors. `semantic_search_nodes_tool` automatically uses vector similarity when matching embeddings are available and falls back to keyword/FTS search otherwise.
|
||||
|
||||
Embedding providers are local sentence-transformers, OpenAI-compatible endpoints, Google Gemini, and MiniMax. Local embeddings use `CRG_EMBEDDING_MODEL`; OpenAI-compatible providers use `CRG_OPENAI_BASE_URL`, `CRG_OPENAI_API_KEY`, and `CRG_OPENAI_MODEL`. Cloud providers are opt-in and print an egress warning unless `CRG_ACCEPT_CLOUD_EMBEDDINGS=1` is set.
|
||||
|
||||
### 7. Detect changes with risk scoring (v2)
|
||||
```
|
||||
Ask your MCP client: "Review my recent changes with risk scoring"
|
||||
```
|
||||
Uses `detect_changes_tool` to map diffs to affected functions, flows, communities, and test gaps.
|
||||
|
||||
### 8. Explore architecture (v2)
|
||||
```
|
||||
Ask your MCP client: "Show me the architecture of this project"
|
||||
```
|
||||
Uses `get_architecture_overview_tool` for community-based architecture map with coupling warnings.
|
||||
|
||||
### 9. Generate wiki (v2)
|
||||
```bash
|
||||
code-review-graph wiki
|
||||
```
|
||||
Creates markdown wiki pages for each detected community in `.code-review-graph/wiki/`.
|
||||
|
||||
### 10. Multi-repo search (v2)
|
||||
```bash
|
||||
code-review-graph register /path/to/other/repo --alias mylib
|
||||
```
|
||||
Then use `cross_repo_search_tool` to search across all registered repositories.
|
||||
|
||||
## Context Savings
|
||||
|
||||
CRG reduces review context by sending graph-derived structural context instead of broad file dumps. The exact reduction depends on the repository and change shape. The evaluation runner reports the current benchmark data used in the README:
|
||||
|
||||
```bash
|
||||
code-review-graph eval --all
|
||||
```
|
||||
|
||||
Since v2.3.4, review and impact tools include compact `context_savings` metadata. In v2.3.5 the CLI surfaces this as a boxed `Token Savings` panel on both `detect-changes --brief` and `update --brief`, with a per-category breakdown (Functions / Tests / Risk / Other) that sums exactly to the graph response size. Add `--verify` to cross-check the displayed numbers against OpenAI's `cl100k_base` tokenizer (requires `pip install tiktoken`). All numbers are labelled estimated because they use a conservative approximation rather than model-specific tokenisation; calibration shows the estimate stays within ~1% of real GPT-4 tokens in aggregate. Small single-file changes can occasionally use more context than the raw file because graph metadata has overhead.
|
||||
|
||||
## Supported Languages
|
||||
|
||||
The parser currently covers Python, JavaScript, TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Vue/Svelte single-file components, Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks (`.ipynb`), and Perl XS files (`.xs`).
|
||||
|
||||
Extension-less scripts are detected by shebang for common bash/sh/zsh/ksh/dash/ash, Python, Node, Ruby, Perl, Lua, Rscript, and PHP interpreters.
|
||||
|
||||
Languages not covered yet can be added without a fork via a `.code-review-graph/languages.toml` config — see [CUSTOM_LANGUAGES.md](CUSTOM_LANGUAGES.md).
|
||||
|
||||
## What Gets Indexed
|
||||
|
||||
- **Nodes**: Files, Classes, Functions/Methods, Types, Tests
|
||||
- **Edges**: CALLS, IMPORTS_FROM, INHERITS, IMPLEMENTS, CONTAINS, TESTED_BY, DEPENDS_ON
|
||||
|
||||
See [schema.md](schema.md) for full details.
|
||||
|
||||
## Ignore Patterns
|
||||
|
||||
By default, these paths are excluded from indexing:
|
||||
|
||||
```
|
||||
.code-review-graph/** node_modules/** .git/**
|
||||
__pycache__/** *.pyc .venv/**
|
||||
venv/** dist/** build/**
|
||||
.next/** target/** *.min.js
|
||||
*.min.css *.map *.lock
|
||||
package-lock.json yarn.lock *.db
|
||||
*.sqlite *.db-journal
|
||||
```
|
||||
|
||||
To add custom patterns, create a `.code-review-graphignore` file in your repo root (same syntax as `.gitignore`):
|
||||
|
||||
```
|
||||
generated/**
|
||||
vendor/**
|
||||
*.generated.ts
|
||||
```
|
||||
|
||||
In git repos, indexing is based on tracked files (`git ls-files`), so gitignored files are skipped automatically. Use `.code-review-graphignore` to exclude tracked files or when git isn't available.
|
||||
@@ -0,0 +1,120 @@
|
||||
# Architecture
|
||||
|
||||
## System Overview
|
||||
|
||||
`code-review-graph` is a local-first code intelligence graph exposed through a CLI and MCP server. It maintains a persistent, incrementally updated knowledge graph of a codebase so AI coding tools can review changes with structural context instead of reading broad file dumps. Claude Code is supported, but it is one client among several.
|
||||
|
||||
## Component Diagram
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ AI coding clients / CLI │
|
||||
│ │
|
||||
│ MCP clients Hooks / watch mode │
|
||||
│ ├── Codex └── incremental update │
|
||||
│ ├── Claude Code │
|
||||
│ ├── Cursor, Windsurf, Zed, Continue │
|
||||
│ └── Gemini CLI, Qwen, Qoder, Copilot, OpenCode │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────────────────────────────────────┐ │
|
||||
│ │ MCP Server (stdio or localhost HTTP) │ │
|
||||
│ │ │ │
|
||||
│ │ 30 MCP Tools + 5 MCP Prompts │ │
|
||||
│ │ ├── Core: build, impact, query, review, │ │
|
||||
│ │ │ search, traverse, embed, stats, docs │ │
|
||||
│ │ ├── Flows: list, get, affected │ │
|
||||
│ │ ├── Communities: list, get, architecture │ │
|
||||
│ │ ├── Analysis: detect_changes, refactor, │ │
|
||||
│ │ │ apply_refactor, hotspots, gaps │ │
|
||||
│ │ ├── Wiki: generate, get_page │ │
|
||||
│ │ └── Multi-repo: list_repos, cross_search │ │
|
||||
│ └────────────────┬───────────────────────────┘ │
|
||||
└───────────────────┼──────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────────┐
|
||||
│ Parser │ │ Graph │ │ Incremental │
|
||||
│ │ │ Store │ │ Engine │
|
||||
└────┬────┘ └────┬────┘ └──────┬──────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Tree-sitter SQLite DB git/svn diff
|
||||
grammars (.code-review- subprocess
|
||||
graph/
|
||||
graph.db)
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Full Build
|
||||
1. `collect_all_files()` gathers tracked files (`git ls-files`) and applies `.code-review-graphignore` (gitignored files are skipped automatically when git is available)
|
||||
2. For each file, `CodeParser.parse_file()` uses Tree-sitter to extract AST
|
||||
3. AST walker identifies structural nodes (classes, functions, imports) and edges (calls, inheritance)
|
||||
4. `GraphStore.store_file_nodes_edges()` persists to SQLite with file hash for change detection
|
||||
5. Metadata updated with timestamp
|
||||
|
||||
### Incremental Update
|
||||
1. `get_changed_files()` uses VCS metadata to identify changed files (git diff by default, with SVN support in the incremental layer)
|
||||
2. `find_dependents()` queries the graph for files importing the changed files
|
||||
3. Changed + dependent files are re-parsed (others skipped via hash comparison)
|
||||
4. Only affected rows in SQLite are updated
|
||||
|
||||
### Review Context Generation
|
||||
1. Changed files identified (git diff or explicit list)
|
||||
2. `get_impact_radius()` performs BFS from changed nodes through the graph
|
||||
3. Source snippets extracted for changed areas only
|
||||
4. Review guidance generated (test coverage gaps, wide blast radius warnings)
|
||||
5. Assembled into a structured, token-efficient context for MCP clients and the CLI
|
||||
6. Where a cheap baseline can be estimated, compact `context_savings` metadata is attached as an estimate rather than an exact tokenisation
|
||||
|
||||
## Storage
|
||||
|
||||
### SQLite Schema
|
||||
- **nodes** table: id, kind, name, qualified_name, file_path, line_start/end, language, community_id, etc.
|
||||
- **edges** table: id, kind, source_qualified, target_qualified, file_path, line
|
||||
- **metadata** table: key-value pairs (last_updated, build_type, schema_version)
|
||||
- **flows** table: id, name, entry_point_id, depth, node_count, file_count, criticality, path_json
|
||||
- **flow_memberships** table: flow_id, node_id, position
|
||||
- **communities** table: id, name, level, parent_id, cohesion, size, dominant_language, description
|
||||
- **nodes_fts** (FTS5 virtual table): full-text search on name, qualified_name, file_path, signature
|
||||
- **community_summaries**, **flow_snapshots**, **risk_index** tables: compact precomputed summaries for token-efficient queries
|
||||
- **embeddings** table (separate DB): qualified_name, vector, text_hash, provider
|
||||
|
||||
Indexes on qualified_name, file_path, edge source/target, criticality, community_id, and cohesion for fast lookups.
|
||||
|
||||
WAL mode enabled for concurrent read access during updates.
|
||||
|
||||
### Qualified Names
|
||||
Nodes are uniquely identified by qualified names:
|
||||
- Files: absolute path (e.g., `/repo/src/auth.py`)
|
||||
- Functions: `file_path::function_name` (e.g., `/repo/src/auth.py::authenticate`)
|
||||
- Methods: `file_path::ClassName.method_name` (e.g., `/repo/src/auth.py::AuthService.login`)
|
||||
|
||||
## Parsing Strategy
|
||||
|
||||
Tree-sitter provides language-agnostic AST access. The parser:
|
||||
1. Walks the AST recursively
|
||||
2. Pattern-matches on node types (language-specific mappings in `_CLASS_TYPES`, `_FUNCTION_TYPES`, etc.)
|
||||
3. Extracts names, parameters, return types, base classes
|
||||
4. Identifies calls within function bodies
|
||||
5. Resolves imports to module paths
|
||||
|
||||
This approach is more robust than tree-sitter queries across grammar versions.
|
||||
|
||||
## Visualization
|
||||
|
||||
The `visualization.py` module generates an interactive D3.js force-directed graph as a self-contained HTML file. It reads all nodes and edges from the SQLite graph store and renders them in the browser, allowing developers to visually explore code relationships, filter by node kind, and inspect dependencies.
|
||||
|
||||
## Impact Analysis Algorithm
|
||||
|
||||
BFS from seed nodes (changed files' contents):
|
||||
1. Seed = all qualified names in changed files
|
||||
2. For each node in frontier:
|
||||
- Follow forward edges (what this node affects)
|
||||
- Follow reverse edges (what depends on this node)
|
||||
3. Expand up to `max_depth` hops (default: 2)
|
||||
4. Collect all reached nodes as "impacted"
|
||||
|
||||
This captures both downstream effects (things that call changed code) and upstream context (things that the changed code depends on).
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
# Knowledge Graph Schema
|
||||
|
||||
## Node Types
|
||||
|
||||
### File
|
||||
Represents a source code file.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| name | string | Absolute file path |
|
||||
| file_path | string | Same as name for File nodes |
|
||||
| language | string | Detected language (python, typescript, go, etc.) |
|
||||
| line_start | int | Always 1 |
|
||||
| line_end | int | Total line count |
|
||||
| file_hash | string | SHA-256 of file contents (for change detection) |
|
||||
|
||||
### Class
|
||||
Represents a class, struct, interface, enum, or module definition.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| name | string | Class name |
|
||||
| file_path | string | File containing the class |
|
||||
| line_start | int | Definition start line |
|
||||
| line_end | int | Definition end line |
|
||||
| language | string | Source language |
|
||||
| parent_name | string? | Enclosing class (for nested classes) |
|
||||
| modifiers | string? | Access modifiers (public, abstract, etc.) |
|
||||
|
||||
### Function
|
||||
Represents a function, method, or constructor definition.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| name | string | Function name |
|
||||
| file_path | string | File containing the function |
|
||||
| line_start | int | Definition start line |
|
||||
| line_end | int | Definition end line |
|
||||
| language | string | Source language |
|
||||
| parent_name | string? | Enclosing class (for methods) |
|
||||
| params | string? | Parameter list as source text |
|
||||
| return_type | string? | Return type annotation |
|
||||
| is_test | bool | Whether this is a test function |
|
||||
|
||||
### Test
|
||||
Same schema as Function, but `kind = "Test"` and `is_test = true`. Identified by:
|
||||
- Name starts with `test_` or `Test`
|
||||
- Name ends with `_test` or `_spec`
|
||||
- File matches test file patterns (`test_*.py`, `*.test.ts`, `*_test.go`, etc.)
|
||||
- Language-specific test markers where supported, such as common Rust test attributes
|
||||
|
||||
### Type
|
||||
Represents a type alias, interface, enum, struct-like type, or parser-specific type construct where the language exposes one.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| name | string | Type name |
|
||||
| file_path | string | File containing the type |
|
||||
| line_start | int | Definition start line |
|
||||
| line_end | int | Definition end line |
|
||||
|
||||
## Edge Types
|
||||
|
||||
### CALLS
|
||||
A function calls another function.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| source | string | Qualified name of the caller |
|
||||
| target | string | Name of the called function (may be unqualified) |
|
||||
| file_path | string | File where the call occurs |
|
||||
| line | int | Line number of the call |
|
||||
|
||||
### IMPORTS_FROM
|
||||
A file imports from another module or file.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| source | string | Importing file path |
|
||||
| target | string | Imported module/path |
|
||||
| file_path | string | Same as source |
|
||||
| line | int | Line number of the import |
|
||||
|
||||
### INHERITS
|
||||
A class extends/inherits from another class.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| source | string | Child class qualified name |
|
||||
| target | string | Parent class name |
|
||||
| file_path | string | File containing the child class |
|
||||
|
||||
### IMPLEMENTS
|
||||
A class implements an interface (Java, C#, TypeScript, Go).
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| source | string | Implementing class |
|
||||
| target | string | Interface name |
|
||||
|
||||
### CONTAINS
|
||||
Structural containment: a file contains a class, a class contains a method.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| source | string | Container (file path or class qualified name) |
|
||||
| target | string | Contained node qualified name |
|
||||
|
||||
### TESTED_BY
|
||||
A function is tested by a test function.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| source | string | Function being tested |
|
||||
| target | string | Test function qualified name |
|
||||
|
||||
### DEPENDS_ON
|
||||
General dependency relationship (used for non-specific dependencies).
|
||||
|
||||
### REFERENCES
|
||||
A value-level reference to another symbol, often used for function-as-value patterns such as callback maps, arrays, or assignment.
|
||||
|
||||
### INJECTS
|
||||
A dependency-injection relationship, currently used by Java/Spring enrichment for injected fields and constructor parameters.
|
||||
|
||||
### CONSUMES / PRODUCES
|
||||
Data or event flow relationships emitted by specialised parsers when a source consumes or produces a named resource.
|
||||
|
||||
### TEMPORAL_STUB
|
||||
Temporal dependency placeholder emitted by specialised parsers when a time/order relationship is detected but cannot be resolved to a stronger edge type.
|
||||
|
||||
## Qualified Name Format
|
||||
|
||||
Nodes are uniquely identified by qualified names:
|
||||
|
||||
```
|
||||
# File node
|
||||
/absolute/path/to/file.py
|
||||
|
||||
# Top-level function
|
||||
/absolute/path/to/file.py::function_name
|
||||
|
||||
# Method in a class
|
||||
/absolute/path/to/file.py::ClassName.method_name
|
||||
|
||||
# Nested class method
|
||||
/absolute/path/to/file.py::OuterClass.InnerClass.method_name
|
||||
```
|
||||
|
||||
## SQLite Tables
|
||||
|
||||
```sql
|
||||
-- Nodes table
|
||||
CREATE TABLE nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
kind TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
qualified_name TEXT NOT NULL UNIQUE,
|
||||
file_path TEXT NOT NULL,
|
||||
line_start INTEGER,
|
||||
line_end INTEGER,
|
||||
language TEXT,
|
||||
parent_name TEXT,
|
||||
params TEXT,
|
||||
return_type TEXT,
|
||||
modifiers TEXT,
|
||||
is_test INTEGER DEFAULT 0,
|
||||
file_hash TEXT,
|
||||
extra TEXT DEFAULT '{}',
|
||||
community_id INTEGER,
|
||||
updated_at REAL NOT NULL
|
||||
);
|
||||
|
||||
-- Edges table
|
||||
CREATE TABLE edges (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
kind TEXT NOT NULL,
|
||||
source_qualified TEXT NOT NULL,
|
||||
target_qualified TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
line INTEGER DEFAULT 0,
|
||||
extra TEXT DEFAULT '{}',
|
||||
confidence REAL DEFAULT 1.0,
|
||||
confidence_tier TEXT DEFAULT 'EXTRACTED',
|
||||
updated_at REAL NOT NULL
|
||||
);
|
||||
|
||||
-- Metadata table
|
||||
CREATE TABLE metadata (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Flows table (v2.0)
|
||||
CREATE TABLE flows (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
entry_point_id INTEGER NOT NULL,
|
||||
depth INTEGER NOT NULL,
|
||||
node_count INTEGER NOT NULL,
|
||||
file_count INTEGER NOT NULL,
|
||||
criticality REAL NOT NULL DEFAULT 0.0,
|
||||
path_json TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- Flow memberships table (v2.0)
|
||||
CREATE TABLE flow_memberships (
|
||||
flow_id INTEGER NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
position INTEGER NOT NULL,
|
||||
PRIMARY KEY (flow_id, node_id)
|
||||
);
|
||||
|
||||
-- Communities table (v2.0)
|
||||
CREATE TABLE communities (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
level INTEGER NOT NULL DEFAULT 0,
|
||||
parent_id INTEGER,
|
||||
cohesion REAL NOT NULL DEFAULT 0.0,
|
||||
size INTEGER NOT NULL DEFAULT 0,
|
||||
dominant_language TEXT,
|
||||
description TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- Full-text search virtual table (v2.0)
|
||||
CREATE VIRTUAL TABLE nodes_fts USING fts5(
|
||||
name, qualified_name, file_path, signature,
|
||||
content='nodes', content_rowid='rowid',
|
||||
tokenize='porter unicode61'
|
||||
);
|
||||
|
||||
-- Token-efficient summary tables (v6)
|
||||
CREATE TABLE community_summaries (
|
||||
community_id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
purpose TEXT DEFAULT '',
|
||||
key_symbols TEXT DEFAULT '[]',
|
||||
risk TEXT DEFAULT 'unknown',
|
||||
size INTEGER DEFAULT 0,
|
||||
dominant_language TEXT DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE flow_snapshots (
|
||||
flow_id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
entry_point TEXT NOT NULL,
|
||||
critical_path TEXT DEFAULT '[]',
|
||||
criticality REAL DEFAULT 0.0,
|
||||
node_count INTEGER DEFAULT 0,
|
||||
file_count INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE risk_index (
|
||||
node_id INTEGER PRIMARY KEY,
|
||||
qualified_name TEXT NOT NULL,
|
||||
risk_score REAL DEFAULT 0.0,
|
||||
caller_count INTEGER DEFAULT 0,
|
||||
test_coverage TEXT DEFAULT 'unknown',
|
||||
security_relevant INTEGER DEFAULT 0,
|
||||
last_computed TEXT DEFAULT ''
|
||||
);
|
||||
|
||||
-- Embeddings table, stored in the embeddings database
|
||||
CREATE TABLE embeddings (
|
||||
qualified_name TEXT PRIMARY KEY,
|
||||
vector BLOB NOT NULL,
|
||||
text_hash TEXT NOT NULL,
|
||||
provider TEXT NOT NULL DEFAULT 'unknown'
|
||||
);
|
||||
```
|
||||
|
||||
Indexes include qualified-name, file-path, node-kind, edge source/target/kind, community, flow criticality, risk score, compound edge lookup indexes, and the composite edge upsert index.
|
||||
Reference in New Issue
Block a user