Skip to main content

aftershock

tests license: MIT

Find the exact lines a library, API or config change breaks in other code — verified, with file:line, never a risk score.

A signature diff tells you what changed. aftershock tells you which lines elsewhere stop working: in the same repository, in the services that import the library, in the Helm chart that configures it, or in the TypeScript UI that calls its routes. Rules decide everything they can; a calibrated model judges only what code cannot decide; anything still uncertain goes to a person, never into a verdict.

what changed (griffe API diff)  ─►  who might use it (AST candidates)  ─►  rules decide what rules can
                                                                                   │
                                                  a calibrated model judges the rest (optional)
                                                                                   │
                                     ❌ breaks · ❓ for a person · ✅ still valid — each with file:line

Install

pip install "aftershock @ git+https://github.com/apoorv1997/aftershock"            # rules only
pip install "aftershock[model] @ git+https://github.com/apoorv1997/aftershock"     # + model judgement

Python 3.11+. The rules-only install depends on griffe alone; spans the rules cannot decide are listed for review. The model extra adds TypeSafe's Jev, which returns probabilities rather than prose, for exactly two questions: does this consumer really use the changed API (not a look-alike), and does the change break it. It needs TYPESAFE_API_KEY (https://console.typesafe.ai/keys); every call is capped by a spend budget.

Quickstart

# a library's API change -> the lines it breaks in a consumer tree
aftershock api-impact --lib ../httpx --package httpx --old 0.27.2 --new 0.28.0 --consumer ../my-service
aftershock api-impact ... --github --per-query 60 --max-queries 12      # public consumers via GitHub code search
aftershock api-impact ... --no-tests --format md --json out.json        # production code only, PR-comment markdown

# env keys the code reads (os.getenv, pydantic settings with env_prefix / aliases / helper-built prefixes) vs keys a chart sets
aftershock config-contract --code ./service --chart ./charts/service/values.yaml ./charts/service/templates

# HTTP routes a Python service exposes (FastAPI decorators + include_router prefixes, across files and repositories),
# old vs new, against URL literals in any consumer — a TypeScript UI included
aftershock routes --lib ../api --old <ref> --new <ref> --consumer ../web-ui --ext .ts,.tsx

# a named-query registry (function signatures served over HTTP) vs the payload keys its consumers send
aftershock named-queries --lib ../data-service --src app/queries --old <ref> --new <ref> --consumer ../reporting --no-tests

# one pull request, every applicable lens, one comment, driven by a manifest of which repositories consume which
# library and which chart configures which service; printed by default, --post sends it, --only-breaks posts only on ❌
aftershock pr-check --estate ~/.config/aftershock/estate.json --repo my-lib --pr 123

Spend caps: --budget-usd per run (default $0.02; skipped spans are reported for review, never guessed) and AFTERSHOCK_MAX_SPEND_USD across runs (default $1), tracked in ~/.cache/aftershock/spend.jsonl.

What a report means

mark meaning
❌ verified consequence the code uses the changed API and the change breaks it — file:line and the finding
❓ for a person evidence exists but the receiver's type or the intent is unclear, or a cap stopped the call
✅ still valid the use was checked and still works after the change
not reached a look-alike: the same name on a different object or library

Production code is listed before tests; --no-tests drops tests. A new or changed settings default is reported as an advisory ⚠️ row with the charts that set the key — no signature changes, so only a person can judge it.

Proposed fixes, and whether they hold up

aftershock.fix proposes a fix for each broken file, most certain first: pass moved or keyword-only arguments by name (behaviour-identical by construction); import a removed object from the one module that now defines it (exact only if its definition is unchanged); or apply the edit the change itself made to its other callers. aftershock.fixcheck then climbs a ladder and reports the highest rung the fix reaches:

rung passes when
parses the patched file is valid Python
resolves every library module and name it uses exists at the new version — and, for a file of the library's own repository, nothing it imports is a dependency the change dropped
clears re-running the rules on the patched file finds no break of the change, and no new one
runs the patched module imports against the new library

A fix that deletes an argument, a call or a line is flagged as a behaviour change whatever it passes, and a fix that makes code outside the library import one of its _private names is flagged too. "Runs" means it imports, not that it does what the author intended. (Python API today; not yet a CLI command.)

Evidence (public repositories, reproducible)

Measured 2026-10 on full clones; method and every number in spike/CALIBRATION.md.

Precision on each library's own history — for every commit that changed a public API, does aftershock flag exactly the lines that commit had to change?

library verified true / verified recall
click 109 / 109 88%
pydantic 10 / 10 88%
pandas 11 / 11 100%
huggingface_hub 41 / 41 89%
Prefect 181 / 182 — the one is a real escape: a script imported a module a refactor removed, fixed 145 days later —
werkzeug 364 / 373 87%
flask 124 / 128 81%

Live pull requests. Every open, non-draft PR updated in the last 30 days that touches Python, at its latest commit, across Prefect, fastapi, pydantic, pydantic-settings, huggingface_hub, click and flask: 106 PRs, nothing posted. No verified break. The one false flag (fastapi #16236 removes APIRouter.app because Starlette's Router, an external base class, provides it) became a rule, as did every false positive found on the way.

Fixes. Hiding one file's edits from its commit and asking for a fix: on click, 11 of 13 files got a fix that holds up to runs. Few match the author's edit exactly — most are working alternatives — and every rejection inspected was right (for example, an import fix for a script that also used a dependency the change had removed).

Model calibration on httpx 0.28 and click 8.2: with the whole call statement as evidence the reach answer is bimodal (48/48 at 0.5, 50/50 on 14 adversarial cases — kwargs dicts, partial, wrappers, subclasses, aliases); a planted misleading comment never flipped a verdict, so comments are stripped from evidence. Thresholds: reach ≥ 0.7 / < 0.4, break ≥ 0.8.

Reproduce: spike/history_bench.py <repo> <package> --search src --since <date>, spike/pr_sweep.py --estate <manifest>, spike/fix_bench.py <repo> <package> --rows <history rows>, spike/head_scan.py <repo> <package>.

Known limits

  • Python only. The routes lens reads consumers in any language; the API lens reads Python.
  • Behaviour changes are invisible by design. A new default, a changed return value or a slower query changes no signature; settings defaults get an advisory row, nothing more.
  • Unknown receivers (x.method(...)) are matched by method name; when the consumer defines a same-named member itself, a "breaks" answer is capped to review. On very large PRs the review list can be long.
  • Members provided by an external base class cannot be checked (its source is not in the repository), so their removal is never verified — it goes to review.
  • A branch whose copy of a library is behind the trunk is API-compatible yet behaviourally stale; it is reported as a commits-behind note.

Layout

aftershock/contracts.py        griffe wrapper, Finding, re-export map, library module list, name bindings
aftershock/reach.py            AST candidates: imports and re-exports, receivers, aliases, scopes, ambiguity
aftershock/rules.py            the one deterministic router (CLI and benchmarks share it); inheritance and still-bound checks
aftershock/positional.py       signature rules: moved, newly required, kind-changed, removed-and-passed-positionally
aftershock/verify.py           optional model claims (reach, break) with calibrated thresholds; review on any failure
aftershock/fix.py              proposed fixes: keyword rewrite, moved import, analogy from the change's own edits
aftershock/fixcheck.py         the hold-up ladder: parses, resolves, clears, runs
aftershock/config_contract.py  env keys read (os.getenv, pydantic settings) vs set by a chart
aftershock/routes.py           HTTP route contract across files, repositories and languages
aftershock/named_queries.py    named-query registry signatures vs consumer payload keys
aftershock/prcheck.py          pr-check: one PR, every applicable lens, one comment
aftershock/explain.py          prose over verified consequences only (template / ollama / anthropic), with checks on the prose
aftershock/budget.py           per-run and cumulative spend caps
spike/                         benchmarks (history, PR pairs, fixes, still-broken-at-HEAD, PR sweep) and the calibration record
tests/                         rule-level tests, no network

Contributing

Contributions are welcome, and the most useful one is often a false positive turned into a test. Found a ❌ that does not break? Open a False positive issue. Want to add a rule, a lens or a benchmark on a new library? Start with CONTRIBUTING.md: setup takes a minute and the tests need no network.

License: MIT (see LICENSE).

Metadata

Release files for aftershock 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aftershock 0.1.0
File Size Uploaded Provenance Actions
aftershock-0.1.0.tar.gz 121.9 kB With provenance Details for aftershock-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for aftershock 0.1.0
File Interpreter ABI Platform Actions
aftershock-0.1.0-py3-none-any.whl Python 3 none any Details for aftershock-0.1.0-py3-none-any.whl

Total release size: 196.9 kB

Release files / aftershock-0.1.0.tar.gz

Download URL aftershock-0.1.0.tar.gz
Size 121.9 kB
Tags Source
SHA-256 checksum
How to use checksums
c2053a74ecd042037248e86c5a8d29d2876c06673ab79b04061266e809c4bca1
BLAKE2b-256 checksum
How to use checksums
922b514fdf87a3500aa9ea8fcc31f57daa9fba1f67cabb349749931b28fb9329
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / aftershock-0.1.0-py3-none-any.whl

Download URL aftershock-0.1.0-py3-none-any.whl
Size 75.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
192cc9df9831661a5c5227f05ef445b89faff060aca092bba142975ee5c1ad70
BLAKE2b-256 checksum
How to use checksums
63d5f9ad1cf18471e4b6547808d7bcb0c9e8820965b41ed5ff9762f25e4f3448
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Security for aftershock 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for aftershock-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page