rails-ai-context 0.2.0 → 0.4.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/.rubocop.yml +2 -2
- data/CHANGELOG.md +62 -0
- data/CLAUDE.md +47 -0
- data/README.md +88 -22
- data/exe/rails-ai-context +30 -2
- data/lib/rails_ai_context/configuration.rb +5 -1
- data/lib/rails_ai_context/doctor.rb +149 -0
- data/lib/rails_ai_context/engine.rb +8 -0
- data/lib/rails_ai_context/fingerprinter.rb +54 -0
- data/lib/rails_ai_context/introspector.rb +16 -0
- data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +50 -0
- data/lib/rails_ai_context/introspectors/action_text_introspector.rb +48 -0
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +81 -0
- data/lib/rails_ai_context/introspectors/api_introspector.rb +92 -0
- data/lib/rails_ai_context/introspectors/asset_pipeline_introspector.rb +92 -0
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +115 -0
- data/lib/rails_ai_context/introspectors/config_introspector.rb +85 -0
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +135 -0
- data/lib/rails_ai_context/introspectors/convention_detector.rb +20 -1
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +39 -0
- data/lib/rails_ai_context/introspectors/devops_introspector.rb +111 -0
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +51 -1
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +66 -0
- data/lib/rails_ai_context/introspectors/model_introspector.rb +52 -10
- data/lib/rails_ai_context/introspectors/rake_task_introspector.rb +68 -0
- data/lib/rails_ai_context/introspectors/route_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +81 -0
- data/lib/rails_ai_context/introspectors/test_introspector.rb +137 -0
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +80 -0
- data/lib/rails_ai_context/introspectors/view_introspector.rb +130 -0
- data/lib/rails_ai_context/middleware.rb +39 -0
- data/lib/rails_ai_context/resources.rb +105 -0
- data/lib/rails_ai_context/serializers/claude_serializer.rb +45 -0
- data/lib/rails_ai_context/serializers/context_file_serializer.rb +16 -10
- data/lib/rails_ai_context/serializers/copilot_serializer.rb +29 -0
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +247 -7
- data/lib/rails_ai_context/serializers/rules_serializer.rb +26 -0
- data/lib/rails_ai_context/server.rb +10 -3
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +42 -7
- data/lib/rails_ai_context/tools/base_tool.rb +15 -3
- data/lib/rails_ai_context/tools/get_config.rb +41 -0
- data/lib/rails_ai_context/tools/get_controllers.rb +67 -0
- data/lib/rails_ai_context/tools/get_conventions.rb +1 -1
- data/lib/rails_ai_context/tools/get_gems.rb +2 -2
- data/lib/rails_ai_context/tools/get_model_details.rb +1 -1
- data/lib/rails_ai_context/tools/get_routes.rb +2 -2
- data/lib/rails_ai_context/tools/get_schema.rb +6 -4
- data/lib/rails_ai_context/tools/get_test_info.rb +34 -0
- data/lib/rails_ai_context/tools/search_code.rb +42 -9
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/watcher.rb +75 -0
- data/lib/rails_ai_context.rb +31 -1
- data/rails-ai-context.gemspec +6 -5
- metadata +32 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f3efeed4bfe44c20ff766c0398b1d8a526a13c053b47d2a93d8faa320abfdda
|
|
4
|
+
data.tar.gz: 2cfe5512db995e2b08a2f9516793f1e68e787e8b76092780370ea6b04e280f69
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ccbff85f3a231a4c207522834387662af03240511c2bbbf491023e2b6361b68ea1e109cc3d8b383f02c9989db5542e555b6579b01037c25757acb3d84cc4525
|
|
7
|
+
data.tar.gz: 62a53606b4e98f331e716a44a8e424479e2cd61649de82814778feb1f4b3de23607f6a1f35fb6664c465fc130c9f2a140d928c6ce8864c4d4038f7ed27ddbcbc
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,68 @@ 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
|
+
## [0.4.0] - 2026-03-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **14 new introspectors** — Controllers, Views, Turbo/Hotwire, I18n, Config, Active Storage, Action Text, Auth, API, Tests, Rake Tasks, Asset Pipeline, DevOps, Action Mailbox
|
|
13
|
+
- **3 new MCP tools** — `rails_get_controllers`, `rails_get_config`, `rails_get_test_info`
|
|
14
|
+
- **3 new MCP resources** — `rails://controllers`, `rails://config`, `rails://tests`
|
|
15
|
+
- **Model introspector enhancements** — Extracts `has_secure_password`, `encrypts`, `normalizes`, `delegate`, `serialize`, `store`, `generates_token_for`, `has_one_attached`, `has_many_attached`, `has_rich_text`, `broadcasts_to` via source parsing
|
|
16
|
+
- **Stimulus introspector enhancements** — Extracts `outlets` and `classes` from controllers
|
|
17
|
+
- **Gem introspector enhancements** — 30+ new notable gems: monitoring (Sentry, Datadog, New Relic, Skylight), admin (ActiveAdmin, Administrate, Avo), pagination (Pagy, Kaminari), search (Ransack, pg_search, Searchkick), forms (SimpleForm), utilities (Faraday, Flipper, Bullet, Rack::Attack), and more
|
|
18
|
+
- **Convention detector enhancements** — Detects concerns, validators, policies, serializers, notifiers, Phlex, PWA, encrypted attributes, normalizations
|
|
19
|
+
- **Markdown serializer sections** — All 14 new introspector sections rendered in generated context files
|
|
20
|
+
- **Doctor enhancements** — 4 new checks: controllers, views, i18n, tests (11 total)
|
|
21
|
+
- **Fingerprinter expansion** — Watches `app/controllers`, `app/views`, `app/jobs`, `app/mailers`, `app/channels`, `app/javascript/controllers`, `config/initializers`, `lib/tasks`; glob now covers `.rb`, `.rake`, `.js`, `.ts`, `.erb`, `.haml`, `.slim`, `.yml`
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **YAML parsing** — `YAML.load_file` calls now pass `permitted_classes: [Symbol], aliases: true` for Psych 4 (Ruby 3.1+) compatibility
|
|
26
|
+
- **Rake task parser** — Fixed `@last_desc` instance variable leaking between files; fixed namespace tracking with indent-based stack
|
|
27
|
+
- **Vite detection** — Changed `File.exist?("vite.config")` to `Dir.glob("vite.config.*")` to match `.js`/`.ts`/`.mjs` extensions
|
|
28
|
+
- **Health check regex** — Added word boundaries to avoid false positives on substrings (e.g. "groups" matching "up")
|
|
29
|
+
- **Multi-attribute macros** — `normalizes :email, :name` now captures all attributes, not just the first
|
|
30
|
+
- **Stimulus action regex** — Requires `method(args) {` pattern to avoid matching control flow keywords
|
|
31
|
+
- **Controller respond_to** — Simplified format extraction to avoid nested `end` keyword issues
|
|
32
|
+
- **GetRoutes nil guard** — Added `|| {}` fallback for `by_controller` to prevent crash on partial introspection data
|
|
33
|
+
- **GetSchema nil guard** — Added `|| {}` fallback for `schema[:tables]` to prevent crash on partial schema data
|
|
34
|
+
- **View layout discovery** — Added `File.file?` filter to exclude directories from layout listing
|
|
35
|
+
- **Fingerprinter glob** — Changed from `**/*.rb` to multi-extension glob to detect changes in `.rake`, `.js`, `.ts`, `.erb` files
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- Default introspectors expanded from 7 to 21
|
|
40
|
+
- MCP tools expanded from 6 to 9
|
|
41
|
+
- Static MCP resources expanded from 4 to 7
|
|
42
|
+
- Doctor checks expanded from 7 to 11
|
|
43
|
+
- Test suite expanded from 149 to 247 examples with exact value assertions
|
|
44
|
+
|
|
45
|
+
## [0.3.0] - 2026-03-18
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **Cache invalidation** — TTL + file fingerprinting for MCP tool cache (replaces permanent `||=` cache)
|
|
50
|
+
- **MCP Resources** — Static resources (`rails://schema`, `rails://routes`, `rails://conventions`, `rails://gems`) and resource template (`rails://models/{name}`)
|
|
51
|
+
- **Per-assistant serializers** — Claude gets behavioral rules, Cursor/Windsurf get compact rules, Copilot gets task-oriented GFM
|
|
52
|
+
- **Stimulus introspector** — Extracts Stimulus controller targets, values, and actions from JS/TS files
|
|
53
|
+
- **Database stats introspector** — Opt-in PostgreSQL approximate row counts via `pg_stat_user_tables`
|
|
54
|
+
- **Auto-mount HTTP middleware** — Rack middleware for MCP endpoint when `config.auto_mount = true`
|
|
55
|
+
- **Diff-aware regeneration** — Context file generation skips unchanged files
|
|
56
|
+
- **`rails ai:doctor`** — Diagnostic command with AI readiness score (0-100)
|
|
57
|
+
- **`rails ai:watch`** — File watcher that auto-regenerates context files on change (requires `listen` gem)
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- **Shell injection in SearchCode** — Replaced backtick execution with `Open3.capture2` array form; added file_type validation, max_results cap, and path traversal protection
|
|
62
|
+
- **Scope extraction** — Fixed broken `model.methods.grep(/^_scope_/)` by parsing source files for `scope :name` declarations
|
|
63
|
+
- **Route introspector** — Fixed `route.internal?` compatibility with Rails 8.1
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- `generate_context` now returns `{ written: [], skipped: [] }` instead of flat array
|
|
68
|
+
- Default introspectors now include `:stimulus`
|
|
69
|
+
|
|
8
70
|
## [0.2.0] - 2026-03-18
|
|
9
71
|
|
|
10
72
|
### Added
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# CLAUDE.md — rails-ai-context development guide
|
|
2
|
+
|
|
3
|
+
This is a Ruby gem that auto-introspects Rails applications and exposes their
|
|
4
|
+
structure to AI assistants via the Model Context Protocol (MCP).
|
|
5
|
+
|
|
6
|
+
## Architecture
|
|
7
|
+
|
|
8
|
+
- `lib/rails_ai_context.rb` — Main entry point, public API
|
|
9
|
+
- `lib/rails_ai_context/introspector.rb` — Orchestrates sub-introspectors
|
|
10
|
+
- `lib/rails_ai_context/introspectors/` — Individual introspectors (schema, models, routes, jobs, gems, conventions, stimulus, database_stats, controllers, views, turbo, i18n, config, active_storage, action_text, auth, api, tests, rake_tasks, assets, devops, action_mailbox)
|
|
11
|
+
- `lib/rails_ai_context/tools/` — MCP tools using the official mcp SDK
|
|
12
|
+
- `lib/rails_ai_context/serializers/` — Output formatters (claude, rules, copilot, markdown, JSON)
|
|
13
|
+
- `lib/rails_ai_context/resources.rb` — MCP resources (static data AI clients read directly)
|
|
14
|
+
- `lib/rails_ai_context/server.rb` — MCP server configuration (stdio + HTTP transports)
|
|
15
|
+
- `lib/rails_ai_context/middleware.rb` — Rack middleware for auto-mounting MCP HTTP endpoint
|
|
16
|
+
- `lib/rails_ai_context/fingerprinter.rb` — SHA256 file fingerprinting for cache invalidation
|
|
17
|
+
- `lib/rails_ai_context/doctor.rb` — Diagnostic checks and AI readiness scoring
|
|
18
|
+
- `lib/rails_ai_context/watcher.rb` — File watcher for auto-regenerating context files
|
|
19
|
+
- `lib/rails_ai_context/engine.rb` — Rails Engine for auto-integration
|
|
20
|
+
|
|
21
|
+
## Key Design Decisions
|
|
22
|
+
|
|
23
|
+
1. **Built on official mcp SDK** — not a custom protocol implementation
|
|
24
|
+
2. **Zero-config** — Railtie auto-registers at boot, introspects without setup
|
|
25
|
+
3. **Graceful degradation** — works without DB by parsing schema.rb as text
|
|
26
|
+
4. **Read-only tools only** — all MCP tools are annotated as non-destructive
|
|
27
|
+
5. **Dual output** — static files (CLAUDE.md) + live MCP server (stdio/HTTP)
|
|
28
|
+
6. **Diff-aware** — context regeneration skips unchanged files
|
|
29
|
+
7. **Per-assistant serializers** — each AI tool gets tailored output format
|
|
30
|
+
|
|
31
|
+
## Testing
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bundle exec rspec # Run specs
|
|
35
|
+
bundle exec rubocop # Lint
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Uses combustion gem for testing Rails engine behavior in isolation.
|
|
39
|
+
|
|
40
|
+
## Conventions
|
|
41
|
+
|
|
42
|
+
- Ruby 3.2+ features OK (pattern matching, etc.)
|
|
43
|
+
- Follow rubocop-rails-omakase style
|
|
44
|
+
- Every introspector returns a Hash, never raises (wraps errors in `{ error: msg }`)
|
|
45
|
+
- MCP tools return `MCP::Tool::Response` objects per SDK convention
|
|
46
|
+
- All tools prefixed with `rails_` per MCP naming best practices
|
|
47
|
+
- `generate_context` returns `{ written: [], skipped: [] }` hash
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://github.com/crisnahine/rails-ai-context/actions)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
`rails-ai-context` automatically introspects your Rails application and exposes your models, routes, schema, jobs, gems, and conventions to AI assistants through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io).
|
|
9
|
+
`rails-ai-context` automatically introspects your Rails application and exposes your models, routes, schema, controllers, views, jobs, gems, auth, API, tests, config, and conventions to AI assistants through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io).
|
|
10
10
|
|
|
11
11
|
**Your AI assistant instantly understands your entire Rails app. No configuration. No manual tool definitions. Just `bundle add` and go.**
|
|
12
12
|
|
|
@@ -26,12 +26,22 @@ bundle add rails-ai-context
|
|
|
26
26
|
|
|
27
27
|
That's it. Now your AI assistant knows:
|
|
28
28
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
29
|
+
- **Every table, column, index, and foreign key** in your database
|
|
30
|
+
- **Every model** with associations, validations, scopes, enums, callbacks, and macros (has_secure_password, encrypts, normalizes, etc.)
|
|
31
|
+
- **Every controller** with actions, filters, strong params, and concerns
|
|
32
|
+
- **Every view** with layouts, templates, partials, helpers, and template engines
|
|
33
|
+
- **Every route** with HTTP verbs, paths, and controller actions
|
|
34
|
+
- **Every background job**, mailer, and Action Cable channel
|
|
35
|
+
- **Hotwire/Turbo** — Turbo Frames, Turbo Streams, model broadcasts
|
|
36
|
+
- **Every notable gem** (70+) and what it means (Devise = auth, Sidekiq = jobs, Turbo = Hotwire)
|
|
37
|
+
- **Auth & security** — Devise modules, Pundit policies, CanCanCan, CORS, CSP
|
|
38
|
+
- **API layer** — serializers, GraphQL, versioning, rate limiting
|
|
39
|
+
- **Test infrastructure** — framework, factories/fixtures, CI config, coverage
|
|
40
|
+
- **Configuration** — cache store, session store, middleware, initializers
|
|
41
|
+
- **Asset pipeline** — Propshaft/Sprockets, importmaps, CSS framework, JS bundler
|
|
42
|
+
- **DevOps** — Puma config, Procfile, Docker, deployment tools
|
|
43
|
+
- **Your architecture patterns**: service objects, STI, polymorphism, state machines, multi-tenancy
|
|
44
|
+
- **Stimulus controllers** with targets, values, actions, outlets, and classes
|
|
35
45
|
|
|
36
46
|
---
|
|
37
47
|
|
|
@@ -51,12 +61,12 @@ rails ai:context
|
|
|
51
61
|
```
|
|
52
62
|
|
|
53
63
|
This creates:
|
|
54
|
-
- `CLAUDE.md` — for Claude Code
|
|
55
|
-
- `.cursorrules` — for Cursor
|
|
56
|
-
- `.windsurfrules` — for Windsurf
|
|
57
|
-
- `.github/copilot-instructions.md` — for GitHub Copilot
|
|
64
|
+
- `CLAUDE.md` — for Claude Code (with behavioral rules)
|
|
65
|
+
- `.cursorrules` — for Cursor (compact rules format)
|
|
66
|
+
- `.windsurfrules` — for Windsurf (compact rules format)
|
|
67
|
+
- `.github/copilot-instructions.md` — for GitHub Copilot (task-oriented)
|
|
58
68
|
|
|
59
|
-
**Commit these files.** Your entire team gets smarter AI assistance.
|
|
69
|
+
Each file is tailored to the AI assistant's preferred format. **Commit these files.** Your entire team gets smarter AI assistance.
|
|
60
70
|
|
|
61
71
|
### 3. Start the MCP Server
|
|
62
72
|
|
|
@@ -84,7 +94,7 @@ Or add to your Claude Code config (`~/.claude/claude_desktop_config.json`):
|
|
|
84
94
|
|
|
85
95
|
## MCP Tools
|
|
86
96
|
|
|
87
|
-
The gem exposes
|
|
97
|
+
The gem exposes 9 tools via MCP that AI clients can call:
|
|
88
98
|
|
|
89
99
|
| Tool | Description | Annotations |
|
|
90
100
|
|------|-------------|-------------|
|
|
@@ -94,9 +104,27 @@ The gem exposes 6 tools via MCP that AI clients can call:
|
|
|
94
104
|
| `rails_get_gems` | Notable gems categorized by function with explanations | read-only, idempotent |
|
|
95
105
|
| `rails_search_code` | Ripgrep-powered code search across the codebase | read-only, idempotent |
|
|
96
106
|
| `rails_get_conventions` | Architecture patterns, directory structure, config files | read-only, idempotent |
|
|
107
|
+
| `rails_get_controllers` | Controller actions, filters, strong params, concerns | read-only, idempotent |
|
|
108
|
+
| `rails_get_config` | App configuration: cache, sessions, middleware, initializers | read-only, idempotent |
|
|
109
|
+
| `rails_get_test_info` | Test framework, factories, CI config, coverage | read-only, idempotent |
|
|
97
110
|
|
|
98
111
|
All tools are **read-only** — they never modify your application or database.
|
|
99
112
|
|
|
113
|
+
## MCP Resources
|
|
114
|
+
|
|
115
|
+
In addition to tools, the gem registers MCP resources that AI clients can read directly:
|
|
116
|
+
|
|
117
|
+
| Resource | Description |
|
|
118
|
+
|----------|-------------|
|
|
119
|
+
| `rails://schema` | Full database schema (JSON) |
|
|
120
|
+
| `rails://routes` | All routes (JSON) |
|
|
121
|
+
| `rails://conventions` | Detected patterns and architecture (JSON) |
|
|
122
|
+
| `rails://gems` | Notable gems with categories (JSON) |
|
|
123
|
+
| `rails://controllers` | All controllers with actions and filters (JSON) |
|
|
124
|
+
| `rails://config` | Application configuration (JSON) |
|
|
125
|
+
| `rails://tests` | Test infrastructure details (JSON) |
|
|
126
|
+
| `rails://models/{name}` | Per-model details (resource template) |
|
|
127
|
+
|
|
100
128
|
---
|
|
101
129
|
|
|
102
130
|
## How It Works
|
|
@@ -124,9 +152,10 @@ All tools are **read-only** — they never modify your application or database.
|
|
|
124
152
|
(reads file) any MCP client
|
|
125
153
|
```
|
|
126
154
|
|
|
127
|
-
**
|
|
155
|
+
**Three modes:**
|
|
128
156
|
1. **Static files** (`rails ai:context`) — generates markdown files that AI tools read as project context. Zero runtime cost. Works everywhere.
|
|
129
157
|
2. **MCP server** (`rails ai:serve`) — live introspection tools that AI clients call on-demand. Richer, always up-to-date.
|
|
158
|
+
3. **Watch mode** (`rails ai:watch`) — auto-regenerates context files when your code changes.
|
|
130
159
|
|
|
131
160
|
---
|
|
132
161
|
|
|
@@ -145,20 +174,49 @@ RailsAiContext.configure do |config|
|
|
|
145
174
|
config.auto_mount = true
|
|
146
175
|
config.http_path = "/mcp"
|
|
147
176
|
config.http_port = 6029
|
|
177
|
+
|
|
178
|
+
# Cache TTL for MCP tool responses (seconds)
|
|
179
|
+
config.cache_ttl = 30
|
|
180
|
+
|
|
181
|
+
# Enable Postgres row count stats (opt-in)
|
|
182
|
+
# config.introspectors += [:database_stats]
|
|
148
183
|
end
|
|
149
184
|
```
|
|
150
185
|
|
|
151
186
|
---
|
|
152
187
|
|
|
188
|
+
## Diagnostics
|
|
189
|
+
|
|
190
|
+
Check your app's AI readiness:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
rails ai:doctor
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Reports pass/warn/fail for schema, models, routes, gems, controllers, views, i18n, tests, context files, MCP server, and ripgrep. Includes fix suggestions and an AI readiness score (0-100).
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Stimulus Support
|
|
201
|
+
|
|
202
|
+
The gem automatically detects Stimulus controllers and extracts:
|
|
203
|
+
- Controller names (derived from filenames)
|
|
204
|
+
- Static targets, values, outlets, and classes
|
|
205
|
+
- Action methods
|
|
206
|
+
|
|
207
|
+
This gives AI assistants context about your frontend JavaScript alongside your backend Ruby.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
153
211
|
## Supported AI Assistants
|
|
154
212
|
|
|
155
|
-
| AI Assistant | Context File | Command |
|
|
156
|
-
|
|
157
|
-
| Claude Code | `CLAUDE.md` | `rails ai:context:claude` |
|
|
158
|
-
| Cursor | `.cursorrules` | `rails ai:context:cursor` |
|
|
159
|
-
| Windsurf | `.windsurfrules` | `rails ai:context:windsurf` |
|
|
160
|
-
| GitHub Copilot | `.github/copilot-instructions.md` | `rails ai:context:copilot` |
|
|
161
|
-
| JSON (generic) | `.ai-context.json` | `rails ai:context:json` |
|
|
213
|
+
| AI Assistant | Context File | Format | Command |
|
|
214
|
+
|--------------|-------------|--------|---------|
|
|
215
|
+
| Claude Code | `CLAUDE.md` | Verbose + behavioral rules | `rails ai:context:claude` |
|
|
216
|
+
| Cursor | `.cursorrules` | Compact imperative rules | `rails ai:context:cursor` |
|
|
217
|
+
| Windsurf | `.windsurfrules` | Compact imperative rules | `rails ai:context:windsurf` |
|
|
218
|
+
| GitHub Copilot | `.github/copilot-instructions.md` | Task-oriented GFM | `rails ai:context:copilot` |
|
|
219
|
+
| JSON (generic) | `.ai-context.json` | Structured JSON | `rails ai:context:json` |
|
|
162
220
|
|
|
163
221
|
---
|
|
164
222
|
|
|
@@ -166,7 +224,7 @@ end
|
|
|
166
224
|
|
|
167
225
|
| Command | Description |
|
|
168
226
|
|---------|-------------|
|
|
169
|
-
| `rails ai:context` | Generate all context files (
|
|
227
|
+
| `rails ai:context` | Generate all context files (skips unchanged) |
|
|
170
228
|
| `rails ai:context:claude` | Generate CLAUDE.md only |
|
|
171
229
|
| `rails ai:context:cursor` | Generate .cursorrules only |
|
|
172
230
|
| `rails ai:context:windsurf` | Generate .windsurfrules only |
|
|
@@ -175,6 +233,8 @@ end
|
|
|
175
233
|
| `rails ai:serve` | Start MCP server (stdio, for Claude Code) |
|
|
176
234
|
| `rails ai:serve_http` | Start MCP server (HTTP, for remote clients) |
|
|
177
235
|
| `rails ai:inspect` | Print introspection summary to stdout |
|
|
236
|
+
| `rails ai:doctor` | Run diagnostics and report AI readiness score |
|
|
237
|
+
| `rails ai:watch` | Watch for changes and auto-regenerate context files |
|
|
178
238
|
|
|
179
239
|
> **zsh users:** The bracket syntax `rails ai:context_for[claude]` requires quoting in zsh (`rails 'ai:context_for[claude]'`). The named tasks above (`rails ai:context:claude`) work without quoting in any shell.
|
|
180
240
|
|
|
@@ -196,6 +256,8 @@ The gem gracefully degrades when no database is connected — it parses `db/sche
|
|
|
196
256
|
- Ruby >= 3.2
|
|
197
257
|
- Rails >= 7.1
|
|
198
258
|
- [mcp](https://github.com/modelcontextprotocol/ruby-sdk) (official MCP SDK, installed automatically)
|
|
259
|
+
- Optional: `listen` gem for watch mode
|
|
260
|
+
- Optional: `ripgrep` for fast code search (falls back to Ruby)
|
|
199
261
|
|
|
200
262
|
---
|
|
201
263
|
|
|
@@ -225,6 +287,10 @@ bundle exec rspec
|
|
|
225
287
|
|
|
226
288
|
Bug reports and pull requests welcome at https://github.com/crisnahine/rails-ai-context.
|
|
227
289
|
|
|
290
|
+
## Sponsorship
|
|
291
|
+
|
|
292
|
+
If rails-ai-context helps your workflow, consider supporting the project — [become a monthly sponsor or buy me a coffee](https://github.com/sponsors/crisnahine).
|
|
293
|
+
|
|
228
294
|
## License
|
|
229
295
|
|
|
230
296
|
[MIT License](LICENSE)
|
data/exe/rails-ai-context
CHANGED
|
@@ -28,8 +28,9 @@ module RailsAiContext
|
|
|
28
28
|
|
|
29
29
|
format = options[:format].to_sym
|
|
30
30
|
$stderr.puts "Introspecting Rails app..."
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
result = RailsAiContext.generate_context(format: format)
|
|
32
|
+
result[:written].each { |f| $stderr.puts " Written: #{f}" }
|
|
33
|
+
result[:skipped].each { |f| $stderr.puts " Skipped: #{f} (unchanged)" }
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
desc "inspect", "Print introspection summary"
|
|
@@ -42,6 +43,33 @@ module RailsAiContext
|
|
|
42
43
|
puts JSON.pretty_generate(context)
|
|
43
44
|
end
|
|
44
45
|
|
|
46
|
+
desc "watch", "Watch for changes and auto-regenerate context files"
|
|
47
|
+
def watch
|
|
48
|
+
boot_rails!
|
|
49
|
+
require "rails_ai_context"
|
|
50
|
+
|
|
51
|
+
RailsAiContext::Watcher.new.start
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
desc "doctor", "Run diagnostic checks and report AI readiness score"
|
|
55
|
+
def doctor
|
|
56
|
+
boot_rails!
|
|
57
|
+
require "rails_ai_context"
|
|
58
|
+
|
|
59
|
+
result = RailsAiContext::Doctor.new.run
|
|
60
|
+
result[:checks].each do |check|
|
|
61
|
+
icon = case check.status
|
|
62
|
+
when :pass then "PASS"
|
|
63
|
+
when :warn then "WARN"
|
|
64
|
+
when :fail then "FAIL"
|
|
65
|
+
end
|
|
66
|
+
$stderr.puts " [#{icon}] #{check.name}: #{check.message}"
|
|
67
|
+
$stderr.puts " Fix: #{check.fix}" if check.fix
|
|
68
|
+
end
|
|
69
|
+
$stderr.puts ""
|
|
70
|
+
$stderr.puts "AI Readiness Score: #{result[:score]}/100"
|
|
71
|
+
end
|
|
72
|
+
|
|
45
73
|
desc "version", "Print version"
|
|
46
74
|
def version
|
|
47
75
|
require_relative "../rails_ai_context/version"
|
|
@@ -26,10 +26,13 @@ module RailsAiContext
|
|
|
26
26
|
# Maximum depth for association traversal
|
|
27
27
|
attr_accessor :max_association_depth
|
|
28
28
|
|
|
29
|
+
# TTL in seconds for cached introspection (default: 30)
|
|
30
|
+
attr_accessor :cache_ttl
|
|
31
|
+
|
|
29
32
|
def initialize
|
|
30
33
|
@server_name = "rails-ai-context"
|
|
31
34
|
@server_version = RailsAiContext::VERSION
|
|
32
|
-
@introspectors = %i[schema models routes jobs gems conventions]
|
|
35
|
+
@introspectors = %i[schema models routes jobs gems conventions stimulus controllers views turbo i18n config active_storage action_text auth api tests rake_tasks assets devops action_mailbox]
|
|
33
36
|
@excluded_paths = %w[node_modules tmp log vendor .git]
|
|
34
37
|
@auto_mount = false
|
|
35
38
|
@http_path = "/mcp"
|
|
@@ -43,6 +46,7 @@ module RailsAiContext
|
|
|
43
46
|
ActionMailbox::InboundEmail ActionMailbox::Record
|
|
44
47
|
]
|
|
45
48
|
@max_association_depth = 2
|
|
49
|
+
@cache_ttl = 30
|
|
46
50
|
end
|
|
47
51
|
|
|
48
52
|
def output_dir_for(app)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAiContext
|
|
4
|
+
# Diagnostic checker that validates the environment and reports
|
|
5
|
+
# AI readiness with pass/warn/fail checks and a readiness score.
|
|
6
|
+
class Doctor
|
|
7
|
+
Check = Data.define(:name, :status, :message, :fix)
|
|
8
|
+
|
|
9
|
+
CHECKS = %i[
|
|
10
|
+
check_schema
|
|
11
|
+
check_models
|
|
12
|
+
check_routes
|
|
13
|
+
check_gems
|
|
14
|
+
check_controllers
|
|
15
|
+
check_views
|
|
16
|
+
check_i18n
|
|
17
|
+
check_tests
|
|
18
|
+
check_context_files
|
|
19
|
+
check_mcp_buildable
|
|
20
|
+
check_ripgrep
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
attr_reader :app
|
|
24
|
+
|
|
25
|
+
def initialize(app = nil)
|
|
26
|
+
@app = app || Rails.application
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def run
|
|
30
|
+
results = CHECKS.map { |check| send(check) }
|
|
31
|
+
score = compute_score(results)
|
|
32
|
+
{ checks: results, score: score }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def check_schema
|
|
38
|
+
schema_path = File.join(app.root, "db/schema.rb")
|
|
39
|
+
if File.exist?(schema_path)
|
|
40
|
+
Check.new(name: "Schema", status: :pass, message: "db/schema.rb found", fix: nil)
|
|
41
|
+
else
|
|
42
|
+
Check.new(name: "Schema", status: :warn, message: "db/schema.rb not found", fix: "Run `rails db:schema:dump` to generate it")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def check_models
|
|
47
|
+
models_dir = File.join(app.root, "app/models")
|
|
48
|
+
if Dir.exist?(models_dir) && Dir.glob(File.join(models_dir, "**/*.rb")).any?
|
|
49
|
+
count = Dir.glob(File.join(models_dir, "**/*.rb")).size
|
|
50
|
+
Check.new(name: "Models", status: :pass, message: "#{count} model files found", fix: nil)
|
|
51
|
+
else
|
|
52
|
+
Check.new(name: "Models", status: :warn, message: "No model files found in app/models/", fix: "Generate models with `rails generate model`")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def check_routes
|
|
57
|
+
routes_path = File.join(app.root, "config/routes.rb")
|
|
58
|
+
if File.exist?(routes_path)
|
|
59
|
+
Check.new(name: "Routes", status: :pass, message: "config/routes.rb found", fix: nil)
|
|
60
|
+
else
|
|
61
|
+
Check.new(name: "Routes", status: :fail, message: "config/routes.rb not found", fix: "Ensure you're in a Rails app root directory")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def check_gems
|
|
66
|
+
lock_path = File.join(app.root, "Gemfile.lock")
|
|
67
|
+
if File.exist?(lock_path)
|
|
68
|
+
Check.new(name: "Gems", status: :pass, message: "Gemfile.lock found", fix: nil)
|
|
69
|
+
else
|
|
70
|
+
Check.new(name: "Gems", status: :warn, message: "Gemfile.lock not found", fix: "Run `bundle install` to generate it")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def check_controllers
|
|
75
|
+
dir = File.join(app.root, "app/controllers")
|
|
76
|
+
if Dir.exist?(dir) && Dir.glob(File.join(dir, "**/*.rb")).any?
|
|
77
|
+
count = Dir.glob(File.join(dir, "**/*.rb")).size
|
|
78
|
+
Check.new(name: "Controllers", status: :pass, message: "#{count} controller files found", fix: nil)
|
|
79
|
+
else
|
|
80
|
+
Check.new(name: "Controllers", status: :warn, message: "No controller files found in app/controllers/", fix: "Generate controllers with `rails generate controller`")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def check_views
|
|
85
|
+
dir = File.join(app.root, "app/views")
|
|
86
|
+
if Dir.exist?(dir) && Dir.glob(File.join(dir, "**/*")).reject { |f| File.directory?(f) }.any?
|
|
87
|
+
count = Dir.glob(File.join(dir, "**/*")).reject { |f| File.directory?(f) }.size
|
|
88
|
+
Check.new(name: "Views", status: :pass, message: "#{count} view files found", fix: nil)
|
|
89
|
+
else
|
|
90
|
+
Check.new(name: "Views", status: :warn, message: "No view files found in app/views/", fix: "Views are generated alongside controllers")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def check_i18n
|
|
95
|
+
dir = File.join(app.root, "config/locales")
|
|
96
|
+
if Dir.exist?(dir) && Dir.glob(File.join(dir, "**/*.{yml,yaml}")).any?
|
|
97
|
+
count = Dir.glob(File.join(dir, "**/*.{yml,yaml}")).size
|
|
98
|
+
Check.new(name: "I18n", status: :pass, message: "#{count} locale files found", fix: nil)
|
|
99
|
+
else
|
|
100
|
+
Check.new(name: "I18n", status: :warn, message: "No locale files found in config/locales/", fix: "Add locale files for internationalization support")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def check_tests
|
|
105
|
+
if Dir.exist?(File.join(app.root, "spec")) || Dir.exist?(File.join(app.root, "test"))
|
|
106
|
+
framework = Dir.exist?(File.join(app.root, "spec")) ? "RSpec" : "Minitest"
|
|
107
|
+
Check.new(name: "Tests", status: :pass, message: "#{framework} test directory found", fix: nil)
|
|
108
|
+
else
|
|
109
|
+
Check.new(name: "Tests", status: :warn, message: "No test directory found", fix: "Set up tests with `rails generate rspec:install` or use default Minitest")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def check_context_files
|
|
114
|
+
claude_path = File.join(app.root, "CLAUDE.md")
|
|
115
|
+
if File.exist?(claude_path)
|
|
116
|
+
Check.new(name: "Context files", status: :pass, message: "CLAUDE.md exists", fix: nil)
|
|
117
|
+
else
|
|
118
|
+
Check.new(name: "Context files", status: :warn, message: "No context files generated yet", fix: "Run `rails ai:context` to generate them")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def check_mcp_buildable
|
|
123
|
+
Server.new(app).build
|
|
124
|
+
Check.new(name: "MCP server", status: :pass, message: "MCP server builds successfully", fix: nil)
|
|
125
|
+
rescue => e
|
|
126
|
+
Check.new(name: "MCP server", status: :fail, message: "MCP server failed to build: #{e.message}", fix: "Check mcp gem installation: `bundle info mcp`")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def check_ripgrep
|
|
130
|
+
if system("which rg > /dev/null 2>&1")
|
|
131
|
+
Check.new(name: "ripgrep", status: :pass, message: "rg available for code search", fix: nil)
|
|
132
|
+
else
|
|
133
|
+
Check.new(name: "ripgrep", status: :warn, message: "ripgrep not installed (code search will use slower Ruby fallback)", fix: "Install with `brew install ripgrep` or `apt install ripgrep`")
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def compute_score(results)
|
|
138
|
+
total = results.size * 10
|
|
139
|
+
earned = results.sum do |check|
|
|
140
|
+
case check.status
|
|
141
|
+
when :pass then 10
|
|
142
|
+
when :warn then 5
|
|
143
|
+
else 0
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
((earned.to_f / total) * 100).round
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -8,6 +8,14 @@ module RailsAiContext
|
|
|
8
8
|
Rails.application.config.rails_ai_context = RailsAiContext.configuration
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
# Auto-mount MCP HTTP middleware when configured
|
|
12
|
+
initializer "rails_ai_context.middleware" do |app|
|
|
13
|
+
if RailsAiContext.configuration.auto_mount
|
|
14
|
+
require_relative "middleware"
|
|
15
|
+
app.middleware.use RailsAiContext::Middleware
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
11
19
|
# Register Rake tasks
|
|
12
20
|
rake_tasks do
|
|
13
21
|
load File.expand_path("tasks/rails_ai_context.rake", __dir__)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module RailsAiContext
|
|
6
|
+
# Computes a SHA256 fingerprint of key application files to detect changes.
|
|
7
|
+
# Used by BaseTool to invalidate cached introspection when files change.
|
|
8
|
+
class Fingerprinter
|
|
9
|
+
WATCHED_FILES = %w[
|
|
10
|
+
db/schema.rb
|
|
11
|
+
config/routes.rb
|
|
12
|
+
Gemfile.lock
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
WATCHED_DIRS = %w[
|
|
16
|
+
app/models
|
|
17
|
+
app/controllers
|
|
18
|
+
app/views
|
|
19
|
+
app/jobs
|
|
20
|
+
app/mailers
|
|
21
|
+
app/channels
|
|
22
|
+
app/javascript/controllers
|
|
23
|
+
config/initializers
|
|
24
|
+
lib/tasks
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def compute(app)
|
|
29
|
+
root = app.root.to_s
|
|
30
|
+
digest = Digest::SHA256.new
|
|
31
|
+
|
|
32
|
+
WATCHED_FILES.each do |file|
|
|
33
|
+
path = File.join(root, file)
|
|
34
|
+
digest.update(File.mtime(path).to_f.to_s) if File.exist?(path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
WATCHED_DIRS.each do |dir|
|
|
38
|
+
full_dir = File.join(root, dir)
|
|
39
|
+
next unless Dir.exist?(full_dir)
|
|
40
|
+
|
|
41
|
+
Dir.glob(File.join(full_dir, "**/*.{rb,rake,js,ts,erb,haml,slim,yml}")).sort.each do |path|
|
|
42
|
+
digest.update(File.mtime(path).to_f.to_s)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
digest.hexdigest
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def changed?(app, previous)
|
|
50
|
+
compute(app) != previous
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -53,6 +53,22 @@ module RailsAiContext
|
|
|
53
53
|
when :jobs then Introspectors::JobIntrospector.new(app)
|
|
54
54
|
when :gems then Introspectors::GemIntrospector.new(app)
|
|
55
55
|
when :conventions then Introspectors::ConventionDetector.new(app)
|
|
56
|
+
when :stimulus then Introspectors::StimulusIntrospector.new(app)
|
|
57
|
+
when :database_stats then Introspectors::DatabaseStatsIntrospector.new(app)
|
|
58
|
+
when :controllers then Introspectors::ControllerIntrospector.new(app)
|
|
59
|
+
when :views then Introspectors::ViewIntrospector.new(app)
|
|
60
|
+
when :turbo then Introspectors::TurboIntrospector.new(app)
|
|
61
|
+
when :i18n then Introspectors::I18nIntrospector.new(app)
|
|
62
|
+
when :config then Introspectors::ConfigIntrospector.new(app)
|
|
63
|
+
when :active_storage then Introspectors::ActiveStorageIntrospector.new(app)
|
|
64
|
+
when :action_text then Introspectors::ActionTextIntrospector.new(app)
|
|
65
|
+
when :auth then Introspectors::AuthIntrospector.new(app)
|
|
66
|
+
when :api then Introspectors::ApiIntrospector.new(app)
|
|
67
|
+
when :tests then Introspectors::TestIntrospector.new(app)
|
|
68
|
+
when :rake_tasks then Introspectors::RakeTaskIntrospector.new(app)
|
|
69
|
+
when :assets then Introspectors::AssetPipelineIntrospector.new(app)
|
|
70
|
+
when :devops then Introspectors::DevOpsIntrospector.new(app)
|
|
71
|
+
when :action_mailbox then Introspectors::ActionMailboxIntrospector.new(app)
|
|
56
72
|
else
|
|
57
73
|
raise ConfigurationError, "Unknown introspector: #{name}"
|
|
58
74
|
end
|