rails-ai-context 5.26.0 → 5.27.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +140 -0
  3. data/README.md +3 -1
  4. data/docs/COMPATIBILITY.md +4 -1
  5. data/docs/CONFIGURATION.md +27 -0
  6. data/docs/FAQ.md +13 -0
  7. data/docs/INTROSPECTORS.md +1 -1
  8. data/docs/QUICKSTART.md +5 -1
  9. data/docs/SETUP.md +4 -0
  10. data/docs/STANDALONE.md +3 -0
  11. data/docs/TOOLS.md +12 -6
  12. data/exe/rails-ai-context +46 -16
  13. data/lib/generators/rails_ai_context/install/install_generator.rb +54 -11
  14. data/lib/rails_ai_context/action_filters.rb +42 -5
  15. data/lib/rails_ai_context/configuration.rb +8 -1
  16. data/lib/rails_ai_context/doctor.rb +4 -0
  17. data/lib/rails_ai_context/erb_source.rb +45 -0
  18. data/lib/rails_ai_context/gem_lock.rb +18 -2
  19. data/lib/rails_ai_context/hydrators/controller_hydrator.rb +1 -2
  20. data/lib/rails_ai_context/hydrators/model_hints.rb +5 -2
  21. data/lib/rails_ai_context/install/program.rb +33 -9
  22. data/lib/rails_ai_context/install/selection_record.rb +72 -0
  23. data/lib/rails_ai_context/introspectors/active_support_introspector.rb +9 -0
  24. data/lib/rails_ai_context/introspectors/api_introspector.rb +87 -8
  25. data/lib/rails_ai_context/introspectors/convention_introspector.rb +17 -1
  26. data/lib/rails_ai_context/introspectors/env_config_introspector.rb +47 -7
  27. data/lib/rails_ai_context/introspectors/gem_introspector.rb +58 -4
  28. data/lib/rails_ai_context/introspectors/job_introspector.rb +36 -4
  29. data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +36 -0
  30. data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +22 -4
  31. data/lib/rails_ai_context/introspectors/model_introspector.rb +56 -15
  32. data/lib/rails_ai_context/introspectors/performance_introspector.rb +70 -2
  33. data/lib/rails_ai_context/introspectors/route_introspector.rb +30 -13
  34. data/lib/rails_ai_context/introspectors/view_introspector.rb +1 -0
  35. data/lib/rails_ai_context/introspectors/view_template_introspector.rb +16 -7
  36. data/lib/rails_ai_context/output_guard.rb +43 -3
  37. data/lib/rails_ai_context/tasks/rails_ai_context.rake +34 -8
  38. data/lib/rails_ai_context/tools/analyze_feature.rb +7 -1
  39. data/lib/rails_ai_context/tools/base_tool.rb +9 -0
  40. data/lib/rails_ai_context/tools/dependency_graph.rb +96 -15
  41. data/lib/rails_ai_context/tools/generate_test.rb +110 -14
  42. data/lib/rails_ai_context/tools/get_active_support.rb +5 -1
  43. data/lib/rails_ai_context/tools/get_api.rb +24 -0
  44. data/lib/rails_ai_context/tools/get_callbacks.rb +6 -6
  45. data/lib/rails_ai_context/tools/get_controllers.rb +33 -3
  46. data/lib/rails_ai_context/tools/get_env.rb +68 -31
  47. data/lib/rails_ai_context/tools/get_gems.rb +9 -2
  48. data/lib/rails_ai_context/tools/get_job_pattern.rb +25 -3
  49. data/lib/rails_ai_context/tools/get_model_details.rb +2 -1
  50. data/lib/rails_ai_context/tools/get_partial_interface.rb +12 -20
  51. data/lib/rails_ai_context/tools/get_routes.rb +6 -2
  52. data/lib/rails_ai_context/tools/get_service_pattern.rb +81 -20
  53. data/lib/rails_ai_context/tools/get_view.rb +9 -1
  54. data/lib/rails_ai_context/tools/onboard.rb +5 -0
  55. data/lib/rails_ai_context/tools/query.rb +6 -1
  56. data/lib/rails_ai_context/tools/search_code.rb +38 -7
  57. data/lib/rails_ai_context/tools/validate_semantics.rb +32 -1
  58. data/lib/rails_ai_context/version.rb +1 -1
  59. data/lib/rails_ai_context/vfs.rb +22 -2
  60. data/lib/rails_ai_context/view_file.rb +38 -0
  61. data/lib/rails_ai_context/watcher.rb +4 -1
  62. data/lib/rails_ai_context.rb +7 -1
  63. data/server.json +1 -1
  64. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5554194142695cce91ac263b45bcd692fdfa2fe80d85ddbfe70c52a85eab9087
4
- data.tar.gz: 1b040d294942a14f92a5b86b58b185f3c15e840ad9976d2e3b90a888de66b6e8
3
+ metadata.gz: 44038590682c51bc7722a4a5b587ee31b5b78f3842575b6386390a584038b81e
4
+ data.tar.gz: 4c7de07da1f44144291ba86e9bfc34ab3a3da893c0a159b8cfe99de284d3d3be
5
5
  SHA512:
6
- metadata.gz: dc5ee392e5faa628133f7abdf33b1fd655c167f4e7cfbf2fd2dff21b9047dff2648b89e4e5fa3be07ca6fc98a93084e30cf9954e06f588ab43ab40dbf6abc832
7
- data.tar.gz: 71dda3b0936a89dce1c56b6b6f5889762b0f0ce2265a3ec42eab7c824155be84728de8ae2da9104bd7653a9e26568a01858fb6207ca705fd66dfdb4fcb52c932
6
+ metadata.gz: b581d96cb4ff655823a103c21527a53ddb71c3cb36d644a59485a796693001e247f6480b381d1f54ea8ec0995e175aa0389109fe1155bfbeea2dfeefd5566d9c
7
+ data.tar.gz: 61c4aa682dd49afaf780652300f41de2216a0bfbe0b6ed7e6ec188237c63eade5036fe2051ba6771f89e96803a0971811230e532a7c623d7a25d0bfb1f069df8
data/CHANGELOG.md CHANGED
@@ -5,6 +5,146 @@ 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.27.0] - 2026-09-22
9
+
10
+ ### Added
11
+
12
+ - **MCP-only install (`--mcp-only`, `config.context_files`).** Some apps keep
13
+ their own `CLAUDE.md`, `AGENTS.md`, rules files and Copilot instructions and
14
+ want the server and nothing else. There was no supported way to ask for it:
15
+ every install entry point ended by generating context files. The install
16
+ menu now has a third answer, `rails generate rails_ai_context:install
17
+ --mcp-only` and `rails-ai-context init --mcp-only` take it non-interactively,
18
+ and the choice is recorded in the initializer and the YAML like the tool
19
+ mode. With it off, `ai:context` writes nothing and exits 0, `ai:watch` writes
20
+ nothing, `ai:doctor` raises no context-file warning, and the `.ai-context.json`
21
+ line is left out of `.gitignore` because nothing writes that file. A command
22
+ that names a file still writes it: `ai:context:claude` and
23
+ `context --format claude` both work.
24
+ - **Sidekiq workers in `job_pattern`.** A class that includes `Sidekiq::Job` is
25
+ not an ActiveJob descendant and does not live in `app/jobs`, so both job
26
+ passes missed it and no tool in either tier could describe the biggest code
27
+ area of an app that runs its background work that way. Workers are read from
28
+ `app/workers` in both tiers, with each one's `sidekiq_options` and `perform`
29
+ signature. The conventions directory structure counts every directory the app
30
+ keeps under `app/` rather than a fixed list.
31
+ - **ActiveInteraction services.** `service_pattern` lists a service's declared
32
+ filters with their types and defaults, names `ActiveInteraction::Base` as the
33
+ dominant pattern when it is, and `generate_test` writes `.run` with those
34
+ filters rather than a `.call` the base class does not define.
35
+ - **Notable gems that shape an app.** `active_interaction`, `paper_trail`,
36
+ `rack-cors`, `figaro`, `webauthn`, `stripe`, `plaid`, `braintree`,
37
+ `lockbox`, `blind_index`, `neighbor`, `pgvector`, `interactor`,
38
+ `trailblazer-operation` and the Sidekiq add-ons (`sidekiq-pro`,
39
+ `sidekiq-scheduler`, `sidekiq-cron`, `sidekiq-unique-jobs`,
40
+ `sidekiq-throttled`).
41
+
42
+ ### Changed
43
+
44
+ - **`config.ai_tools = []` writes no context files.** It used to write every
45
+ tool's files, which is the opposite of what the value says and of what
46
+ `ContextFileSerializer` has always done with `format: []`. Only an unset
47
+ selection means "all". The install flow never produced an empty list, so this
48
+ reaches hand-edited configs only.
49
+ - **The inherited filter list is emitted root first.** Rails runs the root's
50
+ callbacks first, and the static tier printed the nearest parent's first, so a
51
+ chain read as authentication running before the current user is loaded.
52
+ - **A filter no ancestor's body declares names no class.** `sentry_around_action`
53
+ and `set_paper_trail_whodunnit` arrive through a gem's
54
+ `on_load :action_controller` block; crediting them to the nearest app
55
+ controller sent an agent to a file that never mentions them. They now carry
56
+ `provenance` instead of `from`.
57
+ - **Model callbacks print the macro Rails has.** `after_commit_on_create` is a
58
+ key this gem synthesizes to order the events of one `after_commit on: [...]`;
59
+ copying it got a `NoMethodError`. Every renderer prints
60
+ `after_commit (on: :create)`.
61
+ - **`sanitize_options` keeps Array, numeric, boolean, nil and Symbol option
62
+ values.** They were stringified, so `in: %w[draft sent]` reached
63
+ `generate_test` as one String and `in_array` got a quoted list. A consumer
64
+ reading `validations[].options`, `.ai-context.json` included, sees the
65
+ change: `dependent: :destroy` is the Symbol `:destroy` rather than
66
+ `"destroy"`, and `allow_nil: false` is `false` rather than the truthy String
67
+ `"false"`.
68
+ - **Only a handler extension counts as a template.** A JPEG or a seed file under
69
+ `app/views` was counted as a template and had ivar names read out of its
70
+ bytes.
71
+
72
+ ### Fixed
73
+
74
+ Defects found by a tenth QA round of v5.26.0 against a private Rails 8.0.5.1
75
+ API-only app (issues #184 to #222), each rebuilt on a minimal fixture before
76
+ filing.
77
+
78
+ - **One dangling reflection cost the whole model.** A `has_many :through` naming
79
+ an association the model does not declare loads fine and only raises when
80
+ something touches it, so `class_name` ended in `nil.klass` and the per-model
81
+ rescue replaced the model - its callbacks, its table heading, its graph node -
82
+ with a single error line. The rescue is per association now, and the
83
+ reflection is marked `[UNAVAILABLE: through :x is not an association]`.
84
+ - **The MCP transport lost its stdout across a Bundler re-exec.** `exec` closed
85
+ the saved descriptor, so the new image saved fd 1, by then pointing at stderr,
86
+ and wrote every JSON-RPC response there. A client launched from outside the
87
+ app got no answer to `initialize`.
88
+ - **`dependency_graph` drew nodes no app defines.** A `through` hop was the
89
+ association name camelized (`PrimaryBuyer`, `InvoicePdfAttachment`), the
90
+ static target ignored `source:`, and a derived name knew none of the app's
91
+ acronyms. It also cut the graph at 50 models without saying so.
92
+ - **`service_pattern` named a service after a word in a comment.** The regex ran
93
+ over raw source, never matched `module`, and its basename fallback dropped
94
+ every namespace. The lookup matched by basename before exact path, so only
95
+ the alphabetically first `create.rb` could be looked up, and "Called By" was a
96
+ substring search that listed a non-caller and dropped the real one.
97
+ - **Static route helper names did not exist.** Hyphens were kept
98
+ (`api_v1_gift-cards_redeem_path` is a subtraction in Ruby), a dotted path was
99
+ used in the name where Rails uses none, and a name that cannot be one was
100
+ kept anyway. A booted redirect route appeared in no row and no count.
101
+ - **`get_context` for one action listed every strong-params method in the
102
+ controller**, so `create_params`' permit list read as the fields `deactivate`
103
+ accepts.
104
+ - **Controller Schema Hints reported services, serializers and plain constants
105
+ as missing models.** Only names that resolve to a model are kept now, and the
106
+ rest are dropped without a line.
107
+ - **`env` read only `.rb` files**, so ENV names in `config/*.yml`, ERB views and
108
+ rake tasks were missing with nothing saying a file type was skipped. Category
109
+ matching was unanchored (`PORT` inside `PORTAL`), and a `nil` default printed
110
+ as `[FILTERED]`.
111
+ - **`env_config` reported the first assignment in a file**, so it printed the
112
+ branch that is not running and disagreed with `config` on the same app.
113
+ - **`gems` reported a Minitest suite in a `test/` directory that is not there**,
114
+ from a lockfile entry every Rails app resolves through activesupport, and
115
+ pointed at a `config/sidekiq.yml` without checking that it exists.
116
+ - **`generate_test` wrote specs that cannot run.** Shoulda matchers were emitted
117
+ into apps that do not bundle the gem, a namespaced controller produced
118
+ `create(:api/v1/admin/order)`, the example sent GET whatever the route's verb,
119
+ and a controller with no file was answered with "no routes found".
120
+ - **`performance_check` suggested indexes and counters the app cannot use.**
121
+ Every unindexed `*_id` column was a missing foreign key index whatever its
122
+ type, and a counter the app maintains itself was offered a `counter_cache`
123
+ that double-counts every create.
124
+ - **`query` answered an unknown column with "Database not found" and exit 0.**
125
+ Postgres words a missing column, table and database the same way.
126
+ - **`view` counted images and text files as templates**, read ivars out of
127
+ binary data, and read the CSS rule `@page` as an ivar.
128
+ - **`partial_interface` could not resolve a `.text.erb` partial** by the Rails
129
+ name its own "Available" list had just printed, and cut a local's method calls
130
+ at ten with no marker.
131
+ - **`api` merged every CORS allow block and environment branch into one origin
132
+ list**, so the line read as the API allowing `*` on every resource.
133
+ - **`active_support` listed validator classes as "plain module" concerns.**
134
+ - **`validate` flagged an `acceptance:` virtual attribute as a missing column**
135
+ and suggested a migration for a column nobody needs.
136
+ - **`search_code --match-type trace` reported no internal calls** for a body
137
+ whose calls take no parentheses, counted a comment mentioning the method as a
138
+ call site, and tagged `app/services/models/...` as a Model.
139
+ - **Static answers missed an app's inflections.** A route group found no
140
+ controller for `api/v1/ai_matches`, and a service file with no `class` line
141
+ was named by its camelized basename.
142
+ - **The `controllers/{name}` resource refused a short name** the `routes/{name}`
143
+ resource accepts, and the controller answer pointed at a model that does not
144
+ exist.
145
+ - **`ai:watch` rewrote every tool's files** whatever the configuration asked
146
+ for.
147
+
8
148
  ## [5.26.0] - 2026-09-11
9
149
 
10
150
  ### 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
 
@@ -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
 
data/exe/rails-ai-context CHANGED
@@ -191,6 +191,16 @@ class RailsAiContextCLI < Thor
191
191
  end
192
192
  ::RailsAiContext::LegacyCleanup.prompt_legacy_files(cleanup_tools, root: Dir.pwd)
193
193
 
194
+ # An explicit --format names a file, so it is written either way. With
195
+ # no format and an MCP-only install there is nothing to write, and the
196
+ # rake surface says the same thing in the same words.
197
+ if options[:format].nil? && !::RailsAiContext.configuration.context_files
198
+ $stderr.puts "MCP-only install (config.context_files = false): no context files written."
199
+ $stderr.puts "Run `rails-ai-context context --format claude` to write one anyway, " \
200
+ "or set config.context_files = true."
201
+ return
202
+ end
203
+
194
204
  $stderr.puts "Introspecting Rails app..."
195
205
 
196
206
  # An explicit --format overrides the recorded selection.
@@ -264,6 +274,8 @@ class RailsAiContextCLI < Thor
264
274
 
265
275
  desc "init", "Set up rails-ai-context for standalone use (creates .rails-ai-context.yml and .mcp.json)"
266
276
  option :no_boot, type: :boolean, default: false, desc: "Skip booting the app; serve static analysis only"
277
+ option :mcp_only, type: :boolean, default: false,
278
+ desc: "Write the MCP config and nothing else: no CLAUDE.md, AGENTS.md, rules files or .ai-context.json"
267
279
  def init
268
280
  unless entry_boot.app_present?(Dir.pwd, allow_source_only: true)
269
281
  $stderr.puts "Error: No Rails app found in #{Dir.pwd}"
@@ -278,7 +290,9 @@ class RailsAiContextCLI < Thor
278
290
 
279
291
  # --- Prompts (no Rails needed) ---
280
292
  ai_tools = prompt_ai_tools
281
- tool_mode = prompt_tool_mode
293
+ setup = prompt_setup
294
+ tool_mode = setup.tool_mode
295
+ context_files = setup.context_files
282
296
 
283
297
  # --- Cleanup removed tools ---
284
298
  cleanup_removed_tools(previous_tools, ai_tools) if previous_tools&.any?
@@ -287,13 +301,13 @@ class RailsAiContextCLI < Thor
287
301
  ::RailsAiContext::LegacyCleanup.prompt_legacy_files(ai_tools, root: Dir.pwd)
288
302
 
289
303
  # --- Record the selection (YAML, plus the initializer line) ---
290
- record_selection(ai_tools, tool_mode)
304
+ record_selection(ai_tools, tool_mode, context_files)
291
305
 
292
306
  # --- Write per-tool MCP config files (MCP mode only) ---
293
307
  write_mcp_configs(ai_tools, tool_mode)
294
308
 
295
309
  # --- Add .ai-context.json to .gitignore ---
296
- add_to_gitignore
310
+ add_to_gitignore(context_files)
297
311
 
298
312
  # --- Boot Rails, load config, generate context ---
299
313
  # The config files are already on disk by now, so failing here leaves the
@@ -301,12 +315,17 @@ class RailsAiContextCLI < Thor
301
315
  # what you run on a repo you have just cloned.
302
316
  boot_rails!(command: "init", allow_static: true)
303
317
 
304
- $stderr.puts ""
305
- $stderr.puts "Generating AI context files..."
306
- print_context_result(::RailsAiContext.generate_context(format: ai_tools))
318
+ if context_files
319
+ $stderr.puts ""
320
+ $stderr.puts "Generating AI context files..."
321
+ print_context_result(::RailsAiContext.generate_context(format: ai_tools))
322
+ else
323
+ $stderr.puts ""
324
+ $stderr.puts "MCP-only setup: no context files written."
325
+ end
307
326
 
308
327
  # --- Instructions ---
309
- show_standalone_instructions(ai_tools, tool_mode)
328
+ show_standalone_instructions(ai_tools, tool_mode, context_files)
310
329
  end
311
330
 
312
331
  desc "preset NAME", "Run a multi-tool preset (architecture, debugging, migration)"
@@ -367,8 +386,12 @@ class RailsAiContextCLI < Thor
367
386
  ::RailsAiContext::Install::Program.select_ai_tools(program_surface)
368
387
  end
369
388
 
370
- def prompt_tool_mode
371
- ::RailsAiContext::Install::Program.select_tool_mode(program_surface)
389
+ def prompt_setup
390
+ if options[:mcp_only]
391
+ return ::RailsAiContext::Install::Program::Setup.new(:mcp, false)
392
+ end
393
+
394
+ ::RailsAiContext::Install::Program.select_setup(program_surface)
372
395
  end
373
396
 
374
397
  def read_previous_ai_tools
@@ -381,14 +404,17 @@ class RailsAiContextCLI < Thor
381
404
  )
382
405
  end
383
406
 
384
- def add_to_gitignore
385
- ::RailsAiContext::Install::Program.mark_gitignore(program_surface, root: Dir.pwd)
407
+ def add_to_gitignore(context_files = true)
408
+ ::RailsAiContext::Install::Program.mark_gitignore(program_surface, root: Dir.pwd,
409
+ context_files: context_files)
386
410
  end
387
411
 
388
- def record_selection(ai_tools, tool_mode)
412
+ def record_selection(ai_tools, tool_mode, context_files = true)
389
413
  result = ::RailsAiContext::Install::SelectionRecord.write(
390
- ai_tools, root: Dir.pwd, extra_yaml: { "tool_mode" => tool_mode.to_s }
414
+ ai_tools, root: Dir.pwd,
415
+ extra_yaml: { "tool_mode" => tool_mode.to_s, "context_files" => context_files }
391
416
  )
417
+ ::RailsAiContext::Install::SelectionRecord.write_context_files(context_files, root: Dir.pwd)
392
418
 
393
419
  ::RailsAiContext::Install::SelectionRecord.messages(result).each do |level, text|
394
420
  $stderr.puts(level == :warn ? "Warning: #{text}" : text)
@@ -402,7 +428,7 @@ class RailsAiContextCLI < Thor
402
428
  )
403
429
  end
404
430
 
405
- def show_standalone_instructions(ai_tools, tool_mode)
431
+ def show_standalone_instructions(ai_tools, tool_mode, context_files = true)
406
432
  $stderr.puts ""
407
433
  $stderr.puts "=" * 50
408
434
  $stderr.puts " rails-ai-context initialized!"
@@ -411,11 +437,15 @@ class RailsAiContextCLI < Thor
411
437
  $stderr.puts "Your setup:"
412
438
  ::RailsAiContext::Install::AiTool.all.each do |tool|
413
439
  next unless ai_tools.include?(tool.key)
414
- $stderr.puts " #{tool.name.ljust(16)} -> #{tool.files}"
440
+ $stderr.puts " #{tool.name.ljust(16)} -> #{context_files ? tool.files : 'MCP config only'}"
441
+ end
442
+ unless context_files
443
+ $stderr.puts ""
444
+ $stderr.puts " Left alone on purpose: CLAUDE.md, AGENTS.md, the rules directories and .ai-context.json."
415
445
  end
416
446
  $stderr.puts ""
417
447
  $stderr.puts "Commands:"
418
- $stderr.puts " rails-ai-context context # Regenerate context files"
448
+ $stderr.puts " rails-ai-context context # Regenerate context files" if context_files
419
449
  $stderr.puts " rails-ai-context tool NAME # Run any of the #{RailsAiContext::CountPhrase.call(builtin_tool_count, "tool")}"
420
450
  if tool_mode == :mcp
421
451
  $stderr.puts " rails-ai-context serve # Start MCP server"
@@ -38,6 +38,9 @@ module RailsAiContext
38
38
  class_option :defaults, type: :boolean, default: false,
39
39
  desc: "Skip all interactive prompts and use each prompt's documented default (for CI/non-interactive use)"
40
40
 
41
+ class_option :mcp_only, type: :boolean, default: false,
42
+ desc: "Write the MCP config and nothing else: no CLAUDE.md, AGENTS.md, rules files or .ai-context.json"
43
+
41
44
  BARE_GUARD_PATTERN = RailsAiContext::Install::InitializerFile::BARE_GUARD
42
45
 
43
46
  def select_ai_tools
@@ -54,8 +57,16 @@ module RailsAiContext
54
57
  )
55
58
  end
56
59
 
57
- def select_tool_mode
58
- @tool_mode = RailsAiContext::Install::Program.select_tool_mode(program_surface)
60
+ def select_setup
61
+ if options[:mcp_only]
62
+ @tool_mode = :mcp
63
+ @context_files = false
64
+ return
65
+ end
66
+
67
+ setup = RailsAiContext::Install::Program.select_setup(program_surface)
68
+ @tool_mode = setup.tool_mode
69
+ @context_files = setup.context_files
59
70
  end
60
71
 
61
72
  def create_mcp_config
@@ -79,6 +90,11 @@ module RailsAiContext
79
90
  # :mcp - MCP primary + CLI fallback (default, requires `rails ai:serve`)
80
91
  # :cli - CLI only (no MCP server needed, uses `rails 'ai:tool[NAME]'`)
81
92
  # config.tool_mode = :mcp
93
+
94
+ # Whether this gem writes context files at all. false is MCP-only:
95
+ # the server and the CLI still answer, and CLAUDE.md, AGENTS.md,
96
+ # the rules directories and .ai-context.json are left alone.
97
+ # config.context_files = true
82
98
  SECTION
83
99
  "Introspection" => <<~SECTION,
84
100
  # ── Introspection ─────────────────────────────────────────────────
@@ -261,11 +277,7 @@ module RailsAiContext
261
277
  # Always write uncommented so re-install can detect previous selection
262
278
  tools_line = RailsAiContext::Install::SelectionRecord.initializer_line(@selected_formats)
263
279
 
264
- tool_mode_line = if @tool_mode == :cli
265
- " config.tool_mode = :cli # CLI only (no MCP server needed)"
266
- else
267
- " config.tool_mode = :mcp # MCP primary + CLI fallback"
268
- end
280
+ tool_mode_line = build_tool_mode_line
269
281
 
270
282
  content = "# frozen_string_literal: true\n\nRailsAiContext.configure do |config|\n"
271
283
 
@@ -280,6 +292,7 @@ module RailsAiContext
280
292
  # :mcp - MCP primary + CLI fallback (default, requires `rails ai:serve`)
281
293
  # :cli - CLI only (no MCP server needed, uses `rails 'ai:tool[NAME]'`)
282
294
  #{tool_mode_line}
295
+ #{build_context_files_line}
283
296
 
284
297
  SECTION
285
298
 
@@ -312,7 +325,11 @@ module RailsAiContext
312
325
  existing, changed = update_config_line(existing, "config.tool_mode", build_tool_mode_line)
313
326
  changes << "tool_mode" if changed
314
327
 
315
- # 3. Add any missing config sections
328
+ # 3. Record whether this install writes context files at all
329
+ existing, changed = update_config_line(existing, "config.context_files", build_context_files_line)
330
+ changes << "context_files" if changed
331
+
332
+ # 4. Add any missing config sections
316
333
  CONFIG_SECTIONS.each do |name, section_content|
317
334
  marker = "── #{name}"
318
335
  next if existing.include?(marker)
@@ -423,6 +440,20 @@ module RailsAiContext
423
440
  end
424
441
  end
425
442
 
443
+ # Written uncommented either way, so a re-run reads the same answer the
444
+ # last one recorded.
445
+ def build_context_files_line
446
+ if context_files?
447
+ " config.context_files = true # write CLAUDE.md, AGENTS.md and rules files"
448
+ else
449
+ " config.context_files = false # MCP only: no context files are written"
450
+ end
451
+ end
452
+
453
+ def context_files?
454
+ @context_files.nil? ? true : @context_files
455
+ end
456
+
426
457
  def read_previous_ai_tools
427
458
  RailsAiContext::Install::SelectionRecord.read(root: Rails.root)
428
459
  end
@@ -435,7 +466,7 @@ module RailsAiContext
435
466
  # comment behind. One writer per file, and it is not this call.
436
467
  result = RailsAiContext::Install::SelectionRecord.write(
437
468
  @selected_formats, root: Rails.root,
438
- extra_yaml: { "tool_mode" => @tool_mode.to_s },
469
+ extra_yaml: { "tool_mode" => @tool_mode.to_s, "context_files" => context_files? },
439
470
  initializer: false
440
471
  )
441
472
 
@@ -445,7 +476,8 @@ module RailsAiContext
445
476
  end
446
477
 
447
478
  def add_to_gitignore
448
- RailsAiContext::Install::Program.mark_gitignore(program_surface, root: Rails.root)
479
+ RailsAiContext::Install::Program.mark_gitignore(program_surface, root: Rails.root,
480
+ context_files: context_files?)
449
481
  end
450
482
 
451
483
  def install_validation_hook
@@ -505,6 +537,12 @@ module RailsAiContext
505
537
  end
506
538
 
507
539
  def generate_context_files
540
+ unless context_files?
541
+ say ""
542
+ say "MCP-only install: no context files written.", :yellow
543
+ return
544
+ end
545
+
508
546
  say ""
509
547
  say "Generating AI context files...", :yellow
510
548
 
@@ -540,7 +578,12 @@ module RailsAiContext
540
578
  say "Your setup:", :yellow
541
579
  RailsAiContext::Install::AiTool.all.each do |tool|
542
580
  next unless @selected_formats.include?(tool.key)
543
- say " ✅ #{tool.name.ljust(16)} -> #{tool.files}"
581
+ files = context_files? ? tool.files : "MCP config only"
582
+ say " ✅ #{tool.name.ljust(16)} -> #{files}"
583
+ end
584
+ unless context_files?
585
+ say ""
586
+ say " Left alone on purpose: CLAUDE.md, AGENTS.md, the rules directories and .ai-context.json.", :yellow
544
587
  end
545
588
  say ""
546
589
  say "Commands:", :yellow