Skip to main content

Blast-radius and provenance analysis for Python - what breaks if I change this symbol, and where a pasted snippet came from, read from the bytes on disk right now.

Project description

blast: A Blast Radius Tool for Coding Agents

Overview

An agent about to change a symbol has one question, and it is not "where does this string appear". It is "what breaks". Those are different questions, and a grep answers only the first.

blast SYMBOL answers the second. It reports where the symbol is declared, which functions lean on it, what it leans on in turn, the comments that describe it and will be wrong once you edit it, and the files that historically move alongside it.

Every edge is derived on demand from the corpus as it is right now. There is no project model to configure and no graph to rebuild, so a file another agent saved a second ago is already in the answer, and a symbol in a language nobody taught the tool about still resolves.

That last part is the trade. A compiler front end would be more precise on the one language it was built for; blast is parser-free and reads byte shape, so it covers every language in a polyglot tree at once and pays for it with heuristics it is required to label rather than hide.

Why this over grep?

Use blast when you are about to change code and want the change's footprint before you make it. It is built for an agent editing an unfamiliar tree, and for a human doing the same thing at 2am.

Reach for gist instead when you know the pattern and want the matching lines. gist is a ripgrep-parity indexed search, and a blast report is a much more expensive thing to compute than the list of lines you actually asked for.

Reach for relate instead when the question is about similarity rather than a symbol - what resembles this file, what repeats across the tree, which files jointly explain a task.

Reach for a language server instead when you have one, the tree is one language, and the project builds. blast is what you use when any of those three is false.

Support

File a bug against this repository when a report is wrong: a call site blast missed, a row it invented, or a definition it failed to find. Paste the symbol and the --json report, which is the whole of what blast believes.

File it against irregex when the fault is in matching itself - a pattern that should match and does not, or a Unicode boundary that reads wrong. irregex is the engine underneath all three faces, and a matching bug reproduces there in isolation.

File it against relate when the fault is in similarity - a twin that is not a twin, or a provenance phrase attributed to the wrong file. The kinship and attribution kernels live there; blast only composes them.

Report a vulnerability through the process in SECURITY.md, never as a public issue.

Install

Build from source with a Zig toolchain, or take the CLI from a release.

zig build            # blast → zig-out/bin/blast

On Windows, the installer builds blast.exe, places it in a per-user directory, and adds that directory to the user PATH without elevation:

.\install.ps1

The binary is standalone for the blast verb. provenance additionally reads the codex shelf that relate writes, so install relate if you want attribution.

Reading a Report

A report has six sections, and they are ordered by how likely you are to have to edit them. Run it on any symbol to see the shape.

blast runBlast

Every section is capped, so a report cannot flood a context window however popular the symbol is. Pass --budget N to cap it harder in approximate tokens; what gets trimmed is counted into stats.omitted rather than silently dropped.

Exact evidence and statistical evidence never mix. A line number and a def/use classification come from matching the bytes; a twin distance comes from compression kinship; they stay in separate fields and are never fused into one relevance score you cannot take apart.

Seed

The seed is where the symbol is declared, plus a guess at what kind of thing it is. A symbol declared in several places lists all of them, strongest declaration first.

Only a source file can declare anything. A definition list in prose and a key in a config file both wear shapes that read like declarations, so they are recorded as mentions and can never pose as the symbol's home.

Dependents

Dependents are the references, and they are the reason you ran the tool. Each row names the file, the line, the enclosing function when there is one, and whether that line uses the symbol or redefines it.

A reference outside a function body counts. Registries, dispatch tables, export lists, route maps, and dependency-injection wiring are exactly the edges that break a build when a name moves, and they live at file scope where a function-shaped search cannot see them.

A reference inside a string literal counts too, marked str. Names get wired by string in reflection, in SQL, and in route tables, so dropping them loses real edges - but a string is weaker evidence than a call, and the report says which it found rather than flattening the two.

Dependencies

Dependencies invert the question: what does the seed itself lean on. This is the list of things whose change would break it, which is what you want when the symbol is misbehaving rather than moving.

Resolution is deliberately conservative. The seed's own parameters and locals are excluded, a qualified head.member resolves only inside the module its head names, and a name that a whole package declares is treated as ambient rather than as a dependency.

Only a function has a body to lean on anything, so a type or a value reports no dependencies at all rather than reporting the neighboring words of its declaration.

Comments

Comments that mention the symbol are the documentation your edit is about to falsify. This is the stale-doc surface, and it is the part of a change that review catches last and users notice first.

A mention inside a comment is never counted as a dependent, and a mention inside a string is never counted as a comment. The same parser-free lexer decides both, so the two sections cannot disagree about where a comment ends.

Twins and Ripple

Twins are files that compress well against the seed's file - near-duplicates, forks, and parallel implementations. They are a co-edit signal rather than a dependency: nothing references anything, but historically these files move together, so a change here usually wants the same change there.

Ripple is the second hop. It names files that call the seed's dependents, so a change that propagates through one of them can reach here, and each row records which dependent bridges the two.

Both are statistical and both are labeled as such. A twin carries its distance so you can see how strong the claim is, and a ripple row carries its bridging name so you can dismiss it in one read.

What Outranks What

Authored code outranks generated code everywhere in the report. A generated file is regenerated from a contract, so it is almost never an agent's edit target - and left unranked it dominates, because codegen repeats a symbol in every stub, descriptor, and client shim it emits.

Generated rows are tagged gen and sorted last rather than deleted. Sometimes the generated call site is the evidence you wanted, and a tool that silently hid it would be lying about the radius.

Ranking runs before the caps, which is the part that matters. It means a symbol with six authored call sites and four hundred generated ones reports the six, where a first-come report would fill its entire budget with stubs and never mention the code you have to change.

Codegen is recognized from a generated-by header marker first and a filename convention second. Both are liberal by design: a false demotion only reorders a report, and no signal here can hide a match.

Recipes

Ask what a rename would touch, before you rename anything.

blast WalletService

Narrow to one subtree when you already know the change is local. Scope is optional because a blast radius that stopped at a directory would lie, so narrowing is something you must ask for.

blast Session services/backend clients/web

Take the report as one JSON object when an agent is going to read it rather than a person. The schema is stable, every section is a named key, and nothing is truncated without being counted.

blast Session --json

Cap the report when context is tight. The seed, the stats, and the notes are the spine and are never trimmed; the tail goes first, cheapest evidence first.

blast Session --budget 800

Ask what the tool can do, in a form written for a machine reader with no other documentation.

blast --schema

Provenance

blast provenance TEXT answers a different question with the same discipline: where did this text come from, and does the tree still contain it. It is the verb for a snippet you were handed and are about to paste.

Attribution is relate's, verification is blast's. relate attributes each maximal verbatim phrase to one exemplar file on the codex shelf, then blast re-reads that file's current bytes and re-finds the phrase exactly.

A phrase surfaces only if the live file still holds it. That is the whole point of doing this here rather than in relate: an attribution against a shelf built yesterday can name a line that has since been deleted, and blast will not report one.

blast provenance 'const fd = std.posix.openat(std.posix.AT.FDCWD, path'

Raise --min-phrase above its twelve-byte floor to drop trivial quotes, and -C to widen the context lines around each located phrase. The shelf comes from relate index --shelf, and provenance says so plainly when it is missing rather than reporting an empty answer.

Note the two verbs shape their JSON differently, because their answers are different objects. A blast report is one JSON object; provenance emits NDJSON, one row per attributed phrase.

Contracts

Results go to stdout and diagnostics go to stderr, always. A run you piped and a run you watched produce the same bytes on stdout, so a captured report and a read one can never disagree.

Exit codes are ripgrep-shaped. Zero means the verb ran, two means a usage, parse, or missing-shelf error, and a report with no rows is still a zero.

A name that used to be a verb is a diagnostic, never a silent alias. blast context and blast family folded into relate's --matching modifier, and invoking either prints the invocation that replaced it and exits two - so a pinned script fails loudly instead of drifting onto semantics that moved.

blast --schema is the machine-readable contract: every verb, every flag, its type and default, the exit codes, and the notes. Read it rather than parsing --help, which is written for a person, and note that --version reports this package's own number rather than the engine's.

Build and Test

Build, test, and typecheck with the Zig toolchain.

zig build          # the blast binary → zig-out/bin/blast
zig build test     # the unit suite
zig build check    # compile-only

Builds are ReleaseFast unless -Dcli-optimize says otherwise. The suite here is deliberately small: this package is a face over engines that carry their own much larger suites, so most of blast's behavior is proven underneath it.

The package's import topology is machine-checked by contract/blast.ward, which allows two hops of reach and no more. The face lives in src/surface/face/blast/ and imports everything else: irregex for engines, corpus, and argv; relate for kinship, the shelf, and the composition kernels; gist for the CLI chassis.

Where This Came From

blast was extracted from a package path inside a private monorepo, cut at ce430bbaab. It was briefly named irregex, which is why that name now belongs to the engine package rather than to a binary.

Development uses sibling checkouts wired by build.zig.zon path dependencies; releases pin a url and a hash. The license is Apache-2.0, matching every package underneath it.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

blast_search-1.0.0.tar.gz (31.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

blast_search-1.0.0-py3-none-any.whl (13.1 kB view details)

Uploaded Python 3

File details

Details for the file blast_search-1.0.0.tar.gz.

File metadata

  • Download URL: blast_search-1.0.0.tar.gz
  • Upload date:
  • Size: 31.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for blast_search-1.0.0.tar.gz
Algorithm Hash digest
SHA256 254732e48eec4c003d8cb0ac50b5a2ae96ad1b91d4d880215db7cd15000a2520
MD5 a37cd2fbe640ebd7cd70978319a8e77d
BLAKE2b-256 cfc4a2cd33924f4411a2c6a3cf25d68e484b217d263088fe0e933ba425d3cd7c

See more details on using hashes here.

Provenance

The following attestation bundles were made for blast_search-1.0.0.tar.gz:

Publisher: release.yml on The-Billy-Company/blast

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file blast_search-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: blast_search-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 13.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for blast_search-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5fe2b9c5145b36d2415ffbceea9cab8606d3862cd11c9d7d0e1f9b715964799d
MD5 fda48eace0843a3860a61070cc960b55
BLAKE2b-256 d325b86df9cfc750bb5067217d032f2692b00cdc2b375cce6d27520ea7e06802

See more details on using hashes here.

Provenance

The following attestation bundles were made for blast_search-1.0.0-py3-none-any.whl:

Publisher: release.yml on The-Billy-Company/blast

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page