rails-ai-context 5.17.0 → 5.19.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 +109 -0
- data/CONTEXT.md +17 -0
- data/CONTRIBUTING.md +8 -6
- data/README.md +19 -13
- data/SECURITY.md +1 -1
- data/app/controllers/rails_ai_context/mcp_controller.rb +52 -10
- data/docs/ARCHITECTURE.md +9 -9
- data/docs/CLI.md +2 -2
- data/docs/COMPATIBILITY.md +3 -2
- data/docs/CONFIGURATION.md +3 -3
- data/docs/CUSTOM_TOOLS.md +2 -2
- data/docs/FAQ.md +4 -4
- data/docs/GUIDE.md +19 -16
- data/docs/INTROSPECTORS.md +53 -7
- data/docs/SECURITY.md +2 -2
- data/docs/SETUP.md +2 -2
- data/docs/STANDALONE.md +2 -2
- data/docs/TOOLS.md +50 -2
- data/docs/TROUBLESHOOTING.md +1 -1
- data/docs/_config.yml +1 -1
- data/docs/index.md +3 -3
- data/exe/rails-ai-context +1 -1
- data/lib/rails_ai_context/configuration.rb +1 -1
- data/lib/rails_ai_context/introspector.rb +1 -0
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -0
- data/lib/rails_ai_context/introspectors/active_support_introspector.rb +2 -0
- data/lib/rails_ai_context/introspectors/api_introspector.rb +1 -11
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +31 -15
- data/lib/rails_ai_context/introspectors/autoload_introspector.rb +18 -9
- data/lib/rails_ai_context/introspectors/component_introspector.rb +37 -61
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +50 -20
- data/lib/rails_ai_context/introspectors/env_config_introspector.rb +127 -0
- data/lib/rails_ai_context/introspectors/initializer_introspector.rb +9 -4
- data/lib/rails_ai_context/introspectors/job_introspector.rb +21 -14
- data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +42 -4
- data/lib/rails_ai_context/introspectors/listeners/chained_call_listener.rb +19 -1
- data/lib/rails_ai_context/introspectors/listeners/class_definition_listener.rb +32 -0
- data/lib/rails_ai_context/introspectors/listeners/component_structure_listener.rb +118 -0
- data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +97 -0
- data/lib/rails_ai_context/introspectors/listeners/generic_macro_listener.rb +8 -5
- data/lib/rails_ai_context/introspectors/middleware_introspector.rb +2 -0
- data/lib/rails_ai_context/introspectors/model_introspector.rb +1 -5
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +25 -21
- data/lib/rails_ai_context/introspectors/rake_task_introspector.rb +3 -16
- data/lib/rails_ai_context/introspectors/seeds_introspector.rb +5 -10
- data/lib/rails_ai_context/introspectors/source_introspector.rb +2 -0
- data/lib/rails_ai_context/introspectors/test_introspector.rb +10 -7
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +11 -6
- data/lib/rails_ai_context/introspectors/view_introspector.rb +3 -0
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +5 -0
- data/lib/rails_ai_context/json_budget.rb +301 -0
- data/lib/rails_ai_context/resources.rb +2 -2
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +7 -1
- data/lib/rails_ai_context/server.rb +13 -0
- data/lib/rails_ai_context/tools/base_tool.rb +1 -1
- data/lib/rails_ai_context/tools/get_active_support.rb +107 -0
- data/lib/rails_ai_context/tools/get_autoload.rb +71 -0
- data/lib/rails_ai_context/tools/get_engines.rb +56 -0
- data/lib/rails_ai_context/tools/get_env_config.rb +91 -0
- data/lib/rails_ai_context/tools/get_i18n.rb +135 -0
- data/lib/rails_ai_context/tools/get_mailers.rb +65 -0
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/vfs.rb +4 -4
- data/server.json +4 -4
- metadata +15 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 46645f9291dee2458c1a03529fcf888bfd4d11be1d742310f224ae439baed82f
|
|
4
|
+
data.tar.gz: 6c1a79f00117f9617970e5c0f1380337fdc17578fe350515d1a8d301a0fef3fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ffd81a61373550ad0df085b3c01dc647de386bd79773b148711d8f040969f8a7f71fffb4e059539deef7285fc1fa21480761bd9f04cbaabd7aaaebab0e84e9d
|
|
7
|
+
data.tar.gz: c83fe3ae8dfa8bb663bba3b09dd6cd8b3b29939e2ae7ce6cf379cb9df0f739a2b21e86976a6741bee103240324f6e6118ba1fdf85b70d36927a702521e48a159
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,115 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [5.19.0] - 2026-08-09
|
|
9
|
+
|
|
10
|
+
### Added - 6 new tools surfacing previously unserved introspection (45 tools total)
|
|
11
|
+
|
|
12
|
+
An audit found five introspectors whose data never reached the tool
|
|
13
|
+
surface (three only served context files; `:autoload` and
|
|
14
|
+
`:active_support` were unreachable entirely), plus one nervous-system
|
|
15
|
+
gap nothing introspected. All six are now first-class tools, registered
|
|
16
|
+
automatically in both MCP and CLI:
|
|
17
|
+
|
|
18
|
+
- **`rails_get_i18n`** - default/available locales, backend, locale files with
|
|
19
|
+
key counts, per-locale coverage vs the default locale, and fallbacks
|
|
20
|
+
(data: `:i18n` introspector, previously serializer-only).
|
|
21
|
+
- **`rails_get_mailers`** - every ActionMailer class with its delivery actions
|
|
22
|
+
and delivery method (data: `:jobs` introspector's mailer extraction,
|
|
23
|
+
previously serializer-only). Filter with `mailer:"UserMailer"`.
|
|
24
|
+
- **`rails_get_engines`** - engines mounted in `config/routes.rb` with
|
|
25
|
+
known-engine descriptions, plus loaded engine classes with route/model
|
|
26
|
+
counts (data: `:engines` introspector, previously resource-only).
|
|
27
|
+
- **`rails_get_autoload`** - Zeitwerk vs Classic mode, autoloaders with
|
|
28
|
+
collapsed/ignored dirs, autoload/eager-load paths, and custom inflections
|
|
29
|
+
(data: `:autoload` introspector, previously unreachable).
|
|
30
|
+
- **`rails_get_active_support`** - concerns registry, deprecators,
|
|
31
|
+
MessageVerifier/MessageEncryptor usage, tagged logging, subscribed
|
|
32
|
+
`on_load` hooks, and cache store (data: `:active_support` introspector,
|
|
33
|
+
previously unreachable).
|
|
34
|
+
- **`rails_get_env_config`** - per-environment configuration from
|
|
35
|
+
`config/environments/*.rb`: notable toggles (`force_ssl`, `eager_load`,
|
|
36
|
+
caching, log level, queue adapter, mailer delivery) and every config key
|
|
37
|
+
each environment sets. Backed by the new **EnvConfigIntrospector**
|
|
38
|
+
(40 introspectors total, wired into `PRESETS[:full]`; file-based, so it
|
|
39
|
+
also works in the static tier). Config keys and values are read with
|
|
40
|
+
`ConfigAssignmentListener`, so a multi-line value, an assignment nested in
|
|
41
|
+
a conditional, and the `Rails.application.config.x = y` form all read
|
|
42
|
+
correctly. The key list pages with `offset`/`limit`.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **Engine-mounted MCP returns JSON-RPC errors instead of Rails 500s.**
|
|
47
|
+
`McpController#handle` had no rescue around `handle_request` - a
|
|
48
|
+
transport-level exception escaped into a generic Rails HTML 500, breaking
|
|
49
|
+
the client's JSON-RPC loop. It now answers 500 with a JSON-RPC `-32603`
|
|
50
|
+
body, mirroring `RailsAiContext::Middleware`.
|
|
51
|
+
- **Standalone HTTP transport survives transport exceptions.** The Rack
|
|
52
|
+
lambda behind `rails-ai-context serve --transport http` let a
|
|
53
|
+
`handle_request` exception propagate to rackup (dropped connection). It
|
|
54
|
+
now returns the same JSON-RPC `-32603` body.
|
|
55
|
+
- **MCP resources honor `max_tool_response_chars` without breaking the JSON
|
|
56
|
+
contract.** Static resource, model, and VFS routes payloads were emitted
|
|
57
|
+
unbounded (a huge schema or routes table rode a single JSON-RPC frame).
|
|
58
|
+
They now fit the cap by dropping whole elements from the data rather than
|
|
59
|
+
slicing the serialized string, so a capped payload still parses as the
|
|
60
|
+
`application/json` it is labeled. What was dropped is reported under a
|
|
61
|
+
`_truncated` key, which also counts any over-long string value that had to
|
|
62
|
+
be cut. New `RailsAiContext::JsonBudget` owns the reduction.
|
|
63
|
+
- **A committed SSE stream is no longer overwritten by the error handler.**
|
|
64
|
+
`McpController#handle`'s rescue set a status, headers, and a JSON body on
|
|
65
|
+
responses that were already on the wire - closing a stream commits it, so
|
|
66
|
+
every streaming failure reached the rescue committed. Assigning a body
|
|
67
|
+
there swapped the stream out from under the thread draining it, turning a
|
|
68
|
+
truncated SSE response into a garbled one. Committed failures now re-raise
|
|
69
|
+
to `ActionController::Live`, which logs them with a backtrace and closes
|
|
70
|
+
the connection; uncommitted failures still get the JSON-RPC `-32603` body.
|
|
71
|
+
- **`server.json` tool count** said 38 while the gem served 39; now tracks
|
|
72
|
+
the real count (45). Broken `RAILS_NERVOUS_SYSTEM.md` link in
|
|
73
|
+
`docs/INTROSPECTORS.md` replaced with a plain reference.
|
|
74
|
+
|
|
75
|
+
## [5.18.0] - 2026-08-09
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
|
|
79
|
+
- **Three new Prism listeners.** `ConfigAssignmentListener` reads
|
|
80
|
+
`config.key = value` and `config.key.subkey = value` in initializers, matching
|
|
81
|
+
the root anywhere in the chain so `Rails.application.config.assets.paths`
|
|
82
|
+
resolves too. `ComponentStructureListener` reads ViewComponent and Phlex
|
|
83
|
+
structure: `renders_one`/`renders_many`, slot methods, constant tables,
|
|
84
|
+
`case @ivar` variant branching, and `CONST[@ivar]` indexing.
|
|
85
|
+
`ClassDefinitionListener` reads class definitions with their superclass.
|
|
86
|
+
- **`docs/INTROSPECTORS.md` documents the listener catalogue**, how to add a
|
|
87
|
+
listener, and when regex is the right tool instead of the AST.
|
|
88
|
+
|
|
89
|
+
### Changed
|
|
90
|
+
|
|
91
|
+
- **Auth, component, channel, controller, inflection and initializer reading
|
|
92
|
+
moved from regex to the AST.** Devise and Doorkeeper settings, devise-jwt
|
|
93
|
+
detection, ViewComponent and Phlex structure, Action Cable `identified_by` /
|
|
94
|
+
`stream_from` / `stream_for` / `periodically`, `rate_limit` options, custom
|
|
95
|
+
inflections, CORS origins, RSpec helper `include`s, `DatabaseCleaner.strategy`
|
|
96
|
+
and model class detection are all read structurally now. Formatting that used
|
|
97
|
+
to defeat the patterns (multi-line arguments, adjacent string literals,
|
|
98
|
+
`%i[]` and `%w[]` forms) is read correctly.
|
|
99
|
+
- **A filter's `if:` condition reports the action it names.** A lambda has no
|
|
100
|
+
literal value, so `if: -> { action_name == "create" }` used to surface as
|
|
101
|
+
`[INFERRED]`; it now reads `action_name == "create"`. Same key, same type.
|
|
102
|
+
- **`rate_limit_parsed[:within]` no longer keeps a trailing comma.**
|
|
103
|
+
`within: 1.minute, only: :create` returned `"1.minute,"` and now returns
|
|
104
|
+
`"1.minute"`.
|
|
105
|
+
- **Initializer `setup_calls` sees more.** The old pattern only matched a line
|
|
106
|
+
beginning with `config.`, so `Rails.application.config.x = y` and multi-line
|
|
107
|
+
chains were missed.
|
|
108
|
+
- **Every remaining regex over Ruby source carries a note** saying why regex is
|
|
109
|
+
right there: non-Ruby files, mixed-extension globs, vocabulary matching, or
|
|
110
|
+
scope the listeners cannot see.
|
|
111
|
+
|
|
112
|
+
### Fixed
|
|
113
|
+
|
|
114
|
+
- **Dead namespace-tracking loop removed from `RakeTaskIntrospector`**, which
|
|
115
|
+
walked every line of every `.rake` file and discarded the result.
|
|
116
|
+
|
|
8
117
|
## [5.17.0] - 2026-08-09
|
|
9
118
|
|
|
10
119
|
### Added
|
data/CONTEXT.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Context
|
|
2
|
+
|
|
3
|
+
Terms this project uses in a narrower sense than everyday English. One entry per term that has caused a naming collision or an ambiguous read.
|
|
4
|
+
|
|
5
|
+
## Environment
|
|
6
|
+
|
|
7
|
+
Overloaded. Always qualify it; never use "environment" bare in a tool name, an introspector name, or a config key.
|
|
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 `rails_get_env`, read by `EnvIntrospector`.
|
|
10
|
+
|
|
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_env_config`, read by `EnvConfigIntrospector`.
|
|
12
|
+
|
|
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
|
+
|
|
15
|
+
## Static tier
|
|
16
|
+
|
|
17
|
+
The mode where the app did not boot, or `--no-boot` was passed. An introspector answers in this tier only if it defines `static_call`. Two shapes count as defining it: reading a different source (parsing `db/schema.rb` instead of querying the connection), and reading the same source a booted app would (a file-based introspector, where `static_call` is the same work under another name).
|
data/CONTRIBUTING.md
CHANGED
|
@@ -19,8 +19,8 @@ The test suite uses [Combustion](https://github.com/pat/combustion) to boot a mi
|
|
|
19
19
|
```
|
|
20
20
|
lib/rails_ai_context/
|
|
21
21
|
├── cli/ # CLI tool runner (tool_runner.rb) - executes MCP tools from rake/Thor
|
|
22
|
-
├── introspectors/ #
|
|
23
|
-
├── tools/ #
|
|
22
|
+
├── introspectors/ # 40 introspectors (schema, models, routes, etc.)
|
|
23
|
+
├── tools/ # 45 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)
|
|
@@ -51,11 +51,13 @@ Listeners extract specific concerns (associations, validations, etc.) from the A
|
|
|
51
51
|
1. Create `lib/rails_ai_context/introspectors/listeners/your_listener.rb` inheriting from `BaseListener`
|
|
52
52
|
2. Implement `on_call_node_enter(node)` and/or `on_def_node_enter(node)` - only the events your concern needs
|
|
53
53
|
3. Use `confidence_for(node)` from `BaseListener` to tag results `[VERIFIED]` or `[INFERRED]`
|
|
54
|
-
4. Store results in `@results` (accessed via `#results`)
|
|
55
|
-
5.
|
|
56
|
-
6.
|
|
54
|
+
4. Store results in `@results` (accessed via `#results`) as plain hashes, never Prism nodes
|
|
55
|
+
5. If your listener needs an event `SourceIntrospector.register_listener` doesn't already wire up, add it there
|
|
56
|
+
6. Register the key/class pair in `SourceIntrospector::LISTENER_MAP` only if the listener should run on every model walk; listeners used by one introspector are passed to `SourceIntrospector.walk(path, key => Listener)` at the call site instead
|
|
57
|
+
7. Write specs in `spec/lib/rails_ai_context/introspectors/listeners/your_listener_spec.rb`
|
|
58
|
+
8. Add a row to the listener catalogue in `docs/INTROSPECTORS.md`
|
|
57
59
|
|
|
58
|
-
See existing listeners in `lib/rails_ai_context/introspectors/listeners/` for reference patterns.
|
|
60
|
+
See existing listeners in `lib/rails_ai_context/introspectors/listeners/` for reference patterns, and `docs/INTROSPECTORS.md` for the catalogue and the AST-vs-regex rule.
|
|
59
61
|
|
|
60
62
|
## Adding a CLI Tool Interface
|
|
61
63
|
|
data/README.md
CHANGED
|
@@ -68,7 +68,7 @@ rails-ai-context serve # start MCP server
|
|
|
68
68
|
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
|
-
Now your AI doesn't guess - it **asks your app directly.**
|
|
71
|
+
Now your AI doesn't guess - it **asks your app directly.** 45 tools and 5 resource templates that query your schema, models, routes, controllers, views, and conventions on demand. Model introspection uses Prism AST parsing - every result carries a `[VERIFIED]` or `[INFERRED]` confidence tag so AI knows what's ground truth and what needs runtime checking.
|
|
72
72
|
|
|
73
73
|
<br>
|
|
74
74
|
|
|
@@ -163,7 +163,7 @@ Native Rails controller transport. No separate process needed.
|
|
|
163
163
|
|
|
164
164
|
### CLI
|
|
165
165
|
|
|
166
|
-
Same
|
|
166
|
+
Same 45 tools, no server needed. Works in any terminal, any AI tool.
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
169
|
rails 'ai:tool[search_code]' pattern="publishable?" match_type=trace
|
|
@@ -245,7 +245,7 @@ rails 'ai:tool[stimulus]' controller=chart
|
|
|
245
245
|
|
|
246
246
|
<br>
|
|
247
247
|
|
|
248
|
-
##
|
|
248
|
+
## 45 Tools
|
|
249
249
|
|
|
250
250
|
Every tool is **read-only** and returns data verified against your actual app - not guesses, not training data.
|
|
251
251
|
|
|
@@ -331,6 +331,12 @@ Every tool is **read-only** and returns data verified against your actual app -
|
|
|
331
331
|
| `get_service_pattern` | Interface, dependencies, side effects, callers |
|
|
332
332
|
| `get_job_pattern` | Queue, retries, guard clauses, broadcasts, schedules |
|
|
333
333
|
| `get_component_catalog` | ViewComponent/Phlex: props, slots, previews, sidecar assets |
|
|
334
|
+
| `get_i18n` | Locales, translation files with key counts, per-locale coverage, fallbacks |
|
|
335
|
+
| `get_mailers` | Mailer classes with delivery actions and delivery method |
|
|
336
|
+
| `get_engines` | Mounted engines (with known-engine descriptions) + loaded engine classes |
|
|
337
|
+
| `get_autoload` | Zeitwerk mode, autoload/eager-load paths, collapsed dirs, custom inflections |
|
|
338
|
+
| `get_active_support` | Concerns registry, deprecators, MessageVerifier usage, on_load hooks, cache store |
|
|
339
|
+
| `get_env_config` | Per-environment config: notable toggles + every config key each env sets |
|
|
334
340
|
|
|
335
341
|
</details>
|
|
336
342
|
|
|
@@ -351,7 +357,7 @@ Every tool is **read-only** and returns data verified against your actual app -
|
|
|
351
357
|
|
|
352
358
|
</details>
|
|
353
359
|
|
|
354
|
-
> **[All
|
|
360
|
+
> **[All 45 tools with parameters →](docs/TOOLS.md)** | **[Real-world recipes →](docs/RECIPES.md)**
|
|
355
361
|
|
|
356
362
|
<br>
|
|
357
363
|
|
|
@@ -397,12 +403,12 @@ Enabled by default. Disable with `config.anti_hallucination_rules = false` if yo
|
|
|
397
403
|
|
|
398
404
|
```mermaid
|
|
399
405
|
graph TD
|
|
400
|
-
A["Your Rails App\nmodels + schema + routes + controllers + views + jobs"] -->|"
|
|
406
|
+
A["Your Rails App\nmodels + schema + routes + controllers + views + jobs"] -->|"40 introspectors"| B
|
|
401
407
|
|
|
402
408
|
B["rails-ai-context\nPrism AST parsing · Cached · Confidence-tagged\nVFS: rails-ai-context:// URIs introspected fresh"]
|
|
403
409
|
|
|
404
|
-
B --> C["MCP Server\nstdio / HTTP\
|
|
405
|
-
B --> D["CLI Tools\nRake / Thor\nSame
|
|
410
|
+
B --> C["MCP Server\nstdio / HTTP\n45 tools · 5 templates"]
|
|
411
|
+
B --> D["CLI Tools\nRake / Thor\nSame 45 tools"]
|
|
406
412
|
B --> E["Static Files\nCLAUDE.md · .cursor/rules/ · .cursorrules\n.github/instructions/"]
|
|
407
413
|
|
|
408
414
|
style A fill:#4a9eff,stroke:#2d7ad4,color:#fff
|
|
@@ -440,7 +446,7 @@ Both paths ask which AI tools you use (Claude Code, Cursor, GitHub Copilot, Open
|
|
|
440
446
|
| In-Gemfile | Standalone | What it does |
|
|
441
447
|
|:-----------|:-----------|:------------|
|
|
442
448
|
| `rails ai:context` | `rails-ai-context context` | Generate context files |
|
|
443
|
-
| `rails 'ai:tool[NAME]'` | `rails-ai-context tool NAME` | Run any of the
|
|
449
|
+
| `rails 'ai:tool[NAME]'` | `rails-ai-context tool NAME` | Run any of the 45 tools |
|
|
444
450
|
| `rails ai:tool` | `rails-ai-context tool --list` | List all available tools |
|
|
445
451
|
| `rails ai:serve` | `rails-ai-context serve` | Start MCP server (stdio) |
|
|
446
452
|
| `rails ai:doctor` | `rails-ai-context doctor` | Diagnostics + AI readiness score |
|
|
@@ -491,13 +497,13 @@ empty listings.
|
|
|
491
497
|
| | |
|
|
492
498
|
|:------|:------------|
|
|
493
499
|
| **[Quickstart](docs/QUICKSTART.md)** | 5-minute getting started |
|
|
494
|
-
| **[Tools Reference](docs/TOOLS.md)** | All
|
|
500
|
+
| **[Tools Reference](docs/TOOLS.md)** | All 45 tools with every parameter |
|
|
495
501
|
| **[Recipes](docs/RECIPES.md)** | Real-world workflows and examples |
|
|
496
502
|
| **[Custom Tools](docs/CUSTOM_TOOLS.md)** | Build and test your own MCP tools |
|
|
497
503
|
| **[Configuration](docs/CONFIGURATION.md)** | 40+ config options with defaults |
|
|
498
504
|
| **[AI Tool Setup](docs/SETUP.md)** | Claude, Cursor, Copilot, OpenCode, Codex |
|
|
499
505
|
| **[Architecture](docs/ARCHITECTURE.md)** | System design and internals |
|
|
500
|
-
| **[Introspectors](docs/INTROSPECTORS.md)** | All
|
|
506
|
+
| **[Introspectors](docs/INTROSPECTORS.md)** | All 40 introspectors and AST engine |
|
|
501
507
|
| **[Security](docs/SECURITY.md)** | 4-layer SQL safety and file blocking |
|
|
502
508
|
| **[CLI Reference](docs/CLI.md)** | Commands and argument syntax |
|
|
503
509
|
| **[Standalone](docs/STANDALONE.md)** | Use without Gemfile entry |
|
|
@@ -509,7 +515,7 @@ empty listings.
|
|
|
509
515
|
|
|
510
516
|
## Build your own tools
|
|
511
517
|
|
|
512
|
-
Register custom MCP tools alongside the
|
|
518
|
+
Register custom MCP tools alongside the 45 built-in ones:
|
|
513
519
|
|
|
514
520
|
```ruby
|
|
515
521
|
# app/mcp_tools/rails_get_business_metrics.rb
|
|
@@ -547,7 +553,7 @@ if defined?(RailsAiContext)
|
|
|
547
553
|
RailsAiContext.configure do |config|
|
|
548
554
|
config.ai_tools = %i[claude cursor] # Which AI tools to generate for
|
|
549
555
|
config.tool_mode = :mcp # :mcp (default) or :cli
|
|
550
|
-
config.preset = :full # :full (
|
|
556
|
+
config.preset = :full # :full (40 introspectors) or :standard (17)
|
|
551
557
|
end
|
|
552
558
|
end
|
|
553
559
|
```
|
|
@@ -583,7 +589,7 @@ end
|
|
|
583
589
|
## About
|
|
584
590
|
|
|
585
591
|
Built by a Rails developer with 10+ years of production experience.<br>
|
|
586
|
-
|
|
592
|
+
2727 tests + 158-example e2e harness. 45 tools. 5 resource templates. 40 introspectors. Standalone or in-Gemfile.<br>
|
|
587
593
|
MIT licensed. [Contributions welcome.](CONTRIBUTING.md)
|
|
588
594
|
|
|
589
595
|
<br>
|
data/SECURITY.md
CHANGED
|
@@ -39,7 +39,7 @@ If you discover a security vulnerability in rails-ai-context, please report it r
|
|
|
39
39
|
|
|
40
40
|
## Security Design
|
|
41
41
|
|
|
42
|
-
- All
|
|
42
|
+
- All 45 MCP tools are **read-only** and never modify your application or database.
|
|
43
43
|
- **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.
|
|
44
44
|
- **Path traversal protection** - all file-reading tools validate paths with `File.realpath()` against `Rails.root` to prevent directory escape.
|
|
45
45
|
- **Command injection prevention** - code search uses `Open3.capture2` with array arguments (never shell strings). The `--` flag separator prevents pattern injection.
|
|
@@ -29,15 +29,7 @@ module RailsAiContext
|
|
|
29
29
|
def handle
|
|
30
30
|
status_code, rack_headers, body = self.class.mcp_transport.handle_request(request)
|
|
31
31
|
self.status = status_code
|
|
32
|
-
rack_headers
|
|
33
|
-
# mcp >= 1.0 returns Rack 3-style lowercase header keys. Rails 7.0's
|
|
34
|
-
# response header hash is case-sensitive (Rack 2), so a lowercase
|
|
35
|
-
# "content-type" never registers and Rails falls back to text/html on
|
|
36
|
-
# an otherwise valid JSON body. Write it with the canonical case.
|
|
37
|
-
key = k.casecmp("content-type").zero? ? "Content-Type" : k
|
|
38
|
-
response.headers[key] = v
|
|
39
|
-
end
|
|
40
|
-
|
|
32
|
+
apply_transport_headers(rack_headers)
|
|
41
33
|
if body.respond_to?(:each)
|
|
42
34
|
# Plain enumerable body (initialize, errors, JSON mode): join to a
|
|
43
35
|
# string so Content-Length/ETag semantics stay conventional.
|
|
@@ -75,16 +67,66 @@ module RailsAiContext
|
|
|
75
67
|
else
|
|
76
68
|
self.response_body = body
|
|
77
69
|
end
|
|
70
|
+
rescue => e
|
|
71
|
+
# Once the response is committed the status and headers are already on
|
|
72
|
+
# the wire, so a JSON-RPC frame written here cannot reach the client.
|
|
73
|
+
# Worse, assigning a body swaps the stream out from under the thread
|
|
74
|
+
# still draining the old one, turning a truncated SSE stream into a
|
|
75
|
+
# garbled one. The streaming branch's ensure always closes the stream,
|
|
76
|
+
# and closing commits, so every streaming failure lands here committed.
|
|
77
|
+
# Hand those to Live, which logs them with a backtrace and tears the
|
|
78
|
+
# connection down.
|
|
79
|
+
raise if response.committed?
|
|
80
|
+
|
|
81
|
+
# Mirror Middleware#json_rpc_error_response: a transport failure must
|
|
82
|
+
# still answer in JSON-RPC shape. Without this the exception escapes
|
|
83
|
+
# into a generic Rails 500 (HTML), breaking the client's JSON-RPC loop.
|
|
84
|
+
RailsAiContext.log_warn "[rails-ai-context] MCP request failed: #{e.class}: #{e.message}"
|
|
85
|
+
self.status = 500
|
|
86
|
+
response.headers["Content-Type"] = "application/json"
|
|
87
|
+
self.response_body = {
|
|
88
|
+
jsonrpc: "2.0",
|
|
89
|
+
error: { code: -32603, message: "Internal error: #{e.message}" },
|
|
90
|
+
id: nil
|
|
91
|
+
}.to_json
|
|
78
92
|
end
|
|
79
93
|
|
|
80
94
|
private
|
|
81
95
|
|
|
96
|
+
# Rack 3 transports name their headers in lowercase, and the MCP SDK
|
|
97
|
+
# switched to that in 1.0. Rails 7.0 keeps response headers in a
|
|
98
|
+
# case-sensitive Hash, so a lowercase "content-type" is invisible to the
|
|
99
|
+
# canonical lookup Rails makes when it commits, and it labels the response
|
|
100
|
+
# with its own text/html default - a correct JSON-RPC body under a type no
|
|
101
|
+
# client will parse. Rails 7.1 moved to case-insensitive Rack::Headers and
|
|
102
|
+
# does not have the problem. Only the type needs the canonical spelling,
|
|
103
|
+
# because Rails is the only reader that looks a header up by name; the
|
|
104
|
+
# value is passed through so nothing gains a charset it did not have.
|
|
105
|
+
def apply_transport_headers(rack_headers)
|
|
106
|
+
rack_headers.each do |name, value|
|
|
107
|
+
key = name.to_s.casecmp?("content-type") ? "Content-Type" : name
|
|
108
|
+
response.headers[key] = value
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
82
112
|
def wait_for_stream_close
|
|
83
|
-
sleep 0.5 until
|
|
113
|
+
sleep 0.5 until stream_finished?
|
|
84
114
|
rescue IOError
|
|
85
115
|
nil
|
|
86
116
|
end
|
|
87
117
|
|
|
118
|
+
# A client hangup aborts the buffer instead of closing it, so `closed?`
|
|
119
|
+
# alone leaves this thread parked until the transport's next keepalive
|
|
120
|
+
# write notices the hangup - up to the keepalive interval per dropped
|
|
121
|
+
# client. Live's buffer reports the hangup through `connected?`; the plain
|
|
122
|
+
# buffer used off the streaming path does not define it, so ask first.
|
|
123
|
+
def stream_finished?
|
|
124
|
+
stream = response.stream
|
|
125
|
+
return true if stream.closed?
|
|
126
|
+
|
|
127
|
+
stream.respond_to?(:connected?) && !stream.connected?
|
|
128
|
+
end
|
|
129
|
+
|
|
88
130
|
class << self
|
|
89
131
|
# Class-level memoization - transport persists across requests.
|
|
90
132
|
# Thread-safe: MCP::Server and transport are stateless for reads.
|
data/docs/ARCHITECTURE.md
CHANGED
|
@@ -18,19 +18,19 @@ graph TD
|
|
|
18
18
|
A["models + schema + routes + controllers + views + jobs + config"]
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
A -->|"
|
|
21
|
+
A -->|"40 introspectors"| gem
|
|
22
22
|
|
|
23
23
|
subgraph gem["rails-ai-context"]
|
|
24
24
|
direction TB
|
|
25
25
|
|
|
26
26
|
subgraph engine["Introspection Engine"]
|
|
27
27
|
direction LR
|
|
28
|
-
I["Introspectors\
|
|
29
|
-
AST["AST Engine\nPrism\
|
|
28
|
+
I["Introspectors\n40 modules\nPresets\nCached"]
|
|
29
|
+
AST["AST Engine\nPrism\n24 listeners\nConfidence tags"]
|
|
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\n45 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 45 tools\nNo server needed"]
|
|
44
44
|
S["Serializers\n14 modules\nStatic files\nPer-AI-tool"]
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -73,7 +73,7 @@ sequenceDiagram
|
|
|
73
73
|
alt cache hit (TTL + fingerprint valid)
|
|
74
74
|
Cache-->>TR: cached context
|
|
75
75
|
else cache miss
|
|
76
|
-
Cache->>App: introspect (
|
|
76
|
+
Cache->>App: introspect (40 modules)
|
|
77
77
|
App-->>Cache: structured data
|
|
78
78
|
Cache-->>TR: fresh context
|
|
79
79
|
end
|
|
@@ -101,7 +101,7 @@ flowchart LR
|
|
|
101
101
|
|
|
102
102
|
### Introspectors (`lib/rails_ai_context/introspectors/`)
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
40 modules that extract structured data from your Rails app. Each introspector:
|
|
105
105
|
|
|
106
106
|
- Returns a Hash (never raises - wraps errors in `{ error: msg }`)
|
|
107
107
|
- Is registered in `INTROSPECTOR_MAP` with a symbol key
|
|
@@ -192,7 +192,7 @@ Thor-based CLI that works standalone (no Gemfile entry):
|
|
|
192
192
|
|
|
193
193
|
- `ToolRunner` - Parses CLI args, resolves tool names, executes tools, formats output
|
|
194
194
|
- Supports `--json` mode for machine-readable output
|
|
195
|
-
- Same
|
|
195
|
+
- Same 45 tools available as MCP and CLI
|
|
196
196
|
|
|
197
197
|
### Caching
|
|
198
198
|
|
|
@@ -215,7 +215,7 @@ SHA256-based change detection:
|
|
|
215
215
|
## Key design decisions
|
|
216
216
|
|
|
217
217
|
1. **Official MCP SDK** - Not a custom protocol. Uses `mcp` gem's `MCP::Tool`, `MCP::Server`, transports.
|
|
218
|
-
2. **Read-only tools** - All
|
|
218
|
+
2. **Read-only tools** - All 45 tools annotated as non-destructive. Defense-in-depth for query tool.
|
|
219
219
|
3. **Graceful degradation** - Works without database (parses schema.rb as text), without Brakeman, without ripgrep, without listen gem.
|
|
220
220
|
4. **Zeitwerk autoloading** - Files loaded on-demand. No `require_relative` in the gem.
|
|
221
221
|
5. **Diff-aware generation** - Context file regeneration skips unchanged files using fingerprinting.
|
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 45 tools and functionality.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -40,7 +40,7 @@ rails-ai-context serve --transport http --port 6029 # HTTP transport
|
|
|
40
40
|
|
|
41
41
|
### `tool`
|
|
42
42
|
|
|
43
|
-
Run any of the
|
|
43
|
+
Run any of the 45 MCP tools from the terminal.
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
# Rake syntax
|
data/docs/COMPATIBILITY.md
CHANGED
|
@@ -80,6 +80,7 @@ that define a `static_call` path can answer without a booted app:
|
|
|
80
80
|
| `routes` | `config/routes.rb` parsed with a dedicated Prism listener |
|
|
81
81
|
| `models` | `app/models/**/*.rb` (plus packs/engines/extra paths) parsed, not constantized |
|
|
82
82
|
| `controllers` | `app/controllers/**/*.rb` (plus packs/engines/extra paths) parsed, not constantized |
|
|
83
|
+
| `env_config` | `config/environments/*.rb` read from disk - file-based, so the static tier serves the same data as a booted app |
|
|
83
84
|
|
|
84
85
|
The other 34 introspectors (views, jobs, gems, turbo, i18n, active_storage,
|
|
85
86
|
auth, api, and the rest) have no static path and report `{ unavailable: reason
|
|
@@ -167,11 +168,11 @@ Proof sources:
|
|
|
167
168
|
prints to stdout, writes via the `STDOUT` constant, hangs past the
|
|
168
169
|
timeout) plus `spec/e2e/static_tier_spec.rb` ("broken-boot app over the
|
|
169
170
|
CLI", "broken-boot app over MCP stdio", "syntax error in one model file").
|
|
170
|
-
8. No introspector outside the
|
|
171
|
+
8. No introspector outside the six in the operating-tiers table defines
|
|
171
172
|
`static_call` (`lib/rails_ai_context/introspectors/view_introspector.rb`
|
|
172
173
|
has none); `Introspector#run_introspector` reports `{ unavailable: reason
|
|
173
174
|
}` for every such section regardless of shape.
|
|
174
|
-
9. `spec/e2e/empty_app_spec.rb` - all
|
|
175
|
+
9. `spec/e2e/empty_app_spec.rb` - all 45 built-in tools swept against an app
|
|
175
176
|
with no scaffold, no models, no controllers beyond
|
|
176
177
|
`ApplicationController`, no routes beyond root.
|
|
177
178
|
10. `spec/e2e/massive_app_spec.rb` - `schema`, `model_details`, and `routes`
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -56,7 +56,7 @@ preset: full
|
|
|
56
56
|
|
|
57
57
|
| Option | Type | Default | Description |
|
|
58
58
|
|:-------|:-----|:--------|:------------|
|
|
59
|
-
| `preset` | Symbol | `:full` | `:full` (
|
|
59
|
+
| `preset` | Symbol | `:full` | `:full` (40 introspectors) or `:standard` (17 introspectors) |
|
|
60
60
|
| `context_mode` | Symbol | `:compact` | `:compact` (context files capped at ~150 lines) or `:full` (no line cap) |
|
|
61
61
|
| `introspectors` | Array of symbols | (from preset) | Override the introspector list directly |
|
|
62
62
|
| `generate_root_files` | Boolean | `true` | Set `false` to generate split rules only, no root CLAUDE.md/AGENTS.md |
|
|
@@ -69,7 +69,7 @@ preset: full
|
|
|
69
69
|
|:-------|:-----|:--------|:-----------|:------------|
|
|
70
70
|
| `server_name` | String | `"rails-ai-context"` | - | MCP server name |
|
|
71
71
|
| `cache_ttl` | Integer | `60` | Must be positive | Cache time-to-live in seconds |
|
|
72
|
-
| `max_tool_response_chars` | Integer | `200_000` | Must be positive | Safety cap for tool
|
|
72
|
+
| `max_tool_response_chars` | Integer | `200_000` | Must be positive | Safety cap for tool responses and MCP resource payloads. An over-cap resource keeps its JSON shape: whole elements are dropped and reported under a `_truncated` key |
|
|
73
73
|
| `live_reload` | Symbol/Boolean | `:auto` | - | `:auto` (uses `listen` gem if available), `true`, or `false` |
|
|
74
74
|
| `live_reload_debounce` | Float | `1.5` | - | Seconds to wait before processing file changes |
|
|
75
75
|
| `auto_mount` | Boolean | `false` | - | Auto-mount Rack middleware for HTTP transport |
|
|
@@ -166,7 +166,7 @@ preset: full
|
|
|
166
166
|
|
|
167
167
|
## Presets
|
|
168
168
|
|
|
169
|
-
### `:full` (default) -
|
|
169
|
+
### `:full` (default) - 40 introspectors
|
|
170
170
|
|
|
171
171
|
All available introspectors. Maximum context.
|
|
172
172
|
|
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 45 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 45 built-in tools in both MCP and CLI.
|
|
15
15
|
|
|
16
16
|
## Creating a custom tool
|
|
17
17
|
|
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 45 tools from the terminal, no server needed
|
|
29
29
|
|
|
30
30
|
### Is this safe for production?
|
|
31
31
|
|
|
@@ -47,7 +47,7 @@ Yes. The gem gracefully degrades - it parses `db/schema.rb` as text when no data
|
|
|
47
47
|
|
|
48
48
|
### Can I switch between Gemfile and standalone?
|
|
49
49
|
|
|
50
|
-
Yes, freely. Both generate identical context files and provide the same
|
|
50
|
+
Yes, freely. Both generate identical context files and provide the same 45 tools. Just re-run the install/init to update MCP config files.
|
|
51
51
|
|
|
52
52
|
### Do I need to commit the generated files?
|
|
53
53
|
|
|
@@ -69,7 +69,7 @@ Start with `rails_onboard` for an app overview, `rails_analyze_feature` for feat
|
|
|
69
69
|
|
|
70
70
|
### Can I add my own tools?
|
|
71
71
|
|
|
72
|
-
Yes. See [Custom Tools](CUSTOM_TOOLS.md). Create an `MCP::Tool` subclass, register it via `config.custom_tools`, and it appears alongside the
|
|
72
|
+
Yes. See [Custom Tools](CUSTOM_TOOLS.md). Create an `MCP::Tool` subclass, register it via `config.custom_tools`, and it appears alongside the 45 built-in tools.
|
|
73
73
|
|
|
74
74
|
### Can I remove built-in tools?
|
|
75
75
|
|
|
@@ -99,7 +99,7 @@ PostgreSQL, MySQL, and SQLite. Each gets database-specific safety mechanisms (re
|
|
|
99
99
|
|
|
100
100
|
### What's the difference between `:full` and `:standard` preset?
|
|
101
101
|
|
|
102
|
-
- **`:full`** (default) -
|
|
102
|
+
- **`:full`** (default) - 40 introspectors. Full context for every aspect of your app.
|
|
103
103
|
- **`:standard`** - 17 introspectors. Faster, covers the essentials (schema, models, routes, controllers, tests, etc.).
|
|
104
104
|
|
|
105
105
|
### What's `:compact` vs `:full` context mode?
|