Skip to content

brokenbartender/blast-radius

Repository files navigation

blast-radius

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

PyPI version Downloads Python 3.10+ License: MIT CI


Quickstart

# 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 --json

Terminal output (with Rich)

src/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]

Mermaid diagram (renders in GitHub)

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
Loading

Python API

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)

Configuration

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.0

Or 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"))

pytest plugin — targeted test selection

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-all

Add to pytest.ini or pyproject.toml to make targeted testing the default:

[pytest]
addopts = --blast-radius

The 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.


--staged flag — impact report for all changed files

See the combined blast radius across everything you are about to commit:

impact-radius --build
impact-radius --staged
Staged (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'

pre-commit integration

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 Action — comment blast radius on PRs

# .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 }}

Graphify god-node detection (optional)

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")

Installation

# 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]

Part of the LexiPro Sovereign OS

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.


Known Limitations

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

Comparison: impact-radius vs. alternatives

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

Performance

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).


License

MIT — see LICENSE.

Built by Broken Arrow Entertainment LLC · Sovereign Intelligence Systems Group


OS Integration — God-Node Gate

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.