Class: RailsAiContext::Tools::GetConventions

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

Constant Summary collapse

ARCH_LABELS =
{
  "api_only" => "API-only mode (no views/assets)",
  "hotwire" => "Hotwire (Turbo + Stimulus)",
  "graphql" => "GraphQL API (app/graphql/)",
  "grape_api" => "Grape API framework (app/api/)",
  "service_objects" => "Service objects pattern (app/services/)",
  "form_objects" => "Form objects (app/forms/)",
  "query_objects" => "Query objects (app/queries/)",
  "presenters" => "Presenters/Decorators",
  "view_components" => "ViewComponent (app/components/)",
  "stimulus" => "Stimulus controllers (app/javascript/controllers/)",
  "importmaps" => "Import maps (no JS bundler)",
  "docker" => "Dockerized",
  "kamal" => "Kamal deployment",
  "ci_github_actions" => "GitHub Actions CI"
}.freeze
PATTERN_LABELS =
{
  "sti" => "Single Table Inheritance (STI)",
  "polymorphic" => "Polymorphic associations",
  "soft_delete" => "Soft deletes (paranoia/discard)",
  "versioning" => "Model versioning/auditing",
  "state_machine" => "State machines (AASM/workflow)",
  "multi_tenancy" => "Multi-tenancy",
  "searchable" => "Full-text search (Searchkick/pg_search/Ransack)",
  "taggable" => "Tagging",
  "sluggable" => "Friendly URLs/slugs",
  "nested_set" => "Tree/nested set structures"
}.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(server_context: nil) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
# File 'lib/rails_ai_context/tools/get_conventions.rb', line 15

def self.call(server_context: nil)
  conventions = cached_context[:conventions]
  return text_response("Convention detection not available. Add :conventions to introspectors.") unless conventions
  return text_response("Convention detection failed: #{conventions[:error]}") if conventions[:error]

  lines = [ "# App Conventions & Architecture", "" ]

  # Architecture
  if conventions[:architecture]&.any?
    lines << "## Architecture"
    conventions[:architecture].each { |a| lines << "- #{humanize_arch(a)}" }
  end

  # Patterns
  if conventions[:patterns]&.any?
    lines << "" << "## Detected patterns"
    conventions[:patterns].each { |p| lines << "- #{humanize_pattern(p)}" }
  end

  # Directory structure
  if conventions[:directory_structure]&.any?
    lines << "" << "## Directory structure"
    conventions[:directory_structure].sort_by { |k, _| k }.each do |dir, count|
      lines << "- `#{dir}/` → #{count} files"
    end
  end

  # Frontend stack from package.json
  frontend = detect_frontend_stack
  if frontend.any?
    lines << "" << "## Frontend stack"
    frontend.each { |f| lines << "- #{f}" }
  end

  # App-specific patterns detected from controller source
  app_patterns = detect_app_patterns
  if app_patterns.any?
    lines << "" << "## App Patterns"
    app_patterns.each { |section| lines << section }
  end

  # Custom directories
  if conventions[:custom_directories]&.any?
    lines << "" << "## Custom Directories"
    conventions[:custom_directories].each do |dir, desc|
      lines << "- `#{dir}/` — #{desc}"
    end
  end

  # Config files — only show non-obvious ones (skip files every Rails app has)
  if conventions[:config_files]&.any?
    obvious = %w[
      config/application.rb config/puma.rb config/locales/en.yml
      Gemfile package.json Rakefile
    ]
    notable = conventions[:config_files].reject { |f| obvious.include?(f) }
    if notable.any?
      lines << "" << "## Notable config files"
      notable.each { |f| lines << "- `#{f}`" }
    end
  end

  # I18n / Locale info
  locale_info = detect_locale_info
  if locale_info.any?
    lines << "" << "## I18n"
    locale_info.each { |l| lines << "- #{l}" }
  end

  # Convention Fingerprint — one-paragraph summary of the app's detected conventions
  fingerprint_parts = []
  fingerprint_parts << conventions[:architecture].map { |a| humanize_arch(a) }.join(", ") if conventions[:architecture]&.any?
  fingerprint_parts << conventions[:patterns].map { |p| humanize_pattern(p) }.join(", ") if conventions[:patterns]&.any?
  top_dirs = conventions[:directory_structure]&.sort_by { |_, v| -v }&.first(5)&.map { |k, _| k }
  fingerprint_parts << "key dirs: #{top_dirs.join(', ')}" if top_dirs&.any?
  if fingerprint_parts.any?
    lines << "" << "## Convention Fingerprint"
    lines << "This app uses #{fingerprint_parts.join('; ')}."
  end

  text_response(lines.join("\n"))
end