# `Adze.Extract`
[🔗](https://github.com/matthewlehner/adze/blob/v0.1.0/lib/adze/extract.ex#L1)

`extract` — cut a definition (plus its private closure) out of a module
into a brand-new module file.

Builds on `Adze.LsDeps.ls_extract/3` for the closure and on
`Adze.Definition` for the logical-definition groups. Produces:

  * a new target file containing `defmodule TargetModule do ... end`
    with the closure definitions in original source order and a
    filtered subset of the source module's `alias`/`use`/`import`/
    `require` directives;
  * a modified source where the cut definitions are gone and an
    `alias TargetModule` line is inserted near the top of the source
    module's body.

## Args

    Adze.Extract.extract(source,
      definition: "bar/2",
      module: "MyApp.Bar",
      from_module: "MyApp.Source"   # optional disambiguator
    )

## Output

    {:ok, %{
      target_module: "MyApp.Bar",
      target_path: "lib/my_app/bar.ex",
      target_content: "...",
      new_source: "...",
      source_diff: "..."
    }}

## Behaviour notes (settled for v1)

  * **Target path is derived from `--module`** via `Macro.underscore/1`
    and prefixed with `lib/`. Override with `path:` or `mix_root:` for
    tests / non-standard projects.
  * **Target file must not exist.** Existing file → `{:error,
    {:target_exists, path}}`. Append-into-existing is deliberately
    deferred — surface the collision to the AI.
  * **Multi-module source files** require `from_module:` to
    disambiguate when the named def exists in more than one sibling
    `defmodule`.
  * **Directive policy:**
      * `alias` is mechanically filtered — copied only when the
        binding (last segment or `:as` name) is referenced in the
        closure AST.
      * `use` / `import` / `require` are **never** copied to the
        target. The tool can't know without macro expansion which
        (if any) the closure depends on, so the principled
        mechanical answer is to drop them. The compiler is louder
        when we're under-permissive (missing macro → compile error)
        than when we're over-permissive (extra `import` → silent),
        so the failure mode favors dropping. Every dropped
        directive comes back in `dropped_directives` as
        `%{kind:, line:, text:}` so the AI driver can decide which
        (if any) to add back to the target.
  * **Cross-file caller updating** is out of scope for this session.
    External callers of the moved public def will need updating — the
    next compile run surfaces them. `find-callers` (Session 7) will
    make this proactive.

## `extract!` writes both files

    Adze.Extract.extract!("lib/source.ex",
      definition: "bar/2",
      module: "MyApp.Bar"
    )

Returns the same shape as `extract/2`, plus the side effects of
writing `lib/my_app/bar.ex` (new) and rewriting `lib/source.ex`.

# `dropped_directive`

```elixir
@type dropped_directive() :: %{
  kind: :use | :import | :require,
  line: pos_integer(),
  text: String.t()
}
```

# `opts`

```elixir
@type opts() :: [
  definition: Adze.Definition.definition_spec(),
  module: String.t(),
  from_module: String.t() | nil,
  path: Path.t() | nil,
  mix_root: Path.t() | nil,
  include_attrs: [atom()],
  files: %{required(Path.t()) =&gt; String.t()},
  app_name: atom()
]
```

# `result`

```elixir
@type result() :: %{
  target_module: String.t(),
  target_path: Path.t(),
  target_content: String.t(),
  new_source: String.t(),
  source_diff: String.t(),
  source_module: String.t(),
  public_closure_keys: [{atom(), non_neg_integer()}],
  caller_diffs: %{required(Path.t()) =&gt; String.t()},
  dropped_directives: [dropped_directive()]
}
```

# `extract`

```elixir
@spec extract(String.t(), opts()) :: {:ok, result()} | {:error, term()}
```

# `extract!`

```elixir
@spec extract!(Path.t(), opts()) :: {:ok, result()} | {:error, term()}
```

# `extract_file`

```elixir
@spec extract_file(Path.t(), opts()) :: {:ok, result()} | {:error, term()}
```

---

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