AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Your tests pass. Would they catch a bug? Mutineer changes your Ruby code to help you find missing tests. Start with one file.
Developer toolsRuby · JavaScriptGitHub @davidterenOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Mutineer - a clean-room mutation-testing tool for Ruby. Prism + stdlib only, zero runtime deps.
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗Find bugs your tests would miss. Mutineer changes your Ruby code and runs your Minitest or RSpec tests. Changes that still pass reveal possible test gaps. This is mutation testing.
Features
--framework.--rails boots once. With --daemon, each Minitest worker gets its own SQLite file, PostgreSQL, or MySQL 8 test database.--since origin/main checks changed lines; --baseline gates new survivors, and score drops on full runs. When tests change, run a full scan (--no-since).📖 davidteren.github.io/mutineer →: overview, operators, and usage.
Check your database and framework before adding a CI gate.
Mutineer requires Ruby 3.4 or later. In-process runs require Linux or macOS for forks.
Plain Ruby projects without databases support parallel runs.
In the Status column, supported means parallel runs work and serial only runs one mutant at a time.
| Ruby | Database | Framework | Parallelism | Status |
|---|---|---|---|---|
| 3.4 or later | SQLite file | Minitest | supported with --daemon | supported |
| 3.4 or later | SQLite file | RSpec | not supported | serial only |
| 3.4 or later | PostgreSQL | Minitest | supported with --daemon | supported |
| 3.4 or later | PostgreSQL | RSpec | not supported | serial only |
| 3.4 or later | MySQL 8 | Minitest | supported with --daemon | supported |
| 3.4 or later | MySQL 8 | RSpec | not supported | serial only |
| 3.4 or later | MariaDB | Minitest | not tested | not tested |
| 3.4 or later | MySQL 9 or later | Minitest | not tested | not tested |
| 3.4 or later | SQLite in memory | Minitest | not supported | serial only |
| 3.4 or later | SQLite in memory | RSpec | not supported | serial only |
| 3.4 or later | more than one database | Minitest | one worker | serial only |
| 3.4 or later | more than one database, Rails 7.1 or later, the extra ones in shared_databases | Minitest | supported with --daemon | supported, unless the test environment registers a pool for a shared database |
| 3.4 or later | more than one database | RSpec | not supported | serial only |
This matrix is for a Rails app on Ruby 3.4 or later. A plain Ruby project
with no database runs in parallel with either framework.
not supported means --daemon cannot run that cell; not tested means no CI
job runs it. SQLite file means a database file, not :memory:.
--rails without --daemon is serial on every row. --daemon is Minitest only.--daemon. MariaDB and MySQL 9 or later warn and run.--daemon isolates only the primary connection.
On Rails 7.1 or later, list the databases your tests never use in shared_databases, and the workers
run in parallel. CI tests that row on SQLite. See More than one database.killed, not errored, so the score can read too high
(#347).Mutineer sets PARALLEL_WORKERS=1, so a parallelize call in test_helper.rb starts no Rails workers during a mutant run.
See Rails parallel tests, Spring, Bootsnap, and Redis.
Older Ruby apps use --test-command; Mutineer still requires Ruby 3.4 or later.
This path uses one Mutineer worker and no coverage-based test selection by default; a wrapper can re-enable Rails test workers by setting PARALLEL_WORKERS.
| Subprocess result | Score |
|---|---|
| Exit 1, normally | killed |
| SimpleCov gate exit with a failing test | killed |
| Other exit code | error |
| Crash before a test fails | Can score error |
| Only a SimpleCov gate fails, with its gate message and a passing summary | survived |
See Apps on Ruby < 3.4 and release guarantees in STABILITY.md.
Mutation testing changes code to check whether your tests notice.
Each changed copy is a mutant, such as replacing >= with > or && with ||.
| Result | Meaning |
|---|---|
| Killed | A test failed and caught the change. |
| Survived | All tests passed. Inspect the change for a test gap. |
# lib/discount.rb
class Discount
def rate(qty)
return 0.1 if qty >= 10
0
end
end
Tests check quantities 1 and 50: 100% line coverage. Mutineer changes >= to >; both tests pass:
lib/discount.rb
Discount#rate (discount.rb:3)
Operator: comparison (>= -> >)
- return 0.1 if qty >= 10
+ return 0.1 if qty > 10
No test checks exactly 10. Testing 10 kills the mutant. Run the example, tests and report in Mutation testing, explained.
| Check | What it shows |
|---|---|
| Line coverage | Which lines ran |
| Mutation testing | Whether tests detect changes to those lines |
Tests without assertions add coverage but kill mutants only when changes cause errors. This gap is common in rushed tests and tests written by AI agents.
.mutineer/coverage.json caches the map; source changes rebuild it. Tests must pass alone and
together, including with a cached map. A failure stops the run.--rails.
Children run in parallel on Linux and macOS.
Each loads the changed file (reload) or replaces the changed method (redefine).
Only covering test files run: paired tests first, then fastest to slowest. In-process runs stop at the first failure unless --matrix requests every covering test.killed / (killed + survived). In coverage-guided runs, timeouts, errors, and uncovered lines count as neither; --test-command has no coverage, so a mutant no test reaches survives.| Alternative backend | Changes to step 4 |
|---|---|
--daemon | Boots Rails once in a persistent process; isolates each worker's test database |
--test-command | Runs tests in another Ruby subprocess; writes mutants to the real file, then restores it |
| At a glance | |
|---|---|
| Parser | Prism, with no runtime gem dependencies |
| Ruby | 3.4 or later; older apps use --test-command |
| Operators | 21 (5 by default, 16 opt-in) |
| Isolation | One fork per mutant on Linux and macOS |
| Report formats | human, JSON (versioned schema), HTML |
Plain Ruby:
gem install mutineer
mutineer run lib/discount.rb --test test/discount_test.rb
Rails: add to your Gemfile test group:
gem "mutineer", group: :test, require: false
Install, create .mutineer.yml, and check one model:
bundle install
RAILS_ENV=test bundle exec mutineer init --rails
RAILS_ENV=test bundle exec mutineer run app/models/order.rb --rails
Without --test, app/models/order.rb pairs with test/models/order_test.rb or spec/models/order_spec.rb.
See Quick start.
mutineer run <source...> [--test <test>...] [options]
mutineer init [--rails] [--force]
mutineer triage REPORT.json --reason TEXT (--id ID ... | --all)
mutineer migrate <source...> [--dry-run] [--operators LIST]
Full flags, defaults, and .mutineer.yml keys: CLI reference.
| Flag | Meaning |
|---|---|
--test FILE | Repeat per test file; omit to pair tests by convention |
--threshold FLOAT | Exit 1 when the score is below FLOAT, or when nothing could be scored and something broke, or more than one mutant produced no verdict and they exceed 10% of those attempted (default: 0 = off) |
--since REF | Mutate only lines changed since git REF, such as origin/main |
--baseline FILE | Exit 1 for new survivors, or score drops on full runs, against a prior JSON report from a full run |
--rails | Boot config/environment once; serial without --daemon |
--[no-]daemon | Enable or disable daemon; isolated worker databases, Minitest only |
--jobs N | Worker count; default: one per CPU |
--format human|json|html | Report format; default: human |
--dry-run | List mutants without running tests |
| Code | Meaning |
|---|---|
0 | Score ≥ threshold (or no gate) and no baseline regression. |
1 | Score below --threshold, OR nothing could be scored and something broke, or more than one mutant produced no verdict and they exceed 10% of those attempted, OR a --baseline regression, OR a runtime error, including a boot, source or require: file that raises while loading. |
2 | Usage / invalid-flag error, including a mistyped flag, a bad path (such as a missing --boot or require: file), or an unreadable baseline. |
List with mutineer --list-operators. Enable specific operators with --operators.
| Group | Operators |
|---|---|
| Tier 1 (default) | arithmetic, comparison, boolean_connector, boolean_literal, statement_removal |
| Tier 2 (opt-in) | return_nil, literal_mutation, condition_negation, string_literal, regex, collection_method, safe_navigation, range, negation_removal, chain_link, call_to_receiver, operand_removal, array_literal, condition_true, condition_false, operator_assignment |
| Guide | What it covers |
|---|---|
| Quick start | Install, run, fix a survivor |
| First run to steady state | One file through changed-code CI |
| Reading results | Reports, survivors, suppression, mutant ids, kill matrix |
| Rails setup | Rails, daemon, databases, Spring, Bootsnap, Ruby < 3.4 |
| Rails rollout | Four stages from model to CI gate |
| CI: changed code and baselines | --since, --baseline, Action inputs, timeouts |
| CLI reference | Flags, defaults, .mutineer.yml |
| Comparison | Mutineer, Mutant, other Ruby tools, fit-check prompt |
| AI agents and CI | Agent loop and CI recipes |
| JSON schema | Versioned report contract |
| STABILITY.md |
Use versioned JSON, structured exit codes, and runs scoped to changed lines. Mutant ids survive unrelated edits.
Mutant ids and equivalent-mutant suppression moved to Reading results. Apps on Ruby < 3.4 moved to Rails setup.
| Resource | Links and use |
|---|---|
| AI agents & CI recipes | rendered · source: agent loops, CI gates, avoiding equivalent-mutant loops |
| JSON schema reference | rendered · source: --format json schema and versioning |
| Ruby API (YARD) | https://davidteren.github.io/mutineer/api/: shipped classes |
| Agent skill | skills/mutineer/SKILL.md: installation, agent loop, exit codes |
Install the skill with either command:
gh skill install davidteren/mutineer mutineernpx skills add davidteren/mutineer --skill mutineerMutineer 1.8.0 and later bundle the skill and offer
Rails Hyperdrive companion installation.
For Rails apps bundling Mutineer, bin/rails hyperdrive:init installs it to .claude/skills/mutineer/SKILL.md.
MIT. See LICENSE.
| Release guarantees |