Hex.pm Docs License: MIT

AI coding agents waste context window reading 2000-line files, then fumble multi-edit refactors across a codebase. Adze fixes both problems.

Adze parses Elixir source as AST, returns a structural skeleton with exact line ranges (~50 tokens instead of the whole file), and lets the agent read only the bytes it needs. When it's time to refactor, the same AST machinery performs safe mechanical transforms — extract, rename, reorder — that would be tedious and error-prone as multi-edits.

Built on Sourceror and Igniter.

Getting Started

mix igniter.install adze

This adds the dependency and sets up skill files so your agent knows to reach for mix adze instead of hand-editing:

PlatformSkill pathConfig patched
Claude Code.claude/skills/adze/SKILL.mdCLAUDE.md
Codex / Pi / OpenCode.agents/skills/adze/SKILL.mdAGENTS.md

Target a single platform with --claude or --codex.

Manual install

Add adze as a dev dependency:

def deps do
  [
    {:adze, "~> 0.1.0", only: :dev}
  ]
end

Then mix deps.get.

Adze runs as a Mix task inside your project. It reads Mix.Project.config() (formatter opts, elixirc_paths) so it must run inside a loaded Mix project — there's no standalone binary.

Setting up agent integration manually

After fetching deps, copy the skill file and add a reference to your agent config:

Claude Code:

mkdir -p .claude/skills/adze
cp deps/adze/skill.md .claude/skills/adze/SKILL.md

Codex / Pi / OpenCode:

mkdir -p .agents/skills/adze
cp deps/adze/skill.md .agents/skills/adze/SKILL.md

Then add the following to your CLAUDE.md or AGENTS.md (create it if it doesn't exist):

## adze — structural Elixir refactoring

For Elixir codebase exploration: ALWAYS run `mix adze ls --file PATH` before
reading `.ex` files or spawning exploration agents. Measured: ~150x more
token-efficient than reading full files (maps a 2000-line module in ~50 tokens
with exact line ranges, vs reading the whole file). Returns in milliseconds.
Use the outline to identify the line range you need, then Read only that range.

For structural edits (rename, extract, move, privatize): ALWAYS use `mix adze`
instead of multi-edit. The tool does AST-aware rewrites across the entire project
in one pass — no missed call sites, no broken aliases, no formatting drift.

Key commands:
- `mix adze ls --file PATH` — structural outline with line ranges (~50 tokens per file)
- `mix adze find-callers --target Mod.fun/N` — project-wide caller search (resolves aliases, pipes, captures)
- `mix adze rename! --from Old --to New` — rename a module across the entire project
- `mix adze extract! --file PATH --definition fun/N --module New.Mod` — extract function + private closure into new module
- `mix adze mv! --file PATH --definition fun/N --before other/M` — reorder defs within a module
- `mix adze extract-private! --file PATH --definition fun/N` — flip def → defp after verifying no external callers

Always dry-run first (no `!`), then apply with `!`. Run `mix compile --warnings-as-errors` after writes.

Full reference (flags, caveats, workflows): see the adze skill file in this project's skills directory.

usage_rules (optional, richer context)

If you use usage_rules for your AGENTS.md:

mix usage_rules.sync AGENTS.md adze

This pulls the full usage-rules reference (error shapes, API details, conventions) into your rules file for any agent that reads it.

When to Reach for Adze

You're about to…Use this instead
Read an Elixir file > 200 linesmix adze ls --file PATH first, then read only the line range you need
Multi-edit a defmodule rename across the projectmix adze rename! --from Old --to New
Copy a function + its private helpers into a new module by handmix adze extract! --file ... --definition fun/N --module New.Module
Reorder defs inside a module with multi-editmix adze mv! --file ... --definition fun/N --before other/M
Grep for callers of Mod.fun across the projectmix adze find-callers --target Mod.fun/N
Convert a def to defp after eyeballing call sitesmix adze extract-private! --file ... --definition fun/N
Eyeball a module's alias/import/use clausesmix adze aliases --file PATH

If none of these match, fall back to standard read/edit/grep.

Quick Start

# Map a file's structure (agent reads ~50 tokens instead of the whole file)
mix adze ls --file lib/my_app/accounts.ex

# Find all callers of a function across the project
mix adze find-callers --target MyApp.Accounts.register/1

# Extract a function + its private helpers into a new module
mix adze extract! --file lib/my_app/accounts.ex --definition register/1 --module MyApp.Registration

Command Reference

Read-only (safe, never writes)

CommandDescription
mix adze ls --file PATHStructural outline with line ranges
mix adze deps --file PATHIntra-module call graph (caller → callees)
mix adze ls-deps --file PATH --definition fun/NTransitive dependency tree from one function
mix adze ls-extract --file PATH --definition fun/NPrivate closure that would move with a function
mix adze aliases --file PATHAll alias/import/require/use per module
mix adze find-callers --target Mod.fun/NProject-wide callers (resolves aliases, pipes, captures)

Write (refactoring)

Every write op has a dry-run variant (no !, prints a diff) and a bang variant (!, writes to disk). Always preview first.

CommandDescription
mix adze mv! --file PATH --definition fun/N --before other/MReorder a definition within a module
mix adze extract! --file PATH --definition fun/N --module New.ModExtract function + closure into a new module
mix adze rename! --from Old.Mod --to New.ModRename a module across the entire project
mix adze extract-private! --file PATH --definition fun/NFlip defdefp when no external callers exist
Example output: mix adze ls ``` $ mix adze ls --file lib/sample.ex # lib/sample.ex defmodule Sample L1–40 @moduledoc L2–4 use GenServer L6 alias Sample.Inner L7 import Enum L8 @behaviour "GenServer" L11 defstruct [:id, :name, :count] L14 @spec :greet L16 greet/1 when … L17–19 greet/1 L21 p internal/1 L23 m debug/1 L25–29 g is_pos/1 when … L31 d child_call/1 L33 defmodule Inner L35–39 hello/0 L37 p helper/1 L38 ``` Legend: `p` = `defp`, `m` = `defmacro`, `g` = `defguard`, `d` = `defdelegate`.

Configuration

Custom attachable attributes

Some libraries introduce attributes that semantically belong to the next def (e.g. Oban's @job, Decorator's @decorate, Absinthe's @desc). Tell adze to treat them as part of the definition group:

# config/config.exs
config :adze, include_attrs: [:job, :decorate, :desc]

Output format

Every command accepts --format json for programmatic consumption. Text (default) is optimized for humans and LLMs reading inline.

How It Works

Elixir is homoiconic — source code is data. Sourceror gives us a zipper over the AST with formatting and comments preserved. Every structural operation is a tree walk, not string manipulation.

Single-file analysis (outline, deps, aliases) uses raw Sourceror. Project-wide operations (rename, extract caller rewriting, find-callers) use Igniter for safe multi-file rewrites with formatting preservation.

Limitations

find-callers (and by extension extract-private!) resolves qualified calls, pipe variants, and &Mod.fun/n captures — including alias resolution. It won't find:

  • Unqualified calls via import
  • Dynamic dispatch (apply/3, Kernel.apply/3)
  • String-literal module references ("Elixir.MyApp.Foo")

When in doubt, run mix compile --warnings-as-errors after any write op. The compiler catches what adze can't.

Documentation

Full API documentation is available on HexDocs.

Prior Art & Acknowledgments

Adze exists because of the tools it builds on and the project that showed the way:

  • clj-surgeon by Gene Kim (@realgenekim) — the direct inspiration. Gene watched Claude Code spend 45 minutes refactoring a 5,000-line Clojure file and asked: "What would the ideal tool be to help you manipulate beautiful homoiconic EDN files?" The answer was 13 ops in ~1,500 lines of Clojure, built in one session. Adze is the Elixir port of that idea: same philosophy (outline-first exploration, AST-aware refactoring, the tool does bookkeeping while the AI decides what to do), adapted for Elixir's ecosystem.

  • Sourceror by Dorgan (@doorgan) — the foundation. Every single-file operation in adze is a tree walk on the zipper that Sourceror provides. Comments and formatting are preserved because Sourceror tracks them in AST metadata. Without it, adze would need a custom parser.

  • Igniter by Zach Daniel (@zachdaniel) — the project-wide rewrite engine. Cross-file operations (rename, extract caller rewriting, find-callers) use Igniter for safe multi-file rewrites with formatter preservation. The installer task uses Igniter's code generation framework.

The guiding principle, borrowed directly from clj-surgeon:

The tool does the mechanical work. The AI decides what to do. The compiler catches mistakes. The AI fixes what the compiler reports.

License

MIT