Class: RailsAiContext::Tools::SearchDocs

Inherits:
BaseTool
  • Object
show all
Defined in:
lib/rails_ai_context/tools/search_docs.rb

Constant Summary collapse

VALID_SOURCES =
%w[all guides api stimulus turbo hotwire].freeze
INDEX_PATH =
File.join(File.dirname(__FILE__), "..", "data", "docs", "index.json").freeze

Constants inherited from BaseTool

BaseTool::SESSION_CONTEXT, BaseTool::SHARED_CACHE

Class Method Summary collapse

Methods inherited from BaseTool

abstract!, abstract?, cache_key, cached_context, config, extract_method_source_from_file, extract_method_source_from_string, find_closest_match, fuzzy_find_key, inherited, not_found_response, paginate, rails_app, registered_tools, reset_all_caches!, reset_cache!, session_queries, session_record, session_reset!, set_call_params, text_response

Class Method Details

.call(query:, source: "all", limit: 5, fetch: false, server_context: nil, **_extra) ⇒ Object



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
70
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
# File 'lib/rails_ai_context/tools/search_docs.rb', line 44

def self.call(query:, source: "all", limit: 5, fetch: false, server_context: nil, **_extra)
  # Validate query
  query = query.to_s.strip
  if query.empty?
    return text_response("Query is required. Provide search terms (e.g. 'active record validations').")
  end

  # Validate source
  source = source.to_s.downcase
  unless VALID_SOURCES.include?(source)
    return text_response("Invalid source: '#{source}'. Valid values: #{VALID_SOURCES.join(', ')}")
  end

  # Normalize limit
  limit = limit.to_i
  limit = 5 if limit <= 0
  limit = [ limit, 20 ].min

  # Load index
  index = load_index
  return text_response(index[:error]) if index[:error]

  topics = index[:topics]

  # Detect Rails version and resolve branch
  branch = detect_rails_branch

  # Search
  # Normalize source filter: "guides" maps to the "rails" tag in the index;
  # "hotwire" is an umbrella covering stimulus, turbo, and hotwire_native.
  source_tags = case source
  when "guides"  then %w[rails guides]
  when "hotwire" then %w[stimulus turbo hotwire hotwire_native]
  else           [ source ]
  end

  tokens = query.downcase.split(/\s+/)
  scored = topics.filter_map do |topic|
    next if source != "all" && !source_tags.include?(topic["source"]&.downcase)

    score = compute_score(tokens, topic)
    next if score <= 0

    { topic: topic, score: score }
  end

  scored.sort_by! { |s| -s[:score] }
  results = scored.first(limit)

  if results.empty?
    return text_response("No documentation found for '#{query}'. Try broader terms like 'active record', 'routing', or 'testing'.")
  end

  if fetch
    format_fetch_results(results, query, source, branch)
  else
    format_results(results, query, source, branch)
  end
end