rails-ai-context 5.35.0 → 5.35.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -0
- data/CONTEXT.md +37 -7
- data/CONTRIBUTING.md +30 -1
- data/README.md +9 -9
- data/SECURITY.md +2 -2
- data/docs/ARCHITECTURE.md +12 -4
- data/docs/CLI.md +44 -15
- data/docs/COMPATIBILITY.md +34 -8
- data/docs/CONFIGURATION.md +28 -5
- data/docs/CUSTOM_TOOLS.md +8 -2
- data/docs/FAQ.md +5 -5
- data/docs/GUIDE.md +33 -65
- data/docs/INTROSPECTORS.md +4 -4
- data/docs/QUICKSTART.md +1 -1
- data/docs/RECIPES.md +5 -5
- data/docs/RELEASING.md +3 -1
- data/docs/SECURITY.md +255 -74
- data/docs/SETUP.md +1 -1
- data/docs/STANDALONE.md +2 -2
- data/docs/TOOLS.md +151 -90
- data/docs/TROUBLESHOOTING.md +123 -8
- data/docs/_config.yml +1 -1
- data/docs/adr/0008-query-runs-only-where-it-can-guard.md +38 -0
- data/docs/adr/0009-dbhub-shapes-in-ruby.md +26 -0
- data/docs/adr/0010-one-config-tool-with-sections.md +32 -0
- data/docs/index.md +2 -2
- data/exe/rails-ai-context +24 -9
- data/lib/generators/rails_ai_context/install/install_generator.rb +2 -0
- data/lib/rails_ai_context/app_databases.rb +377 -0
- data/lib/rails_ai_context/app_kind.rb +2 -0
- data/lib/rails_ai_context/cli/app_root.rb +1 -0
- data/lib/rails_ai_context/cli/tool_runner.rb +110 -99
- data/lib/rails_ai_context/configuration.rb +15 -8
- data/lib/rails_ai_context/database_family/mysql_runtime.rb +170 -0
- data/lib/rails_ai_context/database_family/postgresql_plan.rb +124 -0
- data/lib/rails_ai_context/database_family/postgresql_runtime.rb +211 -0
- data/lib/rails_ai_context/database_family/sqlite_runtime.rb +391 -0
- data/lib/rails_ai_context/database_family.rb +282 -0
- data/lib/rails_ai_context/database_file.rb +61 -7
- data/lib/rails_ai_context/database_yml.rb +7 -29
- data/lib/rails_ai_context/doctor.rb +15 -16
- data/lib/rails_ai_context/instrumentation.rb +1 -1
- data/lib/rails_ai_context/introspectors/connection_pool_introspector.rb +8 -23
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +6 -70
- data/lib/rails_ai_context/introspectors/declared_constant.rb +19 -7
- data/lib/rails_ai_context/introspectors/env_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/env_references.rb +1 -1
- data/lib/rails_ai_context/introspectors/job_introspector.rb +7 -7
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +8 -11
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +9 -9
- data/lib/rails_ai_context/introspectors/model_introspector.rb +33 -39
- data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +33 -30
- data/lib/rails_ai_context/introspectors/node_source.rb +2 -0
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +80 -24
- data/lib/rails_ai_context/introspectors/pg_partitions.rb +1 -4
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +6 -8
- data/lib/rails_ai_context/introspectors/schema_dump_path.rb +12 -14
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +47 -27
- data/lib/rails_ai_context/introspectors/schema_reader.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_scan.rb +39 -4
- data/lib/rails_ai_context/migration_status.rb +18 -46
- data/lib/rails_ai_context/payload.rb +48 -1
- data/lib/rails_ai_context/pending_migrations.rb +3 -4
- data/lib/rails_ai_context/run_cache.rb +5 -0
- data/lib/rails_ai_context/schema_adapter.rb +13 -18
- data/lib/rails_ai_context/secret_columns.rb +506 -0
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +2 -2
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +14 -10
- data/lib/rails_ai_context/serializers/section_facts.rb +12 -2
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +5 -2
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +35 -7
- data/lib/rails_ai_context/server.rb +143 -29
- data/lib/rails_ai_context/sql_gate.rb +1007 -0
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +1 -2
- data/lib/rails_ai_context/test_helper.rb +3 -1
- data/lib/rails_ai_context/tools/analyze_feature.rb +11 -7
- data/lib/rails_ai_context/tools/base_tool.rb +71 -48
- data/lib/rails_ai_context/tools/dependency_graph.rb +1 -1
- data/lib/rails_ai_context/tools/diagnose.rb +105 -42
- data/lib/rails_ai_context/tools/generate_test.rb +4 -6
- data/lib/rails_ai_context/tools/get_active_support.rb +1 -6
- data/lib/rails_ai_context/tools/get_api.rb +1 -6
- data/lib/rails_ai_context/tools/get_autoload.rb +1 -6
- data/lib/rails_ai_context/tools/get_callbacks.rb +5 -5
- data/lib/rails_ai_context/tools/get_component_catalog.rb +6 -7
- data/lib/rails_ai_context/tools/get_concern.rb +6 -6
- data/lib/rails_ai_context/tools/get_config.rb +189 -12
- data/lib/rails_ai_context/tools/get_context.rb +111 -105
- data/lib/rails_ai_context/tools/get_controllers.rb +8 -5
- data/lib/rails_ai_context/tools/get_conventions.rb +1 -6
- data/lib/rails_ai_context/tools/get_engines.rb +1 -6
- data/lib/rails_ai_context/tools/get_env.rb +1 -6
- data/lib/rails_ai_context/tools/get_env_config.rb +2 -8
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +1 -6
- data/lib/rails_ai_context/tools/get_gems.rb +1 -6
- data/lib/rails_ai_context/tools/get_helper_methods.rb +4 -5
- data/lib/rails_ai_context/tools/get_i18n.rb +1 -7
- data/lib/rails_ai_context/tools/get_job_pattern.rb +1 -1
- data/lib/rails_ai_context/tools/get_mailers.rb +1 -1
- data/lib/rails_ai_context/tools/get_model_details.rb +5 -8
- data/lib/rails_ai_context/tools/get_partial_interface.rb +3 -4
- data/lib/rails_ai_context/tools/get_schema.rb +10 -8
- data/lib/rails_ai_context/tools/get_service_pattern.rb +60 -24
- data/lib/rails_ai_context/tools/get_turbo_map.rb +1 -1
- data/lib/rails_ai_context/tools/migration_advisor.rb +47 -32
- data/lib/rails_ai_context/tools/onboard.rb +24 -24
- data/lib/rails_ai_context/tools/performance_check.rb +15 -2
- data/lib/rails_ai_context/tools/query.rb +142 -1356
- data/lib/rails_ai_context/tools/read_logs.rb +1 -1
- data/lib/rails_ai_context/tools/review_changes.rb +6 -7
- data/lib/rails_ai_context/tools/runtime_info.rb +125 -88
- data/lib/rails_ai_context/tools/safe_call.rb +70 -28
- data/lib/rails_ai_context/tools/search_code.rb +0 -4
- data/lib/rails_ai_context/tools/search_docs.rb +3 -1
- data/lib/rails_ai_context/tools/security_scan.rb +9 -5
- data/lib/rails_ai_context/tools/session_context.rb +18 -12
- data/lib/rails_ai_context/tools/validate.rb +1 -0
- data/lib/rails_ai_context/tools/validate_semantics.rb +5 -5
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context.rb +4 -7
- data/server.json +2 -2
- metadata +14 -3
data/CONTEXT.md
CHANGED
|
@@ -6,9 +6,9 @@ Terms this project uses in a narrower sense than everyday English. One entry per
|
|
|
6
6
|
|
|
7
7
|
Overloaded. Always qualify it; never use "environment" bare in a tool name, an introspector name, or a config key.
|
|
8
8
|
|
|
9
|
-
**Process environment** - the environment variables a running app sees, plus the places they are declared (`ENV[]` call sites, `.env.example`, Dockerfile, credentials keys). Served by `
|
|
9
|
+
**Process environment** - the environment variables a running app sees, plus the places they are declared (`ENV[]` call sites, `.env.example`, Dockerfile, credentials keys). Served by `rails_get_config(section:"env")`: `EnvIntrospector` reads the variables and the `ENV[]` call sites, and the tool reads the other places itself.
|
|
10
10
|
|
|
11
|
-
**Environment config** - what `config/environments/*.rb` declares per environment: the assigned `config.*` keys and the values of the notable toggles. Served by `
|
|
11
|
+
**Environment config** - what `config/environments/*.rb` declares per environment: the assigned `config.*` keys and the values of the notable toggles. Served by `rails_get_config(section:"env_config")`, read by `EnvConfigIntrospector`.
|
|
12
12
|
|
|
13
13
|
**Environment** (unqualified, as data) - a single named Rails environment: development, production, staging. This is the only sense in which the bare word is allowed, and only as a value, never as a name. The `:environments` payload key means "the list of these", which is why it kept its name when the introspector was renamed.
|
|
14
14
|
|
|
@@ -110,10 +110,10 @@ counted. The engines section, and the static routes section, hold the mounts
|
|
|
110
110
|
parsed from the route files.
|
|
111
111
|
|
|
112
112
|
Distinct from a **Rails engine**, which is a `Rails::Engine` subclass whether
|
|
113
|
-
or not anything mounts it, and which `
|
|
114
|
-
its loaded classes. The record key is `engine:` for the
|
|
115
|
-
key is `mounted_engines:` - it predates the widening,
|
|
116
|
-
mounted app's constant, engine or not.
|
|
113
|
+
or not anything mounts it, and which `rails_get_config(section:"engines")`
|
|
114
|
+
lists separately under its loaded classes. The record key is `engine:` for the
|
|
115
|
+
same reason the section key is `mounted_engines:` - it predates the widening,
|
|
116
|
+
and what it holds is the mounted app's constant, engine or not.
|
|
117
117
|
|
|
118
118
|
## Interaction filter
|
|
119
119
|
|
|
@@ -167,7 +167,7 @@ A gem the context calls out because it shapes how the app is built, counted only
|
|
|
167
167
|
|
|
168
168
|
## Payload
|
|
169
169
|
|
|
170
|
-
The introspection context hash, in the sense consumers read it. `Payload` is the reading side: a section guard and pinned list readers whose key pairs a spec checks against the producing introspector's own output, so a renamed key fails a test instead of silently emptying a section everywhere. Facts rendered from it live once (`SectionFacts`); membership judgments are applied at the introspector seam, and readers trust the payload rather than re-filtering it.
|
|
170
|
+
The introspection context hash, in the sense consumers read it. `Payload` is the reading side: a section guard and pinned list readers whose key pairs a spec checks against the producing introspector's own output, so a renamed key fails a test instead of silently emptying a section everywhere. Facts rendered from it live once (`SectionFacts`); membership judgments are applied at the introspector seam, and readers trust the payload rather than re-filtering it. One reader fills a section a preset leaves out: `Payload.multi_database` reads the app databases list when the `multi_database` introspector did not run, so a database's mark and the first database's own name do not depend on the preset.
|
|
171
171
|
|
|
172
172
|
## Standalone
|
|
173
173
|
|
|
@@ -210,3 +210,33 @@ An install with every question answered on the command line, the way an AI tool
|
|
|
210
210
|
## Install prompt
|
|
211
211
|
|
|
212
212
|
A message a user pastes into their AI tool so the AI tool installs, changes, removes or starts using the gem in their app. `docs/PROMPTS.md` holds them. Each names its commands outright and runs an **unattended install**, since an AI tool's shell has no one at the keyboard to answer a question.
|
|
213
|
+
|
|
214
|
+
## Database family
|
|
215
|
+
|
|
216
|
+
What a database is by its adapter's name, as far as running a statement on it goes: PostgreSQL (PostGIS too), MySQL (Trilogy and MariaDB too) or SQLite. Each family knows how its database opens a read-only transaction, bounds a statement in time, explains a plan and reports table sizes; anything else is no family, and a tool that needs one refuses or says the answer is not available, naming the adapter. `DatabaseFamily` is the one answer, from a connection, a database.yml adapter or a dump's dialect. ADR-0008 records why a family is claimed by name.
|
|
217
|
+
|
|
218
|
+
Not "engine", which this glossary keeps for Rails engines and **mounted apps**, and not "adapter", which is ActiveRecord's class for one driver.
|
|
219
|
+
|
|
220
|
+
## App database
|
|
221
|
+
|
|
222
|
+
A database the app has in any environment: one database.yml configures for the running environment, one only another environment configures (Rails 8's queue, cache and cable outside production, and a replica or a database with `database_tasks` off too), or one only a committed dump names. `AppDatabases` lists them once per run, the primary first, and each knows the environments that configure it, its **database family**, its dump and its migrations. The primary is the database `ActiveRecord::Base` is connected to on the booted tier, and the running environment's first entry on the static tier. Every surface that asks "which databases" reads this list, so the schema section, the models and the migrations cannot disagree about one app.
|
|
223
|
+
|
|
224
|
+
A tool that needs a connection reaches an app database configured here and nothing else: asked for one that only production configures, it says so. A SQLite database whose file is not there is never connected to, since connecting would create the file: the answer names it and says it does not exist yet. A SQLite database other than the primary that lives in memory is refused by name too: it exists only inside the process that created it, and a new connection would open an empty one. Not the **environment** (one named Rails environment), and not a shard, which is one database's copy under another name.
|
|
225
|
+
|
|
226
|
+
## Secret column
|
|
227
|
+
|
|
228
|
+
A column whose values `rails_query` never returns, and whose name a statement may not mention: one the app lists in `query_redacted_columns`, an `encrypts` column, a built-in secret name, a name the result heuristic flags, or a name `Redaction`'s secret words cover, unless the app allows it by name in `query_allowed_columns`. An `encrypts` column is secret whatever that list holds: a statement that names it is refused, and its values come back filtered. A generated column whose expression reads a secret column is a secret column. On MySQL, MariaDB and SQLite, where no result column is traced to its table column, the rule also refuses a view whose definition names a secret column, and a `*` inside a subquery of a statement that names a table holding one. On PostgreSQL a materialized view is held to that view rule, and on SQLite a virtual table that holds a secret column is refused whole. `SecretColumns` decides for every check in the tool, from a read of the schema of the database the statement runs on and of every other schema or database the statement reaches. Only the primary falls back to the cached context's schema when its tables cannot be read. A database whose columns cannot be read at all is not queried. `docs/SECURITY.md` is the full statement of the rule.
|
|
229
|
+
|
|
230
|
+
Not redaction itself: `Redaction` scrubs secret values out of text (logs, config, source). The two share the vocabulary, so a word added to one is secret in both.
|
|
231
|
+
|
|
232
|
+
## SQL gate
|
|
233
|
+
|
|
234
|
+
What decides whether `rails_query` runs a statement: the checks on its text, and on PostgreSQL the checks on its plan. It reads the statement once, with comments taken out, and answers either the text to run or a refusal with the reason, so nothing reaches the database that the gate did not read. On PostgreSQL a statement whose plan cannot be read does not run. `SqlGate`. The read-only transaction behind it is a second guard, and the gate's checks run before it on every database family (ADR-0008). A name the gate refuses belongs to one family and is refused there alone.
|
|
235
|
+
|
|
236
|
+
## Served tool
|
|
237
|
+
|
|
238
|
+
A tool the server answers for under the app's configuration: the built-ins `skip_tools` leaves, then each custom tool whose name no tool before it claims. An app's own class that inherits the gem's base tool counts with the built-ins once it is loaded, whether or not `custom_tools` lists it. Every surface reads the one set: `tools/list`, the CLI's list and lookup, the tool guide, the context files, `rails_get_context`'s includes and every tool name an answer prints. A tool outside it is never offered, run or pointed at. An answer or a guide step names or runs a tool only when the app serves the gem's own tool under that name: a custom tool in a built-in's place is listed, and nothing gives it the built-in's arguments. A custom tool named `rails_get_config` takes the built-in's place when `skip_tools` lists that name, and the **config sections** go with the built-in.
|
|
239
|
+
|
|
240
|
+
## Config section
|
|
241
|
+
|
|
242
|
+
One part of the app's configuration that `rails_get_config` answers by `section`: `app` (the default: database, auth, assets, cache, queue, Action Cable, initializers, middleware), `gems`, `frontend`, `api`, `engines`, `conventions`, `autoload`, `active_support`, `env` (the **process environment**), `env_config` (the **environment config**) and `i18n`. Ten of them were tools of their own; their names still reach their section from the CLI and the MCP server, and `skip_tools` lists them to turn a section off. With `app` off, a call has to name its `section`. ADR-0010.
|
data/CONTRIBUTING.md
CHANGED
|
@@ -20,7 +20,7 @@ The test suite uses [Combustion](https://github.com/pat/combustion) to boot a mi
|
|
|
20
20
|
lib/rails_ai_context/
|
|
21
21
|
├── cli/ # CLI tool runner (tool_runner.rb) - executes MCP tools from rake/Thor
|
|
22
22
|
├── introspectors/ # 40 introspectors (schema, models, routes, etc.)
|
|
23
|
-
├── tools/ #
|
|
23
|
+
├── tools/ # 35 MCP tools with detail levels and pagination
|
|
24
24
|
├── serializers/ # Per-assistant formatters + shared ToolGuideHelper
|
|
25
25
|
├── server.rb # MCP server setup (stdio + HTTP)
|
|
26
26
|
├── live_reload.rb # MCP live reload (file watcher + cache invalidation)
|
|
@@ -82,6 +82,35 @@ bundle exec rspec spec/lib/ # Just lib specs
|
|
|
82
82
|
bundle exec rubocop --parallel # Lint check
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
### Real database servers
|
|
86
|
+
|
|
87
|
+
The suite runs on SQLite. `spec/lib/rails_ai_context/database_family/database_servers_spec.rb`
|
|
88
|
+
runs the PostgreSQL, MySQL and MariaDB families against real database servers, one group
|
|
89
|
+
per URL that is set. `spec/lib/rails_ai_context/tools/query_postgresql_spec.rb` and
|
|
90
|
+
`query_mysql_spec.rb` beside it run `rails_query` end to end on the same URLs.
|
|
91
|
+
`spec/lib/rails_ai_context/app_databases_servers_spec.rb` leases a MySQL and a MariaDB
|
|
92
|
+
database by name, and runs with `RAC_TEST_MYSQL_URL` and `RAC_TEST_MARIADB_URL`:
|
|
93
|
+
|
|
94
|
+
| Variable | Database server | Example |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `RAC_TEST_PG_URL` | PostgreSQL | `postgresql://postgres:secret@127.0.0.1:5432/rac` |
|
|
97
|
+
| `RAC_TEST_MYSQL_URL` | MySQL, through mysql2 or Trilogy | `trilogy://root:secret@127.0.0.1:3306/rac` |
|
|
98
|
+
| `RAC_TEST_MARIADB_URL` | MariaDB, through mysql2 or Trilogy | `trilogy://root:secret@127.0.0.1:3307/rac` |
|
|
99
|
+
|
|
100
|
+
The URL's scheme picks the adapter. The drivers come from a separate Gemfile:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
docker run -d --name rac-mysql -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=rac -p 3306:3306 mysql:8.4
|
|
104
|
+
BUNDLE_GEMFILE=gemfiles/database_servers.gemfile bundle install
|
|
105
|
+
RAC_TEST_MYSQL_URL=trilogy://root:secret@127.0.0.1:3306/rac \
|
|
106
|
+
BUNDLE_GEMFILE=gemfiles/database_servers.gemfile bundle exec rspec spec/lib/rails_ai_context/database_family/database_servers_spec.rb
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The specs create and drop tables and views whose names start with `rac_`, and leave the `postgres_fdw` extension installed on PostgreSQL, so point them at a scratch database.
|
|
110
|
+
CI's `database_servers` job runs them on PostgreSQL 16 in every leg. Rails 8.0 runs two legs: MySQL 8.0 with MariaDB 10.6, and MySQL 8.4 with MariaDB 11.4. Rails 7.0 runs MySQL 8.0 with MariaDB 10.6, and Rails 8.1 runs MySQL 8.4 with MariaDB 11.4.
|
|
111
|
+
Rails 7.0 has no Trilogy adapter, so its leg runs PostgreSQL and mysql2 only.
|
|
112
|
+
The job builds mysql2 against MariaDB's client library (`libmariadb-dev`, through `BUNDLE_BUILD__MYSQL2`), which signs in to MySQL and to MariaDB. A MySQL client from 9.0 on cannot sign in to MariaDB.
|
|
113
|
+
|
|
85
114
|
### Static fixture
|
|
86
115
|
|
|
87
116
|
`spec/fixtures/static_app` is the app the static tier is exercised against, and
|
data/README.md
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
|
-
**rails-ai-context** makes your Rails app the source of truth for your AI coding assistant. The assistant stops guessing your schema, associations, routes and conventions from training data and asks your app instead: **
|
|
28
|
+
**rails-ai-context** makes your Rails app the source of truth for your AI coding assistant. The assistant stops guessing your schema, associations, routes and conventions from training data and asks your app instead: **35 read-only tools** over [MCP](https://modelcontextprotocol.io) or the command line, plus context files written for Claude Code, Cursor, GitHub Copilot, OpenCode and Codex CLI.
|
|
29
29
|
|
|
30
30
|
## Install in one paste
|
|
31
31
|
|
|
@@ -129,7 +129,7 @@ You catch it, fix it, re-prompt, and something next to it breaks. The tokens are
|
|
|
129
129
|
|
|
130
130
|
| | |
|
|
131
131
|
|:---|:---|
|
|
132
|
-
| **
|
|
132
|
+
| **35 read-only tools** | Schema, models, controllers, routes, views, Stimulus, Turbo, jobs, services, mailers, i18n, gems, config, tests, security, performance and more. Every answer comes from your app. |
|
|
133
133
|
| **Answers it can trust** | Models are read with the Prism parser. A model and each scope carry `[VERIFIED]` or `[INFERRED]` (`[STATIC]` without a boot), so the assistant knows what still needs a runtime check. |
|
|
134
134
|
| **Three ways in** | MCP over stdio, MCP mounted in your app over HTTP, or plain CLI in any terminal. |
|
|
135
135
|
| **Context files per AI tool** | `CLAUDE.md`, Cursor rules, Copilot instructions, `AGENTS.md`, each with the MCP config its tool picks up, and six anti-hallucination rules up top. |
|
|
@@ -154,8 +154,8 @@ Switch any time with the [change the setup](docs/PROMPTS.md#change-the-setup) pr
|
|
|
154
154
|
graph TD
|
|
155
155
|
A["Your Rails app\nmodels + schema + routes + controllers + views + jobs"] -->|"40 introspectors"| B
|
|
156
156
|
B["rails-ai-context\nPrism AST · cached · confidence-tagged\nstatic tier when the app can't boot"]
|
|
157
|
-
B --> C["MCP server\nstdio / HTTP\
|
|
158
|
-
B --> D["CLI\nrake / Thor\nsame
|
|
157
|
+
B --> C["MCP server\nstdio / HTTP\n35 tools · 5 templates · 9 resources"]
|
|
158
|
+
B --> D["CLI\nrake / Thor\nsame 35 tools"]
|
|
159
159
|
B --> E["Context files\nCLAUDE.md · .cursor/rules/ · .github/instructions/ · AGENTS.md"]
|
|
160
160
|
|
|
161
161
|
style A fill:#4a9eff,stroke:#2d7ad4,color:#fff
|
|
@@ -177,9 +177,9 @@ Every tool is read-only and answers from your app.
|
|
|
177
177
|
| Understand | `analyze_feature`, `get_context`, `onboard` |
|
|
178
178
|
| Schema and models | `get_schema`, `get_model_details`, `get_callbacks`, `get_concern` |
|
|
179
179
|
| Controllers and routes | `get_controllers`, `get_routes` |
|
|
180
|
-
| Views and frontend | `get_view`, `get_stimulus`, `get_partial_interface`, `get_turbo_map
|
|
180
|
+
| Views and frontend | `get_view`, `get_stimulus`, `get_partial_interface`, `get_turbo_map` |
|
|
181
181
|
| Testing and quality | `get_test_info`, `generate_test`, `validate`, `security_scan`, `performance_check` |
|
|
182
|
-
| App config and services | `
|
|
182
|
+
| App config and services | `get_config` (sections: app, gems, frontend, api, engines, conventions, autoload, active_support, env, env_config, i18n), `get_helper_methods`, `get_service_pattern`, `get_job_pattern`, `get_component_catalog`, `get_mailers` |
|
|
183
183
|
| Data and debugging | `dependency_graph`, `migration_advisor`, `search_docs`, `query`, `read_logs`, `diagnose`, `review_changes`, `runtime_info`, `session_context` |
|
|
184
184
|
|
|
185
185
|
Four worth knowing on day one:
|
|
@@ -189,7 +189,7 @@ Four worth knowing on day one:
|
|
|
189
189
|
- **`get_model_details`** returns associations, validations, scopes, enums and macros from the AST, tagged `[VERIFIED]` or `[INFERRED]`.
|
|
190
190
|
- **`query`** runs read-only SQL with a timeout, a row limit and column redaction.
|
|
191
191
|
|
|
192
|
-
Over MCP each name gets a `rails_` prefix (`rails_get_schema`). From the CLI the short name works: `schema`, `get_schema` and `rails_get_schema` all resolve. Parameters for all
|
|
192
|
+
Over MCP each name gets a `rails_` prefix (`rails_get_schema`). From the CLI the short name works: `schema`, `get_schema` and `rails_get_schema` all resolve. Parameters for all 35 are in the [tools reference](docs/TOOLS.md); worked examples in [recipes](docs/RECIPES.md).
|
|
193
193
|
|
|
194
194
|
<details>
|
|
195
195
|
<summary><b>Live resources</b></summary>
|
|
@@ -241,7 +241,7 @@ mount RailsAiContext::Engine, at: "/mcp" if defined?(RailsAiContext::Engine)
|
|
|
241
241
|
|
|
242
242
|
The `if` keeps the routes file loading where the gem is not installed, as in production, where Bundler skips the development group. Outside development and test the endpoint refuses every request with a 403 unless you set `config.allow_http_in_production = true`, since it has no authentication of its own ([Security](docs/SECURITY.md#mcp-http-transport)). There is also a standalone HTTP process: `rails-ai-context serve --transport http --port 6029`.
|
|
243
243
|
|
|
244
|
-
**CLI** runs the same
|
|
244
|
+
**CLI** runs the same 35 tools with no server, in any terminal:
|
|
245
245
|
|
|
246
246
|
```bash
|
|
247
247
|
bin/rails 'ai:tool[search_code]' pattern="publishable?" match_type=trace
|
|
@@ -361,7 +361,7 @@ It removes the context files, the MCP entries, its config, its `.gitignore` line
|
|
|
361
361
|
| [Quickstart](docs/QUICKSTART.md) | Up and running in 5 minutes |
|
|
362
362
|
| [AI tool setup](docs/SETUP.md) | Claude Code, Cursor, Copilot, OpenCode, Codex CLI, HTTP transport |
|
|
363
363
|
| [Guide](docs/GUIDE.md) | Every command, parameter and option |
|
|
364
|
-
| [Tools reference](docs/TOOLS.md) | All
|
|
364
|
+
| [Tools reference](docs/TOOLS.md) | All 35 tools with every parameter |
|
|
365
365
|
| [Recipes](docs/RECIPES.md) | Real workflows, end to end |
|
|
366
366
|
| [CLI reference](docs/CLI.md) | Commands, flags and argument syntax |
|
|
367
367
|
| [Standalone](docs/STANDALONE.md) | Use without a Gemfile entry |
|
data/SECURITY.md
CHANGED
|
@@ -18,12 +18,12 @@ If you discover a security vulnerability in rails-ai-context, please report it r
|
|
|
18
18
|
|
|
19
19
|
## Security Design
|
|
20
20
|
|
|
21
|
-
- All
|
|
21
|
+
- All 35 MCP tools are **read-only** and never modify your application or database.
|
|
22
22
|
- **Sensitive file blocking** - configurable `sensitive_patterns` blocks access to `.env`, `*.key`, `*.pem`, `credentials.yml.enc` across all search and read tools. Patterns are checked in `rails_search_code`, `rails_get_edit_context`, and all new tools.
|
|
23
23
|
- **Path traversal protection** - all file-reading tools validate paths with `File.realpath()` against `Rails.root` to prevent directory escape.
|
|
24
24
|
- **Command injection prevention** - code search uses `Open3.capture3` with array arguments (never shell strings). The `--` flag separator prevents pattern injection.
|
|
25
25
|
- **Regex DoS protection** - user-supplied regex patterns have 1-2 second timeouts via `Regexp.new(pattern, timeout:)` on Ruby 3.2 and newer. Ruby 3.1 has no `Regexp.timeout`, so the timeout is skipped there.
|
|
26
|
-
- **Credential safety** - `
|
|
26
|
+
- **Credential safety** - `rails_get_config(section:"env")` reads only `.env.example`, `.env.sample` or `.env.template` and leaves `.env` unread. It shows credential key names without their values, and redacts secrets. `rails_get_config` exposes adapter and framework names and no connection string.
|
|
27
27
|
- **Brakeman integration** - optional `rails_security_scan` tool runs static security analysis. Graceful degradation if not installed. Users can exclude it via `config.skip_tools = %w[rails_security_scan]`.
|
|
28
28
|
- **File size limits** - tools that read a whole file skip one over the configurable `max_file_size` (default 5MB) to prevent memory exhaustion on large files. `rails_read_logs` reads the last 1MB of a log instead, and `rails_search_code` caps its output with `max_search_results`.
|
|
29
29
|
- **SQL comment stripping** - `rails_query` strips block (`/* */`), line (`--`), and MySQL-style (`#`) comments before validation to prevent keyword hiding.
|
data/docs/ARCHITECTURE.md
CHANGED
|
@@ -30,7 +30,7 @@ graph TD
|
|
|
30
30
|
H["Hydration Layer\nSchema hints\ninjected into\ntool responses"]
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
engine --> R["Tool Registry\
|
|
33
|
+
engine --> R["Tool Registry\n35 tools auto-discovered via inherited\n+ custom_tools - skip_tools = active tools"]
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
R --> MCP
|
|
@@ -40,7 +40,7 @@ graph TD
|
|
|
40
40
|
subgraph outputs["Output"]
|
|
41
41
|
direction LR
|
|
42
42
|
MCP["MCP Server\nstdio / HTTP\nResources\nVFS URIs"]
|
|
43
|
-
CLI["CLI Runner\nRake / Thor\nSame
|
|
43
|
+
CLI["CLI Runner\nRake / Thor\nSame 35 tools\nNo server needed"]
|
|
44
44
|
S["Serializers\n18 modules\nStatic files\nPer-AI-tool"]
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -128,6 +128,13 @@ Two more answer a question a tool asks:
|
|
|
128
128
|
- **PendingMigrations** - which migration files are not in the applied set, from one derivation both the schema and migrations sections read
|
|
129
129
|
- **ActionFilters** - which filters apply to a controller or to one of its actions: own, inherited, skipped. `skipped` holds unconditional skips only; a skip carrying `if:`/`unless:` leaves the filter in `own` or `inherited` with the condition on the record. Every controller surface reads its filter line from here, so no two answers can disagree
|
|
130
130
|
|
|
131
|
+
Four more answer for the app's databases and for what `rails_query` runs on them:
|
|
132
|
+
|
|
133
|
+
- **DatabaseFamily** - what a database is by its adapter's name: PostgreSQL (PostGIS too), MySQL (Trilogy and MariaDB too) or SQLite, else no family. A family owns its read-only transaction, its statement timeout, EXPLAIN, table sizes, row estimates and index usage. A runtime call on no family raises before anything reaches the database
|
|
134
|
+
- **AppDatabases** - every database the app has, listed once per run: the primary, the rest of the running environment's, those only another environment configures, then dumps no environment claims. Both tiers build it through one builder. It also leases a connection to a database configured here, through a connection handler of its own for the call
|
|
135
|
+
- **SecretColumns** - which columns `rails_query` never returns and a statement may not name, from one read of the leased database's schema. An `encrypts` column is secret whatever `query_allowed_columns` lists
|
|
136
|
+
- **SqlGate** - whether `rails_query` runs a statement: the checks on its text, and on PostgreSQL the checks on its plan. It answers the comment-free text to run or a refusal with the reason
|
|
137
|
+
|
|
131
138
|
### AST Engine
|
|
132
139
|
|
|
133
140
|
**Prism AST parsing** replaced all regex-based Ruby source parsing in v5.2.0.
|
|
@@ -145,6 +152,7 @@ Auto-registration via Ruby's `inherited` hook:
|
|
|
145
152
|
2. Subclass is appended to `@descendants` (protected by `@registry_mutex`)
|
|
146
153
|
3. `BaseTool.registered_tools` eager-loads all tool files and returns non-abstract classes
|
|
147
154
|
4. `BaseTool` itself is marked `abstract!` - excluded from the registry
|
|
155
|
+
5. The ten classes that render a `rails_get_config` section call `section_renderer!`. `rails_get_config` calls them, so they stay out of the registry and are still callable from Ruby
|
|
148
156
|
|
|
149
157
|
**Deadlock-free design**: `eager_load!` walks `Tools.constants` and `const_get`s each one without holding the mutex, because `const_get` triggers Zeitwerk autoloading which calls `inherited`, which takes the mutex itself.
|
|
150
158
|
|
|
@@ -217,7 +225,7 @@ Thor-based CLI that works standalone (no Gemfile entry):
|
|
|
217
225
|
- `EntryBoot` - Boots the app `AppRoot` chose for the standalone binary, or says which tier it fell back to
|
|
218
226
|
- `ToolRunner` - Parses CLI args, resolves tool names, executes tools, formats output
|
|
219
227
|
- Supports `--json` mode for machine-readable output
|
|
220
|
-
- Same
|
|
228
|
+
- Same 35 tools available as MCP and CLI
|
|
221
229
|
|
|
222
230
|
### Path safety (`lib/rails_ai_context/safe_path.rb`, `lib/rails_ai_context/view_file.rb`)
|
|
223
231
|
|
|
@@ -247,7 +255,7 @@ SHA256-based change detection:
|
|
|
247
255
|
## Key design decisions
|
|
248
256
|
|
|
249
257
|
1. **Official MCP SDK** - Not a custom protocol. Uses `mcp` gem's `MCP::Tool`, `MCP::Server`, transports.
|
|
250
|
-
2. **Read-only tools** - All
|
|
258
|
+
2. **Read-only tools** - All 35 tools annotated as non-destructive. Defense-in-depth for query tool.
|
|
251
259
|
3. **Graceful degradation** - Works without database (parses `schema.rb` or `structure.sql`, or replays the migrations), without Brakeman, without ripgrep, without listen gem.
|
|
252
260
|
4. **Zeitwerk autoloading** - Files loaded on-demand. `require_relative` is kept for the few files that load before or outside the loader.
|
|
253
261
|
5. **Diff-aware generation** - Context file regeneration skips a file whose content is unchanged, the timestamp line aside.
|
data/docs/CLI.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| In-Gemfile (Rake) | `rails ai:` | `rails 'ai:tool[schema]' table=users` |
|
|
18
18
|
| Standalone (Thor) | `rails-ai-context` | `rails-ai-context tool schema --table users` |
|
|
19
19
|
|
|
20
|
-
Both provide the same
|
|
20
|
+
Both provide the same 35 tools and functionality.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -46,7 +46,7 @@ The server announces itself to the client as `server_name` from config, else `ra
|
|
|
46
46
|
|
|
47
47
|
### `tool`
|
|
48
48
|
|
|
49
|
-
Run any of the
|
|
49
|
+
Run any of the 35 MCP tools from the terminal.
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
52
|
# Rake syntax
|
|
@@ -92,13 +92,33 @@ than one environment, because the environment decides which database
|
|
|
92
92
|
configuration the booted tier reads.
|
|
93
93
|
|
|
94
94
|
After the name of a tool that declares an `environment` parameter of its own
|
|
95
|
-
(`env_config`), `--environment` is that
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
(`env_config`, or `config --section env_config`), `--environment` is that
|
|
96
|
+
tool's filter and does not set `RAILS_ENV`:
|
|
97
|
+
`tool config --section env_config --environment production` lists production
|
|
98
|
+
and boots the app in its usual environment. The section's name is read in any
|
|
99
|
+
capitalisation. Set `RAILS_ENV` through the environment variable, or put
|
|
100
|
+
`--environment` before the command name, to boot in another one. Every other
|
|
101
|
+
tool reads the flag as `RAILS_ENV`, and so does `config` with any other
|
|
102
|
+
section: `tool config --section gems --environment production` boots the app
|
|
103
|
+
in production. A custom tool that declares the parameter gets the value both
|
|
104
|
+
ways, since its schema cannot be read before the app boots.
|
|
105
|
+
|
|
106
|
+
The ten tools that became `rails_get_config` sections still answer by their
|
|
107
|
+
old names: `tool gems` runs `config --section gems`. `tool gems --help` prints
|
|
108
|
+
"rails_get_gems - runs the `gems` section of rails_get_config (notable gems by
|
|
109
|
+
category)." and that section's parameters alone. A mistyped old name is
|
|
110
|
+
suggested: `tool gemz` answers "Unknown tool 'gemz'. Did you mean 'gems'?". A
|
|
111
|
+
bare word after an old name gets that section's flag: `tool gems auth` answers
|
|
112
|
+
"'auth' is not a flag - did you mean '--category auth'?".
|
|
113
|
+
|
|
114
|
+
Inside an app, or with `--app-path`, `tool <name> --help` describes the tool
|
|
115
|
+
that app serves. It reads `.rails-ai-context.yml` and boots nothing. A tool or
|
|
116
|
+
section `skip_tools` turned off answers "rails_get_env is turned off in this
|
|
117
|
+
app: skip_tools lists it ..." and exits 1. With the `app` section off,
|
|
118
|
+
`tool config --help` marks `section` required and says "Required: the app
|
|
119
|
+
section is turned off in this app." A `skip_tools` set in an initializer is
|
|
120
|
+
not seen, since help boots nothing. Outside an app help describes the gem's
|
|
121
|
+
own tools.
|
|
102
122
|
|
|
103
123
|
#### Exit status
|
|
104
124
|
|
|
@@ -107,7 +127,8 @@ parameter you did not pass, a flag with no value after it, a bare word where a
|
|
|
107
127
|
flag belongs, and an unknown parameter all exit 1 and say which one. So does a
|
|
108
128
|
refusal on policy - a path outside the app root, a sensitive file, a SQL
|
|
109
129
|
statement the read-only validator blocks - because the question went
|
|
110
|
-
unanswered.
|
|
130
|
+
unanswered. So does a tool that fails unexpectedly: the answer starts
|
|
131
|
+
`Tool <name> failed:`.
|
|
111
132
|
|
|
112
133
|
A thing that is simply not there is an ordinary answer and exits 0: a file the
|
|
113
134
|
tool looked for and did not find, a search with no matches, a directory the
|
|
@@ -115,11 +136,19 @@ tool looked for and did not find, a search with no matches, a directory the
|
|
|
115
136
|
booted app and is run with `--no-boot` (`query`, `runtime_info`) prints
|
|
116
137
|
`[UNAVAILABLE]` with the reason and also exits 0.
|
|
117
138
|
|
|
118
|
-
A value
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
139
|
+
A value outside a parameter's list (`--format xml` on `schema`,
|
|
140
|
+
`--action bogus` on `migration_advisor`) exits 1 and names the values the
|
|
141
|
+
parameter takes. The tool is not run, since any other value would answer a
|
|
142
|
+
question you did not ask. `config --section bogus` exits 1 the same way, and
|
|
143
|
+
its answer names the sections that are on in this app. A value of the wrong
|
|
144
|
+
type is refused too: `--limit abc` on `search_code` answers
|
|
145
|
+
"**Error:** `abc` is not a valid `limit`. Valid: an integer", a boolean takes
|
|
146
|
+
`true` or `false`, and an empty `--limit=` is refused. So is `--no-<param>` on
|
|
147
|
+
a parameter that is no boolean: `--no-limit` answers
|
|
148
|
+
"**Error:** `--no-limit` is not a valid `limit`. Valid: an integer", and a
|
|
149
|
+
string or an array parameter says "a string" or "an array". One value falls
|
|
150
|
+
back to the tool's own default with a warning on stderr: a `--detail` level
|
|
151
|
+
the tool does not have, since every level answers the same question.
|
|
123
152
|
|
|
124
153
|
### The static tier and `--no-boot`
|
|
125
154
|
|
data/docs/COMPATIBILITY.md
CHANGED
|
@@ -277,12 +277,13 @@ Proof sources:
|
|
|
277
277
|
In the v5.33.0 release QA: the release gem in a fresh app on Ruby 3.1 with
|
|
278
278
|
Rails 7.0 and 7.1, Ruby 3.2 with Rails 8.0, and Rails 8.1 on Ruby 3.3, 3.4
|
|
279
279
|
and 4.0, each through the standalone and in-Gemfile installs, the rake
|
|
280
|
-
tasks and all 45 tools over MCP stdio; a Rails 8.1 app
|
|
281
|
-
symlinked path, as macOS spells its temporary folders; a
|
|
282
|
-
Rails 7.0 app mounting the engine, six clients calling
|
|
283
|
-
changed every 0.3 seconds; the pre-commit hook installed and
|
|
284
|
-
commits in an app's own repository, a monorepo's folder and a
|
|
285
|
-
and the unit suite and the E2E harness on Linux, macOS and
|
|
280
|
+
tasks and all 45 of that release's tools over MCP stdio; a Rails 8.1 app
|
|
281
|
+
reached through a symlinked path, as macOS spells its temporary folders; a
|
|
282
|
+
Rails 8.1 and a Rails 7.0 app mounting the engine, six clients calling
|
|
283
|
+
while a model changed every 0.3 seconds; the pre-commit hook installed and
|
|
284
|
+
run by real commits in an app's own repository, a monorepo's folder and a
|
|
285
|
+
submodule; and the unit suite and the E2E harness on Linux, macOS and
|
|
286
|
+
Windows.
|
|
286
287
|
In the v5.34.0 release QA: the 21 apps of the v5.33.0 acceptance run
|
|
287
288
|
(Mastodon, OpenProject, Discourse, Canvas, Forem, Whitehall, Consul, Open
|
|
288
289
|
Food Network, Diaspora, Errbit, Huginn, Plots2, a private app and the small
|
|
@@ -303,6 +304,19 @@ Proof sources:
|
|
|
303
304
|
private production app; each AI tool's MCP config started the way its
|
|
304
305
|
client starts it; and the unit suite and the E2E harness on Linux, macOS
|
|
305
306
|
and Windows.
|
|
307
|
+
In the v5.35.1 release QA: `rails_query` through its tool call on real
|
|
308
|
+
PostgreSQL 16, MySQL 8.0 and 8.4 and MariaDB 10.6 and 11.4 servers, through
|
|
309
|
+
mysql2 and Trilogy, on Rails 7.0, 8.0 and 8.1, now a CI job of its own;
|
|
310
|
+
files of statements sent through the tool on each server and on SQLite
|
|
311
|
+
before and after each fix, with every changed answer traced to its fix;
|
|
312
|
+
the tool's own checks timed on Ruby 3.4 for statements up to a megabyte
|
|
313
|
+
and on Ruby 3.1 for statements of 50 kilobytes; and the unit suite and the
|
|
314
|
+
E2E harness on Linux, macOS and Windows.
|
|
315
|
+
In the v5.35.2 closing review: the statement files of the v5.35.1 QA sent
|
|
316
|
+
through the tool again on PostgreSQL 16, MySQL 8.4, MariaDB 11.4 and SQLite,
|
|
317
|
+
on the v5.35.1 code and on the fixed code, with every changed answer traced
|
|
318
|
+
to its fix; and for each fix an example that fails on the v5.35.1 code, on a
|
|
319
|
+
real server where the fix is a server's.
|
|
306
320
|
2. Non-crash coverage for every built-in tool including `get_view` in
|
|
307
321
|
`spec/e2e/in_gemfile_install_spec.rb`'s full-tool sweep; output correctness
|
|
308
322
|
(ivar cross-check, render-form detection, partial interfaces) verified
|
|
@@ -332,13 +346,25 @@ Proof sources:
|
|
|
332
346
|
in `spec/lib/rails_ai_context/introspectors/static_tier_declarations_spec.rb`.
|
|
333
347
|
`Introspector#run_introspector` reports `{ unavailable: reason }` only for
|
|
334
348
|
the eight runtime-only sections, regardless of shape.
|
|
335
|
-
9. `spec/e2e/empty_app_spec.rb` - all
|
|
349
|
+
9. `spec/e2e/empty_app_spec.rb` - all 35 built-in tools swept against an app
|
|
336
350
|
with no scaffold, no models, no controllers beyond
|
|
337
351
|
`ApplicationController`, no routes beyond root.
|
|
338
352
|
10. `spec/e2e/massive_app_spec.rb` - `schema`, `model_details`, and `routes`
|
|
339
|
-
(plus `context`, `onboard`, `analyze_feature`, `get_turbo_map
|
|
353
|
+
(plus `context`, `onboard`, `analyze_feature`, `get_turbo_map` and the
|
|
354
|
+
`env` section of `get_config`)
|
|
340
355
|
sampled against 500 generated models/tables under a 180-second cap.
|
|
341
356
|
|
|
357
|
+
`rails_query`, `rails_runtime_info` and the row estimates run on three
|
|
358
|
+
database families: PostgreSQL (with PostGIS), MySQL (mysql2 and Trilogy, with
|
|
359
|
+
MariaDB) and SQLite. `rails_query` refuses any other adapter (SQL Server,
|
|
360
|
+
Oracle, CockroachDB) by name before sending a statement, and the table sizes,
|
|
361
|
+
row estimates and index usage say they are not available there. CI's
|
|
362
|
+
`database_servers` job runs the families against PostgreSQL 16, MySQL 8.0 and
|
|
363
|
+
8.4 and MariaDB 10.6 and 11.4 on Rails 8.0, through mysql2 and Trilogy. It also
|
|
364
|
+
runs PostgreSQL 16 with MySQL 8.0 and MariaDB 10.6 on Rails 7.0, which has no
|
|
365
|
+
Trilogy adapter, and with MySQL 8.4 and MariaDB 11.4 on Rails 8.1; see
|
|
366
|
+
[CONTRIBUTING.md](../CONTRIBUTING.md#real-database-servers).
|
|
367
|
+
|
|
342
368
|
Postgres itself - live query safety (read-only transactions, the
|
|
343
369
|
`BLOCKED_FUNCTIONS` guard against `pg_read_file`/`dblink`/`COPY ... PROGRAM`,
|
|
344
370
|
DDL rejection) alongside schema/routes - is proven end-to-end against a real
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -177,7 +177,7 @@ filter either runs or is struck through.
|
|
|
177
177
|
| `max_validate_files` | Integer | `50` | Maximum files for validation |
|
|
178
178
|
| `search_extensions` | Array | `nil` | Narrows the Ruby fallback to these extensions. Unset, the fallback searches every non-hidden, non-binary file, as ripgrep does, so both backends return the same lines |
|
|
179
179
|
| `concern_paths` | Array | `nil` (discovers `app/concerns` and `app/*/concerns`) | Paths to scan for concerns. Setting it replaces discovery, so it can narrow as well as widen |
|
|
180
|
-
| `frontend_paths` | Array | `nil` (auto-detect) | Frontend directories, e.g. `["app/frontend", "../web-client"]`. One outside the app root is read for its package.json, lockfiles and bundler config, plus its tsconfig.json and the configs it extends inside that directory, and
|
|
180
|
+
| `frontend_paths` | Array | `nil` (auto-detect) | Frontend directories, e.g. `["app/frontend", "../web-client"]`. One outside the app root is read for its package.json, lockfiles and bundler config, plus its tsconfig.json and the configs it extends inside that directory, and the `frontend` section of `rails_get_config` names it |
|
|
181
181
|
| `extra_app_paths` | Array | `[]` | Extra directories under the app root to treat as application code |
|
|
182
182
|
|
|
183
183
|
Directories that carry their own Rails tree - `plugins/*`, `modules/*`,
|
|
@@ -199,12 +199,24 @@ fits neither that shape nor the conventional one.
|
|
|
199
199
|
|
|
200
200
|
| Option | Type | Default | Validation | Description |
|
|
201
201
|
|:-------|:-----|:--------|:-----------|:------------|
|
|
202
|
-
| `query_timeout` |
|
|
202
|
+
| `query_timeout` | Number | `5` | positive | SQL query timeout in seconds. A fraction of a second is allowed |
|
|
203
203
|
| `query_row_limit` | Integer | `100` | 1 to 1000 | Maximum rows returned |
|
|
204
204
|
| `query_redacted_columns` | Array | 14 patterns | - | Column names that cause a query to be rejected, on top of a fixed built-in list (see [SECURITY.md](SECURITY.md)) |
|
|
205
|
-
| `query_allowed_columns` | Array | `[]` | - | Column names exempted from the built-in
|
|
205
|
+
| `query_allowed_columns` | Array | `[]` | - | Column names exempted from the built-in secret column names and from `query_redacted_columns`. It does not release an `encrypts` column |
|
|
206
206
|
| `allow_query_in_production` | Boolean | `false` | - | Allow `rails_query` tool in production |
|
|
207
207
|
|
|
208
|
+
`rails_query` treats a column as a secret column when one of these holds:
|
|
209
|
+
|
|
210
|
+
- its name is in `query_redacted_columns` or in the built-in list (`password_reset_token`, `remember_token`, `secret`, `private_key`, `authentication_string`, `rolpassword`, `setconfig`);
|
|
211
|
+
- an `encrypts` declaration covers it;
|
|
212
|
+
- its name ends in `password`, `secret`, `token`, `key`, `digest` or `hash`;
|
|
213
|
+
- its name contains `password`, `secret`, `token` or `nonce`;
|
|
214
|
+
- its name is `iv` or ends in `_iv`;
|
|
215
|
+
- its name is secret to the gem's log and config redaction (`passwd`, `salt`, `credentials`, `pepper`, `signing_key` and the rest of its secret words);
|
|
216
|
+
- it is a generated column whose expression reads a secret column.
|
|
217
|
+
|
|
218
|
+
A statement that names a secret column is refused, and a secret column's values come back as `[FILTERED]`. A name in `query_allowed_columns` is not secret. An `encrypts` column, and a generated column that reads one, is the exception: a statement that names it is still refused, and its values stay filtered. On MySQL, MariaDB and SQLite a statement is also refused when it names a view whose definition reads a secret column, or holds a `*` inside a subquery while it names a table with a secret column. On PostgreSQL the view rule holds for a materialized view. [SECURITY.md](SECURITY.md#layer-4---secret-column-rejection) is the full statement of the rule, with how each check reads it and its stated limits.
|
|
219
|
+
|
|
208
220
|
### Logs
|
|
209
221
|
|
|
210
222
|
| Option | Type | Default | Description |
|
|
@@ -224,8 +236,19 @@ its exact name blocks it; see [SECURITY.md](SECURITY.md).
|
|
|
224
236
|
|
|
225
237
|
| Option | Type | Default | Description |
|
|
226
238
|
|:-------|:-----|:--------|:------------|
|
|
227
|
-
| `custom_tools` | Array | `[]` | Additional MCP::Tool classes to register (initializer only - a class reference cannot be written in YAML) |
|
|
228
|
-
| `skip_tools` | Array | `[]` | Built-in tool names to exclude (e.g., `%w[rails_security_scan]`).
|
|
239
|
+
| `custom_tools` | Array | `[]` | Additional MCP::Tool classes to register (initializer only - a class reference cannot be written in YAML). A custom tool takes a built-in's name only when `skip_tools` lists that built-in. One that reuses the old name of a `rails_get_config` section (`rails_get_gems`) is served as its own tool, and the gem's own hints still mean the built-in section: they print `rails_get_config(section:"gems")` |
|
|
240
|
+
| `skip_tools` | Array | `[]` | Built-in tool names to exclude (e.g., `%w[rails_security_scan]`). The notes below this table say what a skipped tool leaves and how the `rails_get_config` sections are skipped |
|
|
241
|
+
|
|
242
|
+
What `skip_tools` does:
|
|
243
|
+
|
|
244
|
+
- A skipped tool is left out of `tools/list`, `tool --list`, the tool guide and the context files.
|
|
245
|
+
- No other tool's answer points at it: the hint that would name it is left out.
|
|
246
|
+
- A custom tool with the same name takes its place and is listed with the app's tools. The gem's own hints, guide steps and composite answers leave it out, since those carry the built-in's arguments.
|
|
247
|
+
- A call that names a skipped tool anyway says it is turned off here. That holds from the CLI, rake, an MCP client and `rails_get_context(include:)`.
|
|
248
|
+
- The ten tools that became `rails_get_config` sections still go here by their old names: `rails_get_env` turns the `env` section off.
|
|
249
|
+
- `rails_get_config` turns off only `app`. With `app` off, `section` is required.
|
|
250
|
+
- The tool leaves `tools/list` once all eleven names are listed.
|
|
251
|
+
- Listing `rails_get_config` while a custom tool has that name gives the custom tool the built-in's place. The ten sections go with the built-in, and a call to an old name such as `rails_get_gems` is then an unknown tool.
|
|
229
252
|
|
|
230
253
|
### Output
|
|
231
254
|
|
data/docs/CUSTOM_TOOLS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Custom Tools
|
|
4
4
|
|
|
5
|
-
**Build your own MCP tools that run alongside the
|
|
5
|
+
**Build your own MCP tools that run alongside the 35 built-in ones.**
|
|
6
6
|
|
|
7
7
|
[Tools Reference](TOOLS.md) · [Configuration](CONFIGURATION.md) · [Architecture](ARCHITECTURE.md) · [FAQ](FAQ.md)
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
> [!NOTE]
|
|
14
|
-
> Custom tools have full access to your Rails environment - ActiveRecord, services, mailers, everything. They appear alongside the
|
|
14
|
+
> Custom tools have full access to your Rails environment - ActiveRecord, services, mailers, everything. They appear alongside the 35 built-in tools in both MCP and CLI.
|
|
15
15
|
|
|
16
16
|
## Creating a custom tool
|
|
17
17
|
|
|
@@ -210,6 +210,12 @@ RailsAiContext.configure do |config|
|
|
|
210
210
|
end
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
+
The replacement is listed with the app's tools. The gem's own hints, the guide's steps and `rails_get_context` leave it out, since they would call it with the built-in's arguments.
|
|
214
|
+
|
|
215
|
+
A custom `rails_get_config` listed in `skip_tools` replaces the built-in with its sections: the old names such as `rails_get_gems` then reach nothing, and no answer points at a section.
|
|
216
|
+
|
|
217
|
+
A custom tool may also reuse one of those old names with the built-in left on. It is served as its own tool. The gem's own hints still mean the built-in section and print it as `rails_get_config(section:"gems")`.
|
|
218
|
+
|
|
213
219
|
## Tips
|
|
214
220
|
|
|
215
221
|
- Custom tools have access to the full Rails environment - ActiveRecord, services, mailers, etc.
|
data/docs/FAQ.md
CHANGED
|
@@ -25,7 +25,7 @@ Claude Code, Cursor, GitHub Copilot, OpenCode, and Codex CLI. Each gets tailored
|
|
|
25
25
|
No. The gem works three ways:
|
|
26
26
|
1. **MCP server** - AI calls tools via the protocol (best experience)
|
|
27
27
|
2. **Static files** - Generated context files (CLAUDE.md, .cursor/rules/, etc.)
|
|
28
|
-
3. **CLI** - Same
|
|
28
|
+
3. **CLI** - Same 35 tools from the terminal, no server needed
|
|
29
29
|
|
|
30
30
|
### Is this safe for production?
|
|
31
31
|
|
|
@@ -55,7 +55,7 @@ Yes. [Copy-paste prompts](PROMPTS.md) has one prompt per setup: the usual instal
|
|
|
55
55
|
|
|
56
56
|
### Can I switch between Gemfile and standalone?
|
|
57
57
|
|
|
58
|
-
Yes, freely. Both write the same context files and serve the same
|
|
58
|
+
Yes, freely. Both write the same context files and serve the same 35 tools; only the commands those files name follow the install (`rails ai:context` and `rails 'ai:tool[...]'` in the Gemfile, `rails-ai-context context` and `rails-ai-context tool ...` standalone). Re-run the install or `init` after switching: it updates the MCP config files and regenerates the context files with the new commands.
|
|
59
59
|
|
|
60
60
|
### My editor opens a folder that holds several apps. Does that work?
|
|
61
61
|
|
|
@@ -96,7 +96,7 @@ Start with `rails_onboard` for an app overview, `rails_analyze_feature` for feat
|
|
|
96
96
|
|
|
97
97
|
### Can I add my own tools?
|
|
98
98
|
|
|
99
|
-
Yes. See [Custom Tools](CUSTOM_TOOLS.md). Create an `MCP::Tool` subclass, register it via `config.custom_tools`, and it appears alongside the
|
|
99
|
+
Yes. See [Custom Tools](CUSTOM_TOOLS.md). Create an `MCP::Tool` subclass, register it via `config.custom_tools`, and it appears alongside the 35 built-in tools, in the generated context files' tool counts and lists too.
|
|
100
100
|
|
|
101
101
|
### Can I remove built-in tools?
|
|
102
102
|
|
|
@@ -110,7 +110,7 @@ The server, the CLI and the generated context files then leave them out alike, s
|
|
|
110
110
|
|
|
111
111
|
### What's the `detail` parameter?
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
Every tool that takes `detail` takes the same three levels: `summary` (compact), `standard` (default), `full` (everything). The generated tool guide lists which tools take it. Start with summary and drill down as needed. This keeps AI context windows lean. Composite tools (`rails_get_context`, `rails_analyze_feature`) do not accept `detail` - they always return their full bundled output.
|
|
114
114
|
|
|
115
115
|
### What are `[VERIFIED]` and `[INFERRED]` tags?
|
|
116
116
|
|
|
@@ -120,7 +120,7 @@ Confidence tags from Prism AST parsing:
|
|
|
120
120
|
|
|
121
121
|
### Does the query tool support all databases?
|
|
122
122
|
|
|
123
|
-
PostgreSQL, MySQL
|
|
123
|
+
PostgreSQL, MySQL (with MariaDB) and SQLite. Each gets database-specific safety mechanisms (read-only transactions, timeouts). On any other adapter the tool answers with the adapter's name and runs nothing. See [Security](SECURITY.md).
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|