Class: RailsAiBridge::Generators::InstallGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Defined in:
lib/generators/rails_ai_bridge/install/install_generator.rb

Instance Method Summary collapse

Instance Method Details

#add_to_gitignorevoid

This method returns an undefined value.

Appends rails-ai-bridge-specific entries to .gitignore when the file exists and the entries are not already present. Does nothing if .gitignore is absent. Uses Thor's #append_to_file so the operation respects +--pretend+ (dry-run).



163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/generators/rails_ai_bridge/install/install_generator.rb', line 163

def add_to_gitignore
  gitignore_path = '.gitignore'
  gitignore_full = File.join(destination_root, gitignore_path)
  return unless File.exist?(gitignore_full)

  content = File.read(gitignore_full)
  append = []
  append << '.ai-context.json' unless content.include?('.ai-context.json')

  return unless append.any?

  append_to_file gitignore_path, "\n# rails-ai-bridge (JSON cache — markdown files should be committed)\n#{append.join("\n")}\n"
  say 'Updated .gitignore', :green
end

#create_assistant_overrides_templatevoid

This method returns an undefined value.

Creates the config/rails_ai_bridge/ directory with an overrides.md stub and an overrides.md.example reference file. Skips each file if it already exists so the method is safe to re-run (idempotent).



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/generators/rails_ai_bridge/install/install_generator.rb', line 137

def create_assistant_overrides_template
  dir = 'config/rails_ai_bridge'
  empty_directory dir

  stub = File.join(destination_root, dir, 'overrides.md')
  unless File.exist?(stub)
    create_file "#{dir}/overrides.md", <<~MD
      <!-- rails-ai-bridge:omit-merge -->

    MD
    say "Created #{dir}/overrides.md (stub — remove omit-merge line when adding real rules)", :green
  end

  example = File.join(destination_root, dir, 'overrides.md.example')
  return if File.exist?(example)

  copy_file 'overrides.md.example', "#{dir}/overrides.md.example"
  say "Created #{dir}/overrides.md.example (reference outline, not merged)", :green
end

#create_initializervoid

This method returns an undefined value.

Creates config/initializers/rails_ai_bridge.rb containing a commented configuration guide for rails-ai-bridge. The generated initializer documents introspector presets (with interpolated counts), options for enabling/disabling introspectors, security exclusions (tables, models, paths), primary domain model hints, context/output controls, assistant override guidance, and a SECURITY CRITICAL HTTP MCP / auto_mount section with recommended authentication approaches.



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
# File 'lib/generators/rails_ai_bridge/install/install_generator.rb', line 45

def create_initializer
  standard_count = RailsAiBridge::Configuration::PRESETS[:standard].size
  full_count     = RailsAiBridge::Configuration::PRESETS[:full].size
  regulated_count = RailsAiBridge::Configuration::PRESETS[:regulated].size

  create_file 'config/initializers/rails_ai_bridge.rb', <<~RUBY
    # frozen_string_literal: true

    RailsAiBridge.configure do |config|
      # --- Introspector preset ---
      #   :standard  — #{standard_count} core introspectors (schema, models, routes, jobs, gems, conventions, controllers, tests, migrations)
      #   :full      — all #{full_count} introspectors (adds views, turbo, auth, API, config, assets, devops, etc.)
      #   :regulated — #{regulated_count} introspectors — no schema/models/migrations (for apps with strict data governance)
      # config.preset = :standard

      # Or cherry-pick individual introspectors:
      # config.introspectors += %i[non_ar_models views turbo auth api]

      # Disable whole product categories at runtime (schema + models + migrations, optional :non_ar_models, api, views/turbo/i18n):
      # config.disabled_introspection_categories << :domain_metadata

      # --- Security exclusions ---
      # Tables to hide from schema + model introspection (exact name or glob, e.g. "pii_*"):
      # config.excluded_tables += %w[secrets audit_logs pii_*]

      # Models to exclude from introspection:
      # config.excluded_models += %w[AdminUser InternalThing]

      # Primary domain models (semantic tier: core_entity in introspection & Claude rules):
      # config.core_models += %w[User Order Project]

      # Paths excluded from rails_search_code:
      # config.excluded_paths += %w[vendor/bundle]

      # --- Context output ---
      # :compact — ≤150 lines, references MCP tools for details (default)
      # :full    — full dump (good for small apps)
      # config.context_mode = :compact
      # config.claude_max_lines = 150
      # config.max_tool_response_chars = 120_000

      # Team rules merged into compact Copilot/Codex output (remove omit-merge line when ready):
      # config.assistant_overrides_path = "config/rails_ai_bridge/overrides.md"

      # Compact model list caps (0 = MCP pointer only, no names listed):
      # config.copilot_compact_model_list_limit = 5
      # config.codex_compact_model_list_limit = 3

      # ==========================================================================
      # HTTP MCP / auto_mount — SECURITY CRITICAL
      # ==========================================================================
      # Exposes read-only MCP tools over HTTP. Still reveals routes, schema, and
      # code layout — treat as sensitive. Prefer stdio (`rails ai:serve`) for local
      # AI clients.
      #
      # In production you MUST configure one auth mechanism AND set
      # allow_auto_mount_in_production = true. Options (highest priority first):
      #
      #   1. JWT decoder (no JWT gem required — supply your own lambda):
      #      config.mcp_jwt_decoder = ->(token) {
      #        JWT.decode(token, credentials.jwt_secret, true, algorithm: "HS256").first
      #      rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::ImmatureSignature
      #        nil
      #      }
      #
      #   2. Token resolver (Devise, database lookup, etc.):
      #      config.mcp_token_resolver = ->(token) { User.find_by(mcp_api_token: token) }
      #
      #   3. Static shared secret:
      #      config.http_mcp_token = "generate-a-long-random-secret"
      #      # ENV["RAILS_AI_BRIDGE_MCP_TOKEN"] overrides this when set
      #
      # IMPORTANT: Token comparison is timing-safe but does NOT prevent
      # brute-force guessing. Add rate limiting on the MCP endpoint in
      # production (e.g. Rack::Attack throttle on config.http_path).
      #
      # config.auto_mount = false
      # config.allow_auto_mount_in_production = false
      # config.http_path = "/mcp"
      # config.http_port = 6029
    end
  RUBY

  say 'Created config/initializers/rails_ai_bridge.rb', :green
end

#create_mcp_configvoid

This method returns an undefined value.

Creates a .mcp.json MCP server definition named "rails-ai-bridge". The created file configures an MCP server that runs bundle exec rails ai:serve and is intended for auto-discovery by tools such as Claude Code and Cursor.



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/generators/rails_ai_bridge/install/install_generator.rb', line 23

def create_mcp_config
  mcp_config = {
    mcpServers: {
      'rails-ai-bridge' => {
        command: 'bundle',
        args: ['exec', 'rails', 'ai:serve']
      }
    }
  }
  create_file '.mcp.json', "#{JSON.pretty_generate(mcp_config)}\n"

  say 'Created .mcp.json (auto-discovered by Claude Code, Cursor, etc.)', :green
end

#generate_context_filesvoid

This method returns an undefined value.

Calls RailsAiBridge.generate_context to write initial bridge files (CLAUDE.md, .cursorrules, etc.) according to the selected install profile. Skipped when +Rails.application+ is not available. Any StandardError is rescued and reported with the error class only — no raw message or sensitive path/credential details are printed or logged. +Rails.logger.debug+ receives only the exception class name and a short 12-character fingerprint derived from it, never the raw exception message.



187
188
189
190
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
# File 'lib/generators/rails_ai_bridge/install/install_generator.rb', line 187

def generate_context_files
  say ''
  say 'Generating AI bridge files...', :yellow

  return handle_skip_context if options[:skip_context]
  return handle_no_rails_app unless Rails.application

  begin
    profile = resolve_profile
  rescue ArgumentError
    say '  Run `rails generate rails_ai_bridge:install --profile=minimal` (or full/custom/mcp).', :yellow
    return
  end
  @selected_profile = profile

  case profile
  when 'mcp'
    say '  Skipped (MCP-only profile). Run `rails ai:bridge` to generate context files later.', :yellow
    return
  when 'minimal', 'full'
    formats = ProfileResolver.formats_for(profile)
    split_rules = ProfileResolver.split_rules_for(profile)
    return generate_context_for_formats(formats, split_rules: split_rules)
  end

  return say('  Skipped. Run `rails ai:bridge` to generate context files later.', :yellow) unless yes?('Generate AI assistant context files? (y/n)')

  formats = collect_selected_formats
  return say('  No formats selected. Run `rails ai:bridge` to generate context files later.', :yellow) if formats.empty?

  generate_context_for_formats(formats, split_rules: true)
end

#show_instructionsvoid

This method returns an undefined value.

Prints post-install usage instructions to stdout: available rake tasks, generated file locations per AI assistant, MCP auto-discovery notes, and bridge mode options.



225
226
227
228
229
# File 'lib/generators/rails_ai_bridge/install/install_generator.rb', line 225

def show_instructions
  return show_skip_context_instructions if options[:skip_context]

  show_full_instructions
end