All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.1.0 - 2026-09-07
Initial release.
Added
- Compile-once metamutant. Source under
lib/is rewritten into a single program that embeds every mutant behind a:persistent_termruntime switch, compiled once; the suite then runs once per mutant by flippingMUTARE_ACTIVE_MUTANT— no per-mutant recompilation. - A broad built-in mutator set (all on by default): arithmetic/operator and
operand swaps, relational and logical swaps, strict-equality relaxation,
literals of every kind (integer/float/string/charlist/atom/sigil/regex/
bitstring/date-time), collection/string/map/keyword call rewrites, pattern and
clause restructurings, guard/default/call drops, and more. See
Mutare.Mutators. - Unit-return classification — a function (or anonymous function) whose every
return path is literally
:okornilreturns no data, so its tails draw no return-value constant and no:ok → :errorswap. Syntactic, and one-sided: it can miss a unit function, never silence a data-returning one. - Coverage-guided test selection — coverage is self-recorded by the metamutant
at runtime and keyed by mutant id; each mutant runs only the test cases that
cover it (
--per-filewidens that to the covering test files, for statefulasync: falsesuites;--fullruns the whole suite every time). Uncovered mutants are skipped and excluded from the score. - Parallel workers with per-mutant timeouts — mutants run
:workersat a time, each capped by a wall-clock deadline; a mutation that hangs halts itself and counts as a kill (no process-tree killing). - Compile-poison recovery — a mutant that won't compile is identified from the
compile error, dropped (reported as poisoned, excluded from the score), and
the build retried, for a bounded number of rounds; only a compile error that
can't be attributed to any mutant (or that outlasts the bound) aborts the run,
with a copy-pasteable
:skipsnippet. # mutare:ignoredirective — suppress a known-equivalent mutant per line, per span (-start/-end), or per file (-file), with an optional free-text reason and an optional[family]/[family:label]filter; ineffective directives are surfaced (--strict-ignoresescalates).- Reporters — human (default, survivor diffs + score), plus machine-readable
json(Stryker / mutation-testing-elements schema),html(interactive viewer), andsarif(GitHub code scanning) via--report FORMAT[:PATH](repeatable) or:reporters. - Live progress on stderr; the detailed report and score print to stdout.
- CI integration —
--since <ref>to scope to the lines changed against a git ref,--min-scoreto gate,--lineto target lines, and a kept sandbox (the default) so the compiled build carries across runs;--sandbox <path>pins it at a CI cache,--no-keep-sandboxopts back into a throwaway copy. - Umbrella-aware — target one app, several, or the whole workspace.
- Extension surface —
Mutare.Mutator(custom mutators), independentMutare.CallRoutingandMutare.UseExpansioncapabilities,:extensionsfor non-mutating integrations, declarative:call_routesconfiguration (user-tier treatments only; the adapter-grade treatments must come from a module implementingMutare.CallRouting), theMutare.ASTnode constructors that discharge Sourceror's emission invariants for plugins, and theMutare.Callscall-resolution readers (resolved_call_to/3,module_key/1) so plugins match calls without building core's key representation, and a declarative environment guard (required_modules/0, on mutators and extensions): the modules a DSL plugin routes are checked loadable once at startup, aborting withMutare.EnvironmentErrorinstead of silently registering routes against nothing on an external-source run. mix igniter.install mutareinstaller that detects frameworks and wires up the matching companion packages and.mutare.exs.- Call routes (
call_routes:/--skip-call Module.fun/arity) — leave a call alone::skipmakes a whole call an inert leaf (functions, macros, and the construct special forms alike —Kernel.if/2orcaseincluded; a piped receiver and the enclosing function's return-value mutants are unaffected),:rawleaves an argument as written,:interiormutates an argument's contents but never its own node, and a keyed refinement ([:expression, timeout: :raw]) reaches one option of a literal keyword argument. The forms Mutare analyzes structurally (if,case, the boolean operators, …) take:skiponly, and definitions (def,defmodule, …) and literal syntax ({},%{},=, …) take no route. Routes match qualified, aliased, imported, and piped forms, and an entry that matches no call in a full scan is warned about. - Argument marks (
argument_marks:) — extend the built-in timeout table (or any label a mutator declares) to your own functions, with the mutators' value-aware reaction:{MyApp.Http, :get, 2, [{:keyword, :recv_timeout}], :timeout}.