rails-ai-context 3.1.0 → 4.0.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 +38 -0
- data/CLAUDE.md +10 -6
- data/CONTRIBUTING.md +2 -2
- data/README.md +15 -10
- data/SECURITY.md +5 -3
- data/docs/GUIDE.md +18 -15
- data/lib/rails_ai_context/configuration.rb +3 -2
- data/lib/rails_ai_context/introspector.rb +3 -0
- data/lib/rails_ai_context/introspectors/accessibility_introspector.rb +188 -0
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +26 -1
- data/lib/rails_ai_context/introspectors/component_introspector.rb +195 -0
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +57 -2
- data/lib/rails_ai_context/introspectors/convention_detector.rb +27 -3
- data/lib/rails_ai_context/introspectors/design_token_introspector.rb +70 -1
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +26 -0
- data/lib/rails_ai_context/introspectors/job_introspector.rb +43 -1
- data/lib/rails_ai_context/introspectors/model_introspector.rb +57 -0
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +293 -0
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +89 -1
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +62 -3
- data/lib/rails_ai_context/introspectors/test_introspector.rb +37 -1
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +101 -1
- data/lib/rails_ai_context/introspectors/view_introspector.rb +62 -1
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +39 -1
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +63 -1
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +21 -0
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +22 -6
- data/lib/rails_ai_context/server.rb +5 -1
- data/lib/rails_ai_context/tools/analyze_feature.rb +44 -0
- data/lib/rails_ai_context/tools/dependency_graph.rb +201 -0
- data/lib/rails_ai_context/tools/get_component_catalog.rb +170 -0
- data/lib/rails_ai_context/tools/get_controllers.rb +18 -0
- data/lib/rails_ai_context/tools/get_conventions.rb +19 -0
- data/lib/rails_ai_context/tools/get_design_system.rb +32 -0
- data/lib/rails_ai_context/tools/get_model_details.rb +27 -0
- data/lib/rails_ai_context/tools/get_schema.rb +26 -0
- data/lib/rails_ai_context/tools/get_stimulus.rb +41 -0
- data/lib/rails_ai_context/tools/get_test_info.rb +28 -0
- data/lib/rails_ai_context/tools/get_turbo_map.rb +81 -14
- data/lib/rails_ai_context/tools/get_view.rb +11 -0
- data/lib/rails_ai_context/tools/migration_advisor.rb +380 -0
- data/lib/rails_ai_context/tools/performance_check.rb +126 -0
- data/lib/rails_ai_context/tools/security_scan.rb +23 -2
- data/lib/rails_ai_context/tools/validate.rb +32 -0
- data/lib/rails_ai_context/version.rb +1 -1
- metadata +11 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c4dfcbb5d44af0ed1defb25731f71a4777f9e0905dd662f0e7fa3ff83a870f02
|
|
4
|
+
data.tar.gz: 8321f5166e78daa800a68c34f05b4b30eb83c6096a43d87916463d4ff7f07ee2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e66d58d898c8136321ba0d7bd3e188a446f219746bbb2073f73bf9b270b7e71ff0e776afa568ab91f9abe4343e9957b066384c63d0571d68592643bf740811ea
|
|
7
|
+
data.tar.gz: fcfc393aa754c35ee729157322e58fd03d47649ddd4c1e4d68ff8e6f40c4498ab2c9375381c28e10b0016880cf0672ed030de927d432762651b32493e7a64889
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,44 @@ 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
|
+
## [4.0.0] — 2026-03-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- 4 new MCP tools: `rails_get_component_catalog`, `rails_performance_check`, `rails_dependency_graph`, `rails_migration_advisor`
|
|
13
|
+
- 3 new introspectors: ComponentIntrospector (ViewComponent/Phlex), AccessibilityIntrospector (ARIA/a11y), PerformanceIntrospector (N+1/indexes)
|
|
14
|
+
- ViewComponent/Phlex component catalog: props, slots, previews, sidecar assets, usage examples
|
|
15
|
+
- Accessibility scanning: ARIA attributes, semantic HTML, screen reader text, alt text, landmark roles, accessibility score
|
|
16
|
+
- Performance analysis: N+1 query risks, missing counter_cache, missing FK indexes, Model.all anti-patterns, eager load candidates
|
|
17
|
+
- Dependency graph generation in Mermaid or text format
|
|
18
|
+
- Migration code generation with reversibility warnings and affected model detection
|
|
19
|
+
- Component and accessibility split rules for Claude, Cursor, Copilot, and OpenCode
|
|
20
|
+
- Stimulus cross-controller composition detection
|
|
21
|
+
- Stimulus import graph and complexity metrics
|
|
22
|
+
- Turbo 8 morph meta and permanent element detection
|
|
23
|
+
- Turbo Drive configuration scanning (data-turbo-*, preload)
|
|
24
|
+
- Form builder detection (form_with, simple_form, formtastic)
|
|
25
|
+
- Semantic HTML element counting
|
|
26
|
+
- DaisyUI theme and component detection
|
|
27
|
+
- Font loading strategy detection (@font-face, Google Fonts, system fonts)
|
|
28
|
+
- CSS @layer and PostCSS plugin detection
|
|
29
|
+
- Convention fingerprint with SolidQueue/SolidCache/SolidCable awareness
|
|
30
|
+
- Dynamic directory detection in app/
|
|
31
|
+
- Controller rate_limit and rescue_from extraction
|
|
32
|
+
- Model encryption, normalizes, and generates_token_for details
|
|
33
|
+
- Schema check constraints, enum types, and generated columns
|
|
34
|
+
- Factory trait extraction and test count by category
|
|
35
|
+
- Expanded NOTABLE_GEMS list (30+ new gems including dry-rb, Solid stack)
|
|
36
|
+
- Job retry_on/discard_on and perform argument extraction
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Standard preset: 13 → 14 introspectors (added :components)
|
|
41
|
+
- Full preset: 28 → 31 introspectors (added :components, :accessibility, :performance)
|
|
42
|
+
- Tool count: 25 → 29
|
|
43
|
+
- Test count: 681 → 806 examples
|
|
44
|
+
- Combustion test app expanded with Stimulus controllers, ViewComponents, accessible views, factories
|
|
45
|
+
|
|
8
46
|
## [3.1.0] - 2026-03-26
|
|
9
47
|
|
|
10
48
|
### Fixed
|
data/CLAUDE.md
CHANGED
|
@@ -8,8 +8,8 @@ structure to AI assistants via the Model Context Protocol (MCP).
|
|
|
8
8
|
- `lib/rails_ai_context.rb` — Main entry point, public API (Zeitwerk autoloaded)
|
|
9
9
|
- `lib/rails_ai_context/configuration.rb` — User-facing config with presets (:standard, :full)
|
|
10
10
|
- `lib/rails_ai_context/introspector.rb` — Orchestrates sub-introspectors
|
|
11
|
-
- `lib/rails_ai_context/introspectors/` —
|
|
12
|
-
- `lib/rails_ai_context/tools/` —
|
|
11
|
+
- `lib/rails_ai_context/introspectors/` — 32 introspectors (schema, models, routes, jobs, gems, conventions, stimulus, database_stats, controllers, views, view_templates, design_tokens, turbo, i18n, config, active_storage, action_text, auth, api, tests, rake_tasks, assets, devops, action_mailbox, migrations, seeds, middleware, engines, multi_database, components, accessibility, performance)
|
|
12
|
+
- `lib/rails_ai_context/tools/` — 29 MCP tools using the official mcp SDK
|
|
13
13
|
- `lib/rails_ai_context/cli/` — CLI tool runner (`tool_runner.rb`) — executes MCP tools from rake/Thor
|
|
14
14
|
- `lib/rails_ai_context/serializers/` — Output formatters (claude, claude_rules, opencode, opencode_rules, cursor_rules, copilot, copilot_instructions, rules, markdown, JSON, context_file_serializer, test_command_detection, tool_guide_helper, design_system_helper, stack_overview_helper)
|
|
15
15
|
- `lib/rails_ai_context/resources.rb` — MCP resources (static data AI clients read directly)
|
|
@@ -34,24 +34,28 @@ structure to AI assistants via the Model Context Protocol (MCP).
|
|
|
34
34
|
7. **Diff-aware** — context regeneration skips unchanged files
|
|
35
35
|
8. **Per-assistant serializers** — each AI tool gets tailored output format
|
|
36
36
|
9. **Zeitwerk autoloading** — files loaded on-demand, not all upfront
|
|
37
|
-
10. **Introspector presets** — `:full` (
|
|
37
|
+
10. **Introspector presets** — `:full` (31) default, `:standard` (14 core) for lightweight usage
|
|
38
38
|
11. **MCP auto-discovery** — `.mcp.json` generated by install generator
|
|
39
39
|
12. **Compact by default** — context files ≤150 lines, MCP tools use `detail` parameter (summary/standard/full)
|
|
40
40
|
13. **Per-tool split rules** — `.claude/rules/`, `.cursor/rules/`, `.github/instructions/`
|
|
41
41
|
14. **Section markers** — root file content wrapped in `<!-- BEGIN/END rails-ai-context -->` to preserve user content
|
|
42
42
|
15. **generate_root_files toggle** — when false, skip root files (CLAUDE.md, etc.), only generate split rules
|
|
43
|
-
16. **custom_tools API** — `config.custom_tools` array lets users register additional MCP::Tool subclasses alongside the
|
|
43
|
+
16. **custom_tools API** — `config.custom_tools` array lets users register additional MCP::Tool subclasses alongside the 29 built-in tools
|
|
44
44
|
17. **Design system extraction** — view templates analyzed for canonical examples, color palette, typography, responsive patterns, interactive states, dark mode
|
|
45
45
|
18. **skip_tools API** — `config.skip_tools` array lets users exclude specific built-in tools (e.g. `%w[rails_security_scan]`)
|
|
46
46
|
19. **Security scanning** — optional Brakeman integration via `rails_security_scan` tool (graceful degradation if not installed)
|
|
47
47
|
20. **tool_mode config** — `:mcp` (default, MCP primary + CLI fallback) or `:cli` (CLI only, no MCP server needed). Selected during install.
|
|
48
|
-
21. **CLI tool access** — all
|
|
48
|
+
21. **CLI tool access** — all 29 MCP tools callable from terminal: `rails ai:tool[schema]`, `rails-ai-context tool schema`. Tool name resolution: `schema` → `get_schema` → `rails_get_schema`.
|
|
49
49
|
22. **Shared ToolGuideHelper** — serializers use a shared module for tool reference sections, rendering MCP or CLI syntax based on `tool_mode`
|
|
50
|
+
23. **Component catalog** — ViewComponent/Phlex introspection: props, slots, previews, sidecar assets, usage examples via `rails_get_component_catalog`
|
|
51
|
+
24. **Accessibility scanning** — ARIA attributes, semantic HTML, screen reader text, alt text, landmark roles, accessibility score via AccessibilityIntrospector
|
|
52
|
+
25. **Performance analysis** — N+1 query risks, missing counter_cache, missing FK indexes, eager load candidates via `rails_performance_check`
|
|
53
|
+
26. **Migration advisor** — migration code generation with reversibility warnings and affected model detection via `rails_migration_advisor`
|
|
50
54
|
|
|
51
55
|
## Testing
|
|
52
56
|
|
|
53
57
|
```bash
|
|
54
|
-
bundle exec rspec # Run specs (
|
|
58
|
+
bundle exec rspec # Run specs (806 examples)
|
|
55
59
|
bundle exec rubocop # Lint
|
|
56
60
|
```
|
|
57
61
|
|
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/ # 32 introspectors (schema, models, routes, etc.)
|
|
23
|
+
├── tools/ # 29 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)
|
data/README.md
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
|
|
10
10
|
**Works with:** Claude Code • Cursor • GitHub Copilot • OpenCode • Any terminal
|
|
11
11
|
|
|
12
|
-
> Built by a Rails developer with 10+ years of production experience. AI assisted — the same way it assists me shipping features at work. I designed the architecture, made every decision, reviewed every line, and wrote
|
|
12
|
+
> Built by a Rails developer with 10+ years of production experience. AI assisted — the same way it assists me shipping features at work. I designed the architecture, made every decision, reviewed every line, and wrote 806 tests. This gem exists because I understand Rails deeply enough to know exactly what AI agents get wrong and what context they need to get it right.
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
gem "rails-ai-context", group: :development
|
|
16
16
|
rails generate rails_ai_context:install
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
That's it. Your AI now has
|
|
19
|
+
That's it. Your AI now has 29 tools that understand your entire Rails app — via MCP server or CLI. Zero config.
|
|
20
20
|
|
|
21
21
|
> **[Full Guide →](docs/GUIDE.md)** — every command, every parameter, every configuration option.
|
|
22
22
|
|
|
@@ -46,7 +46,7 @@ rails 'ai:tool[schema]' table=users
|
|
|
46
46
|
rails 'ai:tool[analyze_feature]' feature=billing
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Same
|
|
49
|
+
Same 29 tools. Same output. AI agents run these as shell commands. **Works in any terminal, any AI tool, any workflow.** No MCP client required.
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
@@ -170,7 +170,7 @@ Tested on a real Rails 8 app (5 models, 19 controllers, 95 routes):
|
|
|
170
170
|
|
|
171
171
|
---
|
|
172
172
|
|
|
173
|
-
##
|
|
173
|
+
## 29 Tools
|
|
174
174
|
|
|
175
175
|
Every tool is **read-only** and returns structured, token-efficient data.
|
|
176
176
|
|
|
@@ -209,6 +209,11 @@ Every tool is **read-only** and returns structured, token-efficient data.
|
|
|
209
209
|
| **Services & Jobs** | | | |
|
|
210
210
|
| `get_service_pattern` | `rails_get_service_pattern` | `rails 'ai:tool[service_pattern]'` | Interface, dependencies, side effects, callers |
|
|
211
211
|
| `get_job_pattern` | `rails_get_job_pattern` | `rails 'ai:tool[job_pattern]'` | Queue, retries, guard clauses, broadcasts, schedules |
|
|
212
|
+
| **Components & Quality** | | | |
|
|
213
|
+
| `get_component_catalog` | `rails_get_component_catalog` | `rails 'ai:tool[component_catalog]'` | ViewComponent/Phlex: props, slots, previews, sidecar assets, usage |
|
|
214
|
+
| `performance_check` | `rails_performance_check` | `rails 'ai:tool[performance_check]'` | N+1 risks, missing indexes, counter_cache, eager load candidates |
|
|
215
|
+
| `dependency_graph` | `rails_dependency_graph` | `rails 'ai:tool[dependency_graph]'` | Model/service dependency graph in Mermaid or text format |
|
|
216
|
+
| `migration_advisor` | `rails_migration_advisor` | `rails 'ai:tool[migration_advisor]'` | Migration code generation with reversibility + affected models |
|
|
212
217
|
|
|
213
218
|
> **[Full parameter docs →](docs/GUIDE.md)**
|
|
214
219
|
|
|
@@ -221,7 +226,7 @@ Every tool is **read-only** and returns structured, token-efficient data.
|
|
|
221
226
|
│ Your Rails App │
|
|
222
227
|
│ models + schema + routes + controllers + views + jobs │
|
|
223
228
|
└────────────────────────┬────────────────────────────────┘
|
|
224
|
-
│ introspects (
|
|
229
|
+
│ introspects (32 introspectors)
|
|
225
230
|
▼
|
|
226
231
|
┌─────────────────────────────────────────────────────────┐
|
|
227
232
|
│ rails-ai-context │
|
|
@@ -231,7 +236,7 @@ Every tool is **read-only** and returns structured, token-efficient data.
|
|
|
231
236
|
▼ ▼ ▼
|
|
232
237
|
┌──────────────────┐ ┌────────────┐ ┌────────────────────┐
|
|
233
238
|
│ Static Files │ │ MCP Server │ │ CLI Tools │
|
|
234
|
-
│ CLAUDE.md │ │
|
|
239
|
+
│ CLAUDE.md │ │ 29 tools │ │ Same 29 tools │
|
|
235
240
|
│ .cursor/rules/ │ │ stdio/HTTP │ │ No server needed │
|
|
236
241
|
│ .github/instr... │ │ .mcp.json │ │ rails 'ai:tool[X]' │
|
|
237
242
|
└──────────────────┘ └────────────┘ └────────────────────┘
|
|
@@ -267,7 +272,7 @@ MCP auto-discovery: `.mcp.json` is detected automatically by Claude Code and Cur
|
|
|
267
272
|
| Command | What it does |
|
|
268
273
|
|---------|-------------|
|
|
269
274
|
| `rails ai:context` | Generate context files for your AI tools |
|
|
270
|
-
| `rails 'ai:tool[NAME]'` | Run any of the
|
|
275
|
+
| `rails 'ai:tool[NAME]'` | Run any of the 29 tools from the CLI |
|
|
271
276
|
| `rails ai:tool` | List all available tools with short names |
|
|
272
277
|
| `rails ai:serve` | Start MCP server (stdio) |
|
|
273
278
|
| `rails ai:doctor` | Diagnostics + AI readiness score |
|
|
@@ -287,7 +292,7 @@ RailsAiContext.configure do |config|
|
|
|
287
292
|
# Tool mode: :mcp (default, MCP + CLI fallback) or :cli (CLI only)
|
|
288
293
|
# config.tool_mode = :mcp
|
|
289
294
|
|
|
290
|
-
# Presets: :full (
|
|
295
|
+
# Presets: :full (31 introspectors, default) or :standard (14 core)
|
|
291
296
|
# config.preset = :full
|
|
292
297
|
|
|
293
298
|
# Exclude models from introspection
|
|
@@ -305,7 +310,7 @@ end
|
|
|
305
310
|
|--------|---------|-------------|
|
|
306
311
|
| **Presets & Introspectors** | | |
|
|
307
312
|
| `preset` | `:full` | Introspector preset (`:full` or `:standard`) |
|
|
308
|
-
| `introspectors` |
|
|
313
|
+
| `introspectors` | 31 (full) | Array of introspector symbols |
|
|
309
314
|
| **Context Generation** | | |
|
|
310
315
|
| `context_mode` | `:compact` | `:compact` (≤150 lines) or `:full` (dump everything) |
|
|
311
316
|
| `claude_max_lines` | `150` | Max lines for CLAUDE.md in compact mode |
|
|
@@ -340,7 +345,7 @@ end
|
|
|
340
345
|
```bash
|
|
341
346
|
git clone https://github.com/crisnahine/rails-ai-context.git
|
|
342
347
|
cd rails-ai-context && bundle install
|
|
343
|
-
bundle exec rspec #
|
|
348
|
+
bundle exec rspec # 806 examples
|
|
344
349
|
bundle exec rubocop # Lint
|
|
345
350
|
```
|
|
346
351
|
|
data/SECURITY.md
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
| Version | Supported |
|
|
6
6
|
|---------|--------------------|
|
|
7
|
-
|
|
|
8
|
-
|
|
|
7
|
+
| 4.0.x | :white_check_mark: |
|
|
8
|
+
| 3.1.x | :white_check_mark: |
|
|
9
|
+
| 3.0.x | :x: |
|
|
10
|
+
| 2.0.x | :x: |
|
|
9
11
|
| < 2.0 | :x: |
|
|
10
12
|
|
|
11
13
|
## Reporting a Vulnerability
|
|
@@ -22,7 +24,7 @@ If you discover a security vulnerability in rails-ai-context, please report it r
|
|
|
22
24
|
|
|
23
25
|
## Security Design
|
|
24
26
|
|
|
25
|
-
- All
|
|
27
|
+
- All 29 MCP tools are **read-only** and never modify your application or database.
|
|
26
28
|
- **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.
|
|
27
29
|
- **Path traversal protection** — all file-reading tools validate paths with `File.realpath()` against `Rails.root` to prevent directory escape.
|
|
28
30
|
- **Command injection prevention** — code search uses `Open3.capture2` with array arguments (never shell strings). The `--` flag separator prevents pattern injection.
|
data/docs/GUIDE.md
CHANGED
|
@@ -40,7 +40,7 @@ rails ai:context
|
|
|
40
40
|
This creates:
|
|
41
41
|
1. `config/initializers/rails_ai_context.rb` — configuration file
|
|
42
42
|
2. `.mcp.json` — MCP auto-discovery for Claude Code and Cursor
|
|
43
|
-
3.
|
|
43
|
+
3. 29 context files — tailored for each AI assistant
|
|
44
44
|
|
|
45
45
|
### Existing project
|
|
46
46
|
|
|
@@ -124,7 +124,7 @@ end
|
|
|
124
124
|
|
|
125
125
|
## Generated Files
|
|
126
126
|
|
|
127
|
-
`rails ai:context` generates **
|
|
127
|
+
`rails ai:context` generates **29 files** across all AI assistants:
|
|
128
128
|
|
|
129
129
|
### Claude Code (6 files)
|
|
130
130
|
|
|
@@ -184,7 +184,7 @@ Commit **all files except `.ai-context.json`** (which is gitignored). This gives
|
|
|
184
184
|
|
|
185
185
|
| Command | Mode | Format | Description |
|
|
186
186
|
|---------|------|--------|-------------|
|
|
187
|
-
| `rails ai:context` | compact | all | Generate all
|
|
187
|
+
| `rails ai:context` | compact | all | Generate all 29 context files |
|
|
188
188
|
| `rails ai:context:full` | full | all | Generate all files in full mode |
|
|
189
189
|
| `rails ai:context:claude` | compact | Claude | CLAUDE.md + .claude/rules/ |
|
|
190
190
|
| `rails ai:context:opencode` | compact | OpenCode | AGENTS.md + per-directory AGENTS.md |
|
|
@@ -252,7 +252,7 @@ rails ai:context:claude # Use this instead (no quoting needed)
|
|
|
252
252
|
|
|
253
253
|
## CLI Tools
|
|
254
254
|
|
|
255
|
-
All
|
|
255
|
+
All 29 MCP tools can be run directly from the terminal — no MCP server or AI client needed.
|
|
256
256
|
|
|
257
257
|
### Rake
|
|
258
258
|
|
|
@@ -316,7 +316,7 @@ The `tool_mode` is selected during `rails generate rails_ai_context:install`.
|
|
|
316
316
|
|
|
317
317
|
## MCP Tools — Full Reference
|
|
318
318
|
|
|
319
|
-
All
|
|
319
|
+
All 29 tools are **read-only** and **idempotent** — they never modify your application or database.
|
|
320
320
|
|
|
321
321
|
### rails_get_schema
|
|
322
322
|
|
|
@@ -996,7 +996,7 @@ All tools that support `detail` use these three levels. Default limits vary by t
|
|
|
996
996
|
| Level | What it returns | Schema default limit | Best for |
|
|
997
997
|
|-------|----------------|---------------------|----------|
|
|
998
998
|
| `summary` | Names + counts | 50 | Getting the landscape, understanding what exists |
|
|
999
|
-
| `standard` | Names + key details |
|
|
999
|
+
| `standard` | Names + key details | 29 | Working context, column types, action names |
|
|
1000
1000
|
| `full` | Everything | 10 | Deep inspection, indexes, FKs, constraints |
|
|
1001
1001
|
|
|
1002
1002
|
Other tools default to higher limits (e.g. models/controllers/stimulus: 50 for all levels, routes: 100/200).
|
|
@@ -1117,7 +1117,7 @@ RailsAiContext.configure do |config|
|
|
|
1117
1117
|
end
|
|
1118
1118
|
```
|
|
1119
1119
|
|
|
1120
|
-
Both transports are **read-only** — they expose the same
|
|
1120
|
+
Both transports are **read-only** — they expose the same 29 tools and never modify your app.
|
|
1121
1121
|
|
|
1122
1122
|
---
|
|
1123
1123
|
|
|
@@ -1128,7 +1128,7 @@ Both transports are **read-only** — they expose the same 25 tools and never mo
|
|
|
1128
1128
|
RailsAiContext.configure do |config|
|
|
1129
1129
|
# --- Introspectors ---
|
|
1130
1130
|
|
|
1131
|
-
# Presets: :full (
|
|
1131
|
+
# Presets: :full (31 introspectors, default) or :standard (14 core)
|
|
1132
1132
|
config.preset = :full
|
|
1133
1133
|
|
|
1134
1134
|
# Cherry-pick on top of a preset
|
|
@@ -1300,7 +1300,7 @@ All split rules include an app overview file, so no context is lost when root fi
|
|
|
1300
1300
|
|
|
1301
1301
|
## Introspectors — Full List
|
|
1302
1302
|
|
|
1303
|
-
### Standard preset (
|
|
1303
|
+
### Standard preset (14 introspectors)
|
|
1304
1304
|
|
|
1305
1305
|
Core Rails structure only. Use `config.preset = :standard` for a lighter footprint.
|
|
1306
1306
|
|
|
@@ -1319,8 +1319,9 @@ Core Rails structure only. Use `config.preset = :standard` for a lighter footpri
|
|
|
1319
1319
|
| `stimulus` | Stimulus controllers with targets, values (with types), actions, outlets, classes. Extracted from JS/TS files. |
|
|
1320
1320
|
| `view_templates` | View file contents, partial references, Stimulus data attributes, UI pattern extraction, model field usage in partials. |
|
|
1321
1321
|
| `design_tokens` | Auto-detects CSS framework (Tailwind v3/v4, Bootstrap, Sass, plain CSS) and extracts design tokens from config files and built CSS. |
|
|
1322
|
+
| `components` | ViewComponent/Phlex components: props, slots, previews, sidecar assets, usage examples. |
|
|
1322
1323
|
|
|
1323
|
-
### Full preset (
|
|
1324
|
+
### Full preset (31 introspectors) — default
|
|
1324
1325
|
|
|
1325
1326
|
Includes all standard introspectors plus:
|
|
1326
1327
|
|
|
@@ -1341,6 +1342,8 @@ Includes all standard introspectors plus:
|
|
|
1341
1342
|
| `middleware` | Custom Rack middleware in app/middleware/ with detected patterns (auth, rate limiting, tenant isolation, logging). Full middleware stack. |
|
|
1342
1343
|
| `engines` | Mounted Rails engines from routes.rb with paths and descriptions for 23+ known engines (Sidekiq::Web, Flipper::UI, PgHero, ActiveAdmin, etc.). |
|
|
1343
1344
|
| `multi_database` | Multiple databases, replicas, sharding config, model-specific `connects_to` declarations. database.yml parsing fallback. |
|
|
1345
|
+
| `accessibility` | ARIA attributes, semantic HTML elements, screen reader text, alt text coverage, landmark roles, accessibility score. |
|
|
1346
|
+
| `performance` | N+1 query risks, missing counter_cache, missing FK indexes, Model.all anti-patterns, eager load candidates. |
|
|
1344
1347
|
| `database_stats` | PostgreSQL approximate row counts via `pg_stat_user_tables`. **Opt-in only** — not in any preset, add manually: `config.introspectors += [:database_stats]`. |
|
|
1345
1348
|
|
|
1346
1349
|
### Using the standard preset
|
|
@@ -1432,11 +1435,11 @@ OpenCode uses **per-directory lazy-loading**: when the agent reads a file, it wa
|
|
|
1432
1435
|
|
|
1433
1436
|
| Setup | Coverage | Notes |
|
|
1434
1437
|
|-------|----------|-------|
|
|
1435
|
-
| Rails full-stack (ERB + Hotwire) |
|
|
1436
|
-
| Rails + Inertia.js (React/Vue) | ~
|
|
1437
|
-
| Rails API + React/Next.js SPA | ~
|
|
1438
|
-
| Rails API + mobile app | ~
|
|
1439
|
-
| Rails engine (mountable gem) | ~
|
|
1438
|
+
| Rails full-stack (ERB + Hotwire) | 32/32 | All introspectors relevant |
|
|
1439
|
+
| Rails + Inertia.js (React/Vue) | ~25/32 | Views/Turbo partially useful, backend fully covered |
|
|
1440
|
+
| Rails API + React/Next.js SPA | ~23/32 | Schema, models, routes, API, auth, jobs — all covered |
|
|
1441
|
+
| Rails API + mobile app | ~23/32 | Same as SPA — backend introspection is identical |
|
|
1442
|
+
| Rails engine (mountable gem) | ~18/32 | Core introspectors (schema, models, routes, gems) work |
|
|
1440
1443
|
|
|
1441
1444
|
Frontend introspectors (views, Turbo, Stimulus, assets) degrade gracefully — they report nothing when those features aren't present.
|
|
1442
1445
|
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
module RailsAiContext
|
|
4
4
|
class Configuration
|
|
5
5
|
PRESETS = {
|
|
6
|
-
standard: %i[schema models routes jobs gems conventions controllers tests migrations stimulus view_templates design_tokens config],
|
|
6
|
+
standard: %i[schema models routes jobs gems conventions controllers tests migrations stimulus view_templates design_tokens config components],
|
|
7
7
|
full: %i[schema models routes jobs gems conventions stimulus controllers views view_templates design_tokens turbo
|
|
8
8
|
i18n config active_storage action_text auth api tests rake_tasks assets
|
|
9
|
-
devops action_mailbox migrations seeds middleware engines multi_database
|
|
9
|
+
devops action_mailbox migrations seeds middleware engines multi_database
|
|
10
|
+
components accessibility performance]
|
|
10
11
|
}.freeze
|
|
11
12
|
|
|
12
13
|
# MCP server settings
|
|
@@ -76,6 +76,9 @@ module RailsAiContext
|
|
|
76
76
|
when :middleware then Introspectors::MiddlewareIntrospector.new(app)
|
|
77
77
|
when :engines then Introspectors::EngineIntrospector.new(app)
|
|
78
78
|
when :multi_database then Introspectors::MultiDatabaseIntrospector.new(app)
|
|
79
|
+
when :components then Introspectors::ComponentIntrospector.new(app)
|
|
80
|
+
when :accessibility then Introspectors::AccessibilityIntrospector.new(app)
|
|
81
|
+
when :performance then Introspectors::PerformanceIntrospector.new(app)
|
|
79
82
|
else
|
|
80
83
|
raise ConfigurationError, "Unknown introspector: #{name}"
|
|
81
84
|
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAiContext
|
|
4
|
+
module Introspectors
|
|
5
|
+
# Scans view templates for accessibility patterns: ARIA attributes,
|
|
6
|
+
# semantic HTML elements, screen reader text, alt text, label associations.
|
|
7
|
+
class AccessibilityIntrospector
|
|
8
|
+
attr_reader :app
|
|
9
|
+
|
|
10
|
+
def initialize(app)
|
|
11
|
+
@app = app
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
views = collect_view_content
|
|
16
|
+
{
|
|
17
|
+
aria_attributes: extract_aria_attributes(views),
|
|
18
|
+
roles: extract_roles(views),
|
|
19
|
+
semantic_elements: count_semantic_elements(views),
|
|
20
|
+
screen_reader_text: count_screen_reader_text(views),
|
|
21
|
+
images: analyze_images(views),
|
|
22
|
+
labels: analyze_labels(views),
|
|
23
|
+
landmarks: extract_landmarks(views),
|
|
24
|
+
summary: build_summary(views)
|
|
25
|
+
}
|
|
26
|
+
rescue => e
|
|
27
|
+
{ error: e.message }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def root
|
|
33
|
+
app.root.to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def views_dir
|
|
37
|
+
File.join(root, "app/views")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def components_dir
|
|
41
|
+
File.join(root, "app/components")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def collect_view_content
|
|
45
|
+
views = []
|
|
46
|
+
|
|
47
|
+
[ views_dir, components_dir ].each do |dir|
|
|
48
|
+
next unless Dir.exist?(dir)
|
|
49
|
+
|
|
50
|
+
Dir.glob(File.join(dir, "**/*.{erb,haml,slim,html}")).each do |path|
|
|
51
|
+
content = File.read(path, encoding: "UTF-8", invalid: :replace, undef: :replace)
|
|
52
|
+
views << { file: path.sub("#{root}/", ""), content: content }
|
|
53
|
+
rescue
|
|
54
|
+
next
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
views
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def extract_aria_attributes(views)
|
|
62
|
+
attributes = Hash.new(0)
|
|
63
|
+
views.each do |view|
|
|
64
|
+
view[:content].scan(/aria-(\w+[-\w]*)/) do |attr,|
|
|
65
|
+
attributes["aria-#{attr}"] += 1
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
attributes.sort_by { |_, count| -count }.to_h
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def extract_roles(views)
|
|
72
|
+
roles = Hash.new(0)
|
|
73
|
+
views.each do |view|
|
|
74
|
+
view[:content].scan(/role=["'](\w+)["']/) do |role,|
|
|
75
|
+
roles[role] += 1
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
roles.sort_by { |_, count| -count }.to_h
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def count_semantic_elements(views)
|
|
82
|
+
elements = %w[nav main header footer article section aside figure figcaption details summary dialog]
|
|
83
|
+
counts = {}
|
|
84
|
+
|
|
85
|
+
all_content = views.map { |v| v[:content] }.join("\n")
|
|
86
|
+
elements.each do |el|
|
|
87
|
+
count = all_content.scan(/<#{el}[\s>]/).size
|
|
88
|
+
counts[el] = count if count > 0
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
counts
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def count_screen_reader_text(views)
|
|
95
|
+
all_content = views.map { |v| v[:content] }.join("\n")
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
sr_only: all_content.scan(/\bsr-only\b/).size,
|
|
99
|
+
visually_hidden: all_content.scan(/\bvisually-hidden\b/).size,
|
|
100
|
+
aria_hidden: all_content.scan(/aria-hidden=["']true["']/).size
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def analyze_images(views)
|
|
105
|
+
all_content = views.map { |v| v[:content] }.join("\n")
|
|
106
|
+
|
|
107
|
+
# Count img tags with and without alt
|
|
108
|
+
img_with_alt = all_content.scan(/<img\b[^>]*\balt=/).size
|
|
109
|
+
img_total = all_content.scan(/<img\b/).size
|
|
110
|
+
# image_tag helper always generates alt (from filename if not provided)
|
|
111
|
+
image_tag_count = all_content.scan(/image_tag\b/).size
|
|
112
|
+
|
|
113
|
+
{
|
|
114
|
+
total: img_total + image_tag_count,
|
|
115
|
+
with_alt: img_with_alt + image_tag_count,
|
|
116
|
+
missing_alt: [ img_total - img_with_alt, 0 ].max,
|
|
117
|
+
decorative: all_content.scan(/alt=["']\s*["']/).size
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def analyze_labels(views)
|
|
122
|
+
all_content = views.map { |v| v[:content] }.join("\n")
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
label_for: all_content.scan(/<label\b[^>]*\bfor=/).size +
|
|
126
|
+
all_content.scan(/\.label\s+:\w+/).size,
|
|
127
|
+
aria_label: all_content.scan(/aria-label=/).size,
|
|
128
|
+
aria_labelledby: all_content.scan(/aria-labelled?by=/).size,
|
|
129
|
+
aria_describedby: all_content.scan(/aria-describedby=/).size
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def extract_landmarks(views)
|
|
134
|
+
all_content = views.map { |v| v[:content] }.join("\n")
|
|
135
|
+
|
|
136
|
+
landmarks = {}
|
|
137
|
+
%w[banner navigation main complementary contentinfo search form].each do |role|
|
|
138
|
+
count = all_content.scan(/role=["']#{role}["']/).size
|
|
139
|
+
landmarks[role] = count if count > 0
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Semantic elements that imply landmarks
|
|
143
|
+
{ "banner" => "header", "navigation" => "nav", "main" => "main",
|
|
144
|
+
"complementary" => "aside", "contentinfo" => "footer" }.each do |role, el|
|
|
145
|
+
tag_count = all_content.scan(/<#{el}[\s>]/).size
|
|
146
|
+
landmarks[role] = (landmarks[role] || 0) + tag_count if tag_count > 0
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
landmarks
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def build_summary(views)
|
|
153
|
+
all_content = views.map { |v| v[:content] }.join("\n")
|
|
154
|
+
|
|
155
|
+
aria_count = all_content.scan(/aria-\w+/).size
|
|
156
|
+
role_count = all_content.scan(/role=["']\w+["']/).size
|
|
157
|
+
semantic_count = %w[nav main header footer article section aside].sum { |el|
|
|
158
|
+
all_content.scan(/<#{el}[\s>]/).size
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
total_signals = aria_count + role_count + semantic_count
|
|
162
|
+
file_count = views.size
|
|
163
|
+
|
|
164
|
+
score = if file_count == 0
|
|
165
|
+
0
|
|
166
|
+
else
|
|
167
|
+
signals_per_file = total_signals.to_f / file_count
|
|
168
|
+
case signals_per_file
|
|
169
|
+
when 0..0.5 then 1
|
|
170
|
+
when 0.5..1.5 then 2
|
|
171
|
+
when 1.5..3 then 3
|
|
172
|
+
when 3..5 then 4
|
|
173
|
+
else 5
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
{
|
|
178
|
+
files_scanned: file_count,
|
|
179
|
+
total_aria_attributes: aria_count,
|
|
180
|
+
total_roles: role_count,
|
|
181
|
+
total_semantic_elements: semantic_count,
|
|
182
|
+
accessibility_score: score,
|
|
183
|
+
score_label: %w[none minimal basic good excellent][score > 0 ? score - 1 : 0]
|
|
184
|
+
}
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -15,7 +15,8 @@ module RailsAiContext
|
|
|
15
15
|
{
|
|
16
16
|
authentication: detect_authentication,
|
|
17
17
|
authorization: detect_authorization,
|
|
18
|
-
security: detect_security
|
|
18
|
+
security: detect_security,
|
|
19
|
+
devise_modules_per_model: detect_devise_modules_per_model
|
|
19
20
|
}
|
|
20
21
|
rescue => e
|
|
21
22
|
{ error: e.message }
|
|
@@ -81,6 +82,30 @@ module RailsAiContext
|
|
|
81
82
|
security
|
|
82
83
|
end
|
|
83
84
|
|
|
85
|
+
def detect_devise_modules_per_model
|
|
86
|
+
models_dir = File.join(root, "app/models")
|
|
87
|
+
return {} unless Dir.exist?(models_dir)
|
|
88
|
+
|
|
89
|
+
result = {}
|
|
90
|
+
Dir.glob(File.join(models_dir, "**/*.rb")).each do |path|
|
|
91
|
+
content = File.read(path) rescue next
|
|
92
|
+
next unless content.match?(/\bdevise\b/)
|
|
93
|
+
|
|
94
|
+
model_name = File.basename(path, ".rb").camelize
|
|
95
|
+
# Extract devise modules, handling multiline declarations with trailing commas
|
|
96
|
+
# Join continuation lines (lines starting with whitespace + colon after a line ending with comma)
|
|
97
|
+
devise_block = content.scan(/devise\s+((?:.*,\s*\n)*.*?)$/m).flatten.first
|
|
98
|
+
next unless devise_block
|
|
99
|
+
|
|
100
|
+
modules = devise_block.scan(/:(\w+)/).flatten
|
|
101
|
+
result[model_name] = modules if modules.any?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
result
|
|
105
|
+
rescue
|
|
106
|
+
{}
|
|
107
|
+
end
|
|
108
|
+
|
|
84
109
|
def scan_models_for(pattern)
|
|
85
110
|
models_dir = File.join(root, "app/models")
|
|
86
111
|
return [] unless Dir.exist?(models_dir)
|