NxtSoftLabs
DEVELOPER TOOLSBeta0

Blastline

Graph-backed test impact and blast radius for CI and PRs.

TypeScript

Why it exists

Deciding which tests a diff needs means knowing what the change reaches — and that answer lives in the relationships between files, not in the diff. Existing test-impact tools lock you to one build system, one language via coverage instrumentation, or a closed SaaS.

Blastline computes it from a deterministic CGraph code graph instead: changed lines map to graph symbols, a transitive-dependents walk finds every test that reaches them, and anything the graph cannot vouch for fails open to a full run — with machine-readable reasons. The contract is "run at least these," never "safe to skip."

What it does

Impacted tests, not guesses
blastline tests main..HEAD | xargs vitest run runs exactly what the change reaches.
Blast radius on every PR
The GitHub Action comments transitive dependents with file:line before the reviewer reads the diff.
Safe superset, fail-open
Unmapped files, stale or sparse graphs, and oversized diffs all fail open to a full run, with typed reasons.
Cross-repo seams
Fuse two services' graphs through a seam spec — a TypeScript schema change surfaces the Python worker's call sites.
A safety audit on every merge
Every push to main replays selection, runs the full suite, and records any escaped failure to a public ledger — the badge turns red on the first contract violation.
Check what an agent claims
blastline check callers <symbol> answers "did it really update every call site?" against the graph — it refutes with file:line evidence, and never certifies.

How selection works

git diff --unified=0
  -> changed line ranges
  -> innermost graph node per line
  -> transitive dependents walk
  -> intersect with test files
  -> subset + blast radius, or ALL with reasons
Diff in, impacted tests and blast radius out — pure graph traversal, under a millisecond on a built graph

Every subset carries the graph's content root as provenance, and --expect-root / --daemon-verify pin a selection to an exact source tree — a stale index names both roots and fails open instead of answering from the past.

Seven language families, and what each one has earned

Blastline separates detected from verified, and says which is which. Five families are replay-verified by historical-commit replay: every semantically selectable test the author co-changed must appear in the selection computed from the non-test changes. Two more are advisory — the graph is good enough to select from, but the replay that would promote them has not run yet.

  • TypeScript (Vitest/Jest) — 43/43 on es-toolkit
  • Python (pytest conventions) — 4/4 on itsdangerous
  • Go (*_test.go) — 10/10 on gorilla/mux, after the dispatch barrier (v0.8) stopped one interface implementation's edit from cascading through its sibling implementers — mean subset fell from 45.9% to 30.3% with every co-changed test still selected
  • C/C++ (googletest/ctest conventions) — 62/62 on CGraph itself
  • Rust (Cargo integration targets) — advisory, promoted from always-fail-open once upstream CGraph work made Rust graphs legible: clap's co-changed recall went 0/29 → 23/29 and tokio's 1/12 → 11/12
  • Java (Surefire, Failsafe conventions) — 14/14 on stleary/JSON-java, after CGraph learned to resolve new Foo(), then gained interface dispatch and a receiver tier that scopes XML.toJSONObject() to the class its call site names — co-changed recall went 8/14 → 14/14 and every behaviorally-confirmed miss is now selected
  • Kotlin (Kotest/Spek conventions) — advisory, once CGraph learned to extract Kotlin at all; the graph is legible (cashapp/turbine reaches 0.625) but no co-changed replay has been run, and reachability is not the bar

Whether a family selects at all is decided per graph at run time, not by a flag: if tests can forward-reach under 25% of the code's symbols, Blastline calls the graph blind and runs everything. That floor is why Rust shipped gated and then lifted itself as CGraph improved, with no Blastline release in between.

The verification loop is the product working on itself: each benchmark run has caught real CGraph extraction bugs before they shipped — silently dropped files, unresolved member calls, missing interface-dispatch edges, overload sets invisible to reverse walks, and 317 tokio call sites hidden inside cfg_*! macros — each fixed upstream, then re-verified. CGraph's own pull requests now get Blastline comments, and a safety-audit workflow re-runs the same judgment on every merge to Blastline's own main, recording any escaped failure to a public ledger.

Where static reachability stops

One benchmark miss survives every graph fix, and it is not a bug. On ripgrep, reverting a flag change fails 14 tests in tests/index/disallowed.rs — those tests genuinely depend on the change. But they invoke the built rg binary as a subprocess, not through any function call, so no static edge ties them to the code and none ever can.

That is the boundary of static reachability, and it is the reason the contract is shaped the way it is: Blastline returns a superset, fails open when it cannot vouch for a diff, and never certifies a test as safe to skip.

Quick start

cgraph --root . --out cgraph-out
npx blastline tests main..HEAD | xargs vitest run
Tip

Set graph-root and the Action installs CGraph and builds the head graph itself, cached by tree hash — no graph.json to produce or store. The Action is on the GitHub Marketplace as “Blastline Test Impact,” and CGraph publishes prebuilt binaries, so the whole pipeline runs on a stock CI runner with no from-source build.

Next steps