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.

Returns environment variables shell commands should use for this changeset.

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.

Discards one changed file from the changeset view.

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.

Validates the merge plan back to the real project without applying it.

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.

command_env(cs)

@spec command_env(changeset()) :: [{String.t(), String.t()}]

Returns environment variables shell commands should use for this changeset.

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.

discard_file(cs, relative_path)

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

Discards one changed file from the changeset view.

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 | {:conflict, map()} | {: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 {:conflict, details} listing what couldn't be auto-merged. Stops the GenServer only on success; conflict results keep the changeset alive for review.

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.

preflight_merge(cs)

@spec preflight_merge(changeset()) :: {:ok, [map()]} | {:error, term()}

Validates the merge plan back to the real project without applying it.

This performs the same path validation and conflict planning as merge/1 but does not write, delete, or clean up anything. It is useful when a caller needs to know whether a merge would succeed before mutating other state.

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 | {:error, term()}

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 | term()}

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.