Skip to main content
Glama
README.md
# blastr

[![npm](https://img.shields.io/npm/v/@justfeltlikerunning/blastradius?logo=npm)](https://www.npmjs.com/package/@justfeltlikerunning/blastradius)
[![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-ffdd00?logo=buymeacoffee&logoColor=black)](https://buymeacoffee.com/justfeltlikerunning)

**See exactly which of your files and functions break before you upgrade an npm dependency.**

Every other tool tells you *that* an upgrade might break something — `npm outdated` lists versions, Dependabot/Renovate give crowd-sourced risk scores, `ncu --doctor` runs your tests and hopes. None of them tell you **which lines of your code actually touch the API that changed.** blastr does.

```
$ blastr chalk@5

blastr chalk  4.1.2 → 5.0.0

package-level changes:
  ! package "type" changed commonjs -> module (require()/import interop may break)

src/logger.ts
  ✖ ● 12:18  chalk.Instance  [symbol-removed]
  ! ○ 40:8   chalk.Level     [symbol-removed]  ← formatBanner

summary  2 breaking, 0 possibly-breaking across 1 file(s)
confidence  ● 1 confirmed  ◐ 0 likely  ○ 1 unknown
```

## Dogfooded on itself

blastr is developed with blastr. Running `blastr audit` on **its own** dependencies
ranks every pending upgrade by blast radius:

```
$ blastr audit

package           upgrade             brk  psb  files
es-module-lexer   1.7.0→2.3.0           0    3      1
cjs-module-lexer  1.4.3→2.2.0           0    2      1
vitest            2.1.9→4.1.10          0    0      0
typescript        5.6.3→7.0.2           0    0      0
...
ranked by blast radius — top rows are the risky upgrades to review first
```

…and drilling into the top one shows the exact sites — with confidence tiers and
the transitive call chain — that a `es-module-lexer` bump would break:

```
$ blastr es-module-lexer@2

./src/core/surface.ts
  ! ● 386:33  esParse  [signature-changed]
  ! ● 388:14  esParse  [signature-changed]
  ! ◐ 436:25  esParse  [signature-changed]  ← esmExports
  ! ○ 559:29  esParse  [signature-changed]  ← esmExports ← extractWithLexers

summary  0 breaking, 4 possibly-breaking across 1 file(s)
confidence  ● 2 confirmed  ◐ 1 likely  ○ 1 unknown
```

(TypeScript 5.6→**7.0** shows `0` impact — blastr doesn't import the compiler API in
a way v7 changed, so that jump is safe. That's the point: it distinguishes the
scary-looking-but-safe from the actually-risky.)

## How it works

blastr joins two analyses nobody else connects:

1. **Version-to-version API diff.** It fetches the target version, extracts the public API surface of both versions (TypeScript declarations when present, runtime probing for untyped packages), and produces a precise set of removed / signature-changed / kind-changed symbols.
2. **Consumer usage tracing.** It statically resolves your imports to those changed symbols using the TypeScript compiler, finds every usage site, and expands through your own call graph to catch code that *transitively* depends on the break.

The result is a **deterministic blast-radius graph** — `changed symbol → your import → usage site → the functions that call it` — with provenance on every finding.

### Honesty by design

blastr proves the **presence** of impact, never its absence. A clean report means "no usages found," not "safe." Every finding carries a confidence tier:

| Tier | Meaning |
|------|---------|
| ● `confirmed` | the TypeScript checker resolved the identifier to the changed symbol in that exact package |
| ◐ `likely` | syntactic match without full type resolution (untyped JS, destructured `require`) |
| ○ `unknown` | your code uses a pattern static analysis can't see through (dynamic `require`, computed member access) — reported with the reason, never silently dropped |

## Install

```
npm install -g @justfeltlikerunning/blastradius   # or: npx @justfeltlikerunning/blastradius <package>
```

## Usage

```
blastr <package>[@target] [options]     analyze one upgrade against your code
blastr diff <package> <from> <to>       standalone API diff (no repo needed)
blastr audit [--wanted]                 rank ALL outdated deps by blast radius
blastr mcp                              stdio MCP server for AI agents

  --to <spec>       target version/range/tag (default: latest)
  --from <version>  override the installed version to compare against
  --cwd <dir>       consumer repo root (default: current directory)
  --depth <n>       transitive expansion depth (default: 2; 0 = direct only)
  --dynamic         enable the runtime probe for untyped packages
  --behavioral      also scan the changelog for same-signature behavioral breaks
                    (needs ANTHROPIC_API_KEY; LLM-assisted, best-effort)
  --json / --ndjson / --pretty   output format (pretty defaults on a TTY)
```

**Exit codes:** `0` no impact · `1` impact found · `2` analysis error.

### Triage your whole upgrade backlog

```
$ blastr audit

blastr audit  8 outdated dependenc(ies) → latest

package  upgrade             brk  psb  files
chalk    4.1.0→5.6.2           1    0      1
express  4.19.2→5.1.0          1    2      3
ms       2.1.2→2.1.3           0    0      0
...
ranked by blast radius — top rows are the risky upgrades to review first
```

The answer to "which of my 20 Dependabot PRs actually matter."

### Accuracy: only real breaks

blastr classifies signature changes by **caller-compatibility** (jsii-style variance):
adding an optional parameter or an overload is *not* breaking and won't be flagged;
removing a parameter or adding a required one *is*. A real `zod` 3→4 run drops from
14 raw signature-change alarms to the 1 that actually matters — with a transparent
count of what was classified compatible, so nothing is silently hidden.

### Untyped packages

Packages that ship no `.d.ts` still get analyzed, via a tiered fallback:

1. **`@types` sidecar** — if `@types/<pkg>` exists on DefinitelyTyped, blastr borrows it for a full typed surface (so `express` upgrades analyze at `confirmed` confidence with real signatures).
2. **Module lexers** — names of the runtime exports.
3. **`--dynamic`** — load the package in a **sandboxed child process** (`--no-addons`, hard timeout, lifecycle scripts never run) to enumerate its real runtime surface.

## For AI agents

blastr is built to be driven by coding agents, not just humans.

- **`--json`** emits a stable, versioned schema (`schemaVersion: 1`). Inspect it with `blastr --print-schema`.
- **`--ndjson`** streams findings one JSON object per line for progressive consumption.
- **Exit codes** let an agent branch without parsing (`1` = there's a blast radius to review).
- **Results go to stdout, logs to stderr** — always safe to pipe.
- **MCP server:** run `blastr mcp` for a stdio Model Context Protocol server exposing a single `blastr_check` tool that returns the same report. Point Claude Code / any MCP client at it:

  ```json
  { "mcpServers": { "blastradius": { "command": "npx", "args": ["@justfeltlikerunning/blastradius", "mcp"] } } }
  ```

### In CI / on pull requests

`--markdown` emits a PR-comment-ready summary. Drop this into a workflow to get a
blast-radius comment on every dependency-bump PR:

```yaml
# .github/workflows/blastr.yml
name: blast-radius
on: pull_request
jobs:
  blastr:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - name: Blast-radius audit
        run: npx @justfeltlikerunning/blastradius audit --markdown > blastradius.md
      - uses: marocchino/sticky-pull-request-comment@v2
        with:
          path: blastradius.md
```

Or feed findings into the **GitHub Security tab** as code-scanning alerts (SARIF):

```yaml
      - run: npx @justfeltlikerunning/blastradius <pkg>@<target> --sarif > blastr.sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: blastr.sarif
```

Use `--fail-on breaking` (or `possibly` / `any` / `none`) to control when the job fails.

## Configuration (optional)

Drop a `blastr.config.json` in your project root to set defaults and ignore paths:

```json
{
  "ignore": ["**/test/**", "**/*.stories.tsx"],
  "depth": 2,
  "dynamic": false,
  "audit": { "ignore": ["some-internal-pkg"] }
}
```

CLI flags always override config. `ignore` globs are excluded from usage scanning.

## What it is not

blastr checks the **API surface** — removed exports, changed signatures, entry-point and module-type changes. It cannot see purely *behavioral* changes that keep the same signature (a function that now throws on bad input, a changed default). Read the changelog for those; blastr tells you where the structural breaks land.

## License

MIT