blast-radius
When you change a file, find out what else might break.
blast-radius is a fast CLI that traces every file that depends — directly or
transitively — on the code you're about to touch, and gives you a one-glance
risk verdict. Point it at a file and it answers the question every code review
asks: "how far does this change reach?"
MODERATE ██████████░░░░░░░░░░ 6 impacted files · 2 packages
3 direct, 3 indirect · depth 2 · 1 endpoint
── IMPACTED FILES · 6 IN 2 PACKAGES ──────────────────────
apps/storefront (3)
src/ (3)
App.tsx ◎ endpoint
LegacyButtonCard.jsx
PromoCard.tsx
packages/ui (3)
src/ (3)
Card.tsx
Toolbar.tsx
index.ts
Use it to:
- Gut-check a change before you start — is this a 2-file tweak or a 200-file ripple?
- Catch surprises in code review — surface the files a diff touches that aren't in the diff.
- Gate risky commits in CI or pre-commit hooks — fail the build when a change reaches too far.
It works out of the box for JavaScript and TypeScript repos (including monorepos), and supports Python, Rust, Ruby, Java, Vue, and Svelte as optional add-ons.
Quick start
Install via npm — no Rust toolchain required. The package pulls in a prebuilt binary for your platform (Linux x64/arm64, macOS x64/arm64, Windows x64) with all language features included:
The same prebuilt binaries are attached to each GitHub Release if you'd rather download one directly.
Or build from source (requires a Rust toolchain with cargo):
# From crates.io
# Straight from GitHub
# Or from a local clone
Then point it at any file in your repo:
# What depends on this component?
# What depends on a specific export?
# Check several files at once (e.g. everything in a commit)
By default it analyzes the current directory. Use --repo-root to point
elsewhere:
Use it in pre-commit hooks and CI
The most common setup is to run blast-radius on changed files so you (and
your reviewers) see the reach of a commit before it lands.
files takes a list of paths and reports each file's blast radius plus a
combined total — designed to receive staged filenames from hook managers like
lint-staged, Husky, Lefthook, and pre-commit. For example, with
lint-staged:
To turn the verdict into a gate, exit non-zero when a change reaches too far:
# Fail (exit code 2) if a change impacts more than 50 downstream files
# Or fail when the risk verdict hits "risky" or above
Exit codes: 0 no gate tripped, 1 analysis error, 2 gate tripped, 64
usage error (so CI can tell a misspelled flag apart from a tripped gate).
See docs/local-toolchain.md for ready-to-paste examples with lint-staged,
Lefthook, and the pre-commit framework.
Reading the output
The default tree output leads with a risk verdict — minor, moderate,
risky, or high — plus a meter and the counts behind it. For larger blast
radii a hotspots chart follows, showing the directories with the most
impacted files, so you can see where the change lands before reading any paths.
The impacted files are then listed grouped by package and directory. Files
marked ◎ endpoint are leaves nothing else depends on (apps, routes, pages) —
a signal the change can reach something user-facing. Past 200 impacted files
the per-file lists collapse to directory rollups; pass -v to list every file.
The last line reports confidence: how many files were scanned and whether any import edges were ambiguous, so you know how much to trust the result.
Pass --verbose (-v) to see the full root → cascade tree of exactly how the
impact propagates.
Commands
| Command | What it does |
|---|---|
file <path> |
Everything that depends on this file. |
export <path> <name> |
Everything that depends on a specific named export. |
files <path>... |
Blast radius for each file plus a combined total. |
Global flags:
| Flag | Purpose |
|---|---|
--repo-root <dir> |
Repo to analyze (default: current directory). |
--format <tree|json|mermaid|dot> |
Output format (default: tree). |
--output <file> |
Write output to a file instead of stdout. |
--verbose, -v |
Show the full cascade tree. |
--explain-unresolved |
Group unresolved internal imports by likely cause. |
--fail-threshold <n> |
Exit code 2 when more than n downstream files are impacted (the changed files themselves are not counted). |
--fail-on-risk <tier> |
Exit code 2 when the verdict is at or above tier. |
Output formats
tree— the default human-readable verdict, meter, and impacted-file list.json— structured output; the per-input-file breakdown lives in therootsarray.mermaid— a Mermaid graph definition.dot— Graphviz DOT.
Language support
The default binary supports JavaScript and TypeScript (js, jsx, ts,
tsx), including ESM imports/exports, CommonJS require/module.exports,
default and named exports, barrels, export *, side-effect imports
(import './setup'), tsconfig.json path aliases, baseUrl, extends
chains (including tsconfig.base.json-style shared configs), package
imports/exports, .js specifiers backed by TypeScript source files,
.d.ts declaration files, and cross-package resolution across workspace
packages.
Other languages are compiled in at build time with Cargo features (there is
no runtime --language flag — a binary scans whatever was built into it). The
prebuilt binaries (the blast-radius-cli npm package and GitHub Release
downloads) ship with all language features already compiled in, so this
only matters when building from source:
Note: the Ruby adapter follows explicit require/require_relative only —
Rails/Zeitwerk autoloading is not modeled, so autoloaded apps produce
near-empty graphs (see docs/language-support.md).
See docs/language-support.md for the multi-language architecture.
Configuration
An optional .blast-radius.json at the repo root lets a repository declare
tooling quirks the analyzer shouldn't hardcode. Today it supports ignoring
import specifiers that point at generated/virtual modules (CSS-in-JS codegen,
route type stubs, published dist output, etc.) so they don't count against the
unresolved-import confidence signal:
{
// comments and trailing commas are allowed (parsed as JSONC, like tsconfig)
"unresolved": {
"ignore": ["styled-system/css", ".velite", "/+types/"],
},
}
Each entry is matched as a substring of the import specifier. Asset imports
(.svg, .css, .json, images, …) and type-only imports are ignored
automatically. See examples/chakra-ui/.blast-radius.json.
Examples
The examples/ directory has runnable fixtures for each supported language:
| Fixture | Exercises |
|---|---|
monorepo-demo |
Aliases, barrels, CommonJS, transitive React usage |
vite-react-ts |
A real Vite React + TypeScript template |
chakra-ui † |
Chakra UI snapshot for large-repo stress testing |
python-demo / fastapi † |
Python package, relative, and __init__.py reexport imports |
rust-demo |
mod, pub use, crate:: / self:: imports |
component-demo |
Mixed Vue/Svelte component imports |
ruby-demo |
require_relative, classes, modules, methods |
java-demo |
Packages, imports, public classes |
† chakra-ui and fastapi are large real-world snapshots that aren't committed
to the repo. Fetch them on demand (pinned to a known upstream commit) before
running their examples:
Run against any of them with --repo-root:
# JS/TS monorepo fixture
# Large React monorepo, with the full cascade tree
# Python (needs the feature compiled in)
# Rust
# Vue/Svelte
Development
This project expects a Rust toolchain with cargo available locally. Common
quality commands:
The Makefile has the full set, including per-language test/quality/stress
targets (make test-python, make stress-chakra, etc.). See docs/quality.md
for what each command validates and docs/language-support.md for the
multi-language architecture.