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_term runtime switch, compiled once; the suite then runs once per mutant by flipping MUTARE_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 :ok or nil returns no data, so its tails draw no return-value constant and no :ok → :error swap. 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-file widens that to the covering test files, for stateful async: false suites; --full runs the whole suite every time). Uncovered mutants are skipped and excluded from the score.
  • Parallel workers with per-mutant timeouts — mutants run :workers at 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 :skip snippet.
  • # mutare:ignore directive — 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-ignores escalates).
  • Reporters — human (default, survivor diffs + score), plus machine-readable json (Stryker / mutation-testing-elements schema), html (interactive viewer), and sarif (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-score to gate, --line to 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-sandbox opts back into a throwaway copy.
  • Umbrella-aware — target one app, several, or the whole workspace.
  • Extension surfaceMutare.Mutator (custom mutators), independent Mutare.CallRouting and Mutare.UseExpansion capabilities, :extensions for non-mutating integrations, declarative :call_routes configuration (user-tier treatments only; the adapter-grade treatments must come from a module implementing Mutare.CallRouting), the Mutare.AST node constructors that discharge Sourceror's emission invariants for plugins, and the Mutare.Calls call-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 with Mutare.EnvironmentError instead of silently registering routes against nothing on an external-source run.
  • mix igniter.install mutare installer 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: :skip makes a whole call an inert leaf (functions, macros, and the construct special forms alike — Kernel.if/2 or case included; a piped receiver and the enclosing function's return-value mutants are unaffected), :raw leaves an argument as written, :interior mutates 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 :skip only, 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}.