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
blastline tests main..HEAD | xargs vitest run runs exactly what the change reaches.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.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 reasonsEvery 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 scopesXML.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 runSet 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
- Read the launch post — the design, the benchmarks, and the bugs the loop caught.
- v0.4–v0.8: the safety audit and the dispatch barrier — cross-repo seams, C/C++, base-graph automation, a standing audit on every merge, and the fix that cut Go's mean subset by a third.
- Our benchmark was lying — how a dependency-filtered oracle turned a 6/12 "recall crisis" into one genuine miss, and the macro-blindness bug that chase uncovered.
- GitHub Marketplace — add the Action to a workflow.
- npm —
npx blastlineagainst any repo CGraph can extract. - Contribute — the spec-driven history in
openspec/is the fastest way in.