MingaAgent.Changeset (Minga v0.1.0)

Copy Markdown View Source

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)

Summary

Functions

Returns the current attempt count and budget.

Creates a new changeset against project_root.

Deletes a file from the changeset's view.

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

Edits a file by replacing old_text with new_text.

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

Returns modified and deleted file lists.

Returns the overlay directory path.

Returns the project root this changeset was created against.

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

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

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

Runs a shell command in the overlay directory.

Returns a summary of all changes.

Undoes the last edit to a specific file.

Writes content to relative_path within the changeset.

Types

changeset()

@type changeset() :: pid()

Functions

attempt_info(cs)

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

Returns the current attempt count and budget.

create(project_root, opts \\ [])

@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(cs, relative_path)

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

Deletes a file from the changeset's view.

discard(cs)

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

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

edit_file(cs, relative_path, old_text, new_text)

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

Edits a file by replacing old_text with new_text.

merge(cs)

@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(cs)

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

Returns modified and deleted file lists.

overlay_path(cs)

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

Returns the overlay directory path.

project_root(cs)

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

Returns the project root this changeset was created against.

read_file(cs, relative_path)

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

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

record_attempt(cs)

@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(cs)

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

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

run(cs, command, opts \\ [])

@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(cs)

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

Returns a summary of all changes.

undo(cs, relative_path)

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

Undoes the last edit to a specific file.

write_file(cs, relative_path, content)

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

Writes content to relative_path within the changeset.