Class: Legion::CLI::Chat::Tools::EditFile

Inherits:
Tools::Base
  • Object
show all
Defined in:
lib/legion/cli/chat/tools/edit_file.rb

Class Method Summary collapse

Methods inherited from Tools::Base

deferred, deferred?, description, error_response, extension, handle_exception, input_schema, log, mcp_category, mcp_tier, runner, sticky, tags, text_response, tool_name, trigger_words

Class Method Details

.call(path:, new_text:, old_text: nil, start_line: nil, end_line: nil) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/legion/cli/chat/tools/edit_file.rb', line 28

def self.call(path:, new_text:, old_text: nil, start_line: nil, end_line: nil)
  expanded = File.expand_path(path)
  return "Error: file not found: #{path}" unless File.exist?(expanded)

  require 'legion/cli/chat/checkpoint'

  if start_line
    line_replace(expanded, new_text, start_line, end_line || start_line)
  else
    return 'Error: old_text is required when not using line-number mode' if old_text.nil?

    string_replace(expanded, old_text, new_text)
  end
rescue StandardError => e
  Legion::Logging.warn("EditFile#execute failed for #{path}: #{e.message}") if defined?(Legion::Logging)
  "Error editing #{path}: #{e.message}"
end

.line_replace(expanded, new_text, start_line, end_line) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/legion/cli/chat/tools/edit_file.rb', line 58

def self.line_replace(expanded, new_text, start_line, end_line)
  lines = File.readlines(expanded, encoding: 'utf-8')
  total = lines.length

  return "Error: start_line #{start_line} out of bounds (file has #{total} lines)" if start_line < 1 || start_line > total
  return "Error: end_line #{end_line} out of bounds (file has #{total} lines)" if end_line < 1 || end_line > total
  return "Error: end_line #{end_line} is before start_line #{start_line}" if end_line < start_line

  Checkpoint.save(expanded)
  replacement_lines = new_text.end_with?("\n") ? [new_text] : ["#{new_text}\n"]
  lines[(start_line - 1)..(end_line - 1)] = replacement_lines
  File.write(expanded, lines.join, encoding: 'utf-8')
  "Replaced lines #{start_line}#{end_line} in #{expanded}"
end

.string_replace(expanded, old_text, new_text) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/legion/cli/chat/tools/edit_file.rb', line 46

def self.string_replace(expanded, old_text, new_text)
  content = File.read(expanded, encoding: 'utf-8')
  occurrences = content.scan(old_text).length

  return "Error: old_text not found in #{expanded}" if occurrences.zero?
  return "Error: old_text matches #{occurrences} locations — must be unique (provide more context)" if occurrences > 1

  Checkpoint.save(expanded)
  File.write(expanded, content.sub(old_text, new_text), encoding: 'utf-8')
  "Replaced 1 occurrence in #{expanded}"
end