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
@type changeset() :: pid()
Functions
@spec attempt_info(changeset()) :: %{ attempts: non_neg_integer(), budget: pos_integer() | :unlimited }
Returns the current attempt count and budget.
Returns environment variables shell commands should use for this changeset.
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)
Deletes a file from the changeset's view.
@spec discard(changeset()) :: :ok
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.
@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.
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.
Returns modified and deleted file lists.
Returns the overlay directory path.
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.
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).
@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}.
Resets the entire changeset, restoring all files to their original state.
@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.
Returns a summary of all changes.
Undoes the last edit to a specific file.
Writes content to relative_path within the changeset.