# How CRG Implements Blast Radius Analysis for Code Changes

> Learn how CRG implements blast radius analysis for code changes. Discover how it walks dependency graphs and scores reachable nodes with weighted, direction-aware traversal for precise impact assessment.

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: deep-dive
- Published: 2026-08-10

---

**CRG determines a change's blast radius by walking the dependency graph from modified files and scoring every reachable node using weighted, direction-aware traversal with best-score BFS relaxation.**

The **Code-Review-Graph (CRG)** is an open-source dependency analysis tool that quantifies how far a code change propagates through a codebase. Its blast radius analysis helps developers identify which components, tests, and downstream consumers are affected by a modification before it ships.

## How Blast Radius Analysis Works in CRG

CRG's blast radius computation combines three interconnected components: seed generation from changed files, weighted edge traversal with direction rules, and a bounded best-score breadth-first search. The implementation spans [`graph.py`](https://github.com/tirth8205/code-review-graph/blob/main/graph.py), [`constants.py`](https://github.com/tirth8205/code-review-graph/blob/main/constants.py), and supporting tool files.

### Step 1: Collect Impact Seeds from Changed Files

The analysis begins by identifying **seed nodes**—every qualified name contained in the modified files. This happens in `Graph._impact_seed_qns` at `graph.py:L1307-L1315`.

For each file in `changed_files`, CRG retrieves all nodes via `self.get_nodes_by_file` and extracts their `qualified_name`. C# files receive special handling: their declared `csharp_namespaces` are also added as seeds, since namespaces act as bridges for import-related traversals.

```python

# From graph.py:L1307-L1315

def _impact_seed_qns(self, changed_files: list[str]) -> set[str]:
    seeds = set()
    for f in changed_files:
        for node in self.get_nodes_by_file(f):
            seeds.add(node.qualified_name)
            if node.language == "csharp":
                seeds.update(node.csharp_namespaces or [])
    return seeds

```

### Step 2: Initialize Impact Tracking Tables

The SQLite-based implementation creates four temporary tables to manage traversal state:

| Table | Purpose |
|-------|---------|
| `_impact_seeds` | Stores the original seed qualified names |
| `_impact_best` | Tracks the highest score seen for each visited node (initialized to `1.0` for seeds) |
| `_impact_frontier` | Current "wave" of nodes to expand in the next iteration |
| `_impact_next` | Buffer for candidates that survive score filtering |

### Step 3: Execute Weighted, Direction-Aware BFS

The core traversal logic resides in `Graph.get_impact_radius_sql` (`graph.py:L1450-L1500`). For each depth level up to `max_depth`, CRG:

1. **Joins the frontier with edges**, respecting **direction rules** from `IMPACT_EDGE_DIRECTIONS`
2. **Multiplies scores** by the edge's **weight** and the **depth decay factor** (`IMPACT_DEPTH_DECAY`, default `0.6`)
3. **Filters candidates** against the current best score and global floor (`IMPACT_SCORE_FLOOR`, default `0.05`)
4. **Promotes survivors** to `_impact_best` and advances the frontier

```python

# Edge weights and directions defined in constants.py:L57-L84

IMPACT_EDGE_WEIGHTS: dict[EdgeKind, float] = {
    EdgeKind.CALLS: 1.0,
    EdgeKind.INHERITS_FROM: 0.9,
    EdgeKind.IMPORTS_FROM: 0.5,
    EdgeKind.TESTED_BY: 0.7,
    # ... additional edge kinds

}

IMPACT_EDGE_DIRECTIONS: dict[EdgeKind, str] = {
    EdgeKind.CALLS: "incoming",        # changes to callee impact callers

    EdgeKind.INHERITS_FROM: "incoming",
    EdgeKind.IMPORTS_FROM: "incoming",
    EdgeKind.TESTED_BY: "outgoing",    # changes to production code impact tests

    # ... additional edge kinds

}

```

The **best-score relaxation** guarantees each node is retained with its strongest path score, preventing exponential explosion in cyclic graphs.

### Step 4: Return Structured Impact Results

When traversal terminates (depth limit reached, score floor hit, or no new candidates), CRG returns:

- `changed_nodes`: original seed nodes
- `impacted_nodes`: all reached nodes sorted by decreasing score
- `impacted_files`: files containing impacted nodes
- `edges`: traversed edges for visualization
- `impact_scores`: mapping of qualified names to final scores

## Using CRG Blast Radius Analysis

### Command-Line Interface

The `impact` subcommand in [`cli.py`](https://github.com/tirth8205/code-review-graph/blob/main/cli.py) provides direct access:

```bash

# Basic usage

$ crg impact --files src/auth.py --depth 3

# Multiple files with custom limits

$ crg impact --files services/payment.py models/user.py --depth 2 --max-nodes 500

```

### Python API

For programmatic access, use `GraphStore.get_impact_radius`:

```python
from code_review_graph.graph import GraphStore

store = GraphStore(db_path="crg.db", repo_root="/path/to/repo")
impact = store.get_impact_radius(
    changed_files=["services/payment.py"],
    max_depth=2,
    max_nodes=500,
)

print(f"Seeds: {len(impact['changed_nodes'])}")
print(f"Total impacted: {len(impact['impacted_nodes'])}")

for node in impact["impacted_nodes"][:10]:
    score = impact["impact_scores"][node.qualified_name]
    print(f"- {node.qualified_name} (score={score:.2f})")

```

### MCP Tool Integration

The analysis is exposed as an MCP tool for IDE and agent integration via `get_impact_radius_tool` in [`main.py`](https://github.com/tirth8205/code-review-graph/blob/main/main.py):

```python
from code_review_graph.main import get_impact_radius_tool

result = get_impact_radius_tool(
    changed_files=["services/payment.py"],
    max_depth=2,
    repo_root="/path/to/repo",
)

```

## Key Configuration Parameters

Tune blast radius behavior through constants in [`constants.py`](https://github.com/tirth8205/code-review-graph/blob/main/constants.py):

| Parameter | Default | Effect |
|-----------|---------|--------|
| `IMPACT_EDGE_WEIGHTS` | varies by `EdgeKind` | How much each edge type contributes to impact score |
| `IMPACT_EDGE_DIRECTIONS` | `incoming` or `outgoing` | Which direction impact propagates per edge type |
| `IMPACT_DEPTH_DECAY` | `0.6` | Multiplicative score reduction per traversal depth |
| `IMPACT_SCORE_FLOOR` | `0.05` | Minimum score to retain a node |
| `CRG_MAX_IMPACT_DEPTH` | `5` | Hard stop for traversal depth |
| `CRG_MAX_IMPACT_NODES` | `10000` | Hard stop for total nodes visited |

## Summary

- **Seed generation** (`_impact_seed_qns`) extracts all qualified names from changed files plus C# namespaces

- **Edge weights and directions** (`IMPACT_EDGE_WEIGHTS`, `IMPACT_EDGE_DIRECTIONS`) assign semantic meaning to different dependency types
- **Best-score BFS** (`get_impact_radius_sql`) propagates impact with score decay and bounded growth
- **Multiple interfaces** expose the same core logic: CLI (`crg impact`), Python API (`GraphStore.get_impact_radius`), and MCP tools (`get_impact_radius_tool`)

## Frequently Asked Questions

### What makes CRG's blast radius analysis different from simple file-level dependency tracking?

CRG operates at the **symbol level** (functions, classes, modules) rather than files, and uses **weighted edge semantics** to distinguish tight coupling (`CALLS`) from loose references (`IMPORTS_FROM`). The best-score BFS with configurable decay produces a ranked list of impacted components rather than a flat set.

### How does CRG handle circular dependencies during blast radius analysis?

The **best-score relaxation** in `get_impact_radius_sql` ensures each node keeps only its highest-scoring path. When cycles are encountered, the decay factor (`IMPACT_DEPTH_DECAY`) progressively reduces scores, and the global floor (`IMPACT_SCORE_FLOOR`) eventually prunes low-impact paths. This guarantees termination without exponential blowup.

### Can I customize which edge types contribute to blast radius scores?

Yes. Modify `IMPACT_EDGE_WEIGHTS` and `IMPACT_EDGE_DIRECTIONS` in [`constants.py`](https://github.com/tirth8205/code-review-graph/blob/main/constants.py) before building the graph, or override constants at runtime. For example, setting `TESTED_BY: 0.0` would exclude test coverage from impact propagation, while increasing `IMPORTS_FROM` would amplify cross-module dependency effects.

### What is the performance characteristic of blast radius analysis on large codebases?

The SQLite-based implementation runs in **O(E × D)** where E is edges examined and D is `max_depth`, bounded by `CRG_MAX_IMPACT_NODES`. For massive graphs, CRG also provides `Graph._get_impact_radius_networkx` as an alternative backend, though `get_impact_radius_sql` remains the default for memory efficiency.