Class: SwarmSDK::Tools::Grep

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

Overview

Grep tool for searching file contents using ripgrep-style patterns

Powerful search capabilities with regex support, context lines, and filtering. Built on ripgrep (rg) for fast, efficient searching.

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(directory:) ⇒ Grep

Returns a new instance of Grep.



19
20
21
22
# File 'lib/swarm_sdk/tools/grep.rb', line 19

def initialize(directory:)
  super()
  @directory = File.expand_path(directory)
end

Class Method Details

.creation_requirementsObject



14
15
16
# File 'lib/swarm_sdk/tools/grep.rb', line 14

def creation_requirements
  [:directory]
end

Instance Method Details

#execute(pattern:, path: nil, glob: nil, type: nil, output_mode: "files_with_matches", case_insensitive: false, multiline: false, context_before: nil, context_after: nil, context: nil, show_line_numbers: false, head_limit: nil) ⇒ Object



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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/swarm_sdk/tools/grep.rb', line 99

def execute(
  pattern:,
  path: nil,
  glob: nil,
  type: nil,
  output_mode: "files_with_matches",
  case_insensitive: false,
  multiline: false,
  context_before: nil,
  context_after: nil,
  context: nil,
  show_line_numbers: false,
  head_limit: nil
)
  # Validate inputs
  return validation_error("pattern is required") if pattern.nil? || pattern.empty?

  # CRITICAL: Default path to agent's directory (NOT current directory)
  path = if path.nil? || path.to_s.strip.empty?
    @directory
  else
    # Resolve relative paths against agent directory
    resolve_path(path)
  end

  # Validate output_mode
  valid_modes = ["content", "files_with_matches", "count"]
  unless valid_modes.include?(output_mode)
    return validation_error("output_mode must be one of: #{valid_modes.join(", ")}")
  end

  # Build ripgrep command
  cmd = ["rg"]

  # Output mode flags
  case output_mode
  when "files_with_matches"
    cmd << "-l" # List files with matches
  when "count"
    cmd << "-c" # Count matches per file
  when "content"
    # Default mode, no special flag needed
    # Add line numbers if requested
    cmd << "-n" if show_line_numbers

    # Add context flags
    cmd << "-B" << context_before.to_s if context_before
    cmd << "-A" << context_after.to_s if context_after
    cmd << "-C" << context.to_s if context
  end

  # Case sensitivity
  cmd << "-i" if case_insensitive

  # Multiline mode
  if multiline
    cmd << "-U" << "--multiline-dotall"
  end

  # File filtering (only add if non-empty)
  cmd << "--type" << type if type && !type.to_s.strip.empty?
  cmd << "--glob" << glob if glob && !glob.to_s.strip.empty?

  # Pattern
  cmd << "-e" << pattern

  # Path
  cmd << path

  # Execute command
  begin
    require "open3"

    stdout, stderr, status = Open3.capture3(*cmd)

    # Handle no matches (exit code 1 for ripgrep means no matches found)
    if status.exitstatus == 1 && stderr.empty?
      return "No matches found for pattern: #{pattern}"
    end

    # Handle errors (exit code 2 means error)
    if status.exitstatus == 2 || !stderr.empty?
      return error("ripgrep error: #{stderr}")
    end

    # Success - format output
    output = stdout

    # Apply head_limit if specified
    if head_limit && head_limit > 0
      lines = output.lines
      if lines.count > head_limit
        output = lines.take(head_limit).join
        output += "\n\n<system-reminder>Output limited to first #{head_limit} lines. Total results: #{lines.count} lines.</system-reminder>"
      end
    end

    # Add reminder about usage
    reminder = build_usage_reminder(output_mode, pattern)
    output = "#{output}\n\n#{reminder}" unless reminder.empty?

    output.empty? ? "No matches found for pattern: #{pattern}" : output
  rescue Errno::ENOENT
    error("ripgrep (rg) is not installed or not in PATH. Please install ripgrep to use the Grep tool.")
  rescue Errno::EACCES
    error("Permission denied: Cannot search in '#{path}'")
  rescue StandardError => e
    error("Failed to execute search: #{e.class.name} - #{e.message}")
  end
rescue StandardError => e
  error("Unexpected error during search: #{e.class.name} - #{e.message}")
end