Reach

Program dependence graph and release-safety toolkit for Elixir, Erlang, Gleam, JavaScript, and TypeScript.

Reach builds a graph of what depends on what in your code: control flow, call graph, data flow, effects, and OTP/process relationships. Use it to inspect risky functions, trace values, validate architecture policy, and generate interactive HTML reports.

Elixir 1.18+ / OTP 27+.

Installation

def deps do
  [
    {:reach, "~> 2.0", only: [:dev, :test], runtime: false}
  ]
end

Optional dependencies enable richer output:

{:jason, "~> 1.0"},       # JSON output
{:boxart, "~> 0.3.3"},    # terminal graphs
{:makeup, "~> 1.0"},
{:makeup_elixir, "~> 1.0"},
{:makeup_js, "~> 0.1"}

Quickstart

Generate an interactive report:

mix reach

Map the project:

mix reach.map
mix reach.map --modules
mix reach.map --coupling
mix reach.map --hotspots

Inspect a target:

mix reach.inspect MyApp.Accounts.create_user/1 --context
mix reach.inspect lib/my_app/accounts.ex:42 --impact
mix reach.inspect MyApp.Accounts.create_user/1 --why MyApp.Repo

Trace data:

mix reach.trace --from conn.params --to Repo
mix reach.trace --variable changeset --in MyApp.Accounts.create_user/1

Run release checks:

mix reach.check --arch
mix reach.check --changed --base main
mix reach.check --candidates

Inspect OTP/process risks:

mix reach.otp
mix reach.otp --concurrency

Canonical CLI

Reach 2.x uses five canonical analysis tasks plus the HTML report task.

Command Purpose
mix reach Interactive HTML report
mix reach.map Project map: modules, coupling, hotspots, effects, depth, data flow
mix reach.inspect TARGET Target-local deps, impact, graph, context, data, candidates, why paths
mix reach.trace Data-flow, taint, and slicing workflows
mix reach.check CI/release checks: architecture, changed code, dead code, smells, candidates
mix reach.otp OTP/process analysis: behaviours, state machines, supervision, concurrency, coupling

Use --format json for automation. Canonical commands emit pure JSON envelopes with stable command names.

Older task names were removed in Reach 2.0 and fail fast with migration guidance. See the Canonical CLI guide.

Configuration

Reach reads .reach.exs for architecture and change-safety policy:

[
  layers: [
    web: "MyAppWeb.*",
    domain: "MyApp.*",
    data: ["MyApp.Repo", "MyApp.Schemas.*"]
  ],
  deps: [
    forbidden: [
      {:domain, :web},
      {:data, :web}
    ]
  ],
  source: [
    forbidden_modules: ["MyApp.Legacy.*"],
    forbidden_files: ["lib/my_app/legacy/**"]
  ],
  calls: [
    forbidden: [
      {"MyApp.Domain.*", ["IO.puts", "Jason.encode!"]}
    ]
  ],
  tests: [
    hints: [
      {"lib/my_app/accounts/**", ["test/my_app/accounts_test.exs"]}
    ]
  ]
]

Start from examples/reach.exs. See the configuration guide for the full reference and narrative examples.

Library API

Reach can also analyze snippets, files, and source directories directly:

graph = Reach.string_to_graph!("""
def run(input) do
  command = String.trim(input)
  System.cmd("sh", ["-c", command])
end
""")

[cmd_call] = Reach.nodes(graph, type: :call, module: System, function: :cmd)
Reach.backward_slice(graph, cmd_call.id)

Common queries:

Reach.backward_slice(graph, node_id)
Reach.forward_slice(graph, node_id)
Reach.taint_analysis(graph, sources: [function: :params], sinks: [module: System, function: :cmd])
Reach.independent?(graph, node_a.id, node_b.id)
Reach.data_flows?(graph, source_id, sink_id)

Documentation

HexDocs guides are organized by workflow:

Validation

Reach itself is validated with:

mix compile --force --warnings-as-errors
mix ci
/tmp/reach_validate_canonical_full.sh
mix docs
mix hex.build

mix ci includes formatting, JS checks, Credo/ExSlop, ExDNA duplication checks, architecture policy, Dialyzer, and tests.

Acknowledgements

Some structural smell patterns were informed by public Credence rules. Reach implements them over its own IR/project model and keeps them advisory.

License

MIT. See LICENSE.