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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1287 -0
- data/CONTEXT.md +26 -2
- data/README.md +13 -9
- data/Rakefile +6 -0
- data/app/controllers/rails_ai_context/mcp_controller.rb +114 -84
- data/docs/ARCHITECTURE.md +6 -4
- data/docs/CLI.md +79 -6
- data/docs/COMPATIBILITY.md +65 -1
- data/docs/CONFIGURATION.md +30 -11
- data/docs/CUSTOM_TOOLS.md +2 -1
- data/docs/FAQ.md +16 -6
- data/docs/GUIDE.md +117 -71
- data/docs/INTROSPECTORS.md +8 -7
- data/docs/QUICKSTART.md +3 -3
- data/docs/RECIPES.md +1 -1
- data/docs/SECURITY.md +109 -25
- data/docs/SETUP.md +30 -6
- data/docs/STANDALONE.md +10 -3
- data/docs/TOOLS.md +145 -34
- data/docs/TROUBLESHOOTING.md +90 -12
- data/docs/adr/0003-shared-tool-cache-semantics.md +8 -0
- data/docs/adr/0005-workspace-mcp-entries.md +39 -0
- data/exe/rails-ai-context +733 -104
- data/lib/generators/rails_ai_context/install/install_generator.rb +341 -125
- data/lib/rails_ai_context/ast_cache.rb +7 -0
- data/lib/rails_ai_context/base_mixins.rb +8 -8
- data/lib/rails_ai_context/boot_manager.rb +22 -3
- data/lib/rails_ai_context/change_watch.rb +14 -11
- data/lib/rails_ai_context/cli/app_root.rb +401 -0
- data/lib/rails_ai_context/cli/entry_boot.rb +53 -20
- data/lib/rails_ai_context/cli/tool_runner.rb +47 -21
- data/lib/rails_ai_context/code_reloader.rb +115 -1
- data/lib/rails_ai_context/concern_membership.rb +3 -1
- data/lib/rails_ai_context/concern_paths.rb +8 -3
- data/lib/rails_ai_context/configuration.rb +41 -9
- data/lib/rails_ai_context/context_file_report.rb +19 -2
- data/lib/rails_ai_context/database_file.rb +26 -0
- data/lib/rails_ai_context/doctor.rb +1122 -184
- data/lib/rails_ai_context/engine.rb +11 -1
- data/lib/rails_ai_context/erb_source.rb +19 -0
- data/lib/rails_ai_context/executable.rb +21 -0
- data/lib/rails_ai_context/facts_formatter.rb +5 -2
- data/lib/rails_ai_context/file_walk.rb +61 -10
- data/lib/rails_ai_context/fingerprinter/snapshot.rb +234 -0
- data/lib/rails_ai_context/fingerprinter.rb +40 -12
- data/lib/rails_ai_context/gem_lock.rb +176 -26
- data/lib/rails_ai_context/git_ignore.rb +22 -0
- data/lib/rails_ai_context/install/ai_tool.rb +31 -5
- data/lib/rails_ai_context/install/cleanup.rb +21 -0
- data/lib/rails_ai_context/install/program.rb +209 -35
- data/lib/rails_ai_context/install/selection_record.rb +258 -97
- data/lib/rails_ai_context/install/surface.rb +6 -1
- data/lib/rails_ai_context/install/validation_hook.rb +282 -0
- data/lib/rails_ai_context/install/workspace.rb +166 -0
- data/lib/rails_ai_context/install_mode.rb +71 -13
- data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/action_presence.rb +11 -6
- data/lib/rails_ai_context/introspectors/action_source.rb +182 -0
- data/lib/rails_ai_context/introspectors/action_text_introspector.rb +1 -2
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -3
- data/lib/rails_ai_context/introspectors/active_support_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/admin_resources.rb +1 -1
- data/lib/rails_ai_context/introspectors/anyway_configs.rb +1 -1
- data/lib/rails_ai_context/introspectors/api_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +10 -3
- data/lib/rails_ai_context/introspectors/component_introspector.rb +3 -2
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +56 -8
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +14 -2
- data/lib/rails_ai_context/introspectors/devops_introspector.rb +3 -1
- data/lib/rails_ai_context/introspectors/eager_load.rb +201 -9
- data/lib/rails_ai_context/introspectors/engine_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/env_config_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/env_references.rb +10 -1
- data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +2 -1
- data/lib/rails_ai_context/introspectors/grape_endpoints.rb +1 -1
- data/lib/rails_ai_context/introspectors/habtm_join_tables.rb +1 -1
- data/lib/rails_ai_context/introspectors/helper_names.rb +1 -1
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +2 -2
- data/lib/rails_ai_context/introspectors/job_introspector.rb +8 -6
- data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +46 -1
- data/lib/rails_ai_context/introspectors/listeners/env_access_listener.rb +10 -1
- data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +3 -1
- data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +14 -3
- data/lib/rails_ai_context/introspectors/listeners/scopes_listener.rb +88 -12
- data/lib/rails_ai_context/introspectors/middleware_introspector.rb +2 -2
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +27 -0
- data/lib/rails_ai_context/introspectors/migration_replay.rb +2 -2
- data/lib/rails_ai_context/introspectors/model_introspector.rb +91 -5
- data/lib/rails_ai_context/introspectors/n_plus_one_scan.rb +620 -0
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +11 -174
- data/lib/rails_ai_context/introspectors/route_introspector.rb +10 -1
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +57 -4
- data/lib/rails_ai_context/introspectors/seeds_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_scan.rb +13 -9
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +15 -6
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +130 -11
- data/lib/rails_ai_context/introspectors/view_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +15 -1
- data/lib/rails_ai_context/legacy_cleanup.rb +17 -4
- data/lib/rails_ai_context/live_reload.rb +20 -20
- data/lib/rails_ai_context/mcp_config_generator.rb +916 -109
- data/lib/rails_ai_context/mcp_edge.rb +185 -32
- data/lib/rails_ai_context/middleware.rb +5 -1
- data/lib/rails_ai_context/migration_status.rb +88 -25
- data/lib/rails_ai_context/output_guard.rb +48 -2
- data/lib/rails_ai_context/package_json.rb +1 -1
- data/lib/rails_ai_context/path_resolver.rb +46 -13
- data/lib/rails_ai_context/payload.rb +55 -7
- data/lib/rails_ai_context/pending_migrations.rb +5 -1
- data/lib/rails_ai_context/portable_path.rb +3 -2
- data/lib/rails_ai_context/presets.rb +15 -11
- data/lib/rails_ai_context/redaction.rb +254 -51
- data/lib/rails_ai_context/resources.rb +55 -20
- data/lib/rails_ai_context/route_coverage.rb +8 -0
- data/lib/rails_ai_context/safe_file.rb +14 -1
- data/lib/rails_ai_context/safe_path.rb +101 -4
- data/lib/rails_ai_context/schema_adapter.rb +8 -0
- data/lib/rails_ai_context/schema_version.rb +9 -0
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +86 -32
- data/lib/rails_ai_context/serializers/compact_serializer_helper.rb +36 -34
- data/lib/rails_ai_context/serializers/context_file_serializer.rb +21 -29
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +8 -6
- data/lib/rails_ai_context/serializers/copilot_serializer.rb +2 -7
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +17 -15
- data/lib/rails_ai_context/serializers/guide_examples.rb +141 -0
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +142 -29
- data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +36 -14
- data/lib/rails_ai_context/serializers/section_facts.rb +35 -1
- data/lib/rails_ai_context/serializers/section_marker_writer.rb +94 -12
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +24 -12
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +244 -129
- data/lib/rails_ai_context/server.rb +304 -71
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +112 -43
- data/lib/rails_ai_context/tools/analyze_feature.rb +72 -18
- data/lib/rails_ai_context/tools/argument_schema.rb +49 -0
- data/lib/rails_ai_context/tools/base_tool.rb +352 -41
- data/lib/rails_ai_context/tools/brakeman_guard.rb +24 -0
- data/lib/rails_ai_context/tools/dependency_graph.rb +63 -28
- data/lib/rails_ai_context/tools/diagnose.rb +218 -11
- data/lib/rails_ai_context/tools/generate_test.rb +339 -39
- data/lib/rails_ai_context/tools/get_callbacks.rb +24 -13
- data/lib/rails_ai_context/tools/get_component_catalog.rb +1 -1
- data/lib/rails_ai_context/tools/get_concern.rb +91 -27
- data/lib/rails_ai_context/tools/get_config.rb +13 -4
- data/lib/rails_ai_context/tools/get_context.rb +71 -14
- data/lib/rails_ai_context/tools/get_controllers.rb +157 -40
- data/lib/rails_ai_context/tools/get_conventions.rb +26 -4
- data/lib/rails_ai_context/tools/get_edit_context.rb +27 -14
- data/lib/rails_ai_context/tools/get_env.rb +18 -9
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +3 -1
- data/lib/rails_ai_context/tools/get_gems.rb +1 -1
- data/lib/rails_ai_context/tools/get_helper_methods.rb +9 -5
- data/lib/rails_ai_context/tools/get_i18n.rb +1 -1
- data/lib/rails_ai_context/tools/get_job_pattern.rb +2 -1
- data/lib/rails_ai_context/tools/get_mailers.rb +3 -3
- data/lib/rails_ai_context/tools/get_model_details.rb +16 -6
- data/lib/rails_ai_context/tools/get_partial_interface.rb +242 -23
- data/lib/rails_ai_context/tools/get_routes.rb +19 -6
- data/lib/rails_ai_context/tools/get_schema.rb +51 -18
- data/lib/rails_ai_context/tools/get_service_pattern.rb +24 -10
- data/lib/rails_ai_context/tools/get_stimulus.rb +37 -1
- data/lib/rails_ai_context/tools/get_test_info.rb +18 -4
- data/lib/rails_ai_context/tools/get_turbo_map.rb +221 -58
- data/lib/rails_ai_context/tools/get_view.rb +14 -14
- data/lib/rails_ai_context/tools/migration_advisor.rb +355 -48
- data/lib/rails_ai_context/tools/onboard.rb +46 -6
- data/lib/rails_ai_context/tools/performance_check.rb +4 -2
- data/lib/rails_ai_context/tools/query.rb +914 -86
- data/lib/rails_ai_context/tools/read_logs.rb +69 -15
- data/lib/rails_ai_context/tools/review_changes.rb +204 -43
- data/lib/rails_ai_context/tools/runtime_info.rb +51 -1
- data/lib/rails_ai_context/tools/safe_call.rb +35 -14
- data/lib/rails_ai_context/tools/search_code.rb +243 -67
- data/lib/rails_ai_context/tools/search_docs.rb +3 -3
- data/lib/rails_ai_context/tools/security_scan.rb +141 -31
- data/lib/rails_ai_context/tools/session_context.rb +38 -4
- data/lib/rails_ai_context/tools/validate.rb +259 -40
- data/lib/rails_ai_context/tools/validate_semantics.rb +129 -55
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/vfs.rb +67 -40
- data/lib/rails_ai_context/view_file.rb +41 -3
- data/lib/rails_ai_context/watcher.rb +51 -13
- data/lib/rails_ai_context.rb +21 -0
- data/server.json +1 -1
- metadata +13 -1
data/docs/TOOLS.md
CHANGED
|
@@ -43,6 +43,8 @@ Tool name resolution is flexible - all of these work:
|
|
|
43
43
|
| `get_schema` | `rails_get_schema` |
|
|
44
44
|
| `rails_get_schema` | `rails_get_schema` |
|
|
45
45
|
|
|
46
|
+
Over MCP the name is exact. A name the server has no tool for fails with a JSON-RPC `-32602` error, `Tool not found: <name>`, that names the nearest tools, or every tool when none is near; `data.available` lists them all.
|
|
47
|
+
|
|
46
48
|
Most lookup tools accept a **`detail`** parameter: `summary` (compact), `standard` (default), or `full` (everything). Start with summary, drill down as needed. Composite tools (`rails_get_context`, `rails_analyze_feature`) do not accept `detail`.
|
|
47
49
|
|
|
48
50
|
<p align="right"><a href="#table-of-contents">↑ back to top</a></p>
|
|
@@ -74,9 +76,9 @@ Search your codebase with regex, ripgrep acceleration, and sensitive file blocki
|
|
|
74
76
|
|
|
75
77
|
| Parameter | Type | Default | Description |
|
|
76
78
|
|:----------|:-----|:--------|:------------|
|
|
77
|
-
| `pattern` | string | *required* | Regex pattern to search for |
|
|
79
|
+
| `pattern` | string | *required* | Regex pattern to search for. With `definition` or `class`, a plain method or constant name matches literally (`valid?`), anything else as a regex (`generate_.*`) |
|
|
78
80
|
| `path` | string | - | Subdirectory or file to search (relative to Rails root); a file is searched whatever its type or ignore files say, as ripgrep does, unless it is sensitive |
|
|
79
|
-
| `match_type` | enum | `any` | `any`, `definition
|
|
81
|
+
| `match_type` | enum | `any` | `any`, `definition` (def lines whose method name contains the pattern), `class` (class/module lines whose name contains it), `call`, `trace` |
|
|
80
82
|
| `file_type` | string | - | Filter by extension (`rb`, `erb`, `js`, etc.) |
|
|
81
83
|
| `exact_match` | boolean | `false` | Literal, whole-word match. `def reblog?` does not match `def reblog` |
|
|
82
84
|
| `exclude_tests` | boolean | `false` | Skip test/spec directories |
|
|
@@ -85,17 +87,19 @@ Search your codebase with regex, ripgrep acceleration, and sensitive file blocki
|
|
|
85
87
|
| `offset` | integer | `0` | Skip this many emitted lines |
|
|
86
88
|
| `limit` | integer | auto | Max lines to return; the default is sized in matches, and a limit too small for one match and its context still returns that match |
|
|
87
89
|
|
|
88
|
-
The header counts matches, not emitted lines, so below the cap it does not move with `context_lines`.
|
|
90
|
+
The header counts matches, not emitted lines, so below the cap it does not move with `context_lines`. The cap always holds the first match and the context before it, so a `max_search_results` below `context_lines + 1` is raised to that rather than answering "No results found". When the search hits the cap (a cap on emitted lines) the header says `first N lines scanned` and marks the count `N+`: it is then the matches among the lines that were scanned, a floor rather than the total, so it falls as `context_lines` grows. `offset` and `limit` count lines, so a search with context returns more lines than matches. A limit smaller than one match's context block would hold only context, so the page starts at the next match instead and says so.
|
|
91
|
+
|
|
92
|
+
> **Trace mode** returns definition + source code + every caller grouped by file and labelled by type + tests - replaces 4-5 sequential file reads. A model's `scope :name` is the definition of a method of that name when no `def` defines one.
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
ripgrep follows no symlink while it walks, so a directory linked in from the app's git repository (a monorepo package linked into `packs/`) is handed to it as a path of its own, and its rows are named the way the app spells them (`packs/billing/app/models/invoice.rb`). `path` may name such a directory or a file in it. A link out of the repository, or out of the app when it is in none, is neither searched nor accepted as `path`.
|
|
91
95
|
|
|
92
|
-
Without ripgrep on the PATH, the Ruby fallback returns the rows ripgrep would, context lines and the line cap included, from the files ripgrep would search: no hidden files, no symlinks followed, no binary files (a NUL byte in the first 64 KiB), and ignore files read with ripgrep's precedence, where the file type decides before depth: any `.rgignore`, then any `.ignore`, then any `.gitignore`, then `.git/info/exclude`, then the global excludes file, the deepest file first within a type. `.ignore` and `.rgignore` apply everywhere, including the directories above the app; the git files apply only inside a repository and only as far up as the nearest `.git` (a file for a worktree or submodule), whose `info/exclude` is the only one read. Patterns match case-sensitively.
|
|
96
|
+
Without ripgrep on the PATH, the Ruby fallback returns the rows ripgrep would, context lines and the line cap included, from the files ripgrep would search: no hidden files, no other symlinks followed, no binary files (a NUL byte in the first 64 KiB), and ignore files read with ripgrep's precedence, where the file type decides before depth: any `.rgignore`, then any `.ignore`, then any `.gitignore`, then `.git/info/exclude`, then the global excludes file, the deepest file first within a type. `.ignore` and `.rgignore` apply everywhere, including the directories above the app; the git files apply only inside a repository and only as far up as the nearest `.git` (a file for a worktree or submodule), whose `info/exclude` is the only one read. Patterns match case-sensitively.
|
|
93
97
|
|
|
94
|
-
Redaction treats a value under a secret-named key as a secret unless it is clearly something else: shorter than eight characters (four under a password-type key: `password`, `passwd`, `pass`, `pin`, `passphrase`), containing whitespace (a message; not under `passphrase`), a URL, an email address or a hostname (not under a password-type key), a URL query (`?mode=safe`) or a path (two slashes, no `+` or `=`), a placeholder (`<token>`, `${VAR}`, `%{var}`, a `{name}` slot, `xxxx`, `your-...`, `put-your-key-here`), a version (`2.4.1`, `v2`) or a date, an HTTP header name (`X-Api-Key`; not under a password-type key), an env name (capitals joined by `_`, led by `_` or `HTTP_` or naming a secret word, like `_DISCOURSE_API`), a name spelled from a secret word (`user_api_key`; not under a password-type key, where `admin_password` is the password), the key's own name (an enum's `password: 'password'`), or not ASCII. Keys ending `_url`, `_error`, `_use` and the like describe a secret rather than hold one. A call or block whose first argument names a secret holds its value too (`ENV.fetch("SECRET_KEY_BASE", "...")`, `let(:api_key) { "..." }`, `option :client_secret, default: "..."`, a `credentials.fetch` default); a ternary's `? "a" : "b"` is two values, not a key and its value. Under a name that ends in `_KEY`, `Key` or `key` without a secret word (`ALGOLIA_ADMIN_KEY`, `apiKey`), a value is filtered only when it looks like a credential: a run of 16+ characters mixing letters and digits, or a known token format; a cache or i18n key (`views/posts/1`, `users.index.title`), a storage key (`wp-content/uploads/...`), a word, a path or a header name is kept. Log lines use the same rule. An unquoted `NAME=value` with a secret-named name, in any file (shell, Dockerfile `ENV`, compose, env, code), has its value filtered unless it is `$VAR`, `${...}`, `#{...}` or ERB; the value ends at the first quote, bracket, `&`, `,`, `;` or space, so in a URL query (`?token=...&page=2`) only the value goes. A `:name=` setter symbol
|
|
98
|
+
Redaction treats a value under a secret-named key as a secret unless it is clearly something else: shorter than eight characters (four under a password-type key: `password`, `passwd`, `pass`, `pin`, `passphrase`, Redis's `requirepass` and `masterauth`, the `MYSQL_PWD` and `REDISCLI_AUTH` the mysql and redis-cli clients read, and a long password word that ends a name with another joined on before it, as Postgres's `PGPASSWORD` and a camel-cased `userPassword` have), containing whitespace (a message; not under `passphrase`), a URL, an email address or a hostname (not under a password-type key), a URL query (`?mode=safe`) or a path (two slashes, no `+` or `=`), a placeholder (`<token>`, `${VAR}`, `%{var}`, a `{name}` slot, `xxxx`, `your-...`, `put-your-key-here`), a version (`2.4.1`, `v2`) or a date, an HTTP header name (`X-Api-Key`; not under a password-type key), an env name (capitals joined by `_`, led by `_` or `HTTP_` or naming a secret word, like `_DISCOURSE_API`), a name spelled from a secret word (`user_api_key`; not under a password-type key, where `admin_password` is the password), the key's own name in any case (an enum's `password: 'password'`, `currentPassword: "current_password"`), a switch's value (`true`, `false`, `null`), or not ASCII. Keys ending `_url`, `_error`, `_use`, `_file`, `_dir`, `_id` and the like (in camel case too: `tokenType`) describe a secret rather than hold one, and so does a Stimulus target, action, controller, outlet or class (`password_visibility_target`). A quoted name joined by dashes, a header or an attribute, reads with its dashes as underscores, so `"X-Api-Key" => "..."` and `req["x-auth-token"] = "..."` hold an API key and a token, while `"Content-Type"` names no secret. A camel-cased name with `Secret` or `Token` among its words (`accessToken`, `clientSecret`, `jwtSecretKey`) is a secret's name, as JSON and JavaScript spell `access_token`, in source and in a log line. A call or block whose first argument names a secret holds its value too (`ENV.fetch("SECRET_KEY_BASE", "...")`, `let(:api_key) { "..." }`, `option :client_secret, default: "..."`, a `credentials.fetch` default); a ternary's `? "a" : "b"` is two values, not a key and its value. Under a name that ends in `_KEY`, `Key` or `key` without a secret word (`ALGOLIA_ADMIN_KEY`, `apiKey`), a value is filtered only when it looks like a credential: a run of 16+ characters mixing letters and digits, or a known token format; a cache or i18n key (`views/posts/1`, `users.index.title`), a storage key (`wp-content/uploads/...`), a word, a path or a header name is kept. Log lines use the same rule. An unquoted `NAME=value` with a secret-named name, in any file (shell, Dockerfile `ENV`, compose, env, code), a flag (`--password=...`, `--requirepass=...`) included, has its value filtered unless it is `$VAR`, `${...}`, `#{...}` or ERB; the value ends at the first quote, bracket, `&`, `,`, `;` or space, so in a URL query (`?token=...&page=2`) only the value goes. A secret-named flag's value given as the next word (`redis-server --requirepass ...`, `--token ...`) is filtered, bare or quoted, when it has a digit and would be filtered under that name, so a word (`the --password option`), a metavariable (`--token TOKEN`), a variable, ERB, another flag, a `--no-...` switch, a path (`~/.ssh/id_ed25519`, `./keys/deploy.pem`, `keys/deploy.pem`) or a `name=value` list (BuildKit's `--secret id=...,env=...`) stays. A `:name=` setter symbol, a `(?<=name=` lookbehind and a regex's wildcard (`/--password=.+/`) are not assignments. The credential after an auth scheme (`Bearer`, `Basic`, `Token`), under an `Authorization` key, in a curl line or a cassette, is filtered in any file when it is shaped like one: 16 or more letters and digits, both, read past a UUID's dashes and base64's `+`, `/` and `=`; the scheme stays, and `Bearer #{token}`, `Bearer YOUR_TOKEN` and `Basic realm=...` hold no credential. A log line's `Authorization` header goes whole in every spelling: `Authorization: Basic ...`, a quoted value, a hash or JSON key, `HTTP_AUTHORIZATION`. In an example file (`*.example`, `*.sample`, `*.template`) a value under a secret-named key is kept only when it is plainly a placeholder: a placeholder shape above, or up to 20 characters of words with no digits (`changeme`, `your_password_here`). Locale files are filtered only for credential formats. In a YAML or JSON file read whole, a mapping a secret's name names holds that secret in its `value`: Heroku's `app.json` (`"STRIPE_SECRET_KEY": { "value": "..." }`, on one line or several), a YAML `api_token:` over `value: ...`, and a `render.yaml` or Kubernetes entry named by a sibling (`- key: CLIENT_SECRET`, `- name: API_TOKEN`) over its `value:`; a boolean, and the next entry's value, stay. A YAML key written as a Ruby symbol (`:password:` in `config/sidekiq.yml`) reads as its name.
|
|
95
99
|
|
|
96
100
|
### `rails_get_edit_context`
|
|
97
101
|
|
|
98
|
-
Method-aware code extraction with surrounding class context. Every line returned passes through redaction, so in a file no pattern names a string literal under a secret-named key (8+ characters, unless it is clearly not a credential: a message, URL, path, placeholder, version, date, header, env or parameter name, or a translation) or in a known credential format (vendor tokens, JWTs, a PEM or PGP private key) reads as `[FILTERED]`; code, ENV lookups, ERB and placeholders are never rewritten, so the text stays usable as the text to replace.
|
|
102
|
+
Method-aware code extraction with surrounding class context. Every line returned passes through redaction, so in a file no pattern names a string literal under a secret-named key (8+ characters, unless it is clearly not a credential: a message, URL, path, placeholder, version, date, header, env or parameter name, or a translation) or in a known credential format (vendor tokens, JWTs, a PEM or PGP private key) reads as `[FILTERED]`; code, ENV lookups, ERB and placeholders are never rewritten, so the text stays usable as the text to replace. `near` is looked for in the lines as they are returned, so text only a filtered value holds is not found, and the context line names the enclosing class and method from those lines too.
|
|
99
103
|
|
|
100
104
|
| Parameter | Type | Default | Description |
|
|
101
105
|
|:----------|:-----|:--------|:------------|
|
|
@@ -111,7 +115,7 @@ Method-aware code extraction with surrounding class context. Every line returned
|
|
|
111
115
|
|
|
112
116
|
### `rails_analyze_feature`
|
|
113
117
|
|
|
114
|
-
Full-stack feature analysis: models + controllers + routes + services + admin resources + jobs + views + tests in one call.
|
|
118
|
+
Full-stack feature analysis: models + controllers + routes + services (with the files that call them) + admin resources + jobs and Sidekiq workers + views + tests in one call.
|
|
115
119
|
|
|
116
120
|
| Parameter | Type | Default | Description |
|
|
117
121
|
|:----------|:-----|:--------|:------------|
|
|
@@ -131,7 +135,10 @@ Composite context: schema + model + controller + routes + views for a resource.
|
|
|
131
135
|
|
|
132
136
|
### `rails_onboard`
|
|
133
137
|
|
|
134
|
-
Narrative app walkthrough for getting up to speed.
|
|
138
|
+
Narrative app walkthrough for getting up to speed. The stack line counts the
|
|
139
|
+
tables of every database the app's own schema dumps declare, as
|
|
140
|
+
`rails_get_schema` reads them; the databases Rails 8 gives Solid Queue, Solid
|
|
141
|
+
Cache and Solid Cable get one line, without their tables. It ends with the app's custom
|
|
135
142
|
rake tasks (Rakefile, `lib/tasks`, `rakelib`), each with its arguments,
|
|
136
143
|
description and file: the first 15 at `standard`, every one at `full`.
|
|
137
144
|
Then the app's own generators under `lib/generators` (the `bin/rails generate`
|
|
@@ -197,7 +204,13 @@ a `before_` or `around_` callback with `prepend: true` first. `after_commit`
|
|
|
197
204
|
and `after_rollback` are last declared first unless the app turns on
|
|
198
205
|
`run_after_transaction_callbacks_in_order_defined` (`load_defaults 7.1`). A
|
|
199
206
|
method declared again for one type shows once, with the later declaration's
|
|
200
|
-
conditions, as Rails keeps it.
|
|
207
|
+
conditions, as Rails keeps it. A block or a lambda is listed as
|
|
208
|
+
`[inline_block]`, and `detail:"full"` prints its declaration. turbo-rails'
|
|
209
|
+
`broadcasts`, `broadcasts_to`, `broadcasts_refreshes` and
|
|
210
|
+
`broadcasts_refreshes_to` are listed as the commit callbacks they declare
|
|
211
|
+
(`broadcasts_refreshes (turbo-rails)` under `after_create_commit`,
|
|
212
|
+
`after_update_commit` and `after_destroy_commit`), and `detail:"full"` names
|
|
213
|
+
the method each one runs. The order is read from source in both tiers,
|
|
201
214
|
and dynamic dispatch is not evaluated: a callback registered through
|
|
202
215
|
`send(:before_save, ...)`, through a method called with `send` or defined with
|
|
203
216
|
`define_singleton_method`, or through a module included in `class << self` is
|
|
@@ -218,6 +231,13 @@ that nests its own validator class stays a concern. Every concern is named by
|
|
|
218
231
|
the constant its file declares, so an app inflection (`sdg/tag_list.rb`
|
|
219
232
|
declaring `SDG::TagList`) is answered under the name the app has.
|
|
220
233
|
|
|
234
|
+
The private methods are listed under their own heading, since an includer
|
|
235
|
+
gets them too, and the listing counts them apart from the public ones. The
|
|
236
|
+
macros an `included` block declares are listed, `helper_method` among them. An
|
|
237
|
+
`include` under a condition (`include Pagy::Backend if defined?(Pagy::Backend)`)
|
|
238
|
+
carries the condition; on a booted app it also says when the module is not
|
|
239
|
+
defined, so was never mixed in.
|
|
240
|
+
|
|
221
241
|
| Parameter | Type | Default | Description |
|
|
222
242
|
|:----------|:-----|:--------|:------------|
|
|
223
243
|
| `name` | string | - | Concern name (e.g., `Trackable`, `Admin::ExportControllerConcern`). Omit to list all concerns |
|
|
@@ -234,10 +254,12 @@ declaring `SDG::TagList`) is answered under the name the app has.
|
|
|
234
254
|
|
|
235
255
|
Controller actions with inherited filters, render map, strong params. Includes schema hints for referenced models.
|
|
236
256
|
|
|
257
|
+
Rails counts every public method that a concern or a parent controller gives among a controller's actions. A `page_info` in a concern that `ApplicationController` includes is an action of each controller. An app keeps the helpers its controllers share in such places, so the action lists take one of these methods only where a route names it for that controller, booted or not. An `action` lookup also finds one that no route names, under the rules the lists apply: no callback, no name that starts with `_` or ends in `?` or `!`, and no required argument. The answer reads the method from the file that defines it. It names the parent controller under **Inherited** when the method comes from one, and says under **Not listed** why the lists leave the method out. A private `def` of the same name lower down hides the method, as it does from Rails.
|
|
258
|
+
|
|
237
259
|
| Parameter | Type | Default | Description |
|
|
238
260
|
|:----------|:-----|:--------|:------------|
|
|
239
261
|
| `controller` | string | - | Controller name (e.g., `UsersController`) |
|
|
240
|
-
| `action` | string | - | Specific action; requires `controller
|
|
262
|
+
| `action` | string | - | Specific action; requires `controller`. Also finds one a concern or a parent controller gives that no route names (see above) |
|
|
241
263
|
| `detail` | enum | `standard` | `summary`, `standard`, `full` |
|
|
242
264
|
| `limit` | integer | `50` | Max controllers when listing |
|
|
243
265
|
| `offset` | integer | `0` | Skip this many controllers |
|
|
@@ -253,13 +275,22 @@ spells one out. A mounted Grape API lists its endpoints under the mount (verb,
|
|
|
253
275
|
full path, declared params), read from its classes in `app/api` and `lib/api`;
|
|
254
276
|
the summary counts them.
|
|
255
277
|
|
|
278
|
+
`app_only` leaves out the routes Rails' engines and gems draw on their own:
|
|
279
|
+
Active Storage, Action Mailbox's ingresses and its conductor, Turbo Native's
|
|
280
|
+
navigation routes, and Rails' development pages (`rails/info`, `rails/mailers`,
|
|
281
|
+
`rails/welcome`), as `config.excluded_route_prefixes` lists them. A route the
|
|
282
|
+
app's own route files declare is an app route, so `devise_for :users` and the
|
|
283
|
+
`get "up" => "rails/health#show"` health check are listed. A `controller` filter
|
|
284
|
+
searches every route, so `controller:"devise/sessions"` or
|
|
285
|
+
`controller:"active_storage/blobs"` answers whatever `app_only` says.
|
|
286
|
+
|
|
256
287
|
| Parameter | Type | Default | Description |
|
|
257
288
|
|:----------|:-----|:--------|:------------|
|
|
258
|
-
| `controller` | string | - | Filter by controller |
|
|
289
|
+
| `controller` | string | - | Filter by controller; searches every route, framework ones included |
|
|
259
290
|
| `detail` | enum | `standard` | `summary`, `standard`, `full` |
|
|
260
291
|
| `limit` | integer | auto | Max routes to return; the default depends on `detail` |
|
|
261
292
|
| `offset` | integer | `0` | Skip this many routes |
|
|
262
|
-
| `app_only` | boolean | `true` | Leave out
|
|
293
|
+
| `app_only` | boolean | `true` | Leave out the routes Rails' engines and gems draw on their own (see above) |
|
|
263
294
|
|
|
264
295
|
<p align="right"><a href="#table-of-contents">↑ back to top</a></p>
|
|
265
296
|
|
|
@@ -295,6 +326,15 @@ the naming rule of its directory when one is known: a loader that imports the
|
|
|
295
326
|
directory by a derived path (`import(`./dynamic/${path}.controller.ts`)`), or
|
|
296
327
|
two or more confirmed neighbours that all drop the directory's segment.
|
|
297
328
|
|
|
329
|
+
The change callbacks Stimulus runs itself (`countValueChanged`,
|
|
330
|
+
`itemTargetConnected`, `resultsOutletDisconnected`) are listed with the
|
|
331
|
+
lifecycle hooks, not as actions. The copy-paste markup at `full` detail wires
|
|
332
|
+
each action to the events the app's own templates use (`input->search#queue`),
|
|
333
|
+
and to no named event, so the element's default, when no template wires it; it
|
|
334
|
+
leaves out a method no template names that the controller calls itself, and
|
|
335
|
+
carries a `data-*-class` and `data-*-outlet` attribute for each class and
|
|
336
|
+
outlet the controller reads.
|
|
337
|
+
|
|
298
338
|
| Parameter | Type | Default | Description |
|
|
299
339
|
|:----------|:-----|:--------|:------------|
|
|
300
340
|
| `controller` | string | - | Stimulus controller name |
|
|
@@ -304,7 +344,7 @@ two or more confirmed neighbours that all drop the directory's segment.
|
|
|
304
344
|
|
|
305
345
|
### `rails_get_partial_interface`
|
|
306
346
|
|
|
307
|
-
What locals to pass to a partial and what methods are called on them.
|
|
347
|
+
What locals to pass to a partial and what methods are called on them. Render sites are `render` calls, a `partial:` passed to a Turbo Stream action (`turbo_stream.prepend`) or a broadcast (`broadcast_prepend_to` in a model, controller, job or channel), and jbuilder's `json.partial!` and `json.array! ..., partial:`. A site counts only where Rails would render this file: `_post.html.erb` gets the html, Turbo Stream and broadcast sites and `_post.json.jbuilder` the jbuilder ones, and `render "comments/form"` names comments' form, never the one beside the view. A call that names `formats:` among the options it renders with - beside `partial:` (`render partial: "posts/post", formats: [:json]`), or among `json.partial!`'s keywords - counts for those formats. Rails reads no other key as one: a `format:` is a local, as is any key inside `locals:`, and `render "shared/date", date: d, format: :short` passes every key as a local. In a controller or helper a call inside `format.json { }` counts for json, and any other renders the request's format, so it counts for both. A jbuilder partial's locals are the bare names it reads; a helper, a route helper or a call with parentheses is not a local.
|
|
308
348
|
|
|
309
349
|
| Parameter | Type | Default | Description |
|
|
310
350
|
|:----------|:-----|:--------|:------------|
|
|
@@ -313,12 +353,12 @@ What locals to pass to a partial and what methods are called on them.
|
|
|
313
353
|
|
|
314
354
|
### `rails_get_turbo_map`
|
|
315
355
|
|
|
316
|
-
Turbo Stream broadcast-to-subscription wiring with mismatch warnings.
|
|
356
|
+
Turbo Stream broadcast-to-subscription wiring with mismatch warnings. Streams are compared the way Turbo names them, part by part: a record by its model, a symbol or string by its text. So `broadcast_prepend_to [product, :reviews]` (or `broadcast_prepend_to product, :reviews`) in Review, whose `product` is a belongs_to, reaches `turbo_stream_from @product, :reviews`, and `broadcasts_refreshes` reaches `turbo_stream_from @product` on update and destroy and the model's plural stream on create. In a view, `current_user` and `Current.user` name a User record. A `stream` or `controller` filter narrows which streams are shown, each with every broadcaster and subscriber the app has for it, so a stream heard in another controller's views is not called unheard. A part that names nothing the map can resolve (no model by that name, a method rather than an association) is reported as "can't tell", never as a mismatch.
|
|
317
357
|
|
|
318
358
|
| Parameter | Type | Default | Description |
|
|
319
359
|
|:----------|:-----|:--------|:------------|
|
|
320
360
|
| `detail` | enum | `standard` | `summary`, `standard`, `full` |
|
|
321
|
-
| `stream` | string | - | Filter by stream or channel name (e.g., `notifications`) |
|
|
361
|
+
| `stream` | string | - | Filter by stream or channel name (e.g., `notifications`); a name no broadcast or subscription carries says so and lists the app's streams |
|
|
322
362
|
| `controller` | string | - | Filter by controller name (e.g., `messages`) |
|
|
323
363
|
|
|
324
364
|
### `rails_get_frontend_stack`
|
|
@@ -353,13 +393,19 @@ Test scaffolding that matches your project's patterns (fixtures vs factories, RS
|
|
|
353
393
|
|:----------|:-----|:--------|:------------|
|
|
354
394
|
| `model` | string | - | Model to generate a model test for (e.g., `User`) |
|
|
355
395
|
| `controller` | string | - | Controller to generate a request test for (e.g., `PostsController`) |
|
|
356
|
-
| `file` | string | - | File to generate tests for, relative to Rails root; the type is detected |
|
|
357
|
-
| `type` | enum | `unit` | `unit`, `request`, `system` |
|
|
396
|
+
| `file` | string | - | File to generate tests for, relative to Rails root; the type is detected (a job gets a job test under test/jobs or spec/jobs) |
|
|
397
|
+
| `type` | enum | `unit` | `unit`, `request`, `system`. With `model` or `controller`, `system` writes a system test that visits the subject's index and show pages |
|
|
398
|
+
|
|
399
|
+
When the subject's test file exists already, the cases come anyway, addressed to that file, to add the ones it lacks. A controller behind a login filter gets a test that signs in with the app's own sign-in helper and a users fixture or factory where that helper answers the filter: Rails 8's `sign_in_as` for its `require_authentication`, or a `sign_in` for Devise's `authenticate_<scope>!`. Any other filter (a token check, a hand-written one) and any `ActionController::API` controller gets a TODO instead, naming the filter and saying the requests are redirected or refused until the test signs in. Devise and Doorkeeper keep their own sign-in lines.
|
|
358
400
|
|
|
359
401
|
### `rails_validate`
|
|
360
402
|
|
|
361
403
|
Syntax + semantic + Brakeman security validation in one call.
|
|
362
404
|
|
|
405
|
+
An ERB template is parsed as the method body Rails compiles it into, with the trim tags `<%-` and `-%>` read as tags, as Rails reads them.
|
|
406
|
+
|
|
407
|
+
JavaScript (`.js`, `.mjs`, `.cjs`) is parsed by `node --check` as the module system Node would load it with: the extension, else the nearest `package.json`'s `type`, else a module when CommonJS rejects only its `import`/`export` (a Stimulus controller). A failure names the line and column node reports. A file node cannot judge is listed as skipped with the reason, not as passed, so the pre-commit hook never stops a commit for syntax the app's own toolchain compiles: JSX or a decorator (`@observable`, `export @controller class`), named by the line node stopped at; a file that declares Flow (`// @flow`), or one Babel compiles - a `babel.config.*` or `.babelrc*` file, or a `"babel"` key in `package.json`, in its folder or one above it, up to the top of the app's repository - where node's error is named beside the skip, since it may be the toolchain's syntax or the file's own mistake; or no `node` on the PATH, where only an unmatched bracket still fails. Type annotations in a `.js` file nothing compiles still fail, as TypeScript and esbuild refuse them there too.
|
|
408
|
+
|
|
363
409
|
| Parameter | Type | Default | Description |
|
|
364
410
|
|:----------|:-----|:--------|:------------|
|
|
365
411
|
| `files` | array | required | File paths to validate |
|
|
@@ -378,13 +424,35 @@ Brakeman static analysis: SQL injection, XSS, mass assignment, command injection
|
|
|
378
424
|
|
|
379
425
|
> Requires the `brakeman` gem. When the app's bundle does not carry it but the
|
|
380
426
|
> machine has it, the scan runs brakeman as its own process from outside the
|
|
381
|
-
> bundle and says so
|
|
382
|
-
> brakeman is
|
|
427
|
+
> bundle and says so: the installed gem's own `bin/brakeman`, run by the app's
|
|
428
|
+
> Ruby, not whatever `brakeman` is on PATH. When no scan can run, the answer
|
|
429
|
+
> says which case it is: brakeman is nowhere on the machine, or it is
|
|
430
|
+
> installed and produced no report.
|
|
383
431
|
|
|
384
432
|
### `rails_performance_check`
|
|
385
433
|
|
|
386
434
|
N+1 query risks, missing indexes, missing counter_cache, eager load candidates.
|
|
387
435
|
|
|
436
|
+
An N+1 risk is an association call on a record that a loop hands out. Each
|
|
437
|
+
action's body and the before filters that run for it say what its instance
|
|
438
|
+
variables hold (`@reviews = @product.reviews.latest`, `@orders =
|
|
439
|
+
current_user.orders`), and its templates and the partials they render say what
|
|
440
|
+
walks them: `@reviews.each do |review|`, `render @reviews`, `render partial:,
|
|
441
|
+
collection:`, `json.array!`, a loop record passed to a partial as a local, or a
|
|
442
|
+
loop in a partial rendered with no locals (`render "dashboard/list"`).
|
|
443
|
+
A `review.user` read there is a risk; the same name anywhere else is not. The
|
|
444
|
+
row names the action and the view the call sits in. `high` means the query
|
|
445
|
+
preloads nothing, `medium` that it preloads other associations, and `low` that
|
|
446
|
+
it preloads this one; a named scope in the query counts with its body, so
|
|
447
|
+
`Post.with_author` preloads what `scope :with_author, -> { includes(:user) }`
|
|
448
|
+
does. `.count` on an association runs a query per record even when preloaded,
|
|
449
|
+
so it stays `high`. `.size`, `.any?`, `.empty?` and `.none?` on a has_many
|
|
450
|
+
whose other side keeps a `counter_cache` read the counter column, so they are
|
|
451
|
+
not a risk. The inverse Rails sets on each record of
|
|
452
|
+
`@product.reviews` (`review.product`) is not a risk, and a branch on a local
|
|
453
|
+
the render passes as a literal (`show_seller: false`) is not read. ERB and
|
|
454
|
+
jbuilder are read; Haml and Slim templates are not, and neither are serializers.
|
|
455
|
+
|
|
388
456
|
| Parameter | Type | Default | Description |
|
|
389
457
|
|:----------|:-----|:--------|:------------|
|
|
390
458
|
| `model` | string | - | Filter results to one model |
|
|
@@ -431,7 +499,7 @@ found under a load-order prefix too (`config/initializers/3_omniauth.rb`).
|
|
|
431
499
|
|
|
432
500
|
### `rails_get_env`
|
|
433
501
|
|
|
434
|
-
Environment variables + credentials keys (values are never exposed). Scans `.rb`, `.rake`, ERB views and config YAML under `app`, `config` and `lib`, plus `config.ru`, `db/seeds.rb`, `db/seeds/` and the Ruby scripts in `bin
|
|
502
|
+
Environment variables + credentials keys (values are never exposed). Scans `.rb`, `.rake`, ERB views and config YAML under `app`, `config` and `lib`, plus `config.ru`, `db/seeds.rb`, `db/seeds/` and the Ruby scripts in `bin/`. A tag on a YAML comment line is not read: ERB runs it, but what it writes goes with the comment. Config YAML matching `sensitive_patterns` (`config/database.yml`, `config/cable.yml`, `config/storage.yml` and the rest) is read only for the ENV names in its ERB tags, so a default there is listed as present and not read; credentials and keys are never read, and the answer says so. A default is read from a second argument or from a block whose one statement is a literal (`ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" }`). The env Kamal's `config/deploy.yml` gives the app container is listed too: secret names only, and clear values, except one holding a URL or a key-like token, or under a secret-named variable, which shows as hidden, and one an ERB tag sets, which says so. So are the setting keys in the config gem's `config/settings.yml` and `config/settings/<env>.yml` when the bundle has the config gem, and the attributes of each `Anyway::Config` class in `config/configs` or `app/configs` with the env name it reads (`PAYMENT_API_KEY`), never their values. A variable whose call sites pass different defaults is labelled as such rather than with one site's default; `detail:"full"` names each site's. A default is redacted by the rule a source literal gets: a credential format under any name, a URL's password, and under a secret-named variable a value that is not clearly something else, so an address, URL or hostname default prints as written.
|
|
435
503
|
|
|
436
504
|
| Parameter | Type | Default | Description |
|
|
437
505
|
|:----------|:-----|:--------|:------------|
|
|
@@ -589,21 +657,40 @@ Model association graph in Mermaid or text format.
|
|
|
589
657
|
| `model` | string | - | Center the graph on this model |
|
|
590
658
|
| `depth` | integer | `2` | Hops from the centre model (1-3) |
|
|
591
659
|
| `format` | enum | `mermaid` | `mermaid`, `text` |
|
|
592
|
-
| `show_cycles` | boolean | `false` |
|
|
660
|
+
| `show_cycles` | boolean | `false` | List circular dependencies: chains of foreign keys that lead back to the model they start from, through another |
|
|
593
661
|
| `show_sti` | boolean | `false` | Show Single Table Inheritance hierarchies |
|
|
594
662
|
|
|
595
663
|
The graph is capped at 50 nodes, with or without `model`, and says so when it cuts.
|
|
596
664
|
|
|
665
|
+
A polymorphic `belongs_to` lists the models that implement it, the ones that declare `as:` for it. A cycle follows foreign keys, one edge per key from the table that holds it: a `belongs_to` and the `has_many` or `has_one` at its other end are one key, so two models joined both ways are not a cycle, and neither is a key to the model's own table. With `show_cycles` and no cycle, the answer says none.
|
|
666
|
+
|
|
597
667
|
### `rails_migration_advisor`
|
|
598
668
|
|
|
599
669
|
Migration code generation with duplicate/nonexistent column warnings, reversibility flags, table name normalization. The generated class is stamped with the app's own Rails version (from the booted app, or from `Gemfile.lock` under `--no-boot`); when neither names one, the output says which version it fell back to.
|
|
600
670
|
|
|
671
|
+
Before the code it warns about what the app's migrations already hold: a
|
|
672
|
+
file already named what the generator would name this one (`rails generate
|
|
673
|
+
migration` refuses that name), and a migration not yet run that already adds
|
|
674
|
+
the column or creates the table when it runs (a `def down`, a `dir.down` or a
|
|
675
|
+
`revert` block only undoes, so what it adds does not count). It also warns
|
|
676
|
+
about a `create_table` for a
|
|
677
|
+
table that exists and a `null: false` column with no default on a table with
|
|
678
|
+
rows (the row count is the database's estimate), and refuses a column type no
|
|
679
|
+
adapter knows. For `remove_column`, `rename_column` and `change_type` it lists
|
|
680
|
+
the lines in `app/`, `lib/` and `config/` that name the column, the code the
|
|
681
|
+
change breaks.
|
|
682
|
+
|
|
683
|
+
It refuses a `table`, `type` or `new_name` longer than 256 characters, a
|
|
684
|
+
`column` that long outside `create_table`, and `options` longer than 1,024
|
|
685
|
+
characters: no migration takes one, and the code it writes would carry it
|
|
686
|
+
whole.
|
|
687
|
+
|
|
601
688
|
| Parameter | Type | Default | Description |
|
|
602
689
|
|:----------|:-----|:--------|:------------|
|
|
603
690
|
| `action` | enum | *required* | `add_column`, `remove_column`, `rename_column`, `add_index`, `add_association`, `change_type`, `create_table` |
|
|
604
691
|
| `table` | string | *required* | Table name |
|
|
605
|
-
| `column` | string | - | Column name |
|
|
606
|
-
| `type` | string | - | Column type (e.g., `string`, `integer`, `references`) |
|
|
692
|
+
| `column` | string | - | Column name; for `add_association`, the reference (`user`, `parent`) |
|
|
693
|
+
| `type` | string | - | Column type (e.g., `string`, `integer`, `references`); for `add_association` with `column`, the table the reference points at. A `parent` reference with no `parents` table points at its own table |
|
|
607
694
|
| `new_name` | string | - | New column name, for `rename_column` only |
|
|
608
695
|
| `options` | string | - | Extra options (e.g., `null: false, default: 0`) |
|
|
609
696
|
|
|
@@ -620,7 +707,7 @@ Bundled topic index with weighted keyword search. Optional on-demand GitHub fetc
|
|
|
620
707
|
|
|
621
708
|
### `rails_query`
|
|
622
709
|
|
|
623
|
-
Safe read-only SQL with
|
|
710
|
+
Safe read-only SQL with layered security: regex validation, a PostgreSQL plan check (whole-row leaks, expanded sensitive columns and VOLATILE admin functions refused before anything runs), `SET TRANSACTION READ ONLY`, timeout, and column redaction. [Learn about the security model →](SECURITY.md)
|
|
624
711
|
|
|
625
712
|
| Parameter | Type | Default | Description |
|
|
626
713
|
|:----------|:-----|:--------|:------------|
|
|
@@ -631,25 +718,33 @@ Safe read-only SQL with 4-layer security: regex validation, `SET TRANSACTION REA
|
|
|
631
718
|
|
|
632
719
|
> Disabled in production by default.
|
|
633
720
|
|
|
721
|
+
> Name the columns you need. A query that references a sensitive column (directly, or through an alias or expression), serialises a whole row, carries a column-alias list that renames a wildcard (a `VALUES`-body list such as `(VALUES (1,'a')) AS v(id, name)` is allowed - it renames no table column), uses UNION, or calls a session-effecting function is refused with a message that says why. `SELECT *` is allowed and its sensitive columns come back `[FILTERED]`. `SHOW CREATE TABLE` / `SHOW CREATE VIEW` are allowed and their definition text is secret-redacted; other `SHOW CREATE` forms are refused. In the default format a definition is shown whole, as the DDL in a fenced code block with its line breaks kept (a view's character set and collation follow as `name: value` lines); `csv` returns it as one quoted field. When the row limit holds rows back - only when the query really returned more than the cap - the answer says so in every format.
|
|
722
|
+
|
|
723
|
+
> **Cell rendering by format.** A `markdown` table cell shows `NULL` as `_NULL_`, escapes a pipe (`a\|b`) so it cannot end the cell, renders a line break as the two characters `\n` so it cannot end the row, and shortens a value over 100 characters to `…` (a `SHOW CREATE` definition is not a table cell, so it is never shortened). A `csv` cell is the value itself: `NULL` is an empty field, nothing is escaped beyond RFC 4180 quoting (a field holding a comma, a quote or a line break is wrapped in quotes with its quotes doubled), and nothing is truncated - so a line break stays a real line break inside the quoted field. Binary (non-text) data shows as `[BLOB]` in both.
|
|
724
|
+
|
|
634
725
|
### `rails_read_logs`
|
|
635
726
|
|
|
636
727
|
Reverse file tail with level filtering and sensitive data redaction.
|
|
637
728
|
|
|
638
729
|
| Parameter | Type | Default | Description |
|
|
639
730
|
|:----------|:-----|:--------|:------------|
|
|
640
|
-
| `file` | string | current environment's log | Log file name in `log/` (e.g., `production`, `sidekiq`); the `.log` suffix is optional |
|
|
641
|
-
| `lines` | integer | `50` | Number of lines, max 500 |
|
|
731
|
+
| `file` | string | current environment's log | Log file name in `log/` (e.g., `production`, `sidekiq`); the `.log` suffix is optional. A log that links out of the app is refused and not listed |
|
|
732
|
+
| `lines` | integer | `50` | Number of lines, max 500. With `search`, the number of matching lines to show |
|
|
642
733
|
| `level` | enum | `all` | Minimum level: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, `all` |
|
|
643
|
-
| `search` | string | - | Keep lines matching this term. The match runs on the redacted line, so a redacted value cannot be searched for |
|
|
734
|
+
| `search` | string | - | Keep lines matching this term, searched for in the last 4 MB of the file (about 50,000 lines of a typical Rails log); the answer says how many lines that was, and whether older ones were left unsearched. The match runs on the redacted line, so a redacted value cannot be searched for |
|
|
644
735
|
|
|
645
736
|
### `rails_diagnose`
|
|
646
737
|
|
|
647
738
|
One-call error diagnosis with classification, context, recent git changes, and log correlation. It does not call a method undefined when the model's method list could be missing one - a concern's, a parent's, or anything past the payload's own cap.
|
|
648
739
|
|
|
740
|
+
A `NoMethodError` is a nil reference only when the receiver is nil; on any other receiver it is an undefined method, and on a booted app the answer names the receiver's closest method (`totl` → `total`). An error it has no rule for names the gem or app file that defines the exception class. The error can be pasted as Ruby prints it (`NoMethodError: ...`) or as a Rails log writes it (`NoMethodError (...)`).
|
|
741
|
+
|
|
742
|
+
Log correlation reads the last megabyte of the current environment's log, the window `rails_read_logs` reads, and shows the latest entry for the error: the request that raised it (path, controller action, parameters, status, request id when the log is tagged), the error line and the first backtrace frames, redacted. It says so when the log holds no entry for the error.
|
|
743
|
+
|
|
649
744
|
| Parameter | Type | Default | Description |
|
|
650
745
|
|:----------|:-----|:--------|:------------|
|
|
651
746
|
| `error` | string | *required* | Error message or class |
|
|
652
|
-
| `file` | string | - | File where error occurred |
|
|
747
|
+
| `file` | string | - | File where error occurred. A path refused on policy is named in the answer, which still diagnoses the error and comes back as an error result ([how a refusal is reported](SECURITY.md#how-a-refusal-is-reported)) |
|
|
653
748
|
| `line` | integer | - | Line number |
|
|
654
749
|
| `action` | string | - | `controller#action` (e.g., `posts#create`); adds that action's context |
|
|
655
750
|
|
|
@@ -657,15 +752,25 @@ One-call error diagnosis with classification, context, recent git changes, and l
|
|
|
657
752
|
|
|
658
753
|
PR/commit review with per-file context and warnings.
|
|
659
754
|
|
|
755
|
+
A removed validation is a `validates` line the diff takes out when no line it adds validates the same attribute; an edited `validates` line is not one.
|
|
756
|
+
|
|
757
|
+
A changed migration lists the tables it affects: the table each schema method takes first (`add_column`, `remove_columns`, `rename_index`, `add_foreign_key`, ...), every table `drop_table` drops, a renamed table's new name, and for `create_join_table` and `drop_join_table` the join table, named as Rails names it, and the two tables it joins. The first two are shown with the columns the schema has now.
|
|
758
|
+
|
|
759
|
+
An app in a subfolder of its repository (a monorepo's `backend/`) is reviewed on its own: its changed files, named from the app root as `rails_validate` takes them, and the commits that touched it.
|
|
760
|
+
|
|
761
|
+
Each diff passes through the redaction every tool's source does, read as the file it comes from, so a secret a change adds or takes out reads `[FILTERED]`, in a hunk header's line too. A changed file on `sensitive_patterns` (`config/database.yml`, `.env`, a key) is listed with its line counts and none of its lines.
|
|
762
|
+
|
|
660
763
|
| Parameter | Type | Default | Description |
|
|
661
764
|
|:----------|:-----|:--------|:------------|
|
|
662
|
-
| `ref` | string | `HEAD` | Git ref to diff against. `HEAD` reviews uncommitted changes |
|
|
765
|
+
| `ref` | string | `HEAD` | Git ref to diff against: a branch, a tag, `HEAD~3` or a commit SHA. `HEAD` reviews uncommitted changes. Any other ref reviews the commits since HEAD left it, as a pull request does: the file list and each file's diff run from the commit the two last shared to HEAD. A ref that names no commit is refused |
|
|
663
766
|
| `files` | array | - | Review only these changed files (omit for all) |
|
|
664
767
|
|
|
665
768
|
### `rails_runtime_info`
|
|
666
769
|
|
|
667
770
|
Live database pool stats, table sizes, pending migrations, cache stats, queue depth.
|
|
668
771
|
|
|
772
|
+
The pool and the cache are read inside the MCP server process, and the answer says so: the pool's counts are that process's own connections, and a `MemoryStore`'s entries are its own, since each app server process holds its own. A store the processes share (Redis, Memcached, Solid Cache, a file store) is the app's. Another environment that configures a shared store is named, as where the app's cache lives there.
|
|
773
|
+
|
|
669
774
|
| Parameter | Type | Default | Description |
|
|
670
775
|
|:----------|:-----|:--------|:------------|
|
|
671
776
|
| `detail` | enum | `standard` | `summary`, `standard`, `full` |
|
|
@@ -673,12 +778,14 @@ Live database pool stats, table sizes, pending migrations, cache stats, queue de
|
|
|
673
778
|
|
|
674
779
|
### `rails_session_context`
|
|
675
780
|
|
|
676
|
-
Session-aware context tracking across tool calls within a conversation. Pass `action` or `mark`; a call with neither is an error.
|
|
781
|
+
Session-aware context tracking across tool calls within a conversation. Pass `action` or `mark`; a call with neither is an error. A conversation's record keeps its latest 200 queries, and `status` and `summary` say how many older ones it dropped; a param longer than 80 characters is kept as its first 80 and its length.
|
|
677
782
|
|
|
678
783
|
| Parameter | Type | Default | Description |
|
|
679
784
|
|:----------|:-----|:--------|:------------|
|
|
680
|
-
| `action` | enum | - | `status` (queried tools with timestamps), `summary` (short recap), `reset` (clear
|
|
681
|
-
| `mark` | string | - | Record a tool and its params as already queried (e.g., `get_schema:users`) |
|
|
785
|
+
| `action` | enum | - | `status` (queried tools with timestamps), `summary` (short recap), `reset` (clear this conversation's record) |
|
|
786
|
+
| `mark` | string | - | Record a tool and its params as already queried (e.g., `get_schema:users`). The tool must be one the server has; a short name such as `schema` is taken |
|
|
787
|
+
|
|
788
|
+
Each conversation has its own record: over HTTP, one server process serves every client, and each client's `Mcp-Session-Id` keeps its calls, and its `reset`, to itself. A tool that answers by calling others (`rails_get_context`, `rails_diagnose`, `rails_review_changes`) is recorded as the one call the client made.
|
|
682
789
|
|
|
683
790
|
<p align="right"><a href="#table-of-contents">↑ back to top</a></p>
|
|
684
791
|
|
|
@@ -686,7 +793,7 @@ Session-aware context tracking across tool calls within a conversation. Pass `ac
|
|
|
686
793
|
|
|
687
794
|
## Live Resources (VFS)
|
|
688
795
|
|
|
689
|
-
In addition to tools, AI clients can read structured data through **resource templates** - `rails-ai-context://` URIs introspected
|
|
796
|
+
In addition to tools, AI clients can read structured data through **resource templates** - `rails-ai-context://` URIs introspected afresh on every request, after the app's code is reloaded for any edit.
|
|
690
797
|
|
|
691
798
|
| URI Pattern | Returns |
|
|
692
799
|
|:------------|:--------|
|
|
@@ -698,6 +805,10 @@ In addition to tools, AI clients can read structured data through **resource tem
|
|
|
698
805
|
|
|
699
806
|
The legacy `rails://models/{name}` form is still accepted.
|
|
700
807
|
|
|
808
|
+
`controllers/{name}/{action}` also reads an action that a concern or a parent controller gives and no route names, as the `rails_get_controllers` `action` lookup does. `inherited_from` names the parent controller an action comes from. `note` says why the controller's `actions` leave out one that no route names. For a controller that cannot load - its file, or one its class body needs, does not compile - both reads give its `error` and `file` and nothing read from its source.
|
|
809
|
+
|
|
810
|
+
A read for a name the app does not have - a model, a controller, an action, a view - fails with a JSON-RPC `-32602` error whose message gives the reason and whose `data.available` lists the names there are: for a view, the ones in its directory, else the app's first twenty. So does a view over the size cap, and a URI the server does not serve, whose `data.available` lists the resources and templates it does. A path refused on policy fails as `Resource not found`, without saying why.
|
|
811
|
+
|
|
701
812
|
Plus 9 static resources (schema, routes, conventions, gems, controllers, config, tests, migrations, engines).
|
|
702
813
|
|
|
703
814
|
<p align="right"><a href="#table-of-contents">↑ back to top</a></p>
|
data/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
> [!TIP]
|
|
14
|
-
> Always start with `rails ai:doctor`. It
|
|
14
|
+
> Always start with `rails ai:doctor`. It checks the app, its databases, the MCP configs and the context files, and catches most issues automatically.
|
|
15
15
|
|
|
16
16
|
## Diagnostics first
|
|
17
17
|
|
|
@@ -20,7 +20,7 @@ rails ai:doctor # In-Gemfile
|
|
|
20
20
|
rails-ai-context doctor # Standalone
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This runs
|
|
23
|
+
This runs every check that applies to the app ([the list](GUIDE.md#diagnostics)) and returns an AI readiness score (0-100). Each check that fails or warns includes a fix suggestion.
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -70,9 +70,10 @@ ls -la .mcp.json .cursor/ .vscode/ .github/
|
|
|
70
70
|
- OpenCode: `opencode.json`
|
|
71
71
|
- Codex: `.codex/config.toml`
|
|
72
72
|
|
|
73
|
-
2. Verify the command works:
|
|
73
|
+
2. Verify the command the config holds works:
|
|
74
74
|
```bash
|
|
75
|
-
bundle exec rails
|
|
75
|
+
bundle exec rails-ai-context serve # in-Gemfile
|
|
76
|
+
rails-ai-context serve # standalone
|
|
76
77
|
# Should output nothing (waiting for stdio input)
|
|
77
78
|
# Ctrl+C to stop
|
|
78
79
|
```
|
|
@@ -82,18 +83,66 @@ ls -la .mcp.json .cursor/ .vscode/ .github/
|
|
|
82
83
|
rails generate rails_ai_context:install
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
### "Could not write .vscode/mcp.json - that tool will not auto-discover the MCP server"
|
|
87
|
+
|
|
88
|
+
The install never replaces a config it cannot write back faithfully: one that does not parse as JSON, one holding comments that writing it back would drop, or one that is not UTF-8. Trailing commas, which VS Code and OpenCode accept, are read the way they read them under json 2.9 or later; an older json refuses them, as JSON does. The line after the warning says which, and ends with the exact entry to add, as `Add {"servers":{...}} to it by hand`. Add that entry yourself, or remove the comments (and, under an older json, the trailing commas) and run the install again. `doctor` names the same entry for a file whose comments keep the install out, rather than sending you back to the install, and asks for one that is not UTF-8 to be saved as UTF-8 first.
|
|
89
|
+
|
|
90
|
+
Claude Code reads `.mcp.json` with a plain JSON parser, and loads no server from a file holding a comment or a trailing comma ("MCP config is not a valid JSON"); Cursor's `.cursor/mcp.json` is held to plain JSON too. Either one is reported as `Claude Code cannot read it: it holds a trailing comma, which JSON does not allow`, by the install and by `doctor`, however current the entry in it. Make the file plain JSON and run the install again. `doctor` reports one that does not parse, or holds no object to merge into, a workspace's above the app included. Dropping an AI tool leaves such a file alone the same way and names the entries to remove.
|
|
91
|
+
|
|
92
|
+
### Codex: "TOML parse error ... duplicate key"
|
|
93
|
+
|
|
94
|
+
Codex refuses a `config.toml` that declares a server twice. An earlier install could write a second `[mcp_servers.rails-ai-context]` table beside the server spelled another way: a quoted name (`[mcp_servers."rails-ai-context"]`), an inline table under `[mcp_servers]`, or dotted keys. `doctor` names such a file. Two tables are merged into one when the install runs again; for an inline table or dotted keys, delete one of the two by hand.
|
|
95
|
+
|
|
96
|
+
The install now finds the server however its name is quoted and replaces its table where it stands. An entry of the gem's written as an inline table or with dotted keys is left as it is, with a warning, as is a file whose `mcp_servers` is one inline table: write it as a `[mcp_servers.rails-ai-context]` table, or delete it, and the next run keeps it current. `doctor` gives that same edit for any problem in such an entry that only a rewrite fixes, a stale env snapshot included.
|
|
97
|
+
|
|
98
|
+
### Codex: "stream did not contain valid UTF-8"
|
|
99
|
+
|
|
100
|
+
Codex reads `config.toml` as TOML, which is UTF-8, and loads nothing from a file holding a byte that is not - one in a comment included. The install still writes its table into such a file, keeping every byte around it, and says `Codex CLI cannot read it: it is not UTF-8, which TOML is`; `doctor` fails it in the same words. Save the file as UTF-8.
|
|
101
|
+
|
|
85
102
|
### "MCP server fails to start"
|
|
86
103
|
|
|
104
|
+
`rails ai:doctor` (or `rails-ai-context doctor`) checks the command each config holds and says which one cannot start and why. The usual causes:
|
|
105
|
+
|
|
106
|
+
- `rails-ai-context is not currently included in the bundle`: the config runs `bundle exec rails-ai-context serve`, but the gem left the app's Gemfile. A standalone install runs `rails-ai-context serve`: run `rails-ai-context init` to rewrite the configs.
|
|
107
|
+
- The config runs `rails-ai-context serve` while the app's Gemfile.lock carries the gem: it needs a copy installed outside the bundle as well, which hands the run to the bundle's. Run `rails generate rails_ai_context:install` to write `bundle exec rails-ai-context serve`.
|
|
108
|
+
- `can't find executable rails-ai-context for gem rails-ai-context`: the bundle's copy of the gem lists no executable, as a `path:` copy of 5.32.2 or earlier outside a git checkout does. Point the Gemfile at a git checkout or a release.
|
|
109
|
+
- On Windows, a config that runs `bundle` or `rails-ai-context` itself rather than through `cmd /c`: those are batch files there, which Claude Code, Codex and the other clients that start a server without a shell do not run (`doctor` warns about it). Run the install again; on Windows it writes `cmd /c bundle exec rails-ai-context serve`.
|
|
110
|
+
- On macOS or Linux, a config that runs `cmd /c ...`: it was written on Windows, and there is no `cmd` here (`doctor` fails it). Run the install again on this machine, and keep the MCP configs out of git if the team works on both.
|
|
111
|
+
|
|
87
112
|
Check for Bundler/Ruby path issues:
|
|
88
113
|
|
|
89
114
|
```bash
|
|
90
115
|
which bundle
|
|
91
|
-
which rails
|
|
116
|
+
which rails-ai-context
|
|
92
117
|
ruby -v
|
|
93
118
|
```
|
|
94
119
|
|
|
95
120
|
For Codex CLI specifically, the env section in `.codex/config.toml` must match your current Ruby environment. Re-run install if you changed Ruby versions.
|
|
96
121
|
|
|
122
|
+
### Answers say "Boot did not finish within 20s"
|
|
123
|
+
|
|
124
|
+
The app's boot hung, usually an initializer waiting on a service that is
|
|
125
|
+
down (Redis, Elasticsearch, a secrets store). A stdio server gives the boot 20
|
|
126
|
+
seconds, because its client stops waiting for the first answer after about 30
|
|
127
|
+
(Claude Code, the MCP Inspector), and then serves the static tier; every other
|
|
128
|
+
command gives it 60. Start the service, or, for an app that is healthy but
|
|
129
|
+
slow to boot, raise both limits in the server's MCP entry: the boot's with
|
|
130
|
+
`RAILS_AI_CONTEXT_BOOT_TIMEOUT` (seconds) in its `env`, and the client's with
|
|
131
|
+
its own setting (Claude Code reads `MCP_TIMEOUT`, in milliseconds). Raising
|
|
132
|
+
only the boot's leaves a client that gives up first. `rails ai:serve` has no
|
|
133
|
+
static tier, so it exits with the same message instead.
|
|
134
|
+
|
|
135
|
+
### A server set up for a folder of apps fails to start
|
|
136
|
+
|
|
137
|
+
`rails-ai-context init` run in a folder of apps writes entries that name each app relative to that folder (`--app-path shop`, and for an in-Gemfile app `BUNDLE_GEMFILE=shop/Gemfile`, or the monorepo's shared Gemfile its `config/boot.rb` names). Claude Code, Codex and OpenCode start a server in the folder they were launched in, so:
|
|
138
|
+
|
|
139
|
+
1. Start the AI tool in the folder of apps, not inside one of them. Launched inside an app, the tool still reads the folder's config above it, and the server says `--app-path shop is read from ...; it names an app from ...`. An in-Gemfile entry fails earlier, in Bundler, with a Gemfile it cannot find. To work in one app on its own, run `rails-ai-context init` inside it too.
|
|
140
|
+
2. Check the Ruby. Every server in the folder runs under the Ruby your version manager picks in the folder, and `init` warns about an app that declares another. `Your Ruby version is ..., but your Gemfile specified ...` from an in-Gemfile entry is that case: install the app's bundle for that Ruby, or give the app its own config with `init` inside it.
|
|
141
|
+
|
|
142
|
+
### "No Rails app found in ..., and 2 below it"
|
|
143
|
+
|
|
144
|
+
You ran a command in a folder that holds several apps. The error lists the exact command for each, with `--app-path`. To set them all up at once, run `rails-ai-context init` in that folder.
|
|
145
|
+
|
|
97
146
|
### "Tools return empty results"
|
|
98
147
|
|
|
99
148
|
1. Check that your Rails app boots: `rails runner "puts 'ok'"`
|
|
@@ -110,7 +159,8 @@ dependency graph, or launching from inside another Bundler project's
|
|
|
110
159
|
directory. Fixes:
|
|
111
160
|
|
|
112
161
|
1. Launch `rails-ai-context serve` with the app root as the working directory
|
|
113
|
-
(the generated MCP configs do this by default
|
|
162
|
+
(the generated MCP configs do this by default; a folder of apps' configs
|
|
163
|
+
start in that folder and name the app with `--app-path`).
|
|
114
164
|
2. Make sure `GEM_PATH` covers the complete default gem path
|
|
115
165
|
(`gem env path`), not just `gem env gemdir` - Ruby's bundled gems
|
|
116
166
|
(racc, etc.) live in a separate directory.
|
|
@@ -120,10 +170,29 @@ directory. Fixes:
|
|
|
120
170
|
### MCP server responds slowly
|
|
121
171
|
|
|
122
172
|
- Check `config.cache_ttl` - lower values mean more frequent re-introspection
|
|
173
|
+
- Without the `listen` gem, each call first checks the app's files for edits, about 100 ms at 10,000 files; adding `listen` to the development group replaces that check with live reload
|
|
123
174
|
- Check `config.preset` - `:standard` is faster than `:full`
|
|
124
175
|
- Large schema files (>10MB) slow down schema introspection
|
|
125
176
|
- Run `rails ai:doctor` - it checks schema size and view count
|
|
126
177
|
|
|
178
|
+
### The stdio server stops with "MCP stdio connection closed: stdio frame exceeds 4194304 bytes"
|
|
179
|
+
|
|
180
|
+
The MCP SDK reads one JSON-RPC message per line and refuses a line of 4 MiB or more. The server answers the client with a JSON-RPC `-32600` error, prints the reason on stderr and exits 1. The message is too large for any client to need; look for a tool argument the client filled with a whole file.
|
|
181
|
+
|
|
182
|
+
### HTTP answers an HTML error page while a migration is pending
|
|
183
|
+
|
|
184
|
+
In development Rails answers every request with its pending-migration page, and the mounted engine is a route like any other. Run the migration, or use `auto_mount`, stdio or the standalone server, which all answer ahead of that check; `rails_runtime_info` lists the pending migrations.
|
|
185
|
+
|
|
186
|
+
### HTTP answers 403 "Forbidden: Invalid Host header"
|
|
187
|
+
|
|
188
|
+
The standalone server (`serve --transport http`, `rails ai:serve_http`) answers only requests whose `Host` header is `127.0.0.1`, `::1` or `localhost`, as the MCP SDK does, to stop DNS rebinding. A client that reaches it by the machine's IP or hostname is refused, whatever `http_bind` says. Connect to `localhost`: through a published container port, or an SSH tunnel (`ssh -L 6029:localhost:6029 your-host`).
|
|
189
|
+
|
|
190
|
+
The mounted engine and `auto_mount` answer the hosts the app's `config.hosts` allows, as the rest of the app does. Add the name the client uses to `config.hosts`, the way you would for any other route.
|
|
191
|
+
|
|
192
|
+
### HTTP answers 403 "does not serve MCP over HTTP in production"
|
|
193
|
+
|
|
194
|
+
The mounted engine and `auto_mount` refuse every request outside development and test (production, staging) unless the app sets `config.allow_http_in_production = true`. Set it only with the endpoint behind your app's authentication; see [Security](SECURITY.md#mcp-http-transport).
|
|
195
|
+
|
|
127
196
|
---
|
|
128
197
|
|
|
129
198
|
## Context file issues
|
|
@@ -151,7 +220,11 @@ rails ai:context
|
|
|
151
220
|
|
|
152
221
|
### "My custom content in CLAUDE.md was overwritten"
|
|
153
222
|
|
|
154
|
-
The gem
|
|
223
|
+
The gem writes its section between `<!-- BEGIN rails-ai-context -->` and `<!-- END rails-ai-context -->` and replaces only what lies between them, so text above and below the markers survives every run; text inside them does not. A file with no markers at all gets the section added at the top, and its own text kept below.
|
|
224
|
+
|
|
225
|
+
The per-directory `app/models/AGENTS.md` and `app/controllers/AGENTS.md` that OpenCode and Codex read follow the same rule. One the gem generated whole carries `> Auto-generated by rails-ai-context. Do not edit manually.` and is rewritten whole; one you wrote yourself keeps its text, and the listing goes between the markers above it.
|
|
226
|
+
|
|
227
|
+
If one marker goes missing, or the file holds a second pair, nothing says where the gem's section ends. The run then leaves the file as it is and reports it as `unpaired rails-ai-context marker, left as it is`. Put the lost marker back where the gem's section ends, or delete the leftover section with its markers, and the next run updates the file again.
|
|
155
228
|
|
|
156
229
|
### "Generated files are too large"
|
|
157
230
|
|
|
@@ -218,6 +291,9 @@ brew install ripgrep
|
|
|
218
291
|
|
|
219
292
|
# Ubuntu
|
|
220
293
|
apt install ripgrep
|
|
294
|
+
|
|
295
|
+
# Windows
|
|
296
|
+
winget install BurntSushi.ripgrep.MSVC
|
|
221
297
|
```
|
|
222
298
|
|
|
223
299
|
The gem falls back to Ruby regex if ripgrep isn't available. Search still works, just slower on large codebases.
|
|
@@ -238,7 +314,7 @@ Standalone mode boots the app with a small shim, then restores the `$LOAD_PATH`
|
|
|
238
314
|
|
|
239
315
|
The file is read from the app root, once, at boot, and an initializer does not replace it: a `configure` block wins only the keys it assigns, key by key ([Precedence](CONFIGURATION.md#precedence)). In standalone mode the initializer contributes nothing at all, since the gem is not loaded while `config/initializers` runs. Check:
|
|
240
316
|
|
|
241
|
-
1. You are
|
|
317
|
+
1. You are inside the app, or passed `--app-path`. A command run from a subdirectory reads the config of the app root it walks up to, and names it on stderr.
|
|
242
318
|
2. The key is one the gem knows. An unknown key warns on stderr and is ignored.
|
|
243
319
|
3. The file is readable and parses. Corrupted YAML degrades gracefully with a warning.
|
|
244
320
|
|
|
@@ -262,10 +338,12 @@ Without it, the tool reports "not installed" but the gem works fine otherwise.
|
|
|
262
338
|
|
|
263
339
|
The scan runs under the app's own bundle, so a brakeman installed globally is
|
|
264
340
|
not on its load path. The tool falls back on its own: it runs the installed
|
|
265
|
-
brakeman as a separate process outside the
|
|
266
|
-
results. This message means that fallback
|
|
267
|
-
is there and the run failed.
|
|
268
|
-
|
|
341
|
+
gem's `bin/brakeman` with the app's Ruby, as a separate process outside the
|
|
342
|
+
bundle, and says so under the results. This message means that fallback
|
|
343
|
+
produced no report either - the gem is there and the run failed. `rails
|
|
344
|
+
ai:doctor` runs the same script's `--version` and names what it printed. Run
|
|
345
|
+
`brakeman` in the app directory to see what it hit, or add it to the Gemfile
|
|
346
|
+
so the scan runs in-process:
|
|
269
347
|
|
|
270
348
|
```bash
|
|
271
349
|
bundle add brakeman --group development
|