rails-ai-context 5.30.2 → 5.32.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 +868 -0
- data/CONTEXT.md +7 -3
- data/README.md +1 -1
- data/docs/ARCHITECTURE.md +3 -3
- data/docs/CLI.md +1 -1
- data/docs/COMPATIBILITY.md +50 -14
- data/docs/CONFIGURATION.md +4 -1
- data/docs/FAQ.md +1 -1
- data/docs/GUIDE.md +14 -8
- data/docs/INTROSPECTORS.md +50 -23
- data/docs/SECURITY.md +56 -1
- data/docs/TOOLS.md +58 -12
- data/lib/generators/rails_ai_context/install/install_generator.rb +1 -0
- data/lib/rails_ai_context/action_filters.rb +121 -48
- data/lib/rails_ai_context/app_commands.rb +2 -2
- data/lib/rails_ai_context/app_kind.rb +65 -8
- data/lib/rails_ai_context/ast_cache.rb +53 -18
- data/lib/rails_ai_context/base_mixins.rb +46 -16
- data/lib/rails_ai_context/boot_manager.rb +5 -1
- data/lib/rails_ai_context/cli/entry_boot.rb +17 -2
- data/lib/rails_ai_context/cli/tool_runner.rb +15 -1
- data/lib/rails_ai_context/concern_macros.rb +791 -111
- data/lib/rails_ai_context/concern_membership.rb +25 -5
- data/lib/rails_ai_context/concern_paths.rb +63 -17
- data/lib/rails_ai_context/config_yaml.rb +40 -0
- data/lib/rails_ai_context/configuration.rb +3 -3
- data/lib/rails_ai_context/data/docs/index.json +324 -0
- data/lib/rails_ai_context/database_yml.rb +136 -0
- data/lib/rails_ai_context/doctor.rb +30 -37
- data/lib/rails_ai_context/erb_source.rb +40 -0
- data/lib/rails_ai_context/fingerprinter.rb +48 -22
- data/lib/rails_ai_context/fixture_keys.rb +42 -1
- data/lib/rails_ai_context/gem_lock.rb +275 -47
- data/lib/rails_ai_context/git_ignore.rb +13 -11
- data/lib/rails_ai_context/hydrators/hydration_formatter.rb +8 -2
- data/lib/rails_ai_context/hydrators/schema_hint_builder.rb +10 -5
- data/lib/rails_ai_context/introspector.rb +10 -1
- data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +27 -19
- data/lib/rails_ai_context/introspectors/action_presence.rb +11 -8
- data/lib/rails_ai_context/introspectors/action_resolver.rb +38 -26
- data/lib/rails_ai_context/introspectors/action_text_introspector.rb +2 -2
- data/lib/rails_ai_context/introspectors/active_record_settings.rb +56 -0
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/active_support_introspector.rb +98 -22
- data/lib/rails_ai_context/introspectors/admin_resources.rb +101 -0
- data/lib/rails_ai_context/introspectors/anyway_configs.rb +64 -0
- data/lib/rails_ai_context/introspectors/apartment_config.rb +47 -0
- data/lib/rails_ai_context/introspectors/api_introspector.rb +125 -26
- data/lib/rails_ai_context/introspectors/asset_pipeline_introspector.rb +24 -19
- data/lib/rails_ai_context/introspectors/ast_walk.rb +18 -0
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +71 -18
- data/lib/rails_ai_context/introspectors/autoload_introspector.rb +62 -15
- data/lib/rails_ai_context/introspectors/base.rb +38 -0
- data/lib/rails_ai_context/introspectors/call_site_expansion.rb +349 -39
- data/lib/rails_ai_context/introspectors/component_introspector.rb +26 -14
- data/lib/rails_ai_context/introspectors/config_introspector.rb +22 -9
- data/lib/rails_ai_context/introspectors/connection_pool_introspector.rb +1 -4
- data/lib/rails_ai_context/introspectors/controller_filters.rb +335 -31
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +315 -129
- data/lib/rails_ai_context/introspectors/controller_settings.rb +182 -0
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +18 -15
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/declared_constant.rb +108 -23
- data/lib/rails_ai_context/introspectors/eager_load.rb +12 -7
- data/lib/rails_ai_context/introspectors/engine_introspector.rb +13 -5
- data/lib/rails_ai_context/introspectors/env_config_introspector.rb +62 -17
- data/lib/rails_ai_context/introspectors/env_introspector.rb +8 -6
- data/lib/rails_ai_context/introspectors/env_references.rb +21 -4
- data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +37 -22
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +3 -1
- data/lib/rails_ai_context/introspectors/gemfile_gems.rb +53 -5
- data/lib/rails_ai_context/introspectors/grape_endpoints.rb +109 -0
- data/lib/rails_ai_context/introspectors/helper_names.rb +12 -1
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +101 -46
- data/lib/rails_ai_context/introspectors/includers.rb +6 -1
- data/lib/rails_ai_context/introspectors/initializer_introspector.rb +2 -5
- data/lib/rails_ai_context/introspectors/job_introspector.rb +548 -103
- data/lib/rails_ai_context/introspectors/listener_registration.rb +11 -1
- data/lib/rails_ai_context/introspectors/listeners/associations_listener.rb +51 -4
- data/lib/rails_ai_context/introspectors/listeners/autoload_ignore_listener.rb +31 -0
- data/lib/rails_ai_context/introspectors/listeners/autoload_paths_listener.rb +4 -47
- data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +80 -14
- data/lib/rails_ai_context/introspectors/listeners/branch_conditions.rb +156 -0
- data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +85 -23
- data/lib/rails_ai_context/introspectors/listeners/class_definition_listener.rb +35 -6
- data/lib/rails_ai_context/introspectors/listeners/component_structure_listener.rb +30 -2
- data/lib/rails_ai_context/introspectors/listeners/conditional_macro_listener.rb +19 -0
- data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +255 -10
- data/lib/rails_ai_context/introspectors/listeners/constant_reference_listener.rb +34 -0
- data/lib/rails_ai_context/introspectors/listeners/constructor_macro_listener.rb +123 -0
- data/lib/rails_ai_context/introspectors/listeners/definition_file_paths_listener.rb +66 -0
- data/lib/rails_ai_context/introspectors/listeners/enums_listener.rb +2 -2
- data/lib/rails_ai_context/introspectors/listeners/env_access_listener.rb +31 -0
- data/lib/rails_ai_context/introspectors/listeners/filter_macro_listener.rb +54 -0
- data/lib/rails_ai_context/introspectors/listeners/fixture_paths_listener.rb +62 -0
- data/lib/rails_ai_context/introspectors/listeners/gemfile_dsl_listener.rb +42 -1
- data/lib/rails_ai_context/introspectors/listeners/generic_macro_listener.rb +28 -2
- data/lib/rails_ai_context/introspectors/listeners/grape_api_listener.rb +137 -0
- data/lib/rails_ai_context/introspectors/listeners/http_client_call_listener.rb +50 -0
- data/lib/rails_ai_context/introspectors/listeners/i18n_load_path_listener.rb +54 -0
- data/lib/rails_ai_context/introspectors/listeners/literal_paths.rb +123 -0
- data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +248 -4
- data/lib/rails_ai_context/introspectors/listeners/mailbox_routing_listener.rb +2 -1
- data/lib/rails_ai_context/introspectors/listeners/method_call_listener.rb +7 -1
- data/lib/rails_ai_context/introspectors/listeners/methods_listener.rb +352 -69
- data/lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb +1 -1
- data/lib/rails_ai_context/introspectors/listeners/migration_replay_listener.rb +35 -26
- data/lib/rails_ai_context/introspectors/listeners/mixins_listener.rb +62 -29
- data/lib/rails_ai_context/introspectors/listeners/mount_listener.rb +8 -0
- data/lib/rails_ai_context/introspectors/listeners/namespaced_roots_listener.rb +35 -0
- data/lib/rails_ai_context/introspectors/listeners/nested_constants_listener.rb +32 -0
- data/lib/rails_ai_context/introspectors/listeners/owner_scope.rb +67 -0
- data/lib/rails_ai_context/introspectors/listeners/preview_paths_listener.rb +16 -7
- data/lib/rails_ai_context/introspectors/listeners/proc_literal_listener.rb +42 -0
- data/lib/rails_ai_context/introspectors/listeners/queue_assignment_listener.rb +54 -0
- data/lib/rails_ai_context/introspectors/listeners/rake_task_dsl_listener.rb +21 -8
- data/lib/rails_ai_context/introspectors/listeners/route_files_listener.rb +4 -11
- data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +461 -65
- data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +76 -14
- data/lib/rails_ai_context/introspectors/listeners/scopes_listener.rb +40 -1
- data/lib/rails_ai_context/introspectors/listeners/validations_listener.rb +10 -1
- data/lib/rails_ai_context/introspectors/listeners/view_paths_listener.rb +37 -0
- data/lib/rails_ai_context/introspectors/middleware_introspector.rb +47 -3
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/migration_replay/helpers.rb +4 -3
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +72 -12
- data/lib/rails_ai_context/introspectors/migration_replay.rb +110 -14
- data/lib/rails_ai_context/introspectors/model_introspector.rb +708 -214
- data/lib/rails_ai_context/introspectors/module_aliases.rb +11 -3
- data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +9 -57
- data/lib/rails_ai_context/introspectors/observability_introspector.rb +5 -8
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +28 -8
- data/lib/rails_ai_context/introspectors/rake_task_introspector.rb +118 -51
- data/lib/rails_ai_context/introspectors/recurring_schedules.rb +150 -0
- data/lib/rails_ai_context/introspectors/rendered_record.rb +87 -0
- data/lib/rails_ai_context/introspectors/retry_policy.rb +8 -5
- data/lib/rails_ai_context/introspectors/route_introspector.rb +153 -42
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +127 -40
- data/lib/rails_ai_context/introspectors/schema_dump_path.rb +80 -0
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +346 -112
- data/lib/rails_ai_context/introspectors/schema_reader.rb +69 -29
- data/lib/rails_ai_context/introspectors/service_classes.rb +21 -15
- data/lib/rails_ai_context/introspectors/source_calls.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_introspector.rb +17 -2
- data/lib/rails_ai_context/introspectors/source_scan.rb +174 -26
- data/lib/rails_ai_context/introspectors/sqlite_virtual_tables.rb +28 -0
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +16 -5
- data/lib/rails_ai_context/introspectors/structure_sql_reader.rb +372 -98
- data/lib/rails_ai_context/introspectors/superclass_chain.rb +14 -15
- data/lib/rails_ai_context/introspectors/table_name.rb +46 -45
- data/lib/rails_ai_context/introspectors/test_introspector.rb +289 -78
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +10 -5
- data/lib/rails_ai_context/introspectors/view_introspector.rb +5 -40
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +12 -10
- data/lib/rails_ai_context/method_name.rb +31 -0
- data/lib/rails_ai_context/migration_status.rb +5 -5
- data/lib/rails_ai_context/output_guard.rb +6 -4
- data/lib/rails_ai_context/package_json.rb +96 -5
- data/lib/rails_ai_context/path_resolver.rb +170 -15
- data/lib/rails_ai_context/payload.rb +48 -0
- data/lib/rails_ai_context/pending_migrations.rb +15 -18
- data/lib/rails_ai_context/portable_path.rb +76 -4
- data/lib/rails_ai_context/redaction.rb +8 -2
- data/lib/rails_ai_context/route_coverage.rb +5 -1
- data/lib/rails_ai_context/safe_file.rb +4 -1
- data/lib/rails_ai_context/safe_path.rb +61 -10
- data/lib/rails_ai_context/schema_hint.rb +8 -2
- data/lib/rails_ai_context/schema_version.rb +7 -9
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +1 -1
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +13 -4
- data/lib/rails_ai_context/serializers/section_facts.rb +5 -1
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +2 -2
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +7 -4
- data/lib/rails_ai_context/server.rb +26 -7
- data/lib/rails_ai_context/test_framework.rb +32 -5
- data/lib/rails_ai_context/tools/analyze_feature.rb +22 -10
- data/lib/rails_ai_context/tools/base_tool.rb +69 -30
- data/lib/rails_ai_context/tools/diagnose.rb +1 -1
- data/lib/rails_ai_context/tools/generate_test.rb +8 -9
- data/lib/rails_ai_context/tools/get_active_support.rb +19 -4
- data/lib/rails_ai_context/tools/get_api.rb +17 -11
- data/lib/rails_ai_context/tools/get_autoload.rb +10 -5
- data/lib/rails_ai_context/tools/get_callbacks.rb +65 -14
- data/lib/rails_ai_context/tools/get_component_catalog.rb +7 -4
- data/lib/rails_ai_context/tools/get_concern.rb +31 -18
- data/lib/rails_ai_context/tools/get_config.rb +52 -18
- data/lib/rails_ai_context/tools/get_controllers.rb +57 -13
- data/lib/rails_ai_context/tools/get_conventions.rb +44 -22
- data/lib/rails_ai_context/tools/get_engines.rb +2 -0
- data/lib/rails_ai_context/tools/get_env.rb +209 -52
- data/lib/rails_ai_context/tools/get_env_config.rb +38 -7
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +56 -7
- data/lib/rails_ai_context/tools/get_helper_methods.rb +136 -26
- data/lib/rails_ai_context/tools/get_job_pattern.rb +143 -87
- data/lib/rails_ai_context/tools/get_mailers.rb +59 -2
- data/lib/rails_ai_context/tools/get_model_details.rb +178 -28
- data/lib/rails_ai_context/tools/get_partial_interface.rb +101 -62
- data/lib/rails_ai_context/tools/get_routes.rb +35 -9
- data/lib/rails_ai_context/tools/get_schema.rb +160 -61
- data/lib/rails_ai_context/tools/get_service_pattern.rb +121 -25
- data/lib/rails_ai_context/tools/get_stimulus.rb +1 -1
- data/lib/rails_ai_context/tools/get_test_info.rb +152 -82
- data/lib/rails_ai_context/tools/get_view.rb +37 -20
- data/lib/rails_ai_context/tools/migration_advisor.rb +64 -24
- data/lib/rails_ai_context/tools/onboard.rb +174 -88
- data/lib/rails_ai_context/tools/performance_check.rb +5 -13
- data/lib/rails_ai_context/tools/query.rb +122 -28
- data/lib/rails_ai_context/tools/read_logs.rb +45 -17
- data/lib/rails_ai_context/tools/runtime_info.rb +1 -1
- data/lib/rails_ai_context/tools/safe_call.rb +20 -15
- data/lib/rails_ai_context/tools/search_code.rb +114 -40
- data/lib/rails_ai_context/tools/search_docs.rb +22 -11
- data/lib/rails_ai_context/tools/security_scan.rb +51 -4
- data/lib/rails_ai_context/tools/validate.rb +21 -4
- data/lib/rails_ai_context/tools/validate_semantics.rb +18 -6
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/vfs.rb +2 -2
- data/lib/rails_ai_context/view_file.rb +53 -3
- data/lib/rails_ai_context.rb +10 -2
- data/server.json +1 -1
- metadata +34 -3
data/CONTEXT.md
CHANGED
|
@@ -56,9 +56,9 @@ The other half of the same problem is the reverse trip. Once a name is the decla
|
|
|
56
56
|
|
|
57
57
|
## Table name
|
|
58
58
|
|
|
59
|
-
The table a model reads. Rails builds it as `table_name_prefix` + the demodulized class name pluralized + `table_name_suffix`, unless the class assigns one itself or is an STI child, which reads its parent's. The static tier answers the same order from source: an explicit `self.table_name =`, else the table of the model it inherits from, else the prefix and suffix the enclosing modules declare around the file's own basename pluralized. Never the underscored constant - that turns `Admin::ActionLog` into `admin/action_logs` and `OAuthClientConfig` into `o_auth_client_configs`, neither of which is a table.
|
|
59
|
+
The table a model reads. Rails builds it as `table_name_prefix` + the demodulized class name, pluralized unless `pluralize_table_names` is off, + `table_name_suffix`, unless the class assigns one itself or is an STI child, which reads its parent's. The static tier answers the same order from source: an explicit `self.table_name =`, else the table of the model it inherits from, else the prefix and suffix the enclosing modules declare around the file's own basename. That basename is pluralized unless the class, then its superclass chain, then the app sets `pluralize_table_names = false`. The app-wide affixes and pluralize setting come from `ActiveRecordSettings`, which reads config/application.rb, the environment file, the initializers and `on_load(:active_record)` blocks. Never the underscored constant - that turns `Admin::ActionLog` into `admin/action_logs` and `OAuthClientConfig` into `o_auth_client_configs`, neither of which is a table.
|
|
60
60
|
|
|
61
|
-
`TableName` reads
|
|
61
|
+
`TableName` reads the five declarations of one class body (`table_name`, `table_name_prefix`, `table_name_suffix`, `pluralize_table_names` and `primary_key`) and holds the two derivations; walking the namespace and the superclass chain belongs to the caller, because only it has the other files. A reader that starts from a model name instead of a file asks `TableName.for_model_name`, which takes the table the model tier already recorded before falling back to the convention. A prefix declared outside the model directories is still invisible, so the answer stays [STATIC].
|
|
62
62
|
|
|
63
63
|
## Static tier
|
|
64
64
|
|
|
@@ -133,7 +133,11 @@ Which filters a controller runs, and which of them a given action runs. `ActionF
|
|
|
133
133
|
|
|
134
134
|
**Ask about one action and the answer is absolute again.** On that action the filter either runs or is struck through, so a constraint that does not cover the action leaves no tail at all. That cuts both ways, and the second direction is the one that is easy to get wrong: a skip whose constraint leaves this action alone is the only evidence the chain runs that filter here at all, when the class that declared it is not in the payload. Such a record is marked as evidence, never as a skip, and a real condition on the same name always outranks it.
|
|
135
135
|
|
|
136
|
-
**Order is the
|
|
136
|
+
**Order is the chain order, and a class's own body wins.** After filters run in reverse of it, as Rails does. The chain keeps the order reflection or the source gives it, so a `prepend_before_action` does not fall behind everything the class inherits. One call's callbacks come in argument order with the block last, as Rails adds them. A filter the body declares survives an ancestor's skip of the same name, because Rails re-adds it. Attribution names the ancestor whose body declared the filter, not the nearest one that merely carries it inherited.
|
|
137
|
+
|
|
138
|
+
**A macro inside a method runs when the method is called.** A `skip_before_action` in a concern's `class_methods` (the Rails 8 `allow_unauthenticated_access`) declares nothing until a controller calls the method; at that call it declares what the method body does, with the call's options, wherever on the chain the method is defined. `ControllerFilters` reads it so for both tiers.
|
|
139
|
+
|
|
140
|
+
**A block filter is named by its line.** `before_action { ... }` and `after_action -> { ... }` are callbacks of their own, listed as `block (line N)` and `lambda (line N)` (a lambda says so, as `Proc#lambda?` does, so it reads apart from a block on its line), N being the line the block opens on in the controller's own file; a block in any other file (a concern's class method, an ancestor's body) is `block (line N of app/controllers/concerns/cache_concern.rb)`, as two blocks on one line number in two files are two callbacks. The booted tier lists only a block the app wrote; a framework's or a gem's (`allow_browser`) is left out, as the static tier cannot see it. `skip_forgery_protection` is the `skip_before_action :verify_authenticity_token` Rails defines it as.
|
|
137
141
|
|
|
138
142
|
**What it cannot see is stated rather than guessed.** The walk reads `ApplicationController`'s file by the one name Rails fixes, so its filters are in the chain; a parent the listing does not hold and the app has no file for - a gem-owned base, or one an inflection renames - still ends the walk. `docs/COMPATIBILITY.md` says both halves.
|
|
139
143
|
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://rubygems.org/gems/rails-ai-context)
|
|
8
8
|
[](https://rubygems.org/gems/rails-ai-context)
|
|
9
|
-
[](https://github.com/crisnahine/rails-ai-context/actions)
|
|
9
|
+
[](https://github.com/crisnahine/rails-ai-context/actions)
|
|
10
10
|
[](https://registry.modelcontextprotocol.io)
|
|
11
11
|
[](https://github.com/crisnahine/rails-ai-context)
|
|
12
12
|
[](https://github.com/crisnahine/rails-ai-context)
|
data/docs/ARCHITECTURE.md
CHANGED
|
@@ -26,7 +26,7 @@ graph TD
|
|
|
26
26
|
subgraph engine["Introspection Engine"]
|
|
27
27
|
direction LR
|
|
28
28
|
I["Introspectors\n40 modules\nPresets\nCached"]
|
|
29
|
-
AST["AST Engine\nPrism\
|
|
29
|
+
AST["AST Engine\nPrism\n44 listeners\nConfidence tags"]
|
|
30
30
|
H["Hydration Layer\nSchema hints\ninjected into\ntool responses"]
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -134,7 +134,7 @@ Two more answer a question a tool asks:
|
|
|
134
134
|
|
|
135
135
|
- **AstCache** - Thread-safe parse cache (`Concurrent::Map`), keyed by path + SHA256 + mtime; a stat match answers without a read for a file already two seconds older than the read that recorded it
|
|
136
136
|
- **SourceIntrospector** - Single-pass Prism Dispatcher walks the AST once, feeding every registered listener simultaneously
|
|
137
|
-
- **
|
|
137
|
+
- **Listeners** - Associations, Validations, Scopes, Enums, Callbacks, Macros and Methods are the default map for model analysis; the rest are used through targeted walks over schema dumps, migrations, Gemfiles, rake tasks and initializers, and `MethodCallListener` reports a named call with its arguments and options wherever one is asked for
|
|
138
138
|
- **Confidence** - Every result carries `[VERIFIED]` (static literals) or `[INFERRED]` (dynamic expressions), and a record in a static-tier entry is capped at `[STATIC]`, since no record can claim more than the tier that carries it
|
|
139
139
|
|
|
140
140
|
### Tool Registry (`lib/rails_ai_context/tools/base_tool.rb`)
|
|
@@ -237,7 +237,7 @@ Four cache layers:
|
|
|
237
237
|
|
|
238
238
|
SHA256-based change detection:
|
|
239
239
|
|
|
240
|
-
- Watches: `app/`, `config/`, `db/`, `lib
|
|
240
|
+
- Watches: `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`, Gemfile.lock, `config.ru`, the Rakefile
|
|
241
241
|
- Computes a composite fingerprint from all watched files
|
|
242
242
|
- Used by introspection cache and live reload to detect actual changes
|
|
243
243
|
|
data/docs/CLI.md
CHANGED
|
@@ -175,7 +175,7 @@ rails ai:watch
|
|
|
175
175
|
rails-ai-context watch
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
Requires the `listen` gem. Watches `app/`, `config/`, `db/`, `lib
|
|
178
|
+
Requires the `listen` gem. Watches `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`.
|
|
179
179
|
|
|
180
180
|
### `init` (standalone only)
|
|
181
181
|
|
data/docs/COMPATIBILITY.md
CHANGED
|
@@ -22,6 +22,7 @@ document is aspirational.
|
|
|
22
22
|
reference `DidYouMean::SPELL_CHECKERS`, which did_you_mean removed)
|
|
23
23
|
- **prism:** `>= 1.4, < 2.0` (a CI leg pins the floor exactly and runs the suite against it)
|
|
24
24
|
- **concurrent-ruby:** `>= 1.2, < 3.0`
|
|
25
|
+
- **zeitwerk:** `~> 2.5` (2.5 is what railties 7.0 allows; a CI leg pins 2.5.0 on Rails 7.0 and Ruby 3.1)
|
|
25
26
|
- **json:** unconstrained by this gem, but `json >= 3.0` and Rails 7.0 to 8.0 do
|
|
26
27
|
not work together, whatever gem is in the middle. `json 3.0` removed the
|
|
27
28
|
`quirks_mode` keyword and `ActiveSupport::JSON::Encoding` passes it on every
|
|
@@ -217,7 +218,22 @@ Proof sources:
|
|
|
217
218
|
partition marker, and Discourse, Mastodon, OpenFoodNetwork and Forem
|
|
218
219
|
answering as before. In the v5.30.2 release QA: 14 real apps answered by
|
|
219
220
|
v5.30.1 and by the release branch side by side, and the lab regressions on
|
|
220
|
-
SQLite in all three tiers, PostgreSQL and MySQL.
|
|
221
|
+
SQLite in all three tiers, PostgreSQL and MySQL. In the v5.31.0 release QA:
|
|
222
|
+
a private Rails 8.0 API app booted and static, and Discourse, Mastodon,
|
|
223
|
+
Forem, OpenFoodNetwork, OpenProject, Consul, Canvas and Errbit answered by
|
|
224
|
+
v5.30.2 and by the release branch side by side, every changed answer
|
|
225
|
+
checked against the app's source; the lab regressions on SQLite in all three
|
|
226
|
+
tiers, PostgreSQL and MySQL; and the unit suite on Ruby 3.1 with Rails 7.0.
|
|
227
|
+
In the v5.32.0 release QA: the same private app booted and static, Mastodon,
|
|
228
|
+
Discourse, OpenProject, Canvas, Whitehall, Consul, OpenFoodNetwork, Forem,
|
|
229
|
+
Huginn, Errbit, Diaspora, Plots2 and Decidim answered by v5.31.0 and by the
|
|
230
|
+
release branch side by side, every changed answer checked against the app's
|
|
231
|
+
source; 23 small apps built one per project shape (API-only, no Active
|
|
232
|
+
Record, Mongoid, Sequel, multi-database, engine root and dummy, packs,
|
|
233
|
+
monorepo, multi-tenant, GraphQL, view-heavy, Rails 6.1 and 7.0, Sorbet,
|
|
234
|
+
Sinatra and a plain gem); the lab regressions on SQLite in all three tiers,
|
|
235
|
+
PostgreSQL and MySQL; and the unit suite on Ruby 3.1 with Rails 7.0 through
|
|
236
|
+
Ruby 4.0 with Rails 8.1.
|
|
221
237
|
2. Non-crash coverage for every built-in tool including `get_view` in
|
|
222
238
|
`spec/e2e/in_gemfile_install_spec.rb`'s full-tool sweep; output correctness
|
|
223
239
|
(ivar cross-check, render-form detection, partial interfaces) verified
|
|
@@ -277,12 +293,21 @@ Postgres instance in `spec/e2e/postgres_install_spec.rb`, opt-in via
|
|
|
277
293
|
stay marked `[UNAVAILABLE]`. A concern whose file cannot be found, a gem's
|
|
278
294
|
module for example, is named under `Concerns` as not read.
|
|
279
295
|
- **Callbacks are what the file declares.** In both tiers the callback list is
|
|
280
|
-
what the model file, the app concerns it includes and the classes it
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
296
|
+
what the model file, the app concerns it includes and the classes it inherits
|
|
297
|
+
from declare. A callback a gem registers on include without an `include` line
|
|
298
|
+
in the model file (Devise, counter caches, attachment purges) is not listed.
|
|
299
|
+
Within one type the order is the order Rails runs them: base classes first, a
|
|
300
|
+
concern's callbacks where its `include` line stands, then the model's own,
|
|
301
|
+
and a `before_` or `around_` callback with `prepend: true` first.
|
|
302
|
+
`after_commit` and `after_rollback` run last declared first unless
|
|
303
|
+
`run_after_transaction_callbacks_in_order_defined` is on (`load_defaults 7.1`
|
|
304
|
+
or later; Rails 7.0 has no such setting). The booted tier reads the setting;
|
|
305
|
+
the static tier reads `load_defaults` and any assignment in
|
|
306
|
+
`config/application.rb` or an initializer, the last line that sets it
|
|
307
|
+
winning, and when those do not say, keeps declaration order and the answer
|
|
308
|
+
says so. The static tier does not read `config/environments/*.rb` for this
|
|
309
|
+
setting or for `belongs_to_required_by_default`, so a value set per
|
|
310
|
+
environment is missed. The order across types is Rails' event order.
|
|
286
311
|
- **Concern-style Mongoid documents in runtime results.** Mongoid documents
|
|
287
312
|
are invisible to ActiveRecord reflection, so `ModelIntrospector#call` falls
|
|
288
313
|
back to the same source-parsing pass used in the static tier even when the
|
|
@@ -320,16 +345,27 @@ Postgres instance in `spec/e2e/postgres_install_spec.rb`, opt-in via
|
|
|
320
345
|
A booted run answers that one from reflection.
|
|
321
346
|
- **Some route macros surface as a dynamic tally, not resolved entries.**
|
|
322
347
|
`RouteIntrospector#static_call` counts routes behind `devise_for`, a `match`
|
|
323
|
-
whose `via:` it cannot read,
|
|
348
|
+
whose `via:` it cannot read, a `draw` it cannot read, a block drawn
|
|
324
349
|
through an app class whose prefix is not a literal (and that class's own
|
|
325
|
-
`resources`),
|
|
326
|
-
lambda or a
|
|
350
|
+
`resources`), a route whose `to:` is a lambda or a `redirect(...)`, a mount
|
|
351
|
+
of a lambda or a variable, a resource whose options are a variable or a
|
|
352
|
+
splat, a call it does not know (`load`, `use_doorkeeper`,
|
|
353
|
+
`ActiveAdmin.routes(self)`) and a call to a route-file method that takes
|
|
354
|
+
arguments into a `dynamic_routes` count rather than
|
|
327
355
|
fabricating per-route controller/action pairs it can't actually determine
|
|
328
|
-
from source. Routing concerns
|
|
329
|
-
parameter
|
|
356
|
+
from source. Routing concerns, a `with_options` block that takes no block
|
|
357
|
+
parameter and a route-file method called without arguments are expanded, so
|
|
358
|
+
they are not in that count; `direct` and `resolve` draw no route and are not
|
|
359
|
+
counted either. A `with_options` that
|
|
330
360
|
yields a mapper is in it, and so is a `concerns:` naming a concern defined in
|
|
331
|
-
another drawn file. `constraints do...end`
|
|
332
|
-
children: they are read and resolved,
|
|
361
|
+
another drawn file. `constraints do...end` and any other block that draws
|
|
362
|
+
routes do not hide their children: they are read and resolved, and a
|
|
363
|
+
literal hash constraint or `defaults:` is listed beside the route as
|
|
364
|
+
`bin/rails routes` prints it, with any extra option Rails reads as a default. A route under `if`/`unless` or a `case` branch is listed with
|
|
365
|
+
that condition, since source cannot tell whether it holds. Routes an initializer
|
|
366
|
+
under `config/initializers` adds with `Rails.application.routes.prepend` or
|
|
367
|
+
`.append` are read, before and after the draw as Rails orders them. Routes Rails' own
|
|
368
|
+
engines and gems draw into the app's table are read only with the app booted.
|
|
333
369
|
|
|
334
370
|
<br>
|
|
335
371
|
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -173,7 +173,7 @@ filter either runs or is struck through.
|
|
|
173
173
|
| `max_validate_files` | Integer | `50` | Maximum files for validation |
|
|
174
174
|
| `search_extensions` | Array | `nil` | Narrows the Ruby fallback to these extensions. Unset, the fallback searches every non-hidden, non-binary file, as ripgrep does, so both backends return the same lines |
|
|
175
175
|
| `concern_paths` | Array | `nil` (discovers `app/concerns` and `app/*/concerns`) | Paths to scan for concerns. Setting it replaces discovery, so it can narrow as well as widen |
|
|
176
|
-
| `frontend_paths` | Array | `nil` (auto-detect) |
|
|
176
|
+
| `frontend_paths` | Array | `nil` (auto-detect) | Frontend directories, e.g. `["app/frontend", "../web-client"]`. One outside the app root is read for its package.json, lockfiles and bundler config, plus its tsconfig.json and the configs it extends inside that directory, and frontend_stack names it |
|
|
177
177
|
| `extra_app_paths` | Array | `[]` | Extra directories under the app root to treat as application code |
|
|
178
178
|
|
|
179
179
|
Directories that carry their own Rails tree - `plugins/*`, `modules/*`,
|
|
@@ -213,6 +213,9 @@ fits neither that shape nor the conventional one.
|
|
|
213
213
|
|:-------|:-----|:--------|:------------|
|
|
214
214
|
| `sensitive_patterns` | Array | 34 patterns | File patterns blocked from search/read (`.env*`, `*.env`, `.envrc`, `*.key`, `*.pem`, `config/credentials.yml.enc`, `config/application.yml`, `config/settings.local.yml`, `.ssh/*`, etc.) |
|
|
215
215
|
|
|
216
|
+
A placeholder such as `.env.example` stays readable unless a path pattern or
|
|
217
|
+
its exact name blocks it; see [SECURITY.md](SECURITY.md).
|
|
218
|
+
|
|
216
219
|
### Extensibility
|
|
217
220
|
|
|
218
221
|
| Option | Type | Default | Description |
|
data/docs/FAQ.md
CHANGED
|
@@ -179,7 +179,7 @@ No. The gem only runs in development. Tools execute on demand (not continuously)
|
|
|
179
179
|
|
|
180
180
|
### How does live reload work?
|
|
181
181
|
|
|
182
|
-
The `listen` gem watches `app/`, `config/`, `db/`, `lib
|
|
182
|
+
The `listen` gem watches `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`. When files change, caches are invalidated and MCP clients are notified. Debounce interval: 1.5s (configurable).
|
|
183
183
|
|
|
184
184
|
---
|
|
185
185
|
|
data/docs/GUIDE.md
CHANGED
|
@@ -502,7 +502,7 @@ rails_get_controllers(detail: "full")
|
|
|
502
502
|
|
|
503
503
|
Returns application configuration. No parameters.
|
|
504
504
|
|
|
505
|
-
**Returns:** cache store, session store, timezone, queue adapter, mailer settings, the app's own middleware classes with their files and the rest of the stack as additions (framework defaults are filtered out), notable initializers, CurrentAttributes classes.
|
|
505
|
+
**Returns:** cache store, session store, timezone, queue adapter, mailer settings, the app's own middleware classes with their files and the rest of the stack as additions (framework defaults are filtered out), the `use` and `map` calls in `config.ru`, notable initializers, CurrentAttributes classes with the attributes they declare, their defaults and reset hooks.
|
|
506
506
|
|
|
507
507
|
```
|
|
508
508
|
rails_get_config()
|
|
@@ -826,7 +826,13 @@ rails_get_callbacks(model: "Order", detail: "full")
|
|
|
826
826
|
→ Order's callbacks with the actual method source code for each callback
|
|
827
827
|
```
|
|
828
828
|
|
|
829
|
-
**Returns:** Callbacks grouped by type, in Rails event order. Includes
|
|
829
|
+
**Returns:** Callbacks grouped by type, in Rails event order. Includes
|
|
830
|
+
concern-provided callbacks, with their bodies read from the concern file.
|
|
831
|
+
Within one type the order is the order Rails runs them, base classes first, a
|
|
832
|
+
`before_` or `around_` callback with `prepend: true` first, and
|
|
833
|
+
`after_commit`/`after_rollback` last declared first unless the app runs them in
|
|
834
|
+
order (`load_defaults 7.1`). A method declared again shows once with the later
|
|
835
|
+
declaration's conditions.
|
|
830
836
|
|
|
831
837
|
### rails_get_helper_methods
|
|
832
838
|
|
|
@@ -908,7 +914,7 @@ rails_get_job_pattern(detail: "full")
|
|
|
908
914
|
|
|
909
915
|
### rails_get_env
|
|
910
916
|
|
|
911
|
-
Discover environment variables, external service dependencies, and credentials keys used by the app. Scans Ruby files for ENV[], .env.example, Dockerfile, external HTTP calls, and credentials keys (never values).
|
|
917
|
+
Discover environment variables, external service dependencies, and credentials keys used by the app. Scans Ruby files for ENV[], .env.example, Dockerfile, the env Kamal's config/deploy.yml sets (secret names; clear values, except one holding a URL or a key-like token, or under a secret-named variable, which shows as hidden, and one an ERB tag sets, which says so), config gem setting keys, Anyway::Config attributes with their env names, external HTTP calls, and credentials keys (never values).
|
|
912
918
|
|
|
913
919
|
**Parameters:**
|
|
914
920
|
|
|
@@ -1042,7 +1048,7 @@ In addition to tools, the gem registers static MCP resources that AI clients can
|
|
|
1042
1048
|
| `rails://schema` | Full database schema (JSON) |
|
|
1043
1049
|
| `rails://routes` | All routes (JSON) |
|
|
1044
1050
|
| `rails://conventions` | Detected patterns and architecture (JSON) |
|
|
1045
|
-
| `rails://gems` | Notable gems with categories, plus `declared_ruby_version` and which of the lockfile, Gemfile, `.ruby-version
|
|
1051
|
+
| `rails://gems` | Notable gems with categories, plus `declared_ruby_version` and which of the lockfile, Gemfile, `.ruby-version`, `.tool-versions` or a mise config file answered (JSON) |
|
|
1046
1052
|
| `rails://controllers` | All controllers with actions and filters (JSON) |
|
|
1047
1053
|
| `rails://config` | Application configuration (JSON) |
|
|
1048
1054
|
| `rails://tests` | Test infrastructure details (JSON) |
|
|
@@ -1429,14 +1435,14 @@ Includes all standard introspectors plus:
|
|
|
1429
1435
|
| `active_storage` | Attachments (has_one_attached, has_many_attached per model), storage services, direct upload config. |
|
|
1430
1436
|
| `action_text` | Rich text fields (has_rich_text per model), Action Text installation status. |
|
|
1431
1437
|
| `api` | API-only mode, API versioning (from directory structure), serializers (Jbuilder, AMS, etc.), GraphQL (types, mutations), rate limiting (Rack::Attack). |
|
|
1432
|
-
| `rake_tasks` | Custom rake tasks
|
|
1438
|
+
| `rake_tasks` | Custom rake tasks from the Rakefile, `lib/tasks/` and `rakelib/` (names, arguments, descriptions, namespaces, file paths); the app's generators under `lib/generators`, `lib/templates` overrides and the Railties under `lib/`. |
|
|
1433
1439
|
| `assets` | Asset pipeline (Propshaft/Sprockets), JS bundler (importmap/esbuild/webpack/vite), CSS framework, importmap pins, manifest files. |
|
|
1434
1440
|
| `devops` | Puma config (threads, workers, port), Procfile entries, Docker (multi-stage detection), deployment tools, health check routes. |
|
|
1435
1441
|
| `action_mailbox` | Action Mailbox mailboxes with routing patterns. |
|
|
1436
1442
|
| `seeds` | db/seeds.rb analysis (Faker usage, environment conditionals), seed files in db/seeds/, models seeded. |
|
|
1437
1443
|
| `middleware` | Custom Rack middleware in app/middleware/ and lib/middleware/ with detected patterns (auth, rate limiting, tenant isolation, logging), what the app's config inserts, moves or removes, and the full middleware stack. |
|
|
1438
1444
|
| `engines` | Mounted Rails engines from routes.rb with paths and descriptions for 23+ known engines (Sidekiq::Web, Flipper::UI, PgHero, ActiveAdmin, etc.). |
|
|
1439
|
-
| `env_config` | Per-environment config files (`config/environments/*.rb`): notable toggles (`force_ssl`, `eager_load`, caching, log level, queue adapter, mailer delivery) with URI credentials redacted, assigned config keys. |
|
|
1445
|
+
| `env_config` | Per-environment config files (`config/environments/*.rb`): notable toggles (`force_ssl`, `eager_load`, caching, log level, queue adapter, mailer delivery) with URI credentials redacted, assigned config keys, plus the keys `config/application.rb` sets for every environment and the keys each `config_for` YAML file gives. |
|
|
1440
1446
|
| `multi_database` | Multiple databases, replicas, sharding config, model-specific `connects_to` declarations. database.yml parsing fallback. |
|
|
1441
1447
|
| `frontend_frameworks` | Frontend JS framework detection (React/Vue/Svelte/Angular), mounting strategy (Inertia/react-rails), TypeScript config, state management, package manager. |
|
|
1442
1448
|
| `database_stats` | PostgreSQL approximate row counts via `pg_stat_user_tables`. Gracefully skips on non-PostgreSQL adapters. |
|
|
@@ -1619,7 +1625,7 @@ Requires the `listen` gem:
|
|
|
1619
1625
|
gem "listen", group: :development
|
|
1620
1626
|
```
|
|
1621
1627
|
|
|
1622
|
-
Watches for changes in: `app/`, `config/`, `db/`, `lib
|
|
1628
|
+
Watches for changes in: `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`, and regenerates only the files that changed (diff-aware, skips unchanged files).
|
|
1623
1629
|
|
|
1624
1630
|
---
|
|
1625
1631
|
|
|
@@ -1629,7 +1635,7 @@ When running the MCP server via `rails ai:serve`, **live reload** automatically
|
|
|
1629
1635
|
|
|
1630
1636
|
### How it works
|
|
1631
1637
|
|
|
1632
|
-
1. A background thread watches `app/`, `config/`, `db/`, and `
|
|
1638
|
+
1. A background thread watches `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/` and `spec/` for changes
|
|
1633
1639
|
2. On change (debounced 1.5s), it checks the file fingerprint to avoid false positives
|
|
1634
1640
|
3. If files truly changed, it:
|
|
1635
1641
|
- Clears all MCP tool caches
|
data/docs/INTROSPECTORS.md
CHANGED
|
@@ -118,9 +118,9 @@ end
|
|
|
118
118
|
| GemIntrospector | `:gems` | Notable gems with versions and categories |
|
|
119
119
|
| ConventionIntrospector | `:conventions` | Auth patterns, flash messages, test patterns |
|
|
120
120
|
| I18nIntrospector | `:i18n` | Locale files, translation keys |
|
|
121
|
-
| MiddlewareIntrospector | `:middleware` | Rack middleware stack. The static tier declares an alternate source rather than an empty stack: without a booted app it answers only the file facts it can read |
|
|
121
|
+
| MiddlewareIntrospector | `:middleware` | Rack middleware stack. The static tier declares an alternate source rather than an empty stack: without a booted app it answers only the file facts it can read. `config.ru`'s top-level `use` and `map` calls (`rackup`) are read on both tiers with `ConditionalMacroListener`, each with the `if` it sits under; a `map` whose block runs the app itself is a path prefix, so the `use` calls inside it are listed instead of the map |
|
|
122
122
|
| EngineIntrospector | `:engines` | Mounted engines |
|
|
123
|
-
| EnvConfigIntrospector | `:env_config` | Per-environment config files: notable toggles (`force_ssl`, `eager_load`, caching, queue adapter), assigned config keys |
|
|
123
|
+
| EnvConfigIntrospector | `:env_config` | Per-environment config files: notable toggles (`force_ssl`, `eager_load`, caching, queue adapter), assigned config keys, plus the keys `config/application.rb` sets and each `config_for` file's keys |
|
|
124
124
|
| DevopsIntrospector | `:devops` | Dockerfile, CI config, deployment |
|
|
125
125
|
|
|
126
126
|
### Jobs & Services
|
|
@@ -128,7 +128,7 @@ end
|
|
|
128
128
|
| Introspector | Key | What it extracts |
|
|
129
129
|
|:-------------|:----|:-----------------|
|
|
130
130
|
| JobIntrospector | `:jobs` | Background jobs and Sidekiq workers, read from `app/jobs`, `app/workers` and `app/sidekiq`, and mailers (anywhere under `app/`, by parent chain): queue, retries, `sidekiq_options`, any `sidekiq_throttle`, schedules, and the `file:` each one is defined in |
|
|
131
|
-
| RakeTaskIntrospector | `:rake_tasks` | Custom rake tasks |
|
|
131
|
+
| RakeTaskIntrospector | `:rake_tasks` | Custom rake tasks from the Rakefile, lib/tasks and rakelib; the app's generators (lib/generators), generator template overrides (lib/templates) and Railties under lib/ |
|
|
132
132
|
|
|
133
133
|
### Security & Auth
|
|
134
134
|
|
|
@@ -159,9 +159,9 @@ These introspectors map directly onto the internal RAILS_NERVOUS_SYSTEM checklis
|
|
|
159
159
|
| Introspector | Key | Nervous-system § | What it extracts |
|
|
160
160
|
|:-------------|:----|:---|:-----------------|
|
|
161
161
|
| InitializerIntrospector | `:initializers` | §2 | `Rails.application.initializers` graph: name, owner, `before:`/`after:` edges, block `source_location`, per-file `config/initializers/*.rb` summary |
|
|
162
|
-
| AutoloadIntrospector | `:autoload` | §3 | Zeitwerk presence, autoloaders (`:main` / `:once`) with collapsed + ignored dirs, `autoload_paths`, `eager_load_paths`, custom inflections (`acronym`, `plural`, `singular`, `irregular`) |
|
|
162
|
+
| AutoloadIntrospector | `:autoload` | §3 | Zeitwerk presence, autoloaders (`:main` / `:once`) with collapsed + ignored dirs, `autoload_paths`, `eager_load_paths`, the root directories each loader holds (other engines' and `push_dir` roots, with the namespace they load under), custom inflections (`acronym`, `plural`, `singular`, `irregular`) |
|
|
163
163
|
| ConnectionPoolIntrospector | `:connection_pool` | §10 | Per-database adapter config: pool size, `checkout_timeout`, `reaping_frequency`, `prepared_statements`, `advisory_locks`, replica flag, connection-handler roles, automatic shard selector detection |
|
|
164
|
-
| ActiveSupportIntrospector | `:active_support` | §17 | Concerns in `app/**/concerns/` (ActiveSupport::Concern flags, `included do`/`class_methods do` blocks), deprecators registry, MessageEncryptor/Verifier usage, TaggedLogging config, common on-load hooks, cache store options |
|
|
164
|
+
| ActiveSupportIntrospector | `:active_support` | §17 | Concerns in `app/**/concerns/` (ActiveSupport::Concern flags, `included do`/`class_methods do` blocks), deprecators registry, MessageEncryptor/Verifier usage, notification subscriptions read from source (`subscribe`, `monotonic_subscribe`, `attach_to`), TaggedLogging config, common on-load hooks, cache store options |
|
|
165
165
|
| CredentialsIntrospector | `:credentials` | §30 | Default + per-env encrypted files, master-key source (`env:RAILS_MASTER_KEY` vs `file:config/master.key` vs missing), `require_master_key` flag, arbitrary encrypted configs (`config/*.yml.enc`), top-level key **names only** (never values) |
|
|
166
166
|
| SecurityIntrospector | `:security` | §32 | `force_ssl`, SSL options (HSTS `expires`/`subdomains`/`preload`), `host_authorization` hosts, ContentSecurityPolicy directives + `report_only`, PermissionsPolicy directives, CSRF config (`protect_from_forgery`, `per_form_csrf_tokens`, `origin_check`), cookie session options, Rails 7.2+ `allow_browser` usage |
|
|
167
167
|
| ObservabilityIntrospector | `:observability` | §34 + §38 | `ActiveSupport::LogSubscriber.log_subscribers` catalog, AS::Notifications subscriber registry (pattern + count + sample class), `ActionDispatch::ServerTiming` middleware detection, Rails 8.1 `event_reporter` availability, log level + tags, canonical Rails event-name catalog (10 subsystems) |
|
|
@@ -179,14 +179,14 @@ It runs a single-pass Dispatcher that walks the AST once and feeds events to all
|
|
|
179
179
|
|
|
180
180
|
| Listener | What it detects |
|
|
181
181
|
|:---------|:---------------|
|
|
182
|
-
| AssociationsListener | `belongs_to`, `has_many`, `has_one`, `has_and_belongs_to_many`, under the options of an enclosing `with_options` block |
|
|
182
|
+
| AssociationsListener | `belongs_to`, `has_many`, `has_one`, `has_and_belongs_to_many`, the polymorphic `belongs_to` of `delegated_type`, the `belongs_to` of `acts_as_tenant` (tenant defaults to `:account`), under the options of an enclosing `with_options` block |
|
|
183
183
|
| ValidationsListener | `validates`, `validates_*_of`, custom `validate :method`, under the options of an enclosing `with_options` block |
|
|
184
184
|
| ScopesListener | `scope :name, -> { ... }`, `lambda { ... }` and the block form |
|
|
185
185
|
| EnumsListener | Rails 7+ and legacy enum syntax, prefix/suffix options |
|
|
186
186
|
| CallbacksListener | All AR callback types including `around_*`, `after_touch`, `after_initialize` and `after_find`; `after_commit` with `on:` resolution; a callback object by its constant, a block as `[inline_block]`; `if:`/`unless:` kept as the source wrote them, and the options of an enclosing `with_options` block |
|
|
187
|
-
| MacrosListener | `encrypts`, `normalizes`, `delegate`, `has_secure_password`, `serialize`, `store`, `has_one_attached`, `has_many_attached`, `has_rich_text`, `generates_token_for`, `attribute` |
|
|
188
|
-
| MethodsListener | `def`/`def self.`, visibility tracking, parameter extraction, `class << self`; `include_initialize: true` adds the constructor a caller reports on its own |
|
|
189
|
-
| MixinsListener | `include`, `prepend`, `extend`, flagging the ones that reach the ancestor chain |
|
|
187
|
+
| MacrosListener | (each attribute macro and `has_secure_password` with its keyword options as written, under `written`) `encrypts`, `normalizes`, `delegate`, `has_secure_password`, `serialize`, `store`, `has_one_attached`, `has_many_attached`, `has_rich_text`, `generates_token_for`, `attribute`, `alias_attribute`, `store_accessor`, `self.ignored_columns`, `has_secure_token`, `accepts_nested_attributes_for`, `attr_readonly`, `query_constraints`, `connects_to` as written (a child carries its base's, with the base's name under `declared_in`), the class settings `self.inheritance_column`/`store_full_sti_class`/`strict_loading_by_default`/`implicit_order_column`/`locking_column =` as written, an `aasm` block's states, initial state, events and transitions, and model gems' class macros (`GEM_MACROS`: `has_paper_trail`, `friendly_id`, `mount_uploader`, `monetize`, `pg_search_scope`, `acts_as_list` and others) as written, without their block |
|
|
188
|
+
| MethodsListener | `def`/`def self.`, visibility tracking, parameter extraction, `class << self`, the methods `delegate` and Forwardable's `def_delegators`/`def_delegator` define (public unless `private: true`, and with no `end_location`, since a delegation has no body), and the methods `alias`, `alias_method`, `attr_*`, `define_method` with a literal name, `class_attribute` and `cattr_*`/`mattr_*` define (an alias keeps its original's visibility and parameters; Active Support's accessors are public on the class and, unless an option turns them off, on instances); each `def` carries `offset`/`end_offset`, which `SourceIntrospector.outside_defs` pairs against a call's `offset` to tell a call on the same line as a def from one inside it; a `Struct.new`/`Class.new`/`Module.new`/`Data.define` block assigned to a constant is that constant's body, and a `def self.x` in `included do` is marked `class_methods_block` like one in `class_methods do`; `include_initialize: true` adds the constructor a caller reports on its own |
|
|
189
|
+
| MixinsListener | `include`, `prepend`, `extend`, `singleton_class.include` and `singleton_class.prepend` (`singleton_class.extend` is not read), flagging the ones that reach the ancestor chain, as reflection reports them. GitLab's `prepend_mod_with("Note")`, `include_mod_with`, `extend_mod_with` and their `_mod` forms record `EE::Note` and `JH::Note` flagged `edition`; `ConcernMembership.owned_by` keeps one only where the app has its file, and credits `Note.prepend X` to Note |
|
|
190
190
|
|
|
191
191
|
### The targeted-walk listeners
|
|
192
192
|
|
|
@@ -194,27 +194,47 @@ Passed to `SourceIntrospector.walk(path, key => Listener)` when a specific file
|
|
|
194
194
|
|
|
195
195
|
| Listener | What it detects |
|
|
196
196
|
|:---------|:---------------|
|
|
197
|
-
| GenericMacroListener | Any receiver-less macro you name: `GenericMacroListener.new(:devise, :rate_limit)
|
|
197
|
+
| GenericMacroListener | Any receiver-less macro you name: `GenericMacroListener.new(:devise, :rate_limit)`; `any_receiver: true` also counts `base.macro`, as a mixin's `self.included(base)` hook writes it. Returns args, values (with a source-slice fallback), options, option values and option nodes, `proc_lines` (the line each block, `->`, `lambda {}`, `proc {}` or `Proc.new {}` argument opens on, as `Proc#source_location` gives it), plus the nesting: `parent_offset` is the offset of the target macro call whose block this one sits in, paired against each call's own `offset` rather than its line |
|
|
198
|
+
| ConditionalMacroListener | GenericMacroListener plus `condition`, the `if`/`unless`/`case` branches the call sits under (`BranchConditions`), as `if Rails.env.test?`. Controller filters read it through FilterMacroListener, so `before_action :x if Rails.env.test?` is listed with its condition |
|
|
199
|
+
| FilterMacroListener | ConditionalMacroListener plus `callbacks`, what each positional argument adds, named at its node: `[:name, x]` for a symbol, string or class, `[:object, Klass]` for an instance (`Class` for an anonymous class), `[:lambda]` for `-> {}` or `lambda {}`, `[:block]` for a proc, `[:unread, source]` for anything else. Used by `ControllerFilters`, so the static tier names each callback as the booted tier does |
|
|
200
|
+
| ConstructorMacroListener | Class-body macros that define a constructor: T::Struct `const`/`prop`, Dry::Struct `attribute`/`attribute?`, dry-initializer `param`/`option` (with `extend` recorded so a consumer can check for `Dry::Initializer`), and the attr_extras initializers, `method_object` and `static_facade`. Each record adds `owner`, the one-line `source`, and `params` as `[kind, name, default]` with lambda defaults read as the value they return. Used by `rails_get_service_pattern`, which keeps a record only when the class is that library's |
|
|
201
|
+
| GrapeApiListener | A Grape API class body: each class with its superclass, `version` (with `using:`) and `prefix`, each verb endpoint with the namespaces around it (`namespace`, `resource(s)`, `group`, `segment`, `route_param` as `:id`) and the `requires`/`optional` names of the `params` block before it, and each `mount`. Calls inside an endpoint body are skipped. Used by `GrapeEndpoints`, which decides which classes reach `Grape::API` and builds each path the way Grape does, for `rails_get_routes` |
|
|
198
202
|
| ChainedCallListener | Calls on a receiver: `ChainedCallListener.new(:includes)`, or `receiver: :inflect` to pin the receiver. Reports the receiver name |
|
|
199
|
-
| ConfigAssignmentListener | `config.key = value` and `config.a.b = value` in initializers and `config/environments/*.rb`, plus bare `config.jwt do ... end` section references. Takes a root name (`:config` by default, e.g. `:DatabaseCleaner`) |
|
|
200
|
-
| ClassDefinitionListener | Class definitions with their superclass
|
|
203
|
+
| ConfigAssignmentListener | `config.key = value` and `config.a.b = value` in initializers and `config/environments/*.rb`, plus bare `config.jwt do ... end` section references and settings written without `=` (`<<`, a call with arguments, `+=`/`||=`/`&&=`, a block), tagged `write:`. An assignment whose value is a `config_for` call carries `config_for:` (the YAML file it reads and its `env:`). An assignment whose value is a string or symbol written as one carries `literal: true`. An assignment whose value is not redacted carries the same `arguments:`, `computed:` and `constants:` MethodCallListener reports for the value as one argument. Takes a root name (`:config` by default, e.g. `:DatabaseCleaner`); the root `on_load(:active_record)` reads `self.x =` and `base.x =` for the block's first param inside that `ActiveSupport.on_load` block (not inside a nested class, def or `Foo.class_eval`; a `class_eval` on that self, that param or a root constant keeps the root), and a root named `Receiver.method` (`Apartment.configure`) binds that block's first param |
|
|
204
|
+
| ClassDefinitionListener | Class definitions with their superclass and the nesting a bare superclass is read in |
|
|
205
|
+
| NestedConstantsListener | The offset ranges of the modules and classes a class body nests, so a filter declared inside one is not read as the outer class's own. Used by `ControllerFilters` |
|
|
201
206
|
| ComponentStructureListener | ViewComponent and Phlex structure: `renders_one`/`renders_many`, slot methods, hash/array constant tables, `case @ivar` variant branching, `CONST[@ivar]` indexing |
|
|
202
207
|
| MiddlewareConfigListener | The app's own stack, reached through its config (`config.middleware`, `Rails.configuration.middleware`, `app.config.middleware`), through the app (`Rails.application.middleware`, `app.middleware`) or through the app's own application class (`MyApp::Application.config.middleware`, given as `app_class:`), never another rack stack: any other constant anywhere in the chain (`MyEngine.config.middleware`, `GoodJob::Engine.middleware`) is an engine's. Reads `use`, `insert`, `insert_before`, `insert_after`, `unshift`, `swap`, `move_before`, `move_after`, `delete`, and `config.exceptions_app =` as its own `exceptions_app` action |
|
|
203
208
|
| RouteFilesListener | The route files `config/application.rb` puts in `config.paths["config/routes.rb"]`: an assignment (a list, `.map`ped or not), `<<`/`push`/`concat`, `unshift`/`prepend`, `Rails.root.join` and literal `Dir[...]` globs, each as `set`/`append`/`prepend`. A list the app computes is recorded as `computed` |
|
|
204
209
|
| AutoloadPathsListener | Autoload roots `config/application.rb` adds by hand: `autoload_paths`/`eager_load_paths`/`autoload_once_paths` appends, `autoload_lib`, and `config.paths.add` with `eager_load:`. Literal paths under the app root only |
|
|
205
|
-
|
|
|
206
|
-
|
|
|
210
|
+
| AutoloadIgnoreListener | The lib subdirectories `autoload_lib(ignore:)` and `autoload_lib_once(ignore:)` keep out of autoloading, as `lib/<name>`. Literal strings and symbols only |
|
|
211
|
+
| PreviewPathsListener | ViewComponent preview directories the config sets: `view_component.previews.paths`, `preview_paths`, `preview_path`, in the same literal forms as AutoloadPathsListener; with `framework: :action_mailer`, the mailer preview directories `action_mailer.preview_paths` and `preview_path` set |
|
|
212
|
+
| I18nLoadPathListener | Locale files `config.i18n.load_path` or `I18n.load_path` adds: `+=`, `<<`, `push`, `append`, `concat`, with `Dir[]`/`Dir.glob` around the same literal forms as AutoloadPathsListener |
|
|
213
|
+
| FixturePathsListener | Fixture directories a test helper sets: `fixture_paths =`/`<<`/`+=`/`push` and the older `fixture_path =`, on `self`, `config`, a constant or no receiver, in the same literal forms as AutoloadPathsListener plus `File.expand_path("x", __dir__)`, `File.expand_path("x", __FILE__)` and `File.join(__dir__, "x")` when given the helper's file; a write whose path it cannot read records `:unread` |
|
|
214
|
+
| DefinitionFilePathsListener | Where a test helper points factory_bot: `FactoryBot.definition_file_paths =` (replaces the defaults) and `<<`/`+=`/`push`/`concat` (adds), each write as `{ replace:, paths: }` in the same forms as FixturePathsListener (`unread: true` and no paths when it cannot read them), and each `FactoryBot.find_definitions`/`FactoryBot.reload` as `{ load: }`, in source order; `::FactoryBot` counts |
|
|
215
|
+
| ViewPathsListener | View roots `config/application.rb` adds to `config.paths["app/views"]`: `unshift` puts one before app/views, `<<`/`push`/`concat` after it, in the same literal forms as AutoloadPathsListener; a write whose path it cannot read is left out |
|
|
216
|
+
| NamespacedRootsListener | Roots `config/application.rb` or an initializer hands Zeitwerk under a namespace: `push_dir(path, namespace: Const)`, as phlex:install writes, in the same literal forms as AutoloadPathsListener; a write whose path it cannot read is left out |
|
|
217
|
+
| SchemaDslListener | `schema.rb`: `create_table`, `t.string`, `t.column`, `t.index`, `add_foreign_key`, `create_enum`, `create_view`, `create_virtual_table`, and the comment the dumper writes for a table it could not describe |
|
|
207
218
|
| MigrationDslListener | Migration DSL: `create_table`, `add_column`, `add_index`, `add_reference`, and friends |
|
|
208
219
|
| MigrationReplayListener | What a replay needs beside the DSL: `def down`, `down`/`revert` blocks, and `t.timestamps` |
|
|
209
220
|
| RoutesDslListener | `config/routes.rb`, resolving namespace/scope/resources nesting into flat routes (a `controller:` with a leading slash is absolute, as in Rails); routing concerns (`concern` definitions replayed at each `concerns:` site), `with_options` defaults merged under each inner call, `match ... via:` (or the `via:` of an enclosing `scope`) as one route answering each verb it names (`GET|POST`, and `ANY` for `via: :all`, as the booted table has it), the controller a `scope(controller:)` or a route's own `controller:` names (with Rails' `a/b` shorthand when no action is given), and the `as:`, `param:`, `module:`, `path:` and `only:`/`except:` options. A block drawn through an app class (`ApiRouteSet::V1.draw(self) do`) takes the path and `as:` prefixes that class's `self.prefix` and `mapper_prefix` return as literals, or a literal prefix argument; a class whose prefix is not a literal, and that class's own `resources`, are counted as unexpanded. Routes drawn into an engine's table (`Spree::Core::Engine.routes.draw`) sit under the engine's namespace and carry `engine:`, which the route introspector files under that engine's mount, apart from the app's count. A file pulled in by `draw` is walked inside the scope its `draw` sits in, once per scope that draws it, so `draw :api` under `namespace :api` and `scope module: :v1` routes to `api/v1/...` at `/api/...` |
|
|
210
|
-
| MountListener | `mount Sidekiq::Web, at: "/sidekiq"`, the hash form, and a Rack app attached with `match "/metrics", to: MetricsApp` - `mount` is that call with a name derived. Paths carry the enclosing `namespace`/`scope` prefix; a scope whose own name is an expression yields no path rather than an unprefixed one, and `scope path: nil` adds no segment. A mounted app built by a call on a constant (`Flipper::UI.app(Flipper)`) is named by that call, arguments off |
|
|
211
|
-
| GemfileDslListener | `gem "name", "version"
|
|
212
|
-
| RakeTaskDslListener | `namespace
|
|
213
|
-
| EnvAccessListener | `ENV["KEY"]`, `ENV.fetch("KEY")`, `ENV.fetch("KEY", default)` |
|
|
221
|
+
| MountListener | `mount Sidekiq::Web, at: "/sidekiq"`, the hash form, and a Rack app attached with `match "/metrics", to: MetricsApp` - `mount` is that call with a name derived. Paths carry the enclosing `namespace`/`scope` prefix; a scope whose own name is an expression yields no path rather than an unprefixed one, and `scope path: nil` adds no segment. A mounted app built by a call on a constant (`Flipper::UI.app(Flipper)`) is named by that call, arguments off. A mount under `if`/`unless`/`case` carries that `condition` |
|
|
222
|
+
| GemfileDslListener | `gem "name", "version"`, `group :development do ... end` and `eval_gemfile "path"` (a literal or `File.expand_path("x", __dir__)`) with the groups around it; `ruby "3.3.6"` with its `engine:` and `engine_version:`; an `:unknown_gems` entry for `gemspec` and for a `gem` or `eval_gemfile` whose argument is not a literal |
|
|
223
|
+
| RakeTaskDslListener | `namespace` (with the span its block covers), `desc`, `task`, `multitask` in `.rake` files |
|
|
224
|
+
| EnvAccessListener | `ENV["KEY"]`, `ENV.fetch("KEY")`, `ENV.fetch("KEY", default)`, and the ENV name Rails 8.2's `Rails.app.creds` or `Rails.app.envs` `require`/`option` reads (`option(:database, :host)` is `DATABASE__HOST`) |
|
|
214
225
|
| MailboxRoutingListener | Action Mailbox `routing` and processing callbacks |
|
|
215
226
|
| ModelReferenceListener | Model constants used in controllers: `Post.find`, `params.require(:post)`, ivar writes |
|
|
216
227
|
| VariantCallListener | `variant` calls (ChainedCallListener with `:variant` preset) |
|
|
217
|
-
|
|
|
228
|
+
| ProcLiteralListener | Proc literals: line, source, assigned constant |
|
|
229
|
+
| QueueAssignmentListener | A queue a class body assigns outside any method: Resque's `@queue = :name`, Que's `self.queue = "name"`, or a `def self.queue` returning one, a literal as its value, anything else as source |
|
|
230
|
+
| HttpClientCallListener | Calls on an HTTP client constant (`Faraday`, `Net::HTTP`, `HTTParty`, `RestClient`, `HTTP`, `Excon`, `Typhoeus`, `URI.open`) with a literal URL, bare, wrapped in `URI(...)`/`URI.parse(...)` or as `url:`, and the host `Net::HTTP.start`/`.new` takes. `rails_get_env` names external services from it |
|
|
231
|
+
| ConstantReferenceListener | References to constants by last name, qualified or not (`ActiveSupport::MessageVerifier`), skipping one that only qualifies a nested constant (`MessageVerifier::InvalidSignature`) and mentions in comments or strings. Used by the ActiveSupport introspector for message verifier and encryptor usage |
|
|
232
|
+
| MethodCallListener | Call sites by name or pattern anywhere in a file, inside a `def`, a lambda or a block included, with arguments, options, receiver, line, offset, the owning class or module, and `constants:` (each argument that is a constant, or builds one with `.new`, mapped to the constant's name). Used by the Turbo introspector for broadcast calls, the ActiveSupport introspector for notification subscriptions, and others |
|
|
233
|
+
|
|
234
|
+
`GenericMacroListener.new(*names, block_source: [:name])` adds `block`, the
|
|
235
|
+
one-line source of the block those macros are given. `call_source: [:name]`
|
|
236
|
+
adds `text`, the one-line source of the whole call (the Mongoid `index` reader). ProcLiteralListener is
|
|
237
|
+
what the job introspector reads a `queue_as` Proc with.
|
|
218
238
|
|
|
219
239
|
### Adding a listener
|
|
220
240
|
|
|
@@ -248,19 +268,26 @@ same wherever it is asked. Those live as their own modules under
|
|
|
248
268
|
|:-------|:---------------|
|
|
249
269
|
| `DeclaredConstant` | The constant a source file calls its own class, against the one its path camelizes to |
|
|
250
270
|
| `ActionPresence` | Whether a controller has an action: the public methods and `define_method` names of the controller, its ancestors up to Rails' base and the modules they include, the templates at each ancestor's view prefix, and the ancestors or modules no app source holds, which leave a missing action unverified. `rails_validate` and `rails_generate_test` both ask it |
|
|
271
|
+
| `ControllerSettings` | The layout a controller renders in (declared on it or an ancestor, else the `layouts/<controller_path>` file Rails finds by name, walking up the chain) and the `allow_browser`, `protect_from_forgery`, `add_flash_types`, `default_form_builder` and `wrap_parameters` calls it and its ancestors make. `rails_get_controllers` and `rails_get_view` both ask it |
|
|
251
272
|
| `TableName` | The table a model reads, from its own declarations |
|
|
273
|
+
| `ActiveRecordSettings` | What config/application.rb, the environment file and the initializers set on Active Record: the table affixes, `pluralize_table_names` and `schema_format`, in the order Rails applies them. Read once per introspection run or tool call |
|
|
252
274
|
| `HabtmJoinTables` | The join tables every `has_and_belongs_to_many` under the app's code and lib names, lib patches and engines included, for the schema's model-less table warning |
|
|
253
275
|
| `SuperclassChain` | What a class inherits from, followed through the app's own sources: the chain from a file's class up to a named base, and the constant-to-source lookup over the app's autoload roots that walks it |
|
|
254
276
|
| `CallSiteExpansion` | What one call of a mixin's macro-declaring method declares. `attachable :receipt, has_one: true` runs `def attachable(name, opts = {})`, whose body declares `has_one name` or `has_many name` by `opts[:has_one]`: the parameters bind to the call's literal arguments, and a branch (`if`, `unless`, `case`) the literals decide is taken alone. Where they cannot decide, what every way through declares alike is listed once; the rest, a lone `if`'s body included, comes back under `:conditional` with its condition and is not counted. A block the method evaluates on another receiver (`other.instance_eval`) comes back under `:foreign`, named with the receiver and any condition it runs under |
|
|
255
277
|
| `Includers` | Which classes and modules mix a module in, by `include`, `prepend` or `extend`: a written name resolves from the includer's namespace outward, and the nearest module the app declares decides. `rails_get_concern`'s "Included by", the service listing and the HABTM join-table owners all ask it |
|
|
256
278
|
| `RetryPolicy` | What a job does when it raises, as a reader would write it: the macro, its exceptions, then `attempts:` and `wait:` whatever order the source put them in |
|
|
257
279
|
| `SourceCalls` | Which other classes a file hands work to, off the call nodes: the verb list, the framework receivers left out, and the call or the class alone |
|
|
258
|
-
| `ServiceClasses` | Which classes under `app/services` are services and which are only the base of one, for the tool's listing and the generated files' line alike |
|
|
259
|
-
| `EnvReferences` | Every ENV name the app's source reads, file by file, for `rails_get_env` and the context file's `env` section alike: `app`, `config` and `lib` Ruby, ERB and config YAML, with config YAML on `sensitive_patterns` read for the names in its ERB tags only |
|
|
260
|
-
| `
|
|
280
|
+
| `ServiceClasses` | Which classes under `app/services`, `app/interactions` and `app/interactors` are services and which are only the base of one, for the tool's listing and the generated files' line alike |
|
|
281
|
+
| `EnvReferences` | Every ENV name the app's source reads, file by file, for `rails_get_env` and the context file's `env` section alike: `app`, `config` and `lib` Ruby, ERB and config YAML, `config.ru`, `db/seeds` and the `bin/` scripts whose shebang is Ruby, with config YAML on `sensitive_patterns` read for the names in its ERB tags only |
|
|
282
|
+
| `AnywayConfigs` | Each Anyway::Config class under `config/configs` and `app/configs`, every class in a file, with the attributes its own body declares (a nested class keeps its macros) and the env name anyway_config reads each from: `env_prefix`, else `config_name`, else the name before `Config`. `rails_get_env` lists them |
|
|
283
|
+
| `GemfileGems` | The one Gemfile read, off `GemfileDslListener`: its entries with options and groups (the gems section's local gems and groups) and the gem names (every other asker), so a commented-out `gem` line is no gem anywhere; a file named by `eval_gemfile` is followed when it is inside the bundle's directory (the app root, or the directory of the Gemfile `config/boot.rb` points at), and one it cannot read is an `:unknown_gems` entry. GemLock takes the declared Ruby and the gem names from it once the gem is loaded; the walk is kept until one of the files changes. Before the boot, GemLock walks the one Gemfile with `AstWalk` instead and leaves `eval_gemfile` unread |
|
|
261
284
|
| `ModuleAliases` | Which app file a bare JS import specifier names: tsconfig/jsconfig `compilerOptions.paths` followed through `extends` (relative files and installed packages), and a vite/webpack/rspack `resolve.alias` written as a literal object. The Stimulus scan uses it to tie a registration or a base class to the controller file it imports |
|
|
262
285
|
| `HelperNames` | The helper methods a view can call: every method the app's helper modules define in every code root, and those of a module they `include`, found through the app's autoload roots (`lib` among them) or in its enclosing namespace's file (`CanonicalURL::Helpers` in `canonical_url.rb`). `rails_get_partial_interface` uses it so a helper call is not read as a local |
|
|
263
286
|
| `Interaction` | Whether a class runs as an ActiveInteraction, following its superclass chain through the app's own sources, and the filters it takes - inherited ones first, one per name, each carrying the filters nested inside its block. See the **Interaction filter** entry in `CONTEXT.md` |
|
|
287
|
+
| `RecurringSchedules` | The recurring tasks each scheduler reads from its own file: Solid Queue's `config/recurring.yml`, sidekiq-cron's `config/schedule.yml`, sidekiq-scheduler's section of `config/sidekiq.yml`, GoodJob's `config.good_job.cron` and whenever's `config/schedule.rb`. The job introspector hands it its own config walk, so a file it also reads for queue or mailer settings is walked once |
|
|
288
|
+
| `AdminResources` | The models an admin gem exposes (ActiveAdmin, Trestle, Administrate, Avo, Madmin), from the folder each gem's generator writes to, with ActiveAdmin's `permit_params`. `rails_get_conventions` and `rails_analyze_feature` both ask it |
|
|
289
|
+
| `SchemaDumpPath` | The primary database's dump file as `DatabaseTasks.schema_dump_path` names it: `database.yml`'s `schema_dump` in the configured `schema_format`, then each format's default file, and the secondary databases' dumps. Every schema reader, the doctor and the pending-migration check ask it |
|
|
290
|
+
| `ApartmentConfig` | The models an Apartment initializer keeps in the shared schema (`excluded_models`), or the source of a list the file computes. The model introspector asks it to say which models get one table per tenant |
|
|
264
291
|
|
|
265
292
|
### Confidence tagging
|
|
266
293
|
|
|
@@ -298,7 +325,7 @@ Introspection results are cached at three levels:
|
|
|
298
325
|
2. **AST cache** - Per-file parse results, invalidated by file content change (SHA256), with a stat shortcut for a file older than the read
|
|
299
326
|
3. **Run cache** - File lists, stats and directory answers for one introspection run, dropped when it ends
|
|
300
327
|
|
|
301
|
-
The **Fingerprinter** computes a composite SHA256 from all watched directories (`app/`, `config/`, `db/`, `lib
|
|
328
|
+
The **Fingerprinter** computes a composite SHA256 from all watched directories (`app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`, `Gemfile.lock`, `config.ru`, the Rakefile). When the fingerprint changes, the introspection cache is invalidated even if TTL hasn't expired.
|
|
302
329
|
|
|
303
330
|
**Live Reload** watches these directories and calls `reset_all_caches!` when changes are detected, then notifies connected MCP clients via `notify_resources_list_changed`.
|
|
304
331
|
|
data/docs/SECURITY.md
CHANGED
|
@@ -66,10 +66,23 @@ After validation, the query runs inside a transaction:
|
|
|
66
66
|
|:---------|:----------|
|
|
67
67
|
| PostgreSQL | `SET TRANSACTION READ ONLY` + `SET LOCAL statement_timeout` |
|
|
68
68
|
| MySQL | `SET TRANSACTION READ ONLY` + `MAX_EXECUTION_TIME` hint |
|
|
69
|
-
| SQLite |
|
|
69
|
+
| SQLite | A read-only connection in a child process, killed at timeout |
|
|
70
70
|
|
|
71
71
|
All queries execute inside a transaction, then rollback (even if they could write, they can't).
|
|
72
72
|
|
|
73
|
+
A SQLite query runs in-process under `PRAGMA query_only = ON`, with no time
|
|
74
|
+
limit, for an in-memory database, on a platform without `fork`, or when it
|
|
75
|
+
needs a function, virtual-table module, collation or encryption key only the
|
|
76
|
+
app's own connection has. The table and EXPLAIN answers then say so; CSV output
|
|
77
|
+
stays plain data.
|
|
78
|
+
|
|
79
|
+
On sqlite3 1.x a query fails at its timeout, with the statement-timeout error,
|
|
80
|
+
when another connection in the same process held an exclusive lock on a
|
|
81
|
+
rollback-journal database as it started (`BEGIN EXCLUSIVE`): the child
|
|
82
|
+
inherits that lock record and it never clears. A lock held
|
|
83
|
+
by another process is waited on as usual, and an open write transaction or any
|
|
84
|
+
lock in WAL mode does not block it.
|
|
85
|
+
|
|
73
86
|
### Layer 3 - Row limit
|
|
74
87
|
|
|
75
88
|
- Default: 100 rows
|
|
@@ -90,6 +103,9 @@ If one of your own columns merely looks sensitive (an `oauth_applications.secret
|
|
|
90
103
|
config.query_allowed_columns = %w[secret]
|
|
91
104
|
```
|
|
92
105
|
|
|
106
|
+
The exemption covers the results too: an allowed name comes back unredacted. A
|
|
107
|
+
column declared with `encrypts` stays `[FILTERED]` either way.
|
|
108
|
+
|
|
93
109
|
### Environment guard
|
|
94
110
|
|
|
95
111
|
> [!WARNING]
|
|
@@ -115,6 +131,12 @@ config/mongoid.yml config/redis.yml
|
|
|
115
131
|
.ssh/* .aws/credentials .aws/config .netrc .pgpass .my.cnf
|
|
116
132
|
```
|
|
117
133
|
|
|
134
|
+
A placeholder whose name ends in `.example`, `.sample`, `.template` or
|
|
135
|
+
`.dist` (`.env.example`) is committed to be read, so a basename glob such as
|
|
136
|
+
`.env.*` does not block it. A pattern that names it exactly, with no glob
|
|
137
|
+
characters, still does, and so does a path pattern (one with a `/`, such as
|
|
138
|
+
`.ssh/*`), which covers everything under it.
|
|
139
|
+
|
|
118
140
|
### AI context file exclusions
|
|
119
141
|
|
|
120
142
|
Search also excludes generated AI context files to prevent circular references:
|
|
@@ -147,6 +169,39 @@ raise unless real_path.start_with?(Rails.root.to_s)
|
|
|
147
169
|
|
|
148
170
|
The VFS (`rails-ai-context://views/{path}`) applies the same protection for view template reads.
|
|
149
171
|
|
|
172
|
+
### Frontend roots outside the app
|
|
173
|
+
|
|
174
|
+
Two directories outside `Rails.root` are read, for frontend manifests only: each
|
|
175
|
+
`frontend_paths` entry you configure (`../web-client`), and the JS workspace root
|
|
176
|
+
above the app (the nearest ancestor holding a lockfile or declaring `workspaces`,
|
|
177
|
+
never above the git root and never outside a git repository). Only `package.json`,
|
|
178
|
+
lockfiles and the presence of a bundler config (`vite.config.*` and similar) are
|
|
179
|
+
read there, plus a configured entry's `tsconfig.json` and the configs it extends
|
|
180
|
+
inside that same directory. Sensitive patterns and symlink containment apply relative to that
|
|
181
|
+
directory, and frontend_stack names it in its answer.
|
|
182
|
+
|
|
183
|
+
### The bundle config/boot.rb declares
|
|
184
|
+
|
|
185
|
+
An app with no lockfile of its own (an engine's `test/dummy`) has its gems in the
|
|
186
|
+
Gemfile its `config/boot.rb` sets as `BUNDLE_GEMFILE` (`../../Gemfile`). That
|
|
187
|
+
Gemfile and its lockfile (`gems.rb` and `gems.locked` alike) are read with the same
|
|
188
|
+
trust as the app's own `Gemfile.lock`, only when their directory is inside the app's
|
|
189
|
+
git repository, never above its root and never outside a repository. Besides those two,
|
|
190
|
+
only a file that Gemfile names with `eval_gemfile` inside that directory, and the `lib/`
|
|
191
|
+
of a path gem the lockfile names inside the repository (the engine's own `remote: .`),
|
|
192
|
+
are read. A file there that links out of the directory is refused.
|
|
193
|
+
|
|
194
|
+
### The engine an app's test/dummy runs in
|
|
195
|
+
|
|
196
|
+
Booted from an engine's `test/dummy`, the engine whose root holds the app root (a loaded
|
|
197
|
+
`Rails::Engine`, never this gem) is the project's own source: its `app/` code, its
|
|
198
|
+
`app/views` layouts and templates, and, when the dummy keeps no `test/` or `spec/` of its
|
|
199
|
+
own, its test suite are read. Paths into it are printed relative to the app root
|
|
200
|
+
(`../../app/models/...`), and symlink containment applies relative to the engine root.
|
|
201
|
+
Without booting, only the test suite is read this way, and only from the directory of the
|
|
202
|
+
bundle `config/boot.rb` declares (read under the rule above) when it holds a `*.gemspec`
|
|
203
|
+
and contains the app root.
|
|
204
|
+
|
|
150
205
|
### How a refusal is reported
|
|
151
206
|
|
|
152
207
|
A path refused on policy - outside the app, a traversal, a sensitive file - comes back as an
|