Resolve the shape of the target into a copy-root and a mutate-scope.
Mutare copies one tree into the sandbox and mutates a set of source files in it.
For a plain Mix project those are the same directory. For an umbrella they
differ: the whole umbrella is copied (so in_umbrella sibling deps and the
shared deps//config/ keep resolving), while only a chosen subset of apps/*
gets metamutants.
resolve/2 turns the user-supplied target path plus the --app/--workspace
flags into:
copy_root— the directory materialised into the sandbox (the umbrella root, or the project root for a single app). This is therootthe rest of the pipeline (Mutare.Schema,Mutare.Sandbox,Mutare.Runner) treats as authoritative for every path operation; the struct duplicates it only so it is available to the entry points.mutate_scope— the apps whose sources are mutated, as%{app, dir}entries withdirrelative tocopy_root("apps/foo", or"."for a single app).apps— every app whose suite runs (all umbrella apps), which the per-app bootstrap injection and the dependency-graph scoping need.
Detection is static — Mutare runs outside the target's Mix, so the layout is
read from the filesystem (an apps/ dir plus an apps_path: in the root
mix.exs) rather than from Mix.Project.
Summary
Functions
Per mutate-scope app, the root-relative test/ dirs a broad (whole-suite) run
may be narrowed to: the app itself plus every app that (transitively) depends on
it. A mutant in app A can only be killed by a test that executes A's code, and a
sibling executes A's code only through a declared dependency — so this set is a
safe superset of A's possible killers; narrowing below it would risk a false
survivor, so we never do.
Root-relative directories that hold a Mix project file Mutare may rewrite in the sandbox.
The root-relative mix.exs path for a project_dirs/1 entry.
Resolve target (+ scope flags) into a t/0.
Is dir (an absolute path) the root of an umbrella project?
Types
Functions
@spec app_test_scopes(t(), Path.t(), %{required(atom()) => [atom()]}) :: %{ required(atom()) => [String.t()] }
Per mutate-scope app, the root-relative test/ dirs a broad (whole-suite) run
may be narrowed to: the app itself plus every app that (transitively) depends on
it. A mutant in app A can only be killed by a test that executes A's code, and a
sibling executes A's code only through a declared dependency — so this set is a
safe superset of A's possible killers; narrowing below it would risk a false
survivor, so we never do.
forward is the declared inter-app graph, %{app => [apps it depends on]},
as Mutare.Runner.AppGraph.read/2 reads it from Mix (nodes and deps outside the
umbrella are ignored). Declared, not runtime: a runtime: false sibling dep is
absent from the compiled .app's applications yet fully callable from the
dependent's tests. Only dirs that actually exist under sandbox are returned.
Returns %{} (⇒ no narrowing, run the whole umbrella) for a single app, or when
forward lacks an umbrella app — a missing node would hide that app's
dependents, so degrade safe rather than narrow on doubt.
Root-relative directories that hold a Mix project file Mutare may rewrite in the sandbox.
The root, plus each umbrella child. Mutare.Sandbox attempts to wrap exactly these
mix.exs files to force infer_signatures: false. Wrapping is not possible for an unreadable
file, source that Sourceror cannot faithfully round-trip, or a mix.exs defining no module
of its own. Mutare.Sandbox.Seed therefore realigns manifests
against the successfully applied wrappers — reported back by
CompilerOptions.project_source/1 — not against this list. Realigning an app that
compiles with inference on invents a cache-key mismatch and cold-compiles it for nothing.
The root-relative mix.exs path for a project_dirs/1 entry.
The one derivation both halves of the inference override share: Mutare.Sandbox builds the
files it wraps from it, and Mutare.Sandbox.Seed rebuilds the same key to check whether a
given app's wrapper was applied. Two spellings of this join would silently stop matching.
Resolve target (+ scope flags) into a t/0.
opts:
:apps— app-name strings to mutate (from--app);nil/[]means "all".:workspace—truemutates every app (from--workspace).
A single (non-umbrella) project resolves to copy_root: target with a lone
%{app: nil, dir: "."} scope, so the existing single-app pipeline is unchanged.
Is dir (an absolute path) the root of an umbrella project?