Class: RailsAiContext::Tools::Diagnose
- Defined in:
- lib/rails_ai_context/tools/diagnose.rb
Constant Summary collapse
- MAX_TOTAL_OUTPUT =
Section size limits for output truncation
20_000- MAX_SECTION_CHARS =
{ controller_context: 3_000, model_context: 3_000, code_context: 3_000, schema_context: 3_000, method_trace: 3_000, git_changes: 2_000, logs: 2_000 }.freeze
- ERROR_CLASSIFICATIONS =
{ /NameError.*uninitialized constant/ => { type: :name_error, likely: "A class or module constant could not be found. This usually means a typo in the class/module name, a missing require, or an autoload issue.", fix: "1. Check for typo in class/module name, missing require, or autoload issue\n2. Verify the file is in the correct autoload path (e.g. app/models, app/services)\n3. Run `rails_search_code(pattern:\"ClassName\", match_type:\"trace\")` to find where the constant is defined" }, /NoMethodError/ => { type: :nil_reference, likely: "A method was called on nil or an undefined name was referenced. Check for: missing association, unfetched record, typo in method name.", fix: "1. Check the variable/object is not nil before calling the method\n2. Verify the association or attribute exists on the model\n3. Use `&.` safe navigation if the value can legitimately be nil" }, /NameError/ => { type: :name_error, likely: "An undefined name was referenced. This could be a typo in a variable, method, or constant name, or a missing require/autoload.", fix: "1. Check for typo in class/module name, missing require, or autoload issue\n2. Verify the name is defined and accessible in the current scope\n3. Use `rails_search_code(pattern:\"name\", match_type:\"trace\")` to find where it is defined" }, /RecordNotFound/ => { type: :record_not_found, likely: "A `.find()` or `.find_by!()` call failed because the record doesn't exist. Common causes: stale URL, deleted record, wrong ID parameter.", fix: "1. Use `.find_by` (returns nil) instead of `.find` (raises)\n2. Add a rescue handler or `before_action :set_record` with proper error handling\n3. Check the route parameter name matches what the controller expects" }, /RecordInvalid|RecordNotSaved/ => { type: :validation_failure, likely: "A `.save!`, `.create!`, or `.update!` call failed validation. The model has validations that the submitted data doesn't satisfy.", fix: "1. Use `.save` (returns false) instead of `.save!` (raises) for user-facing flows\n2. Check model validations against the params being submitted\n3. Verify required associations exist before saving" }, /RoutingError/ => { type: :routing, likely: "No route matches the request. The URL or HTTP verb doesn't match any defined route.", fix: "1. Run `rails_get_routes` to see all defined routes\n2. Check the HTTP verb (GET vs POST vs PATCH) matches\n3. Verify the route is not restricted by constraints" }, /ParameterMissing/ => { type: :strong_params, likely: "A required parameter is missing from the request. The `params.require(:key)` call didn't find the expected key.", fix: "1. Check the form field names match what strong params expects\n2. Verify the parameter nesting (e.g., `params[:user][:name]` vs `params[:name]`)\n3. Check the controller's `*_params` method" }, /StatementInvalid|UndefinedColumn|UndefinedTable/ => { type: :schema_mismatch, likely: "A database query references a column or table that doesn't exist. Common after migrations or in stale schema.", fix: "1. Run `rails db:migrate` to apply pending migrations\n2. Check `rails_get_schema(table:\"...\")` for actual column names\n3. Verify the migration was generated correctly" }, /Template::Error|ActionView/ => { type: :view_error, likely: "An error occurred while rendering a view template. The underlying error is usually a NoMethodError or missing local variable.", fix: "1. Check the instance variables set in the controller action\n2. Verify partial locals are passed correctly\n3. Use `rails_get_view(controller:\"...\")` to see template structure" }, /ArgumentError/ => { type: :argument_error, likely: "A method received wrong number or type of arguments.", fix: "1. Check the method signature matches the call site\n2. Use `rails_search_code(pattern:\"method_name\", match_type:\"trace\")` to see definition and callers" } }.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(error:, file: nil, line: nil, action: nil, server_context: nil) ⇒ Object
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 |
# File 'lib/rails_ai_context/tools/diagnose.rb', line 105 def self.call(error:, file: nil, line: nil, action: nil, server_context: nil) return text_response("The `error` parameter is required.") if error.nil? || error.strip.empty? parsed = parse_error(error) classification = classify_error(parsed) lines = [ "# Error Diagnosis", "" ] lines << "**Error:** `#{parsed[:exception_class] || 'Unknown'}`" lines << "**Message:** #{parsed[:message]}" lines << "**Classification:** #{classification[:type]}" lines << "" # Likely cause — enriched with specific inference when possible lines << "## Likely Cause" lines << classification[:likely] specific = infer_specific_cause(parsed, classification, file, action) lines << "" << "**Specific:** #{specific}" if specific lines << "" # Suggested fix lines << "## Suggested Fix" lines << classification[:fix] lines << "" # Gather context based on parameters and error type context_sections = gather_context(parsed, classification, file, line, action) # Recent git changes git_section = gather_git_context(file, parsed[:file_refs]) # Recent error logs log_section = gather_log_context(parsed[:exception_class]) # Truncate large sections before assembling final output context_sections = truncate_section(context_sections, "Controller Context", MAX_SECTION_CHARS[:controller_context]) context_sections = truncate_section(context_sections, "Code Context", MAX_SECTION_CHARS[:code_context]) context_sections = truncate_section(context_sections, "Schema Context", MAX_SECTION_CHARS[:schema_context]) context_sections = truncate_section(context_sections, "Model Context", MAX_SECTION_CHARS[:model_context]) context_sections = truncate_section(context_sections, "Method Trace", MAX_SECTION_CHARS[:method_trace]) git_section = truncate_section(git_section, "Recent Git Changes", MAX_SECTION_CHARS[:git_changes]) log_section = truncate_section(log_section, "Recent Error Logs", MAX_SECTION_CHARS[:logs]) lines.concat(context_sections) lines.concat(git_section) if git_section.any? lines.concat(log_section) if log_section.any? # Next steps lines << "## Next Steps" if file lines << "_Use `rails_get_edit_context(file:\"#{file}\", near:\"#{parsed[:method_name] || line || parsed[:exception_class]}\")` to see the code._" end if parsed[:method_name] lines << "_Use `rails_search_code(pattern:\"#{parsed[:method_name]}\", match_type:\"trace\")` to trace the method._" end output = lines.join("\n") # Final safety cap: if total output still exceeds limit, hard-truncate if output.length > MAX_TOTAL_OUTPUT output = output[0, MAX_TOTAL_OUTPUT] + "\n\n_... output truncated (#{output.length} chars exceeded #{MAX_TOTAL_OUTPUT} limit)._" end text_response(output) rescue => e text_response("Diagnosis error: #{e.}") end |