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.

Lists a directory in the changeset's logical project view.

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

Returns modified and deleted file lists.

Returns the overlay directory path.

Previews the merge back to the real project without applying it.

Materializes and returns the overlay directory path for shell/search tools.

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.

list_directory(cs, relative_path)

@spec list_directory(changeset(), String.t()) ::
  {:ok, [%{name: String.t(), type: :directory | :file}]} | {:error, term()}

Lists a directory in the changeset's logical project view.

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, [tuple()]} | {:conflict, map()} | {:error, term()}

Previews the merge back to the real project without applying it.

This runs the same path resolution and three-way merge planning as merge/1 but writes, deletes, and cleans up nothing on disk, and never stops the GenServer. Use it when a caller needs to know whether a merge would succeed before mutating other state.

Returns {:ok, results} when every file would merge cleanly, or {:conflict, details} (same shape as merge/1) listing what could not be auto-merged. In both cases results is the list of per-file decision tuples ({:ok, path, kind} / {:conflict, path, reason}) the merge would produce.

prepare_working_dir(cs)

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

Materializes and returns the overlay directory path for shell/search tools.

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.

Materializes a writable project view first and 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.