rails-ai-context 5.26.0 → 5.28.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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +329 -0
  3. data/README.md +3 -1
  4. data/docs/ARCHITECTURE.md +2 -2
  5. data/docs/COMPATIBILITY.md +4 -1
  6. data/docs/CONFIGURATION.md +27 -0
  7. data/docs/FAQ.md +13 -0
  8. data/docs/INTROSPECTORS.md +1 -1
  9. data/docs/QUICKSTART.md +5 -1
  10. data/docs/SETUP.md +4 -0
  11. data/docs/STANDALONE.md +3 -0
  12. data/docs/TOOLS.md +12 -6
  13. data/exe/rails-ai-context +59 -20
  14. data/lib/generators/rails_ai_context/install/install_generator.rb +54 -11
  15. data/lib/rails_ai_context/action_filters.rb +43 -7
  16. data/lib/rails_ai_context/boot_manager.rb +23 -2
  17. data/lib/rails_ai_context/cli/entry_boot.rb +6 -0
  18. data/lib/rails_ai_context/cli/tool_runner.rb +10 -22
  19. data/lib/rails_ai_context/concern_macros.rb +1 -2
  20. data/lib/rails_ai_context/concern_paths.rb +6 -1
  21. data/lib/rails_ai_context/configuration.rb +8 -1
  22. data/lib/rails_ai_context/detail_level.rb +7 -4
  23. data/lib/rails_ai_context/doctor.rb +5 -2
  24. data/lib/rails_ai_context/erb_source.rb +45 -0
  25. data/lib/rails_ai_context/fingerprinter.rb +1 -2
  26. data/lib/rails_ai_context/gem_lock.rb +18 -2
  27. data/lib/rails_ai_context/hydrators/controller_hydrator.rb +3 -6
  28. data/lib/rails_ai_context/hydrators/model_hints.rb +5 -2
  29. data/lib/rails_ai_context/hydrators/schema_hint_builder.rb +1 -1
  30. data/lib/rails_ai_context/hydrators/view_hydrator.rb +1 -2
  31. data/lib/rails_ai_context/install/cleanup.rb +23 -7
  32. data/lib/rails_ai_context/install/program.rb +37 -13
  33. data/lib/rails_ai_context/install/selection_record.rb +72 -0
  34. data/lib/rails_ai_context/install_mode.rb +1 -2
  35. data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +1 -2
  36. data/lib/rails_ai_context/introspectors/action_resolver.rb +45 -13
  37. data/lib/rails_ai_context/introspectors/action_text_introspector.rb +2 -4
  38. data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +4 -8
  39. data/lib/rails_ai_context/introspectors/active_support_introspector.rb +17 -16
  40. data/lib/rails_ai_context/introspectors/api_introspector.rb +92 -21
  41. data/lib/rails_ai_context/introspectors/asset_pipeline_introspector.rb +2 -8
  42. data/lib/rails_ai_context/introspectors/auth_introspector.rb +11 -22
  43. data/lib/rails_ai_context/introspectors/autoload_introspector.rb +6 -12
  44. data/lib/rails_ai_context/introspectors/component_introspector.rb +3 -15
  45. data/lib/rails_ai_context/introspectors/config_introspector.rb +9 -64
  46. data/lib/rails_ai_context/introspectors/connection_pool_introspector.rb +5 -10
  47. data/lib/rails_ai_context/introspectors/controller_filters.rb +1 -2
  48. data/lib/rails_ai_context/introspectors/controller_introspector.rb +23 -114
  49. data/lib/rails_ai_context/introspectors/convention_introspector.rb +21 -49
  50. data/lib/rails_ai_context/introspectors/credentials_introspector.rb +3 -6
  51. data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +3 -6
  52. data/lib/rails_ai_context/introspectors/declared_constant.rb +2 -4
  53. data/lib/rails_ai_context/introspectors/devops_introspector.rb +35 -56
  54. data/lib/rails_ai_context/introspectors/eager_load.rb +1 -2
  55. data/lib/rails_ai_context/introspectors/engine_introspector.rb +2 -4
  56. data/lib/rails_ai_context/introspectors/env_config_introspector.rb +48 -11
  57. data/lib/rails_ai_context/introspectors/env_introspector.rb +2 -4
  58. data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +12 -31
  59. data/lib/rails_ai_context/introspectors/gem_introspector.rb +60 -8
  60. data/lib/rails_ai_context/introspectors/i18n_introspector.rb +48 -48
  61. data/lib/rails_ai_context/introspectors/initializer_introspector.rb +4 -8
  62. data/lib/rails_ai_context/introspectors/job_introspector.rb +89 -18
  63. data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +3 -8
  64. data/lib/rails_ai_context/introspectors/listeners/class_definition_listener.rb +2 -2
  65. data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +36 -0
  66. data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +22 -4
  67. data/lib/rails_ai_context/introspectors/middleware_introspector.rb +3 -6
  68. data/lib/rails_ai_context/introspectors/migration_introspector.rb +2 -4
  69. data/lib/rails_ai_context/introspectors/migration_replay.rb +21 -36
  70. data/lib/rails_ai_context/introspectors/model_introspector.rb +58 -30
  71. data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +5 -10
  72. data/lib/rails_ai_context/introspectors/observability_introspector.rb +6 -12
  73. data/lib/rails_ai_context/introspectors/performance_introspector.rb +73 -30
  74. data/lib/rails_ai_context/introspectors/route_introspector.rb +37 -30
  75. data/lib/rails_ai_context/introspectors/schema_introspector.rb +6 -12
  76. data/lib/rails_ai_context/introspectors/schema_reader.rb +1 -2
  77. data/lib/rails_ai_context/introspectors/security_introspector.rb +9 -18
  78. data/lib/rails_ai_context/introspectors/source_introspector.rb +1 -2
  79. data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +6 -51
  80. data/lib/rails_ai_context/introspectors/table_name.rb +1 -2
  81. data/lib/rails_ai_context/introspectors/test_introspector.rb +20 -46
  82. data/lib/rails_ai_context/introspectors/turbo_introspector.rb +19 -51
  83. data/lib/rails_ai_context/introspectors/view_introspector.rb +6 -10
  84. data/lib/rails_ai_context/introspectors/view_template_introspector.rb +19 -10
  85. data/lib/rails_ai_context/legacy_cleanup.rb +3 -1
  86. data/lib/rails_ai_context/mcp_config_generator.rb +42 -67
  87. data/lib/rails_ai_context/migration_status.rb +1 -2
  88. data/lib/rails_ai_context/output_guard.rb +43 -3
  89. data/lib/rails_ai_context/package_json.rb +98 -0
  90. data/lib/rails_ai_context/payload.rb +45 -0
  91. data/lib/rails_ai_context/portable_path.rb +1 -2
  92. data/lib/rails_ai_context/redaction.rb +26 -10
  93. data/lib/rails_ai_context/schema_adapter.rb +2 -2
  94. data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +6 -34
  95. data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +1 -37
  96. data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +1 -40
  97. data/lib/rails_ai_context/serializers/stack_overview_helper.rb +41 -0
  98. data/lib/rails_ai_context/serializers/tool_guide_helper.rb +7 -1
  99. data/lib/rails_ai_context/tasks/rails_ai_context.rake +37 -16
  100. data/lib/rails_ai_context/tools/analyze_feature.rb +45 -73
  101. data/lib/rails_ai_context/tools/base_tool.rb +41 -35
  102. data/lib/rails_ai_context/tools/dependency_graph.rb +96 -15
  103. data/lib/rails_ai_context/tools/diagnose.rb +2 -4
  104. data/lib/rails_ai_context/tools/generate_test.rb +144 -54
  105. data/lib/rails_ai_context/tools/get_active_support.rb +5 -1
  106. data/lib/rails_ai_context/tools/get_api.rb +25 -6
  107. data/lib/rails_ai_context/tools/get_callbacks.rb +7 -11
  108. data/lib/rails_ai_context/tools/get_component_catalog.rb +1 -5
  109. data/lib/rails_ai_context/tools/get_concern.rb +43 -57
  110. data/lib/rails_ai_context/tools/get_context.rb +2 -1
  111. data/lib/rails_ai_context/tools/get_controllers.rb +54 -34
  112. data/lib/rails_ai_context/tools/get_conventions.rb +25 -29
  113. data/lib/rails_ai_context/tools/get_env.rb +99 -116
  114. data/lib/rails_ai_context/tools/get_frontend_stack.rb +1 -5
  115. data/lib/rails_ai_context/tools/get_gems.rb +9 -2
  116. data/lib/rails_ai_context/tools/get_helper_methods.rb +58 -51
  117. data/lib/rails_ai_context/tools/get_job_pattern.rb +29 -18
  118. data/lib/rails_ai_context/tools/get_model_details.rb +10 -25
  119. data/lib/rails_ai_context/tools/get_partial_interface.rb +21 -40
  120. data/lib/rails_ai_context/tools/get_routes.rb +7 -7
  121. data/lib/rails_ai_context/tools/get_schema.rb +3 -9
  122. data/lib/rails_ai_context/tools/get_service_pattern.rb +134 -51
  123. data/lib/rails_ai_context/tools/get_stimulus.rb +10 -13
  124. data/lib/rails_ai_context/tools/get_test_info.rb +3 -9
  125. data/lib/rails_ai_context/tools/get_turbo_map.rb +4 -11
  126. data/lib/rails_ai_context/tools/get_view.rb +12 -10
  127. data/lib/rails_ai_context/tools/migration_advisor.rb +2 -4
  128. data/lib/rails_ai_context/tools/onboard.rb +6 -223
  129. data/lib/rails_ai_context/tools/performance_check.rb +28 -35
  130. data/lib/rails_ai_context/tools/query.rb +6 -1
  131. data/lib/rails_ai_context/tools/runtime_info.rb +4 -11
  132. data/lib/rails_ai_context/tools/search_code.rb +41 -21
  133. data/lib/rails_ai_context/tools/search_docs.rb +1 -2
  134. data/lib/rails_ai_context/tools/section_fetch.rb +1 -2
  135. data/lib/rails_ai_context/tools/security_scan.rb +1 -5
  136. data/lib/rails_ai_context/tools/session_context.rb +1 -2
  137. data/lib/rails_ai_context/tools/validate.rb +1 -2
  138. data/lib/rails_ai_context/tools/validate_semantics.rb +42 -17
  139. data/lib/rails_ai_context/version.rb +1 -1
  140. data/lib/rails_ai_context/vfs.rb +9 -18
  141. data/lib/rails_ai_context/view_file.rb +38 -0
  142. data/lib/rails_ai_context/watcher.rb +4 -1
  143. data/lib/rails_ai_context.rb +22 -1
  144. data/server.json +1 -1
  145. metadata +3 -2
  146. data/lib/rails_ai_context/serializers/section_guard.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5554194142695cce91ac263b45bcd692fdfa2fe80d85ddbfe70c52a85eab9087
4
- data.tar.gz: 1b040d294942a14f92a5b86b58b185f3c15e840ad9976d2e3b90a888de66b6e8
3
+ metadata.gz: 78848e75315236681b6b31bba337665e75a143d862d2d1ff828f48f50b1756f5
4
+ data.tar.gz: 8af5164b8ee4ea9900d82450d2a74905dcf1b831c02d7ba95c5c869274c18e9e
5
5
  SHA512:
6
- metadata.gz: dc5ee392e5faa628133f7abdf33b1fd655c167f4e7cfbf2fd2dff21b9047dff2648b89e4e5fa3be07ca6fc98a93084e30cf9954e06f588ab43ab40dbf6abc832
7
- data.tar.gz: 71dda3b0936a89dce1c56b6b6f5889762b0f0ce2265a3ec42eab7c824155be84728de8ae2da9104bd7653a9e26568a01858fb6207ca705fd66dfdb4fcb52c932
6
+ metadata.gz: 6dbfa116e75a4675314e6c4a06ada6c2c7d7b8c7f0d9fb5ca0abe9945add65f1729b0101200debf5c25ea2190e66b57f41761b4307d8414d77ccfc62dc292e9d
7
+ data.tar.gz: 86984bc5566480da28d88b7ef475138bb85c9ba459fcd87e58f8a4afd03239a365ee30d032412701291ec0bd60f7f0e0cccfa882fee0150e6baed74c966a33b1
data/CHANGELOG.md CHANGED
@@ -5,6 +5,335 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.28.0] - 2026-09-22
9
+
10
+ Thirty-six changes an architecture survey of v5.27.0 asked for, nine of them
11
+ defects where a user got a wrong answer, plus what three review rounds found in
12
+ the fixes themselves.
13
+
14
+ ### Added
15
+
16
+ - **Services, helpers, jobs and concerns are found wherever the app keeps
17
+ them.** The four tools globbed `app/<kind>` off the app root, so a packwerk
18
+ app whose services live under `packs/billing/app/services` was told it had no
19
+ services directory and may not use the pattern. Directory discovery reads
20
+ `PathResolver.dirs_for`, which already resolved packs, engines and
21
+ `extra_app_paths` for the payload side. `ConcernPaths.resolve` and
22
+ `ActiveSupportIntrospector` read the same resolver, so a concern's listing,
23
+ its lookup and its "Used By" line now agree on one app. Counts move on any
24
+ app with packs or engines. Each walk names the directory a file came from, so
25
+ two files of the same basename under different roots no longer collide, and a
26
+ tool that finds nothing names all three directory patterns it searched
27
+ instead of the conventional one alone.
28
+ - **`RailsAiContext.debug_fail`.** About 258 rescue bodies in `lib/`
29
+ hand-rolled the same three lines: rescue the error, write a `DEBUG`-gated
30
+ warning to stderr, return a fallback. They call one method now, with every
31
+ message string and every fallback value unchanged. `exe/rails-ai-context`
32
+ shims it beside `log_warn`, because the standalone install path loads seven
33
+ files by hand and a converted rescue inside `install_mode.rb` would otherwise
34
+ raise `NoMethodError` after the removed-tool cleanup had already deleted
35
+ files.
36
+ - **`PackageJson`.** GemLock's twin for the node side: `package.json` parsed
37
+ once per root, `dependencies` merged with `devDependencies`, capped by
38
+ `configuration.max_file_size` rather than by a constant no caller can reach,
39
+ and empty for anything it cannot read. A scope named after the package
40
+ counts, so an app reaching tailwind through `@tailwindcss/vite` keeps its
41
+ answer.
42
+
43
+ ### Removed
44
+
45
+ - **`config.job_processor`, gone from `.ai-context.json` and from
46
+ `get_config`.** `ConfigIntrospector` parsed `config/sidekiq.yml` a second
47
+ time to publish it, and nothing read it: no renderer, no spec, no template.
48
+ The two readers disagreed, so a `:queues:` block written with weights,
49
+ `- [critical, 2]`, lost `critical` on the config side and kept it on the jobs
50
+ side, and one sidekiq.yml shipped two queue lists in one payload. The same
51
+ concurrency and queues stay under `jobs.sidekiq_config`, which
52
+ `get_job_pattern` already consumes, and `config.queue_adapter` still names
53
+ the adapter. Both sections sit in the standard and full presets, so no preset
54
+ loses the fact.
55
+ - **`action_bindings` and `outlet_controllers`, gone from every Stimulus
56
+ controller entry.** Filling `action_bindings` read every ERB, HAML and Slim
57
+ file under `app/views` and `app/components` on every generation, and
58
+ `outlet_controllers` restated the outlets list one line above it. Nothing in
59
+ `lib/` read either key: `get_stimulus` renders outlets, and the compact
60
+ serializer touches the section through `controllers` and `total_controllers`.
61
+ Both keys leave the artifact, and one full pass over the view and component
62
+ trees leaves every generation.
63
+ - **`Serializers::SectionGuard`, `DetailLevel::SCHEMA_ENUM` and
64
+ `Install::Program.select_tool_mode`.** SectionGuard was 15 lines forwarding
65
+ to `Tools::SectionFetch.usable?`; both callers sat in `SchemaAdapter`, which
66
+ is not a serializer, and `RouteCoverage` already called SectionFetch
67
+ directly. SCHEMA_ENUM went with its last reference when
68
+ `DetailLevel.schema(description)` replaced the same four-line input-schema
69
+ hash in 21 tools; every published schema is byte-identical, and
70
+ `rails_onboard` keeps its own literal because its levels are quick, standard
71
+ and full. `select_tool_mode` was a shim for `select_setup(surface).tool_mode`
72
+ with no caller left in `lib/`, and it cannot express the MCP-config-only
73
+ mode. `ModelIntrospector#sti_bases` and
74
+ `ControllerIntrospector#extract_permit_details` go with them; only their own
75
+ recursion and their own specs were calling them.
76
+
77
+ ### Changed
78
+
79
+ - **`.github/instructions/rails-context.instructions.md` prints every gem
80
+ category.** `.first(6)` on the grouped Hash returned the first six pairs, so
81
+ the seventh category onward was dropped with nothing saying so, while
82
+ `.cursor/rules/rails-project.mdc` rendered the same grouping uncapped. One
83
+ payload, two gem lists.
84
+ - **`.cursor/rules/rails-project.mdc` bolds `Global before_actions:`** like the
85
+ Claude and Copilot files. The three serializers each hand-wrote the same
86
+ ten-step overview and had drifted; `StackOverviewHelper#overview_lines`
87
+ states the body once and each renderer keeps only its frontmatter, title and
88
+ MCP hint.
89
+ - **Puma settings are read inside blocks.** The hand-rolled recursion stopped
90
+ at the top level, so a `workers` line guarded by an environment conditional,
91
+ which is what the generated `puma.rb` ships, was invisible and the answer
92
+ read as no workers configured. `MethodCallListener` sees a receiverless call
93
+ at any depth. The cost of the widening is that nesting no longer hides a
94
+ setting, so a name set twice reports the last one wherever it sits. Also
95
+ `threads ENV.fetch("RAILS_MIN_THREADS") { 5 }, ENV.fetch("RAILS_MAX_THREADS")
96
+ { 5 }` reports 5 and 5 where it reported nothing, and the digit match is
97
+ anchored away from word characters, so `ENV.fetch("PORT_2", 3000)` no longer
98
+ reads as 2.
99
+ - **`get_conventions`' frontend stack string reads parsed dependencies.**
100
+ `detect_frontend_stack` matched bare substrings against the raw file, with no
101
+ quotes at all, so `vite-plugin-ruby` on its own reported Vite. The markers
102
+ carry alternates where an exact name would have lost an answer the substring
103
+ caught: svelte or `@sveltejs/kit`, `@hotwired/turbo` or
104
+ `@hotwired/turbo-rails`.
105
+ - **`api_introspector`'s codegen list reads parsed dependencies.** It searched
106
+ the file for the quoted tool name, so `openapi-typescript`, `orval` or
107
+ `@graphql-codegen/cli` named anywhere in `package.json`, an `overrides` block
108
+ included, counted as a client generator the app runs.
109
+ - **`rails_generate_test` resolves a controller by the rule
110
+ `rails_get_controllers` uses.** It camelized the string and looked the result
111
+ up, so with `Admin::GiftCardsController` as the only gift-card controller,
112
+ `rails_get_controllers(controller: "gift_cards")` resolved and
113
+ `rails_generate_test(controller: "gift_cards")` answered "not found".
114
+ `Payload.find_controller` answers for both, with the camelize kept as the
115
+ fallback for a payload carrying no controllers at all.
116
+
117
+ ### Fixed
118
+
119
+ Nine defects the survey confirmed against v5.27.0, and the defects three review
120
+ rounds found in the work that fixed them.
121
+
122
+ - **`onboard(detail: "quick")` no longer prints a guessed domain noun.** Eleven
123
+ ordered regexes read model, job and service names and stated the result as
124
+ plain fact with no confidence marker, so one model named `Message` made an
125
+ app "a messaging app". Quick mode now states what it measured: versions,
126
+ table, model and job counts, the frontend and the test framework.
127
+ - **`api_namespaces` invented a namespace and missed one.** The two tiers
128
+ derived the list their own way. The booted regex kept a trailing slash on
129
+ `/api/users`, matched unanchored so `namespace :admin { namespace :api }`
130
+ reported `/api/v1` for an app that serves `/admin/api/v1`, and dropped a
131
+ route sitting at exactly `/api`. The static spelling got the segment boundary
132
+ wrong, so `/apidocs` reported `/api`. Both tiers read one anchored form.
133
+ - **Turbo drive settings were double-counted for layouts.** `app/views/layouts`
134
+ was walked again after `app/views/**/*` had already reached it. The permanent
135
+ element pass threw its duplicate away through `uniq`; the drive settings did
136
+ not, so one attribute in a layout and one in a view printed as 3.
137
+ - **The asset pipeline reported the wrong bundler and CSS framework.** Three
138
+ readers asked whether a package was present by searching the raw file for its
139
+ quoted name, so an app pinning CVE fixes in an `overrides` block was told
140
+ esbuild is its bundler and postcss its CSS framework, while the one reader
141
+ that parsed the file called the same app vite. Both answers landed in one
142
+ payload.
143
+ - **`analyze_feature`'s Jobs, Mailers and Channels sections came from a glob.**
144
+ Each job's queue was read off its own `queue_as`, so a job inheriting the
145
+ queue from `ApplicationJob` was reported on a queue it does not use. The
146
+ three sections read the payload now, which carries what the booted tier
147
+ resolved, mailer actions and channel stream methods included. A section whose
148
+ payload entry is not there, because the `:jobs` introspector is off, is left
149
+ out rather than rendered from a directory walk.
150
+ - **`performance_check` attributed queries to methods that are not actions.** A
151
+ `def` inside a nested class became a call site, and a one-line
152
+ `private def set_post` leaked its body into the action above it.
153
+ `ActionResolver` answers what an action is here, as it does everywhere else.
154
+ A second collision went with it: each body was found by searching the whole
155
+ file for the first `def <name>`, so a nested class defining the same name
156
+ earlier handed back its body and the real action was never scanned. The body
157
+ is cut from the lines the owner-filtered walk recorded, and `get_controllers`
158
+ and `get_context` share the seam. An empty owner filter falls back to the
159
+ line scan rather than answering nothing, and `def self.index` no longer
160
+ stands in for `def index`.
161
+ - **A model whose only rule is `validate :method` printed its bullets under the
162
+ wrong heading.** `## Validations` was emitted only when the reflected list
163
+ had entries, and the custom bullets come from a second list that is disjoint
164
+ from it on both tiers, so they landed under Associations.
165
+ - **Two ERB readers missed `<%== ... %>` and read a commented ivar as used.**
166
+ `check_instance_variable_usage` and `extract_local_variable_references` each
167
+ carried their own copy of the tag regex, byte for byte the same and neither
168
+ matching `ErbSource::TAG`. Both read `<%[=\-]?`, one optional character, so
169
+ `<%== title %>` parsed as a body of "= title" and the local never reached a
170
+ partial's expected locals. The comment skip was a second answer too:
171
+ `get_partial_interface` re-implemented it and `validate_semantics` never had
172
+ one, so `<%# @ghost %>` was reported as an ivar used in the view and not set
173
+ in the controller. Both readers ask `ErbSource` now.
174
+ - **`rails_get_controllers` refused a name the VFS resource accepts.** VFS
175
+ resolved five ways, the tool two, so `controller: "gift_cards"` failed
176
+ against `Admin::GiftCardsController` while the resource answered off the same
177
+ payload. Both read `Payload.find_controller`, so a route key, an unambiguous
178
+ basename and the singularize and classify spellings now resolve in the tool
179
+ too.
180
+ - **Two files behind one name no longer hide each other.** Once services,
181
+ helpers and concerns were read from packs and engines, a name could have more
182
+ than one file behind it. The ambiguous-service list printed
183
+ `app/services/<path>` for files that live under a pack, so two candidates
184
+ printed as two identical lines naming a file that exists in neither, and the
185
+ suggestion that followed handed back the name it had just refused. Paths
186
+ print from the app root, and a narrowing suggestion is offered only when some
187
+ candidate's relative path is unique. `rails_get_helper_methods` rendered the
188
+ first match as the whole module and listed the others under "Also defined
189
+ in" even when they declare different modules, so a file declaring
190
+ `Reports::DashboardHelper` was named under an `Admin::DashboardHelper`
191
+ heading; matches are split by the module each file declares.
192
+ `rails_get_concern` broke on the first directory that resolved, so an app
193
+ with both `app/models/concerns/trackable.rb` and
194
+ `app/controllers/concerns/trackable.rb` saw only the controller one. Every
195
+ "available" list is deduplicated where it is built.
196
+
197
+ ## [5.27.0] - 2026-09-22
198
+
199
+ ### Added
200
+
201
+ - **MCP-only install (`--mcp-only`, `config.context_files`).** Some apps keep
202
+ their own `CLAUDE.md`, `AGENTS.md`, rules files and Copilot instructions and
203
+ want the server and nothing else. There was no supported way to ask for it:
204
+ every install entry point ended by generating context files. The install
205
+ menu now has a third answer, `rails generate rails_ai_context:install
206
+ --mcp-only` and `rails-ai-context init --mcp-only` take it non-interactively,
207
+ and the choice is recorded in the initializer and the YAML like the tool
208
+ mode. With it off, `ai:context` writes nothing and exits 0, `ai:watch` writes
209
+ nothing, `ai:doctor` raises no context-file warning, and the `.ai-context.json`
210
+ line is left out of `.gitignore` because nothing writes that file. A command
211
+ that names a file still writes it: `ai:context:claude` and
212
+ `context --format claude` both work.
213
+ - **Sidekiq workers in `job_pattern`.** A class that includes `Sidekiq::Job` is
214
+ not an ActiveJob descendant and does not live in `app/jobs`, so both job
215
+ passes missed it and no tool in either tier could describe the biggest code
216
+ area of an app that runs its background work that way. Workers are read from
217
+ `app/workers` in both tiers, with each one's `sidekiq_options` and `perform`
218
+ signature. The conventions directory structure counts every directory the app
219
+ keeps under `app/` rather than a fixed list.
220
+ - **ActiveInteraction services.** `service_pattern` lists a service's declared
221
+ filters with their types and defaults, names `ActiveInteraction::Base` as the
222
+ dominant pattern when it is, and `generate_test` writes `.run` with those
223
+ filters rather than a `.call` the base class does not define.
224
+ - **Notable gems that shape an app.** `active_interaction`, `paper_trail`,
225
+ `rack-cors`, `figaro`, `webauthn`, `stripe`, `plaid`, `braintree`,
226
+ `lockbox`, `blind_index`, `neighbor`, `pgvector`, `interactor`,
227
+ `trailblazer-operation` and the Sidekiq add-ons (`sidekiq-pro`,
228
+ `sidekiq-scheduler`, `sidekiq-cron`, `sidekiq-unique-jobs`,
229
+ `sidekiq-throttled`).
230
+
231
+ ### Changed
232
+
233
+ - **`config.ai_tools = []` writes no context files.** It used to write every
234
+ tool's files, which is the opposite of what the value says and of what
235
+ `ContextFileSerializer` has always done with `format: []`. Only an unset
236
+ selection means "all". The install flow never produced an empty list, so this
237
+ reaches hand-edited configs only.
238
+ - **The inherited filter list is emitted root first.** Rails runs the root's
239
+ callbacks first, and the static tier printed the nearest parent's first, so a
240
+ chain read as authentication running before the current user is loaded.
241
+ - **A filter no ancestor's body declares names no class.** `sentry_around_action`
242
+ and `set_paper_trail_whodunnit` arrive through a gem's
243
+ `on_load :action_controller` block; crediting them to the nearest app
244
+ controller sent an agent to a file that never mentions them. They now carry
245
+ `provenance` instead of `from`.
246
+ - **Model callbacks print the macro Rails has.** `after_commit_on_create` is a
247
+ key this gem synthesizes to order the events of one `after_commit on: [...]`;
248
+ copying it got a `NoMethodError`. Every renderer prints
249
+ `after_commit (on: :create)`.
250
+ - **`sanitize_options` keeps Array, numeric, boolean, nil and Symbol option
251
+ values.** They were stringified, so `in: %w[draft sent]` reached
252
+ `generate_test` as one String and `in_array` got a quoted list. A consumer
253
+ reading `validations[].options`, `.ai-context.json` included, sees the
254
+ change: `dependent: :destroy` is the Symbol `:destroy` rather than
255
+ `"destroy"`, and `allow_nil: false` is `false` rather than the truthy String
256
+ `"false"`.
257
+ - **Only a handler extension counts as a template.** A JPEG or a seed file under
258
+ `app/views` was counted as a template and had ivar names read out of its
259
+ bytes.
260
+
261
+ ### Fixed
262
+
263
+ Defects found by a tenth QA round of v5.26.0 against a private Rails 8.0.5.1
264
+ API-only app (issues #184 to #222), each rebuilt on a minimal fixture before
265
+ filing.
266
+
267
+ - **One dangling reflection cost the whole model.** A `has_many :through` naming
268
+ an association the model does not declare loads fine and only raises when
269
+ something touches it, so `class_name` ended in `nil.klass` and the per-model
270
+ rescue replaced the model - its callbacks, its table heading, its graph node -
271
+ with a single error line. The rescue is per association now, and the
272
+ reflection is marked `[UNAVAILABLE: through :x is not an association]`.
273
+ - **The MCP transport lost its stdout across a Bundler re-exec.** `exec` closed
274
+ the saved descriptor, so the new image saved fd 1, by then pointing at stderr,
275
+ and wrote every JSON-RPC response there. A client launched from outside the
276
+ app got no answer to `initialize`.
277
+ - **`dependency_graph` drew nodes no app defines.** A `through` hop was the
278
+ association name camelized (`PrimaryBuyer`, `InvoicePdfAttachment`), the
279
+ static target ignored `source:`, and a derived name knew none of the app's
280
+ acronyms. It also cut the graph at 50 models without saying so.
281
+ - **`service_pattern` named a service after a word in a comment.** The regex ran
282
+ over raw source, never matched `module`, and its basename fallback dropped
283
+ every namespace. The lookup matched by basename before exact path, so only
284
+ the alphabetically first `create.rb` could be looked up, and "Called By" was a
285
+ substring search that listed a non-caller and dropped the real one.
286
+ - **Static route helper names did not exist.** Hyphens were kept
287
+ (`api_v1_gift-cards_redeem_path` is a subtraction in Ruby), a dotted path was
288
+ used in the name where Rails uses none, and a name that cannot be one was
289
+ kept anyway. A booted redirect route appeared in no row and no count.
290
+ - **`get_context` for one action listed every strong-params method in the
291
+ controller**, so `create_params`' permit list read as the fields `deactivate`
292
+ accepts.
293
+ - **Controller Schema Hints reported services, serializers and plain constants
294
+ as missing models.** Only names that resolve to a model are kept now, and the
295
+ rest are dropped without a line.
296
+ - **`env` read only `.rb` files**, so ENV names in `config/*.yml`, ERB views and
297
+ rake tasks were missing with nothing saying a file type was skipped. Category
298
+ matching was unanchored (`PORT` inside `PORTAL`), and a `nil` default printed
299
+ as `[FILTERED]`.
300
+ - **`env_config` reported the first assignment in a file**, so it printed the
301
+ branch that is not running and disagreed with `config` on the same app.
302
+ - **`gems` reported a Minitest suite in a `test/` directory that is not there**,
303
+ from a lockfile entry every Rails app resolves through activesupport, and
304
+ pointed at a `config/sidekiq.yml` without checking that it exists.
305
+ - **`generate_test` wrote specs that cannot run.** Shoulda matchers were emitted
306
+ into apps that do not bundle the gem, a namespaced controller produced
307
+ `create(:api/v1/admin/order)`, the example sent GET whatever the route's verb,
308
+ and a controller with no file was answered with "no routes found".
309
+ - **`performance_check` suggested indexes and counters the app cannot use.**
310
+ Every unindexed `*_id` column was a missing foreign key index whatever its
311
+ type, and a counter the app maintains itself was offered a `counter_cache`
312
+ that double-counts every create.
313
+ - **`query` answered an unknown column with "Database not found" and exit 0.**
314
+ Postgres words a missing column, table and database the same way.
315
+ - **`view` counted images and text files as templates**, read ivars out of
316
+ binary data, and read the CSS rule `@page` as an ivar.
317
+ - **`partial_interface` could not resolve a `.text.erb` partial** by the Rails
318
+ name its own "Available" list had just printed, and cut a local's method calls
319
+ at ten with no marker.
320
+ - **`api` merged every CORS allow block and environment branch into one origin
321
+ list**, so the line read as the API allowing `*` on every resource.
322
+ - **`active_support` listed validator classes as "plain module" concerns.**
323
+ - **`validate` flagged an `acceptance:` virtual attribute as a missing column**
324
+ and suggested a migration for a column nobody needs.
325
+ - **`search_code --match-type trace` reported no internal calls** for a body
326
+ whose calls take no parentheses, counted a comment mentioning the method as a
327
+ call site, and tagged `app/services/models/...` as a Model.
328
+ - **Static answers missed an app's inflections.** A route group found no
329
+ controller for `api/v1/ai_matches`, and a service file with no `class` line
330
+ was named by its camelized basename.
331
+ - **The `controllers/{name}` resource refused a short name** the `routes/{name}`
332
+ resource accepts, and the controller answer pointed at a model that does not
333
+ exist.
334
+ - **`ai:watch` rewrote every tool's files** whatever the configuration asked
335
+ for.
336
+
8
337
  ## [5.26.0] - 2026-09-11
9
338
 
10
339
  ### Added
data/README.md CHANGED
@@ -87,7 +87,9 @@ bundle add rails-ai-context --group development
87
87
  rails generate rails_ai_context:install
88
88
  ```
89
89
 
90
- The generator asks which AI tools you use and whether you want MCP or CLI mode, then writes the context files, the MCP config for each tool, and `config/initializers/rails_ai_context.rb`. Re-running it is safe; it keeps what you have and adds what is missing.
90
+ The generator asks which AI tools you use and what to write, then creates the context files, the MCP config for each tool, and `config/initializers/rails_ai_context.rb`. Re-running it is safe; it keeps what you have and adds what is missing.
91
+
92
+ Keeping your own `CLAUDE.md` and `AGENTS.md`? `rails generate rails_ai_context:install --mcp-only` writes the MCP config and leaves every context file alone.
91
93
 
92
94
  ### Install standalone
93
95
 
data/docs/ARCHITECTURE.md CHANGED
@@ -110,11 +110,12 @@ flowchart LR
110
110
 
111
111
  The `Introspector` orchestrator runs configured introspectors and merges results.
112
112
 
113
- Three modules answer questions every introspector used to answer for itself:
113
+ Four modules answer questions every introspector used to answer for itself:
114
114
 
115
115
  - **SourceScan** - one walk over a kind of app source, across every directory `PathResolver` resolves: `paths` stats, `each` reads, `classes` names by the declared constant
116
116
  - **EagerLoad** - loads a directory's constants for a booted-tier walk, one file at a time, so an unloadable file costs only itself
117
117
  - **GemLock** - which gems the app resolved and at what version, read once per lockfile and matched by exact name across GEM, GIT and PATH
118
+ - **PackageJson** - which npm packages the app depends on, read once per `package.json`: present means named in `dependencies` or `devDependencies`, so an `overrides` pin is not a dependency, and `@tailwindcss/vite` counts as tailwindcss
118
119
 
119
120
  Two more answer a question a tool asks:
120
121
 
@@ -198,7 +199,6 @@ Result: controller and view tools automatically include relevant schema informat
198
199
  | `ToolGuideHelper` | MCP/CLI tool reference sections |
199
200
  | `TestCommandDetection` | Test framework detection |
200
201
  | `SectionFacts` | The facts every surface states about an app - auth, assets, associations, the filter chain, an unread entry's row, the static-tier notice - each rendered in one place |
201
- | `SectionGuard` | Whether a section resolved, so a refused one is not rendered |
202
202
  | `SectionMarkerWriter` | Writes a managed section into a file the user also owns |
203
203
  | `ContextModeDispatch` | Picks full or compact rendering for a run |
204
204
 
@@ -200,7 +200,10 @@ Proof sources:
200
200
  (Rails 8.1, Ruby 3.4) in the v5.25.0 release QA, booted and static tiers,
201
201
  standalone and in-Gemfile installs; Mastodon again in the v5.26.0 release QA,
202
202
  with packs, in-repo engines, Postgres and concurrent tool calls covered by
203
- hand where no lab shape plants them.
203
+ hand where no lab shape plants them; a private Rails 8.0.5.1 API-only app
204
+ (Ruby 3.4.10, 133 models, 102 controllers, 1630 ActiveInteraction services,
205
+ 516 Sidekiq workers) in the v5.27.0 QA round, booted and static tiers, with
206
+ every report rebuilt on a minimal Rails 8.0.5.1 fixture before it was filed.
204
207
  2. Non-crash coverage for every built-in tool including `get_view` in
205
208
  `spec/e2e/in_gemfile_install_spec.rb`'s full-tool sweep; output correctness
206
209
  (ivar cross-check, render-form detection, partial interfaces) verified
@@ -51,6 +51,33 @@ preset: full
51
51
  |:-------|:-----|:--------|:------------|
52
52
  | `ai_tools` | Array of symbols | `[:claude]` | Which AI tools to generate context for. Options: `:claude`, `:cursor`, `:copilot`, `:opencode`, `:codex` |
53
53
  | `tool_mode` | Symbol | `:mcp` | `:mcp` (MCP server primary, CLI fallback) or `:cli` (CLI only, no MCP server) |
54
+ | `context_files` | Boolean | `true` | Set `false` for MCP-only: the server and the CLI still answer, and no context file is written or touched |
55
+
56
+ #### MCP only
57
+
58
+ Some apps keep their own `CLAUDE.md`, `AGENTS.md` and rules files and want the
59
+ server and nothing else:
60
+
61
+ ```bash
62
+ rails generate rails_ai_context:install --mcp-only # or: rails-ai-context init --mcp-only
63
+ ```
64
+
65
+ That writes the MCP config for the tools you pick and records:
66
+
67
+ ```ruby
68
+ RailsAiContext.configure do |config|
69
+ config.tool_mode = :mcp
70
+ config.context_files = false
71
+ end
72
+ ```
73
+
74
+ `rails ai:context` then writes nothing and exits 0, `rails ai:watch` writes
75
+ nothing, and `rails ai:doctor` raises no context-file warning. A command that
76
+ names a file still writes it: `rails ai:context:claude` and
77
+ `rails-ai-context context --format claude`.
78
+
79
+ `config.ai_tools = []` means no context files too, and still picks which MCP
80
+ config file is written. Before v5.27.0 an empty list wrote every tool's files.
54
81
 
55
82
  ### Introspection
56
83
 
data/docs/FAQ.md CHANGED
@@ -59,6 +59,19 @@ Yes, freely. Both generate identical context files and provide the same 45 tools
59
59
  **Don't commit:**
60
60
  - `.ai-context.json` - auto-added to .gitignore by the install generator
61
61
 
62
+ ### Can I use only the MCP server?
63
+
64
+ Yes:
65
+
66
+ ```bash
67
+ rails generate rails_ai_context:install --mcp-only # or: rails-ai-context init --mcp-only
68
+ ```
69
+
70
+ The MCP config is written, `config.context_files = false` is recorded, and no
71
+ `CLAUDE.md`, `AGENTS.md`, rules file or `.ai-context.json` is written or
72
+ touched. Every tool still answers over MCP and over the CLI. See
73
+ [CONFIGURATION.md](CONFIGURATION.md#mcp-only).
74
+
62
75
  ---
63
76
 
64
77
  ## MCP & Tools
@@ -126,7 +126,7 @@ end
126
126
 
127
127
  | Introspector | Key | What it extracts |
128
128
  |:-------------|:----|:-----------------|
129
- | JobIntrospector | `:jobs` | Background jobs and mailers: queue, retries, schedules, and the `file:` each one is defined in |
129
+ | JobIntrospector | `:jobs` | Background jobs, Sidekiq workers under `app/workers`, and mailers: queue, retries, `sidekiq_options`, schedules, and the `file:` each one is defined in |
130
130
  | RakeTaskIntrospector | `:rake_tasks` | Custom rake tasks |
131
131
 
132
132
  ### Security & Auth
data/docs/QUICKSTART.md CHANGED
@@ -23,7 +23,11 @@ rails generate rails_ai_context:install
23
23
  The generator asks two questions:
24
24
 
25
25
  1. **Which AI tools do you use?** - Claude Code, Cursor, GitHub Copilot, OpenCode, Codex CLI, or all
26
- 2. **Do you want MCP server support?** - Yes (MCP mode) or No (CLI-only mode)
26
+ 2. **What should rails-ai-context write?** - MCP config + context files (default), context files only (CLI mode), or MCP config only
27
+
28
+ The third answer is MCP-only: the server and the CLI answer in full, and your
29
+ own `CLAUDE.md`, `AGENTS.md` and rules files are left alone. Non-interactively
30
+ that is `--mcp-only`.
27
31
 
28
32
  That's it. Your AI tool now has live access to your schema, models, routes, controllers, views, and conventions.
29
33
 
data/docs/SETUP.md CHANGED
@@ -66,6 +66,10 @@ This creates:
66
66
  - `.claude/rules/rails-context.md` - General context rules (always loaded)
67
67
  - `.claude/rules/rails-mcp-tools.md` - Tool reference (always loaded)
68
68
 
69
+ Keeping your own `CLAUDE.md`? Add `--mcp-only` and only `.mcp.json` is
70
+ written; every context file is left alone. See
71
+ [CONFIGURATION.md](CONFIGURATION.md#mcp-only).
72
+
69
73
  ### Manual MCP config
70
74
 
71
75
  If you need to configure manually, create `.mcp.json`:
data/docs/STANDALONE.md CHANGED
@@ -143,6 +143,9 @@ rails-ai-context init
143
143
 
144
144
  The MCP config files are updated automatically. Both modes generate identical context files and provide the same 45 tools.
145
145
 
146
+ Both also take `--mcp-only`, which writes the MCP config and no context files
147
+ at all. See [CONFIGURATION.md](CONFIGURATION.md#mcp-only).
148
+
146
149
  ## Troubleshooting
147
150
 
148
151
  ### "Bundler::GemNotFound" on `rails-ai-context serve`
data/docs/TOOLS.md CHANGED
@@ -349,7 +349,7 @@ Notable gems with versions, categories, and config file locations.
349
349
 
350
350
  ### `rails_get_env`
351
351
 
352
- Environment variables + credentials keys (values are never exposed).
352
+ Environment variables + credentials keys (values are never exposed). Scans `.rb`, `.rake`, ERB views and config YAML under `app`, `config` and `lib`; files matching `sensitive_patterns` (`config/database.yml`, credentials, keys) are never read, and the answer says so.
353
353
 
354
354
  | Parameter | Type | Default | Description |
355
355
  |:----------|:-----|:--------|:------------|
@@ -374,7 +374,9 @@ Service object interface, dependencies, side effects, callers.
374
374
 
375
375
  ### `rails_get_job_pattern`
376
376
 
377
- Background job queue, retries, guard clauses, broadcasts, schedules.
377
+ Background job queue, retries, guard clauses, broadcasts, schedules. Sidekiq
378
+ workers under `app/workers` are listed alongside the ActiveJob jobs, with
379
+ their `sidekiq_options` and `perform` signature.
378
380
 
379
381
  | Parameter | Type | Default | Description |
380
382
  |:----------|:-----|:--------|:------------|
@@ -430,7 +432,7 @@ ActiveSupport surface: concerns registry, deprecators, MessageVerifier/MessageEn
430
432
 
431
433
  ### `rails_get_env_config`
432
434
 
433
- Per-environment configuration from `config/environments/*.rb`: notable toggles (`force_ssl`, `eager_load`, caching, log level, queue adapter, mailer delivery) and every config key each environment sets.
435
+ Per-environment configuration from `config/environments/*.rb`: notable toggles (`force_ssl`, `eager_load`, caching, log level, queue adapter, mailer delivery) and every config key each environment sets. A key assigned in more than one branch reports every value with its condition; booted, the running environment reports the value the app resolved.
434
436
 
435
437
  | Parameter | Type | Default | Description |
436
438
  |:----------|:-----|:--------|:------------|
@@ -450,9 +452,13 @@ Model/service dependency graph in Mermaid or text format.
450
452
 
451
453
  | Parameter | Type | Default | Description |
452
454
  |:----------|:-----|:--------|:------------|
453
- | `root` | string | - | Starting node |
454
- | `format` | enum | `text` | `text`, `mermaid` |
455
- | `detail` | enum | `standard` | `summary`, `standard`, `full` |
455
+ | `model` | string | - | Center the graph on this model |
456
+ | `depth` | integer | `2` | Hops from the centre model (1-3) |
457
+ | `format` | enum | `mermaid` | `mermaid`, `text` |
458
+ | `show_cycles` | boolean | `false` | Detect and list circular dependencies |
459
+ | `show_sti` | boolean | `false` | Show Single Table Inheritance hierarchies |
460
+
461
+ Without `model` the graph is capped at 50 nodes, and says so when it cuts.
456
462
 
457
463
  ### `rails_migration_advisor`
458
464