rails-ai-context 5.32.0 → 5.32.2

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -0
  3. data/CONTEXT.md +10 -8
  4. data/CONTRIBUTING.md +2 -2
  5. data/README.md +14 -11
  6. data/SECURITY.md +4 -4
  7. data/docs/ARCHITECTURE.md +11 -10
  8. data/docs/CLI.md +33 -7
  9. data/docs/COMPATIBILITY.md +69 -32
  10. data/docs/CONFIGURATION.md +10 -8
  11. data/docs/CUSTOM_TOOLS.md +8 -8
  12. data/docs/FAQ.md +6 -5
  13. data/docs/GUIDE.md +154 -80
  14. data/docs/INTROSPECTORS.md +25 -17
  15. data/docs/QUICKSTART.md +14 -3
  16. data/docs/RECIPES.md +50 -40
  17. data/docs/SECURITY.md +23 -13
  18. data/docs/SETUP.md +23 -26
  19. data/docs/STANDALONE.md +7 -3
  20. data/docs/TOOLS.md +79 -45
  21. data/docs/TROUBLESHOOTING.md +12 -11
  22. data/exe/rails-ai-context +8 -4
  23. data/lib/generators/rails_ai_context/install/install_generator.rb +1 -1
  24. data/lib/rails_ai_context/configuration.rb +2 -2
  25. data/lib/rails_ai_context/database_yml.rb +52 -2
  26. data/lib/rails_ai_context/install/ai_tool.rb +11 -2
  27. data/lib/rails_ai_context/install/cleanup.rb +69 -23
  28. data/lib/rails_ai_context/install/program.rb +4 -1
  29. data/lib/rails_ai_context/install/selection_record.rb +20 -2
  30. data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +30 -9
  31. data/lib/rails_ai_context/introspectors/model_introspector.rb +1 -1
  32. data/lib/rails_ai_context/introspectors/pg_naming.rb +133 -0
  33. data/lib/rails_ai_context/introspectors/schema_conventions.rb +0 -6
  34. data/lib/rails_ai_context/introspectors/schema_introspector.rb +188 -51
  35. data/lib/rails_ai_context/introspectors/schema_reader.rb +98 -12
  36. data/lib/rails_ai_context/introspectors/structure_sql_reader.rb +56 -33
  37. data/lib/rails_ai_context/payload.rb +25 -2
  38. data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +2 -2
  39. data/lib/rails_ai_context/server.rb +2 -2
  40. data/lib/rails_ai_context/tasks/rails_ai_context.rake +12 -4
  41. data/lib/rails_ai_context/test_helper.rb +2 -0
  42. data/lib/rails_ai_context/tools/base_tool.rb +3 -1
  43. data/lib/rails_ai_context/tools/dependency_graph.rb +2 -2
  44. data/lib/rails_ai_context/tools/get_concern.rb +1 -1
  45. data/lib/rails_ai_context/tools/get_env.rb +1 -1
  46. data/lib/rails_ai_context/tools/get_gems.rb +2 -2
  47. data/lib/rails_ai_context/tools/get_model_details.rb +2 -0
  48. data/lib/rails_ai_context/tools/get_schema.rb +17 -14
  49. data/lib/rails_ai_context/tools/query.rb +107 -39
  50. data/lib/rails_ai_context/tools/search_code.rb +20 -16
  51. data/lib/rails_ai_context/version.rb +1 -1
  52. data/server.json +1 -1
  53. metadata +2 -1
@@ -105,8 +105,8 @@ from the booted path:
105
105
  | `jobs` | `app/jobs`, `app/workers`, `app/sidekiq`, `app/mailers` and `app/channels` parsed for classes and their public methods, plus any class elsewhere under `app/` whose parent chain reaches `ActionMailer::Base` |
106
106
  | `i18n` | `config.i18n.available_locales` read from `config/`, or every top-level key across `config/locales` when the app never assigns it; the default locale read from `config/`. The backend and the fallbacks stay in the answer and are declared unanswered, being facts about the running process |
107
107
  | `api` | every detection but the mode is a file read and runs unchanged; `config.api_only` comes from the assignment in `config/application.rb` |
108
- | `engines` | `config/routes.rb` mounts, plus the Gemfile |
109
- | `active_support` | concern and core-extension use read from source |
108
+ | `engines` | the mounts in `config/routes.rb` and the files it draws, plus the in-repo engines found in the tree; the loaded `Rails::Engine` classes are declared unavailable |
109
+ | `active_support` | concerns, message verifier and encryptor use, notification subscriptions and a `TaggedLogging` initializer read from source; deprecators, load hooks and the cache store are declared unavailable |
110
110
  | `middleware` | `app/middleware`, `lib/middleware`, and the stack changes in `config/initializers`, `config/environments` and `config/application.rb`; the booted stack and its count are declared unavailable |
111
111
 
112
112
  **runtime-only** (8) report `{ unavailable: reason }` here, and only these:
@@ -174,14 +174,14 @@ a consumer must not join it to the app root. The gem's own readers ask
174
174
  ## Shape matrix
175
175
 
176
176
  Rows are app shapes; columns are schema, models, routes, and controllers
177
- (the four whose static behavior varies by shape), plus views for contrast.
177
+ (the four whose static behavior varies by shape), plus views.
178
178
  `n/a` means the shape doesn't change that introspector's behavior - see
179
179
  the stock full-stack row for its baseline proof. Reference numbers point at
180
180
  the proof list below the table.
181
181
 
182
182
  | Shape | Schema | Models | Routes | Controllers | Views |
183
183
  |:---|:---|:---|:---|:---|:---|
184
- | Stock full-stack (`schema.rb`) | runtime + static [1] | runtime + static [1] | runtime + static [1] | runtime + static [1] | runtime only [2] |
184
+ | Stock full-stack (`schema.rb`) | runtime + static [1] | runtime + static [1] | runtime + static [1] | runtime + static [1] | runtime [2] + static [8] |
185
185
  | API-only | runtime + static [1] | runtime + static [1] | runtime + static [1] | runtime + static [1] | runtime, "not applicable" [3] |
186
186
  | `structure.sql` - PostgreSQL dialect | static [4] | n/a | n/a | n/a | n/a |
187
187
  | `structure.sql` - MySQL/Trilogy dialect | static [4] | n/a | n/a | n/a | n/a |
@@ -191,7 +191,7 @@ the proof list below the table.
191
191
  | In-repo `engines/` | n/a | n/a | n/a | static [5] | n/a |
192
192
  | In-repo `plugins/`, `modules/`, `gems/plugins/` | n/a | static [5] | n/a | static [5] | n/a |
193
193
  | Mongoid | `[UNAVAILABLE]`, honest signal [6] | static (fields + embeds) [6] | n/a | n/a | n/a |
194
- | Broken-boot (any full-stack app) | static [7] | static, per-file isolation [7] | static [7] | static [7] | `[UNAVAILABLE]` [8] |
194
+ | Broken-boot (any full-stack app) | static [7] | static, per-file isolation [7] | static [7] | static [7] | static [8] |
195
195
  | Packs + engines under `--no-boot` | n/a | static [5] | n/a | static [5] | n/a |
196
196
  | Empty/greenfield app (no scaffold) | graceful, no crash [9] | graceful, no crash [9] | graceful, no crash [9] | graceful, no crash [9] | graceful, no crash [9] |
197
197
  | Massive app (500 models/tables) | no crash at scale [10] | no crash at scale [10] | no crash at scale [10] | n/a | n/a |
@@ -234,6 +234,17 @@ Proof sources:
234
234
  Sinatra and a plain gem); the lab regressions on SQLite in all three tiers,
235
235
  PostgreSQL and MySQL; and the unit suite on Ruby 3.1 with Rails 7.0 through
236
236
  Ruby 4.0 with Rails 8.1.
237
+ In the v5.32.1 release QA: a PostgreSQL 17 lab on Rails 8.1 and 8.0 with a
238
+ two-schema search path, answered booted and static from schema.rb and
239
+ structure.sql side by side; the small apps and the same private app
240
+ answering as v5.32.0 did; and the unit suite on Ruby 3.1 with Rails 7.0
241
+ through Ruby 4.0 with Rails 8.1.
242
+ In the v5.32.2 release QA: `rails_query` on the same private app's
243
+ PostgreSQL database, with comment markers inside string literals, E strings
244
+ and dollar quotes, a `#>>` that starts a line, and every spelling of a row
245
+ limit; the installer's cleanup on a small Rails 8.1 app holding hand-written
246
+ rule files; and the unit suite on Ruby 3.1 with Rails 7.0 through Ruby 4.0
247
+ with Rails 8.1.
237
248
  2. Non-crash coverage for every built-in tool including `get_view` in
238
249
  `spec/e2e/in_gemfile_install_spec.rb`'s full-tool sweep; output correctness
239
250
  (ivar cross-check, render-form detection, partial interfaces) verified
@@ -253,14 +264,16 @@ Proof sources:
253
264
  tier)" and "multi-database schema dumps".
254
265
  6. `spec/e2e/shapes_spec.rb`: "Mongoid app (bare directory, --app-path, no
255
266
  boot possible)".
256
- 7. `spec/e2e/boot_resilience_spec.rb` (four boot-failure modes: raises,
257
- prints to stdout, writes via the `STDOUT` constant, hangs past the
258
- timeout) plus `spec/e2e/static_tier_spec.rb` ("broken-boot app over the
267
+ 7. `spec/e2e/boot_resilience_spec.rb` (five boot-failure modes: raises,
268
+ prints to stdout, writes via the `STDOUT` constant, calls `exit`/`abort`,
269
+ hangs past the timeout) plus `spec/e2e/static_tier_spec.rb` ("broken-boot app over the
259
270
  CLI", "broken-boot app over MCP stdio", "syntax error in one model file").
260
- 8. No introspector outside the six in the operating-tiers table defines
261
- `static_call` (`lib/rails_ai_context/introspectors/view_introspector.rb`
262
- has none); `Introspector#run_introspector` reports `{ unavailable: reason
263
- }` for every such section regardless of shape.
271
+ 8. `views` is files-only
272
+ (`lib/rails_ai_context/introspectors/view_introspector.rb`), so its `call`
273
+ runs unchanged with nothing booted, proven against `spec/fixtures/static_app`
274
+ in `spec/lib/rails_ai_context/introspectors/static_tier_declarations_spec.rb`.
275
+ `Introspector#run_introspector` reports `{ unavailable: reason }` only for
276
+ the eight runtime-only sections, regardless of shape.
264
277
  9. `spec/e2e/empty_app_spec.rb` - all 45 built-in tools swept against an app
265
278
  with no scaffold, no models, no controllers beyond
266
279
  `ApplicationController`, no routes beyond root.
@@ -283,14 +296,16 @@ Postgres instance in `spec/e2e/postgres_install_spec.rb`, opt-in via
283
296
  `ApplicationRecord`, pass their concerns, scopes, callbacks and macros down
284
297
  the way Rails does. Only the table stops at an abstract base, because a child
285
298
  of one has its own. A concern's macro is tagged with the concern that
286
- declared it. Both tiers stop at the same place: a superclass whose file is not
299
+ declared it. Both tiers leave out a superclass whose file is not
287
300
  under one of the app's model directories, a gem-owned base for example. The
288
301
  booted tier could read that file and deliberately does not, because an answer
289
- the static tier can never match is two answers to one question. Reflection
302
+ the static tier can never match is two answers to one question. The static
303
+ tier ends the walk at such a base; the booted tier steps over it and still
304
+ reads an app base above it. Reflection
290
305
  still carries such a base's associations, validations and enums onto the
291
- child. The validations Rails generates at boot
292
- (implicit `belongs_to` presence, attachment validations) are runtime-only and
293
- stay marked `[UNAVAILABLE]`. A concern whose file cannot be found, a gem's
306
+ child. The presence validation Rails adds for a required
307
+ `belongs_to` is listed in both tiers as implicit; without a boot it is worked
308
+ out from `optional:`/`required:` and `belongs_to_required_by_default`. A concern whose file cannot be found, a gem's
294
309
  module for example, is named under `Concerns` as not read.
295
310
  - **Callbacks are what the file declares.** In both tiers the callback list is
296
311
  what the model file, the app concerns it includes and the classes it inherits
@@ -312,36 +327,58 @@ Postgres instance in `spec/e2e/postgres_install_spec.rb`, opt-in via
312
327
  are invisible to ActiveRecord reflection, so `ModelIntrospector#call` falls
313
328
  back to the same source-parsing pass used in the static tier even when the
314
329
  app is booted (`lib/rails_ai_context/introspectors/model_introspector.rb`).
315
- Fields or associations defined in an included concern module rather than
316
- directly in the document class body are not resolved, in either tier.
317
- - **Static-tier API-only detection.** `Tools::BaseTool#api_only_app?` reads
318
- `app.config.api_only`. `RailsAiContext::StaticApp` exposes no `config`
319
- method, so this check is always false in the static tier - the "not
320
- applicable" messaging for view/frontend tools on API-only apps only fires
321
- once the app has actually booted.
330
+ A concern's fields, embedded relations, `store_in` and indexes are read. A
331
+ `belongs_to`/`has_many`, validation, scope or callback declared in a concern
332
+ rather than in the document class body is not resolved, in either tier.
333
+ - **Static-tier API-only detection.** `RailsAiContext::StaticApp` exposes no
334
+ `config` method, so in the static tier `Tools::BaseTool.api_only_app?` reads
335
+ the `config.api_only = true` assignment in `config/application.rb`
336
+ (`AppKind.api_only?`). A value set anywhere else, or one that is not a
337
+ literal `true`, is missed, and the "not applicable" messaging for
338
+ view/frontend tools then only fires once the app has booted.
322
339
  - **Live multi-DB connections are not iterated, only dumps.**
323
340
  `MultiDatabaseIntrospector` reads `config/database.yml` and the model source
324
341
  in the static tier, so databases, replicas, sharding and `connects_to`
325
342
  declarations come from the files rather than from the live connection
326
343
  handlers. A database only a runtime `connects_to` names is missed.
344
+ - **Rails 7.0 merges same-name enum types.** On Rails 7.0, two PostgreSQL enum
345
+ types with one name in different schemas come back from the connection as
346
+ one type holding both label lists; the static tier keeps the first type's
347
+ labels.
348
+ - **A schema.rb from before Rails 8.1 does not say which schema holds a
349
+ table.** It names each table bare, whichever search path schema holds it,
350
+ so without a boot a schema-qualified name such as `app.users` finds a listed
351
+ table only when the search path has one schema. A Rails 7.0 schema.rb names
352
+ no schema at all, so the static tier takes every schema the search path
353
+ names as existing, except the one `"$user"` stands for.
354
+ - **A dump made with a `schema_search_path` holds only those schemas.** By
355
+ default Rails passes each search path schema to pg_dump, so structure.sql
356
+ holds no other schema's tables and no extension, and a Rails 8.1 schema.rb
357
+ holds no other schema's tables. Without a boot those tables are not found and
358
+ the extension list is plpgsql alone. `config.active_record.dump_schemas =
359
+ :all` dumps everything.
327
360
  - **Inherited controller actions and filters are resolved by parent name, so
328
- some walks end early.** A controller that defines no action of its own takes
329
- the actions of the nearest app ancestor the listing holds, walked through the
361
+ some walks end early.** Every controller also takes the actions of each app
362
+ ancestor the listing holds, up to the app's base controller, walked through the
330
363
  `parent_class` each entry carries, and the filter chain is walked the same
331
364
  way. A superclass spelled relatively inside a `module` body (`module
332
365
  Settings; class ProfileController < BaseController`) is resolved against the
333
- enclosing namespace first, the way Ruby resolves it. What still ends the walk
334
- is a gem-owned parent such as `OAuth::AuthorizationsController <
335
- Doorkeeper::AuthorizationsController`, which the payload cannot hold. A
336
- booted run answers that one.
366
+ enclosing namespace first, the way Ruby resolves it. A gem-owned parent such as
367
+ `OAuth::AuthorizationsController < Doorkeeper::AuthorizationsController`,
368
+ which the payload cannot hold, still ends the inherited-actions walk. The
369
+ filter walk skips that class's own filters: for a Devise or Doorkeeper parent
370
+ it resumes at the base class the gem's initializer names, or the gem's
371
+ default, and for any other gem-owned parent it ends. A booted run answers
372
+ those.
337
373
  - **`ApplicationController` is not in the listing, but its filters are in the
338
374
  chain.** Every app has one and it would sit in every listing row, so the
339
375
  controller listing leaves it out. The chain walk reads its file anyway, by
340
376
  the one name Rails fixes, so a filter it declares is attributed to it in both
341
377
  tiers with its `only:`/`except:`/`if:` intact. A parent the listing does not
342
378
  hold and the app has no file for still ends the walk: reconstructing a path
343
- from a class name breaks on an app inflection, and a gem-owned parent such as
344
- `Doorkeeper::AuthorizationsController` has no file under the app root at all.
379
+ from a class name breaks on an app inflection, and a gem-owned parent has no file under the app
380
+ root at all (a Devise or Doorkeeper one resumes at its configured base, as
381
+ above).
345
382
  A booted run answers that one from reflection.
346
383
  - **Some route macros surface as a dynamic tally, not resolved entries.**
347
384
  `RouteIntrospector#static_call` counts routes behind `devise_for`, a `match`
@@ -16,7 +16,7 @@
16
16
 
17
17
  ```ruby
18
18
  # config/initializers/rails_ai_context.rb
19
- if defined?(RailsAiContext)
19
+ if defined?(RailsAiContext) && RailsAiContext.respond_to?(:configure)
20
20
  RailsAiContext.configure do |config|
21
21
  config.ai_tools = %i[claude cursor copilot opencode codex]
22
22
  config.tool_mode = :mcp
@@ -49,7 +49,7 @@ preset: full
49
49
 
50
50
  | Option | Type | Default | Description |
51
51
  |:-------|:-----|:--------|:------------|
52
- | `ai_tools` | Array of symbols | `[:claude]` | Which AI tools to generate context for. Options: `:claude`, `:cursor`, `:copilot`, `:opencode`, `:codex` |
52
+ | `ai_tools` | Array of symbols | `nil` (all five) | Which AI tools to generate context for. Options: `:claude`, `:cursor`, `:copilot`, `:opencode`, `:codex`. Unset, it reads the selection the installer recorded, and all five when there is none |
53
53
  | `tool_mode` | Symbol | `:mcp` | `:mcp` (MCP server primary, CLI fallback) or `:cli` (CLI only, no MCP server) |
54
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
55
 
@@ -76,8 +76,10 @@ nothing, and `rails ai:doctor` raises no context-file warning. A command that
76
76
  names a file still writes it: `rails ai:context:claude` and
77
77
  `rails-ai-context context --format claude`.
78
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.
79
+ `config.ai_tools = []` means no AI tool at all: no context files and no MCP
80
+ config file are written, and `rails ai:context` does not ask which tools you
81
+ use. The installer has no such choice, so set it by hand. Before v5.27.0 an
82
+ empty list wrote every tool's files.
81
83
 
82
84
  ### Introspection
83
85
 
@@ -88,7 +90,7 @@ config file is written. Before v5.27.0 an empty list wrote every tool's files.
88
90
  | `introspectors` | Array of symbols | (from preset) | Override the introspector list directly |
89
91
  | `generate_root_files` | Boolean | `true` | Set `false` to generate split rules only: no CLAUDE.md, AGENTS.md, .cursorrules or copilot-instructions.md (`.ai-context.json` is still written) |
90
92
  | `anti_hallucination_rules` | Boolean | `true` | Embed 6-rule verification protocol in generated context files |
91
- | `claude_max_lines` | Integer | `150` | Max non-blank lines in a compact context file's gem-managed block, the `<!-- BEGIN/END rails-ai-context -->` markers included. Over budget, data lines are cut and the Commands, Warnings, Rules and MCP-tools sections kept whole |
93
+ | `claude_max_lines` | Integer | `150` | Max non-blank lines in a compact context file's gem-managed block, the `<!-- BEGIN/END rails-ai-context -->` markers included. Over budget, data lines are cut and the Commands, Warnings, Rules and MCP-tools sections kept whole. Must be positive |
92
94
 
93
95
  ### MCP Server
94
96
 
@@ -197,8 +199,8 @@ fits neither that shape nor the conventional one.
197
199
  |:-------|:-----|:--------|:-----------|:------------|
198
200
  | `query_timeout` | Integer | `5` | - | SQL query timeout in seconds |
199
201
  | `query_row_limit` | Integer | `100` | 1 to 1000 | Maximum rows returned |
200
- | `query_redacted_columns` | Array | 14 patterns | - | Column names that cause a query to be rejected |
201
- | `query_allowed_columns` | Array | `[]` | - | Column names exempted from the built-in sensitive list |
202
+ | `query_redacted_columns` | Array | 14 patterns | - | Column names that cause a query to be rejected, on top of a fixed built-in list (see [SECURITY.md](SECURITY.md)) |
203
+ | `query_allowed_columns` | Array | `[]` | - | Column names exempted from the built-in sensitive list and from `query_redacted_columns` |
202
204
  | `allow_query_in_production` | Boolean | `false` | - | Allow `rails_query` tool in production |
203
205
 
204
206
  ### Logs
@@ -252,7 +254,7 @@ Lightweight subset for faster generation:
252
254
  | Claude Code | `CLAUDE.md` | `.claude/rules/*.md` | `.mcp.json` |
253
255
  | Cursor | `.cursorrules` (legacy fallback) | `.cursor/rules/*.mdc` | `.cursor/mcp.json` |
254
256
  | GitHub Copilot | `.github/copilot-instructions.md` | `.github/instructions/*.instructions.md` | `.vscode/mcp.json` |
255
- | OpenCode | `AGENTS.md` | `app/*/AGENTS.md` | `opencode.json` |
257
+ | OpenCode | `AGENTS.md` | `app/models/AGENTS.md`, `app/controllers/AGENTS.md` | `opencode.json` |
256
258
  | Codex CLI | (shares `AGENTS.md`) | (shares OpenCode rules) | `.codex/config.toml` |
257
259
 
258
260
  ---
data/docs/CUSTOM_TOOLS.md CHANGED
@@ -19,6 +19,8 @@ Custom tools are subclasses of `MCP::Tool` that you register in your configurati
19
19
 
20
20
  ```ruby
21
21
  # app/mcp_tools/rails_get_business_metrics.rb
22
+ require "mcp" # the gem loads the MCP SDK lazily, so a file read first needs it
23
+
22
24
  class RailsGetBusinessMetrics < MCP::Tool
23
25
  tool_name "rails_get_business_metrics"
24
26
  description "Returns key business metrics for the current environment"
@@ -33,7 +35,7 @@ class RailsGetBusinessMetrics < MCP::Tool
33
35
  }
34
36
  )
35
37
 
36
- def call(period: "week")
38
+ def self.call(period: "week", server_context: nil)
37
39
  # Your logic here - full access to Rails models, services, etc.
38
40
  stats = {
39
41
  users: User.where("created_at > ?", period_start(period)).count,
@@ -46,9 +48,7 @@ class RailsGetBusinessMetrics < MCP::Tool
46
48
  ])
47
49
  end
48
50
 
49
- private
50
-
51
- def period_start(period)
51
+ def self.period_start(period)
52
52
  case period
53
53
  when "day" then 1.day.ago
54
54
  when "week" then 1.week.ago
@@ -56,7 +56,7 @@ class RailsGetBusinessMetrics < MCP::Tool
56
56
  end
57
57
  end
58
58
 
59
- def format_stats(stats)
59
+ def self.format_stats(stats)
60
60
  <<~TEXT
61
61
  ## Business Metrics
62
62
  - New users: #{stats[:users]}
@@ -73,7 +73,7 @@ Add your tool classes to the configuration:
73
73
 
74
74
  ```ruby
75
75
  # config/initializers/rails_ai_context.rb
76
- if defined?(RailsAiContext)
76
+ if defined?(RailsAiContext) && RailsAiContext.respond_to?(:configure)
77
77
  RailsAiContext.configure do |config|
78
78
  config.custom_tools = ["RailsGetBusinessMetrics"]
79
79
  end
@@ -98,7 +98,7 @@ class RailsGetDeployStatus < MCP::Tool
98
98
  tool_name "rails_get_deploy_status"
99
99
  description "Returns current deployment status"
100
100
 
101
- def call
101
+ def self.call(server_context: nil)
102
102
  # Use SafeFile for safe reading
103
103
  version = RailsAiContext::SafeFile.read(Rails.root.join("REVISION"))
104
104
  deployed_at = File.mtime(Rails.root.join("tmp/restart.txt")) rescue nil
@@ -215,7 +215,7 @@ end
215
215
  - Custom tools have access to the full Rails environment - ActiveRecord, services, mailers, etc.
216
216
  - Keep tools read-only when possible. MCP tools annotated as non-destructive build more trust with AI clients.
217
217
  - Return `MCP::Tool::Response` objects with `type: "text"` content blocks.
218
- - Tool responses are automatically truncated at `config.max_tool_response_chars` (default: 200,000).
218
+ - A response you build with `MCP::Tool::Response.new` is sent as written. The `config.max_tool_response_chars` cap (default: 200,000) is applied by the built-in tools' `text_response`, so keep a custom tool's output short yourself.
219
219
 
220
220
  ---
221
221
 
data/docs/FAQ.md CHANGED
@@ -52,12 +52,13 @@ Yes, freely. Both generate identical context files and provide the same 45 tools
52
52
  ### Do I need to commit the generated files?
53
53
 
54
54
  **Yes, commit these:**
55
- - `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `opencode.json`, `.codex/config.toml` - so teammates get MCP auto-discovery
56
- - `CLAUDE.md`, `.cursor/rules/`, `.cursorrules`, `.github/instructions/`, `AGENTS.md` - so AI has context
55
+ - `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `opencode.json` - so teammates get MCP auto-discovery
56
+ - `CLAUDE.md`, `.claude/rules/`, `.cursor/rules/`, `.cursorrules`, `.github/copilot-instructions.md`, `.github/instructions/`, `AGENTS.md` - so AI has context
57
57
  - `config/initializers/rails_ai_context.rb`, `.rails-ai-context.yml` - so config is shared
58
58
 
59
59
  **Don't commit:**
60
60
  - `.ai-context.json` - auto-added to .gitignore by the install generator
61
+ - `.codex/config.toml` - it embeds this machine's Ruby PATH and GEM_HOME, so install adds it to .gitignore too and `rails ai:doctor` fails when it is not ignored
61
62
 
62
63
  ### Can I use only the MCP server?
63
64
 
@@ -141,7 +142,7 @@ Depends on your `ai_tools` config. For all tools:
141
142
  | Claude | CLAUDE.md, .claude/rules/*.md |
142
143
  | Cursor | .cursor/rules/*.mdc AND .cursorrules (legacy fallback for chat agent) |
143
144
  | Copilot | .github/copilot-instructions.md, .github/instructions/*.instructions.md |
144
- | OpenCode | AGENTS.md, app/*/AGENTS.md |
145
+ | OpenCode | AGENTS.md, app/models/AGENTS.md, app/controllers/AGENTS.md |
145
146
  | Codex | Shares AGENTS.md and OpenCode rules |
146
147
 
147
148
  ### How do I regenerate context files?
@@ -175,7 +176,7 @@ Introspection results are cached with TTL (default: 60s) and fingerprint invalid
175
176
 
176
177
  ### Does this affect my app's performance?
177
178
 
178
- No. The gem only runs in development. Tools execute on demand (not continuously). The MCP server is a separate process (stdio) or a development-only endpoint (HTTP).
179
+ No, as long as the gem sits in the Gemfile's `:development` group, where `bundle add rails-ai-context --group development` puts it: Bundler then never loads it in production. The gem itself does not check the environment, apart from `rails_query` refusing to run in production by default. Tools execute on demand (not continuously). The MCP server is a separate process (stdio), or an HTTP endpoint that exists only when you mount it or set `auto_mount`.
179
180
 
180
181
  ### How does live reload work?
181
182
 
@@ -187,7 +188,7 @@ The `listen` gem watches `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`,
187
188
 
188
189
  ### Can tools modify my database?
189
190
 
190
- No. The query tool uses `SET TRANSACTION READ ONLY` + rollback. Even if SQL validation were bypassed, the database layer prevents writes.
191
+ No. The query tool uses `SET TRANSACTION READ ONLY` + rollback on PostgreSQL and MySQL, and a read-only connection on SQLite. Even if SQL validation were bypassed, the database layer prevents writes.
191
192
 
192
193
  ### Can tools read my .env or credentials?
193
194