rails-ai-context 0.10.2 → 0.11.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/CLAUDE.md +1 -1
- data/README.md +1 -1
- data/demo_script.sh +1 -1
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +107 -4
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +51 -4
- data/lib/rails_ai_context/serializers/claude_serializer.rb +17 -0
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +25 -0
- data/lib/rails_ai_context/serializers/copilot_serializer.rb +13 -0
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +28 -0
- data/lib/rails_ai_context/serializers/opencode_serializer.rb +17 -0
- data/lib/rails_ai_context/serializers/rules_serializer.rb +13 -0
- data/lib/rails_ai_context/serializers/windsurf_rules_serializer.rb +14 -0
- data/lib/rails_ai_context/serializers/windsurf_serializer.rb +11 -0
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +3 -3
- data/lib/rails_ai_context/tools/get_view.rb +19 -2
- data/lib/rails_ai_context/version.rb +1 -1
- data/server.json +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bdd226d739429a34c2ea8cedc8faf12e854d0220de60f5ffb08fe8fed5459f71
|
|
4
|
+
data.tar.gz: 7b609ca0007e97206b168cbda0937fb9fb581888491464619ba727a8a1b2d945
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b54b6354d0236f8f46f813593a5472fcca35dd4ceb1aefc4631479829c541b53d8a34509602b2f5903ff1af4b34b3d43d758e41f2c8e84cb5498b3212011541a
|
|
7
|
+
data.tar.gz: 0ddd8d1b1d413c3313a3189168c790dbbe776de2d573be006163867732287e939c0c8ea4490b39d1b86239f6940cfd923fcfab27ce24f6d9ba9bc3cda0c750fb
|
data/CLAUDE.md
CHANGED
data/README.md
CHANGED
|
@@ -389,7 +389,7 @@ The gem parses `db/schema.rb` as text when no database is connected. Works in CI
|
|
|
389
389
|
```bash
|
|
390
390
|
git clone https://github.com/crisnahine/rails-ai-context.git
|
|
391
391
|
cd rails-ai-context && bundle install
|
|
392
|
-
bundle exec rspec #
|
|
392
|
+
bundle exec rspec # 468 examples
|
|
393
393
|
bundle exec rubocop # Lint
|
|
394
394
|
```
|
|
395
395
|
|
data/demo_script.sh
CHANGED
|
@@ -14,11 +14,13 @@ module RailsAiContext
|
|
|
14
14
|
|
|
15
15
|
def call
|
|
16
16
|
views_dir = File.join(app.root.to_s, "app", "views")
|
|
17
|
-
return { templates: {}, partials: {} } unless Dir.exist?(views_dir)
|
|
17
|
+
return { templates: {}, partials: {}, ui_patterns: {} } unless Dir.exist?(views_dir)
|
|
18
18
|
|
|
19
|
+
all_content = collect_all_view_content(views_dir)
|
|
19
20
|
{
|
|
20
21
|
templates: scan_templates(views_dir),
|
|
21
|
-
partials: scan_partials(views_dir)
|
|
22
|
+
partials: scan_partials(views_dir),
|
|
23
|
+
ui_patterns: extract_ui_patterns(all_content)
|
|
22
24
|
}
|
|
23
25
|
rescue => e
|
|
24
26
|
{ error: e.message }
|
|
@@ -49,12 +51,113 @@ module RailsAiContext
|
|
|
49
51
|
Dir.glob(File.join(views_dir, "**", "_*")).each do |path|
|
|
50
52
|
next if File.directory?(path)
|
|
51
53
|
relative = path.sub("#{views_dir}/", "")
|
|
52
|
-
|
|
53
|
-
partials[relative] = {
|
|
54
|
+
content = File.read(path) rescue next
|
|
55
|
+
partials[relative] = {
|
|
56
|
+
lines: content.lines.count,
|
|
57
|
+
fields: extract_model_fields(content),
|
|
58
|
+
helpers: extract_helper_calls(content)
|
|
59
|
+
}
|
|
54
60
|
end
|
|
55
61
|
partials
|
|
56
62
|
end
|
|
57
63
|
|
|
64
|
+
def collect_all_view_content(views_dir)
|
|
65
|
+
content = ""
|
|
66
|
+
Dir.glob(File.join(views_dir, "**", "*.{erb,haml,slim}")).each do |path|
|
|
67
|
+
next if File.directory?(path)
|
|
68
|
+
content += (File.read(path) rescue "")
|
|
69
|
+
end
|
|
70
|
+
content
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def extract_ui_patterns(all_content) # rubocop:disable Metrics/MethodLength
|
|
74
|
+
patterns = {}
|
|
75
|
+
class_groups = Hash.new(0)
|
|
76
|
+
|
|
77
|
+
# Match both double and single quoted class attributes
|
|
78
|
+
all_content.scan(/class="([^"]+)"/).each do |m|
|
|
79
|
+
classes = m[0].gsub(/<%=.*?%>/, "").strip # Strip ERB interpolation
|
|
80
|
+
next if classes.length < 5
|
|
81
|
+
class_groups[classes] += 1
|
|
82
|
+
end
|
|
83
|
+
all_content.scan(/class='([^']+)'/).each do |m|
|
|
84
|
+
classes = m[0].gsub(/<%=.*?%>/, "").strip
|
|
85
|
+
next if classes.length < 5
|
|
86
|
+
class_groups[classes] += 1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Find repeated patterns (used 2+ times) grouped by element type
|
|
90
|
+
# Framework-agnostic: works with Tailwind, Bootstrap, or custom CSS
|
|
91
|
+
buttons = []
|
|
92
|
+
cards = []
|
|
93
|
+
inputs = []
|
|
94
|
+
labels = []
|
|
95
|
+
badges = []
|
|
96
|
+
links = []
|
|
97
|
+
|
|
98
|
+
class_groups.each do |classes, count|
|
|
99
|
+
next if count < 2
|
|
100
|
+
|
|
101
|
+
if classes.match?(/btn|button|submit|bg-\w+-\d+.*text-white|hover:bg|btn-primary|btn-secondary/)
|
|
102
|
+
buttons << classes
|
|
103
|
+
elsif classes.match?(/card|panel|shadow|border.*rounded.*p-\d|bg-white.*rounded/)
|
|
104
|
+
cards << classes
|
|
105
|
+
elsif classes.match?(/input|field|form-control|border.*rounded.*px-\d|focus:ring|focus:border/)
|
|
106
|
+
inputs << classes
|
|
107
|
+
elsif classes.match?(/label|font-semibold.*mb-|font-medium.*mb-|form-label|block.*text-sm/)
|
|
108
|
+
labels << classes
|
|
109
|
+
elsif classes.match?(/badge|rounded-full|pill|tag|px-2.*py-1.*text-xs/)
|
|
110
|
+
badges << classes
|
|
111
|
+
elsif classes.match?(/link|hover:text-|hover:underline|hover:bg-.*text-.*font-/)
|
|
112
|
+
links << classes
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
patterns[:buttons] = buttons.first(3) if buttons.any?
|
|
117
|
+
patterns[:cards] = cards.first(3) if cards.any?
|
|
118
|
+
patterns[:inputs] = inputs.first(3) if inputs.any?
|
|
119
|
+
patterns[:labels] = labels.first(3) if labels.any?
|
|
120
|
+
patterns[:badges] = badges.first(3) if badges.any?
|
|
121
|
+
patterns[:links] = links.first(3) if links.any?
|
|
122
|
+
|
|
123
|
+
patterns
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
EXCLUDED_METHODS = %w[
|
|
127
|
+
each map select reject first last size count any? empty? present? blank?
|
|
128
|
+
new build create find where order limit nil? join class html_safe
|
|
129
|
+
to_s to_i to_f inspect strip chomp downcase upcase capitalize
|
|
130
|
+
humanize pluralize singularize truncate gsub sub scan match split
|
|
131
|
+
freeze dup clone length bytes chars reverse uniq compact flatten
|
|
132
|
+
flat_map zip sort sort_by min max sum group_by
|
|
133
|
+
persisted? new_record? valid? errors reload save destroy update
|
|
134
|
+
delete respond_to? is_a? kind_of? send try
|
|
135
|
+
abs round ceil floor
|
|
136
|
+
strftime iso8601 beginning_of_day end_of_day ago from_now
|
|
137
|
+
].freeze
|
|
138
|
+
|
|
139
|
+
def extract_model_fields(content)
|
|
140
|
+
fields = []
|
|
141
|
+
content.scan(/(?:@?\w+)\.(\w+)/).each do |m|
|
|
142
|
+
field = m[0]
|
|
143
|
+
next if field.length < 3 || field.length > 40
|
|
144
|
+
next if field.match?(/\A[0-9a-f]+\z/)
|
|
145
|
+
next if field.match?(/\A[A-Z]/) # skip constant/class access
|
|
146
|
+
next if EXCLUDED_METHODS.include?(field)
|
|
147
|
+
next if field.start_with?("to_", "html_")
|
|
148
|
+
next if field.end_with?("?", "!")
|
|
149
|
+
fields << field
|
|
150
|
+
end
|
|
151
|
+
fields.uniq.first(15)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def extract_helper_calls(content)
|
|
155
|
+
helpers = []
|
|
156
|
+
# Custom helper methods (render_*, format_*, *_path, *_url)
|
|
157
|
+
content.scan(/\b(render_\w+|format_\w+)\b/).each { |m| helpers << m[0] }
|
|
158
|
+
helpers.uniq
|
|
159
|
+
end
|
|
160
|
+
|
|
58
161
|
def extract_partial_refs(content)
|
|
59
162
|
refs = []
|
|
60
163
|
# render "partial_name" or render partial: "name"
|
|
@@ -24,6 +24,7 @@ module RailsAiContext
|
|
|
24
24
|
"rails-context.md" => render_context_overview,
|
|
25
25
|
"rails-schema.md" => render_schema_reference,
|
|
26
26
|
"rails-models.md" => render_models_reference,
|
|
27
|
+
"rails-ui-patterns.md" => render_ui_patterns_reference,
|
|
27
28
|
"rails-mcp-tools.md" => render_mcp_tools_reference
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -96,11 +97,34 @@ module RailsAiContext
|
|
|
96
97
|
""
|
|
97
98
|
]
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
skip_cols = %w[id created_at updated_at]
|
|
101
|
+
# Always show these even if they end in _id/_type (important for AI)
|
|
102
|
+
keep_cols = %w[type deleted_at discarded_at]
|
|
103
|
+
|
|
104
|
+
tables.keys.sort.first(30).each do |name|
|
|
100
105
|
data = tables[name]
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
columns = data[:columns] || []
|
|
107
|
+
col_count = columns.size
|
|
108
|
+
pk = data[:primary_key]
|
|
109
|
+
pk_display = pk.is_a?(Array) ? pk.join(", ") : (pk || "id").to_s
|
|
110
|
+
|
|
111
|
+
key_cols = columns.map { |c| c[:name] }.select do |c|
|
|
112
|
+
next true if keep_cols.include?(c)
|
|
113
|
+
next true if c.end_with?("_type") # polymorphic associations
|
|
114
|
+
next false if skip_cols.include?(c)
|
|
115
|
+
next false if c.end_with?("_id")
|
|
116
|
+
true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
col_sample = key_cols.first(12)
|
|
120
|
+
shown = col_sample.join(", ")
|
|
121
|
+
shown += ", ..." if key_cols.size > 12
|
|
122
|
+
col_names = col_sample.any? ? " — #{shown}" : ""
|
|
123
|
+
lines << "- #{name} (#{col_count} cols, pk: #{pk_display})#{col_names}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if tables.size > 30
|
|
127
|
+
lines << "- ...#{tables.size - 30} more tables (use `rails_get_schema` MCP tool)"
|
|
104
128
|
end
|
|
105
129
|
|
|
106
130
|
lines.join("\n")
|
|
@@ -133,6 +157,29 @@ module RailsAiContext
|
|
|
133
157
|
lines.join("\n")
|
|
134
158
|
end
|
|
135
159
|
|
|
160
|
+
def render_ui_patterns_reference
|
|
161
|
+
vt = context[:view_templates]
|
|
162
|
+
return nil unless vt.is_a?(Hash) && !vt[:error]
|
|
163
|
+
patterns = vt[:ui_patterns] || {}
|
|
164
|
+
return nil if patterns.empty?
|
|
165
|
+
|
|
166
|
+
lines = [
|
|
167
|
+
"# UI Patterns",
|
|
168
|
+
"",
|
|
169
|
+
"Common CSS class patterns found in this app's views.",
|
|
170
|
+
"Use these when creating new views to match the existing design.",
|
|
171
|
+
""
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
patterns.each do |type, classes_list|
|
|
175
|
+
classes_list.each do |classes|
|
|
176
|
+
lines << "- #{type.to_s.chomp('s').capitalize}: `#{classes}`"
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
lines.join("\n")
|
|
181
|
+
end
|
|
182
|
+
|
|
136
183
|
def render_mcp_tools_reference # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
137
184
|
lines = [
|
|
138
185
|
"# Rails MCP Tools — ALWAYS Use These First",
|
|
@@ -29,6 +29,7 @@ module RailsAiContext
|
|
|
29
29
|
lines.concat(render_key_models)
|
|
30
30
|
lines.concat(render_notable_gems)
|
|
31
31
|
lines.concat(render_architecture)
|
|
32
|
+
lines.concat(render_ui_patterns)
|
|
32
33
|
lines.concat(render_mcp_guide)
|
|
33
34
|
lines.concat(render_conventions)
|
|
34
35
|
lines.concat(render_commands)
|
|
@@ -158,6 +159,22 @@ module RailsAiContext
|
|
|
158
159
|
lines
|
|
159
160
|
end
|
|
160
161
|
|
|
162
|
+
def render_ui_patterns
|
|
163
|
+
vt = context[:view_templates]
|
|
164
|
+
return [] unless vt.is_a?(Hash) && !vt[:error]
|
|
165
|
+
patterns = vt[:ui_patterns] || {}
|
|
166
|
+
return [] if patterns.empty?
|
|
167
|
+
|
|
168
|
+
lines = [ "## UI Patterns" ]
|
|
169
|
+
patterns.each do |type, classes_list|
|
|
170
|
+
classes_list.each do |classes|
|
|
171
|
+
lines << "- #{type.to_s.chomp('s').capitalize}: `#{classes}`"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
lines << ""
|
|
175
|
+
lines
|
|
176
|
+
end
|
|
177
|
+
|
|
161
178
|
def render_mcp_guide # rubocop:disable Metrics/MethodLength
|
|
162
179
|
[
|
|
163
180
|
"## MCP Tools (11) — ALWAYS Use These First",
|
|
@@ -22,6 +22,7 @@ module RailsAiContext
|
|
|
22
22
|
"rails-context.instructions.md" => render_context_instructions,
|
|
23
23
|
"rails-models.instructions.md" => render_models_instructions,
|
|
24
24
|
"rails-controllers.instructions.md" => render_controllers_instructions,
|
|
25
|
+
"rails-ui-patterns.instructions.md" => render_ui_patterns_instructions,
|
|
25
26
|
"rails-mcp-tools.instructions.md" => render_mcp_tools_instructions
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -134,6 +135,30 @@ module RailsAiContext
|
|
|
134
135
|
lines.join("\n")
|
|
135
136
|
end
|
|
136
137
|
|
|
138
|
+
def render_ui_patterns_instructions
|
|
139
|
+
vt = context[:view_templates]
|
|
140
|
+
return nil unless vt.is_a?(Hash) && !vt[:error]
|
|
141
|
+
patterns = vt[:ui_patterns] || {}
|
|
142
|
+
return nil if patterns.empty?
|
|
143
|
+
|
|
144
|
+
lines = [
|
|
145
|
+
"---",
|
|
146
|
+
"applyTo: \"app/views/**/*.erb\"",
|
|
147
|
+
"---",
|
|
148
|
+
"",
|
|
149
|
+
"# UI Patterns",
|
|
150
|
+
"",
|
|
151
|
+
"Use these CSS class patterns to match the existing design.",
|
|
152
|
+
""
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
patterns.each do |type, classes_list|
|
|
156
|
+
classes_list.each { |c| lines << "- #{type.to_s.chomp('s').capitalize}: `#{c}`" }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
lines.join("\n")
|
|
160
|
+
end
|
|
161
|
+
|
|
137
162
|
def render_mcp_tools_instructions # rubocop:disable Metrics/MethodLength
|
|
138
163
|
lines = [
|
|
139
164
|
"---",
|
|
@@ -80,6 +80,19 @@ module RailsAiContext
|
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
# UI Patterns
|
|
84
|
+
vt = context[:view_templates]
|
|
85
|
+
if vt.is_a?(Hash) && !vt[:error]
|
|
86
|
+
patterns = vt[:ui_patterns] || {}
|
|
87
|
+
if patterns.any?
|
|
88
|
+
lines << "## UI Patterns"
|
|
89
|
+
patterns.each do |type, classes_list|
|
|
90
|
+
classes_list.each { |c| lines << "- #{type.to_s.chomp('s').capitalize}: `#{c}`" }
|
|
91
|
+
end
|
|
92
|
+
lines << ""
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
83
96
|
# MCP tools
|
|
84
97
|
lines << "## MCP Tool Reference"
|
|
85
98
|
lines << ""
|
|
@@ -25,6 +25,7 @@ module RailsAiContext
|
|
|
25
25
|
"rails-project.mdc" => render_project_rule,
|
|
26
26
|
"rails-models.mdc" => render_models_rule,
|
|
27
27
|
"rails-controllers.mdc" => render_controllers_rule,
|
|
28
|
+
"rails-ui-patterns.mdc" => render_ui_patterns_rule,
|
|
28
29
|
"rails-mcp-tools.mdc" => render_mcp_tools_rule
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -154,6 +155,33 @@ module RailsAiContext
|
|
|
154
155
|
lines.join("\n")
|
|
155
156
|
end
|
|
156
157
|
|
|
158
|
+
def render_ui_patterns_rule
|
|
159
|
+
vt = context[:view_templates]
|
|
160
|
+
return nil unless vt.is_a?(Hash) && !vt[:error]
|
|
161
|
+
patterns = vt[:ui_patterns] || {}
|
|
162
|
+
return nil if patterns.empty?
|
|
163
|
+
|
|
164
|
+
lines = [
|
|
165
|
+
"---",
|
|
166
|
+
"description: \"UI/CSS patterns used in this Rails app\"",
|
|
167
|
+
"globs:",
|
|
168
|
+
" - \"app/views/**/*.erb\"",
|
|
169
|
+
"alwaysApply: false",
|
|
170
|
+
"---",
|
|
171
|
+
"",
|
|
172
|
+
"# UI Patterns",
|
|
173
|
+
"",
|
|
174
|
+
"Use these CSS class patterns to match the existing design.",
|
|
175
|
+
""
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
patterns.each do |type, classes_list|
|
|
179
|
+
classes_list.each { |c| lines << "- #{type.to_s.chomp('s').capitalize}: `#{c}`" }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
lines.join("\n")
|
|
183
|
+
end
|
|
184
|
+
|
|
157
185
|
# Always-on MCP tool reference — strongest enforcement point for Cursor
|
|
158
186
|
def render_mcp_tools_rule # rubocop:disable Metrics/MethodLength
|
|
159
187
|
lines = [
|
|
@@ -29,6 +29,7 @@ module RailsAiContext
|
|
|
29
29
|
lines.concat(render_key_models)
|
|
30
30
|
lines.concat(render_notable_gems)
|
|
31
31
|
lines.concat(render_architecture)
|
|
32
|
+
lines.concat(render_ui_patterns)
|
|
32
33
|
lines.concat(render_mcp_guide)
|
|
33
34
|
lines.concat(render_conventions)
|
|
34
35
|
lines.concat(render_commands)
|
|
@@ -158,6 +159,22 @@ module RailsAiContext
|
|
|
158
159
|
lines
|
|
159
160
|
end
|
|
160
161
|
|
|
162
|
+
def render_ui_patterns
|
|
163
|
+
vt = context[:view_templates]
|
|
164
|
+
return [] unless vt.is_a?(Hash) && !vt[:error]
|
|
165
|
+
patterns = vt[:ui_patterns] || {}
|
|
166
|
+
return [] if patterns.empty?
|
|
167
|
+
|
|
168
|
+
lines = [ "## UI Patterns" ]
|
|
169
|
+
patterns.each do |type, classes_list|
|
|
170
|
+
classes_list.each do |classes|
|
|
171
|
+
lines << "- #{type.to_s.chomp('s').capitalize}: `#{classes}`"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
lines << ""
|
|
175
|
+
lines
|
|
176
|
+
end
|
|
177
|
+
|
|
161
178
|
def render_mcp_guide # rubocop:disable Metrics/MethodLength
|
|
162
179
|
[
|
|
163
180
|
"## MCP Tools (11) — ALWAYS Use These First",
|
|
@@ -73,6 +73,19 @@ module RailsAiContext
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
# UI Patterns
|
|
77
|
+
vt = context[:view_templates]
|
|
78
|
+
if vt.is_a?(Hash) && !vt[:error]
|
|
79
|
+
patterns = vt[:ui_patterns] || {}
|
|
80
|
+
if patterns.any?
|
|
81
|
+
lines << "## UI Patterns"
|
|
82
|
+
patterns.each do |type, classes_list|
|
|
83
|
+
classes_list.each { |c| lines << "- #{type.to_s.chomp('s').capitalize}: `#{c}`" }
|
|
84
|
+
end
|
|
85
|
+
lines << ""
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
76
89
|
# MCP tools
|
|
77
90
|
lines << "## MCP Tool Reference"
|
|
78
91
|
lines << ""
|
|
@@ -22,6 +22,7 @@ module RailsAiContext
|
|
|
22
22
|
|
|
23
23
|
files = {
|
|
24
24
|
"rails-context.md" => render_context_rule,
|
|
25
|
+
"rails-ui-patterns.md" => render_ui_patterns_rule,
|
|
25
26
|
"rails-mcp-tools.md" => render_mcp_tools_rule
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -49,6 +50,19 @@ module RailsAiContext
|
|
|
49
50
|
WindsurfSerializer.new(context).call
|
|
50
51
|
end
|
|
51
52
|
|
|
53
|
+
def render_ui_patterns_rule
|
|
54
|
+
vt = context[:view_templates]
|
|
55
|
+
return nil unless vt.is_a?(Hash) && !vt[:error]
|
|
56
|
+
patterns = vt[:ui_patterns] || {}
|
|
57
|
+
return nil if patterns.empty?
|
|
58
|
+
|
|
59
|
+
lines = [ "# UI Patterns", "", "Match these CSS classes when creating new views.", "" ]
|
|
60
|
+
patterns.each do |type, classes_list|
|
|
61
|
+
classes_list.first(2).each { |c| lines << "- #{type}: `#{c}`" }
|
|
62
|
+
end
|
|
63
|
+
lines.join("\n")
|
|
64
|
+
end
|
|
65
|
+
|
|
52
66
|
def render_mcp_tools_rule # rubocop:disable Metrics/MethodLength
|
|
53
67
|
lines = [
|
|
54
68
|
"# Rails MCP Tools (11) — Use These First",
|
|
@@ -71,6 +71,17 @@ module RailsAiContext
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# UI Patterns (compact — character budget is tight)
|
|
75
|
+
vt = context[:view_templates]
|
|
76
|
+
if vt.is_a?(Hash) && !vt[:error]
|
|
77
|
+
patterns = vt[:ui_patterns] || {}
|
|
78
|
+
if patterns.any?
|
|
79
|
+
lines << "# UI Patterns"
|
|
80
|
+
patterns.each { |type, list| list.first(1).each { |c| lines << "- #{type}: `#{c}`" } }
|
|
81
|
+
lines << ""
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
74
85
|
# MCP tools — compact but complete (character budget is tight)
|
|
75
86
|
lines << "# MCP Tools (detail:\"summary\"|\"standard\"|\"full\")"
|
|
76
87
|
lines << "- rails_get_schema(table:\"name\"|detail:\"summary\"|limit:N|offset:N)"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
ASSISTANT_TABLE = <<~TABLE
|
|
3
|
+
ASSISTANT_TABLE = <<~TABLE unless defined?(ASSISTANT_TABLE)
|
|
4
4
|
AI Assistant Context File Command
|
|
5
5
|
-- -- --
|
|
6
6
|
Claude Code CLAUDE.md + .claude/rules/ rails ai:context:claude
|
|
@@ -14,7 +14,7 @@ TABLE
|
|
|
14
14
|
def print_result(result)
|
|
15
15
|
result[:written].each { |f| puts " ✅ #{f}" }
|
|
16
16
|
result[:skipped].each { |f| puts " ⏭️ #{f} (unchanged)" }
|
|
17
|
-
end
|
|
17
|
+
end unless defined?(print_result)
|
|
18
18
|
|
|
19
19
|
def apply_context_mode_override
|
|
20
20
|
if ENV["CONTEXT_MODE"]
|
|
@@ -22,7 +22,7 @@ def apply_context_mode_override
|
|
|
22
22
|
RailsAiContext.configuration.context_mode = mode
|
|
23
23
|
puts "📐 Context mode: #{mode}"
|
|
24
24
|
end
|
|
25
|
-
end
|
|
25
|
+
end unless defined?(apply_context_mode_override)
|
|
26
26
|
|
|
27
27
|
namespace :ai do
|
|
28
28
|
desc "Generate AI context files (CLAUDE.md, .cursor/rules/, .windsurfrules, .github/copilot-instructions.md)"
|
|
@@ -63,15 +63,32 @@ module RailsAiContext
|
|
|
63
63
|
text_response(lines.join("\n"))
|
|
64
64
|
|
|
65
65
|
when "standard"
|
|
66
|
-
|
|
66
|
+
all_partials = data[:partials] || {}
|
|
67
|
+
lines = [ "# Views (#{templates.size} templates, #{all_partials.size} partials)", "" ]
|
|
67
68
|
templates.keys.map { |k| k.split("/").first }.uniq.sort.each do |ctrl|
|
|
68
69
|
ctrl_templates = templates.select { |k, _| k.start_with?("#{ctrl}/") }
|
|
69
70
|
lines << "## #{ctrl}/"
|
|
70
71
|
ctrl_templates.sort.each do |name, meta|
|
|
71
|
-
parts = meta[:partials]&.any? ? "
|
|
72
|
+
parts = meta[:partials]&.any? ? " renders: #{meta[:partials].join(', ')}" : ""
|
|
72
73
|
stim = meta[:stimulus]&.any? ? " stimulus: #{meta[:stimulus].join(', ')}" : ""
|
|
73
74
|
lines << "- #{File.basename(name)} (#{meta[:lines]} lines)#{parts}#{stim}"
|
|
74
75
|
end
|
|
76
|
+
# Show partials for this controller with field/helper info
|
|
77
|
+
ctrl_partials = all_partials.select { |k, _| k.start_with?("#{ctrl}/") }
|
|
78
|
+
ctrl_partials.sort.each do |name, meta|
|
|
79
|
+
fields = meta[:fields]&.any? ? " fields: #{meta[:fields].join(', ')}" : ""
|
|
80
|
+
helpers = meta[:helpers]&.any? ? " helpers: #{meta[:helpers].join(', ')}" : ""
|
|
81
|
+
lines << "- #{File.basename(name)} (#{meta[:lines]} lines)#{fields}#{helpers}"
|
|
82
|
+
end
|
|
83
|
+
lines << ""
|
|
84
|
+
end
|
|
85
|
+
# Show shared partials
|
|
86
|
+
shared = all_partials.select { |k, _| k.start_with?("shared/") }
|
|
87
|
+
if shared.any?
|
|
88
|
+
lines << "## shared/"
|
|
89
|
+
shared.sort.each do |name, meta|
|
|
90
|
+
lines << "- #{File.basename(name)} (#{meta[:lines]} lines)"
|
|
91
|
+
end
|
|
75
92
|
lines << ""
|
|
76
93
|
end
|
|
77
94
|
text_response(lines.join("\n"))
|
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": "0.
|
|
10
|
+
"version": "0.11.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "mcpb",
|
|
14
|
-
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v0.
|
|
14
|
+
"identifier": "https://github.com/crisnahine/rails-ai-context/releases/download/v0.11.0/rails-ai-context-mcp.mcpb",
|
|
15
15
|
"fileSha256": "dd711a0ad6c4de943ae4da94eaf59a6dc9494b9d57f726e24649ed4e2f156990",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|