Class: RailsAiContext::Tools::GetControllers

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

Constant Summary

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(controller: nil, action: nil, detail: "standard", limit: nil, offset: 0, server_context: nil) ⇒ Object



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
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
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
# File 'lib/rails_ai_context/tools/get_controllers.rb', line 39

def self.call(controller: nil, action: nil, detail: "standard", limit: nil, offset: 0, server_context: nil)
  data = cached_context[:controllers]
  return text_response("Controller introspection not available. Add :controllers to introspectors.") unless data
  return text_response("Controller introspection failed: #{data[:error]}") if data[:error]

  controllers = data[:controllers] || {}

  # Filter out framework-internal controllers for listings/error messages
  framework_controllers = RailsAiContext.configuration.excluded_controllers
  app_controller_names = controllers.keys.reject { |name| framework_controllers.include?(name) }.sort

  # Specific controller — always full detail (searches ALL controllers including framework)
  # Flexible matching: "cooks", "CooksController", "cookscontroller" all work
  if controller
    # Accept multiple formats: "CooksController", "cooks", "bonus/crises", "Bonus::CrisesController"
    # Use underscore for CamelCase→snake_case: "OmniauthCallbacks" → "omniauth_callbacks"
    # Also match on plain downcase to handle "userscontroller" → "users"
    input_snake = controller.gsub("/", "::").underscore.delete_suffix("_controller")
    input_down = controller.downcase.delete_suffix("controller").tr("/", "::")
    key = controllers.keys.find { |k|
      key_snake = k.underscore.delete_suffix("_controller")
      key_down = k.downcase.delete_suffix("controller")
      key_snake == input_snake || key_down == input_down
    } || controller
    info = controllers[key]
    unless info
      return not_found_response("Controller", controller, app_controller_names,
        recovery_tool: "Call rails_get_controllers(detail:\"summary\") to see all controllers")
    end
    return text_response("Error inspecting #{key}: #{info[:error]}") if info[:error]

    # Specific action — return source code
    if action
      return text_response(format_action_source(key, info, action))
    end

    return text_response(format_controller(key, info))
  end

  app_controllers = controllers.reject { |name, _| framework_controllers.include?(name) }

  # Pagination
  all_names = app_controllers.keys.sort
  page = paginate(all_names, offset: offset, limit: limit, default_limit: 50)
  paginated_names = page[:items]

  if paginated_names.empty? && page[:total] > 0
    return text_response(page[:hint])
  end

  pagination_hint = page[:hint].empty? ? "" : "\n#{page[:hint]}"

  # Listing mode
  case detail
  when "summary"
    lines = [ "# Controllers (#{page[:total]})", "" ]
    paginated_names.each do |name|
      info = app_controllers[name]
      action_count = info[:actions]&.size || 0
      lines << "- **#{name}** — #{action_count} actions"
    end
    lines << "" << "_Use `controller:\"Name\"` for full detail._#{pagination_hint}"
    text_response(lines.join("\n"))

  when "standard"
    lines = [ "# Controllers (#{page[:total]})", "" ]
    paginated_names.each do |name|
      info = app_controllers[name]
      actions = info[:actions]&.join(", ") || "none"
      lines << "- **#{name}** — #{actions}"
    end
    lines << "" << "_Use `controller:\"Name\"` for filters and strong params, or `detail:\"full\"` for everything._#{pagination_hint}"
    text_response(lines.join("\n"))

  when "full"
    lines = [ "# Controllers (#{page[:total]})", "" ]

    # Group sibling controllers that share the same parent and identical structure
    paginated_ctrl = app_controllers.select { |k, _| paginated_names.include?(k) }
    grouped = paginated_ctrl.keys.sort.group_by do |name|
      info = app_controllers[name]
      parent = info[:parent_class]
      # Group by parent + actions + filters + params fingerprint
      if parent && parent != "ApplicationController"
        actions_sig = info[:actions]&.sort&.join(",")
        filters_sig = info[:filters]&.map { |f| "#{f[:kind]}:#{f[:name]}" }&.sort&.join(",")
        params_sig = info[:strong_params]&.sort&.join(",")
        "#{parent}|#{actions_sig}|#{filters_sig}|#{params_sig}"
      else
        name # unique key = no grouping
      end
    end

    grouped.each do |_key, names|
      if names.size > 2 && app_controllers[names.first][:parent_class] != "ApplicationController"
        # Compress group: show once with all names
        info = app_controllers[names.first]
        short_names = names.map { |n| n.sub(/Controller$/, "").split("::").last }
        parent = info[:parent_class] || "ApplicationController"
        lines << "## #{names.first.split('::').first}::* (#{short_names.join(', ')})"
        lines << "- Inherits: #{parent}"
        lines << "- Actions: #{info[:actions]&.join(', ')}" if info[:actions]&.any?
        if info[:filters]&.any?
          lines << "- Filters: #{info[:filters].map { |f| "#{f[:kind]} #{f[:name]}" }.join(', ')}"
        end
        lines << "- Strong params: #{info[:strong_params].join(', ')}" if info[:strong_params]&.any?
        lines << ""
      else
        names.each do |name|
          info = app_controllers[name]
          lines << "## #{name}"
          lines << "- Actions: #{info[:actions]&.join(', ')}" if info[:actions]&.any?
          if info[:filters]&.any?
            lines << "- Filters: #{info[:filters].map { |f| "#{f[:kind]} #{f[:name]}" }.join(', ')}"
          end
          lines << "- Strong params: #{info[:strong_params].join(', ')}" if info[:strong_params]&.any?
          lines << "- Rescue from: #{info[:rescue_from].join(', ')}" if info[:rescue_from]&.any?
          lines << "- Rate limit: #{info[:rate_limit]}" if info[:rate_limit]
          lines << "- Turbo Stream actions: #{info[:turbo_stream_actions].join(', ')}" if info[:turbo_stream_actions]&.any?
          lines << ""
        end
      end
    end
    lines << pagination_hint unless pagination_hint.empty?
    text_response(lines.join("\n"))

  else
    list = paginated_names.map { |c| "- #{c}" }.join("\n")
    text_response("# Controllers (#{page[:total]})\n\n#{list}#{pagination_hint}")
  end
end