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
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-ai-context
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- crisnahine
|
|
@@ -167,7 +167,7 @@ description: |
|
|
|
167
167
|
conventions connect. 25 live MCP tools let agents query structure on demand with
|
|
168
168
|
semantic validation that catches cross-file errors (wrong columns, missing
|
|
169
169
|
partials, broken routes) before code runs. Auto-generates context files for
|
|
170
|
-
Claude Code, Cursor,
|
|
170
|
+
Claude Code, Cursor, GitHub Copilot, and OpenCode. Zero config.
|
|
171
171
|
email:
|
|
172
172
|
- crisjosephnahine@gmail.com
|
|
173
173
|
executables:
|
|
@@ -192,6 +192,7 @@ files:
|
|
|
192
192
|
- lib/generators/rails_ai_context/install/install_generator.rb
|
|
193
193
|
- lib/rails-ai-context.rb
|
|
194
194
|
- lib/rails_ai_context.rb
|
|
195
|
+
- lib/rails_ai_context/cli/tool_runner.rb
|
|
195
196
|
- lib/rails_ai_context/configuration.rb
|
|
196
197
|
- lib/rails_ai_context/doctor.rb
|
|
197
198
|
- lib/rails_ai_context/engine.rb
|
|
@@ -243,8 +244,7 @@ files:
|
|
|
243
244
|
- lib/rails_ai_context/serializers/rules_serializer.rb
|
|
244
245
|
- lib/rails_ai_context/serializers/stack_overview_helper.rb
|
|
245
246
|
- lib/rails_ai_context/serializers/test_command_detection.rb
|
|
246
|
-
- lib/rails_ai_context/serializers/
|
|
247
|
-
- lib/rails_ai_context/serializers/windsurf_serializer.rb
|
|
247
|
+
- lib/rails_ai_context/serializers/tool_guide_helper.rb
|
|
248
248
|
- lib/rails_ai_context/server.rb
|
|
249
249
|
- lib/rails_ai_context/tasks/rails_ai_context.rake
|
|
250
250
|
- lib/rails_ai_context/tools/analyze_feature.rb
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RailsAiContext
|
|
4
|
-
module Serializers
|
|
5
|
-
# Generates .windsurf/rules/*.md files in the new Windsurf rules format.
|
|
6
|
-
# Each file is hard-capped at 5,800 characters (within Windsurf's 6K limit).
|
|
7
|
-
class WindsurfRulesSerializer
|
|
8
|
-
include DesignSystemHelper
|
|
9
|
-
|
|
10
|
-
MAX_CHARS_PER_FILE = 5_800
|
|
11
|
-
|
|
12
|
-
attr_reader :context
|
|
13
|
-
|
|
14
|
-
def initialize(context)
|
|
15
|
-
@context = context
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def call(output_dir)
|
|
19
|
-
rules_dir = File.join(output_dir, ".windsurf", "rules")
|
|
20
|
-
FileUtils.mkdir_p(rules_dir)
|
|
21
|
-
|
|
22
|
-
written = []
|
|
23
|
-
skipped = []
|
|
24
|
-
|
|
25
|
-
files = {
|
|
26
|
-
"rails-context.md" => render_context_rule,
|
|
27
|
-
"rails-ui-patterns.md" => render_ui_patterns_rule,
|
|
28
|
-
"rails-mcp-tools.md" => render_mcp_tools_rule
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
files.each do |filename, content|
|
|
32
|
-
next unless content
|
|
33
|
-
# Enforce Windsurf's 6K limit
|
|
34
|
-
content = content[0...MAX_CHARS_PER_FILE] if content.length > MAX_CHARS_PER_FILE
|
|
35
|
-
|
|
36
|
-
filepath = File.join(rules_dir, filename)
|
|
37
|
-
if File.exist?(filepath) && File.read(filepath) == content
|
|
38
|
-
skipped << filepath
|
|
39
|
-
else
|
|
40
|
-
File.write(filepath, content)
|
|
41
|
-
written << filepath
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
{ written: written, skipped: skipped }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def render_context_rule
|
|
51
|
-
# Reuse WindsurfSerializer content
|
|
52
|
-
WindsurfSerializer.new(context).call
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def render_ui_patterns_rule
|
|
56
|
-
vt = context[:view_templates]
|
|
57
|
-
return nil unless vt.is_a?(Hash) && !vt[:error]
|
|
58
|
-
components = vt.dig(:ui_patterns, :components) || []
|
|
59
|
-
return nil if components.empty?
|
|
60
|
-
|
|
61
|
-
# Compact design system for Windsurf's character budget
|
|
62
|
-
lines = render_design_system(context, max_lines: 25)
|
|
63
|
-
return nil if lines.empty?
|
|
64
|
-
|
|
65
|
-
lines.join("\n")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def render_mcp_tools_rule # rubocop:disable Metrics/MethodLength
|
|
69
|
-
lines = [
|
|
70
|
-
"# Rails MCP Tools (25) — MANDATORY, Use Before Read",
|
|
71
|
-
"",
|
|
72
|
-
"CRITICAL: This project has live MCP tools. Use them for ALL context gathering.",
|
|
73
|
-
"Read files ONLY when you are about to edit them.",
|
|
74
|
-
"",
|
|
75
|
-
"Mandatory Workflow:",
|
|
76
|
-
"1. Gathering context → use MCP tools (NOT file reads)",
|
|
77
|
-
"2. Reading files → ONLY files you will edit",
|
|
78
|
-
"3. After editing → rails_validate(files:[...]) every time",
|
|
79
|
-
"",
|
|
80
|
-
"Do NOT Bypass:",
|
|
81
|
-
"- Reading db/schema.rb → rails_get_schema(table:\"name\")",
|
|
82
|
-
"- Reading config/routes.rb → rails_get_routes(controller:\"name\")",
|
|
83
|
-
"- Reading model files → rails_get_model_details(model:\"Name\")",
|
|
84
|
-
"- Grep for code → rails_search_code(pattern:\"regex\")",
|
|
85
|
-
"- Reading test files → rails_get_test_info(model:\"Name\")",
|
|
86
|
-
"- Reading controller → rails_get_controllers(controller:\"Name\", action:\"x\")",
|
|
87
|
-
"- Reading JS for Stimulus → rails_get_stimulus(controller:\"name\")",
|
|
88
|
-
"- Multiple reads for feature → rails_analyze_feature(feature:\"keyword\")",
|
|
89
|
-
"- ruby -c / erb / node -c → rails_validate(files:[...])",
|
|
90
|
-
"",
|
|
91
|
-
"All 25 Tools:",
|
|
92
|
-
"- rails_get_schema | rails_get_model_details | rails_get_routes | rails_get_controllers",
|
|
93
|
-
"- rails_get_view | rails_get_stimulus | rails_get_test_info | rails_analyze_feature",
|
|
94
|
-
"- rails_get_design_system | rails_get_edit_context | rails_validate | rails_search_code",
|
|
95
|
-
"- rails_get_config | rails_get_gems | rails_get_conventions | rails_security_scan",
|
|
96
|
-
"- rails_get_concern | rails_get_callbacks | rails_get_helper_methods | rails_get_service_pattern",
|
|
97
|
-
"- rails_get_job_pattern | rails_get_env | rails_get_partial_interface | rails_get_turbo_map",
|
|
98
|
-
"- rails_get_context(model:\"X\") — composite cross-layer context in one call",
|
|
99
|
-
"",
|
|
100
|
-
"Power Features:",
|
|
101
|
-
"- rails_search_code(pattern:\"method\", match_type:\"trace\") — trace: definition + source + siblings + callers + tests",
|
|
102
|
-
"- rails_get_concern(name:\"X\", detail:\"full\") — concern methods with source code",
|
|
103
|
-
"- rails_analyze_feature — full-stack with inherited filters, route helpers, test gaps",
|
|
104
|
-
"- rails_get_schema — columns, indexes, defaults, encrypted hints, orphaned table warnings"
|
|
105
|
-
]
|
|
106
|
-
|
|
107
|
-
lines.join("\n")
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RailsAiContext
|
|
4
|
-
module Serializers
|
|
5
|
-
# Generates .windsurfrules within Windsurf's hard 6,000 character limit.
|
|
6
|
-
# Always produces compact output regardless of context_mode.
|
|
7
|
-
class WindsurfSerializer
|
|
8
|
-
include TestCommandDetection
|
|
9
|
-
include StackOverviewHelper
|
|
10
|
-
include DesignSystemHelper
|
|
11
|
-
|
|
12
|
-
MAX_CHARS = 5_800 # Leave buffer below 6K limit
|
|
13
|
-
|
|
14
|
-
attr_reader :context
|
|
15
|
-
|
|
16
|
-
def initialize(context)
|
|
17
|
-
@context = context
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def call
|
|
21
|
-
content = render
|
|
22
|
-
# HARD enforce character limit — Windsurf silently truncates
|
|
23
|
-
if content.length > MAX_CHARS
|
|
24
|
-
content = content[0...MAX_CHARS]
|
|
25
|
-
content += "\n\n# Use MCP tools for full details."
|
|
26
|
-
end
|
|
27
|
-
content
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def render
|
|
33
|
-
lines = []
|
|
34
|
-
lines << "# #{context[:app_name]} — Rails #{context[:rails_version]}"
|
|
35
|
-
lines << ""
|
|
36
|
-
|
|
37
|
-
# Stack (very compact)
|
|
38
|
-
schema = context[:schema]
|
|
39
|
-
lines << "Database: #{schema[:adapter]}, #{schema[:total_tables]} tables" if schema && !schema[:error]
|
|
40
|
-
|
|
41
|
-
models = context[:models]
|
|
42
|
-
lines << "Models: #{models.size}" if models.is_a?(Hash) && !models[:error]
|
|
43
|
-
|
|
44
|
-
routes = context[:routes]
|
|
45
|
-
lines << "Routes: #{routes[:total_routes]}" if routes && !routes[:error]
|
|
46
|
-
|
|
47
|
-
lines.concat(full_preset_stack_lines)
|
|
48
|
-
|
|
49
|
-
# Gems (one line per category)
|
|
50
|
-
gems = context[:gems]
|
|
51
|
-
if gems.is_a?(Hash) && !gems[:error]
|
|
52
|
-
notable = gems[:notable_gems] || gems[:notable] || gems[:detected] || []
|
|
53
|
-
grouped = notable.group_by { |g| g[:category]&.to_s || "other" }
|
|
54
|
-
grouped.first(6).each do |cat, gem_list|
|
|
55
|
-
lines << "#{cat}: #{gem_list.map { |g| g[:name] }.first(4).join(', ')}"
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
lines << ""
|
|
60
|
-
|
|
61
|
-
# Key models (names only — character budget is tight)
|
|
62
|
-
if models.is_a?(Hash) && !models[:error] && models.any?
|
|
63
|
-
lines << "# Key models"
|
|
64
|
-
models.keys.sort.first(20).each do |name|
|
|
65
|
-
data = models[name]
|
|
66
|
-
lines << "- #{name}"
|
|
67
|
-
scopes = (data[:scopes] || [])
|
|
68
|
-
constants = (data[:constants] || [])
|
|
69
|
-
if scopes.any? || constants.any?
|
|
70
|
-
extras = []
|
|
71
|
-
extras << "scopes: #{scopes.join(', ')}" if scopes.any?
|
|
72
|
-
constants.each { |c| extras << "#{c[:name]}: #{c[:values].join(', ')}" }
|
|
73
|
-
lines << " #{extras.join(' | ')}"
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
lines << "- ...#{models.size - 20} more" if models.size > 20
|
|
77
|
-
lines << ""
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Architecture
|
|
81
|
-
conv = context[:conventions]
|
|
82
|
-
if conv.is_a?(Hash) && !conv[:error]
|
|
83
|
-
arch = conv[:architecture] || []
|
|
84
|
-
if arch.any?
|
|
85
|
-
arch_labels = RailsAiContext::Tools::GetConventions::ARCH_LABELS rescue {}
|
|
86
|
-
lines << "# Architecture"
|
|
87
|
-
arch.first(5).each { |p| lines << "- #{arch_labels[p] || p}" }
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# List service objects
|
|
92
|
-
begin
|
|
93
|
-
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
94
|
-
services_dir = File.join(root, "app", "services")
|
|
95
|
-
if Dir.exist?(services_dir)
|
|
96
|
-
service_files = Dir.glob(File.join(services_dir, "*.rb"))
|
|
97
|
-
.map { |f| File.basename(f, ".rb").camelize }
|
|
98
|
-
.reject { |s| s == "ApplicationService" }
|
|
99
|
-
lines << "Services: #{service_files.join(', ')}" if service_files.any?
|
|
100
|
-
end
|
|
101
|
-
rescue; end
|
|
102
|
-
|
|
103
|
-
# List jobs
|
|
104
|
-
begin
|
|
105
|
-
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
106
|
-
jobs_dir = File.join(root, "app", "jobs")
|
|
107
|
-
if Dir.exist?(jobs_dir)
|
|
108
|
-
job_files = Dir.glob(File.join(jobs_dir, "*.rb"))
|
|
109
|
-
.map { |f| File.basename(f, ".rb").camelize }
|
|
110
|
-
.reject { |j| j == "ApplicationJob" }
|
|
111
|
-
lines << "Jobs: #{job_files.join(', ')}" if job_files.any?
|
|
112
|
-
end
|
|
113
|
-
rescue; end
|
|
114
|
-
|
|
115
|
-
# ApplicationController before_actions
|
|
116
|
-
begin
|
|
117
|
-
root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
118
|
-
app_ctrl = File.join(root, "app", "controllers", "application_controller.rb")
|
|
119
|
-
if File.exist?(app_ctrl)
|
|
120
|
-
source = File.read(app_ctrl)
|
|
121
|
-
before_actions = source.scan(/before_action\s+:([\w!?]+)/).flatten
|
|
122
|
-
lines << "Global before_actions: #{before_actions.join(', ')}" if before_actions.any?
|
|
123
|
-
end
|
|
124
|
-
rescue; end
|
|
125
|
-
|
|
126
|
-
lines << ""
|
|
127
|
-
|
|
128
|
-
# Design System (compact — character budget is tight)
|
|
129
|
-
ds_lines = render_design_system(context, max_lines: 15)
|
|
130
|
-
lines.concat(ds_lines) if ds_lines.any?
|
|
131
|
-
|
|
132
|
-
# MCP tools — compact but complete (character budget is tight)
|
|
133
|
-
lines << "# MCP Tools (detail:\"summary\"|\"standard\"|\"full\")"
|
|
134
|
-
lines << "- rails_get_schema(table:\"name\"|detail:\"summary\"|limit:N|offset:N)"
|
|
135
|
-
lines << "- rails_get_model_details(model:\"Name\"|detail:\"summary\")"
|
|
136
|
-
lines << "- rails_get_routes(controller:\"name\"|detail:\"summary\"|limit:N|offset:N)"
|
|
137
|
-
lines << "- rails_get_controllers(controller:\"Name\"|detail:\"summary\")"
|
|
138
|
-
lines << "- rails_get_config — cache, session, middleware"
|
|
139
|
-
lines << "- rails_get_test_info — framework, factories, CI"
|
|
140
|
-
lines << "- rails_get_gems — categorized gems"
|
|
141
|
-
lines << "- rails_get_conventions — architecture patterns"
|
|
142
|
-
lines << "- rails_search_code(pattern:\"regex\"|file_type:\"rb\"|max_results:N)"
|
|
143
|
-
lines << "- rails_get_edit_context(file:\"path\"|near:\"keyword\")"
|
|
144
|
-
lines << "- rails_analyze_feature(feature:\"auth\") — combined context for a feature"
|
|
145
|
-
lines << "- rails_get_design_system — color palette, components, page examples"
|
|
146
|
-
lines << "- rails_validate(files:[\"path\"])"
|
|
147
|
-
lines << "Start with detail:\"summary\", then drill into specifics."
|
|
148
|
-
lines << ""
|
|
149
|
-
lines << "# Rules"
|
|
150
|
-
lines << "- Follow existing patterns"
|
|
151
|
-
lines << "- Check schema via MCP before writing migrations"
|
|
152
|
-
lines << "- Run `#{detect_test_command}` after changes"
|
|
153
|
-
lines << "- After editing: use rails_validate, do NOT re-read files to verify"
|
|
154
|
-
lines << "- Stimulus controllers auto-register — no manual import needed"
|
|
155
|
-
|
|
156
|
-
lines.join("\n")
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|