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