Class: ConsoleAgent::Tools::CodeTools

Inherits:
Object
  • Object
show all
Defined in:
lib/console_agent/tools/code_tools.rb

Constant Summary collapse

MAX_FILE_LINES =
500
MAX_LIST_ENTRIES =
100
MAX_SEARCH_RESULTS =
50

Instance Method Summary collapse

Instance Method Details

#list_files(directory = nil) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/console_agent/tools/code_tools.rb', line 8

def list_files(directory = nil)
  directory = sanitize_directory(directory || 'app')
  root = rails_root
  return "Rails.root is not available." unless root

  full_path = File.join(root, directory)
  return "Directory '#{directory}' not found." unless File.directory?(full_path)

  files = Dir.glob(File.join(full_path, '**', '*.rb')).sort
  files = files.map { |f| f.sub("#{root}/", '') }

  if files.length > MAX_LIST_ENTRIES
    truncated = files.first(MAX_LIST_ENTRIES)
    truncated.join("\n") + "\n... and #{files.length - MAX_LIST_ENTRIES} more files"
  elsif files.empty?
    "No Ruby files found in '#{directory}'."
  else
    files.join("\n")
  end
rescue => e
  "Error listing files: #{e.message}"
end

#read_file(path, start_line: nil, end_line: nil) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/console_agent/tools/code_tools.rb', line 31

def read_file(path, start_line: nil, end_line: nil)
  return "Error: path is required." if path.nil? || path.strip.empty?

  root = rails_root
  return "Rails.root is not available." unless root

  path = sanitize_path(path)
  full_path = File.expand_path(File.join(root, path))

  # Security: ensure resolved path is under Rails.root
  unless full_path.start_with?(File.expand_path(root))
    return "Error: path must be within the Rails application."
  end

  return "File '#{path}' not found." unless File.exist?(full_path)
  return "Error: '#{path}' is a directory, not a file." if File.directory?(full_path)

  all_lines = File.readlines(full_path)
  total = all_lines.length

  # Apply line range if specified (1-based, inclusive)
  if start_line || end_line
    s = [(start_line || 1).to_i, 1].max
    e = [(end_line || total).to_i, total].min
    return "Error: start_line (#{s}) is beyond end of file (#{total} lines)." if s > total
    lines = all_lines[(s - 1)..(e - 1)] || []
    offset = s - 1
    numbered = lines.each_with_index.map { |line, i| "#{offset + i + 1}: #{line}" }
    header = "Lines #{s}-#{[e, s + lines.length - 1].min} of #{total}:\n"
    header + numbered.join
  elsif total > MAX_FILE_LINES
    numbered = all_lines.first(MAX_FILE_LINES).each_with_index.map { |line, i| "#{i + 1}: #{line}" }
    numbered.join + "\n... truncated (#{total} total lines, showing first #{MAX_FILE_LINES}). Use start_line/end_line to read specific sections."
  else
    all_lines.each_with_index.map { |line, i| "#{i + 1}: #{line}" }.join
  end
rescue => e
  "Error reading file '#{path}': #{e.message}"
end

#search_code(query, directory = nil) ⇒ Object



71
72
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
# File 'lib/console_agent/tools/code_tools.rb', line 71

def search_code(query, directory = nil)
  return "Error: query is required." if query.nil? || query.strip.empty?

  directory = sanitize_directory(directory || 'app')
  root = rails_root
  return "Rails.root is not available." unless root

  full_path = File.join(root, directory)
  return "Directory '#{directory}' not found." unless File.directory?(full_path)

  results = []
  Dir.glob(File.join(full_path, '**', '*.rb')).sort.each do |file|
    break if results.length >= MAX_SEARCH_RESULTS

    relative = file.sub("#{root}/", '')
    File.readlines(file).each_with_index do |line, idx|
      if line.include?(query)
        results << "#{relative}:#{idx + 1}: #{line.strip}"
        break if results.length >= MAX_SEARCH_RESULTS
      end
    end
  rescue => e
    # skip unreadable files
  end

  if results.empty?
    "No matches found for '#{query}' in #{directory}/."
  else
    header = "Found #{results.length} match#{'es' if results.length != 1}:\n"
    header + results.join("\n")
  end
rescue => e
  "Error searching: #{e.message}"
end