What breaks if I change this file?
blast-radius builds an incremental AST call graph for your Python project and
answers the question instantly. It indexes imports and symbol definitions into
SQLite (SHA-256 cached — only changed files are re-indexed) and resolves
transitive dependents in milliseconds.
pip install impact-radius# Build the graph (incremental — only re-indexes changed files)
impact-radius --build
# What breaks if I change src/utils.py?
impact-radius --query src/utils.py
# Output a Mermaid diagram (renders in GitHub READMEs / PRs)
impact-radius --query src/utils.py --mermaid
# Watch mode — auto-rebuild on every save
impact-radius --watch
# Raw JSON output (pipe to jq, scripts, CI)
impact-radius --query src/utils.py --jsonsrc/utils.py ⚡ GOD NODE
├── 📄 src/api/routes.py
├── 📄 src/models/user.py
├── 🧪 tests/test_utils.py
└── 🧪 tests/test_api.py
14 file(s) affected · in-degree 47 · 2 test file(s)
Install Rich for colored output:
pip install impact-radius[rich]from blast_radius import build_graph, get_blast_radius, to_mermaid
build_graph()
result = get_blast_radius("src/utils.py")
print(to_mermaid(result))Paste the output into any GitHub issue, PR, or README:
graph TD
utils_py["src/utils.py"]
utils_py --> routes_py["src/api/routes.py"]
utils_py --> user_py["src/models/user.py"]
utils_py --> test_utils_py["tests/test_utils.py"]
style test_utils_py fill:#22c55e,color:#fff
%% total affected: 3 | in-degree: 0
from blast_radius import build_graph, get_blast_radius, to_mermaid, watch
# Build the import graph (incremental — re-indexes only changed files)
n = build_graph()
print(f"{n} files indexed")
# Query blast radius
result = get_blast_radius("src/mymodule.py")
print(result["total_affected"]) # int
print(result["direct_dependents"]) # list of file paths
print(result["test_files"]) # files that start with tests/
# Mermaid output
diagram = to_mermaid(result)
# Watch mode (blocking — runs until Ctrl+C)
watch(interval=2.0)All paths are configurable — no hardcoded assumptions:
# Scan only specific directories
impact-radius --build --scan src tests
# Use a custom DB path (useful in CI)
impact-radius --build --db /tmp/my-graph.db
# Adjust watch poll interval
impact-radius --watch --interval 1.0Or via Python API:
from pathlib import Path
build_graph(scan_dirs=["src", "tests"], db_path=Path("/tmp/my-graph.db"))
get_blast_radius("src/utils.py", db_path=Path("/tmp/my-graph.db"))Run only the tests that are actually affected by your staged changes:
pip install "impact-radius[pytest]"# Run only tests in the blast radius of staged files (pre-commit mode)
pytest --blast-radius
# Explicit file
pytest --blast-radius --br-file src/utils.py
# Bypass filter — run full suite (useful when --blast-radius is in addopts)
pytest --blast-radius --br-allAdd to pytest.ini or pyproject.toml to make targeted testing the default:
[pytest]
addopts = --blast-radiusThe plugin deselects any test not in the blast radius of your changed files. Falls back to the full suite safely if no staged files are found or the graph is unavailable.
See the combined blast radius across everything you are about to commit:
impact-radius --build
impact-radius --stagedStaged (2 file(s)):
src/utils.py
src/models/user.py
Blast radius — 6 file(s) affected:
-> src/api/routes.py
-> src/api/auth.py
-> tests/test_utils.py [TEST]
-> tests/test_api.py [TEST]
-> tests/test_auth.py [TEST]
-> src/cli.py
Add --json for machine-readable output:
impact-radius --staged --json | jq '.test_files'Add to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/brokenbartender/blast-radius
rev: v1.1.0
hooks:
# Rebuild the graph whenever Python files change
- id: blast-radius-build
# Show impact report for staged files (informational, never blocks)
- id: blast-radius-staged# .github/workflows/blast-radius.yml
name: Blast Radius
on: [pull_request]
jobs:
blast-radius:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with: {python-version: "3.11"}
- run: pip install impact-radius
- name: Compute blast radius for changed files
id: br
run: |
impact-radius --build
# Get the first changed .py file to query
CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- '*.py' | head -1)
if [ -n "$CHANGED" ]; then
echo "mermaid<<EOF" >> $GITHUB_OUTPUT
impact-radius --query "$CHANGED" --mermaid >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "mermaid=No Python files changed." >> $GITHUB_OUTPUT
fi
- uses: marocchino/sticky-pull-request-comment@v2
with:
message: |
## Blast Radius
${{ steps.br.outputs.mermaid }}If you have graphify installed and
have run graphify update . in your repo, blast-radius will enrich results with
transitive centrality data and flag god nodes (files imported by 30+ modules):
result = get_blast_radius("src/core.py")
if result.get("graphify", {}).get("god_node"):
print("WARNING: this is a god node — change it carefully")# Core (no dependencies)
pip install impact-radius
# With Rich colored output
pip install impact-radius[rich]
# With watchdog file watching (faster than polling)
pip install impact-radius[watch]
# Everything
pip install impact-radius[all]blast-radius is extracted from LexiPro — a local-first
agentic OS where it powers the pre-edit safety gate: before any agent touches a
kernel file, get_blast_radius() checks if it's a god node. Agents touching files
with in-degree ≥ 30 must acquire a hard mutex first.
| Limitation | Impact | Mitigation |
|---|---|---|
| Static analysis only | Dynamic imports (__import__, importlib) flagged but not graph-traversed |
Check result["dynamic_import_warning"] in results; manually verify dynamic targets |
| Python only | No cross-language analysis | Pair with language-specific tools for polyglot repos |
| Import aliases not resolved | import numpy as np; np.something won't resolve symbol-level deps |
Structural file-level deps still captured |
__all__ not parsed for re-exports |
Re-exported symbols via __all__ look like dead ends |
Treat re-export files as high-blast-radius by convention |
| No runtime import analysis | Conditional imports (if TYPE_CHECKING) count as live deps |
Use --type-checking-only flag (planned) to filter |
| Feature | impact-radius | pydeps |
importlab |
modulegraph |
|---|---|---|---|---|
| Transitive blast radius query | Yes | No | Partial | No |
| Incremental SQLite cache | Yes (SHA-256) | No | No | No |
| God-node detection | Yes | No | No | No |
| Mermaid diagram output | Yes | No | No | No |
| File-watch mode | Yes | No | No | No |
| Dynamic import detection | Yes (flagged) | No | Partial | No |
| CLI + Python API | Both | CLI only | Python only | Python only |
| Install size | Minimal | Medium | Minimal | Medium |
| Project size | Index build | Blast radius query |
|---|---|---|
| 50 files | ~0.3s | <1ms |
| 500 files | ~2s | <5ms |
| 5,000 files | ~18s | <20ms |
After the initial build, incremental re-index only processes changed files. Query time is independent of project size — SQLite index lookup is O(log n).
MIT — see LICENSE.
Built by Broken Arrow Entertainment LLC · Sovereign Intelligence Systems Group
blast-radius wires into pre-edit safety gates to enforce a hard stop before any
agent touches a high-centrality file. This is the v9.6 integration pattern used in
the LexiPro Sovereign OS:
# tool_hook_pipeline.py — _hook_guardian_path_guard (v9.6)
from blast_radius import get_blast_radius, rebuild_if_stale
async def pre_edit_gate(file_path: str) -> dict:
rebuild_if_stale(max_age_seconds=300)
br = get_blast_radius(file_path)
# Hard block — god-node writes require explicit mutex acquisition
if br.get("graphify", {}).get("god_node"):
in_deg = br["graphify"]["in_degree_sum"]
return {
"allowed": False,
"error": (
f"GOD_NODE_PROTECTED: {file_path} has in_degree_sum={in_deg}. "
f"Acquire hard mutex before editing god-node files."
),
}
# High blast radius — warn + emit contention pheromone
if br["total_affected"] >= 10:
emit_contention_pheromone(file_path, strength=br["total_affected"] / 50.0)
return {"allowed": True, "warning": f"{br['total_affected']} dependents affected"}God-node threshold: files with in_degree_sum >= 30 (via Graphify) require
explicit mutex acquisition before any write. Without Graphify, the gate still warns
on total_affected >= 10.
Pheromone broadcast: high-blast-radius edits emit a WRITE_CONTENTION signal
to the NeuralBus so other agents observe the contested resource and can defer
non-critical writes.
Run graphify update . in your repo root to generate graphify-out/graph.json
and enable god-node detection.