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.
Files changed (222) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +868 -0
  3. data/CONTEXT.md +7 -3
  4. data/README.md +1 -1
  5. data/docs/ARCHITECTURE.md +3 -3
  6. data/docs/CLI.md +1 -1
  7. data/docs/COMPATIBILITY.md +50 -14
  8. data/docs/CONFIGURATION.md +4 -1
  9. data/docs/FAQ.md +1 -1
  10. data/docs/GUIDE.md +14 -8
  11. data/docs/INTROSPECTORS.md +50 -23
  12. data/docs/SECURITY.md +56 -1
  13. data/docs/TOOLS.md +58 -12
  14. data/lib/generators/rails_ai_context/install/install_generator.rb +1 -0
  15. data/lib/rails_ai_context/action_filters.rb +121 -48
  16. data/lib/rails_ai_context/app_commands.rb +2 -2
  17. data/lib/rails_ai_context/app_kind.rb +65 -8
  18. data/lib/rails_ai_context/ast_cache.rb +53 -18
  19. data/lib/rails_ai_context/base_mixins.rb +46 -16
  20. data/lib/rails_ai_context/boot_manager.rb +5 -1
  21. data/lib/rails_ai_context/cli/entry_boot.rb +17 -2
  22. data/lib/rails_ai_context/cli/tool_runner.rb +15 -1
  23. data/lib/rails_ai_context/concern_macros.rb +791 -111
  24. data/lib/rails_ai_context/concern_membership.rb +25 -5
  25. data/lib/rails_ai_context/concern_paths.rb +63 -17
  26. data/lib/rails_ai_context/config_yaml.rb +40 -0
  27. data/lib/rails_ai_context/configuration.rb +3 -3
  28. data/lib/rails_ai_context/data/docs/index.json +324 -0
  29. data/lib/rails_ai_context/database_yml.rb +136 -0
  30. data/lib/rails_ai_context/doctor.rb +30 -37
  31. data/lib/rails_ai_context/erb_source.rb +40 -0
  32. data/lib/rails_ai_context/fingerprinter.rb +48 -22
  33. data/lib/rails_ai_context/fixture_keys.rb +42 -1
  34. data/lib/rails_ai_context/gem_lock.rb +275 -47
  35. data/lib/rails_ai_context/git_ignore.rb +13 -11
  36. data/lib/rails_ai_context/hydrators/hydration_formatter.rb +8 -2
  37. data/lib/rails_ai_context/hydrators/schema_hint_builder.rb +10 -5
  38. data/lib/rails_ai_context/introspector.rb +10 -1
  39. data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +27 -19
  40. data/lib/rails_ai_context/introspectors/action_presence.rb +11 -8
  41. data/lib/rails_ai_context/introspectors/action_resolver.rb +38 -26
  42. data/lib/rails_ai_context/introspectors/action_text_introspector.rb +2 -2
  43. data/lib/rails_ai_context/introspectors/active_record_settings.rb +56 -0
  44. data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -1
  45. data/lib/rails_ai_context/introspectors/active_support_introspector.rb +98 -22
  46. data/lib/rails_ai_context/introspectors/admin_resources.rb +101 -0
  47. data/lib/rails_ai_context/introspectors/anyway_configs.rb +64 -0
  48. data/lib/rails_ai_context/introspectors/apartment_config.rb +47 -0
  49. data/lib/rails_ai_context/introspectors/api_introspector.rb +125 -26
  50. data/lib/rails_ai_context/introspectors/asset_pipeline_introspector.rb +24 -19
  51. data/lib/rails_ai_context/introspectors/ast_walk.rb +18 -0
  52. data/lib/rails_ai_context/introspectors/auth_introspector.rb +71 -18
  53. data/lib/rails_ai_context/introspectors/autoload_introspector.rb +62 -15
  54. data/lib/rails_ai_context/introspectors/base.rb +38 -0
  55. data/lib/rails_ai_context/introspectors/call_site_expansion.rb +349 -39
  56. data/lib/rails_ai_context/introspectors/component_introspector.rb +26 -14
  57. data/lib/rails_ai_context/introspectors/config_introspector.rb +22 -9
  58. data/lib/rails_ai_context/introspectors/connection_pool_introspector.rb +1 -4
  59. data/lib/rails_ai_context/introspectors/controller_filters.rb +335 -31
  60. data/lib/rails_ai_context/introspectors/controller_introspector.rb +315 -129
  61. data/lib/rails_ai_context/introspectors/controller_settings.rb +182 -0
  62. data/lib/rails_ai_context/introspectors/convention_introspector.rb +18 -15
  63. data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +1 -1
  64. data/lib/rails_ai_context/introspectors/declared_constant.rb +108 -23
  65. data/lib/rails_ai_context/introspectors/eager_load.rb +12 -7
  66. data/lib/rails_ai_context/introspectors/engine_introspector.rb +13 -5
  67. data/lib/rails_ai_context/introspectors/env_config_introspector.rb +62 -17
  68. data/lib/rails_ai_context/introspectors/env_introspector.rb +8 -6
  69. data/lib/rails_ai_context/introspectors/env_references.rb +21 -4
  70. data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +37 -22
  71. data/lib/rails_ai_context/introspectors/gem_introspector.rb +3 -1
  72. data/lib/rails_ai_context/introspectors/gemfile_gems.rb +53 -5
  73. data/lib/rails_ai_context/introspectors/grape_endpoints.rb +109 -0
  74. data/lib/rails_ai_context/introspectors/helper_names.rb +12 -1
  75. data/lib/rails_ai_context/introspectors/i18n_introspector.rb +101 -46
  76. data/lib/rails_ai_context/introspectors/includers.rb +6 -1
  77. data/lib/rails_ai_context/introspectors/initializer_introspector.rb +2 -5
  78. data/lib/rails_ai_context/introspectors/job_introspector.rb +548 -103
  79. data/lib/rails_ai_context/introspectors/listener_registration.rb +11 -1
  80. data/lib/rails_ai_context/introspectors/listeners/associations_listener.rb +51 -4
  81. data/lib/rails_ai_context/introspectors/listeners/autoload_ignore_listener.rb +31 -0
  82. data/lib/rails_ai_context/introspectors/listeners/autoload_paths_listener.rb +4 -47
  83. data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +80 -14
  84. data/lib/rails_ai_context/introspectors/listeners/branch_conditions.rb +156 -0
  85. data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +85 -23
  86. data/lib/rails_ai_context/introspectors/listeners/class_definition_listener.rb +35 -6
  87. data/lib/rails_ai_context/introspectors/listeners/component_structure_listener.rb +30 -2
  88. data/lib/rails_ai_context/introspectors/listeners/conditional_macro_listener.rb +19 -0
  89. data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +255 -10
  90. data/lib/rails_ai_context/introspectors/listeners/constant_reference_listener.rb +34 -0
  91. data/lib/rails_ai_context/introspectors/listeners/constructor_macro_listener.rb +123 -0
  92. data/lib/rails_ai_context/introspectors/listeners/definition_file_paths_listener.rb +66 -0
  93. data/lib/rails_ai_context/introspectors/listeners/enums_listener.rb +2 -2
  94. data/lib/rails_ai_context/introspectors/listeners/env_access_listener.rb +31 -0
  95. data/lib/rails_ai_context/introspectors/listeners/filter_macro_listener.rb +54 -0
  96. data/lib/rails_ai_context/introspectors/listeners/fixture_paths_listener.rb +62 -0
  97. data/lib/rails_ai_context/introspectors/listeners/gemfile_dsl_listener.rb +42 -1
  98. data/lib/rails_ai_context/introspectors/listeners/generic_macro_listener.rb +28 -2
  99. data/lib/rails_ai_context/introspectors/listeners/grape_api_listener.rb +137 -0
  100. data/lib/rails_ai_context/introspectors/listeners/http_client_call_listener.rb +50 -0
  101. data/lib/rails_ai_context/introspectors/listeners/i18n_load_path_listener.rb +54 -0
  102. data/lib/rails_ai_context/introspectors/listeners/literal_paths.rb +123 -0
  103. data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +248 -4
  104. data/lib/rails_ai_context/introspectors/listeners/mailbox_routing_listener.rb +2 -1
  105. data/lib/rails_ai_context/introspectors/listeners/method_call_listener.rb +7 -1
  106. data/lib/rails_ai_context/introspectors/listeners/methods_listener.rb +352 -69
  107. data/lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb +1 -1
  108. data/lib/rails_ai_context/introspectors/listeners/migration_replay_listener.rb +35 -26
  109. data/lib/rails_ai_context/introspectors/listeners/mixins_listener.rb +62 -29
  110. data/lib/rails_ai_context/introspectors/listeners/mount_listener.rb +8 -0
  111. data/lib/rails_ai_context/introspectors/listeners/namespaced_roots_listener.rb +35 -0
  112. data/lib/rails_ai_context/introspectors/listeners/nested_constants_listener.rb +32 -0
  113. data/lib/rails_ai_context/introspectors/listeners/owner_scope.rb +67 -0
  114. data/lib/rails_ai_context/introspectors/listeners/preview_paths_listener.rb +16 -7
  115. data/lib/rails_ai_context/introspectors/listeners/proc_literal_listener.rb +42 -0
  116. data/lib/rails_ai_context/introspectors/listeners/queue_assignment_listener.rb +54 -0
  117. data/lib/rails_ai_context/introspectors/listeners/rake_task_dsl_listener.rb +21 -8
  118. data/lib/rails_ai_context/introspectors/listeners/route_files_listener.rb +4 -11
  119. data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +461 -65
  120. data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +76 -14
  121. data/lib/rails_ai_context/introspectors/listeners/scopes_listener.rb +40 -1
  122. data/lib/rails_ai_context/introspectors/listeners/validations_listener.rb +10 -1
  123. data/lib/rails_ai_context/introspectors/listeners/view_paths_listener.rb +37 -0
  124. data/lib/rails_ai_context/introspectors/middleware_introspector.rb +47 -3
  125. data/lib/rails_ai_context/introspectors/migration_introspector.rb +3 -3
  126. data/lib/rails_ai_context/introspectors/migration_replay/helpers.rb +4 -3
  127. data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +72 -12
  128. data/lib/rails_ai_context/introspectors/migration_replay.rb +110 -14
  129. data/lib/rails_ai_context/introspectors/model_introspector.rb +708 -214
  130. data/lib/rails_ai_context/introspectors/module_aliases.rb +11 -3
  131. data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +9 -57
  132. data/lib/rails_ai_context/introspectors/observability_introspector.rb +5 -8
  133. data/lib/rails_ai_context/introspectors/performance_introspector.rb +28 -8
  134. data/lib/rails_ai_context/introspectors/rake_task_introspector.rb +118 -51
  135. data/lib/rails_ai_context/introspectors/recurring_schedules.rb +150 -0
  136. data/lib/rails_ai_context/introspectors/rendered_record.rb +87 -0
  137. data/lib/rails_ai_context/introspectors/retry_policy.rb +8 -5
  138. data/lib/rails_ai_context/introspectors/route_introspector.rb +153 -42
  139. data/lib/rails_ai_context/introspectors/schema_conventions.rb +127 -40
  140. data/lib/rails_ai_context/introspectors/schema_dump_path.rb +80 -0
  141. data/lib/rails_ai_context/introspectors/schema_introspector.rb +346 -112
  142. data/lib/rails_ai_context/introspectors/schema_reader.rb +69 -29
  143. data/lib/rails_ai_context/introspectors/service_classes.rb +21 -15
  144. data/lib/rails_ai_context/introspectors/source_calls.rb +1 -1
  145. data/lib/rails_ai_context/introspectors/source_introspector.rb +17 -2
  146. data/lib/rails_ai_context/introspectors/source_scan.rb +174 -26
  147. data/lib/rails_ai_context/introspectors/sqlite_virtual_tables.rb +28 -0
  148. data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +16 -5
  149. data/lib/rails_ai_context/introspectors/structure_sql_reader.rb +372 -98
  150. data/lib/rails_ai_context/introspectors/superclass_chain.rb +14 -15
  151. data/lib/rails_ai_context/introspectors/table_name.rb +46 -45
  152. data/lib/rails_ai_context/introspectors/test_introspector.rb +289 -78
  153. data/lib/rails_ai_context/introspectors/turbo_introspector.rb +10 -5
  154. data/lib/rails_ai_context/introspectors/view_introspector.rb +5 -40
  155. data/lib/rails_ai_context/introspectors/view_template_introspector.rb +12 -10
  156. data/lib/rails_ai_context/method_name.rb +31 -0
  157. data/lib/rails_ai_context/migration_status.rb +5 -5
  158. data/lib/rails_ai_context/output_guard.rb +6 -4
  159. data/lib/rails_ai_context/package_json.rb +96 -5
  160. data/lib/rails_ai_context/path_resolver.rb +170 -15
  161. data/lib/rails_ai_context/payload.rb +48 -0
  162. data/lib/rails_ai_context/pending_migrations.rb +15 -18
  163. data/lib/rails_ai_context/portable_path.rb +76 -4
  164. data/lib/rails_ai_context/redaction.rb +8 -2
  165. data/lib/rails_ai_context/route_coverage.rb +5 -1
  166. data/lib/rails_ai_context/safe_file.rb +4 -1
  167. data/lib/rails_ai_context/safe_path.rb +61 -10
  168. data/lib/rails_ai_context/schema_hint.rb +8 -2
  169. data/lib/rails_ai_context/schema_version.rb +7 -9
  170. data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +1 -1
  171. data/lib/rails_ai_context/serializers/markdown_serializer.rb +13 -4
  172. data/lib/rails_ai_context/serializers/section_facts.rb +5 -1
  173. data/lib/rails_ai_context/serializers/stack_overview_helper.rb +2 -2
  174. data/lib/rails_ai_context/serializers/tool_guide_helper.rb +7 -4
  175. data/lib/rails_ai_context/server.rb +26 -7
  176. data/lib/rails_ai_context/test_framework.rb +32 -5
  177. data/lib/rails_ai_context/tools/analyze_feature.rb +22 -10
  178. data/lib/rails_ai_context/tools/base_tool.rb +69 -30
  179. data/lib/rails_ai_context/tools/diagnose.rb +1 -1
  180. data/lib/rails_ai_context/tools/generate_test.rb +8 -9
  181. data/lib/rails_ai_context/tools/get_active_support.rb +19 -4
  182. data/lib/rails_ai_context/tools/get_api.rb +17 -11
  183. data/lib/rails_ai_context/tools/get_autoload.rb +10 -5
  184. data/lib/rails_ai_context/tools/get_callbacks.rb +65 -14
  185. data/lib/rails_ai_context/tools/get_component_catalog.rb +7 -4
  186. data/lib/rails_ai_context/tools/get_concern.rb +31 -18
  187. data/lib/rails_ai_context/tools/get_config.rb +52 -18
  188. data/lib/rails_ai_context/tools/get_controllers.rb +57 -13
  189. data/lib/rails_ai_context/tools/get_conventions.rb +44 -22
  190. data/lib/rails_ai_context/tools/get_engines.rb +2 -0
  191. data/lib/rails_ai_context/tools/get_env.rb +209 -52
  192. data/lib/rails_ai_context/tools/get_env_config.rb +38 -7
  193. data/lib/rails_ai_context/tools/get_frontend_stack.rb +56 -7
  194. data/lib/rails_ai_context/tools/get_helper_methods.rb +136 -26
  195. data/lib/rails_ai_context/tools/get_job_pattern.rb +143 -87
  196. data/lib/rails_ai_context/tools/get_mailers.rb +59 -2
  197. data/lib/rails_ai_context/tools/get_model_details.rb +178 -28
  198. data/lib/rails_ai_context/tools/get_partial_interface.rb +101 -62
  199. data/lib/rails_ai_context/tools/get_routes.rb +35 -9
  200. data/lib/rails_ai_context/tools/get_schema.rb +160 -61
  201. data/lib/rails_ai_context/tools/get_service_pattern.rb +121 -25
  202. data/lib/rails_ai_context/tools/get_stimulus.rb +1 -1
  203. data/lib/rails_ai_context/tools/get_test_info.rb +152 -82
  204. data/lib/rails_ai_context/tools/get_view.rb +37 -20
  205. data/lib/rails_ai_context/tools/migration_advisor.rb +64 -24
  206. data/lib/rails_ai_context/tools/onboard.rb +174 -88
  207. data/lib/rails_ai_context/tools/performance_check.rb +5 -13
  208. data/lib/rails_ai_context/tools/query.rb +122 -28
  209. data/lib/rails_ai_context/tools/read_logs.rb +45 -17
  210. data/lib/rails_ai_context/tools/runtime_info.rb +1 -1
  211. data/lib/rails_ai_context/tools/safe_call.rb +20 -15
  212. data/lib/rails_ai_context/tools/search_code.rb +114 -40
  213. data/lib/rails_ai_context/tools/search_docs.rb +22 -11
  214. data/lib/rails_ai_context/tools/security_scan.rb +51 -4
  215. data/lib/rails_ai_context/tools/validate.rb +21 -4
  216. data/lib/rails_ai_context/tools/validate_semantics.rb +18 -6
  217. data/lib/rails_ai_context/version.rb +1 -1
  218. data/lib/rails_ai_context/vfs.rb +2 -2
  219. data/lib/rails_ai_context/view_file.rb +53 -3
  220. data/lib/rails_ai_context.rb +10 -2
  221. data/server.json +1 -1
  222. 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 those three declarations out of a source 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].
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 run order, and a class's own body wins.** The chain keeps the order reflection or the source gives it, so a `prepend_before_action` does not fall behind everything the class inherits. 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.
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
  [![Gem Version](https://img.shields.io/gem/v/rails-ai-context?style=flat-square&color=brightgreen)](https://rubygems.org/gems/rails-ai-context)
8
8
  [![Downloads](https://img.shields.io/gem/dt/rails-ai-context?style=flat-square&color=blue)](https://rubygems.org/gems/rails-ai-context)
9
- [![CI](https://img.shields.io/github/actions/workflow/status/crisnahine/rails-ai-context/ci.yml?style=flat-square&label=CI)](https://github.com/crisnahine/rails-ai-context/actions)
9
+ [![CI](https://img.shields.io/github/actions/workflow/status/crisnahine/rails-ai-context/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/crisnahine/rails-ai-context/actions)
10
10
  [![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-green?style=flat-square)](https://registry.modelcontextprotocol.io)
11
11
  [![Ruby](https://img.shields.io/badge/Ruby-3.1_to_3.4-CC342D?style=flat-square&logo=ruby&logoColor=white)](https://github.com/crisnahine/rails-ai-context)
12
12
  [![Rails](https://img.shields.io/badge/Rails-7.0_to_8.1-CC0000?style=flat-square&logo=rubyonrails&logoColor=white)](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\n26 listeners\nConfidence tags"]
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
- - **26 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
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/tasks/`, Gemfile.lock
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/tasks/`.
178
+ Requires the `listen` gem. Watches `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`.
179
179
 
180
180
  ### `init` (standalone only)
181
181
 
@@ -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
- inherits from declare. A callback a
282
- gem registers on include without an `include` line in the model file (Devise,
283
- counter caches, attachment purges) is not listed. Within one type the order
284
- is declaration order, the model file first and then its concerns, not the
285
- order Rails registered them in; the order across types is Rails' event order.
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, `direct`, `resolve`, a `draw` it cannot read, a block drawn
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`), and a route whose `to:` is a
326
- lambda or a `redirect(...)` into a `dynamic_routes` count rather than
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 and a `with_options` block that takes no block
329
- parameter are expanded, so they are not in that count. A `with_options` that
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` does not hide its
332
- children: they are read and resolved, the constraint itself is ignored.
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
 
@@ -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) | Override frontend file paths |
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/tasks/`. When files change, caches are invalidated and MCP clients are notified. Debounce interval: 1.5s (configurable).
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 concern-provided callbacks, with their bodies read from the concern file. Within one type the order is declaration order, not the order Rails registered them in.
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` or `.tool-versions` answered (JSON) |
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 in `lib/tasks/` with names, descriptions, namespaces, file paths. |
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/tasks/`, and regenerates only the files that changed (diff-aware, skips unchanged files).
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 `lib/tasks/` for changes
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
@@ -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)`. Returns args, values (with a source-slice fallback), options, option values and option nodes, 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 |
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, namespaces resolved |
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
- | PreviewPathsListener | ViewComponent preview directories the config sets: `view_component.previews.paths`, `preview_paths`, `preview_path`, in the same literal forms as AutoloadPathsListener |
206
- | SchemaDslListener | `schema.rb`: `create_table`, `t.string`, `t.index`, `add_foreign_key`, `create_enum` |
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"` and `group :development do ... end` |
212
- | RakeTaskDslListener | `namespace`, `desc`, `task` in `.rake` files |
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
- | MethodCallListener | Call sites by name or pattern anywhere in a file, inside a `def`, a lambda or a block included, with arguments, options, receiver and line. Used by the Turbo introspector for broadcast calls and by `ActionFilters` for the skip macros |
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
- | `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 |
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/tasks/`, `Gemfile.lock`). When the fingerprint changes, the introspection cache is invalidated even if TTL hasn't expired.
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 | `PRAGMA query_only = ON` + progress handler for timeout |
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