rails-ai-context 2.0.4 → 3.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 +27 -0
- data/CLAUDE.md +8 -4
- data/CONTRIBUTING.md +7 -2
- data/README.md +169 -85
- data/SECURITY.md +2 -2
- data/docs/GUIDE.md +89 -31
- data/exe/rails-ai-context +37 -1
- data/lib/generators/rails_ai_context/install/install_generator.rb +118 -11
- data/lib/rails_ai_context/cli/tool_runner.rb +259 -0
- data/lib/rails_ai_context/configuration.rb +6 -2
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +3 -135
- data/lib/rails_ai_context/serializers/claude_serializer.rb +3 -49
- data/lib/rails_ai_context/serializers/context_file_serializer.rb +1 -9
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +6 -40
- data/lib/rails_ai_context/serializers/copilot_serializer.rb +4 -40
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +6 -43
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +1 -1
- data/lib/rails_ai_context/serializers/opencode_serializer.rb +3 -38
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +214 -0
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +116 -5
- data/lib/rails_ai_context/tools/get_concern.rb +8 -1
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context.rb +1 -1
- data/server.json +2 -2
- metadata +4 -4
- data/lib/rails_ai_context/serializers/windsurf_rules_serializer.rb +0 -111
- data/lib/rails_ai_context/serializers/windsurf_serializer.rb +0 -160
|
@@ -9,6 +9,7 @@ module RailsAiContext
|
|
|
9
9
|
include TestCommandDetection
|
|
10
10
|
include StackOverviewHelper
|
|
11
11
|
include DesignSystemHelper
|
|
12
|
+
include ToolGuideHelper
|
|
12
13
|
|
|
13
14
|
attr_reader :context
|
|
14
15
|
|
|
@@ -91,50 +92,13 @@ module RailsAiContext
|
|
|
91
92
|
# Design System
|
|
92
93
|
lines.concat(render_design_system(context, max_lines: 35))
|
|
93
94
|
|
|
94
|
-
#
|
|
95
|
-
lines
|
|
96
|
-
lines << ""
|
|
97
|
-
lines << "This project has MCP tools for live introspection."
|
|
98
|
-
lines << "**Always start with `detail:\"summary\"`, then drill into specifics.**"
|
|
99
|
-
lines << ""
|
|
100
|
-
lines << "### Detail levels (schema, routes, models, controllers)"
|
|
101
|
-
lines << "- `summary` — names + counts (default limit: 50)"
|
|
102
|
-
lines << "- `standard` — names + key details (default limit: 15, this is the default)"
|
|
103
|
-
lines << "- `full` — everything including indexes, FKs (default limit: 5)"
|
|
104
|
-
lines << ""
|
|
105
|
-
lines << "### rails_get_schema"
|
|
106
|
-
lines << "Params: `table`, `detail`, `limit`, `offset`, `format`"
|
|
107
|
-
lines << "- `rails_get_schema(detail:\"summary\")` — all tables with column counts"
|
|
108
|
-
lines << "- `rails_get_schema(table:\"users\")` — full detail for one table"
|
|
109
|
-
lines << "- `rails_get_schema(detail:\"summary\", limit:20, offset:40)` — paginate"
|
|
110
|
-
lines << ""
|
|
111
|
-
lines << "### rails_get_model_details"
|
|
112
|
-
lines << "Params: `model`, `detail`"
|
|
113
|
-
lines << "- `rails_get_model_details(detail:\"summary\")` — list all model names"
|
|
114
|
-
lines << "- `rails_get_model_details(model:\"User\")` — associations, validations, scopes, enums"
|
|
115
|
-
lines << ""
|
|
116
|
-
lines << "### rails_get_routes"
|
|
117
|
-
lines << "Params: `controller`, `detail`, `limit`, `offset`"
|
|
118
|
-
lines << "- `rails_get_routes(detail:\"summary\")` — route counts per controller"
|
|
119
|
-
lines << "- `rails_get_routes(controller:\"users\")` — routes for one controller"
|
|
120
|
-
lines << ""
|
|
121
|
-
lines << "### rails_get_controllers"
|
|
122
|
-
lines << "Params: `controller`, `detail`"
|
|
123
|
-
lines << "- `rails_get_controllers(detail:\"summary\")` — names + action counts"
|
|
124
|
-
lines << "- `rails_get_controllers(controller:\"UsersController\")` — actions, filters, params"
|
|
125
|
-
lines << ""
|
|
126
|
-
lines << "### Other tools"
|
|
127
|
-
lines << "- `rails_get_config` — cache store, session, timezone, middleware"
|
|
128
|
-
lines << "- `rails_get_test_info` — test framework, factories/fixtures, CI config"
|
|
129
|
-
lines << "- `rails_get_gems` — notable gems categorized by function"
|
|
130
|
-
lines << "- `rails_get_conventions` — architecture patterns, directory structure"
|
|
131
|
-
lines << "- `rails_search_code(pattern:\"regex\", file_type:\"rb\", max_results:20)` — codebase search"
|
|
132
|
-
lines << ""
|
|
95
|
+
# Tools reference (respects tool_mode)
|
|
96
|
+
lines.concat(render_tools_guide)
|
|
133
97
|
|
|
134
98
|
# Conventions
|
|
135
99
|
lines << "## Conventions"
|
|
136
100
|
lines << "- Follow existing patterns and naming conventions"
|
|
137
|
-
lines << "- Use
|
|
101
|
+
lines << "- Use the introspection tools to check schema before writing migrations"
|
|
138
102
|
lines << "- Run `#{detect_test_command}` after changes"
|
|
139
103
|
lines << ""
|
|
140
104
|
|
|
@@ -8,6 +8,7 @@ module RailsAiContext
|
|
|
8
8
|
class CursorRulesSerializer
|
|
9
9
|
include StackOverviewHelper
|
|
10
10
|
include DesignSystemHelper
|
|
11
|
+
include ToolGuideHelper
|
|
11
12
|
|
|
12
13
|
attr_reader :context
|
|
13
14
|
|
|
@@ -141,7 +142,6 @@ module RailsAiContext
|
|
|
141
142
|
|
|
142
143
|
lines = [
|
|
143
144
|
"---",
|
|
144
|
-
"description: \"ActiveRecord models reference\"",
|
|
145
145
|
"globs:",
|
|
146
146
|
" - \"app/models/**/*.rb\"",
|
|
147
147
|
"alwaysApply: false",
|
|
@@ -184,7 +184,6 @@ module RailsAiContext
|
|
|
184
184
|
|
|
185
185
|
lines = [
|
|
186
186
|
"---",
|
|
187
|
-
"description: \"Controller reference\"",
|
|
188
187
|
"globs:",
|
|
189
188
|
" - \"app/controllers/**/*.rb\"",
|
|
190
189
|
"alwaysApply: false",
|
|
@@ -215,7 +214,6 @@ module RailsAiContext
|
|
|
215
214
|
|
|
216
215
|
lines = [
|
|
217
216
|
"---",
|
|
218
|
-
"description: \"Design system and UI patterns for this Rails app\"",
|
|
219
217
|
"globs:",
|
|
220
218
|
" - \"app/views/**/*.erb\"",
|
|
221
219
|
"alwaysApply: false",
|
|
@@ -253,52 +251,17 @@ module RailsAiContext
|
|
|
253
251
|
end
|
|
254
252
|
|
|
255
253
|
# Always-on MCP tool reference — strongest enforcement point for Cursor
|
|
256
|
-
def render_mcp_tools_rule
|
|
254
|
+
def render_mcp_tools_rule
|
|
257
255
|
lines = [
|
|
258
256
|
"---",
|
|
259
|
-
"description: \"Rails
|
|
257
|
+
"description: \"Rails tools (25) — MANDATORY, use before reading any reference files\"",
|
|
260
258
|
"alwaysApply: true",
|
|
261
259
|
"---",
|
|
262
|
-
""
|
|
263
|
-
"# Rails MCP Tools (25) — MANDATORY, Use Before Read",
|
|
264
|
-
"",
|
|
265
|
-
"CRITICAL: This project has live MCP tools. You MUST use them for gathering context.",
|
|
266
|
-
"Read files ONLY when you are about to edit them. Never read reference files directly.",
|
|
267
|
-
"",
|
|
268
|
-
"## Mandatory Workflow",
|
|
269
|
-
"1. Gathering context → use MCP tools (NOT file reads, NOT grep)",
|
|
270
|
-
"2. Reading files → ONLY files you will edit (Read is required before Edit)",
|
|
271
|
-
"3. After editing → `rails_validate(files:[...])` every time, no exceptions",
|
|
272
|
-
"",
|
|
273
|
-
"## Do NOT Bypass — Anti-Patterns",
|
|
274
|
-
"| Instead of... | Use this MCP tool |",
|
|
275
|
-
"|---------------|-------------------|",
|
|
276
|
-
"| Reading db/schema.rb | `rails_get_schema(table:\"name\")` |",
|
|
277
|
-
"| Reading config/routes.rb | `rails_get_routes(controller:\"name\")` |",
|
|
278
|
-
"| Reading model files for context | `rails_get_model_details(model:\"Name\")` |",
|
|
279
|
-
"| Grep for code patterns | `rails_search_code(pattern:\"regex\")` |",
|
|
280
|
-
"| Reading test files for patterns | `rails_get_test_info(model:\"Name\")` |",
|
|
281
|
-
"| Reading controller for context | `rails_get_controllers(controller:\"Name\", action:\"x\")` |",
|
|
282
|
-
"| Reading JS for Stimulus API | `rails_get_stimulus(controller:\"name\")` |",
|
|
283
|
-
"| Multiple reads for a feature | `rails_analyze_feature(feature:\"keyword\")` |",
|
|
284
|
-
"| ruby -c / erb / node -c | `rails_validate(files:[...])` |",
|
|
285
|
-
"",
|
|
286
|
-
"## All 25 Tools",
|
|
287
|
-
"- `rails_get_schema` | `rails_get_model_details` | `rails_get_routes` | `rails_get_controllers`",
|
|
288
|
-
"- `rails_get_view` | `rails_get_stimulus` | `rails_get_test_info` | `rails_analyze_feature`",
|
|
289
|
-
"- `rails_get_design_system` | `rails_get_edit_context` | `rails_validate` | `rails_search_code`",
|
|
290
|
-
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_security_scan`",
|
|
291
|
-
"- `rails_get_concern` | `rails_get_callbacks` | `rails_get_helper_methods` | `rails_get_service_pattern`",
|
|
292
|
-
"- `rails_get_job_pattern` | `rails_get_env` | `rails_get_partial_interface` | `rails_get_turbo_map`",
|
|
293
|
-
"- `rails_get_context(model:\"X\")` — composite cross-layer context in one call",
|
|
294
|
-
"",
|
|
295
|
-
"## Power Features",
|
|
296
|
-
"- `rails_search_code(pattern:\"method\", match_type:\"trace\")` — trace: definition + source + siblings + callers + tests",
|
|
297
|
-
"- `rails_get_concern(name:\"X\", detail:\"full\")` — concern methods with source code",
|
|
298
|
-
"- `rails_analyze_feature` — full-stack with inherited filters, route helpers, test gaps",
|
|
299
|
-
"- `rails_get_schema` — columns, indexes, defaults, encrypted hints, orphaned table warnings"
|
|
260
|
+
""
|
|
300
261
|
]
|
|
301
262
|
|
|
263
|
+
lines.concat(render_tools_guide)
|
|
264
|
+
|
|
302
265
|
lines.join("\n")
|
|
303
266
|
end
|
|
304
267
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RailsAiContext
|
|
4
4
|
module Serializers
|
|
5
5
|
# Generates AI-friendly markdown context files from introspection data.
|
|
6
|
-
# Outputs: CLAUDE.md (for Claude Code), .
|
|
6
|
+
# Outputs: CLAUDE.md (for Claude Code), copilot-instructions.md, etc.
|
|
7
7
|
class MarkdownSerializer # rubocop:disable Metrics/ClassLength
|
|
8
8
|
include TestCommandDetection
|
|
9
9
|
|
|
@@ -9,6 +9,7 @@ module RailsAiContext
|
|
|
9
9
|
include TestCommandDetection
|
|
10
10
|
include StackOverviewHelper
|
|
11
11
|
include DesignSystemHelper
|
|
12
|
+
include ToolGuideHelper
|
|
12
13
|
|
|
13
14
|
attr_reader :context
|
|
14
15
|
|
|
@@ -163,44 +164,8 @@ module RailsAiContext
|
|
|
163
164
|
render_design_system(context, max_lines: 30)
|
|
164
165
|
end
|
|
165
166
|
|
|
166
|
-
def render_mcp_guide
|
|
167
|
-
|
|
168
|
-
"## MCP Tools (25) — MANDATORY, Use Before Read",
|
|
169
|
-
"",
|
|
170
|
-
"CRITICAL: This project has live MCP tools. You MUST use them for gathering context.",
|
|
171
|
-
"Read files ONLY when you are about to edit them. Never read reference files directly.",
|
|
172
|
-
"Start with `detail:\"summary\"`, then drill into specifics.",
|
|
173
|
-
"",
|
|
174
|
-
"### Mandatory Workflow",
|
|
175
|
-
"1. **Before exploring a feature**: `rails_analyze_feature(feature:\"...\")` — models + controllers (inherited filters) + routes (code-ready helpers) + services + jobs + views + tests + gaps",
|
|
176
|
-
"2. **Before writing migrations**: `rails_get_schema(table:\"...\")` — NOT reading db/schema.rb",
|
|
177
|
-
"3. **Before modifying a model**: `rails_get_model_details(model:\"...\")` — NOT reading the model file",
|
|
178
|
-
"4. **Before adding routes**: `rails_get_routes(controller:\"...\")` — Read only when you will edit",
|
|
179
|
-
"5. **Before creating views**: `rails_get_design_system` — match existing patterns",
|
|
180
|
-
"6. **After editing ANY file**: `rails_validate(files:[...])` — no exceptions",
|
|
181
|
-
"",
|
|
182
|
-
"### Do NOT Bypass",
|
|
183
|
-
"| Instead of... | Use this MCP tool |",
|
|
184
|
-
"|---------------|-------------------|",
|
|
185
|
-
"| Reading db/schema.rb | `rails_get_schema(table:\"x\")` — includes orphaned table warnings |",
|
|
186
|
-
"| Reading model files | `rails_get_model_details(model:\"X\")` |",
|
|
187
|
-
"| Reading routes.rb | `rails_get_routes(controller:\"x\")` |",
|
|
188
|
-
"| Grep for code | `rails_search_code(pattern:\"x\", match_type:\"trace\")` |",
|
|
189
|
-
"| Reading test files | `rails_get_test_info(model:\"X\")` |",
|
|
190
|
-
"| Reading controller | `rails_get_controllers(controller:\"X\", action:\"y\")` |",
|
|
191
|
-
"| ruby -c / erb / node | `rails_validate(files:[...])` |",
|
|
192
|
-
"",
|
|
193
|
-
"### All 25 Tools",
|
|
194
|
-
"- `rails_get_schema` | `rails_get_model_details` | `rails_get_routes` | `rails_get_controllers`",
|
|
195
|
-
"- `rails_get_view` | `rails_get_stimulus` | `rails_get_test_info` | `rails_analyze_feature`",
|
|
196
|
-
"- `rails_get_design_system` | `rails_get_edit_context` | `rails_validate` | `rails_search_code`",
|
|
197
|
-
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_security_scan`",
|
|
198
|
-
"- `rails_get_concern(name:\"X\", detail:\"full\")` — concern methods with source code | `rails_get_callbacks` | `rails_get_helper_methods` | `rails_get_service_pattern`",
|
|
199
|
-
"- `rails_get_job_pattern` | `rails_get_env` | `rails_get_partial_interface` | `rails_get_turbo_map`",
|
|
200
|
-
"- `rails_search_code(pattern:\"method\", match_type:\"trace\")` — **trace mode**: definition + class context + source + siblings + callers + test coverage in one call",
|
|
201
|
-
"- `rails_get_context(model:\"X\")` — composite cross-layer context in one call",
|
|
202
|
-
""
|
|
203
|
-
]
|
|
167
|
+
def render_mcp_guide
|
|
168
|
+
render_tools_guide
|
|
204
169
|
end
|
|
205
170
|
|
|
206
171
|
def render_conventions
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAiContext
|
|
4
|
+
module Serializers
|
|
5
|
+
# Shared helper for rendering the tool reference section in context files.
|
|
6
|
+
# Reads config.tool_mode to generate MCP syntax, CLI syntax, or both.
|
|
7
|
+
module ToolGuideHelper
|
|
8
|
+
# Returns the tool invocation example for a given tool call.
|
|
9
|
+
# MCP: rails_analyze_feature(feature:"cook")
|
|
10
|
+
# CLI: rails 'ai:tool[analyze_feature]' feature=cook
|
|
11
|
+
def tool_call(mcp_call, cli_call)
|
|
12
|
+
case tool_mode
|
|
13
|
+
when :cli
|
|
14
|
+
"→ `#{cli_call}`"
|
|
15
|
+
when :mcp
|
|
16
|
+
"→ MCP: `#{mcp_call}`\n→ CLI: `#{cli_call}`"
|
|
17
|
+
else
|
|
18
|
+
"→ `#{mcp_call}`"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def tool_mode
|
|
23
|
+
RailsAiContext.configuration.tool_mode
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def tools_header
|
|
27
|
+
"## Tools (25) — MANDATORY, Use Before Read"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def tools_intro
|
|
31
|
+
case tool_mode
|
|
32
|
+
when :cli
|
|
33
|
+
[
|
|
34
|
+
"This project has 25 introspection tools. **MANDATORY — use these instead of reading files.**",
|
|
35
|
+
"They return only relevant, structured data and save tokens. Read files ONLY when you are about to Edit them.",
|
|
36
|
+
""
|
|
37
|
+
]
|
|
38
|
+
else
|
|
39
|
+
[
|
|
40
|
+
"This project has 25 MCP tools via `rails ai:serve` (configured in `.mcp.json`).",
|
|
41
|
+
"**MANDATORY — use these instead of reading files.** They return structured data and save tokens.",
|
|
42
|
+
"Read files ONLY when you are about to Edit them.",
|
|
43
|
+
"If MCP tools are not connected, use CLI fallback: `#{cli_cmd("TOOL_NAME", "param=value")}`",
|
|
44
|
+
""
|
|
45
|
+
]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def tools_task_section # rubocop:disable Metrics/MethodLength
|
|
50
|
+
[
|
|
51
|
+
"### What Are You Trying to Do?",
|
|
52
|
+
"",
|
|
53
|
+
"**Understand a feature or area:**",
|
|
54
|
+
tool_call("rails_analyze_feature(feature:\"cook\")", cli_cmd("analyze_feature", "feature=cook")),
|
|
55
|
+
tool_call("rails_get_context(model:\"Cook\")", cli_cmd("context", "model=Cook")),
|
|
56
|
+
"",
|
|
57
|
+
"**Understand a method (who calls it, what it calls):**",
|
|
58
|
+
tool_call("rails_search_code(pattern:\"can_cook?\", match_type:\"trace\")", cli_cmd("search_code", "pattern=\"can_cook?\" match_type=trace")),
|
|
59
|
+
"",
|
|
60
|
+
"**Add a field or modify a model:**",
|
|
61
|
+
tool_call("rails_get_schema(table:\"cooks\")", cli_cmd("schema", "table=cooks")),
|
|
62
|
+
tool_call("rails_get_model_details(model:\"Cook\")", cli_cmd("model_details", "model=Cook")),
|
|
63
|
+
"",
|
|
64
|
+
"**Fix a controller bug:**",
|
|
65
|
+
tool_call("rails_get_controllers(controller:\"CooksController\", action:\"create\")", cli_cmd("controllers", "controller=CooksController action=create")),
|
|
66
|
+
"",
|
|
67
|
+
"**Build or modify a view:**",
|
|
68
|
+
tool_call("rails_get_design_system(detail:\"standard\")", cli_cmd("design_system", "detail=standard")),
|
|
69
|
+
tool_call("rails_get_view(controller:\"cooks\")", cli_cmd("view", "controller=cooks")),
|
|
70
|
+
tool_call("rails_get_partial_interface(partial:\"shared/status_badge\")", cli_cmd("partial_interface", "partial=shared/status_badge")),
|
|
71
|
+
"",
|
|
72
|
+
"**Write tests:**",
|
|
73
|
+
tool_call("rails_get_test_info(detail:\"standard\")", cli_cmd("test_info", "detail=standard")),
|
|
74
|
+
tool_call("rails_get_test_info(model:\"Cook\")", cli_cmd("test_info", "model=Cook")),
|
|
75
|
+
"",
|
|
76
|
+
"**Find code:**",
|
|
77
|
+
tool_call("rails_search_code(pattern:\"has_many\")", cli_cmd("search_code", "pattern=\"has_many\"")),
|
|
78
|
+
tool_call("rails_search_code(pattern:\"create\", match_type:\"definition\")", cli_cmd("search_code", "pattern=create match_type=definition")),
|
|
79
|
+
"",
|
|
80
|
+
"**After editing (EVERY time):**",
|
|
81
|
+
tool_call("rails_validate(files:[\"app/models/cook.rb\"], level:\"rails\")", cli_cmd("validate", "files=app/models/cook.rb level=rails")),
|
|
82
|
+
""
|
|
83
|
+
]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def tools_rules_section
|
|
87
|
+
case tool_mode
|
|
88
|
+
when :cli
|
|
89
|
+
[
|
|
90
|
+
"### Rules",
|
|
91
|
+
"",
|
|
92
|
+
"1. NEVER read db/schema.rb, config/routes.rb, model files, or test files for reference — use the CLI tools above",
|
|
93
|
+
"2. NEVER use Grep or search agents for code search — use `#{cli_cmd("search_code")}`",
|
|
94
|
+
"3. NEVER run `ruby -c`, `erb`, or `node -c` — use `#{cli_cmd("validate")}`",
|
|
95
|
+
"4. Read files ONLY when you are about to Edit them",
|
|
96
|
+
"5. Start with `detail=summary` to orient, then drill into specifics",
|
|
97
|
+
""
|
|
98
|
+
]
|
|
99
|
+
else
|
|
100
|
+
[
|
|
101
|
+
"### Rules",
|
|
102
|
+
"",
|
|
103
|
+
"1. NEVER read db/schema.rb, config/routes.rb, model files, or test files for reference — use the MCP tools above",
|
|
104
|
+
"2. NEVER use Grep or search agents for code search — use `rails_search_code`",
|
|
105
|
+
"3. NEVER run `ruby -c`, `erb`, or `node -c` — use `rails_validate`",
|
|
106
|
+
"4. Read files ONLY when you are about to Edit them",
|
|
107
|
+
"5. Start with `detail:\"summary\"` to orient, then drill into specifics",
|
|
108
|
+
"6. If MCP tools are not connected, use CLI: `#{cli_cmd("TOOL_NAME", "param=value")}`",
|
|
109
|
+
""
|
|
110
|
+
]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def tools_table # rubocop:disable Metrics/MethodLength
|
|
115
|
+
lines = [ "### All 25 Tools", "" ]
|
|
116
|
+
|
|
117
|
+
if tool_mode == :cli
|
|
118
|
+
lines.concat(tools_table_cli)
|
|
119
|
+
else
|
|
120
|
+
lines.concat(tools_table_mcp_and_cli)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
lines
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def tools_table_mcp_and_cli # rubocop:disable Metrics/MethodLength
|
|
127
|
+
[
|
|
128
|
+
"| MCP | CLI | What it does |",
|
|
129
|
+
"|-----|-----|-------------|",
|
|
130
|
+
"| `rails_analyze_feature(feature:\"X\")` | `#{cli_cmd("analyze_feature", "feature=X")}` | Full-stack: models + controllers + routes + services + jobs + views + tests |",
|
|
131
|
+
"| `rails_get_context(model:\"X\")` | `#{cli_cmd("context", "model=X")}` | Composite: schema + model + controller + routes + views in one call |",
|
|
132
|
+
"| `rails_search_code(pattern:\"X\", match_type:\"trace\")` | `#{cli_cmd("search_code", "pattern=X match_type=trace")}` | Trace: definition + source + siblings + callers + test coverage |",
|
|
133
|
+
"| `rails_get_controllers(controller:\"X\", action:\"Y\")` | `#{cli_cmd("controllers", "controller=X action=Y")}` | Action source + inherited filters + render map + private methods |",
|
|
134
|
+
"| `rails_validate(files:[...], level:\"rails\")` | `#{cli_cmd("validate", "files=a.rb,b.rb level=rails")}` | Syntax + semantic validation + Brakeman security |",
|
|
135
|
+
"| `rails_get_schema(table:\"X\")` | `#{cli_cmd("schema", "table=X")}` | Columns with [indexed]/[unique]/[encrypted]/[default] hints |",
|
|
136
|
+
"| `rails_get_model_details(model:\"X\")` | `#{cli_cmd("model_details", "model=X")}` | Associations, validations, scopes, enums, macros, delegations |",
|
|
137
|
+
"| `rails_get_routes(controller:\"X\")` | `#{cli_cmd("routes", "controller=X")}` | Routes with code-ready helpers and controller filters inline |",
|
|
138
|
+
"| `rails_get_view(controller:\"X\")` | `#{cli_cmd("view", "controller=X")}` | Templates with ivars, Turbo wiring, Stimulus refs, partial locals |",
|
|
139
|
+
"| `rails_get_design_system` | `#{cli_cmd("design_system")}` | Canonical HTML/ERB copy-paste patterns for buttons, inputs, cards |",
|
|
140
|
+
"| `rails_get_stimulus(controller:\"X\")` | `#{cli_cmd("stimulus", "controller=X")}` | Targets, values, actions + HTML data-attributes + view lookup |",
|
|
141
|
+
"| `rails_get_test_info(model:\"X\")` | `#{cli_cmd("test_info", "model=X")}` | Tests + fixture contents + test template |",
|
|
142
|
+
"| `rails_get_concern(name:\"X\", detail:\"full\")` | `#{cli_cmd("concern", "name=X detail=full")}` | Concern methods with source + which models include it |",
|
|
143
|
+
"| `rails_get_callbacks(model:\"X\")` | `#{cli_cmd("callbacks", "model=X")}` | Callbacks in Rails execution order with source |",
|
|
144
|
+
"| `rails_get_edit_context(file:\"X\", near:\"Y\")` | `#{cli_cmd("edit_context", "file=X near=Y")}` | Code around a match with class/method context |",
|
|
145
|
+
"| `rails_search_code(pattern:\"X\")` | `#{cli_cmd("search_code", "pattern=X")}` | Regex search + `exclude_tests` + `group_by_file` + pagination |",
|
|
146
|
+
"| `rails_get_service_pattern` | `#{cli_cmd("service_pattern")}` | Service objects: interface, dependencies, side effects, callers |",
|
|
147
|
+
"| `rails_get_job_pattern` | `#{cli_cmd("job_pattern")}` | Jobs: queue, retries, guard clauses, broadcasts, schedules |",
|
|
148
|
+
"| `rails_get_env` | `#{cli_cmd("env")}` | Environment variables + credentials keys (not values) |",
|
|
149
|
+
"| `rails_get_partial_interface(partial:\"X\")` | `#{cli_cmd("partial_interface", "partial=X")}` | Partial locals contract: what to pass + usage examples |",
|
|
150
|
+
"| `rails_get_turbo_map` | `#{cli_cmd("turbo_map")}` | Turbo Stream/Frame wiring + mismatch warnings |",
|
|
151
|
+
"| `rails_get_helper_methods` | `#{cli_cmd("helper_methods")}` | App + framework helpers with view cross-references |",
|
|
152
|
+
"| `rails_get_config` | `#{cli_cmd("config")}` | Database adapter, auth, assets, cache, queue, Action Cable |",
|
|
153
|
+
"| `rails_get_gems` | `#{cli_cmd("gems")}` | Notable gems with versions, categories, config file locations |",
|
|
154
|
+
"| `rails_get_conventions` | `#{cli_cmd("conventions")}` | App patterns: auth checks, flash messages, test patterns |",
|
|
155
|
+
"| `rails_security_scan` | `#{cli_cmd("security_scan")}` | Brakeman static analysis: SQL injection, XSS, mass assignment |"
|
|
156
|
+
]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def tools_table_cli # rubocop:disable Metrics/MethodLength
|
|
160
|
+
[
|
|
161
|
+
"| CLI | What it does |",
|
|
162
|
+
"|-----|-------------|",
|
|
163
|
+
"| `#{cli_cmd("analyze_feature", "feature=X")}` | Full-stack: models + controllers + routes + services + jobs + views + tests |",
|
|
164
|
+
"| `#{cli_cmd("context", "model=X")}` | Composite: schema + model + controller + routes + views in one call |",
|
|
165
|
+
"| `#{cli_cmd("search_code", "pattern=X match_type=trace")}` | Trace: definition + source + siblings + callers + test coverage |",
|
|
166
|
+
"| `#{cli_cmd("controllers", "controller=X action=Y")}` | Action source + inherited filters + render map + private methods |",
|
|
167
|
+
"| `#{cli_cmd("validate", "files=a.rb,b.rb level=rails")}` | Syntax + semantic validation + Brakeman security |",
|
|
168
|
+
"| `#{cli_cmd("schema", "table=X")}` | Columns with [indexed]/[unique]/[encrypted]/[default] hints |",
|
|
169
|
+
"| `#{cli_cmd("model_details", "model=X")}` | Associations, validations, scopes, enums, macros, delegations |",
|
|
170
|
+
"| `#{cli_cmd("routes", "controller=X")}` | Routes with code-ready helpers and controller filters inline |",
|
|
171
|
+
"| `#{cli_cmd("view", "controller=X")}` | Templates with ivars, Turbo wiring, Stimulus refs, partial locals |",
|
|
172
|
+
"| `#{cli_cmd("design_system")}` | Canonical HTML/ERB copy-paste patterns for buttons, inputs, cards |",
|
|
173
|
+
"| `#{cli_cmd("stimulus", "controller=X")}` | Targets, values, actions + HTML data-attributes + view lookup |",
|
|
174
|
+
"| `#{cli_cmd("test_info", "model=X")}` | Tests + fixture contents + test template |",
|
|
175
|
+
"| `#{cli_cmd("concern", "name=X detail=full")}` | Concern methods with source + which models include it |",
|
|
176
|
+
"| `#{cli_cmd("callbacks", "model=X")}` | Callbacks in Rails execution order with source |",
|
|
177
|
+
"| `#{cli_cmd("edit_context", "file=X near=Y")}` | Code around a match with class/method context |",
|
|
178
|
+
"| `#{cli_cmd("search_code", "pattern=X")}` | Regex search + `exclude_tests` + `group_by_file` + pagination |",
|
|
179
|
+
"| `#{cli_cmd("service_pattern")}` | Service objects: interface, dependencies, side effects, callers |",
|
|
180
|
+
"| `#{cli_cmd("job_pattern")}` | Jobs: queue, retries, guard clauses, broadcasts, schedules |",
|
|
181
|
+
"| `#{cli_cmd("env")}` | Environment variables + credentials keys (not values) |",
|
|
182
|
+
"| `#{cli_cmd("partial_interface", "partial=X")}` | Partial locals contract: what to pass + usage examples |",
|
|
183
|
+
"| `#{cli_cmd("turbo_map")}` | Turbo Stream/Frame wiring + mismatch warnings |",
|
|
184
|
+
"| `#{cli_cmd("helper_methods")}` | App + framework helpers with view cross-references |",
|
|
185
|
+
"| `#{cli_cmd("config")}` | Database adapter, auth, assets, cache, queue, Action Cable |",
|
|
186
|
+
"| `#{cli_cmd("gems")}` | Notable gems with versions, categories, config file locations |",
|
|
187
|
+
"| `#{cli_cmd("conventions")}` | App patterns: auth checks, flash messages, test patterns |",
|
|
188
|
+
"| `#{cli_cmd("security_scan")}` | Brakeman static analysis: SQL injection, XSS, mass assignment |"
|
|
189
|
+
]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Full tool guide section — used by all serializers.
|
|
193
|
+
def render_tools_guide
|
|
194
|
+
lines = []
|
|
195
|
+
lines << tools_header
|
|
196
|
+
lines << ""
|
|
197
|
+
lines.concat(tools_intro)
|
|
198
|
+
lines.concat(tools_task_section)
|
|
199
|
+
lines.concat(tools_rules_section)
|
|
200
|
+
lines.concat(tools_table)
|
|
201
|
+
lines
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
# Generate zsh-safe CLI command: rails 'ai:tool[name]' params
|
|
207
|
+
def cli_cmd(tool_name, params = nil)
|
|
208
|
+
cmd = "rails 'ai:tool[#{tool_name}]'"
|
|
209
|
+
cmd += " #{params}" if params
|
|
210
|
+
cmd
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -6,7 +6,6 @@ ASSISTANT_TABLE = <<~TABLE unless defined?(ASSISTANT_TABLE)
|
|
|
6
6
|
Claude Code CLAUDE.md + .claude/rules/ rails ai:context:claude
|
|
7
7
|
OpenCode AGENTS.md rails ai:context:opencode
|
|
8
8
|
Cursor .cursor/rules/ rails ai:context:cursor
|
|
9
|
-
Windsurf .windsurfrules + .windsurf/rules/ rails ai:context:windsurf
|
|
10
9
|
GitHub Copilot .github/copilot-instructions.md rails ai:context:copilot
|
|
11
10
|
JSON (generic) .ai-context.json rails ai:context:json
|
|
12
11
|
TABLE
|
|
@@ -28,8 +27,7 @@ AI_TOOL_OPTIONS = {
|
|
|
28
27
|
"1" => { key: :claude, name: "Claude Code" },
|
|
29
28
|
"2" => { key: :cursor, name: "Cursor" },
|
|
30
29
|
"3" => { key: :copilot, name: "GitHub Copilot" },
|
|
31
|
-
"4" => { key: :
|
|
32
|
-
"5" => { key: :opencode, name: "OpenCode" }
|
|
30
|
+
"4" => { key: :opencode, name: "OpenCode" }
|
|
33
31
|
}.freeze unless defined?(AI_TOOL_OPTIONS)
|
|
34
32
|
|
|
35
33
|
def prompt_ai_tools
|
|
@@ -58,6 +56,67 @@ def prompt_ai_tools
|
|
|
58
56
|
selected
|
|
59
57
|
end unless defined?(prompt_ai_tools)
|
|
60
58
|
|
|
59
|
+
def prompt_tool_mode
|
|
60
|
+
puts ""
|
|
61
|
+
puts "Do you also want MCP server support?"
|
|
62
|
+
puts ""
|
|
63
|
+
puts " 1. Yes — MCP primary + CLI fallback (generates .mcp.json)"
|
|
64
|
+
puts " 2. No — CLI only (no server needed)"
|
|
65
|
+
puts ""
|
|
66
|
+
print "Enter number (default: 1): "
|
|
67
|
+
input = $stdin.gets&.strip || "1"
|
|
68
|
+
|
|
69
|
+
mode = input == "2" ? :cli : :mcp
|
|
70
|
+
label = mode == :mcp ? "MCP + CLI fallback" : "CLI only"
|
|
71
|
+
puts "Selected: #{label}"
|
|
72
|
+
mode
|
|
73
|
+
end unless defined?(prompt_tool_mode)
|
|
74
|
+
|
|
75
|
+
def save_tool_mode_to_initializer(mode)
|
|
76
|
+
init_path = Rails.root.join("config/initializers/rails_ai_context.rb")
|
|
77
|
+
return unless File.exist?(init_path)
|
|
78
|
+
|
|
79
|
+
content = File.read(init_path)
|
|
80
|
+
mode_line = " config.tool_mode = :#{mode}"
|
|
81
|
+
|
|
82
|
+
if content.include?("config.tool_mode")
|
|
83
|
+
content.sub!(/^.*config\.tool_mode.*$/, mode_line)
|
|
84
|
+
elsif content.include?("config.ai_tools")
|
|
85
|
+
# Insert after ai_tools line
|
|
86
|
+
content.sub!(/^(.*config\.ai_tools.*)$/, "\\1\n#{mode_line}")
|
|
87
|
+
elsif content.include?("RailsAiContext.configure")
|
|
88
|
+
content.sub!(/RailsAiContext\.configure do \|config\|\n/, "RailsAiContext.configure do |config|\n#{mode_line}\n")
|
|
89
|
+
else
|
|
90
|
+
return
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
File.write(init_path, content)
|
|
94
|
+
rescue
|
|
95
|
+
nil
|
|
96
|
+
end unless defined?(save_tool_mode_to_initializer)
|
|
97
|
+
|
|
98
|
+
def ensure_mcp_json
|
|
99
|
+
mcp_path = Rails.root.join(".mcp.json")
|
|
100
|
+
return if File.exist?(mcp_path)
|
|
101
|
+
|
|
102
|
+
server_entry = { "command" => "bundle", "args" => [ "exec", "rails", "ai:serve" ] }
|
|
103
|
+
content = JSON.pretty_generate({ mcpServers: { "rails-ai-context" => server_entry } }) + "\n"
|
|
104
|
+
File.write(mcp_path, content)
|
|
105
|
+
puts "✅ Created .mcp.json (MCP auto-discovery for Claude Code, Cursor, etc.)"
|
|
106
|
+
rescue => e
|
|
107
|
+
puts "⚠️ Could not create .mcp.json: #{e.message}"
|
|
108
|
+
end unless defined?(ensure_mcp_json)
|
|
109
|
+
|
|
110
|
+
def tool_mode_configured?
|
|
111
|
+
init_path = Rails.root.join("config/initializers/rails_ai_context.rb")
|
|
112
|
+
return false unless File.exist?(init_path)
|
|
113
|
+
content = File.read(init_path)
|
|
114
|
+
# Check for uncommented tool_mode line (not just a comment)
|
|
115
|
+
content.match?(/^\s*config\.tool_mode\s*=/)
|
|
116
|
+
rescue
|
|
117
|
+
false
|
|
118
|
+
end unless defined?(tool_mode_configured?)
|
|
119
|
+
|
|
61
120
|
def save_ai_tools_to_initializer(tools)
|
|
62
121
|
init_path = Rails.root.join("config/initializers/rails_ai_context.rb")
|
|
63
122
|
return unless File.exist?(init_path)
|
|
@@ -82,6 +141,48 @@ rescue
|
|
|
82
141
|
end unless defined?(save_ai_tools_to_initializer)
|
|
83
142
|
|
|
84
143
|
namespace :ai do
|
|
144
|
+
desc "Run an MCP tool from the CLI: rails 'ai:tool[schema]' table=users detail=full"
|
|
145
|
+
task :tool, [ :name ] => :environment do |_t, args|
|
|
146
|
+
require "rails_ai_context"
|
|
147
|
+
|
|
148
|
+
name = args[:name]
|
|
149
|
+
|
|
150
|
+
unless name
|
|
151
|
+
puts RailsAiContext::CLI::ToolRunner.tool_list
|
|
152
|
+
next
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Parse key=value pairs from ARGV (skip rake-internal args)
|
|
156
|
+
params = {}
|
|
157
|
+
ARGV.each do |arg|
|
|
158
|
+
next if arg.start_with?("-") || arg.include?("[") || arg == "ai:tool"
|
|
159
|
+
if arg.include?("=")
|
|
160
|
+
key, value = arg.split("=", 2)
|
|
161
|
+
params[key.to_sym] = value
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
json_mode = ENV["JSON"] == "1"
|
|
166
|
+
|
|
167
|
+
if params.delete(:help) || ARGV.include?("--help")
|
|
168
|
+
runner = RailsAiContext::CLI::ToolRunner.new(name, {})
|
|
169
|
+
puts RailsAiContext::CLI::ToolRunner.tool_help(runner.tool_class)
|
|
170
|
+
next
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
runner = RailsAiContext::CLI::ToolRunner.new(name, params, json_mode: json_mode)
|
|
174
|
+
puts runner.run
|
|
175
|
+
rescue RailsAiContext::CLI::ToolRunner::ToolNotFoundError => e
|
|
176
|
+
$stderr.puts "Error: #{e.message}"
|
|
177
|
+
exit 1
|
|
178
|
+
rescue RailsAiContext::CLI::ToolRunner::InvalidArgumentError => e
|
|
179
|
+
$stderr.puts "Error: #{e.message}"
|
|
180
|
+
exit 3
|
|
181
|
+
rescue => e
|
|
182
|
+
$stderr.puts "Error: #{e.message}"
|
|
183
|
+
exit 2
|
|
184
|
+
end
|
|
185
|
+
|
|
85
186
|
desc "Generate AI context files for configured AI tools (prompts on first run)"
|
|
86
187
|
task context: :environment do
|
|
87
188
|
require "rails_ai_context"
|
|
@@ -96,6 +197,16 @@ namespace :ai do
|
|
|
96
197
|
save_ai_tools_to_initializer(ai_tools) if ai_tools
|
|
97
198
|
end
|
|
98
199
|
|
|
200
|
+
# Prompt for tool_mode if not yet configured in initializer
|
|
201
|
+
unless tool_mode_configured?
|
|
202
|
+
tool_mode = prompt_tool_mode
|
|
203
|
+
RailsAiContext.configuration.tool_mode = tool_mode
|
|
204
|
+
save_tool_mode_to_initializer(tool_mode)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Auto-create .mcp.json when tool_mode is :mcp and it doesn't exist
|
|
208
|
+
ensure_mcp_json if RailsAiContext.configuration.tool_mode == :mcp
|
|
209
|
+
|
|
99
210
|
puts "🔍 Introspecting #{Rails.application.class.module_parent_name}..."
|
|
100
211
|
|
|
101
212
|
if ai_tools.nil? || ai_tools.empty?
|
|
@@ -115,7 +226,7 @@ namespace :ai do
|
|
|
115
226
|
puts "Change AI tools: config/initializers/rails_ai_context.rb (config.ai_tools)"
|
|
116
227
|
end
|
|
117
228
|
|
|
118
|
-
desc "Generate AI context in a specific format (claude, cursor,
|
|
229
|
+
desc "Generate AI context in a specific format (claude, cursor, copilot, json)"
|
|
119
230
|
task :context_for, [ :format ] => :environment do |_t, args|
|
|
120
231
|
require "rails_ai_context"
|
|
121
232
|
|
|
@@ -131,7 +242,7 @@ namespace :ai do
|
|
|
131
242
|
end
|
|
132
243
|
|
|
133
244
|
namespace :context do
|
|
134
|
-
{ claude: "CLAUDE.md", opencode: "AGENTS.md", cursor: ".cursor/rules/",
|
|
245
|
+
{ claude: "CLAUDE.md", opencode: "AGENTS.md", cursor: ".cursor/rules/",
|
|
135
246
|
copilot: ".github/copilot-instructions.md", json: ".ai-context.json" }.each do |fmt, file|
|
|
136
247
|
desc "Generate #{file} context file"
|
|
137
248
|
task fmt => :environment do
|
|
@@ -386,7 +386,14 @@ module RailsAiContext
|
|
|
386
386
|
# Extract method source from raw source string using indentation-based matching
|
|
387
387
|
private_class_method def self.extract_method_source(source, method_name)
|
|
388
388
|
source_lines = source.lines
|
|
389
|
-
|
|
389
|
+
escaped = Regexp.escape(method_name.to_s)
|
|
390
|
+
# Don't use \b after ? or ! — they ARE word boundaries
|
|
391
|
+
pattern = if method_name.to_s.end_with?("?") || method_name.to_s.end_with?("!")
|
|
392
|
+
/\A\s*def\s+#{escaped}/
|
|
393
|
+
else
|
|
394
|
+
/\A\s*def\s+#{escaped}\b/
|
|
395
|
+
end
|
|
396
|
+
start_idx = source_lines.index { |l| l.match?(pattern) }
|
|
390
397
|
return nil unless start_idx
|
|
391
398
|
|
|
392
399
|
def_indent = source_lines[start_idx][/\A\s*/].length
|
data/lib/rails_ai_context.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "zeitwerk"
|
|
4
4
|
|
|
5
5
|
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
|
6
|
-
loader.inflector.inflect("devops_introspector" => "DevOpsIntrospector")
|
|
6
|
+
loader.inflector.inflect("devops_introspector" => "DevOpsIntrospector", "cli" => "CLI")
|
|
7
7
|
loader.ignore("#{__dir__}/generators")
|
|
8
8
|
loader.ignore("#{__dir__}/rails-ai-context.rb")
|
|
9
9
|
loader.setup
|
data/server.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"url": "https://github.com/crisnahine/rails-ai-context",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "
|
|
10
|
+
"version": "3.0.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "mcpb",
|
|
14
|
-
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/
|
|
14
|
+
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v3.0.0/rails-ai-context-mcp.mcpb",
|
|
15
15
|
"fileSha256": "dd711a0ad6c4de943ae4da94eaf59a6dc9494b9d57f726e24649ed4e2f156990",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|