Patchwork (fnord v0.9.29)

View Source

Centralized exact-match text replacement engine. Provides validated string substitution with hashline prefix detection, typography normalization, and ambiguity checks. Used by both the file_edit_tool (coordinator's direct path) and the Patcher agent (natural language instruction path).

Summary

Functions

Parse and validate a list of hashline identifiers ("line:hash" format). Returns {:ok, [{line_num, hash}]} or {:error, reason}. Used both internally during patch application and externally by the Patcher agent to validate LLM responses before attempting a patch.

Applies an exact string replacement to contents via patch/2.

Hash-anchored replacement using line:hash identifiers for precise location and comprehension verification via old_string.

Simplified replacement for callers that don't need replace_all or file creation semantics. Returns {:ok, new_contents} or {:error, reason}.

Types

replace_opts()

@type replace_opts() :: %{
  :old_string => String.t(),
  :new_string => String.t(),
  :replace_all => boolean(),
  optional(atom()) => any()
}

Functions

parse_hashline_ids(ids)

@spec parse_hashline_ids([String.t()]) ::
  {:ok, [{pos_integer(), String.t()}]} | {:error, String.t()}

Parse and validate a list of hashline identifiers ("line:hash" format). Returns {:ok, [{line_num, hash}]} or {:error, reason}. Used both internally during patch application and externally by the Patcher agent to validate LLM responses before attempting a patch.

patch(contents, opts)

@spec patch(binary(), replace_opts()) :: {:ok, binary()} | {:error, String.t()}

Applies an exact string replacement to contents via patch/2.

Validates that old_string does not contain hashline prefixes from file_contents_tool, falls back to typography-normalized matching when a byte-exact match fails, checks for ambiguous multiple occurrences, and optionally applies whitespace fitting.

Returns {:ok, new_contents} or {:error, reason}.

patch_by_hashes(contents, hashline_ids, old_string, new_string)

@spec patch_by_hashes(binary(), [String.t()], String.t(), String.t()) ::
  {:ok, binary()} | {:error, String.t()}

Hash-anchored replacement using line:hash identifiers for precise location and comprehension verification via old_string.

Each element of hashline_ids is a "line:hash" string (e.g. "42:a3f1") copied directly from file_contents_tool output. The line number provides unambiguous location (only one line 42), and the hash verifies the content hasn't changed since the file was read.

The two-part contract serves different purposes:

  • hashline_ids (location): line numbers for unambiguous targeting, hashes for staleness detection. Immune to whitespace/indentation errors.
  • old_string (comprehension): forces the caller to read and reproduce the target region, catching misunderstandings before they produce bad replacement text

The old_string comparison is whitespace-tolerant: leading whitespace is stripped from each line before comparing. The content must match, but indentation differences are forgiven.

Returns {:ok, new_contents} or {:error, reason}.

replace(contents, old_string, new_string)

@spec replace(binary(), String.t(), String.t()) ::
  {:ok, binary()} | {:error, String.t()}

Simplified replacement for callers that don't need replace_all or file creation semantics. Returns {:ok, new_contents} or {:error, reason}.