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
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAiContext
|
|
4
|
+
module CLI
|
|
5
|
+
# Runs MCP tools from the command line without requiring an MCP client.
|
|
6
|
+
# Reads tool schemas at runtime — no hardcoded parameter lists.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# runner = ToolRunner.new("schema", ["--table", "users", "--detail", "full"])
|
|
10
|
+
# puts runner.run
|
|
11
|
+
#
|
|
12
|
+
# runner = ToolRunner.new("schema", { table: "users", detail: "full" })
|
|
13
|
+
# puts runner.run
|
|
14
|
+
class ToolRunner
|
|
15
|
+
class ToolNotFoundError < StandardError; end
|
|
16
|
+
class InvalidArgumentError < StandardError; end
|
|
17
|
+
|
|
18
|
+
attr_reader :tool_class, :raw_args, :json_mode
|
|
19
|
+
|
|
20
|
+
def initialize(tool_name, raw_args, json_mode: false)
|
|
21
|
+
@tool_class = resolve_tool(tool_name)
|
|
22
|
+
@raw_args = raw_args
|
|
23
|
+
@json_mode = json_mode
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def run
|
|
27
|
+
kwargs = build_kwargs
|
|
28
|
+
schema = tool_schema
|
|
29
|
+
validate_kwargs!(kwargs, schema)
|
|
30
|
+
response = tool_class.call(**kwargs)
|
|
31
|
+
extract_output(response)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# List all available tools with short names and descriptions.
|
|
35
|
+
def self.tool_list
|
|
36
|
+
lines = [ "Available tools:", "" ]
|
|
37
|
+
available_tools.each do |tool|
|
|
38
|
+
short = short_name(tool.tool_name)
|
|
39
|
+
desc = tool.description_value.to_s[0..79]
|
|
40
|
+
lines << " #{short.ljust(24)} #{desc}"
|
|
41
|
+
end
|
|
42
|
+
lines << ""
|
|
43
|
+
lines << "Usage: rails 'ai:tool[NAME]' param=value"
|
|
44
|
+
lines << " rails-ai-context tool NAME --param value"
|
|
45
|
+
lines.join("\n")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Filtered tool list respecting skip_tools config.
|
|
49
|
+
def self.available_tools
|
|
50
|
+
skip = RailsAiContext.configuration.skip_tools
|
|
51
|
+
tools = Server::TOOLS
|
|
52
|
+
tools += RailsAiContext.configuration.custom_tools
|
|
53
|
+
return tools if skip.empty?
|
|
54
|
+
tools.reject { |t| skip.include?(t.tool_name) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Generate help for a specific tool from its input_schema.
|
|
58
|
+
def self.tool_help(tool_class)
|
|
59
|
+
schema = tool_class.input_schema_value&.schema || {}
|
|
60
|
+
properties = schema[:properties] || {}
|
|
61
|
+
required = schema[:required] || []
|
|
62
|
+
|
|
63
|
+
lines = [
|
|
64
|
+
"#{tool_class.tool_name} — #{tool_class.description_value}",
|
|
65
|
+
"",
|
|
66
|
+
"Usage:",
|
|
67
|
+
" rails 'ai:tool[#{short_name(tool_class.tool_name)}]' #{properties.keys.map { |k| "#{k}=VALUE" }.join(' ')}",
|
|
68
|
+
" rails-ai-context tool #{short_name(tool_class.tool_name)} #{properties.keys.map { |k| "--#{k.to_s.tr('_', '-')} VALUE" }.join(' ')}",
|
|
69
|
+
""
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
if properties.any?
|
|
73
|
+
lines << "Options:"
|
|
74
|
+
properties.each do |name, prop|
|
|
75
|
+
flag = "--#{name.to_s.tr('_', '-')}"
|
|
76
|
+
type_hint = prop[:type] || "string"
|
|
77
|
+
type_hint = "#{type_hint} (#{prop[:enum].join('/')})" if prop[:enum]
|
|
78
|
+
req = required.include?(name.to_s) ? " [required]" : ""
|
|
79
|
+
desc = prop[:description] || ""
|
|
80
|
+
lines << " #{flag.ljust(24)} #{desc} (#{type_hint})#{req}"
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
lines << " No parameters."
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
lines.join("\n")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Derive short name: rails_get_schema → schema, rails_analyze_feature → analyze_feature
|
|
90
|
+
def self.short_name(tool_name)
|
|
91
|
+
tool_name.sub(/\Arails_get_/, "").sub(/\Arails_/, "")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def tool_schema
|
|
97
|
+
tool_class.input_schema_value&.schema || {}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Resolve tool name: tries short → medium → full form.
|
|
101
|
+
# "schema" → "rails_get_schema", "search_code" → "rails_search_code"
|
|
102
|
+
def resolve_tool(name)
|
|
103
|
+
tools = self.class.available_tools
|
|
104
|
+
tool_names = tools.map(&:tool_name)
|
|
105
|
+
|
|
106
|
+
# Try exact match
|
|
107
|
+
found = tools.find { |t| t.tool_name == name }
|
|
108
|
+
return found if found
|
|
109
|
+
|
|
110
|
+
# Try rails_ prefix
|
|
111
|
+
found = tools.find { |t| t.tool_name == "rails_#{name}" }
|
|
112
|
+
return found if found
|
|
113
|
+
|
|
114
|
+
# Try rails_get_ prefix
|
|
115
|
+
found = tools.find { |t| t.tool_name == "rails_get_#{name}" }
|
|
116
|
+
return found if found
|
|
117
|
+
|
|
118
|
+
# Try case-insensitive short name match
|
|
119
|
+
found = tools.find { |t| self.class.short_name(t.tool_name) == name }
|
|
120
|
+
return found if found
|
|
121
|
+
|
|
122
|
+
# Fuzzy suggestion
|
|
123
|
+
short_names = tools.map { |t| self.class.short_name(t.tool_name) }
|
|
124
|
+
suggestion = Tools::BaseTool.find_closest_match(name, short_names)
|
|
125
|
+
msg = "Unknown tool '#{name}'."
|
|
126
|
+
msg += " Did you mean '#{suggestion}'?" if suggestion
|
|
127
|
+
msg += "\n\nRun with --list to see all available tools."
|
|
128
|
+
raise ToolNotFoundError, msg
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Parse raw_args into keyword arguments hash.
|
|
132
|
+
# Supports both hash input (rake) and array input (CLI).
|
|
133
|
+
def build_kwargs
|
|
134
|
+
kwargs = case raw_args
|
|
135
|
+
when Hash
|
|
136
|
+
raw_args.transform_keys(&:to_sym).except(:server_context)
|
|
137
|
+
when Array
|
|
138
|
+
return parse_cli_args(raw_args).except(:server_context)
|
|
139
|
+
else
|
|
140
|
+
{}
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
properties = (tool_schema[:properties] || {})
|
|
144
|
+
kwargs.each do |key, value|
|
|
145
|
+
prop = properties[key]
|
|
146
|
+
next unless prop
|
|
147
|
+
kwargs[key] = coerce_value(value, prop)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
kwargs
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Parse ["--table", "users", "--detail", "full", "--app-only"] into { table: "users", ... }
|
|
154
|
+
def parse_cli_args(args)
|
|
155
|
+
result = {}
|
|
156
|
+
i = 0
|
|
157
|
+
properties = (tool_schema[:properties] || {})
|
|
158
|
+
|
|
159
|
+
while i < args.size
|
|
160
|
+
arg = args[i]
|
|
161
|
+
|
|
162
|
+
if arg.start_with?("--no-")
|
|
163
|
+
key = arg.sub("--no-", "").tr("-", "_").to_sym
|
|
164
|
+
result[key] = false
|
|
165
|
+
i += 1
|
|
166
|
+
elsif arg.start_with?("--")
|
|
167
|
+
if arg.include?("=")
|
|
168
|
+
key, value = arg.sub("--", "").split("=", 2)
|
|
169
|
+
key = key.tr("-", "_").to_sym
|
|
170
|
+
result[key] = coerce_value(value, properties[key] || {})
|
|
171
|
+
else
|
|
172
|
+
key = arg.sub("--", "").tr("-", "_").to_sym
|
|
173
|
+
prop = properties[key] || {}
|
|
174
|
+
|
|
175
|
+
if prop[:type] == "boolean"
|
|
176
|
+
result[key] = true
|
|
177
|
+
i += 1
|
|
178
|
+
next
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
value = args[i + 1]
|
|
182
|
+
if value && !value.start_with?("--")
|
|
183
|
+
result[key] = coerce_value(value, prop)
|
|
184
|
+
i += 2
|
|
185
|
+
next
|
|
186
|
+
else
|
|
187
|
+
result[key] = true
|
|
188
|
+
i += 1
|
|
189
|
+
next
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
i += 1
|
|
193
|
+
elsif arg.include?("=")
|
|
194
|
+
# key=value style (rake)
|
|
195
|
+
key, value = arg.split("=", 2)
|
|
196
|
+
key = key.tr("-", "_").to_sym
|
|
197
|
+
result[key] = coerce_value(value, properties[key] || {})
|
|
198
|
+
i += 1
|
|
199
|
+
else
|
|
200
|
+
i += 1
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
result
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Coerce a string value to the type specified in the JSON Schema property.
|
|
208
|
+
def coerce_value(raw, property_schema)
|
|
209
|
+
case property_schema[:type]
|
|
210
|
+
when "integer"
|
|
211
|
+
raw.to_i
|
|
212
|
+
when "boolean"
|
|
213
|
+
%w[true 1 yes].include?(raw.to_s.downcase)
|
|
214
|
+
when "array"
|
|
215
|
+
raw.is_a?(Array) ? raw : raw.to_s.split(",").map(&:strip)
|
|
216
|
+
else
|
|
217
|
+
raw.to_s
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Validate kwargs against the tool's input_schema.
|
|
222
|
+
def validate_kwargs!(kwargs, schema)
|
|
223
|
+
properties = schema[:properties] || {}
|
|
224
|
+
required = (schema[:required] || []).map(&:to_s)
|
|
225
|
+
|
|
226
|
+
# Check required params
|
|
227
|
+
required.each do |param|
|
|
228
|
+
unless kwargs.key?(param.to_sym) && !kwargs[param.to_sym].nil? && kwargs[param.to_sym].to_s != ""
|
|
229
|
+
raise InvalidArgumentError,
|
|
230
|
+
"Missing required parameter '#{param}' for #{tool_class.tool_name}.\n" \
|
|
231
|
+
"Run: rails-ai-context tool #{self.class.short_name(tool_class.tool_name)} --help"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Check enum constraints
|
|
236
|
+
kwargs.each do |key, value|
|
|
237
|
+
prop = properties[key]
|
|
238
|
+
next unless prop&.dig(:enum)
|
|
239
|
+
unless prop[:enum].include?(value.to_s)
|
|
240
|
+
raise InvalidArgumentError,
|
|
241
|
+
"Invalid value '#{value}' for --#{key.to_s.tr('_', '-')}. " \
|
|
242
|
+
"Must be one of: #{prop[:enum].join(', ')}"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Extract text from MCP::Tool::Response.
|
|
248
|
+
def extract_output(response)
|
|
249
|
+
text = response.content.first&.dig(:text) || ""
|
|
250
|
+
if json_mode
|
|
251
|
+
require "json"
|
|
252
|
+
JSON.pretty_generate(tool: tool_class.tool_name, output: text)
|
|
253
|
+
else
|
|
254
|
+
text
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -56,7 +56,7 @@ module RailsAiContext
|
|
|
56
56
|
# Debounce interval in seconds for live reload file watching
|
|
57
57
|
attr_accessor :live_reload_debounce
|
|
58
58
|
|
|
59
|
-
# Whether to generate root-level context files (CLAUDE.md, AGENTS.md,
|
|
59
|
+
# Whether to generate root-level context files (CLAUDE.md, AGENTS.md, etc.)
|
|
60
60
|
# When false, only generates split rule files (.claude/rules/, .cursor/rules/, etc.)
|
|
61
61
|
attr_accessor :generate_root_files
|
|
62
62
|
|
|
@@ -76,9 +76,12 @@ module RailsAiContext
|
|
|
76
76
|
attr_accessor :skip_tools
|
|
77
77
|
|
|
78
78
|
# Which AI tools to generate context for (selected during install)
|
|
79
|
-
# nil = all formats, or %i[claude cursor copilot
|
|
79
|
+
# nil = all formats, or %i[claude cursor copilot opencode]
|
|
80
80
|
attr_accessor :ai_tools
|
|
81
81
|
|
|
82
|
+
# Tool invocation mode: :mcp (MCP primary + CLI fallback) or :cli (CLI only)
|
|
83
|
+
attr_accessor :tool_mode
|
|
84
|
+
|
|
82
85
|
# Filtering — customize what's hidden from AI output
|
|
83
86
|
attr_accessor :excluded_controllers # Controller classes hidden from listings (e.g. DeviseController)
|
|
84
87
|
attr_accessor :excluded_route_prefixes # Route controller prefixes hidden with app_only (e.g. action_mailbox/)
|
|
@@ -153,6 +156,7 @@ module RailsAiContext
|
|
|
153
156
|
@custom_tools = []
|
|
154
157
|
@skip_tools = []
|
|
155
158
|
@ai_tools = nil
|
|
159
|
+
@tool_mode = :mcp
|
|
156
160
|
@search_extensions = %w[rb js erb yml yaml json ts tsx vue svelte haml slim]
|
|
157
161
|
@concern_paths = %w[app/models/concerns app/controllers/concerns]
|
|
158
162
|
end
|
|
@@ -7,6 +7,7 @@ module RailsAiContext
|
|
|
7
7
|
class ClaudeRulesSerializer
|
|
8
8
|
include StackOverviewHelper
|
|
9
9
|
include DesignSystemHelper
|
|
10
|
+
include ToolGuideHelper
|
|
10
11
|
|
|
11
12
|
attr_reader :context
|
|
12
13
|
|
|
@@ -293,141 +294,8 @@ module RailsAiContext
|
|
|
293
294
|
lines.join("\n")
|
|
294
295
|
end
|
|
295
296
|
|
|
296
|
-
def render_mcp_tools_reference
|
|
297
|
-
|
|
298
|
-
"# Rails MCP Tools — MANDATORY, Use Before Read/Grep",
|
|
299
|
-
"",
|
|
300
|
-
"CRITICAL: This project has live MCP tools. You MUST use them for gathering context.",
|
|
301
|
-
"MCP tools return parsed, up-to-date, token-efficient data with line numbers.",
|
|
302
|
-
"Read files ONLY when you are about to Edit them. Never read reference files directly.",
|
|
303
|
-
"",
|
|
304
|
-
"## Mandatory Workflow — Follow This Order",
|
|
305
|
-
"",
|
|
306
|
-
"### 1. Gathering context (BEFORE writing any code)",
|
|
307
|
-
"- Exploring a feature area → `rails_analyze_feature(feature:\"...\")` — NOT Explore agents, NOT Grep",
|
|
308
|
-
"- Understanding a model → `rails_get_model_details(model:\"...\")` — NOT reading the model file",
|
|
309
|
-
"- Checking database columns → `rails_get_schema(table:\"...\")` — NOT reading db/schema.rb",
|
|
310
|
-
"- Checking routes → `rails_get_routes(controller:\"...\")` — NOT reading config/routes.rb",
|
|
311
|
-
"- Understanding a controller → `rails_get_controllers(controller:\"...\", action:\"...\")` — NOT reading the file",
|
|
312
|
-
"- Finding code patterns → `rails_search_code(pattern:\"...\")` — NOT using Grep tool",
|
|
313
|
-
"- UI patterns → `rails_get_design_system` — NOT reading view files for patterns",
|
|
314
|
-
"- Full cross-layer context → `rails_get_context(model:\"...\")` — replaces multiple Read calls",
|
|
315
|
-
"",
|
|
316
|
-
"### 2. Reading files (ONLY files you will edit)",
|
|
317
|
-
"- Read a file ONLY when you are about to Edit it (Read is required before Edit)",
|
|
318
|
-
"- For everything else — schema, routes, models, tests, patterns — use MCP tools above",
|
|
319
|
-
"",
|
|
320
|
-
"### 3. After editing (EVERY time, no exceptions)",
|
|
321
|
-
"- `rails_validate(files:[\"path/to/file.rb\", \"path/to/view.erb\"])` — one call checks all",
|
|
322
|
-
"- Do NOT run `ruby -c`, `erb` checks, or `node -c` separately — use rails_validate instead",
|
|
323
|
-
"- Do NOT re-read files to verify edits. Trust your Edit and validate syntax only.",
|
|
324
|
-
"",
|
|
325
|
-
"## Do NOT Bypass — Anti-Patterns",
|
|
326
|
-
"| Instead of... | Use this MCP tool |",
|
|
327
|
-
"|---------------|-------------------|",
|
|
328
|
-
"| Reading db/schema.rb | `rails_get_schema(table:\"name\")` |",
|
|
329
|
-
"| Reading config/routes.rb for reference | `rails_get_routes(controller:\"name\")` |",
|
|
330
|
-
"| Reading model files for associations | `rails_get_model_details(model:\"Name\")` |",
|
|
331
|
-
"| Grep/Explore agent for code search | `rails_search_code(pattern:\"regex\")` |",
|
|
332
|
-
"| Reading test files for patterns | `rails_get_test_info(model:\"Name\")` |",
|
|
333
|
-
"| Reading controller files for context | `rails_get_controllers(controller:\"Name\")` |",
|
|
334
|
-
"| Reading JS files for Stimulus API | `rails_get_stimulus(controller:\"name\")` |",
|
|
335
|
-
"| Multiple reads to understand a feature | `rails_analyze_feature(feature:\"keyword\")` |",
|
|
336
|
-
"| ruby -c / erb / node -c after edits | `rails_validate(files:[...])` |",
|
|
337
|
-
"",
|
|
338
|
-
"## Reference-only files — NEVER read these, use MCP",
|
|
339
|
-
"- db/schema.rb → `rails_get_schema` (column types in rails-schema.md rules for quick checks)",
|
|
340
|
-
"- config/routes.rb → `rails_get_routes` (Read ONLY when adding routes)",
|
|
341
|
-
"- Model files → `rails_get_model_details` (Read ONLY when editing business logic)",
|
|
342
|
-
"- app/javascript/controllers/index.js → Stimulus auto-registers. Never read this.",
|
|
343
|
-
"- Test files → `rails_get_test_info`",
|
|
344
|
-
"",
|
|
345
|
-
"## Tools (25)",
|
|
346
|
-
"",
|
|
347
|
-
"**rails_get_schema** — database tables, columns, indexes, foreign keys, orphaned table detection",
|
|
348
|
-
"- `rails_get_schema(detail:\"summary\")` — all tables with column counts + orphaned tables (tables with no ActiveRecord model)",
|
|
349
|
-
"- `rails_get_schema(table:\"users\")` — full detail for one table",
|
|
350
|
-
"",
|
|
351
|
-
"**rails_get_model_details** — associations, validations, scopes, enums, callbacks",
|
|
352
|
-
"- `rails_get_model_details(detail:\"summary\")` — list all model names",
|
|
353
|
-
"- `rails_get_model_details(model:\"User\")` — full detail for one model",
|
|
354
|
-
"",
|
|
355
|
-
"**rails_get_routes** — HTTP verbs, paths, controller actions",
|
|
356
|
-
"- `rails_get_routes(detail:\"summary\")` — route counts per controller",
|
|
357
|
-
"- `rails_get_routes(controller:\"users\")` — routes for one controller",
|
|
358
|
-
"",
|
|
359
|
-
"**rails_get_controllers** — actions, filters, strong params, action source code",
|
|
360
|
-
"- `rails_get_controllers(detail:\"summary\")` — names + action counts",
|
|
361
|
-
"- `rails_get_controllers(controller:\"CooksController\", action:\"index\")` — action source code + filters",
|
|
362
|
-
"",
|
|
363
|
-
"**rails_get_view** — view templates, partials, Stimulus references",
|
|
364
|
-
"- `rails_get_view(controller:\"cooks\")` — list all views for a controller",
|
|
365
|
-
"- `rails_get_view(path:\"cooks/index.html.erb\")` — full template content",
|
|
366
|
-
"",
|
|
367
|
-
"**rails_get_stimulus** — Stimulus controllers with targets, values, actions",
|
|
368
|
-
"- `rails_get_stimulus(detail:\"summary\")` — all controllers with counts",
|
|
369
|
-
"- `rails_get_stimulus(controller:\"filter-form\")` — full detail for one controller",
|
|
370
|
-
"",
|
|
371
|
-
"**rails_get_test_info** — test framework, fixtures, factories, helpers",
|
|
372
|
-
"- `rails_get_test_info(detail:\"full\")` — fixture names, factory names, helper setup",
|
|
373
|
-
"- `rails_get_test_info(model:\"Cook\")` — full test source for a model",
|
|
374
|
-
"- `rails_get_test_info(model:\"Cook\", detail:\"summary\")` — test names only (saves tokens)",
|
|
375
|
-
"- `rails_get_test_info(controller:\"Cooks\")` — existing controller tests",
|
|
376
|
-
"",
|
|
377
|
-
"**rails_get_edit_context** — surgical edit helper with line numbers",
|
|
378
|
-
"- `rails_get_edit_context(file:\"app/models/cook.rb\", near:\"scope\")` — returns code around match with line numbers",
|
|
379
|
-
"",
|
|
380
|
-
"**rails_validate** — syntax checker for edited files",
|
|
381
|
-
"- `rails_validate(files:[\"app/models/cook.rb\"])` — checks Ruby, ERB, JS syntax in one call",
|
|
382
|
-
"",
|
|
383
|
-
"**rails_analyze_feature** — combined schema + models + controllers + routes for a feature area",
|
|
384
|
-
"- `rails_analyze_feature(feature:\"authentication\")` — one call gets everything related to a feature, including inherited filters from parent controllers and code-ready route helpers",
|
|
385
|
-
"",
|
|
386
|
-
"**rails_get_design_system** — color palette, component patterns, canonical page examples",
|
|
387
|
-
"- `rails_get_design_system(detail:\"standard\")` — colors + components + real HTML examples + design rules",
|
|
388
|
-
"- `rails_get_design_system(detail:\"full\")` — + typography, responsive, dark mode, layout, spacing",
|
|
389
|
-
"",
|
|
390
|
-
"**rails_get_config** — cache store, session, timezone, middleware, initializers",
|
|
391
|
-
"**rails_get_gems** — notable gems categorized by function",
|
|
392
|
-
"**rails_get_conventions** — architecture patterns, directory structure",
|
|
393
|
-
"**rails_search_code** — regex search with trace mode for deep call-chain analysis",
|
|
394
|
-
"- `rails_search_code(pattern:\"regex\", file_type:\"rb\")` — basic regex search across files",
|
|
395
|
-
"- `rails_search_code(pattern:\"can_cook?\", match_type:\"trace\")` — trace mode: definition + class context + source + siblings + callers with route chain + test coverage",
|
|
396
|
-
"- `rails_search_code(pattern:\"cook\", exclude_tests: true)` — search app code only, skip test files",
|
|
397
|
-
"",
|
|
398
|
-
"**rails_security_scan** — Brakeman security analysis",
|
|
399
|
-
"- `rails_security_scan` — run security scan, returns warnings by confidence",
|
|
400
|
-
"",
|
|
401
|
-
"**rails_get_concern** — concern methods and includers",
|
|
402
|
-
"- `rails_get_concern(name:\"Searchable\")` — methods, included modules, includers",
|
|
403
|
-
"- `rails_get_concern(name:\"Searchable\", detail:\"full\")` — + method source code",
|
|
404
|
-
"",
|
|
405
|
-
"**rails_get_callbacks** — model callbacks in execution order",
|
|
406
|
-
"- `rails_get_callbacks(model:\"User\")` — before/after/around callbacks by type",
|
|
407
|
-
"",
|
|
408
|
-
"**rails_get_helper_methods** — app + framework helpers",
|
|
409
|
-
"- `rails_get_helper_methods` — all helper methods across app and framework",
|
|
410
|
-
"",
|
|
411
|
-
"**rails_get_service_pattern** — service object patterns and interfaces",
|
|
412
|
-
"- `rails_get_service_pattern` — service objects with call signatures and dependencies",
|
|
413
|
-
"",
|
|
414
|
-
"**rails_get_job_pattern** — background job patterns and schedules",
|
|
415
|
-
"- `rails_get_job_pattern` — job classes, queues, retry policies, schedules",
|
|
416
|
-
"",
|
|
417
|
-
"**rails_get_env** — environment variables and credentials keys",
|
|
418
|
-
"- `rails_get_env` — ENV vars referenced in code + credentials structure",
|
|
419
|
-
"",
|
|
420
|
-
"**rails_get_partial_interface** — partial locals contract",
|
|
421
|
-
"- `rails_get_partial_interface(path:\"shared/_form\")` — required/optional locals for a partial",
|
|
422
|
-
"",
|
|
423
|
-
"**rails_get_turbo_map** — Turbo Streams/Frames wiring",
|
|
424
|
-
"- `rails_get_turbo_map` — Turbo Frame IDs, Stream channels, broadcast sources",
|
|
425
|
-
"",
|
|
426
|
-
"**rails_get_context** — composite cross-layer context",
|
|
427
|
-
"- `rails_get_context(model:\"User\")` — schema + model + controllers + views in one call"
|
|
428
|
-
]
|
|
429
|
-
|
|
430
|
-
lines.join("\n")
|
|
297
|
+
def render_mcp_tools_reference
|
|
298
|
+
render_tools_guide.join("\n")
|
|
431
299
|
end
|
|
432
300
|
end
|
|
433
301
|
end
|
|
@@ -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
|
|
|
@@ -196,45 +197,8 @@ module RailsAiContext
|
|
|
196
197
|
render_design_system(context, max_lines: 30)
|
|
197
198
|
end
|
|
198
199
|
|
|
199
|
-
def render_mcp_guide
|
|
200
|
-
|
|
201
|
-
"## MCP Tools (25) — ALWAYS Use These First",
|
|
202
|
-
"",
|
|
203
|
-
"This project has live MCP tools. You MUST use them instead of reading reference files.",
|
|
204
|
-
"Start with `detail:\"summary\"`, then drill into specifics.",
|
|
205
|
-
"",
|
|
206
|
-
"### Mandatory Workflow",
|
|
207
|
-
"1. **Before exploring a feature**: `rails_analyze_feature(feature:\"...\")` — NOT Explore agents or Grep",
|
|
208
|
-
"2. **Before writing migrations**: `rails_get_schema(table:\"...\")` — NOT reading db/schema.rb",
|
|
209
|
-
"3. **Before modifying a model**: `rails_get_model_details(model:\"...\")` — NOT reading the model file",
|
|
210
|
-
"4. **Before adding routes**: `rails_get_routes(controller:\"...\")` — Read routes.rb only when you will edit it",
|
|
211
|
-
"5. **Before creating views**: `rails_get_design_system` — match existing patterns",
|
|
212
|
-
"6. **After editing ANY file**: `rails_validate(files:[...])` — no exceptions, no ruby -c / erb / node -c",
|
|
213
|
-
"",
|
|
214
|
-
"### Quick Reference",
|
|
215
|
-
"| Need | Use this MCP tool | Do NOT use |",
|
|
216
|
-
"|------|-------------------|------------|",
|
|
217
|
-
"| Column types | `rails_get_schema(table:\"x\")` — includes orphaned table warnings | Read db/schema.rb |",
|
|
218
|
-
"| Model associations | `rails_get_model_details(model:\"X\")` | Read app/models/x.rb |",
|
|
219
|
-
"| Route paths | `rails_get_routes(controller:\"x\")` | Read config/routes.rb |",
|
|
220
|
-
"| Feature overview | `rails_analyze_feature(feature:\"x\")` — models + controllers (inherited filters) + routes (code-ready helpers) + services + jobs + views + tests + gaps | Explore agent / Grep |",
|
|
221
|
-
"| Find code | `rails_search_code(pattern:\"x\", match_type:\"trace\")` | Grep tool |",
|
|
222
|
-
"| Validate edits | `rails_validate(files:[...])` | ruby -c / erb / node |",
|
|
223
|
-
"| Controller logic | `rails_get_controllers(controller:\"X\", action:\"y\")` | Read controller file |",
|
|
224
|
-
"| UI patterns | `rails_get_design_system` | Read view files |",
|
|
225
|
-
"| Stimulus API | `rails_get_stimulus(controller:\"x\")` | Read JS files |",
|
|
226
|
-
"| Test patterns | `rails_get_test_info(model:\"X\")` | Read test files |",
|
|
227
|
-
"| Full context | `rails_get_context(model:\"X\")` | Multiple Read calls |",
|
|
228
|
-
"",
|
|
229
|
-
"### More Tools",
|
|
230
|
-
"- `rails_get_view(controller:\"x\")` | `rails_get_concern(name:\"X\", detail:\"full\")` — concern methods with source code | `rails_get_callbacks(model:\"X\")`",
|
|
231
|
-
"- `rails_get_helper_methods` | `rails_get_service_pattern` | `rails_get_job_pattern`",
|
|
232
|
-
"- `rails_get_env` | `rails_get_partial_interface(path:\"shared/_form\")` | `rails_get_turbo_map`",
|
|
233
|
-
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_security_scan`",
|
|
234
|
-
"- `rails_search_code(pattern:\"method\", match_type:\"trace\")` — **trace mode**: definition + class context + source + siblings + callers + test coverage in one call",
|
|
235
|
-
"- `rails_get_edit_context(file:\"path\", near:\"keyword\")` — surgical edit helper with line numbers",
|
|
236
|
-
""
|
|
237
|
-
]
|
|
200
|
+
def render_mcp_guide
|
|
201
|
+
render_tools_guide
|
|
238
202
|
end
|
|
239
203
|
|
|
240
204
|
def render_conventions
|
|
@@ -267,18 +231,8 @@ module RailsAiContext
|
|
|
267
231
|
"- Follow existing patterns and conventions",
|
|
268
232
|
"- Match existing code style",
|
|
269
233
|
"- Run tests after changes",
|
|
270
|
-
"- After editing, ALWAYS use `rails_validate(files:[...])` — do NOT use separate ruby -c / erb / node -c calls",
|
|
271
234
|
"- Do NOT re-read files to verify edits — trust your Edit, validate syntax only",
|
|
272
235
|
"- Stimulus controllers auto-register — no manual import in controllers/index.js needed",
|
|
273
|
-
"",
|
|
274
|
-
"## MCP-First Rule — Do NOT Bypass",
|
|
275
|
-
"- Do NOT read db/schema.rb — use `rails_get_schema`",
|
|
276
|
-
"- Do NOT read config/routes.rb for reference — use `rails_get_routes`",
|
|
277
|
-
"- Do NOT read model files for associations/scopes — use `rails_get_model_details`",
|
|
278
|
-
"- Do NOT use Grep to search code — use `rails_search_code`",
|
|
279
|
-
"- Do NOT launch Explore agents for context — use `rails_analyze_feature`",
|
|
280
|
-
"- Do NOT read test files for patterns — use `rails_get_test_info`",
|
|
281
|
-
"- Read files ONLY when you are about to Edit them",
|
|
282
236
|
""
|
|
283
237
|
]
|
|
284
238
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RailsAiContext
|
|
4
4
|
module Serializers
|
|
5
5
|
# Orchestrates writing context files to disk in various formats.
|
|
6
|
-
# Supports: CLAUDE.md, AGENTS.md, .
|
|
6
|
+
# Supports: CLAUDE.md, AGENTS.md, .github/copilot-instructions.md, JSON
|
|
7
7
|
# Also generates split rule files for AI tools that support them.
|
|
8
8
|
#
|
|
9
9
|
# Root files (CLAUDE.md, etc.) are wrapped in section markers so user content
|
|
@@ -15,7 +15,6 @@ module RailsAiContext
|
|
|
15
15
|
FORMAT_MAP = {
|
|
16
16
|
claude: "CLAUDE.md",
|
|
17
17
|
opencode: "AGENTS.md",
|
|
18
|
-
windsurf: ".windsurfrules",
|
|
19
18
|
copilot: ".github/copilot-instructions.md",
|
|
20
19
|
json: ".ai-context.json"
|
|
21
20
|
}.freeze
|
|
@@ -78,7 +77,6 @@ module RailsAiContext
|
|
|
78
77
|
when :json then JsonSerializer.new(context).call
|
|
79
78
|
when :claude then ClaudeSerializer.new(context).call
|
|
80
79
|
when :opencode then OpencodeSerializer.new(context).call
|
|
81
|
-
when :windsurf then WindsurfSerializer.new(context).call
|
|
82
80
|
when :copilot then CopilotSerializer.new(context).call
|
|
83
81
|
else MarkdownSerializer.new(context).call
|
|
84
82
|
end
|
|
@@ -136,12 +134,6 @@ module RailsAiContext
|
|
|
136
134
|
skipped.concat(result[:skipped])
|
|
137
135
|
end
|
|
138
136
|
|
|
139
|
-
if formats.include?(:windsurf)
|
|
140
|
-
result = WindsurfRulesSerializer.new(context).call(output_dir)
|
|
141
|
-
written.concat(result[:written])
|
|
142
|
-
skipped.concat(result[:skipped])
|
|
143
|
-
end
|
|
144
|
-
|
|
145
137
|
if formats.include?(:opencode)
|
|
146
138
|
result = OpencodeRulesSerializer.new(context).call(output_dir)
|
|
147
139
|
written.concat(result[:written])
|
|
@@ -7,6 +7,7 @@ module RailsAiContext
|
|
|
7
7
|
class CopilotInstructionsSerializer
|
|
8
8
|
include StackOverviewHelper
|
|
9
9
|
include DesignSystemHelper
|
|
10
|
+
include ToolGuideHelper
|
|
10
11
|
|
|
11
12
|
attr_reader :context
|
|
12
13
|
|
|
@@ -213,52 +214,17 @@ module RailsAiContext
|
|
|
213
214
|
lines.join("\n")
|
|
214
215
|
end
|
|
215
216
|
|
|
216
|
-
def render_mcp_tools_instructions
|
|
217
|
+
def render_mcp_tools_instructions
|
|
217
218
|
lines = [
|
|
218
219
|
"---",
|
|
219
220
|
"applyTo: \"**/*\"",
|
|
221
|
+
"excludeAgent: \"code-review\"",
|
|
220
222
|
"---",
|
|
221
|
-
""
|
|
222
|
-
"# Rails MCP Tools (25) — MANDATORY, Use Before Read",
|
|
223
|
-
"",
|
|
224
|
-
"CRITICAL: This project has live MCP tools. You MUST use them for gathering context.",
|
|
225
|
-
"Read files ONLY when you are about to edit them. Never read reference files directly.",
|
|
226
|
-
"Start with `detail:\"summary\"`, then drill into specifics.",
|
|
227
|
-
"",
|
|
228
|
-
"## Mandatory Workflow",
|
|
229
|
-
"1. Gathering context → use MCP tools (NOT file reads, NOT grep)",
|
|
230
|
-
"2. Reading files → ONLY files you will edit",
|
|
231
|
-
"3. After editing → `rails_validate(files:[...])` every time, no exceptions",
|
|
232
|
-
"",
|
|
233
|
-
"## Do NOT Bypass",
|
|
234
|
-
"| Instead of... | Use this MCP tool |",
|
|
235
|
-
"|---------------|-------------------|",
|
|
236
|
-
"| Reading db/schema.rb | `rails_get_schema(table:\"name\")` |",
|
|
237
|
-
"| Reading config/routes.rb | `rails_get_routes(controller:\"name\")` |",
|
|
238
|
-
"| Reading model files for context | `rails_get_model_details(model:\"Name\")` |",
|
|
239
|
-
"| Grep for code patterns | `rails_search_code(pattern:\"regex\")` |",
|
|
240
|
-
"| Reading test files | `rails_get_test_info(model:\"Name\")` |",
|
|
241
|
-
"| Reading controller for context | `rails_get_controllers(controller:\"Name\", action:\"x\")` |",
|
|
242
|
-
"| Reading JS for Stimulus API | `rails_get_stimulus(controller:\"name\")` |",
|
|
243
|
-
"| Multiple reads for a feature | `rails_analyze_feature(feature:\"keyword\")` |",
|
|
244
|
-
"| ruby -c / erb / node -c | `rails_validate(files:[...])` |",
|
|
245
|
-
"",
|
|
246
|
-
"## All 25 Tools",
|
|
247
|
-
"- `rails_get_schema` | `rails_get_model_details` | `rails_get_routes` | `rails_get_controllers`",
|
|
248
|
-
"- `rails_get_view` | `rails_get_stimulus` | `rails_get_test_info` | `rails_analyze_feature`",
|
|
249
|
-
"- `rails_get_design_system` | `rails_get_edit_context` | `rails_validate` | `rails_search_code`",
|
|
250
|
-
"- `rails_get_config` | `rails_get_gems` | `rails_get_conventions` | `rails_security_scan`",
|
|
251
|
-
"- `rails_get_concern` | `rails_get_callbacks` | `rails_get_helper_methods` | `rails_get_service_pattern`",
|
|
252
|
-
"- `rails_get_job_pattern` | `rails_get_env` | `rails_get_partial_interface` | `rails_get_turbo_map`",
|
|
253
|
-
"- `rails_get_context(model:\"X\")` — composite cross-layer context in one call",
|
|
254
|
-
"",
|
|
255
|
-
"## Power Features",
|
|
256
|
-
"- `rails_search_code(pattern:\"method\", match_type:\"trace\")` — trace: definition + source + siblings + callers + tests",
|
|
257
|
-
"- `rails_get_concern(name:\"X\", detail:\"full\")` — concern methods with source code",
|
|
258
|
-
"- `rails_analyze_feature` — full-stack with inherited filters, route helpers, test gaps",
|
|
259
|
-
"- `rails_get_schema` — columns, indexes, defaults, encrypted hints, orphaned table warnings"
|
|
223
|
+
""
|
|
260
224
|
]
|
|
261
225
|
|
|
226
|
+
lines.concat(render_tools_guide)
|
|
227
|
+
|
|
262
228
|
lines.join("\n")
|
|
263
229
|
end
|
|
264
230
|
end
|