# `Mutare.Runner.AppGraph`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.2.1/lib/mutare/runner/app_graph.ex#L1)

Read an umbrella's **declared** inter-app dependency graph out of the sandbox.

`Mutare.Project.app_test_scopes/3` narrows a broad (whole-umbrella) mutant run
to the mutant's owning app plus its transitive dependents, which needs the
forward graph `%{app => [sibling apps it depends on]}`. This module gets it from
Mix: one `mix eval` in the sandbox (no compile, no deps check) merges
`Mix.Project.deps_tree/0` from the umbrella root with sibling applications named
by each child's `application/0`. Every child's `deps/0` and `application/0` are
evaluated under `MIX_ENV=test` with the same settings as the test runs. Consequently,
a `runtime: false` sibling and one named only through `:extra_applications` or
explicit `:applications` all count as dependencies, while an `only:` that
excludes the test env does not. Why the graph is read this way rather than only
off the build: NOTES "Umbrella narrowing must follow the declared graph".

## Output contract

The evaluated snippet prints one `mutare-dep <app> <dep> <dep>…` line per node
of the tree and a closing `mutare-dep-end` line. `parse/2` keeps the lines
naming an umbrella app, drops deps that aren't umbrella apps (Hex packages),
matches names as strings against the *known* app list (never `String.to_atom/1`
on subprocess output), and is `:error` unless every umbrella app was reported
and the end marker arrived — a missing node would silently lose that app's
edges, so any incompleteness means "unknown graph" and the caller runs the
whole umbrella.

# `forward`

```elixir
@type forward() :: %{required(atom()) =&gt; [atom()]}
```

The declared forward graph over umbrella apps: `%{app => [apps it depends on]}`.

# `parse`

```elixir
@spec parse(String.t(), [atom()]) :: {:ok, forward()} | :error
```

Decode `mix eval` output (see the module doc's output contract) into the forward
graph over `names`, the known umbrella apps.

# `read`

```elixir
@spec read(Mutare.Project.t(), Path.t()) :: {:ok, forward()} | :error
```

The declared graph of `project`'s umbrella apps, read from the sandbox.

A single (non-umbrella) project is `{:ok, %{}}` without running anything. For an
umbrella, a failed `mix eval` or an incomplete tree (see `parse/2`) is `:error`
— logged, since the fallback (every broad run covers the whole umbrella) is
safe but slow.

---

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