# `mix mob.mutate`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mix/tasks/mob.mutate.ex#L1)

Mutation-test the lines this branch changed.

    mix mob.mutate                      # lines changed vs origin/master
    mix mob.mutate --file lib/foo.ex    # every mutable line in one file
    mix mob.mutate --base HEAD~3        # against a different base
    mix mob.mutate --json

A green suite says the tests ran, not that they guard anything. This changes
the production code one line at a time and reports the changes nothing
noticed.

## Options

  * `--file PATH`     — mutate this file instead of the branch diff; repeatable
  * `--base REF`      — diff base (default: `origin/master`)
  * `--test-command`  — how to run the suite (default: `mix test`). Narrow it
                        when the full suite is slow: every mutant runs it once.
  * `--max N`         — stop after N mutants
  * `--json`          — machine-readable result on stdout, progress on stderr

## Running it on this task

It reports most of `run/1` as surviving, and that is accurate rather than a
bug in either. This repo's convention is that a Mix task stays a thin
unstubbed I/O wrapper and the decisions get extracted and tested — so the
parts with a testable kernel (`source_file?/1`, `summary/4`,
`dirty_message/2`, and everything in `MobDev.Mutate`) are guarded, and the
orchestration that only sequences them is not. The tool measures what the
suite guards; it does not know what you decided not to guard.

## Exit status

Non-zero when any mutation survived, so this can gate a change the way a
failing test does.

## What it does to your working tree

It rewrites real source files in place and restores them from memory after
each mutant. **An interrupted run leaves the last mutant on disk** — Ctrl-C,
a closed terminal and an OOM kill all skip the restore, and no amount of
in-process care changes that.

So the run refuses to start unless the files it would touch are clean in git.
That way `git checkout -- <file>` is always a complete recovery, which is the
only guarantee that survives being killed outright. An earlier version of
this text claimed a snapshot restored the tree "even if the run is
interrupted"; there was no snapshot, the claim was false, and it talked the
reader out of the one habit that would have saved them.

# `dirty_message`

```elixir
@spec dirty_message(String.t(), non_neg_integer()) :: String.t() | nil
```

The refusal for a tree that is not safe to mutate, or `nil`.

Pure, and tested, because it is the only thing standing between an
interrupted run and someone's uncommitted work. A `git status` that fails is
as disqualifying as one that reports changes: if we cannot tell whether
recovery is possible, we must not proceed as though it is.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
