Class: RailsAiBridge::Tools::SearchCode
- Defined in:
- lib/rails_ai_bridge/tools/search_code.rb,
lib/rails_ai_bridge/tools/search_code/formatter.rb,
lib/rails_ai_bridge/tools/search_code/validator.rb,
lib/rails_ai_bridge/tools/search_code/ruby_search.rb,
lib/rails_ai_bridge/tools/search_code/ripgrep_search.rb
Overview
MCP tool searching the app tree with ripgrep (+rg+) or a Ruby fallback.
Pattern size is capped by Configuration#search_code_pattern_max_bytes (default 2048). Wall-clock limits use Configuration#search_code_timeout_seconds (+0+ disables).
Defined Under Namespace
Classes: Formatter, RipgrepSearch, RubySearch, Validator
Constant Summary collapse
- MAX_RESULTS_CAP =
Hard upper bound for +max_results+ regardless of client input.
100- DEFAULT_ALLOWED_FILE_TYPES =
Default extensions when no +file_type+ is given, merged with Configuration#search_code_allowed_file_types.
%w[rb erb js ts jsx tsx yml yaml json].freeze
Class Method Summary collapse
- .allowed_search_file_types ⇒ Object
- .call(pattern:, path: nil, file_type: nil, max_results: 30) ⇒ Object
- .normalize_max_results(max_results) ⇒ Object
- .ripgrep_available? ⇒ Boolean
- .search_engine(search_params) ⇒ Object
- .with_search_timeout ⇒ Object
Methods inherited from BaseTool
cached_context, cached_section, config, rails_app, reset_cache!, text_response
Class Method Details
.allowed_search_file_types ⇒ Object
52 53 54 55 56 57 58 |
# File 'lib/rails_ai_bridge/tools/search_code.rb', line 52 def self.allowed_search_file_types extras = RailsAiBridge.configuration.search_code_allowed_file_types.map do |x| x.to_s.downcase.strip.delete_prefix('.').gsub(/[^a-z0-9]/, '') end.reject(&:empty?) (DEFAULT_ALLOWED_FILE_TYPES + extras).uniq end |
.call(pattern:, path: nil, file_type: nil, max_results: 30) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/rails_ai_bridge/tools/search_code.rb', line 36 def self.call(pattern:, path: nil, file_type: nil, max_results: 30) root = Rails.root.to_s validator = Validator.new(pattern, file_type, root, path) validated = validator.validate return validated if validated.is_a?(MCP::Tool::Response) max_res = normalize_max_results(max_results) search_params = validated.merge(pattern: pattern, max_results: max_res, root: root) results = with_search_timeout do search_engine(search_params) end text_response(Formatter.new.call(results, pattern, path)) end |
.normalize_max_results(max_results) ⇒ Object
60 61 62 63 |
# File 'lib/rails_ai_bridge/tools/search_code.rb', line 60 def self.normalize_max_results(max_results) normalized = [max_results.to_i, MAX_RESULTS_CAP].min normalized < 1 ? 30 : normalized end |
.ripgrep_available? ⇒ Boolean
73 74 75 76 |
# File 'lib/rails_ai_bridge/tools/search_code.rb', line 73 def self.ripgrep_available? @ripgrep_available = system('which rg > /dev/null 2>&1') unless instance_variable_defined?(:@ripgrep_available) @ripgrep_available end |
.search_engine(search_params) ⇒ Object
65 66 67 68 69 70 71 |
# File 'lib/rails_ai_bridge/tools/search_code.rb', line 65 def self.search_engine(search_params) if ripgrep_available? RipgrepSearch.new(search_params).call else RubySearch.new(search_params).call end end |
.with_search_timeout ⇒ Object
78 79 80 81 82 83 84 85 |
# File 'lib/rails_ai_bridge/tools/search_code.rb', line 78 def self.with_search_timeout(&) sec = RailsAiBridge.configuration.search_code_timeout_seconds.to_f return yield if sec <= 0 Timeout.timeout(sec, &) rescue Timeout::Error [{ file: 'error', line_number: 0, content: "Search timed out after #{sec} seconds." }] end |