⚑ Powered by tree-sitter

Mutation testing
for every language.

A single binary that mutates your source code and runs your tests. If tests pass β€” your coverage has gaps. No plugins, no config files, no dependencies.

muti animation
$ docker pull ghcr.io/fibegg/muti:latest

Built different.

🌍

Language-agnostic

Ruby, Go, Python, JavaScript, TypeScript out of the box. Uses tree-sitter AST parsing β€” no language-specific plugins needed.

🧬

14 mutation operators

Boolean swaps, equality negation, arithmetic flips, guard clause removal, error handler deletion, and more. Real mutations, not toy examples.

⚑

In-place mutation

Mutates files directly in your repo, runs your test suite, then resets via git checkout. Fast, simple, no overhead.

πŸ“¦

Single binary

One binary, zero runtime dependencies (except git). Download, run, done. Also available as a Docker image.

πŸ”Œ

Tool pipeline

Every mutation is emitted as JSON. Pipe it to echo, curl, a webhook, a file β€” integrate with anything.

♾️

Continuous mode

Run forever with --forever for ongoing mutation coverage in CI or background. Randomized mutations each round.

Mutate β†’ Probe β†’ Report

1

Mutate

muti parses your source with tree-sitter and applies random AST mutations (swap booleans, negate conditions, etc.)

2

Probe

Your test suite β€” the probe command β€” runs against the mutated code. Any shell command works.

3

Verdict

Tests fail? Mutation killed βœ“ β€” good coverage. Tests pass? Mutation survived β€” coverage gap found.

4

Report

Results stream as JSON to your configured tool β€” stdout, a webhook, a file, or any command.

Three things to know.

The Probe --

Everything after -- is your probe command β€” typically your test suite. If the probe exits non-zero, the mutation is killed (your tests caught it). If it exits zero, the mutation survived.

# The probe is your test suite
muti --dirs app -- bundle exec rspec --fail-fast
muti --dirs src -- pytest -x
muti --dirs src -- npm test

The Tool --tool

Each mutation result is serialized as JSON and piped to the tool's stdin. Default is echo (prints to stdout). Use it to send results anywhere.

# Print to terminal (default)
muti --tool echo -- make test

# Save to file
muti --tool 'tee -a mutations.jsonl' -- make test

# Send to a webhook
muti --tool 'curl -s -X POST ...' -- make test

Rounds & Mutations --rounds

Each round applies one or more mutations, runs the probe, then resets. Control the batch size with --mutations (fixed) or --mru (random up to N).

# 20 rounds, 3 mutations each
muti --rounds 20 --mutations 3 -- make test

# Random 1-7 mutations per round, forever
muti --forever --mru 7 -- make test

# 4 parallel workers
muti --parallel 4 --rounds 100 -- make test

Works with your stack.

Ruby / Rails

Point at your app directory and use RSpec or Minitest as the probe.

# Mutate app/ directory, use RSpec
muti --dirs app -- bundle exec rspec --fail-fast

# Mutate specific directories
muti --dirs app/models,app/services -- bundle exec rspec --fail-fast

# Run forever in CI with random 1-10 mutations
muti --dirs app --forever --mru 10 -- bundle exec rspec --fail-fast

# With Minitest
muti --dirs app -- bundle exec rails test

Python

Works with pytest, unittest, or any test runner.

# Mutate src/ directory, use pytest
muti --dirs src -- pytest -x --tb=short

# Django project
muti --dirs myapp -- python manage.py test --failfast

# Only .py files in a mixed-language project
muti --dirs src --extensions py -- pytest -x

# With coverage threshold check
muti --dirs src -- pytest -x --cov=src --cov-fail-under=80

JavaScript / TypeScript

Supports .js, .jsx, .mjs, .cjs, .ts, .tsx files. Use any test runner.

# Mutate src/ directory, use Jest
muti --dirs src -- npx jest --bail

# Vitest
muti --dirs src -- npx vitest run

# TypeScript only
muti --dirs src --extensions ts,tsx -- npm test

# Mocha
muti --dirs lib -- npx mocha --bail

Go

Go test is the natural probe. Use -count=1 to prevent caching.

# Mutate current directory
muti -- go test -count=1 ./...

# Mutate specific packages
muti --dirs internal -- go test -count=1 ./...

Real mutations, real coverage gaps.

OperatorWhat it does
swap_booleantrue ↔ false
negate_equality== ↔ !=
swap_logical&& ↔ ||, and ↔ or
flip_conditionalSwap if/else branches
swap_comparison> ↔ <, >= ↔ <=
swap_arithmetic+ ↔ -, * ↔ /
swap_integer0 ↔ 1
empty_stringReplace string literal with ""
null_returnreturn expr β†’ return nil
inject_early_returnInsert return nil at function start
remove_statementRemove a random statement from a function body
remove_guard_clauseRemove early-return guard clauses
remove_error_handlerRemove rescue / except / catch blocks
replace_arg_with_nullReplace a method argument with nil

Install in seconds.

πŸ“¦ Binary download

Pre-built for Linux and macOS (amd64 + arm64)

curl -sL https://github.com/fibegg/muti/releases/latest/download/muti_*_linux_amd64.tar.gz | tar xz
sudo mv muti /usr/local/bin/

🐳 Docker

Multi-arch image (linux/amd64, linux/arm64)

docker pull ghcr.io/fibegg/muti:latest

docker run --rm -v "$(pwd):/workspace" \
  ghcr.io/fibegg/muti:latest \
  --dirs src -- make test

πŸ”¨ From source

Requires Go 1.23+, git, and a C compiler

go install github.com/fibegg/muti/cmd/muti@latest

# or clone and build
git clone https://github.com/fibegg/muti.git
cd muti && make build