Mutation testing cheap enough to run while you are still writing the code.
Stage a change and ditto breaks it on purpose, one mutant at a time, then tells you which breaks your tests let through. It pays only for the lines you touched.
$ ditto staged --threshold 0.8┃ price/price.go — 4 mutants… the mutant list, the baseline and the survivors summary, elided …┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╍┅┃ 🧬 Mutant survived: price/price.go:10:16 → Comparison┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃ func IsFree(price int) bool {┃ - return price <= 0┃ + return price < 0┃ }┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╍┅… the diff header and two more survivors, elided …┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ • Total: 4 ┃┃ • Killed: 1 ┃┃ • Survived: 3 ┃┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨┃ ⨯ Score: 0.25 (minimum: 0.80) ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ditto: the mutation score is below the configured minimum of 0.80If the suite were already red, every mutant would look killed and the score would be perfect. This is what stands between that number and you.
A baseline run of your command on unmutated code opens every release. If it is red, the run stops there and shows you the command's own output.
Assertion, build failure, deadline or unknown. Only an assertion credits your suite, as the bar above shows.
A mutant that never became a program leaves the score entirely, so what you read is the share of runnable mutants your tests caught.
path:line:column → Operator (what it replaced), listed before any diff, so you can jump straight to it.
Every mutant reruns your test command, so ditto cuts how many times it runs. The same twelve mutants come back in 0.4 to 1.3 seconds instead of 10 or 11.
ditto stagedPay for the lines the change actually touched
It reads the changed ranges from the git index, so a function you changed costs only the operators that fire on its lines.
--gatedOne compilation carries every mutant in the file
Each file compiles once and all of its mutants run from that build. Where the two paths disagree, the gated one is right.
Each one edits the syntax tree the way a real mistake would: an operator flipped, a constant nudged, a loop cut short. Fourteen are on before you configure anything, from the command or from `Release`; the fifteenth is added by naming it.
+↔-*↔/%↔*+=-=*=/=%=&=|=^=<<=>>=&^=→=+=↔-=*=↔/=%=↔*=&↔|^→&&^→&<<↔>><↔<=>↔>=>↔<=<↔>===↔!=&&operand→true||operand→falsex→x-1.0x→x+1.0n→n-1n→n+1break↔continuecondition→falserange→earlybreakcontext.CancelCauseFunc(err)→(nil)A virus is any struct satisfying the `viruses.Virus` interface, so a mutation that only means something in your codebase is a type and a call to `WithViruses`. The `dittotesting` package carries the helpers the shipped fifteen are tested with, so yours gets the same treatment.
Install the command and walk through a real run, from a dry check to a passing score. Wiring it into a test binary you already run takes the library path below instead.
go install github.com/ Disble/ ditto/ cmd/ ditto@latestOne binary on your PATH, ready for ditto staged from a repository root.
ditto staged --dryIt lists the staged ranges it would judge and runs nothing, so you know the cost first.
$ ditto staged --dryditto: 1 staged file(s) under ~/projects/shop price/price.go: 150-251ditto staged --threshold 0.8Every survivor comes back with its line and the break it made, the same three shown at the top of the page, and a score under the minimum exits 1.
ditto staged --threshold 0.8Two assertions, for 0 and 1, kill the three survivors, and the run exits 0.
$ ditto staged --threshold 0.8… progress, elided …┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ • Total: 4 ┃┃ • Killed: 4 ┃┃ • Survived: 0 ┃┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨┃ ✓ Score: 1.00 (minimum: 0.80) ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ditto — mutation testing for Go ditto run [flags] mutate a repository and report what survived ditto staged [flags] mutate only what a staged change justifies ditto changed [flags] mutate only what a committed change justifies ditto version the module version this binary was built from (also -v, --version) Run `ditto run -h` for its flags.One import and a build tag put the same engine inside the suite you already run, with `WithTestCommand` to name a different test command.
go get github.com/ Disble/ dittogo test -v -tags=mutation//go:build mutation package main_test import ( "testing" "github.com/Disble/ditto") func TestMutation(t *testing.T) { ditto.Release(t)}ditto is a fork of gtramontina/ooze by Guilherme J. Tramontina. All the good ideas here are his, and the licence and copyright stay with him; it is MIT, as the original is.
The six rules ESLint loads from the owned config, shipped as CJS and ESM.
Second-level companion used alongside this engine.


A commit gate that owns the invocation and lets each tool’s exit code be the verdict.
The commit gate that consumes this engine.