Class: SwarmSDK::Tools::Edit

Inherits:
Base
  • Object
show all
Includes:
PathResolver
Defined in:
lib/swarm_sdk/tools/edit.rb

Overview

Edit tool for performing exact string replacements in files

Uses exact string matching to find and replace content. Requires unique matches and proper Read tool usage beforehand. Enforces read-before-edit rule.

Instance Attribute Summary

Attributes included from PathResolver

#agent_name, #directory

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

removable, removable?, #removable?

Constructor Details

#initialize(agent_name:, directory:) ⇒ Edit

Initialize the Edit tool for a specific agent

Parameters:

  • agent_name (Symbol, String)

    The agent identifier

  • directory (String)

    Agent's working directory



63
64
65
66
# File 'lib/swarm_sdk/tools/edit.rb', line 63

def initialize(agent_name:, directory:)
  super()
  initialize_agent_context(agent_name: agent_name, directory: directory)
end

Class Method Details

.creation_requirementsObject



15
16
17
# File 'lib/swarm_sdk/tools/edit.rb', line 15

def creation_requirements
  [:agent_name, :directory]
end

Instance Method Details

#execute(file_path:, old_string:, new_string:, replace_all: false) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/swarm_sdk/tools/edit.rb', line 73

def execute(file_path:, old_string:, new_string:, replace_all: false)
  # Validate inputs
  return validation_error("file_path is required") if file_path.nil? || file_path.to_s.strip.empty?
  return validation_error("old_string is required") if old_string.nil? || old_string.empty?
  return validation_error("new_string is required") if new_string.nil?

  # old_string and new_string must be different
  if old_string == new_string
    return validation_error("old_string and new_string must be different. They are currently identical.")
  end

  # CRITICAL: Resolve path against agent directory
  resolved_path = resolve_path(file_path)

  # File must exist (use resolved path)
  unless File.exist?(resolved_path)
    return validation_error("File does not exist: #{file_path}")
  end

  # Enforce read-before-edit (use resolved path)
  unless Stores::ReadTracker.file_read?(@agent_name, resolved_path)
    return validation_error(
      "Cannot edit file without reading it first. " \
        "You must use the Read tool on '#{file_path}' before editing it. " \
        "This ensures you have the current file contents to match against.",
    )
  end

  # Read current content (use resolved path)
  content = File.read(resolved_path, encoding: "UTF-8")

  # Check if old_string exists in file
  unless content.include?(old_string)
    return validation_error(<<~ERROR.chomp)
      old_string not found in file. Make sure it matches exactly, including all whitespace and indentation.
      Do not include line number prefixes from Read tool output.
    ERROR
  end

  # Count occurrences
  occurrences = content.scan(old_string).count

  # If not replace_all and multiple occurrences, error
  if !replace_all && occurrences > 1
    return validation_error(<<~ERROR.chomp)
      Found #{occurrences} occurrences of old_string.
      Either provide more surrounding context to make the match unique, or use replace_all: true to replace all occurrences.
    ERROR
  end

  # Perform replacement
  new_content = if replace_all
    content.gsub(old_string, new_string)
  else
    content.sub(old_string, new_string)
  end

  # Write back to file (use resolved path)
  File.write(resolved_path, new_content, encoding: "UTF-8")

  # Build success message
  replaced_count = replace_all ? occurrences : 1
  "Successfully replaced #{replaced_count} occurrence(s) in #{file_path}"
rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError
  error("File contains invalid UTF-8. Cannot edit binary or improperly encoded files.")
rescue Errno::EACCES
  error("Permission denied: Cannot read or write file '#{file_path}'")
rescue StandardError => e
  error("Unexpected error editing file: #{e.class.name} - #{e.message}")
end

#nameObject

Override name to return simple "Edit" instead of full class path



69
70
71
# File 'lib/swarm_sdk/tools/edit.rb', line 69

def name
  "Edit"
end