rails-ai-context 5.32.2 → 5.33.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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1287 -0
  3. data/CONTEXT.md +26 -2
  4. data/README.md +13 -9
  5. data/Rakefile +6 -0
  6. data/app/controllers/rails_ai_context/mcp_controller.rb +114 -84
  7. data/docs/ARCHITECTURE.md +6 -4
  8. data/docs/CLI.md +79 -6
  9. data/docs/COMPATIBILITY.md +65 -1
  10. data/docs/CONFIGURATION.md +30 -11
  11. data/docs/CUSTOM_TOOLS.md +2 -1
  12. data/docs/FAQ.md +16 -6
  13. data/docs/GUIDE.md +117 -71
  14. data/docs/INTROSPECTORS.md +8 -7
  15. data/docs/QUICKSTART.md +3 -3
  16. data/docs/RECIPES.md +1 -1
  17. data/docs/SECURITY.md +109 -25
  18. data/docs/SETUP.md +30 -6
  19. data/docs/STANDALONE.md +10 -3
  20. data/docs/TOOLS.md +145 -34
  21. data/docs/TROUBLESHOOTING.md +90 -12
  22. data/docs/adr/0003-shared-tool-cache-semantics.md +8 -0
  23. data/docs/adr/0005-workspace-mcp-entries.md +39 -0
  24. data/exe/rails-ai-context +733 -104
  25. data/lib/generators/rails_ai_context/install/install_generator.rb +341 -125
  26. data/lib/rails_ai_context/ast_cache.rb +7 -0
  27. data/lib/rails_ai_context/base_mixins.rb +8 -8
  28. data/lib/rails_ai_context/boot_manager.rb +22 -3
  29. data/lib/rails_ai_context/change_watch.rb +14 -11
  30. data/lib/rails_ai_context/cli/app_root.rb +401 -0
  31. data/lib/rails_ai_context/cli/entry_boot.rb +53 -20
  32. data/lib/rails_ai_context/cli/tool_runner.rb +47 -21
  33. data/lib/rails_ai_context/code_reloader.rb +115 -1
  34. data/lib/rails_ai_context/concern_membership.rb +3 -1
  35. data/lib/rails_ai_context/concern_paths.rb +8 -3
  36. data/lib/rails_ai_context/configuration.rb +41 -9
  37. data/lib/rails_ai_context/context_file_report.rb +19 -2
  38. data/lib/rails_ai_context/database_file.rb +26 -0
  39. data/lib/rails_ai_context/doctor.rb +1122 -184
  40. data/lib/rails_ai_context/engine.rb +11 -1
  41. data/lib/rails_ai_context/erb_source.rb +19 -0
  42. data/lib/rails_ai_context/executable.rb +21 -0
  43. data/lib/rails_ai_context/facts_formatter.rb +5 -2
  44. data/lib/rails_ai_context/file_walk.rb +61 -10
  45. data/lib/rails_ai_context/fingerprinter/snapshot.rb +234 -0
  46. data/lib/rails_ai_context/fingerprinter.rb +40 -12
  47. data/lib/rails_ai_context/gem_lock.rb +176 -26
  48. data/lib/rails_ai_context/git_ignore.rb +22 -0
  49. data/lib/rails_ai_context/install/ai_tool.rb +31 -5
  50. data/lib/rails_ai_context/install/cleanup.rb +21 -0
  51. data/lib/rails_ai_context/install/program.rb +209 -35
  52. data/lib/rails_ai_context/install/selection_record.rb +258 -97
  53. data/lib/rails_ai_context/install/surface.rb +6 -1
  54. data/lib/rails_ai_context/install/validation_hook.rb +282 -0
  55. data/lib/rails_ai_context/install/workspace.rb +166 -0
  56. data/lib/rails_ai_context/install_mode.rb +71 -13
  57. data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +1 -1
  58. data/lib/rails_ai_context/introspectors/action_presence.rb +11 -6
  59. data/lib/rails_ai_context/introspectors/action_source.rb +182 -0
  60. data/lib/rails_ai_context/introspectors/action_text_introspector.rb +1 -2
  61. data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -3
  62. data/lib/rails_ai_context/introspectors/active_support_introspector.rb +3 -3
  63. data/lib/rails_ai_context/introspectors/admin_resources.rb +1 -1
  64. data/lib/rails_ai_context/introspectors/anyway_configs.rb +1 -1
  65. data/lib/rails_ai_context/introspectors/api_introspector.rb +3 -3
  66. data/lib/rails_ai_context/introspectors/auth_introspector.rb +10 -3
  67. data/lib/rails_ai_context/introspectors/component_introspector.rb +3 -2
  68. data/lib/rails_ai_context/introspectors/controller_introspector.rb +56 -8
  69. data/lib/rails_ai_context/introspectors/convention_introspector.rb +1 -1
  70. data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +14 -2
  71. data/lib/rails_ai_context/introspectors/devops_introspector.rb +3 -1
  72. data/lib/rails_ai_context/introspectors/eager_load.rb +201 -9
  73. data/lib/rails_ai_context/introspectors/engine_introspector.rb +1 -1
  74. data/lib/rails_ai_context/introspectors/env_config_introspector.rb +1 -1
  75. data/lib/rails_ai_context/introspectors/env_references.rb +10 -1
  76. data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +1 -1
  77. data/lib/rails_ai_context/introspectors/gem_introspector.rb +2 -1
  78. data/lib/rails_ai_context/introspectors/grape_endpoints.rb +1 -1
  79. data/lib/rails_ai_context/introspectors/habtm_join_tables.rb +1 -1
  80. data/lib/rails_ai_context/introspectors/helper_names.rb +1 -1
  81. data/lib/rails_ai_context/introspectors/i18n_introspector.rb +2 -2
  82. data/lib/rails_ai_context/introspectors/job_introspector.rb +8 -6
  83. data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +46 -1
  84. data/lib/rails_ai_context/introspectors/listeners/env_access_listener.rb +10 -1
  85. data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +3 -1
  86. data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +14 -3
  87. data/lib/rails_ai_context/introspectors/listeners/scopes_listener.rb +88 -12
  88. data/lib/rails_ai_context/introspectors/middleware_introspector.rb +2 -2
  89. data/lib/rails_ai_context/introspectors/migration_introspector.rb +27 -0
  90. data/lib/rails_ai_context/introspectors/migration_replay.rb +2 -2
  91. data/lib/rails_ai_context/introspectors/model_introspector.rb +91 -5
  92. data/lib/rails_ai_context/introspectors/n_plus_one_scan.rb +620 -0
  93. data/lib/rails_ai_context/introspectors/performance_introspector.rb +11 -174
  94. data/lib/rails_ai_context/introspectors/route_introspector.rb +10 -1
  95. data/lib/rails_ai_context/introspectors/schema_introspector.rb +57 -4
  96. data/lib/rails_ai_context/introspectors/seeds_introspector.rb +1 -1
  97. data/lib/rails_ai_context/introspectors/source_scan.rb +13 -9
  98. data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +15 -6
  99. data/lib/rails_ai_context/introspectors/turbo_introspector.rb +130 -11
  100. data/lib/rails_ai_context/introspectors/view_introspector.rb +3 -3
  101. data/lib/rails_ai_context/introspectors/view_template_introspector.rb +15 -1
  102. data/lib/rails_ai_context/legacy_cleanup.rb +17 -4
  103. data/lib/rails_ai_context/live_reload.rb +20 -20
  104. data/lib/rails_ai_context/mcp_config_generator.rb +916 -109
  105. data/lib/rails_ai_context/mcp_edge.rb +185 -32
  106. data/lib/rails_ai_context/middleware.rb +5 -1
  107. data/lib/rails_ai_context/migration_status.rb +88 -25
  108. data/lib/rails_ai_context/output_guard.rb +48 -2
  109. data/lib/rails_ai_context/package_json.rb +1 -1
  110. data/lib/rails_ai_context/path_resolver.rb +46 -13
  111. data/lib/rails_ai_context/payload.rb +55 -7
  112. data/lib/rails_ai_context/pending_migrations.rb +5 -1
  113. data/lib/rails_ai_context/portable_path.rb +3 -2
  114. data/lib/rails_ai_context/presets.rb +15 -11
  115. data/lib/rails_ai_context/redaction.rb +254 -51
  116. data/lib/rails_ai_context/resources.rb +55 -20
  117. data/lib/rails_ai_context/route_coverage.rb +8 -0
  118. data/lib/rails_ai_context/safe_file.rb +14 -1
  119. data/lib/rails_ai_context/safe_path.rb +101 -4
  120. data/lib/rails_ai_context/schema_adapter.rb +8 -0
  121. data/lib/rails_ai_context/schema_version.rb +9 -0
  122. data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +86 -32
  123. data/lib/rails_ai_context/serializers/compact_serializer_helper.rb +36 -34
  124. data/lib/rails_ai_context/serializers/context_file_serializer.rb +21 -29
  125. data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +8 -6
  126. data/lib/rails_ai_context/serializers/copilot_serializer.rb +2 -7
  127. data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +17 -15
  128. data/lib/rails_ai_context/serializers/guide_examples.rb +141 -0
  129. data/lib/rails_ai_context/serializers/markdown_serializer.rb +142 -29
  130. data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +36 -14
  131. data/lib/rails_ai_context/serializers/section_facts.rb +35 -1
  132. data/lib/rails_ai_context/serializers/section_marker_writer.rb +94 -12
  133. data/lib/rails_ai_context/serializers/stack_overview_helper.rb +24 -12
  134. data/lib/rails_ai_context/serializers/tool_guide_helper.rb +244 -129
  135. data/lib/rails_ai_context/server.rb +304 -71
  136. data/lib/rails_ai_context/tasks/rails_ai_context.rake +112 -43
  137. data/lib/rails_ai_context/tools/analyze_feature.rb +72 -18
  138. data/lib/rails_ai_context/tools/argument_schema.rb +49 -0
  139. data/lib/rails_ai_context/tools/base_tool.rb +352 -41
  140. data/lib/rails_ai_context/tools/brakeman_guard.rb +24 -0
  141. data/lib/rails_ai_context/tools/dependency_graph.rb +63 -28
  142. data/lib/rails_ai_context/tools/diagnose.rb +218 -11
  143. data/lib/rails_ai_context/tools/generate_test.rb +339 -39
  144. data/lib/rails_ai_context/tools/get_callbacks.rb +24 -13
  145. data/lib/rails_ai_context/tools/get_component_catalog.rb +1 -1
  146. data/lib/rails_ai_context/tools/get_concern.rb +91 -27
  147. data/lib/rails_ai_context/tools/get_config.rb +13 -4
  148. data/lib/rails_ai_context/tools/get_context.rb +71 -14
  149. data/lib/rails_ai_context/tools/get_controllers.rb +157 -40
  150. data/lib/rails_ai_context/tools/get_conventions.rb +26 -4
  151. data/lib/rails_ai_context/tools/get_edit_context.rb +27 -14
  152. data/lib/rails_ai_context/tools/get_env.rb +18 -9
  153. data/lib/rails_ai_context/tools/get_frontend_stack.rb +3 -1
  154. data/lib/rails_ai_context/tools/get_gems.rb +1 -1
  155. data/lib/rails_ai_context/tools/get_helper_methods.rb +9 -5
  156. data/lib/rails_ai_context/tools/get_i18n.rb +1 -1
  157. data/lib/rails_ai_context/tools/get_job_pattern.rb +2 -1
  158. data/lib/rails_ai_context/tools/get_mailers.rb +3 -3
  159. data/lib/rails_ai_context/tools/get_model_details.rb +16 -6
  160. data/lib/rails_ai_context/tools/get_partial_interface.rb +242 -23
  161. data/lib/rails_ai_context/tools/get_routes.rb +19 -6
  162. data/lib/rails_ai_context/tools/get_schema.rb +51 -18
  163. data/lib/rails_ai_context/tools/get_service_pattern.rb +24 -10
  164. data/lib/rails_ai_context/tools/get_stimulus.rb +37 -1
  165. data/lib/rails_ai_context/tools/get_test_info.rb +18 -4
  166. data/lib/rails_ai_context/tools/get_turbo_map.rb +221 -58
  167. data/lib/rails_ai_context/tools/get_view.rb +14 -14
  168. data/lib/rails_ai_context/tools/migration_advisor.rb +355 -48
  169. data/lib/rails_ai_context/tools/onboard.rb +46 -6
  170. data/lib/rails_ai_context/tools/performance_check.rb +4 -2
  171. data/lib/rails_ai_context/tools/query.rb +914 -86
  172. data/lib/rails_ai_context/tools/read_logs.rb +69 -15
  173. data/lib/rails_ai_context/tools/review_changes.rb +204 -43
  174. data/lib/rails_ai_context/tools/runtime_info.rb +51 -1
  175. data/lib/rails_ai_context/tools/safe_call.rb +35 -14
  176. data/lib/rails_ai_context/tools/search_code.rb +243 -67
  177. data/lib/rails_ai_context/tools/search_docs.rb +3 -3
  178. data/lib/rails_ai_context/tools/security_scan.rb +141 -31
  179. data/lib/rails_ai_context/tools/session_context.rb +38 -4
  180. data/lib/rails_ai_context/tools/validate.rb +259 -40
  181. data/lib/rails_ai_context/tools/validate_semantics.rb +129 -55
  182. data/lib/rails_ai_context/version.rb +1 -1
  183. data/lib/rails_ai_context/vfs.rb +67 -40
  184. data/lib/rails_ai_context/view_file.rb +41 -3
  185. data/lib/rails_ai_context/watcher.rb +51 -13
  186. data/lib/rails_ai_context.rb +21 -0
  187. data/server.json +1 -1
  188. metadata +13 -1
@@ -30,7 +30,11 @@ document is aspirational.
30
30
  quirks_mode` inside Rails itself. Rails 8.1 dropped the keyword and resolves
31
31
  json 3 cleanly. The test bundle pins `json < 3` below 8.1 for that reason; an
32
32
  app on those lines has the same choice to make, and it is between its own
33
- Rails and json rather than anything here.
33
+ Rails and json rather than anything here. In such an app the stdio server
34
+ still works, because the gem writes its stdio replies with `JSON.generate`.
35
+ Over HTTP the MCP SDK serializes each reply with `to_json`, so every request
36
+ answers with a JSON-RPC error naming `unknown keyword: quirks_mode` until
37
+ the pair is fixed.
34
38
 
35
39
  The gemspec's `railties` bound is wider than the CI matrix: point releases inside
36
40
  7.0-8.1 and any future 8.x minor satisfy Bundler's constraint without a gem
@@ -60,6 +64,31 @@ Source: `.github/workflows/unit-matrix.yml`, which both `ci.yml` and
60
64
  a second run inside the matrix (Ruby 3.3 / Rails 8.0), since `release.yml`
61
65
  has no `lint` job of its own.
62
66
 
67
+ ### Operating systems
68
+
69
+ Linux, macOS and Windows. CI runs the unit suite on Linux for every cell
70
+ above, and on macOS (Ruby 3.4 / Rails 8.1 and Ruby 3.1 / Rails 7.0) and
71
+ Windows (Ruby 3.4 / Rails 8.1). The E2E harness, which builds real apps and
72
+ drives the CLI, the rake tasks and both transports, runs on Linux for six
73
+ Ruby / Rails pairs and on macOS and Windows for Ruby 3.4 / Rails 8.1. A
74
+ release waits for all of them.
75
+
76
+ On Windows:
77
+
78
+ - The MCP configs start the server through `cmd /c`, since a gem's
79
+ executables there are batch files that a client starting its server
80
+ without a shell does not run. See [GUIDE.md](GUIDE.md#mcp-server-setup)
81
+ for teams on both Windows and macOS or Linux.
82
+ - `rails_query` on SQLite runs without its time limit: the limit runs the
83
+ query in a forked child, and Windows has no `fork`. The answer says so.
84
+ - The pre-commit hook runs under the sh that Git for Windows runs hooks with.
85
+ - A checkout whose lines end in CRLF, Git for Windows' default, is read and
86
+ written in them: `rails_search_code` ends a line before its CRLF, so `$`
87
+ anchors as it does on LF, and the context files' managed block and the
88
+ initializer's `config.ai_tools` line keep the file's own line endings.
89
+ - ripgrep installs with `winget install BurntSushi.ripgrep.MSVC`; without it
90
+ `rails_search_code` takes the slower Ruby path.
91
+
63
92
  ### Rails 9
64
93
 
65
94
  Not supported, and the two install paths fail differently:
@@ -245,6 +274,15 @@ Proof sources:
245
274
  limit; the installer's cleanup on a small Rails 8.1 app holding hand-written
246
275
  rule files; and the unit suite on Ruby 3.1 with Rails 7.0 through Ruby 4.0
247
276
  with Rails 8.1.
277
+ In the v5.33.0 release QA: the release gem in a fresh app on Ruby 3.1 with
278
+ Rails 7.0 and 7.1, Ruby 3.2 with Rails 8.0, and Rails 8.1 on Ruby 3.3, 3.4
279
+ and 4.0, each through the standalone and in-Gemfile installs, the rake
280
+ tasks and all 45 tools over MCP stdio; a Rails 8.1 app reached through a
281
+ symlinked path, as macOS spells its temporary folders; a Rails 8.1 and a
282
+ Rails 7.0 app mounting the engine, six clients calling while a model
283
+ changed every 0.3 seconds; the pre-commit hook installed and run by real
284
+ commits in an app's own repository, a monorepo's folder and a submodule;
285
+ and the unit suite and the E2E harness on Linux, macOS and Windows.
248
286
  2. Non-crash coverage for every built-in tool including `get_view` in
249
287
  `spec/e2e/in_gemfile_install_spec.rb`'s full-tool sweep; output correctness
250
288
  (ivar cross-check, render-form detection, partial interfaces) verified
@@ -403,6 +441,32 @@ Postgres instance in `spec/e2e/postgres_install_spec.rb`, opt-in via
403
441
  under `config/initializers` adds with `Rails.application.routes.prepend` or
404
442
  `.append` are read, before and after the draw as Rails orders them. Routes Rails' own
405
443
  engines and gems draw into the app's table are read only with the app booted.
444
+ - **A folder of apps is served from the folder.** `init` run in a folder of
445
+ apps writes entries that name each app relative to that folder, because
446
+ Claude Code, Codex and OpenCode start a server in the folder they were
447
+ launched in, and Cursor and VS Code expand `${workspaceFolder}`. A tool
448
+ launched inside one of the apps still reads the folder's config above it,
449
+ and its servers then start where the relative path misses. Every server in
450
+ the folder also runs under the Ruby the version manager picks in the folder,
451
+ so an app that declares another may answer from the static tier, and its
452
+ `bundle exec` entry may not start; `init` warns when it sees one.
453
+ `spec/e2e/workspace_install_spec.rb` starts each entry the way its tool does,
454
+ on a standalone and an in-Gemfile app. ADR-0005 records the per-tool facts.
455
+ - **A file saved twice within one second at the same size can reload as the
456
+ first save.** Bootsnap's compile cache, which `config/boot.rb` sets up in a
457
+ new Rails app, knows a Ruby file by its size and the second of its mtime.
458
+ A file written again within that second at the same size gets the code
459
+ compiled from the earlier write back from it: on the reload the write sets
460
+ off, and in every process that loads the file after, the app's own server
461
+ and tests included, until the file is written in another second. Bootsnap
462
+ has no way to drop one entry, and the server does not touch the app's files
463
+ to move their stat, so a server that reloads code names such a file at the
464
+ end of every answer (under `_stale_code` in a JSON body), from the call
465
+ that sees the save until the file is saved again. What reflection reads,
466
+ such as associations and enums, may be the earlier save's meanwhile.
467
+ Saving the file again a second later puts the edit through, and
468
+ `DISABLE_BOOTSNAP_COMPILE_CACHE=1` in the server's environment turns the
469
+ compile cache off for that process.
406
470
 
407
471
  <br>
408
472
 
@@ -50,7 +50,7 @@ preset: full
50
50
  | Option | Type | Default | Description |
51
51
  |:-------|:-----|:--------|:------------|
52
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
- | `tool_mode` | Symbol | `:mcp` | `:mcp` (MCP server primary, CLI fallback) or `:cli` (CLI only, no MCP server) |
53
+ | `tool_mode` | Symbol | `:mcp` | `:mcp` (MCP server primary, CLI fallback) or `:cli` (CLI only, no MCP server). Any other value is refused. Under `:cli` the tools' own next-step hints name the command (`rails-ai-context tool schema table=posts`, or the rake task when the gem is in the Gemfile) rather than an MCP call |
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
 
56
56
  #### MCP only
@@ -86,25 +86,26 @@ empty list wrote every tool's files.
86
86
  | Option | Type | Default | Description |
87
87
  |:-------|:-----|:--------|:------------|
88
88
  | `preset` | Symbol | `:full` | `:full` (40 introspectors) or `:standard` (17 introspectors) |
89
- | `context_mode` | Symbol | `:compact` | `:compact` (context files capped at ~150 lines) or `:full` (no line cap) |
89
+ | `context_mode` | Symbol | `:compact` | `:compact` (context files capped at ~150 lines) or `:full` (no line cap). Any other value is refused, and so is any other `CONTEXT_MODE` on a rake task |
90
90
  | `introspectors` | Array of symbols | (from preset) | Override the introspector list directly |
91
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) |
92
92
  | `anti_hallucination_rules` | Boolean | `true` | Embed 6-rule verification protocol in generated context files |
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 |
93
+ | `claude_max_lines` | Integer | `150` | Budget for the non-blank lines of a compact context file's gem-managed block, the `<!-- BEGIN/END rails-ai-context -->` markers included. Over budget, the data sections (Stack, Key models, Gems, Architecture) are cut first, down to none; the title and the Commands, Warnings, Rules and Tools sections are never cut, so they set the smallest file a budget can give (about 110 lines with the anti-hallucination protocol). Must be positive |
94
94
 
95
95
  ### MCP Server
96
96
 
97
97
  | Option | Type | Default | Validation | Description |
98
98
  |:-------|:-----|:--------|:-----------|:------------|
99
- | `server_name` | String | `"rails-ai-context"` | - | MCP server name |
99
+ | `server_name` | String | `"rails-ai-context"` | - | The MCP server name the server announces. When it is left at the default, the `RAILS_AI_CONTEXT_SERVER_NAME` environment variable takes its place for one process, which is how a workspace's entries announce their app |
100
100
  | `cache_ttl` | Integer | `60` | Must be positive | Cache time-to-live in seconds |
101
101
  | `max_tool_response_chars` | Integer | `200_000` | Must be positive | Safety cap for tool responses and MCP resource payloads. An over-cap resource keeps its JSON shape: whole elements are dropped and reported under a `_truncated` key |
102
- | `live_reload` | Symbol/Boolean | `:auto` | - | `:auto` (uses `listen` gem if available), `true`, or `false` |
102
+ | `live_reload` | Symbol/Boolean | `:auto` | - | `:auto` (uses `listen` gem if available), `true`, or `false`. The watch tells clients when files change; every server checks the app's files at each tool call either way, so its answers follow edits with or without it |
103
103
  | `live_reload_debounce` | Float | `1.5` | - | Seconds to wait before processing file changes |
104
104
  | `auto_mount` | Boolean | `false` | - | Auto-mount Rack middleware for HTTP transport |
105
- | `http_path` | String | `"/mcp"` | - | HTTP endpoint path |
106
- | `http_bind` | String | `"127.0.0.1"` | - | HTTP bind address |
107
- | `http_port` | Integer | `6029` | 1 to 65535 | HTTP listen port |
105
+ | `allow_http_in_production` | Boolean | `false` | - | Let the mounted engine and `auto_mount` answer in production and every other environment but development and test. Off, they refuse there with a 403 and a JSON-RPC error saying how to opt in. Every tool answers whoever reaches the endpoint, so set it only with the endpoint behind your app's authentication (see [SECURITY.md](SECURITY.md#mcp-http-transport)). The endpoint answers the hosts the app's `config.hosts` allows |
106
+ | `http_path` | String | `"/mcp"` | - | HTTP endpoint path, for `auto_mount` and the standalone HTTP server (the engine answers where you mount it) |
107
+ | `http_bind` | String | `"127.0.0.1"` | - | Address the standalone HTTP server (`serve --transport http`, `rails ai:serve_http`) binds. Another address opens the port to the network with no authentication, but the MCP SDK answers only requests whose `Host` header is `127.0.0.1`, `::1` or `localhost`: a client that uses the machine's IP or name gets 403 "Invalid Host header", and one that sends `Host: localhost` gets every tool. It suits a container whose port is published to the host. The engine and `auto_mount` use the app's own server and ignore it |
108
+ | `http_port` | Integer | `6029` | 1 to 65535 | Port of the standalone HTTP server. The engine and `auto_mount` use the app's own server and ignore it |
108
109
 
109
110
  ### Cross-Tool Hydration
110
111
 
@@ -119,7 +120,7 @@ empty list wrote every tool's files.
119
120
  |:-------|:-----|:--------|:------------|
120
121
  | `excluded_models` | Array | 8 framework models | Models to skip during introspection |
121
122
  | `excluded_controllers` | Array | 2 framework controllers | Controllers to skip |
122
- | `excluded_route_prefixes` | Array | 6 framework prefixes | Route prefixes to skip |
123
+ | `excluded_route_prefixes` | Array | 7 framework prefixes | Route controller prefixes `rails_get_routes` hides with `app_only`: the routes Rails' engines and gems draw on their own. A route the app's route files declare (`devise_for`, the health check) is not one |
123
124
  | `excluded_filters` | Array | 5 framework filters | Controller filters to skip |
124
125
  | `excluded_middleware` | Array | 25 framework middleware | Middleware to skip in listing |
125
126
  | `excluded_paths` | Array | `["node_modules", "tmp", "log", "vendor", ".git", "doc", "docs"]` | Paths excluded from search |
@@ -223,13 +224,31 @@ its exact name blocks it; see [SECURITY.md](SECURITY.md).
223
224
  | Option | Type | Default | Description |
224
225
  |:-------|:-----|:--------|:------------|
225
226
  | `custom_tools` | Array | `[]` | Additional MCP::Tool classes to register (initializer only - a class reference cannot be written in YAML) |
226
- | `skip_tools` | Array | `[]` | Built-in tool names to exclude (e.g., `%w[rails_security_scan]`) |
227
+ | `skip_tools` | Array | `[]` | Built-in tool names to exclude (e.g., `%w[rails_security_scan]`). A skipped tool is left out of every list and of the `_Next:` hints other tools end with, and a call that names it anyway - from the CLI, rake or an MCP client - says it is turned off here |
227
228
 
228
229
  ### Output
229
230
 
230
231
  | Option | Type | Default | Description |
231
232
  |:-------|:-----|:--------|:------------|
232
- | `output_dir` | String | Rails.root | Directory for generated context files |
233
+ | `output_dir` | String | Rails.root | Directory for generated context files. OpenCode reads `app/models/AGENTS.md` and `app/controllers/AGENTS.md` beside the code, so those two are written only where the directory exists under it, and named `no app/models under output_dir` otherwise |
234
+
235
+ ---
236
+
237
+ ## Environment variables
238
+
239
+ Read from the environment of the process, so an MCP config sets them in its
240
+ server entry's `env`.
241
+
242
+ | Variable | Read by | Effect |
243
+ |:---------|:--------|:-------|
244
+ | `RAILS_AI_CONTEXT_BOOT_TIMEOUT` | Every command that boots the app, and the `ai:serve`, `ai:serve_http` and `ai:tool` rake tasks | Seconds a boot may take before it counts as failed; a command that can then answers from the static tier. Default 20 for `serve` over stdio and `rails ai:serve`, whose client waits about 30 seconds for its first answer, and 60 for everything else. A value that is not a whole number warns and keeps the default |
245
+ | `RAILS_AI_CONTEXT_SERVER_NAME` | The MCP server | The name it announces for one process, in place of the default `server_name`; a `server_name` the app sets wins. A workspace's MCP entries set it |
246
+ | `DEBUG` | Every command and the MCP server | Any value prints the backtrace of a failed boot, a failed command and an exception that escapes a tool, and names the errors a fallback swallows |
247
+ | `RAILS_ENV`, `RACK_ENV` | Every command | The Rails environment to boot, read the way Rails reads it; `--environment` sets it for one command. Default `development` |
248
+ | `JSON` | `rails 'ai:tool[NAME]'` | `JSON=1` prints the JSON envelope, as `--json` does |
249
+ | `CONTEXT_MODE` | `rails ai:context`, `ai:context:<format>` and `ai:context_for` | `compact` or `full` for one run, in place of `config.context_mode`. Any other value stops the task |
250
+ | `FORMAT` | `rails ai:context_for` | The format when the task is given none: `FORMAT=cursor rails ai:context_for`. Default `claude` |
251
+ | `STRICT` | `rails ai:doctor` | `STRICT=1` (or `true`, `yes`) exits 1 when a check fails, as `doctor --strict` does |
233
252
 
234
253
  ---
235
254
 
data/docs/CUSTOM_TOOLS.md CHANGED
@@ -80,7 +80,7 @@ if defined?(RailsAiContext) && RailsAiContext.respond_to?(:configure)
80
80
  end
81
81
  ```
82
82
 
83
- Custom tools appear alongside built-in tools in the MCP server and CLI.
83
+ Custom tools appear alongside built-in tools in the MCP server and CLI, and in the tool counts and lists of the generated context files. The tools table gives a custom tool its own row, described by the first sentence of its `description`.
84
84
 
85
85
  Use the class *name* as a string: classes under `app/` (including
86
86
  `app/mcp_tools/`) are not autoloadable while initializers run, so a bare
@@ -216,6 +216,7 @@ end
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
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
+ - Through the mounted engine a call's answer is sent whole when the call is over. Progress and log notifications a tool sends (`server_context.report_progress`, `server_context.notify_log_message`) arrive together with its answer, and a request it makes of the client (sampling, elicitation, roots) fails at once, saying why. stdio and the standalone HTTP server deliver both as they are sent.
219
220
 
220
221
  ---
221
222
 
data/docs/FAQ.md CHANGED
@@ -47,7 +47,11 @@ Yes. The gem gracefully degrades - it parses `db/schema.rb` as text when no data
47
47
 
48
48
  ### Can I switch between Gemfile and standalone?
49
49
 
50
- Yes, freely. Both generate identical context files and provide the same 45 tools. Just re-run the install/init to update MCP config files.
50
+ Yes, freely. Both write the same context files and serve the same 45 tools; only the commands those files name follow the install (`rails ai:context` and `rails 'ai:tool[...]'` in the Gemfile, `rails-ai-context context` and `rails-ai-context tool ...` standalone). Re-run the install or `init` after switching: it updates the MCP config files and regenerates the context files with the new commands.
51
+
52
+ ### My editor opens a folder that holds several apps. Does that work?
53
+
54
+ Yes. Run `rails-ai-context init` in that folder. It asks once, gives each app its own `.rails-ai-context.yml` and context files, and writes one MCP server per app into the folder's configs, `rails-ai-context-<app folder>`, so your AI tool sees every app and each app's tools are named after it. Start the tool in that folder: the entries name each app relative to it. See [`init`](CLI.md#init-standalone-only).
51
55
 
52
56
  ### Do I need to commit the generated files?
53
57
 
@@ -83,7 +87,7 @@ Start with `rails_onboard` for an app overview, `rails_analyze_feature` for feat
83
87
 
84
88
  ### Can I add my own tools?
85
89
 
86
- Yes. See [Custom Tools](CUSTOM_TOOLS.md). Create an `MCP::Tool` subclass, register it via `config.custom_tools`, and it appears alongside the 45 built-in tools.
90
+ Yes. See [Custom Tools](CUSTOM_TOOLS.md). Create an `MCP::Tool` subclass, register it via `config.custom_tools`, and it appears alongside the 45 built-in tools, in the generated context files' tool counts and lists too.
87
91
 
88
92
  ### Can I remove built-in tools?
89
93
 
@@ -93,6 +97,8 @@ Yes. Use `config.skip_tools`:
93
97
  config.skip_tools = %w[rails_security_scan rails_query]
94
98
  ```
95
99
 
100
+ The server, the CLI and the generated context files then leave them out alike, so no context file tells the AI to call a tool that answers "Unknown tool".
101
+
96
102
  ### What's the `detail` parameter?
97
103
 
98
104
  Individual lookup tools accept `detail`: `summary` (compact), `standard` (default), `full` (everything). Start with summary and drill down as needed. This keeps AI context windows lean. Composite tools (`rails_get_context`, `rails_analyze_feature`) do not accept `detail` - they always return their full bundled output.
@@ -172,15 +178,19 @@ Yes. Add content outside the `<!-- BEGIN/END rails-ai-context -->` markers. The
172
178
 
173
179
  ### Is introspection slow?
174
180
 
175
- Introspection results are cached with TTL (default: 60s) and fingerprint invalidation. The first call is slower; subsequent calls use cache. Prism AST parsing uses a single-pass Dispatcher - all 8 listeners run in one tree walk.
181
+ Introspection results are cached with TTL (default: 60s) and fingerprint invalidation. The first call is slower; subsequent calls use cache until a watched file changes. Prism AST parsing uses a single-pass Dispatcher - all 8 listeners run in one tree walk.
176
182
 
177
183
  ### Does this affect my app's performance?
178
184
 
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`.
185
+ 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. Where the gem is loaded in production, it refuses two things there by default: `rails_query`, and MCP over HTTP from inside the app (the mounted engine and `auto_mount`). 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`.
180
186
 
181
187
  ### How does live reload work?
182
188
 
183
- The `listen` gem watches `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`. When files change, caches are invalidated and MCP clients are notified. Debounce interval: 1.5s (configurable).
189
+ The `listen` gem watches `app/`, `config/`, `db/`, `lib/`, `rakelib/`, `test/`, `spec/`, and when files change, MCP clients are notified. Debounce interval: 1.5s (configurable).
190
+
191
+ Answers do not depend on it. Every tool call first checks those files, with `listen` or without it (a new Rails 8 app does not bundle it), and when one changed it reloads the app's code and drops the caches, so a call sees an edit made a moment before it. The check costs about a millisecond on a small app and about 30 ms at 10,000 files.
192
+
193
+ Under `RAILS_ENV=test`, or in production with eager loading, the app does not reload code, so what reflection reads (associations, enums) stays as it was at boot; once app code changes, every answer ends with a note naming the files, and a restart brings it up to date.
184
194
 
185
195
  ---
186
196
 
@@ -228,7 +238,7 @@ Those are AI coding interfaces. This gem is a data layer that makes *any* of the
228
238
 
229
239
  ### My AI tool doesn't see the MCP server
230
240
 
231
- Run `rails ai:doctor` - it checks MCP config files for all configured tools. See [Troubleshooting](TROUBLESHOOTING.md) for detailed steps.
241
+ Run `rails ai:doctor` - it checks each configured tool's MCP config file, and whether the command it holds can start. See [Troubleshooting](TROUBLESHOOTING.md) for detailed steps.
232
242
 
233
243
  ### Tools return empty results
234
244