Quick start · The problem · What it mutates · CI and agents · FAQ
wouldcatch plants small bugs in only the lines you just changed, runs your tests after each one, and tells you which bugs got through. If your tests stay green when the code is wrong, you'll know the exact line.
Quick start
pipx install wouldcatch # Windows without pipx: py -m pip install wouldcatch
cd your-repo
wouldcatch
That's it. No config file, no plugin, zero dependencies. It reads your git diff, runs pytest, and cleans up after itself.
The problem
Two tests. Every line runs. Everything is green.
def discount(price, is_member): def test_member():
if is_member and price >= 100: assert discount(200, True) == 190
return price - 10
return price def test_guest():
assert discount(50, False) == 50
$ wouldcatch
MISSED shop.py:2 `and` -> `or`
MISSED shop.py:2 `>=` -> `>`
MISSED shop.py:2 `100` -> `101`
4/7 caught. 3 bug(s) your tests would not notice.
| Mutant | The bug it stands for | Why the tests missed it |
|---|---|---|
>= -> > |
A member spending exactly 100 gets no discount | No test checks 100 |
100 -> 101 |
Same boundary, shifted by one | No test checks 100 |
and -> or |
Every guest over 100 gets the discount | No test has a guest over 100 |
Add two asserts, discount(100, True) == 90 and discount(200, False) == 200, and it's 7/7 caught.
Coverage said 100%. Coverage counts which lines ran. It can't tell you whether any test would fail if those lines were wrong. That's the question wouldcatch answers.
Why only the diff
Mutation testing is the honest way to grade tests, but running it across a whole project takes long enough that almost nobody does. wouldcatch only touches the lines in your diff. A normal change gives 5 to 30 mutants, so it's done before you finish the commit message.
| Line coverage | Whole-project mutation testing | wouldcatch | |
|---|---|---|---|
| Tells you a test would fail on a bug | no | yes | yes |
| Time on a typical change | seconds | minutes to hours | seconds to minutes |
| Setup | plugin | config files | none |
| Output | a percentage | a report on everything | the exact lines to test |
What it mutates
One change at a time, only on lines in your diff. Test files are never mutated.
| Kind | Example | Catches tests that never check... |
|---|---|---|
| Comparison | a < b -> a <= b, == -> !=, in -> not in |
boundaries and equality |
| Arithmetic | + -> -, * -> /, += -> -= |
the actual computed value |
| Logic | and -> or, not x -> x |
each side of a condition |
| Constants | True -> False, 100 -> 101 |
exact values and limits |
| Returns | return total -> return None |
the return value at all |
| Calls | max(a, b) -> min(a, b) |
which of two values wins |
| Loops | break -> continue |
how a loop ends early |
| Branches | if cond: -> if not (cond): |
both paths of an if or while |
Want another one? New mutations are small PRs: see the good first issues.
Usage
| Command | What it does |
|---|---|
wouldcatch |
Uncommitted changes; on a clean feature branch, everything since it left main; otherwise the last commit |
wouldcatch --base main |
Everything on this branch since main |
wouldcatch --cmd "pytest tests/unit -x -q" |
Use your own test command |
wouldcatch --json |
Machine-readable output for scripts and agents |
wouldcatch --all |
Show caught mutants too |
wouldcatch --github |
Also print GitHub annotations (automatic inside GitHub Actions) |
wouldcatch --max 100 |
Try more mutants (default 50) |
Exit codes: 0 all caught · 1 something missed · 2 setup problem (not a git repo, tests already failing).
Skip a line on purpose with # wouldcatch: ignore.
CI, pre-commit and AI agents
GitHub Action: missed bugs show up on the exact lines of the pull request
on: pull_request
jobs:
wouldcatch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- run: pip install -e . pytest # your project and its test deps
- uses: Sahil-u07/wouldcatch@v0.2.1
Inputs (all optional): cmd (test command), base (defaults to the PR's base branch), max (default 50), fail-on-missed (default true).
pre-commit: check before every commit
With wouldcatch installed in your project environment:
- repo: https://github.com/Sahil-u07/wouldcatch
rev: v0.2.1
hooks:
- id: wouldcatch
AI coding agents: make them write tests that check something
Agents are good at writing tests that pass and bad at writing tests that would fail on a bug. The MISSED list gives them a concrete target.
Claude Code: copy skills/wouldcatch into .claude/skills/ in your project.
Any other agent: add this to AGENTS.md:
After changing Python code, run `wouldcatch --json`. For every mutant with
"status": "missed", add a test that fails for that change. Re-run until none are missed.
Never change the source code to make a mutant go away.
How it works
git diff ──> changed lines ──> mutants ──> run tests per mutant ──> restore file ──> report
git diffgives the Python lines you changed.- Each mutant is a tiny text edit at an exact position, so your formatting and comments stay untouched.
- Your test command runs once per mutant. A fresh bytecode cache each run means a stale
.pyccan never hide a mutant. - The file is restored byte for byte (CRLF included) after every run, even on Ctrl+C. If the process is killed, the next run restores it from a backup kept inside
.gitfirst.
FAQ
Isn't this what mutmut or pytestmut already do?
Partly, and they're good tools. mutmut and cosmic-ray mutate the whole project, which is a thorough audit. pytestmut also has a diff mode (--diff, --staged) and parallel workers, so if you need speed on a big suite, try it. Diff-based mutation testing isn't new in other languages either: Infection (PHP) and Mull (C/C++) both do it.
wouldcatch aims at one workflow: zero config, run it on the change in front of you, get exact lines back. That includes warnings on the pull request diff through the GitHub Action, and a --json list an AI agent can turn into tests.
Will it mess up my files?
It edits one file at a time and restores it right after, byte for byte. If the process dies mid-run, the next run restores the file from a backup kept inside .git before doing anything else.
My test suite is slow.
Each mutant is one run of your test command. Point --cmd at the relevant tests and add -x so pytest stops at the first failure.
A mutant can't be caught because the code really doesn't care.
That happens (a log interval, a cache size). Mark the line # wouldcatch: ignore.
JavaScript, TypeScript, Go?
Python today. The git side is language-neutral and JS/TS is next. Weigh in on issue #4.
Contributing
PRs welcome, especially new mutations. Start with a good first issue and read CONTRIBUTING.md.
If wouldcatch found a bug your tests missed, a star helps other people find it.
MIT · made by Sahil Lenka
Metadata
Release files for wouldcatch 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| wouldcatch-0.2.1.tar.gz | 21.0 kB | With provenance | Details for wouldcatch-0.2.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| wouldcatch-0.2.1-py3-none-any.whl | Python 3 | none | any | Details for wouldcatch-0.2.1-py3-none-any.whl |
Total release size: 35.7 kB
Release files / wouldcatch-0.2.1.tar.gz
| Download URL | wouldcatch-0.2.1.tar.gz |
|---|---|
| Size | 21.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
828038d2baed048052b4d2d4fd3e77bbc13f0e799c97f251984023a0689a2c83
|
|
BLAKE2b-256 checksum How to use checksums |
211437ca5d38f399a6ad2bc8b94303f5365da04bca4ec0d849d228eb7678cfba
|
| 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 12, 2026.
Transparency logRelease files / wouldcatch-0.2.1-py3-none-any.whl
| Download URL | wouldcatch-0.2.1-py3-none-any.whl |
|---|---|
| Size | 14.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9ba8c832c620649da31c83675df3394089aaf747c94303f09d6166d411777ff0
|
|
BLAKE2b-256 checksum How to use checksums |
dfa6ef9977c4fb540a7ff0e986fa6211c68a2750519eb274651feb360289ff30
|
| 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 12, 2026.
Transparency logSecurity for wouldcatch 0.2.1
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.
Attestation for wouldcatch-0.2.1.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 12, 2026.
Transparency logA 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.