Routes file tool operations through buffer forks and changesets.
Tools call this module instead of directly doing filesystem I/O. The routing decision tree for each file operation:
- Is there an open buffer for this path AND a fork store is active? YES: route through Buffer.Fork (in-memory, instant, undo integration)
- Is there an active changeset? YES: route through Changeset overlay (filesystem-level isolation)
- Neither: fall through to direct I/O (returns
:passthrough)
Buffer.Fork handles files the user has open in a buffer. Changeset handles files that aren't open, or external tools that need a coherent filesystem view. A session can use both simultaneously.
This module is stateless. The fork store pid and changeset pid come from the caller (stored in session state, captured in tool closures).
Summary
Types
Changeset reference (nil when changeset is disabled).
Routing context passed by tool callbacks.
Fork store reference (nil when fork routing is disabled).
Functions
Returns true if any routing is active (fork store or changeset).
Builds a routing context from the fork store and changeset pids.
Deletes a file, routing through changeset if active.
Edits a file by find-and-replace, routing through fork or changeset.
Returns true if a fork store is active and has forks.
Reads a file, routing through fork or changeset if active.
Returns the working directory for shell commands.
Writes a file, routing through fork or changeset if active.
Types
@type changeset() :: pid() | nil
Changeset reference (nil when changeset is disabled).
@type context() :: %{fork_store: fork_store(), changeset: changeset()}
Routing context passed by tool callbacks.
@type fork_store() :: pid() | nil
Fork store reference (nil when fork routing is disabled).
Functions
Returns true if any routing is active (fork store or changeset).
@spec context(fork_store(), changeset()) :: context()
Builds a routing context from the fork store and changeset pids.
Deletes a file, routing through changeset if active.
Buffer forks don't support deletion (you can't delete an open buffer through a fork). Falls through to changeset or passthrough.
@spec edit_file(context(), String.t(), String.t(), String.t()) :: :ok | :passthrough | {:error, term()}
Edits a file by find-and-replace, routing through fork or changeset.
Returns true if a fork store is active and has forks.
Reads a file, routing through fork or changeset if active.
Forks take priority: if there's a fork for this path, read from it. Otherwise try the changeset. If neither, fall through to buffer/filesystem.
Returns the working directory for shell commands.
Returns the changeset overlay directory if active, nil otherwise. (Buffer forks don't affect the filesystem view for shell commands.)
Writes a file, routing through fork or changeset if active.
If a buffer is open for this path and a fork store exists, creates
a fork (lazily) and writes to it. Otherwise tries changeset.
Returns :passthrough if neither is active.