# Mutator catalogue

Mutalisk mutates your source, then runs your tests against each mutated
version. A mutant that your tests catch is **killed**; one they don't is
**survived** — a gap in your test suite.

Mutators are split into two tiers. **Default-on** mutators run with a bare
`MIX_ENV=test mix mut`. **Opt-in** mutators run only when you ask for them; they are either
noisier (more equivalent/false-positive mutants) or niche.

> **Targets and mutator names are independent gates.** `--mutators` picks which
> mutators to consider; `--enable` replaces the enabled target families. If a mutator's target
> is not enabled by default, pass both, for example
> `--enable module_attribute --mutators attribute_literal`. If the target is
> already enabled by default (`dispatch`, `guard`, `env_walker`,
> `pattern_shape`), naming the mutator is enough. Enabling a target without
> naming a mutator runs the default selectable mutator set unlocked by that
> target; a few experimental mutators still require `--mutators` explicitly.

## Default-on

These focus on common faults while limiting equivalent or invalid mutants.

| Mutator | What it does |
|---|---|
| `Arithmetic` | swaps `+ - * /` (e.g. `a + b` → `a - b`) |
| `ComparisonBoundary` | off-by-one on comparisons (`<` → `<=`, `>` → `>=`, …) |
| `ComparisonNegation` | negates comparisons (`==` → `!=`, `<` → `>=`, …) |
| `Boolean` | swaps boolean operators (`and` ↔ `or`, `&&` ↔ `\|\|`) |
| `UnaryNot` | drops `!` / `not` on boolean expressions |
| `GuardComparisonBoundary` | comparison-boundary, inside `when` guards |
| `GuardComparisonNegation` | comparison-negation, inside `when` guards |
| `GuardTypeTest` | swaps guard type tests (`is_integer` → `is_float`, …) |
| `AtomLiteral` | rotates known atoms (`:ok` ↔ `:error`, `:lt`/`:gt`/`:eq`) |
| `IntegerLiteral` | integer-literal change in pattern position (`1` → `0`) |
| `ConcatOperator` | `++` → `--` on list concatenation |
| `Pin` | unpins a pin in a pattern (`^x` → `x`) |
| `FunctionReplace` | closed-allowlist stdlib swaps (`Enum.min`↔`max`, `filter`↔`reject`, `List.first`↔`last`, `String.starts_with?`↔`ends_with?`, …) |

## Opt-in

Reach for these when you want a deeper check and can tolerate more
equivalent/noise mutants. Enable a whole non-default target with
`--enable <target>`, or run just one mutator with `--mutators <name>` plus
`--enable <target>` when that target is not enabled by default. Names are
comma-separated and use the snake_case module name, e.g.
`negate_conditional`.

| Mutator | Target | What it does |
|---|---|---|
| `AttributeLiteral` | `module_attribute` | mutate module-attribute literal values |
| `IntegerLiteral` (body) / `BooleanLiteral` | `body_literal` | integer/boolean literals in function bodies |
| `AtomLiteral` / `BooleanLiteral` / `StringLiteral` / `NilLiteral` (pattern) | `pattern_literal` | opt-in pattern-position literal mutations; `IntegerLiteral` in patterns is already default-on |
| `StringLiteral` | `env_walker` | `""` / `"x"` string-literal swaps |
| `FloatLiteral` | `env_walker` | float-literal change |
| `NilLiteral` | `env_walker` | `nil`-literal swaps |
| `CollectionEmpty` | `env_walker` | empty a list / 2-tuple / map / n-tuple (never a struct) |
| `VariableReplace` | `variable` | swap a variable reference for another in scope |
| `VariableToLiteral` | `variable` | replace a typed variable reference with a boundary literal; requires explicit `--mutators variable_to_literal` |
| `BitwiseOperator` | `dispatch` | bitwise op swaps (`band`/`bor`/`bsl`/…) |
| `Membership` | `dispatch` | `in` → `not in` |
| `NegateConditional` | `conditional` | `if`/`unless` condition: negate (`!cond`) / force true / force false |
| `StatementDelete` | `statement_delete` | delete a non-last statement from a function body |
| `ClauseDelete` | `clause_delete` | delete a clause from `case` / `cond` / `with` / `receive` / `try` |
| `GuardBoolean` | `guard_boolean` | `and` ↔ `or`, drop `not`, inside `when` guards |
| `PipelineDropStage` | `pipeline_drop` | drop a middle stage from a `\|>` chain |
| `MapUpdateDrop` | `map_update_drop` | `%{m \| k: v}` → `m` (drop the update) |
| `ReceiveTimeout` | `receive_timeout` | mutate `receive … after t` (t → 0 / `:infinity` / drop) |

Examples:

    # run the body-literal target only
    MIX_ENV=test mix mut --enable body_literal

    # run only NegateConditional + ClauseDelete
    MIX_ENV=test mix mut --enable conditional,clause_delete \
      --mutators negate_conditional,clause_delete

## Why some mutators are opt-in

Literal, deletion, and structural mutations can produce more equivalent mutants:
changes that no test can distinguish because the program behaves the same.
Enable them when their extra depth is useful, then assess survivors against the
intended behaviour of your code.
