# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.2.1] - 2026-09-17

### Fixed

- **Mutant locations are correct again under Sourceror 1.12.3.** Sourceror used to
  size a bare `true`/`false`/`nil` one column too wide, and Mutare subtracted that
  phantom column to compensate. Sourceror 1.12.3 fixed the over-count upstream, so
  the subtraction began cutting a real character instead: a survivor whose range
  ends at one of those three rendered its diff a character short (`trim: tru`,
  closing paren left behind), and the JSON/SARIF reporters emitted the short
  `endColumn`. The compensation is gone, and `sourceror` is now floored at
  `~> 1.12.3` so there is one upstream behaviour rather than two. Mutation
  behaviour was never affected — a metamutant is built from the AST, never the
  range — and reports on Sourceror 1.12.2 and earlier were correct as they stood.

### Changed

- **Mutant runs execute far more of the target at its original speed.** A function
  holding two or more mutation sites now keeps its own source beside the instrumented
  code, and a mutant elsewhere runs that source. This previously reached only lifted
  functions of eight or more variants written in a narrow subset of Elixir; it now covers
  functions that stay in place and ordinary code — local bindings, sibling calls, closures,
  comprehensions, bitstrings and interpolation, structs, `raise`, `Logger` — about 89–97%
  of generated mutants in the projects measured, up from under 10%. Benchmark kernels that
  previously missed it run at 0.11–0.76× their former time while another mutant is active.
  The one metamutant compile costs about 8% more CPU and 17% more BEAM size.
- **Cheaper entry into every instrumented function.** The active file is stored and
  compared as an atom instead of a path string, and the id projection tells the compiler
  it holds an integer. `Mutare.Selector.put/1` and `active/0` are unchanged; manual
  selection in a generated sandbox still uses `MUTARE_MUTANT_NAMESPACE` and
  `MUTARE_ACTIVE_MUTANT`.

## [0.2.0] - 2026-09-14

### Changed

- **Breaking: score API moved from `Mutare.Report` to `Mutare.Score`:** `score/1`,
  `percent/1`, `passes_gate?/2`, `gate_failures/2`, `harness_error_rate/1`, and
  `harness_errors_exceed?/2`.
- **Breaking: `Mutare.Sandbox.prepare/3` now returns `{sandbox, materialized}`.**
  Seed-reuse outcomes and declined inference overrides are returned as data;
  the runner delivers their progress events.
- **Breaking: live-report text helpers moved to `Mutare.Report.Live.Lines`.**
  Callers of the rendering and time-formatting functions previously on
  `Mutare.Report.Live` should use the new module.
- **Breaking for manual sandbox selection: runtime mutant IDs are now local
  to each file.** Manual selection in a generated sandbox needs both
  `MUTARE_MUTANT_NAMESPACE` (the root-relative file) and `MUTARE_ACTIVE_MUTANT`
  (its local ID). Changing another file's candidate count no longer changes an
  otherwise unchanged metamutant, allowing retained builds to survive unrelated
  changes. Reports still use globally unique IDs; standalone transforms retain
  integer-only selection.

- **Focused runs and ignore directives produce smaller builds.** `--line`,
  `--since`, and `--max-mutants` now limit generated branches as well as execution,
  and ignore directives suppress branches before compilation. IDs, ignore
  diagnostics, and mutant-cap accounting are preserved. Files with no emitted
  mutants keep their original bytes, allowing their compiled modules to be reused.
- **Smaller metamutants and less processing overhead.** Clause mutations share
  code in eligible `case`, anonymous-function, `receive`, and `try/rescue`
  constructs, and exclusion guards compress consecutive IDs. Scanning and poison
  recovery avoid redundant parsing; coverage recording caches hits per file and
  writes a more compact dump.
- **Custom string-pattern mutations follow the built-in redundancy rule.** When
  one mutator offers both empty and non-empty string replacements for an exact
  pattern, Mutare drops the empty replacement.

### Added

- **Guard mutations in more clause positions:** `with` and `for` generators,
  `with` and `try` `else` clauses, `try` `catch` clauses, and `for … reduce:`
  bodies. These are supported inside functions where the runtime selector is
  bound; module-level and default-argument positions remain excluded.

### Fixed

- **Mutations activate before project evaluation, runtime configuration, and
  application startup.** Timeouts and owner-death watchers also start before
  target project code, containing mutations that hang during startup. Closures
  and workers created before test helpers load retain the correct selection and
  can record coverage when they run after recording begins; execution confined
  to startup is still not recorded as coverage.
- **Mutations that prevent startup count as kills.** Failures during project
  evaluation, runtime configuration, or `Application.start/2` use the startup
  retry budget before being scored as killed. Errors, exits, and throws retain
  the evidence needed for this classification even when deep stacktraces lose
  the project or configuration frames. Infrastructure failures remain harness
  errors.
- **An empty coverage result no longer manufactures survivors.** A successful
  probe that records no hits marks the selected mutants `:no_coverage`, including
  runs focused entirely on untested code. Missing or malformed capture data
  still falls back to running the suite.
- **Signature inference stays disabled in sandbox Mix projects on Elixir 1.20.**
  The override now reaches effective compiler options, including umbrella
  children and custom configuration paths, and overrides an explicit
  `infer_signatures: true` to prevent pathological metamutant compile times.
  Other compiler options are preserved. If a project cannot be safely rewritten,
  `--verbose` reports the file and reason, and its seeded compiler cache is left
  consistent with the options it actually uses.
- **Generated operators respect Mutare's semantics under restricted or replaced
  `Kernel` imports.** Selectors, guards, and coverage code no longer resolve
  operators through the target's imports. Coverage short-circuits also work on
  Elixir 1.21 development builds, where `:erlang.andalso` is guard-only.
- **Protocol implementations receive lifted mutations.** Functions in
  module-level `defimpl` blocks now receive guard, head-pattern, and clause
  mutations, with `:skip_lifting` resolving to the implementation module.
- **Immediately invoked anonymous functions are mutated.** Mutare now analyzes
  the callee in `callee.(args)`, including the guards, patterns, and bodies of
  `(fn … end).(args)`.
- **Binding conditions preserve custom and return-value mutations.** Custom
  `condition_replacements` callbacks now receive `if`/`unless` conditions whose
  bindings must escape into the body, even with `IfCondition` disabled. Rewriting
  those conditions also preserves per-branch return mutations and the exclusion
  of unit-returning tails.
- **Bitstring generators no longer produce invalid value mutations.** The
  generator wrapper in `for <<… <- binary>>` is treated as syntax, avoiding
  spurious compile-poison recovery.
- **Macro poison recovery associates each expansion with its own file.** Nested
  expansion stacks no longer cross-match macro names and unrelated call sites;
  findings without a corresponding mutant are discarded instead of crashing
  recovery.
- **Configuration rejects inconsistent extension and routing declarations.**
  Mutator modules cannot be registered as non-mutating extensions merely by
  omitting `@behaviour`; declarative routes reject the internal `{:hosted, hosts}`
  form; and `:partition_env` cannot overwrite `ERL_COMPILER_OPTIONS` or other
  environment variables managed by Mutare.
- **The sandbox ownership-marker writer no longer follows symlinks.**
- **Self-hosted fixture coverage no longer overwrites the outer probe's state or
  dump.**

### Security

- Updated locked Igniter and Mint dependencies to 0.8.4 and 1.10.0 respectively
  to address advisories reported by `mix hex.audit`.

## [0.1.2] - 2026-09-07

### Fixed

- **The kept sandbox preserves empty directories.** The incremental
  materialisation that the default kept sandbox uses mirrored files and
  symlinks only, so a directory with nothing in it never reached the sandbox —
  and a shallow git checkout under `deps/` keeps `.git/refs/heads` and
  `.git/refs/tags` empty. git then no longer recognised the checkout and Mix
  reported every git dependency as a lock mismatch before the metamutant could
  compile; a freshly generated Phoenix 1.8 app (`heroicons`, `daisyui`) could
  not run Mutare at all.
- **Rendering no longer consults the target project's `.formatter.exs`.**
  Sourceror reads `locals_without_parens` from it through `Mix.Tasks.Format` on
  every render — evaluating its `import_deps` and plugins inside Mutare's own
  process — and Mix could refuse the lookup mid-scan ("Unknown dependency
  `:ecto_sql` given to `:import_deps`"). Every render now pins the option
  (`Mutare.AST.render_opts/1`); a parsed call keeps the spelling its metadata
  records, and a node built without metadata renders with parentheses.
- **`mix igniter.install mutare` fetches the companion packages it adds.** The
  companions are chosen from the project's own deps at run time, and Igniter
  writes a dep added that way to `mix.exs` without fetching it — so the
  generated `.mutare.exs` named modules of packages that were never fetched or
  locked. The installer now applies the `mix.exs` change and runs `deps.get`
  before writing `.mutare.exs`.

## [0.1.1] - 2026-09-07

### Fixed

- **Unit-return classification exempts behaviour callbacks.** A function that is a
  callback of one of its module's declared behaviours (direct `@behaviour` or
  `use`-injected, read through the behaviour's `behaviour_info/1` when it is
  loadable), or whose first clause carries an `@impl` other than `@impl false`, is
  never classified unit-returning, however its body reads. Its caller is the
  behaviour's runtime, which the source never shows and which may treat a lone
  `:ok` as one contract outcome among several — `Oban.Worker.perform/1`'s `:ok`
  is one of six. 0.1.0 silenced the `:ok` return mutants of every such callback,
  including `mutare_oban`'s worker-return family.

## [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 are printed 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 surface** — `Mutare.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}`.

[Unreleased]: https://github.com/foxbenjaminfox/mutare/compare/v0.2.1...HEAD
[0.2.1]: https://github.com/foxbenjaminfox/mutare/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/foxbenjaminfox/mutare/compare/v0.1.2...v0.2.0
[0.1.2]: https://github.com/foxbenjaminfox/mutare/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/foxbenjaminfox/mutare/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/foxbenjaminfox/mutare/releases/tag/v0.1.0
