rails-ai-context 5.32.2 → 5.34.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 +1491 -0
- data/CONTEXT.md +27 -3
- data/README.md +16 -12
- data/Rakefile +6 -0
- data/app/controllers/rails_ai_context/mcp_controller.rb +112 -84
- data/config/routes.rb +2 -3
- data/docs/ARCHITECTURE.md +7 -5
- data/docs/CLI.md +79 -6
- data/docs/COMPATIBILITY.md +75 -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 +10 -8
- 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 +152 -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/app_kind.rb +18 -5
- 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 +58 -22
- 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 +18 -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 +186 -27
- 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_record_settings.rb +1 -1
- 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 +15 -10
- data/lib/rails_ai_context/introspectors/call_site_expansion.rb +24 -1
- data/lib/rails_ai_context/introspectors/component_introspector.rb +3 -2
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +387 -60
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +15 -3
- 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 +23 -10
- 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 +3 -3
- data/lib/rails_ai_context/introspectors/job_introspector.rb +8 -6
- data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +12 -8
- data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +78 -4
- data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +1 -0
- data/lib/rails_ai_context/introspectors/listeners/env_access_listener.rb +10 -1
- data/lib/rails_ai_context/introspectors/listeners/foreign_evals_listener.rb +61 -0
- data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +13 -4
- data/lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb +2 -1
- data/lib/rails_ai_context/introspectors/listeners/migration_replay_listener.rb +3 -2
- data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +29 -8
- data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +2 -2
- 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/helpers.rb +1 -1
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +83 -27
- data/lib/rails_ai_context/introspectors/migration_replay.rb +15 -14
- data/lib/rails_ai_context/introspectors/model_introspector.rb +231 -23
- 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_conventions.rb +23 -3
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +66 -16
- data/lib/rails_ai_context/introspectors/schema_reader.rb +4 -3
- data/lib/rails_ai_context/introspectors/seeds_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_scan.rb +39 -9
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +15 -6
- data/lib/rails_ai_context/introspectors/test_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +131 -12
- data/lib/rails_ai_context/introspectors/view_introspector.rb +4 -4
- 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 +182 -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 +92 -8
- 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 +262 -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 +88 -33
- 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 +19 -16
- data/lib/rails_ai_context/serializers/guide_examples.rb +142 -0
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +146 -30
- data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +38 -15
- data/lib/rails_ai_context/serializers/section_facts.rb +44 -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 +447 -372
- 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 +367 -46
- 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 +179 -44
- data/lib/rails_ai_context/tools/get_conventions.rb +28 -6
- 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 +10 -6
- data/lib/rails_ai_context/tools/get_i18n.rb +1 -1
- data/lib/rails_ai_context/tools/get_job_pattern.rb +3 -2
- data/lib/rails_ai_context/tools/get_mailers.rb +3 -3
- data/lib/rails_ai_context/tools/get_model_details.rb +30 -10
- data/lib/rails_ai_context/tools/get_partial_interface.rb +242 -23
- data/lib/rails_ai_context/tools/get_routes.rb +29 -8
- data/lib/rails_ai_context/tools/get_schema.rb +52 -19
- 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 +52 -8
- data/lib/rails_ai_context/tools/performance_check.rb +5 -3
- 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 +215 -53
- 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 +4 -4
- data/lib/rails_ai_context/tools/security_scan.rb +143 -33
- data/lib/rails_ai_context/tools/session_context.rb +39 -5
- data/lib/rails_ai_context/tools/validate.rb +259 -40
- data/lib/rails_ai_context/tools/validate_semantics.rb +130 -56
- 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 +14 -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`, an unquoted name with `=` or `=>` (`HTTP_AUTHORIZATION=Basic ...`, `Authorization => Bearer ...`), and a `Token` or `Digest` header with all its parameters; a switch's value (`skip_authorization = true`) stays. A log file shown as a file (a tracked log in a `rails_review_changes` diff, rotated or kept ones like `.log.1`, `.log-20261001`, `.log.old` and `.log.bak` included) gets the same filtering a log line does. 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,14 @@ 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
|
+
Strong params are the methods that return permitted params. A method named `*_params` counts when it calls `permit`, `permit!` or `expect` on params (directly, through a local or an ivar, on each item a block walks, or on `ActionController::Parameters.new`), or when what it returns comes from `super` (`super.merge(...)`), another strong params method (a call runs the nearest class's method of that name, so one the controller or a parent class defines counts whatever its name, such as `params_slice(:min_id)`; any other named `*_params` or `permitted_*`, such as a concern's or Pundit's `permitted_attributes(@post)`; and Devise's `devise_parameter_sanitizer`), or a call given the whole params (`Filter.new(params).call`), so a `before_action` that writes into another one's params is not one; a method of another name counts only when it returns the permitted params themselves. A parent controller's strong params are listed with the parent named unless the controller defines an instance method of that name itself, `permit!` is marked, and an action that permits inline is not one. A file that names another controller (`GoogleController = OAuth2Controller`) is listed as its alias, with that controller's actions.
|
|
258
|
+
|
|
259
|
+
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.
|
|
260
|
+
|
|
237
261
|
| Parameter | Type | Default | Description |
|
|
238
262
|
|:----------|:-----|:--------|:------------|
|
|
239
263
|
| `controller` | string | - | Controller name (e.g., `UsersController`) |
|
|
240
|
-
| `action` | string | - | Specific action; requires `controller
|
|
264
|
+
| `action` | string | - | Specific action; requires `controller`. Also finds one a concern or a parent controller gives that no route names (see above) |
|
|
241
265
|
| `detail` | enum | `standard` | `summary`, `standard`, `full` |
|
|
242
266
|
| `limit` | integer | `50` | Max controllers when listing |
|
|
243
267
|
| `offset` | integer | `0` | Skip this many controllers |
|
|
@@ -253,13 +277,22 @@ spells one out. A mounted Grape API lists its endpoints under the mount (verb,
|
|
|
253
277
|
full path, declared params), read from its classes in `app/api` and `lib/api`;
|
|
254
278
|
the summary counts them.
|
|
255
279
|
|
|
280
|
+
`app_only` leaves out the routes Rails' engines and gems draw on their own:
|
|
281
|
+
Active Storage, Action Mailbox's ingresses and its conductor, Turbo Native's
|
|
282
|
+
navigation routes, and Rails' development pages (`rails/info`, `rails/mailers`,
|
|
283
|
+
`rails/welcome`), as `config.excluded_route_prefixes` lists them. A route the
|
|
284
|
+
app's own route files declare is an app route, so `devise_for :users` and the
|
|
285
|
+
`get "up" => "rails/health#show"` health check are listed. A `controller` filter
|
|
286
|
+
searches every route, so `controller:"devise/sessions"` or
|
|
287
|
+
`controller:"active_storage/blobs"` answers whatever `app_only` says.
|
|
288
|
+
|
|
256
289
|
| Parameter | Type | Default | Description |
|
|
257
290
|
|:----------|:-----|:--------|:------------|
|
|
258
|
-
| `controller` | string | - | Filter by controller |
|
|
291
|
+
| `controller` | string | - | Filter by controller; searches every route, framework ones included |
|
|
259
292
|
| `detail` | enum | `standard` | `summary`, `standard`, `full` |
|
|
260
293
|
| `limit` | integer | auto | Max routes to return; the default depends on `detail` |
|
|
261
294
|
| `offset` | integer | `0` | Skip this many routes |
|
|
262
|
-
| `app_only` | boolean | `true` | Leave out
|
|
295
|
+
| `app_only` | boolean | `true` | Leave out the routes Rails' engines and gems draw on their own (see above) |
|
|
263
296
|
|
|
264
297
|
<p align="right"><a href="#table-of-contents">↑ back to top</a></p>
|
|
265
298
|
|
|
@@ -295,6 +328,15 @@ the naming rule of its directory when one is known: a loader that imports the
|
|
|
295
328
|
directory by a derived path (`import(`./dynamic/${path}.controller.ts`)`), or
|
|
296
329
|
two or more confirmed neighbours that all drop the directory's segment.
|
|
297
330
|
|
|
331
|
+
The change callbacks Stimulus runs itself (`countValueChanged`,
|
|
332
|
+
`itemTargetConnected`, `resultsOutletDisconnected`) are listed with the
|
|
333
|
+
lifecycle hooks, not as actions. The copy-paste markup at `full` detail wires
|
|
334
|
+
each action to the events the app's own templates use (`input->search#queue`),
|
|
335
|
+
and to no named event, so the element's default, when no template wires it; it
|
|
336
|
+
leaves out a method no template names that the controller calls itself, and
|
|
337
|
+
carries a `data-*-class` and `data-*-outlet` attribute for each class and
|
|
338
|
+
outlet the controller reads.
|
|
339
|
+
|
|
298
340
|
| Parameter | Type | Default | Description |
|
|
299
341
|
|:----------|:-----|:--------|:------------|
|
|
300
342
|
| `controller` | string | - | Stimulus controller name |
|
|
@@ -304,7 +346,7 @@ two or more confirmed neighbours that all drop the directory's segment.
|
|
|
304
346
|
|
|
305
347
|
### `rails_get_partial_interface`
|
|
306
348
|
|
|
307
|
-
What locals to pass to a partial and what methods are called on them.
|
|
349
|
+
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
350
|
|
|
309
351
|
| Parameter | Type | Default | Description |
|
|
310
352
|
|:----------|:-----|:--------|:------------|
|
|
@@ -313,12 +355,12 @@ What locals to pass to a partial and what methods are called on them.
|
|
|
313
355
|
|
|
314
356
|
### `rails_get_turbo_map`
|
|
315
357
|
|
|
316
|
-
Turbo Stream broadcast-to-subscription wiring with mismatch warnings.
|
|
358
|
+
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
359
|
|
|
318
360
|
| Parameter | Type | Default | Description |
|
|
319
361
|
|:----------|:-----|:--------|:------------|
|
|
320
362
|
| `detail` | enum | `standard` | `summary`, `standard`, `full` |
|
|
321
|
-
| `stream` | string | - | Filter by stream or channel name (e.g., `notifications`) |
|
|
363
|
+
| `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
364
|
| `controller` | string | - | Filter by controller name (e.g., `messages`) |
|
|
323
365
|
|
|
324
366
|
### `rails_get_frontend_stack`
|
|
@@ -353,13 +395,19 @@ Test scaffolding that matches your project's patterns (fixtures vs factories, RS
|
|
|
353
395
|
|:----------|:-----|:--------|:------------|
|
|
354
396
|
| `model` | string | - | Model to generate a model test for (e.g., `User`) |
|
|
355
397
|
| `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` |
|
|
398
|
+
| `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) |
|
|
399
|
+
| `type` | enum | `unit` | `unit`, `request`, `system`. With `model` or `controller`, `system` writes a system test that visits the subject's index and show pages |
|
|
400
|
+
|
|
401
|
+
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
402
|
|
|
359
403
|
### `rails_validate`
|
|
360
404
|
|
|
361
405
|
Syntax + semantic + Brakeman security validation in one call.
|
|
362
406
|
|
|
407
|
+
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.
|
|
408
|
+
|
|
409
|
+
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.
|
|
410
|
+
|
|
363
411
|
| Parameter | Type | Default | Description |
|
|
364
412
|
|:----------|:-----|:--------|:------------|
|
|
365
413
|
| `files` | array | required | File paths to validate |
|
|
@@ -378,13 +426,35 @@ Brakeman static analysis: SQL injection, XSS, mass assignment, command injection
|
|
|
378
426
|
|
|
379
427
|
> Requires the `brakeman` gem. When the app's bundle does not carry it but the
|
|
380
428
|
> machine has it, the scan runs brakeman as its own process from outside the
|
|
381
|
-
> bundle and says so
|
|
382
|
-
> brakeman is
|
|
429
|
+
> bundle and says so: the installed gem's own `bin/brakeman`, run by the app's
|
|
430
|
+
> Ruby, not whatever `brakeman` is on PATH. When no scan can run, the answer
|
|
431
|
+
> says which case it is: brakeman is nowhere on the machine, or it is
|
|
432
|
+
> installed and produced no report.
|
|
383
433
|
|
|
384
434
|
### `rails_performance_check`
|
|
385
435
|
|
|
386
436
|
N+1 query risks, missing indexes, missing counter_cache, eager load candidates.
|
|
387
437
|
|
|
438
|
+
An N+1 risk is an association call on a record that a loop hands out. Each
|
|
439
|
+
action's body and the before filters that run for it say what its instance
|
|
440
|
+
variables hold (`@reviews = @product.reviews.latest`, `@orders =
|
|
441
|
+
current_user.orders`), and its templates and the partials they render say what
|
|
442
|
+
walks them: `@reviews.each do |review|`, `render @reviews`, `render partial:,
|
|
443
|
+
collection:`, `json.array!`, a loop record passed to a partial as a local, or a
|
|
444
|
+
loop in a partial rendered with no locals (`render "dashboard/list"`).
|
|
445
|
+
A `review.user` read there is a risk; the same name anywhere else is not. The
|
|
446
|
+
row names the action and the view the call sits in. `high` means the query
|
|
447
|
+
preloads nothing, `medium` that it preloads other associations, and `low` that
|
|
448
|
+
it preloads this one; a named scope in the query counts with its body, so
|
|
449
|
+
`Post.with_author` preloads what `scope :with_author, -> { includes(:user) }`
|
|
450
|
+
does. `.count` on an association runs a query per record even when preloaded,
|
|
451
|
+
so it stays `high`. `.size`, `.any?`, `.empty?` and `.none?` on a has_many
|
|
452
|
+
whose other side keeps a `counter_cache` read the counter column, so they are
|
|
453
|
+
not a risk. The inverse Rails sets on each record of
|
|
454
|
+
`@product.reviews` (`review.product`) is not a risk, and a branch on a local
|
|
455
|
+
the render passes as a literal (`show_seller: false`) is not read. ERB and
|
|
456
|
+
jbuilder are read; Haml and Slim templates are not, and neither are serializers.
|
|
457
|
+
|
|
388
458
|
| Parameter | Type | Default | Description |
|
|
389
459
|
|:----------|:-----|:--------|:------------|
|
|
390
460
|
| `model` | string | - | Filter results to one model |
|
|
@@ -422,6 +492,11 @@ Database config, auth framework, assets, cache, queue, Action Cable. The `use` a
|
|
|
422
492
|
Notable gems with versions, categories, and config file locations. A config
|
|
423
493
|
location is printed only when the app has that file, and an initializer is
|
|
424
494
|
found under a load-order prefix too (`config/initializers/3_omniauth.rb`).
|
|
495
|
+
A notable gem counts when the Gemfile names it, when a gemspec or path gem the
|
|
496
|
+
Gemfile pulls in depends on it, when it is a Rails engine or OmniAuth that
|
|
497
|
+
another gem brings in, or, for a framework `rails` pulls in, when
|
|
498
|
+
config/application.rb loads it; a gem the lockfile holds only for another
|
|
499
|
+
gem's own use is left out.
|
|
425
500
|
|
|
426
501
|
| Parameter | Type | Default | Description |
|
|
427
502
|
|:----------|:-----|:--------|:------------|
|
|
@@ -431,7 +506,7 @@ found under a load-order prefix too (`config/initializers/3_omniauth.rb`).
|
|
|
431
506
|
|
|
432
507
|
### `rails_get_env`
|
|
433
508
|
|
|
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
|
|
509
|
+
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
510
|
|
|
436
511
|
| Parameter | Type | Default | Description |
|
|
437
512
|
|:----------|:-----|:--------|:------------|
|
|
@@ -589,21 +664,40 @@ Model association graph in Mermaid or text format.
|
|
|
589
664
|
| `model` | string | - | Center the graph on this model |
|
|
590
665
|
| `depth` | integer | `2` | Hops from the centre model (1-3) |
|
|
591
666
|
| `format` | enum | `mermaid` | `mermaid`, `text` |
|
|
592
|
-
| `show_cycles` | boolean | `false` |
|
|
667
|
+
| `show_cycles` | boolean | `false` | List circular dependencies: chains of foreign keys that lead back to the model they start from, through another |
|
|
593
668
|
| `show_sti` | boolean | `false` | Show Single Table Inheritance hierarchies |
|
|
594
669
|
|
|
595
670
|
The graph is capped at 50 nodes, with or without `model`, and says so when it cuts.
|
|
596
671
|
|
|
672
|
+
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.
|
|
673
|
+
|
|
597
674
|
### `rails_migration_advisor`
|
|
598
675
|
|
|
599
676
|
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
677
|
|
|
678
|
+
Before the code it warns about what the app's migrations already hold: a
|
|
679
|
+
file already named what the generator would name this one (`rails generate
|
|
680
|
+
migration` refuses that name), and a migration not yet run that already adds
|
|
681
|
+
the column or creates the table when it runs (a `def down`, a `dir.down` or a
|
|
682
|
+
`revert` block only undoes, so what it adds does not count). It also warns
|
|
683
|
+
about a `create_table` for a
|
|
684
|
+
table that exists and a `null: false` column with no default on a table with
|
|
685
|
+
rows (the row count is the database's estimate), and refuses a column type no
|
|
686
|
+
adapter knows. For `remove_column`, `rename_column` and `change_type` it lists
|
|
687
|
+
the lines in `app/`, `lib/` and `config/` that name the column, the code the
|
|
688
|
+
change breaks.
|
|
689
|
+
|
|
690
|
+
It refuses a `table`, `type` or `new_name` longer than 256 characters, a
|
|
691
|
+
`column` that long outside `create_table`, and `options` longer than 1,024
|
|
692
|
+
characters: no migration takes one, and the code it writes would carry it
|
|
693
|
+
whole.
|
|
694
|
+
|
|
601
695
|
| Parameter | Type | Default | Description |
|
|
602
696
|
|:----------|:-----|:--------|:------------|
|
|
603
697
|
| `action` | enum | *required* | `add_column`, `remove_column`, `rename_column`, `add_index`, `add_association`, `change_type`, `create_table` |
|
|
604
698
|
| `table` | string | *required* | Table name |
|
|
605
|
-
| `column` | string | - | Column name |
|
|
606
|
-
| `type` | string | - | Column type (e.g., `string`, `integer`, `references`) |
|
|
699
|
+
| `column` | string | - | Column name; for `add_association`, the reference (`user`, `parent`) |
|
|
700
|
+
| `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
701
|
| `new_name` | string | - | New column name, for `rename_column` only |
|
|
608
702
|
| `options` | string | - | Extra options (e.g., `null: false, default: 0`) |
|
|
609
703
|
|
|
@@ -620,7 +714,7 @@ Bundled topic index with weighted keyword search. Optional on-demand GitHub fetc
|
|
|
620
714
|
|
|
621
715
|
### `rails_query`
|
|
622
716
|
|
|
623
|
-
Safe read-only SQL with
|
|
717
|
+
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
718
|
|
|
625
719
|
| Parameter | Type | Default | Description |
|
|
626
720
|
|:----------|:-----|:--------|:------------|
|
|
@@ -631,25 +725,33 @@ Safe read-only SQL with 4-layer security: regex validation, `SET TRANSACTION REA
|
|
|
631
725
|
|
|
632
726
|
> Disabled in production by default.
|
|
633
727
|
|
|
728
|
+
> 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.
|
|
729
|
+
|
|
730
|
+
> **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.
|
|
731
|
+
|
|
634
732
|
### `rails_read_logs`
|
|
635
733
|
|
|
636
734
|
Reverse file tail with level filtering and sensitive data redaction.
|
|
637
735
|
|
|
638
736
|
| Parameter | Type | Default | Description |
|
|
639
737
|
|:----------|:-----|:--------|:------------|
|
|
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 |
|
|
738
|
+
| `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 |
|
|
739
|
+
| `lines` | integer | `50` | Number of lines, max 500. With `search`, the number of matching lines to show |
|
|
642
740
|
| `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 |
|
|
741
|
+
| `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
742
|
|
|
645
743
|
### `rails_diagnose`
|
|
646
744
|
|
|
647
745
|
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
746
|
|
|
747
|
+
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 (...)`).
|
|
748
|
+
|
|
749
|
+
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.
|
|
750
|
+
|
|
649
751
|
| Parameter | Type | Default | Description |
|
|
650
752
|
|:----------|:-----|:--------|:------------|
|
|
651
753
|
| `error` | string | *required* | Error message or class |
|
|
652
|
-
| `file` | string | - | File where error occurred |
|
|
754
|
+
| `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
755
|
| `line` | integer | - | Line number |
|
|
654
756
|
| `action` | string | - | `controller#action` (e.g., `posts#create`); adds that action's context |
|
|
655
757
|
|
|
@@ -657,15 +759,25 @@ One-call error diagnosis with classification, context, recent git changes, and l
|
|
|
657
759
|
|
|
658
760
|
PR/commit review with per-file context and warnings.
|
|
659
761
|
|
|
762
|
+
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.
|
|
763
|
+
|
|
764
|
+
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.
|
|
765
|
+
|
|
766
|
+
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.
|
|
767
|
+
|
|
768
|
+
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.
|
|
769
|
+
|
|
660
770
|
| Parameter | Type | Default | Description |
|
|
661
771
|
|:----------|:-----|:--------|:------------|
|
|
662
|
-
| `ref` | string | `HEAD` | Git ref to diff against. `HEAD` reviews uncommitted changes |
|
|
772
|
+
| `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
773
|
| `files` | array | - | Review only these changed files (omit for all) |
|
|
664
774
|
|
|
665
775
|
### `rails_runtime_info`
|
|
666
776
|
|
|
667
777
|
Live database pool stats, table sizes, pending migrations, cache stats, queue depth.
|
|
668
778
|
|
|
779
|
+
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.
|
|
780
|
+
|
|
669
781
|
| Parameter | Type | Default | Description |
|
|
670
782
|
|:----------|:-----|:--------|:------------|
|
|
671
783
|
| `detail` | enum | `standard` | `summary`, `standard`, `full` |
|
|
@@ -673,12 +785,14 @@ Live database pool stats, table sizes, pending migrations, cache stats, queue de
|
|
|
673
785
|
|
|
674
786
|
### `rails_session_context`
|
|
675
787
|
|
|
676
|
-
Session-aware context tracking across tool calls within a conversation. Pass `action` or `mark`; a call with neither is an error.
|
|
788
|
+
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
789
|
|
|
678
790
|
| Parameter | Type | Default | Description |
|
|
679
791
|
|:----------|:-----|:--------|:------------|
|
|
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`) |
|
|
792
|
+
| `action` | enum | - | `status` (queried tools with timestamps), `summary` (short recap), `reset` (clear this conversation's record) |
|
|
793
|
+
| `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 |
|
|
794
|
+
|
|
795
|
+
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
796
|
|
|
683
797
|
<p align="right"><a href="#table-of-contents">↑ back to top</a></p>
|
|
684
798
|
|
|
@@ -686,7 +800,7 @@ Session-aware context tracking across tool calls within a conversation. Pass `ac
|
|
|
686
800
|
|
|
687
801
|
## Live Resources (VFS)
|
|
688
802
|
|
|
689
|
-
In addition to tools, AI clients can read structured data through **resource templates** - `rails-ai-context://` URIs introspected
|
|
803
|
+
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
804
|
|
|
691
805
|
| URI Pattern | Returns |
|
|
692
806
|
|:------------|:--------|
|
|
@@ -698,6 +812,10 @@ In addition to tools, AI clients can read structured data through **resource tem
|
|
|
698
812
|
|
|
699
813
|
The legacy `rails://models/{name}` form is still accepted.
|
|
700
814
|
|
|
815
|
+
`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.
|
|
816
|
+
|
|
817
|
+
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.
|
|
818
|
+
|
|
701
819
|
Plus 9 static resources (schema, routes, conventions, gems, controllers, config, tests, migrations, engines).
|
|
702
820
|
|
|
703
821
|
<p align="right"><a href="#table-of-contents">↑ back to top</a></p>
|