Class: RailsAiContext::Tools::ValidateSemantics

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

Overview

Semantic (Rails-aware) half of the rails_validate tool: the Prism visitor and the per-rule checks it feeds. Split from Validate, which keeps syntax validation and orchestration, so a new rule lands here without touching the tool entry point.

Inherits BaseTool for cached_context only; abstract! keeps it out of the MCP tool registry.

Defined Under Namespace

Classes: RailsSemanticVisitor

Constant Summary collapse

ASSET_HELPER_PREFIXES =

── CHECK 2: Route helpers (AST) ─────────────────────────────────

%w[image asset font stylesheet javascript audio video file compute_asset auto_discovery_link favicon].freeze
DEVISE_HELPER_NAMES =
%w[session registration password confirmation unlock omniauth_callback user_session user_registration user_password user_confirmation user_unlock].freeze
MEMORY_LOAD_METHODS =

── CHECK: Memory-loading anti-pattern ───────────────────────────

%w[map filter_map flat_map select reject collect reduce inject each_with_object].freeze

Constants inherited from BaseTool

BaseTool::DEFAULT_SESSION, BaseTool::MAX_SESSIONS, BaseTool::MAX_SESSION_ID_LENGTH, BaseTool::SESSION_CONTEXT, BaseTool::SHARED_CACHE

Class Method Summary collapse

Methods inherited from BaseTool

abstract!, abstract?, api_only_app?, api_only_note, cache_key, cached_context, config, current_session, #dedupe_put_patch_routes, detail_param?, error_response, evict_oldest_sessions, extract_method_source_from_file, extract_method_source_from_string, find_closest_match, fuzzy_find_key, guide_row, inherited, introspection_warnings_note, invalid_detail_note, normalize_detail, not_found_response, paginate, rails_app, rails_env_name, registered_tools, reset_all_caches!, reset_cache!, session_from, session_params, session_queries, session_record, session_reset!, static_tier_banner, static_tier_refusal, text_response, touch_session, unavailable_note, with_session, with_session_for

Methods included from SectionFetch

#fetch_section, usable?

Methods included from CountPhrase

call

Class Method Details

.check_brakeman_security(files) ⇒ Object

── Brakeman security scan (runs once for all files) ───────────



857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
# File 'lib/rails_ai_context/tools/validate_semantics.rb', line 857

def self.check_brakeman_security(files)
  return [] unless brakeman_available?

  tracker = Brakeman.run(
    app_path: rails_app.root.to_s,
    quiet: true,
    report_progress: false,
    print_report: false
  )

  warnings = tracker.filtered_warnings
  return [] if warnings.empty?

  # Filter to only warnings in the validated files
  normalized = files.map { |f| f.delete_prefix("/") }
  relevant = warnings.select do |w|
    path = w.file.relative
    normalized.any? { |f| path == f || path.start_with?(f) }
  end
  return [] if relevant.empty?

  relevant.sort_by(&:confidence).first(5).map do |w|
    loc = w.line ? "#{w.file.relative}:#{w.line}" : w.file.relative
    "[#{w.confidence_name}] #{w.warning_type} - #{loc}: #{w.message}"
  end
rescue => e
  $stderr.puts "[rails-ai-context] check_brakeman_security failed: #{e.message}" if ENV["DEBUG"]
  []
end

.check_rails_semantics(file, full_path) ⇒ Object

── Semantic check dispatcher ────────────────────────────────────



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/rails_ai_context/tools/validate_semantics.rb', line 191

def self.check_rails_semantics(file, full_path)
  warnings = []

  context = begin; cached_context; rescue; return warnings; end
  return warnings unless context

  content = RailsAiContext::SafeFile.read(full_path)
  return warnings unless content

  # Parse with Prism AST visitor (single pass for all checks)
  visitor = parse_and_visit(file, content)

  if file.end_with?(".html.erb", ".erb")
    if visitor
      warnings.concat(check_partial_existence_ast(file, visitor))
      warnings.concat(check_route_helpers_ast(file, visitor, context))
    else
      warnings.concat(check_partial_existence_regex(file, content))
      warnings.concat(check_route_helpers_regex(file, content, context))
    end
    warnings.concat(check_stimulus_controllers(content, context))
    warnings.concat(check_instance_variable_usage(file, content, context))
    warnings.concat(check_respond_to_template_existence(file, content))
  elsif file.end_with?(".rb")
    if visitor
      warnings.concat(check_route_helpers_ast(file, visitor, context))
      warnings.concat(check_partial_existence_ast(file, visitor, qualified_only: true))
      warnings.concat(check_column_references_ast(file, visitor, context))
      warnings.concat(check_strong_params_ast(file, visitor, context))
      warnings.concat(check_callback_existence_ast(file, visitor, context))
    else
      warnings.concat(check_route_helpers_regex(file, content, context))
      warnings.concat(check_column_references_regex(file, content, context))
    end
    # Cache-only checks (no AST needed)
    warnings.concat(check_has_many_dependent(file, context))
    warnings.concat(check_missing_fk_index(file, context))
    warnings.concat(check_route_action_consistency(file, context))
    warnings.concat(check_turbo_stream_channels(file, content, context))
    warnings.concat(check_memory_loading(file, content)) if file.start_with?("app/controllers/")
  end

  # Performance checks from performance introspector
  warnings.concat(check_performance_warnings(file, context))

  warnings
end