rails-ai-context 5.32.2 → 5.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1491 -0
- data/CONTEXT.md +27 -3
- data/README.md +16 -12
- data/Rakefile +6 -0
- data/app/controllers/rails_ai_context/mcp_controller.rb +112 -84
- data/config/routes.rb +2 -3
- data/docs/ARCHITECTURE.md +7 -5
- data/docs/CLI.md +79 -6
- data/docs/COMPATIBILITY.md +75 -1
- data/docs/CONFIGURATION.md +30 -11
- data/docs/CUSTOM_TOOLS.md +2 -1
- data/docs/FAQ.md +16 -6
- data/docs/GUIDE.md +117 -71
- data/docs/INTROSPECTORS.md +10 -8
- data/docs/QUICKSTART.md +3 -3
- data/docs/RECIPES.md +1 -1
- data/docs/SECURITY.md +109 -25
- data/docs/SETUP.md +30 -6
- data/docs/STANDALONE.md +10 -3
- data/docs/TOOLS.md +152 -34
- data/docs/TROUBLESHOOTING.md +90 -12
- data/docs/adr/0003-shared-tool-cache-semantics.md +8 -0
- data/docs/adr/0005-workspace-mcp-entries.md +39 -0
- data/exe/rails-ai-context +733 -104
- data/lib/generators/rails_ai_context/install/install_generator.rb +341 -125
- data/lib/rails_ai_context/app_kind.rb +18 -5
- data/lib/rails_ai_context/ast_cache.rb +7 -0
- data/lib/rails_ai_context/base_mixins.rb +8 -8
- data/lib/rails_ai_context/boot_manager.rb +22 -3
- data/lib/rails_ai_context/change_watch.rb +14 -11
- data/lib/rails_ai_context/cli/app_root.rb +401 -0
- data/lib/rails_ai_context/cli/entry_boot.rb +58 -22
- data/lib/rails_ai_context/cli/tool_runner.rb +47 -21
- data/lib/rails_ai_context/code_reloader.rb +115 -1
- data/lib/rails_ai_context/concern_membership.rb +3 -1
- data/lib/rails_ai_context/concern_paths.rb +8 -3
- data/lib/rails_ai_context/configuration.rb +41 -9
- data/lib/rails_ai_context/context_file_report.rb +19 -2
- data/lib/rails_ai_context/database_file.rb +26 -0
- data/lib/rails_ai_context/doctor.rb +1122 -184
- data/lib/rails_ai_context/engine.rb +18 -1
- data/lib/rails_ai_context/erb_source.rb +19 -0
- data/lib/rails_ai_context/executable.rb +21 -0
- data/lib/rails_ai_context/facts_formatter.rb +5 -2
- data/lib/rails_ai_context/file_walk.rb +61 -10
- data/lib/rails_ai_context/fingerprinter/snapshot.rb +234 -0
- data/lib/rails_ai_context/fingerprinter.rb +40 -12
- data/lib/rails_ai_context/gem_lock.rb +186 -27
- data/lib/rails_ai_context/git_ignore.rb +22 -0
- data/lib/rails_ai_context/install/ai_tool.rb +31 -5
- data/lib/rails_ai_context/install/cleanup.rb +21 -0
- data/lib/rails_ai_context/install/program.rb +209 -35
- data/lib/rails_ai_context/install/selection_record.rb +258 -97
- data/lib/rails_ai_context/install/surface.rb +6 -1
- data/lib/rails_ai_context/install/validation_hook.rb +282 -0
- data/lib/rails_ai_context/install/workspace.rb +166 -0
- data/lib/rails_ai_context/install_mode.rb +71 -13
- data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/action_presence.rb +11 -6
- data/lib/rails_ai_context/introspectors/action_source.rb +182 -0
- data/lib/rails_ai_context/introspectors/action_text_introspector.rb +1 -2
- data/lib/rails_ai_context/introspectors/active_record_settings.rb +1 -1
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -3
- data/lib/rails_ai_context/introspectors/active_support_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/admin_resources.rb +1 -1
- data/lib/rails_ai_context/introspectors/anyway_configs.rb +1 -1
- data/lib/rails_ai_context/introspectors/api_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +15 -10
- data/lib/rails_ai_context/introspectors/call_site_expansion.rb +24 -1
- data/lib/rails_ai_context/introspectors/component_introspector.rb +3 -2
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +387 -60
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +15 -3
- data/lib/rails_ai_context/introspectors/devops_introspector.rb +3 -1
- data/lib/rails_ai_context/introspectors/eager_load.rb +201 -9
- data/lib/rails_ai_context/introspectors/engine_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/env_config_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/env_references.rb +10 -1
- data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +23 -10
- data/lib/rails_ai_context/introspectors/grape_endpoints.rb +1 -1
- data/lib/rails_ai_context/introspectors/habtm_join_tables.rb +1 -1
- data/lib/rails_ai_context/introspectors/helper_names.rb +1 -1
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/job_introspector.rb +8 -6
- data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +12 -8
- data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +78 -4
- data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +1 -0
- data/lib/rails_ai_context/introspectors/listeners/env_access_listener.rb +10 -1
- data/lib/rails_ai_context/introspectors/listeners/foreign_evals_listener.rb +61 -0
- data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +13 -4
- data/lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb +2 -1
- data/lib/rails_ai_context/introspectors/listeners/migration_replay_listener.rb +3 -2
- data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +29 -8
- data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +2 -2
- data/lib/rails_ai_context/introspectors/listeners/scopes_listener.rb +88 -12
- data/lib/rails_ai_context/introspectors/middleware_introspector.rb +2 -2
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +27 -0
- data/lib/rails_ai_context/introspectors/migration_replay/helpers.rb +1 -1
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +83 -27
- data/lib/rails_ai_context/introspectors/migration_replay.rb +15 -14
- data/lib/rails_ai_context/introspectors/model_introspector.rb +231 -23
- data/lib/rails_ai_context/introspectors/n_plus_one_scan.rb +620 -0
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +11 -174
- data/lib/rails_ai_context/introspectors/route_introspector.rb +10 -1
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +23 -3
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +66 -16
- data/lib/rails_ai_context/introspectors/schema_reader.rb +4 -3
- data/lib/rails_ai_context/introspectors/seeds_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_scan.rb +39 -9
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +15 -6
- data/lib/rails_ai_context/introspectors/test_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +131 -12
- data/lib/rails_ai_context/introspectors/view_introspector.rb +4 -4
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +15 -1
- data/lib/rails_ai_context/legacy_cleanup.rb +17 -4
- data/lib/rails_ai_context/live_reload.rb +20 -20
- data/lib/rails_ai_context/mcp_config_generator.rb +916 -109
- data/lib/rails_ai_context/mcp_edge.rb +182 -32
- data/lib/rails_ai_context/middleware.rb +5 -1
- data/lib/rails_ai_context/migration_status.rb +88 -25
- data/lib/rails_ai_context/output_guard.rb +48 -2
- data/lib/rails_ai_context/package_json.rb +1 -1
- data/lib/rails_ai_context/path_resolver.rb +46 -13
- data/lib/rails_ai_context/payload.rb +92 -8
- data/lib/rails_ai_context/pending_migrations.rb +5 -1
- data/lib/rails_ai_context/portable_path.rb +3 -2
- data/lib/rails_ai_context/presets.rb +15 -11
- data/lib/rails_ai_context/redaction.rb +262 -51
- data/lib/rails_ai_context/resources.rb +55 -20
- data/lib/rails_ai_context/route_coverage.rb +8 -0
- data/lib/rails_ai_context/safe_file.rb +14 -1
- data/lib/rails_ai_context/safe_path.rb +101 -4
- data/lib/rails_ai_context/schema_adapter.rb +8 -0
- data/lib/rails_ai_context/schema_version.rb +9 -0
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +88 -33
- data/lib/rails_ai_context/serializers/compact_serializer_helper.rb +36 -34
- data/lib/rails_ai_context/serializers/context_file_serializer.rb +21 -29
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +8 -6
- data/lib/rails_ai_context/serializers/copilot_serializer.rb +2 -7
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +19 -16
- data/lib/rails_ai_context/serializers/guide_examples.rb +142 -0
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +146 -30
- data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +38 -15
- data/lib/rails_ai_context/serializers/section_facts.rb +44 -1
- data/lib/rails_ai_context/serializers/section_marker_writer.rb +94 -12
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +24 -12
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +244 -129
- data/lib/rails_ai_context/server.rb +304 -71
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +447 -372
- data/lib/rails_ai_context/tools/analyze_feature.rb +72 -18
- data/lib/rails_ai_context/tools/argument_schema.rb +49 -0
- data/lib/rails_ai_context/tools/base_tool.rb +367 -46
- data/lib/rails_ai_context/tools/brakeman_guard.rb +24 -0
- data/lib/rails_ai_context/tools/dependency_graph.rb +63 -28
- data/lib/rails_ai_context/tools/diagnose.rb +218 -11
- data/lib/rails_ai_context/tools/generate_test.rb +339 -39
- data/lib/rails_ai_context/tools/get_callbacks.rb +24 -13
- data/lib/rails_ai_context/tools/get_component_catalog.rb +1 -1
- data/lib/rails_ai_context/tools/get_concern.rb +91 -27
- data/lib/rails_ai_context/tools/get_config.rb +13 -4
- data/lib/rails_ai_context/tools/get_context.rb +71 -14
- data/lib/rails_ai_context/tools/get_controllers.rb +179 -44
- data/lib/rails_ai_context/tools/get_conventions.rb +28 -6
- data/lib/rails_ai_context/tools/get_edit_context.rb +27 -14
- data/lib/rails_ai_context/tools/get_env.rb +18 -9
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +3 -1
- data/lib/rails_ai_context/tools/get_gems.rb +1 -1
- data/lib/rails_ai_context/tools/get_helper_methods.rb +10 -6
- data/lib/rails_ai_context/tools/get_i18n.rb +1 -1
- data/lib/rails_ai_context/tools/get_job_pattern.rb +3 -2
- data/lib/rails_ai_context/tools/get_mailers.rb +3 -3
- data/lib/rails_ai_context/tools/get_model_details.rb +30 -10
- data/lib/rails_ai_context/tools/get_partial_interface.rb +242 -23
- data/lib/rails_ai_context/tools/get_routes.rb +29 -8
- data/lib/rails_ai_context/tools/get_schema.rb +52 -19
- data/lib/rails_ai_context/tools/get_service_pattern.rb +24 -10
- data/lib/rails_ai_context/tools/get_stimulus.rb +37 -1
- data/lib/rails_ai_context/tools/get_test_info.rb +18 -4
- data/lib/rails_ai_context/tools/get_turbo_map.rb +221 -58
- data/lib/rails_ai_context/tools/get_view.rb +14 -14
- data/lib/rails_ai_context/tools/migration_advisor.rb +355 -48
- data/lib/rails_ai_context/tools/onboard.rb +52 -8
- data/lib/rails_ai_context/tools/performance_check.rb +5 -3
- data/lib/rails_ai_context/tools/query.rb +914 -86
- data/lib/rails_ai_context/tools/read_logs.rb +69 -15
- data/lib/rails_ai_context/tools/review_changes.rb +215 -53
- data/lib/rails_ai_context/tools/runtime_info.rb +51 -1
- data/lib/rails_ai_context/tools/safe_call.rb +35 -14
- data/lib/rails_ai_context/tools/search_code.rb +243 -67
- data/lib/rails_ai_context/tools/search_docs.rb +4 -4
- data/lib/rails_ai_context/tools/security_scan.rb +143 -33
- data/lib/rails_ai_context/tools/session_context.rb +39 -5
- data/lib/rails_ai_context/tools/validate.rb +259 -40
- data/lib/rails_ai_context/tools/validate_semantics.rb +130 -56
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/vfs.rb +67 -40
- data/lib/rails_ai_context/view_file.rb +41 -3
- data/lib/rails_ai_context/watcher.rb +51 -13
- data/lib/rails_ai_context.rb +21 -0
- data/server.json +1 -1
- metadata +14 -1
|
@@ -74,8 +74,11 @@ module RailsAiContext
|
|
|
74
74
|
stats = []
|
|
75
75
|
schema = Payload.section(ctx, :schema)
|
|
76
76
|
if schema
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
own = own_secondary_databases(ctx, schema)
|
|
78
|
+
table_count = schema[:total_tables].to_i + own.sum { |db| db[:tables] }
|
|
79
|
+
if table_count > 0
|
|
80
|
+
stats << count_phrase(table_count, "table") + (own.any? ? " in #{count_phrase(own.size + 1, "database")}" : "")
|
|
81
|
+
end
|
|
79
82
|
end
|
|
80
83
|
|
|
81
84
|
models = Payload.models(ctx)
|
|
@@ -137,6 +140,10 @@ module RailsAiContext
|
|
|
137
140
|
# Prefer live adapter from config over static_parse from schema introspector
|
|
138
141
|
adapter = RailsAiContext::SchemaAdapter.label(ctx)
|
|
139
142
|
db = "#{adapter} (#{count_phrase(schema[:total_tables].to_i, 'table')})"
|
|
143
|
+
# Every database the app's own dumps declare, as rails_get_schema reads them.
|
|
144
|
+
own_secondary_databases(ctx, schema).each do |other|
|
|
145
|
+
db += ", plus the #{other[:name]} database#{" on #{other[:adapter]}" if other[:adapter]} (#{count_phrase(other[:tables], 'table')})"
|
|
146
|
+
end
|
|
140
147
|
elsif RailsAiContext::AppKind.mongoid?(rails_app.root)
|
|
141
148
|
database = RailsAiContext::AppKind.mongoid_database(rails_app.root)
|
|
142
149
|
db = "MongoDB through Mongoid#{" (database #{database})" if database}"
|
|
@@ -147,6 +154,10 @@ module RailsAiContext
|
|
|
147
154
|
end
|
|
148
155
|
rails = named_rails_version(ctx)
|
|
149
156
|
lines << "#{ctx[:app_name]} is a Rails#{" #{rails}" if rails} application#{ruby_clause(ctx)} #{db ? "on #{db}" : "without Active Record"}."
|
|
157
|
+
solid = schema ? solid_databases(schema) : {}
|
|
158
|
+
if solid.any?
|
|
159
|
+
lines << "#{solid.values.uniq.to_sentence} keep#{"s" if solid.values.uniq.one?} their tables in databases of their own: #{solid.keys.join(", ")}."
|
|
160
|
+
end
|
|
150
161
|
unread = unread_bundle(rails)
|
|
151
162
|
lines << unread if unread
|
|
152
163
|
|
|
@@ -167,6 +178,25 @@ module RailsAiContext
|
|
|
167
178
|
lines
|
|
168
179
|
end
|
|
169
180
|
|
|
181
|
+
# {database name => the framework whose tables are all it holds}: the
|
|
182
|
+
# queue, cache and cable databases Rails 8 gives Solid Queue, Solid
|
|
183
|
+
# Cache and Solid Cable are infrastructure, not the app's data.
|
|
184
|
+
def solid_databases(schema)
|
|
185
|
+
Payload.framework_databases(schema).to_h { |name, info| [ name.to_s, Payload.database_framework(info) ] }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# The app's own databases besides the primary, as its schema dumps
|
|
189
|
+
# declare them: metrics71's analytics database on SQLite went
|
|
190
|
+
# uncounted beside its PostgreSQL primary. Each is
|
|
191
|
+
# { name:, adapter:, tables: }, the adapter nil when database.yml does not say.
|
|
192
|
+
def own_secondary_databases(ctx, schema)
|
|
193
|
+
configured = Array(Payload.section(ctx, :multi_database)&.dig(:databases)).grep(Hash).to_h { |d| [ d[:name].to_s, d ] }
|
|
194
|
+
Payload.app_databases(schema).map do |name, db|
|
|
195
|
+
{ name: name.to_s, adapter: RailsAiContext::SchemaAdapter.database_label(configured[name.to_s]),
|
|
196
|
+
tables: (db[:total_tables] || db[:tables].size).to_i }
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
170
200
|
def section_data_model(ctx)
|
|
171
201
|
models = Payload.models(ctx)
|
|
172
202
|
return [] unless models.any?
|
|
@@ -183,7 +213,11 @@ module RailsAiContext
|
|
|
183
213
|
assocs = Serializers::SectionFacts.associations_list(data)
|
|
184
214
|
validations = data[:validations] || []
|
|
185
215
|
desc = "**#{name}**"
|
|
186
|
-
|
|
216
|
+
if (note = Serializers::SectionFacts.table_note(Payload.section(ctx, :schema), data))
|
|
217
|
+
desc += " (table #{note})"
|
|
218
|
+
elsif data[:table_name]
|
|
219
|
+
desc += " (table: `#{data[:table_name]}`)"
|
|
220
|
+
end
|
|
187
221
|
desc += " - #{assocs.first(4).join(', ')}" if assocs.any?
|
|
188
222
|
desc += ", +#{assocs.size - 4} more" if assocs.size > 4
|
|
189
223
|
if validations.any?
|
|
@@ -265,7 +299,7 @@ module RailsAiContext
|
|
|
265
299
|
|
|
266
300
|
# Controllers with the most actions carry the app's key flows.
|
|
267
301
|
app_ctrls = RouteCoverage.app_controllers(routes)
|
|
268
|
-
top_ctrls = app_ctrls.sort_by { |_, routes_list| -routes_list.size }.first(5)
|
|
302
|
+
top_ctrls = app_ctrls.sort_by.with_index { |(_, routes_list), i| [ -routes_list.size, i ] }.first(5)
|
|
269
303
|
|
|
270
304
|
top_ctrls.each do |ctrl, ctrl_routes|
|
|
271
305
|
actions = ctrl_routes.map { |r| r[:action] }.compact.uniq
|
|
@@ -490,7 +524,13 @@ module RailsAiContext
|
|
|
490
524
|
|
|
491
525
|
payment_gems = %w[stripe pay braintree paddle_pay]
|
|
492
526
|
found = Payload.notable_gems(ctx).select { |g| payment_gems.include?(g[:name]) }
|
|
493
|
-
|
|
527
|
+
named = ->(name) { name.to_s.downcase.match?(/payment|subscription|charge|invoice|plan|billing/) }
|
|
528
|
+
# An STI subclass is a payment model by its parent: BankTransfer < Payment.
|
|
529
|
+
payment_models = models.keys.select do |m|
|
|
530
|
+
chain = [ m ]
|
|
531
|
+
chain << models.dig(chain.last, :parent_model) while models.dig(chain.last, :parent_model) && chain.size < 10
|
|
532
|
+
chain.compact.any?(&named)
|
|
533
|
+
end
|
|
494
534
|
return [] if found.empty? && payment_models.empty?
|
|
495
535
|
|
|
496
536
|
lines = [ "## Payments", "" ]
|
|
@@ -511,9 +551,13 @@ module RailsAiContext
|
|
|
511
551
|
has_content = true
|
|
512
552
|
end
|
|
513
553
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
554
|
+
# The broadcast_*_to calls as well as the model macros: bazaar's
|
|
555
|
+
# three explicit broadcasts went uncounted beside its one macro.
|
|
556
|
+
macros = Payload.model_broadcasts(ctx).size
|
|
557
|
+
calls = Payload.explicit_broadcasts(ctx).size
|
|
558
|
+
if macros + calls > 0
|
|
559
|
+
kinds = [ (count_phrase(macros, "model macro") if macros > 0), (count_phrase(calls, "broadcast call") if calls > 0) ].compact
|
|
560
|
+
lines << "Turbo Stream broadcasts: #{count_phrase(macros + calls, "broadcast point")} (#{kinds.join(', ')})."
|
|
517
561
|
has_content = true
|
|
518
562
|
end
|
|
519
563
|
streams = Payload.turbo_streams(ctx)
|
|
@@ -137,7 +137,7 @@ module RailsAiContext
|
|
|
137
137
|
return [] if filtered.empty?
|
|
138
138
|
|
|
139
139
|
# Sort by risk: high → medium → low
|
|
140
|
-
sorted = filtered.sort_by { |i| RISK_ORDER[
|
|
140
|
+
sorted = filtered.sort_by.with_index { |item, i| [ RISK_ORDER[item[:risk].to_s] || 99, i ] }
|
|
141
141
|
|
|
142
142
|
lines = [ "## N+1 Query Risks (#{sorted.size})#{risk_summary_counts(sorted)}", "" ]
|
|
143
143
|
|
|
@@ -153,12 +153,14 @@ module RailsAiContext
|
|
|
153
153
|
|
|
154
154
|
# One association can be at risk in several actions, and those are
|
|
155
155
|
# different findings. Without the call site on the row they read as
|
|
156
|
-
# one line printed twice, and the section count stops adding up.
|
|
156
|
+
# one line printed twice, and the section count stops adding up. The
|
|
157
|
+
# view is where the loop reads the association.
|
|
157
158
|
def call_site(item)
|
|
158
159
|
return "" unless item[:controller]
|
|
159
160
|
|
|
160
161
|
action = item[:action] ? "##{item[:action]}" : ""
|
|
161
|
-
"
|
|
162
|
+
view = item[:view] ? ", in #{item[:view]}" : ""
|
|
163
|
+
" (#{item[:controller]}#{action}#{view})"
|
|
162
164
|
end
|
|
163
165
|
|
|
164
166
|
def render_section(title, items, model_filter, detail)
|