diff options
| author | Adam Malczewski <[email protected]> | 2026-03-31 23:10:45 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-03-31 23:10:45 +0900 |
| commit | 57c56daf5906442dacc15951c9b3405f89309839 (patch) | |
| tree | e76890119c0e47acb48f8585222b7a2f5e22df56 /lib/dispatch/tool/files/edit_file.rb | |
| parent | 25488d32336e05b69a41391cc7b5153478d3cc8a (diff) | |
| download | dispatch-tool-files-57c56daf5906442dacc15951c9b3405f89309839.tar.gz dispatch-tool-files-57c56daf5906442dacc15951c9b3405f89309839.zip | |
impdev
Diffstat (limited to 'lib/dispatch/tool/files/edit_file.rb')
| -rw-r--r-- | lib/dispatch/tool/files/edit_file.rb | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/lib/dispatch/tool/files/edit_file.rb b/lib/dispatch/tool/files/edit_file.rb new file mode 100644 index 0000000..b1bd159 --- /dev/null +++ b/lib/dispatch/tool/files/edit_file.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +module Dispatch + module Tool + module Files + EDIT_FILE = Dispatch::Tools::Definition.new( + name: "edit_file", + description: "Apply text edits to an existing file. Each edit replaces old_text with new_text sequentially.", + parameters: { + type: "object", + properties: { + path: { + type: "string", + description: "Path to the file to edit, relative to the worktree root." + }, + edits: { + type: "array", + description: "Array of edit operations to apply sequentially.", + items: { + type: "object", + properties: { + old_text: { + type: "string", + description: "The exact text to find in the file." + }, + new_text: { + type: "string", + description: "The text to replace old_text with." + } + }, + required: %w[old_text new_text], + additionalProperties: false + } + } + }, + required: %w[path edits], + additionalProperties: false + } + ) do |params, context| + worktree_path = context[:worktree_path] + path = params[:path] + edits = params[:edits] + + resolved = begin + Sandbox.resolve_path(path, worktree_path:) + rescue SandboxError => e + next Dispatch::Tools::Result.failure(error: e.message) + end + + unless File.exist?(resolved) + next Dispatch::Tools::Result.failure(error: "File not found: #{path}") + end + + content = File.read(resolved) + edits_applied = 0 + error_result = nil + + edits.each do |edit| + old_text = edit[:old_text] || edit["old_text"] + new_text = edit[:new_text] || edit["new_text"] + + escaped = Regexp.new(Regexp.escape(old_text)) + occurrences = content.scan(escaped).length + + if occurrences.zero? + error_result = Dispatch::Tools::Result.failure( + error: "Edit #{edits_applied + 1}: old_text not found in #{path}" + ) + break + end + + if occurrences > 1 + error_result = Dispatch::Tools::Result.failure( + error: "Edit #{edits_applied + 1}: ambiguous match — old_text found #{occurrences} times in #{path}. " \ + "Provide more context to make the match unique." + ) + break + end + + content = content.sub(old_text, new_text) + edits_applied += 1 + end + + next error_result if error_result + + File.write(resolved, content) + Dispatch::Tools::Result.success(output: "Applied #{edits_applied} edit(s) to #{path}") + end + end + end +end |
