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.
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: 2.0.4
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, Windsurf, GitHub Copilot, and OpenCode. Zero config.
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/windsurf_rules_serializer.rb
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