rails-ai-context 0.12.0 → 0.13.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 +62 -0
- data/CLAUDE.md +2 -2
- data/CONTRIBUTING.md +1 -1
- data/README.md +9 -8
- data/demo_script.sh +1 -1
- data/docs/GUIDE.md +8 -8
- data/lib/rails_ai_context/introspectors/model_introspector.rb +6 -0
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +108 -17
- data/lib/rails_ai_context/serializers/claude_serializer.rb +37 -1
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +87 -3
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +90 -3
- data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +48 -2
- data/lib/rails_ai_context/serializers/opencode_serializer.rb +3 -1
- data/lib/rails_ai_context/serializers/windsurf_rules_serializer.rb +44 -2
- data/lib/rails_ai_context/serializers/windsurf_serializer.rb +74 -2
- data/lib/rails_ai_context/server.rb +2 -1
- data/lib/rails_ai_context/tools/validate.rb +205 -0
- data/lib/rails_ai_context/version.rb +1 -1
- data/server.json +3 -3
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f4f1bb24cd5ba8134913a1ac5ba7bbf28720a89aa1b80d9673692389d329a2ea
|
|
4
|
+
data.tar.gz: bc7afb4e33a1e2935beefa4e0bdefc879b7b1d486a9d8b694040d51abd764a9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92889b3f4f6be035241beb7cc8cd95633d44867b6c4b3e0c1e6bc2bc461c51ae649e91f49a07fa32d6d55ed22af408f271a4c744a7ceb8e970355ef2289b7d25
|
|
7
|
+
data.tar.gz: 8d4d623a38995ebf0b31dd673e4bfb80fb459b5ce16169562b7a1b3e0ffba2c6f2549f0508ae000c256e7eddc290cdaf234604f2eba70b302c9f0e8c9f59d4c8
|
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.13.0] - 2026-03-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`rails_validate` MCP tool** — batch syntax validation for Ruby, ERB, and JavaScript files. Replaces separate `ruby -c`, ERB check, and `node -c` calls. Returns pass/fail for each file with error details. Uses `Open3.capture2e` (no shell execution). Falls back to brace-matching when Node.js is unavailable.
|
|
13
|
+
- **Model constants extraction** — introspects `STATUSES = %w[...]` style constants and includes them in model context.
|
|
14
|
+
- **Global before_actions in controller rules** — OpenCode AGENTS.md now shows ApplicationController before_actions.
|
|
15
|
+
- **Service objects and jobs listed** — OpenCode controller AGENTS.md now lists service objects and background jobs.
|
|
16
|
+
- **Validate spec** — 8 tests covering happy path, syntax errors, path traversal, MAX_FILES, unsupported types.
|
|
17
|
+
|
|
18
|
+
### Security
|
|
19
|
+
|
|
20
|
+
- **Validate tool uses Open3 array form** — no shell execution for `ruby -c`, ERB compilation, or `node -c`. Fixed critical shell quoting bug in ERB validation that caused it to always fail.
|
|
21
|
+
- **File size limit** on JavaScript fallback validation (2MB).
|
|
22
|
+
- **`which node` check uses array form** — `system("which", "node")` instead of shell string.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- ERB validation was broken due to shell quoting bug (backticks + nested quotes). Replaced with `Open3.capture2e("ruby", "-e", script, ARGV[0])`.
|
|
27
|
+
- Rubocop offenses in validate.rb (18 spacing issues auto-corrected).
|
|
28
|
+
|
|
29
|
+
## [0.12.0] - 2026-03-20
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **Design Token Introspector** — auto-detects CSS framework and extracts tokens from Tailwind v3/v4, Bootstrap/Sass, plain CSS custom properties, Webpacker-era stylesheets, and ViewComponent sidecar CSS. Tested across 8 CSS setups. Added to standard preset.
|
|
34
|
+
- **`rails_get_edit_context` MCP tool** — purpose-built for surgical edits. Returns code around a match point with line numbers. Replaces the Read + Edit workflow with a single call.
|
|
35
|
+
- **Line numbers in action source** — `rails_get_controllers(action: "index")` now returns start/end line numbers for targeted editing.
|
|
36
|
+
- **Model file structure** — `rails_get_model_details(model: "Cook")` now returns line ranges for each section (associations, validations, scopes, etc.).
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **MCP instructions updated** — "Use MCP for reference files (schema, routes, tests). Read directly if you'll edit." Prevents unnecessary double-reads.
|
|
41
|
+
- **UI pattern extractor rewritten** — semantic labels (primary/secondary/danger), deduplication, 12+ component types, color scheme + radius + form layout extraction, framework-agnostic.
|
|
42
|
+
- **Schema rules include column types** — `status:string, intake:jsonb` instead of just names. Also shows foreign keys, indexes, and enum values.
|
|
43
|
+
- **View standard detail enhanced** — shows partial fields, helper methods, and shared partials.
|
|
44
|
+
|
|
45
|
+
### Security
|
|
46
|
+
|
|
47
|
+
- **File.realpath symlink protection** on all file-reading tools (get_view, get_edit_context, get_test_info, search_code).
|
|
48
|
+
- **File size limits** — 2MB on controllers/models/views, 500KB on test files.
|
|
49
|
+
- **Ripgrep flag injection prevention** — `--` separator before user pattern.
|
|
50
|
+
- **Nil guards** on all component rendering across 10 serializers.
|
|
51
|
+
- **Non-greedy regex** — ReDoS prevention in card/input/label pattern matching.
|
|
52
|
+
- **UTF-8 encoding safety** — all File.read calls handle binary/non-UTF-8 files gracefully.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- Off-by-one in model structure section line ranges.
|
|
57
|
+
- Stimulus sort crash on nil controller name.
|
|
58
|
+
- Secondary button picking up disabled states (`cursor-not-allowed`).
|
|
59
|
+
- Progress bars misclassified as badges.
|
|
60
|
+
- Input detection picking up alert divs instead of actual inputs.
|
|
61
|
+
|
|
62
|
+
## [0.11.0] - 2026-03-20
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- **UI pattern extraction** — scans all views for repeated CSS class patterns. Detects buttons, cards, inputs, labels, badges, links, headings, flashes, alerts. Added to ALL serializers (root files + split rules for Claude, Cursor, Windsurf, Copilot, OpenCode).
|
|
67
|
+
- **View partial structure** — `rails_get_view(detail: "standard")` shows model fields and helper methods used by each partial.
|
|
68
|
+
- **Schema column names** — `.claude/rules/rails-schema.md` shows key column names with types, foreign keys, indexes, and enum values. Keeps polymorphic `_type`, STI `type`, and soft-delete `deleted_at` columns.
|
|
69
|
+
|
|
8
70
|
## [0.10.0] - 2026-03-19
|
|
9
71
|
|
|
10
72
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -9,7 +9,7 @@ structure to AI assistants via the Model Context Protocol (MCP).
|
|
|
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
11
|
- `lib/rails_ai_context/introspectors/` — 29 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)
|
|
12
|
-
- `lib/rails_ai_context/tools/` —
|
|
12
|
+
- `lib/rails_ai_context/tools/` — 13 MCP tools using the official mcp SDK
|
|
13
13
|
- `lib/rails_ai_context/serializers/` — Output formatters (claude, claude_rules, opencode, cursor_rules, windsurf, windsurf_rules, copilot, copilot_instructions, rules, markdown, JSON)
|
|
14
14
|
- `lib/rails_ai_context/resources.rb` — MCP resources (static data AI clients read directly)
|
|
15
15
|
- `lib/rails_ai_context/server.rb` — MCP server configuration (stdio + HTTP transports)
|
|
@@ -42,7 +42,7 @@ structure to AI assistants via the Model Context Protocol (MCP).
|
|
|
42
42
|
## Testing
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
bundle exec rspec # Run specs (
|
|
45
|
+
bundle exec rspec # Run specs (491 examples)
|
|
46
46
|
bundle exec rubocop # Lint
|
|
47
47
|
```
|
|
48
48
|
|
data/CONTRIBUTING.md
CHANGED
|
@@ -19,7 +19,7 @@ The test suite uses [Combustion](https://github.com/pat/combustion) to boot a mi
|
|
|
19
19
|
```
|
|
20
20
|
lib/rails_ai_context/
|
|
21
21
|
├── introspectors/ # 29 introspectors (schema, models, routes, etc.)
|
|
22
|
-
├── tools/ #
|
|
22
|
+
├── tools/ # 13 MCP tools with detail levels and pagination
|
|
23
23
|
├── serializers/ # Per-assistant formatters (claude, opencode, cursor, windsurf, copilot, JSON)
|
|
24
24
|
├── server.rb # MCP server setup (stdio + HTTP)
|
|
25
25
|
├── live_reload.rb # MCP live reload (file watcher + cache invalidation)
|
data/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Same task — *"Add status and date range filters to the Cooks index page"* —
|
|
|
27
27
|
|
|
28
28
|
| Setup | Tokens | Saved | What it knows |
|
|
29
29
|
|-------|--------|-------|---------------|
|
|
30
|
-
| **rails-ai-context (full)** | **28,834** | **37%** |
|
|
30
|
+
| **rails-ai-context (full)** | **28,834** | **37%** | 13 MCP tools + generated docs + rules |
|
|
31
31
|
| rails-ai-context CLAUDE.md only | 33,106 | 27% | Generated docs + rules, no MCP tools |
|
|
32
32
|
| Normal Claude `/init` | 40,700 | 11% | Generic CLAUDE.md only |
|
|
33
33
|
| No rails-ai-context at all | 45,477 | baseline | Nothing — discovers everything from scratch |
|
|
@@ -95,9 +95,9 @@ The install generator creates `.mcp.json` for auto-discovery — Claude Code and
|
|
|
95
95
|
|
|
96
96
|
---
|
|
97
97
|
|
|
98
|
-
##
|
|
98
|
+
## 13 Live MCP Tools
|
|
99
99
|
|
|
100
|
-
The gem exposes **
|
|
100
|
+
The gem exposes **13 read-only tools** via MCP that AI clients call on-demand:
|
|
101
101
|
|
|
102
102
|
| Tool | What it returns |
|
|
103
103
|
|------|----------------|
|
|
@@ -113,6 +113,7 @@ The gem exposes **12 read-only tools** via MCP that AI clients call on-demand:
|
|
|
113
113
|
| `rails_get_view` | View templates, partials, Stimulus references |
|
|
114
114
|
| `rails_get_stimulus` | Stimulus controllers — targets, values, actions, outlets |
|
|
115
115
|
| `rails_get_edit_context` | Surgical edit helper — returns code around a match with line numbers |
|
|
116
|
+
| `rails_validate` | Batch syntax validation for Ruby, ERB, and JavaScript files |
|
|
116
117
|
|
|
117
118
|
### Smart Detail Levels
|
|
118
119
|
|
|
@@ -352,10 +353,10 @@ Works with every Rails architecture — auto-detects what's relevant:
|
|
|
352
353
|
| Setup | Coverage | Notes |
|
|
353
354
|
|-------|----------|-------|
|
|
354
355
|
| Rails full-stack (ERB + Hotwire) | 29/29 | All introspectors relevant |
|
|
355
|
-
| Rails + Inertia.js (React/Vue) | ~22/
|
|
356
|
-
| Rails API + React/Next.js SPA | ~20/
|
|
357
|
-
| Rails API + mobile app | ~20/
|
|
358
|
-
| Rails engine (mountable gem) | ~15/
|
|
356
|
+
| Rails + Inertia.js (React/Vue) | ~22/29 | Views/Turbo partially useful, backend fully covered |
|
|
357
|
+
| Rails API + React/Next.js SPA | ~20/29 | Schema, models, routes, API, auth, jobs — all covered |
|
|
358
|
+
| Rails API + mobile app | ~20/29 | Same as SPA — backend introspection is identical |
|
|
359
|
+
| Rails engine (mountable gem) | ~15/29 | Core introspectors (schema, models, routes, gems) work |
|
|
359
360
|
|
|
360
361
|
Frontend introspectors (views, Turbo, Stimulus, assets) degrade gracefully — they report nothing when those features aren't present.
|
|
361
362
|
|
|
@@ -390,7 +391,7 @@ The gem parses `db/schema.rb` as text when no database is connected. Works in CI
|
|
|
390
391
|
```bash
|
|
391
392
|
git clone https://github.com/crisnahine/rails-ai-context.git
|
|
392
393
|
cd rails-ai-context && bundle install
|
|
393
|
-
bundle exec rspec #
|
|
394
|
+
bundle exec rspec # 491 examples
|
|
394
395
|
bundle exec rubocop # Lint
|
|
395
396
|
```
|
|
396
397
|
|
data/demo_script.sh
CHANGED
data/docs/GUIDE.md
CHANGED
|
@@ -246,7 +246,7 @@ rails ai:context:claude # Use this instead (no quoting needed)
|
|
|
246
246
|
|
|
247
247
|
## MCP Tools — Full Reference
|
|
248
248
|
|
|
249
|
-
All
|
|
249
|
+
All 13 tools are **read-only** and **idempotent** — they never modify your application or database.
|
|
250
250
|
|
|
251
251
|
### rails_get_schema
|
|
252
252
|
|
|
@@ -568,7 +568,7 @@ RailsAiContext.configure do |config|
|
|
|
568
568
|
end
|
|
569
569
|
```
|
|
570
570
|
|
|
571
|
-
Both transports are **read-only** — they expose the same
|
|
571
|
+
Both transports are **read-only** — they expose the same 13 tools and never modify your app.
|
|
572
572
|
|
|
573
573
|
---
|
|
574
574
|
|
|
@@ -673,7 +673,7 @@ All split rules include an app overview file, so no context is lost when root fi
|
|
|
673
673
|
|
|
674
674
|
## Introspectors — Full List
|
|
675
675
|
|
|
676
|
-
### Standard preset (
|
|
676
|
+
### Standard preset (12 introspectors)
|
|
677
677
|
|
|
678
678
|
These run by default. Fast and cover core Rails structure.
|
|
679
679
|
|
|
@@ -689,7 +689,7 @@ These run by default. Fast and cover core Rails structure.
|
|
|
689
689
|
| `tests` | Test framework (rspec/minitest), factories/fixtures with locations and counts, system tests, CI config files, coverage tool, test helpers, VCR cassettes. |
|
|
690
690
|
| `migrations` | Total count, schema version, pending migrations, recent migration history with detected actions (create_table, add_column, etc.), migration statistics. |
|
|
691
691
|
|
|
692
|
-
### Full preset (
|
|
692
|
+
### Full preset (28 introspectors)
|
|
693
693
|
|
|
694
694
|
Includes all standard introspectors plus:
|
|
695
695
|
|
|
@@ -813,10 +813,10 @@ OpenCode uses **per-directory lazy-loading**: when the agent reads a file, it wa
|
|
|
813
813
|
| Setup | Coverage | Notes |
|
|
814
814
|
|-------|----------|-------|
|
|
815
815
|
| Rails full-stack (ERB + Hotwire) | 29/29 | All introspectors relevant |
|
|
816
|
-
| Rails + Inertia.js (React/Vue) | ~22/
|
|
817
|
-
| Rails API + React/Next.js SPA | ~20/
|
|
818
|
-
| Rails API + mobile app | ~20/
|
|
819
|
-
| Rails engine (mountable gem) | ~15/
|
|
816
|
+
| Rails + Inertia.js (React/Vue) | ~22/29 | Views/Turbo partially useful, backend fully covered |
|
|
817
|
+
| Rails API + React/Next.js SPA | ~20/29 | Schema, models, routes, API, auth, jobs — all covered |
|
|
818
|
+
| Rails API + mobile app | ~20/29 | Same as SPA — backend introspection is identical |
|
|
819
|
+
| Rails engine (mountable gem) | ~15/29 | Core introspectors (schema, models, routes, gems) work |
|
|
820
820
|
|
|
821
821
|
Frontend introspectors (views, Turbo, Stimulus, assets) degrade gracefully — they report nothing when those features aren't present.
|
|
822
822
|
|
|
@@ -179,6 +179,12 @@ module RailsAiContext
|
|
|
179
179
|
macros[:serialize] = source.scan(/\bserialize\s+:(\w+)/).flatten if source.match?(/\bserialize\s+:/)
|
|
180
180
|
macros[:store] = source.scan(/\bstore(?:_accessor)?\s+:(\w+)/).flatten if source.match?(/\bstore(?:_accessor)?\s+:/)
|
|
181
181
|
|
|
182
|
+
# Constants with value lists (e.g. STATUSES = %w[pending completed])
|
|
183
|
+
constants = source.scan(/\b([A-Z][A-Z_]+)\s*=\s*%[wi]\[([^\]]+)\]/).map do |name, values|
|
|
184
|
+
{ name: name, values: values.split }
|
|
185
|
+
end
|
|
186
|
+
macros[:constants] = constants if constants.any?
|
|
187
|
+
|
|
182
188
|
# Delegations
|
|
183
189
|
delegations = source.scan(/\bdelegate\s+(.+?),\s*to:\s*:(\w+)/m).map do |methods_str, target|
|
|
184
190
|
{ methods: methods_str.scan(/:(\w+)/).flatten, to: target }
|
|
@@ -77,6 +77,19 @@ module RailsAiContext
|
|
|
77
77
|
(conv[:architecture] || []).first(5).each { |p| lines << "- #{p}" }
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
# ApplicationController before_actions — apply to all controllers
|
|
81
|
+
begin
|
|
82
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
83
|
+
app_ctrl_file = File.join(root, "app", "controllers", "application_controller.rb")
|
|
84
|
+
if File.exist?(app_ctrl_file)
|
|
85
|
+
source = File.read(app_ctrl_file)
|
|
86
|
+
before_actions = source.scan(/before_action\s+:(\w+)/).flatten
|
|
87
|
+
if before_actions.any?
|
|
88
|
+
lines << "" << "**Global before_actions:** #{before_actions.join(', ')}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
rescue; end
|
|
92
|
+
|
|
80
93
|
lines << ""
|
|
81
94
|
lines << "Use MCP tools for detailed data. Start with `detail:\"summary\"`."
|
|
82
95
|
|
|
@@ -92,8 +105,8 @@ module RailsAiContext
|
|
|
92
105
|
lines = [
|
|
93
106
|
"# Database Tables (#{tables.size})",
|
|
94
107
|
"",
|
|
95
|
-
"
|
|
96
|
-
"
|
|
108
|
+
"All columns with types are listed below — no need to read db/schema.rb.",
|
|
109
|
+
"For indexes, foreign keys, or constraints, use `rails_get_schema(table:\"name\")`.",
|
|
97
110
|
""
|
|
98
111
|
]
|
|
99
112
|
|
|
@@ -118,8 +131,7 @@ module RailsAiContext
|
|
|
118
131
|
true
|
|
119
132
|
end
|
|
120
133
|
|
|
121
|
-
col_sample = key_cols.
|
|
122
|
-
col_sample << "..." if key_cols.size > 10
|
|
134
|
+
col_sample = key_cols.map { |c| "#{c[:name]}:#{c[:type]}" }
|
|
123
135
|
col_str = col_sample.any? ? " — #{col_sample.join(', ')}" : ""
|
|
124
136
|
|
|
125
137
|
# Foreign keys
|
|
@@ -158,8 +170,8 @@ module RailsAiContext
|
|
|
158
170
|
lines = [
|
|
159
171
|
"# ActiveRecord Models (#{models.size})",
|
|
160
172
|
"",
|
|
161
|
-
"
|
|
162
|
-
"
|
|
173
|
+
"Check this file first for associations, scopes, constants, and validations.",
|
|
174
|
+
"If you need more detail (callbacks, methods, business logic), use `rails_get_model_details(model:\"Name\")` or Read the file directly.",
|
|
163
175
|
""
|
|
164
176
|
]
|
|
165
177
|
|
|
@@ -172,6 +184,39 @@ module RailsAiContext
|
|
|
172
184
|
line += " (table: #{table})" if table
|
|
173
185
|
line += " — #{assocs} assocs, #{vals} validations"
|
|
174
186
|
lines << line
|
|
187
|
+
|
|
188
|
+
# Include app-specific concerns (filter out Rails/gem internals)
|
|
189
|
+
noise = %w[GeneratedAssociationMethods GeneratedAttributeMethods Kernel PP ObjectMixin
|
|
190
|
+
GlobalID Bullet ActionText Turbo ActiveStorage JSON]
|
|
191
|
+
concerns = (data[:concerns] || []).select { |c|
|
|
192
|
+
!noise.any? { |n| c.include?(n) } && !c.start_with?("Devise") && !c.include?("::")
|
|
193
|
+
}
|
|
194
|
+
lines << " concerns: #{concerns.join(', ')}" if concerns.any?
|
|
195
|
+
|
|
196
|
+
# Include scopes so agents know available query methods
|
|
197
|
+
scopes = data[:scopes] || []
|
|
198
|
+
lines << " scopes: #{scopes.join(', ')}" if scopes.any?
|
|
199
|
+
|
|
200
|
+
# Include app-specific instance methods (filter out Rails-generated ones)
|
|
201
|
+
generated_patterns = %w[build_ create_ reload_ reset_ _changed? _previously_changed?
|
|
202
|
+
_ids _ids= _before_last_save _before_type_cast _came_from_user?
|
|
203
|
+
_for_database _in_database _was]
|
|
204
|
+
methods = (data[:instance_methods] || []).reject { |m|
|
|
205
|
+
generated_patterns.any? { |p| m.include?(p) } || m.end_with?("=")
|
|
206
|
+
}.first(10)
|
|
207
|
+
lines << " methods: #{methods.join(', ')}" if methods.any?
|
|
208
|
+
|
|
209
|
+
# Include constants (e.g. STATUSES, MODES) so agents know valid values
|
|
210
|
+
constants = data[:constants] || []
|
|
211
|
+
constants.each do |c|
|
|
212
|
+
lines << " #{c[:name]}: #{c[:values].join(', ')}"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Include enums so agents know valid values
|
|
216
|
+
enums = data[:enums] || {}
|
|
217
|
+
enums.each do |attr, values|
|
|
218
|
+
lines << " #{attr}: #{values.join(', ')}"
|
|
219
|
+
end
|
|
175
220
|
end
|
|
176
221
|
|
|
177
222
|
lines.join("\n")
|
|
@@ -210,6 +255,45 @@ module RailsAiContext
|
|
|
210
255
|
lines << "- Grid: #{fl[:grid]}" if fl[:grid]
|
|
211
256
|
end
|
|
212
257
|
|
|
258
|
+
# Shared partials — so agents reuse them instead of recreating
|
|
259
|
+
begin
|
|
260
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
261
|
+
shared_dir = File.join(root, "app", "views", "shared")
|
|
262
|
+
if Dir.exist?(shared_dir)
|
|
263
|
+
partials = Dir.glob(File.join(shared_dir, "_*.html.erb"))
|
|
264
|
+
.map { |f| File.basename(f) }
|
|
265
|
+
.sort
|
|
266
|
+
if partials.any?
|
|
267
|
+
lines << "" << "## Shared partials (app/views/shared/)"
|
|
268
|
+
partials.each { |p| lines << "- #{p}" }
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
rescue; end
|
|
272
|
+
|
|
273
|
+
# Helpers — so agents use existing helpers instead of creating new ones
|
|
274
|
+
begin
|
|
275
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
276
|
+
helper_file = File.join(root, "app", "helpers", "application_helper.rb")
|
|
277
|
+
if File.exist?(helper_file)
|
|
278
|
+
helper_methods = File.read(helper_file).scan(/def\s+(\w+)/).flatten
|
|
279
|
+
if helper_methods.any?
|
|
280
|
+
lines << "" << "## Helpers (ApplicationHelper)"
|
|
281
|
+
lines << helper_methods.map { |m| "- #{m}" }.join("\n")
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
rescue; end
|
|
285
|
+
|
|
286
|
+
# Stimulus controllers — so agents reuse existing controllers
|
|
287
|
+
stim = context[:stimulus]
|
|
288
|
+
if stim.is_a?(Hash) && !stim[:error]
|
|
289
|
+
controllers = stim[:controllers] || []
|
|
290
|
+
if controllers.any?
|
|
291
|
+
names = controllers.map { |c| c[:name] || c[:file]&.gsub("_controller.js", "") }.compact.sort
|
|
292
|
+
lines << "" << "## Stimulus controllers"
|
|
293
|
+
lines << names.join(", ")
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
213
297
|
lines.join("\n")
|
|
214
298
|
end
|
|
215
299
|
|
|
@@ -218,26 +302,30 @@ module RailsAiContext
|
|
|
218
302
|
"# Rails MCP Tools — ALWAYS Use These First",
|
|
219
303
|
"",
|
|
220
304
|
"IMPORTANT: This project has live MCP tools that return parsed, up-to-date data.",
|
|
221
|
-
"
|
|
222
|
-
"The tools return structured, token-efficient summaries
|
|
305
|
+
"Use these tools for reference-only files (schema, routes, tests). For files you will edit, Read them directly.",
|
|
306
|
+
"The tools return structured, token-efficient summaries with line numbers.",
|
|
223
307
|
"",
|
|
224
308
|
"## When to use MCP tools vs Read",
|
|
225
309
|
"- Use MCP for files you WON'T edit (schema, routes, understanding context)",
|
|
226
310
|
"- For files you WILL edit, just Read them directly — you need Read before Edit anyway",
|
|
227
311
|
"- Use MCP for orientation (summary calls) on large codebases",
|
|
228
312
|
"- Skip MCP when CLAUDE.md + rules already have the info you need",
|
|
313
|
+
"- Do NOT call rails_get_model_details if CLAUDE.md already shows the model's associations and column types",
|
|
314
|
+
"- Do NOT call rails_get_stimulus just to check if Stimulus exists — CLAUDE.md confirms it",
|
|
229
315
|
"",
|
|
230
|
-
"##
|
|
231
|
-
"- `
|
|
232
|
-
"- `
|
|
233
|
-
"-
|
|
316
|
+
"## After editing — ALWAYS use rails_validate (not Bash)",
|
|
317
|
+
"- `rails_validate(files:[\"app/models/cook.rb\", \"app/controllers/cooks_controller.rb\", \"app/views/cooks/index.html.erb\"])` — one call checks all",
|
|
318
|
+
"- Do NOT run `ruby -c`, `erb` checks, or `node -c` separately — use rails_validate instead",
|
|
319
|
+
"- Do NOT re-read files to verify edits. Trust your Edit and validate syntax only.",
|
|
234
320
|
"",
|
|
235
|
-
"## Reference-only files —
|
|
236
|
-
"-
|
|
237
|
-
"-
|
|
238
|
-
"-
|
|
321
|
+
"## Reference-only files — check rules first, then MCP or Read if needed",
|
|
322
|
+
"- db/schema.rb — column names and types are in rails-schema.md rules. Read only if you need constraints/defaults.",
|
|
323
|
+
"- config/routes.rb — use `rails_get_routes` for reference. Read directly if you'll add routes.",
|
|
324
|
+
"- Model files — scopes, constants, enums are in rails-models.md rules. Read for business logic/methods.",
|
|
325
|
+
"- app/javascript/controllers/index.js — Stimulus auto-registers controllers. No need to read.",
|
|
326
|
+
"- Test files — use `rails_get_test_info(detail:\"full\")` for patterns.",
|
|
239
327
|
"",
|
|
240
|
-
"## Tools (
|
|
328
|
+
"## Tools (13)",
|
|
241
329
|
"",
|
|
242
330
|
"**rails_get_schema** — database tables, columns, indexes, foreign keys",
|
|
243
331
|
"- `rails_get_schema(detail:\"summary\")` — all tables with column counts",
|
|
@@ -271,6 +359,9 @@ module RailsAiContext
|
|
|
271
359
|
"**rails_get_edit_context** — surgical edit helper with line numbers",
|
|
272
360
|
"- `rails_get_edit_context(file:\"app/models/cook.rb\", near:\"scope\")` — returns code around match with line numbers",
|
|
273
361
|
"",
|
|
362
|
+
"**rails_validate** — syntax checker for edited files",
|
|
363
|
+
"- `rails_validate(files:[\"app/models/cook.rb\"])` — checks Ruby, ERB, JS syntax in one call",
|
|
364
|
+
"",
|
|
274
365
|
"**rails_get_config** — cache store, session, timezone, middleware, initializers",
|
|
275
366
|
"**rails_get_gems** — notable gems categorized by function",
|
|
276
367
|
"**rails_get_conventions** — architecture patterns, directory structure",
|
|
@@ -122,6 +122,14 @@ module RailsAiContext
|
|
|
122
122
|
line += " (#{assoc_count}a, #{val_count}v)" if assoc_count > 0 || val_count > 0
|
|
123
123
|
line += " — #{top_assocs}" if top_assocs && !top_assocs.empty?
|
|
124
124
|
lines << line
|
|
125
|
+
scopes = (data[:scopes] || [])
|
|
126
|
+
constants = (data[:constants] || [])
|
|
127
|
+
if scopes.any? || constants.any?
|
|
128
|
+
extras = []
|
|
129
|
+
extras << "scopes: #{scopes.join(', ')}" if scopes.any?
|
|
130
|
+
constants.each { |c| extras << "#{c[:name]}: #{c[:values].join(', ')}" }
|
|
131
|
+
lines << " #{extras.join(' | ')}"
|
|
132
|
+
end
|
|
125
133
|
end
|
|
126
134
|
lines << "- _...#{models.size - max_show} more (use `rails_get_model_details` tool)_" if models.size > max_show
|
|
127
135
|
lines << ""
|
|
@@ -155,6 +163,30 @@ module RailsAiContext
|
|
|
155
163
|
lines = [ "## Architecture" ]
|
|
156
164
|
arch.each { |p| lines << "- #{p}" }
|
|
157
165
|
patterns.first(8).each { |p| lines << "- #{p}" }
|
|
166
|
+
|
|
167
|
+
# List service objects and jobs from conventions directory_structure
|
|
168
|
+
dir_struct = conv[:directory_structure] || {}
|
|
169
|
+
|
|
170
|
+
if dir_struct["app/services"]
|
|
171
|
+
begin
|
|
172
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
173
|
+
service_files = Dir.glob(File.join(root, "app", "services", "*.rb"))
|
|
174
|
+
.map { |f| File.basename(f, ".rb").camelize }
|
|
175
|
+
.reject { |s| s == "ApplicationService" }
|
|
176
|
+
lines << "" << "**Services:** #{service_files.join(', ')}" if service_files.any?
|
|
177
|
+
rescue; end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
if dir_struct["app/jobs"]
|
|
181
|
+
begin
|
|
182
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
183
|
+
job_files = Dir.glob(File.join(root, "app", "jobs", "*.rb"))
|
|
184
|
+
.map { |f| File.basename(f, ".rb").camelize }
|
|
185
|
+
.reject { |j| j == "ApplicationJob" }
|
|
186
|
+
lines << "**Jobs:** #{job_files.join(', ')}" if job_files.any?
|
|
187
|
+
rescue; end
|
|
188
|
+
end
|
|
189
|
+
|
|
158
190
|
lines << ""
|
|
159
191
|
lines
|
|
160
192
|
end
|
|
@@ -193,7 +225,7 @@ module RailsAiContext
|
|
|
193
225
|
|
|
194
226
|
def render_mcp_guide # rubocop:disable Metrics/MethodLength
|
|
195
227
|
[
|
|
196
|
-
"## MCP Tools (
|
|
228
|
+
"## MCP Tools (13) — ALWAYS Use These First",
|
|
197
229
|
"",
|
|
198
230
|
"Use MCP for reference files (schema, routes, tests). Read directly if you'll edit.",
|
|
199
231
|
"MCP tools return line numbers. Start with `detail:\"summary\"`.",
|
|
@@ -206,6 +238,7 @@ module RailsAiContext
|
|
|
206
238
|
"- `rails_get_stimulus(detail:\"summary\")` → `(controller:\"name\")` — targets, actions, values",
|
|
207
239
|
"- `rails_get_test_info(detail:\"full\")` — fixtures, factories, helpers; `(model:\"Cook\")` — tests",
|
|
208
240
|
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_search_code`",
|
|
241
|
+
"- `rails_validate(files:[\"path/to/file.rb\"])` — validate Ruby, ERB, JS syntax in one call",
|
|
209
242
|
""
|
|
210
243
|
]
|
|
211
244
|
end
|
|
@@ -240,6 +273,9 @@ module RailsAiContext
|
|
|
240
273
|
"- Use the MCP tools to check schema before writing migrations",
|
|
241
274
|
"- Match existing code style",
|
|
242
275
|
"- Run tests after changes",
|
|
276
|
+
"- After editing, ALWAYS use `rails_validate(files:[...])` — do NOT use separate ruby -c / erb / node -c calls",
|
|
277
|
+
"- Do NOT re-read files to verify edits — trust your Edit, validate syntax only",
|
|
278
|
+
"- Stimulus controllers auto-register — no manual import in controllers/index.js needed",
|
|
243
279
|
""
|
|
244
280
|
]
|
|
245
281
|
end
|
|
@@ -78,6 +78,41 @@ module RailsAiContext
|
|
|
78
78
|
(conv[:architecture] || []).first(5).each { |p| lines << "- #{p}" }
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
# List service objects
|
|
82
|
+
begin
|
|
83
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
84
|
+
services_dir = File.join(root, "app", "services")
|
|
85
|
+
if Dir.exist?(services_dir)
|
|
86
|
+
service_files = Dir.glob(File.join(services_dir, "*.rb"))
|
|
87
|
+
.map { |f| File.basename(f, ".rb").camelize }
|
|
88
|
+
.reject { |s| s == "ApplicationService" }
|
|
89
|
+
lines << "- Services: #{service_files.join(', ')}" if service_files.any?
|
|
90
|
+
end
|
|
91
|
+
rescue; end
|
|
92
|
+
|
|
93
|
+
# List jobs
|
|
94
|
+
begin
|
|
95
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
96
|
+
jobs_dir = File.join(root, "app", "jobs")
|
|
97
|
+
if Dir.exist?(jobs_dir)
|
|
98
|
+
job_files = Dir.glob(File.join(jobs_dir, "*.rb"))
|
|
99
|
+
.map { |f| File.basename(f, ".rb").camelize }
|
|
100
|
+
.reject { |j| j == "ApplicationJob" }
|
|
101
|
+
lines << "- Jobs: #{job_files.join(', ')}" if job_files.any?
|
|
102
|
+
end
|
|
103
|
+
rescue; end
|
|
104
|
+
|
|
105
|
+
# ApplicationController before_actions
|
|
106
|
+
begin
|
|
107
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
108
|
+
app_ctrl = File.join(root, "app", "controllers", "application_controller.rb")
|
|
109
|
+
if File.exist?(app_ctrl)
|
|
110
|
+
source = File.read(app_ctrl)
|
|
111
|
+
before_actions = source.scan(/before_action\s+:(\w+)/).flatten
|
|
112
|
+
lines << "" << "**Global before_actions:** #{before_actions.join(', ')}" if before_actions.any?
|
|
113
|
+
end
|
|
114
|
+
rescue; end
|
|
115
|
+
|
|
81
116
|
lines << ""
|
|
82
117
|
lines << "Use MCP tools for detailed data. Start with `detail:\"summary\"`."
|
|
83
118
|
|
|
@@ -95,7 +130,7 @@ module RailsAiContext
|
|
|
95
130
|
"",
|
|
96
131
|
"# ActiveRecord Models (#{models.size})",
|
|
97
132
|
"",
|
|
98
|
-
"
|
|
133
|
+
"Check here first for scopes, constants, associations. Read model files for business logic/methods.",
|
|
99
134
|
""
|
|
100
135
|
]
|
|
101
136
|
|
|
@@ -103,6 +138,14 @@ module RailsAiContext
|
|
|
103
138
|
data = models[name]
|
|
104
139
|
assocs = (data[:associations] || []).size
|
|
105
140
|
lines << "- #{name} (#{assocs} associations)"
|
|
141
|
+
scopes = (data[:scopes] || [])
|
|
142
|
+
constants = (data[:constants] || [])
|
|
143
|
+
if scopes.any? || constants.any?
|
|
144
|
+
extras = []
|
|
145
|
+
extras << "scopes: #{scopes.join(', ')}" if scopes.any?
|
|
146
|
+
constants.each { |c| extras << "#{c[:name]}: #{c[:values].join(', ')}" }
|
|
147
|
+
lines << " #{extras.join(' | ')}"
|
|
148
|
+
end
|
|
106
149
|
end
|
|
107
150
|
|
|
108
151
|
lines << "- ...#{models.size - 30} more" if models.size > 30
|
|
@@ -151,6 +194,43 @@ module RailsAiContext
|
|
|
151
194
|
]
|
|
152
195
|
components.first(15).each { |c| next unless c[:label] && c[:classes]; lines << "- #{c[:label]}: `#{c[:classes]}`" }
|
|
153
196
|
|
|
197
|
+
# Shared partials
|
|
198
|
+
begin
|
|
199
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
200
|
+
shared_dir = File.join(root, "app", "views", "shared")
|
|
201
|
+
if Dir.exist?(shared_dir)
|
|
202
|
+
partials = Dir.glob(File.join(shared_dir, "_*.html.erb")).map { |f| File.basename(f) }.sort
|
|
203
|
+
if partials.any?
|
|
204
|
+
lines << "" << "## Shared partials"
|
|
205
|
+
partials.each { |p| lines << "- #{p}" }
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
rescue; end
|
|
209
|
+
|
|
210
|
+
# Helpers
|
|
211
|
+
begin
|
|
212
|
+
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
213
|
+
helper_file = File.join(root, "app", "helpers", "application_helper.rb")
|
|
214
|
+
if File.exist?(helper_file)
|
|
215
|
+
helper_methods = File.read(helper_file).scan(/def\s+(\w+)/).flatten
|
|
216
|
+
if helper_methods.any?
|
|
217
|
+
lines << "" << "## Helpers (ApplicationHelper)"
|
|
218
|
+
helper_methods.each { |m| lines << "- #{m}" }
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
rescue; end
|
|
222
|
+
|
|
223
|
+
# Stimulus controllers
|
|
224
|
+
stim = context[:stimulus]
|
|
225
|
+
if stim.is_a?(Hash) && !stim[:error]
|
|
226
|
+
controllers = stim[:controllers] || []
|
|
227
|
+
if controllers.any?
|
|
228
|
+
names = controllers.map { |c| c[:name] || c[:file]&.gsub("_controller.js", "") }.compact.sort
|
|
229
|
+
lines << "" << "## Stimulus controllers"
|
|
230
|
+
lines << names.join(", ")
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
154
234
|
lines.join("\n")
|
|
155
235
|
end
|
|
156
236
|
|
|
@@ -160,7 +240,7 @@ module RailsAiContext
|
|
|
160
240
|
"applyTo: \"**/*\"",
|
|
161
241
|
"---",
|
|
162
242
|
"",
|
|
163
|
-
"# Rails MCP Tools (
|
|
243
|
+
"# Rails MCP Tools (13) — Use These First",
|
|
164
244
|
"",
|
|
165
245
|
"Use MCP for reference files (schema, routes, tests). Read directly if you'll edit.",
|
|
166
246
|
"MCP tools return line numbers for surgical edits.",
|
|
@@ -173,7 +253,11 @@ module RailsAiContext
|
|
|
173
253
|
"- `rails_get_view(controller:\"cooks\")` — view list; `(path:\"cooks/index.html.erb\")` — content",
|
|
174
254
|
"- `rails_get_stimulus(detail:\"summary\")` → `(controller:\"name\")` — targets, actions, values",
|
|
175
255
|
"- `rails_get_test_info(detail:\"full\")` — fixtures, factories, helpers; `(model:\"Cook\")` — existing tests",
|
|
176
|
-
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_search_code`"
|
|
256
|
+
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_search_code`",
|
|
257
|
+
"- `rails_get_edit_context(file:\"path\", near:\"keyword\")` — surgical edit context with line numbers",
|
|
258
|
+
"- `rails_validate(files:[\"path\"])` — validate Ruby, ERB, JS syntax in one call",
|
|
259
|
+
"",
|
|
260
|
+
"After editing: use rails_validate to check syntax. Do NOT re-read files to verify."
|
|
177
261
|
]
|
|
178
262
|
|
|
179
263
|
lines.join("\n")
|