# `MingaAgent.Changeset`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_agent/changeset.ex#L1)

In-memory changesets with filesystem overlays for agent editing.

A changeset tracks file edits without modifying the original project.
Edits are held in memory and materialized into a hardlink overlay where
external tools (compilers, test runners, linters) see a coherent view
of the project with changes applied.

## Lifecycle

    {:ok, cs} = MingaAgent.Changeset.create("/path/to/project")

    :ok = MingaAgent.Changeset.write_file(cs, "lib/math.ex", new_content)
    :ok = MingaAgent.Changeset.edit_file(cs, "lib/util.ex", "old", "new")

    # External tools see changes through the overlay
    {output, 0} = MingaAgent.Changeset.run(cs, "mix compile")

    # Session ends: merge back with three-way merge
    :ok = MingaAgent.Changeset.merge(cs)

## Budget system

    {:ok, cs} = MingaAgent.Changeset.create("/path/to/project", budget: 3)
    {:ok, 1} = MingaAgent.Changeset.record_attempt(cs)
    {:budget_exhausted, 4, 3} = MingaAgent.Changeset.record_attempt(cs)

# `changeset`

```elixir
@type changeset() :: pid()
```

# `attempt_info`

```elixir
@spec attempt_info(changeset()) :: %{
  attempts: non_neg_integer(),
  budget: pos_integer() | :unlimited
}
```

Returns the current attempt count and budget.

# `create`

```elixir
@spec create(
  String.t(),
  keyword()
) :: {:ok, changeset()} | {:error, term()}
```

Creates a new changeset against `project_root`.

Starts a `Changeset.Server` GenServer under `MingaAgent.Supervisor`.
The server creates a filesystem overlay mirroring the project.

## Options

  * `:budget` - max verification attempts before exhaustion (default: `:unlimited`)

# `delete_file`

```elixir
@spec delete_file(changeset(), String.t()) :: :ok | {:error, term()}
```

Deletes a file from the changeset's view.

# `discard`

```elixir
@spec discard(changeset()) :: :ok
```

Discards all changes, cleans up the overlay, and stops the GenServer.

# `edit_file`

```elixir
@spec edit_file(changeset(), String.t(), String.t(), String.t()) ::
  :ok | {:error, term()}
```

Edits a file by replacing `old_text` with `new_text`.

# `merge`

```elixir
@spec merge(changeset()) ::
  :ok | {:ok, :merged_with_conflicts, list()} | {:error, term()}
```

Merges changes back to the real project with three-way merge.

If someone edited the same files since the changeset was created,
a three-way merge is attempted. Returns `:ok` for clean merges, or
`{:ok, :merged_with_conflicts, details}` listing what couldn't be
auto-merged. Stops the GenServer on success.

# `modified_files`

```elixir
@spec modified_files(changeset()) :: %{modified: [String.t()], deleted: [String.t()]}
```

Returns modified and deleted file lists.

# `overlay_path`

```elixir
@spec overlay_path(changeset()) :: String.t()
```

Returns the overlay directory path.

# `project_root`

```elixir
@spec project_root(changeset()) :: String.t()
```

Returns the project root this changeset was created against.

# `read_file`

```elixir
@spec read_file(changeset(), String.t()) :: {:ok, binary()} | {:error, term()}
```

Reads a file (changeset version if modified, otherwise from project).

# `record_attempt`

```elixir
@spec record_attempt(changeset()) ::
  {:ok, pos_integer()} | {:budget_exhausted, pos_integer(), pos_integer()}
```

Records a verification attempt (e.g., after running tests).

Returns `{:ok, attempt_number}` or `{:budget_exhausted, attempts, budget}`.

# `reset`

```elixir
@spec reset(changeset()) :: :ok
```

Resets the entire changeset, restoring all files to their original state.

# `run`

```elixir
@spec run(changeset(), String.t(), keyword()) :: {String.t(), non_neg_integer()}
```

Runs a shell command in the overlay directory.

Sets `MIX_BUILD_PATH` to an isolated build directory so compilation
doesn't contaminate the real project's `_build`.

# `summary`

```elixir
@spec summary(changeset()) :: [map()]
```

Returns a summary of all changes.

# `undo`

```elixir
@spec undo(changeset(), String.t()) :: :ok | {:error, :nothing_to_undo}
```

Undoes the last edit to a specific file.

# `write_file`

```elixir
@spec write_file(changeset(), String.t(), binary()) :: :ok | {:error, term()}
```

Writes `content` to `relative_path` within the changeset.

---

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