railsforge 1.0.2 → 2.1.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +129 -435
  3. data/lib/railsforge/analyzers/controller_analyzer.rb +29 -55
  4. data/lib/railsforge/analyzers/database_analyzer.rb +16 -30
  5. data/lib/railsforge/analyzers/metrics_analyzer.rb +8 -22
  6. data/lib/railsforge/analyzers/model_analyzer.rb +29 -46
  7. data/lib/railsforge/analyzers/performance_analyzer.rb +34 -94
  8. data/lib/railsforge/analyzers/refactor_analyzer.rb +77 -57
  9. data/lib/railsforge/analyzers/security_analyzer.rb +34 -91
  10. data/lib/railsforge/analyzers/spec_analyzer.rb +17 -31
  11. data/lib/railsforge/cli.rb +28 -645
  12. data/lib/railsforge/cli_minimal.rb +10 -55
  13. data/lib/railsforge/diff.rb +57 -0
  14. data/lib/railsforge/doctor.rb +52 -225
  15. data/lib/railsforge/formatter.rb +128 -0
  16. data/lib/railsforge/issue.rb +23 -0
  17. data/lib/railsforge/loader.rb +5 -64
  18. data/lib/railsforge/version.rb +1 -1
  19. metadata +15 -82
  20. data/lib/railsforge/api_generator.rb +0 -397
  21. data/lib/railsforge/audit.rb +0 -289
  22. data/lib/railsforge/config.rb +0 -181
  23. data/lib/railsforge/database_analyzer.rb +0 -300
  24. data/lib/railsforge/feature_generator.rb +0 -560
  25. data/lib/railsforge/generator.rb +0 -313
  26. data/lib/railsforge/generators/api_generator.rb +0 -392
  27. data/lib/railsforge/generators/base_generator.rb +0 -75
  28. data/lib/railsforge/generators/demo_generator.rb +0 -307
  29. data/lib/railsforge/generators/devops_generator.rb +0 -287
  30. data/lib/railsforge/generators/form_generator.rb +0 -180
  31. data/lib/railsforge/generators/job_generator.rb +0 -176
  32. data/lib/railsforge/generators/monitoring_generator.rb +0 -134
  33. data/lib/railsforge/generators/policy_generator.rb +0 -220
  34. data/lib/railsforge/generators/presenter_generator.rb +0 -173
  35. data/lib/railsforge/generators/query_generator.rb +0 -174
  36. data/lib/railsforge/generators/serializer_generator.rb +0 -166
  37. data/lib/railsforge/generators/service_generator.rb +0 -122
  38. data/lib/railsforge/generators/stimulus_controller_generator.rb +0 -129
  39. data/lib/railsforge/generators/test_generator.rb +0 -289
  40. data/lib/railsforge/generators/view_component_generator.rb +0 -169
  41. data/lib/railsforge/graph.rb +0 -270
  42. data/lib/railsforge/mailer_generator.rb +0 -191
  43. data/lib/railsforge/plugins/plugin_loader.rb +0 -60
  44. data/lib/railsforge/plugins.rb +0 -30
  45. data/lib/railsforge/profiles.rb +0 -99
  46. data/lib/railsforge/refactor_analyzer.rb +0 -401
  47. data/lib/railsforge/refactor_controller.rb +0 -277
  48. data/lib/railsforge/refactors/refactor_controller.rb +0 -117
  49. data/lib/railsforge/template_loader.rb +0 -105
  50. data/lib/railsforge/templates/v1/form/spec_template.rb +0 -18
  51. data/lib/railsforge/templates/v1/form/template.rb +0 -28
  52. data/lib/railsforge/templates/v1/job/spec_template.rb +0 -17
  53. data/lib/railsforge/templates/v1/job/template.rb +0 -13
  54. data/lib/railsforge/templates/v1/policy/spec_template.rb +0 -41
  55. data/lib/railsforge/templates/v1/policy/template.rb +0 -57
  56. data/lib/railsforge/templates/v1/presenter/spec_template.rb +0 -12
  57. data/lib/railsforge/templates/v1/presenter/template.rb +0 -13
  58. data/lib/railsforge/templates/v1/query/spec_template.rb +0 -12
  59. data/lib/railsforge/templates/v1/query/template.rb +0 -16
  60. data/lib/railsforge/templates/v1/serializer/spec_template.rb +0 -13
  61. data/lib/railsforge/templates/v1/serializer/template.rb +0 -11
  62. data/lib/railsforge/templates/v1/service/spec_template.rb +0 -12
  63. data/lib/railsforge/templates/v1/service/template.rb +0 -25
  64. data/lib/railsforge/templates/v1/stimulus_controller/template.rb +0 -35
  65. data/lib/railsforge/templates/v1/view_component/template.rb +0 -24
  66. data/lib/railsforge/templates/v2/job/template.rb +0 -49
  67. data/lib/railsforge/templates/v2/query/template.rb +0 -66
  68. data/lib/railsforge/templates/v2/service/spec_template.rb +0 -33
  69. data/lib/railsforge/templates/v2/service/template.rb +0 -71
  70. data/lib/railsforge/templates/v3/job/template.rb +0 -72
  71. data/lib/railsforge/templates/v3/query/spec_template.rb +0 -54
  72. data/lib/railsforge/templates/v3/query/template.rb +0 -115
  73. data/lib/railsforge/templates/v3/service/spec_template.rb +0 -51
  74. data/lib/railsforge/templates/v3/service/template.rb +0 -93
  75. data/lib/railsforge/wizard.rb +0 -265
  76. data/lib/railsforge/wizard_tui.rb +0 -286
@@ -1,5 +1,5 @@
1
- # Minimal CLI for fast --help and --version
2
- # This is loaded first, then full CLI is lazy-loaded when needed
1
+ # Minimal CLI fast path for --help and --version.
2
+ # All other commands load the full pipeline.
3
3
 
4
4
  module RailsForge
5
5
  class CLIMinimal
@@ -11,28 +11,10 @@ module RailsForge
11
11
  display_help
12
12
  exit 0
13
13
  when "--version", "-v"
14
- # Only load version, nothing else
15
14
  require_relative "version"
16
15
  puts "RailsForge #{RailsForge::VERSION}"
17
16
  exit 0
18
17
  else
19
- # Load full CLI for other commands - this is where the slowness comes from
20
- require_relative "cli"
21
- require_relative "profiles"
22
- require_relative "template_loader"
23
- require_relative "generator"
24
-
25
- # Load all generators
26
- require_relative "generators/base_generator"
27
- require_relative "generators/service_generator"
28
- require_relative "generators/view_component_generator"
29
- require_relative "generators/stimulus_controller_generator"
30
- require_relative "generators/demo_generator"
31
- require_relative "generators/devops_generator"
32
- require_relative "generators/monitoring_generator"
33
- require_relative "generators/test_generator"
34
-
35
- # Load all analyzers
36
18
  require_relative "analyzers/base_analyzer"
37
19
  require_relative "analyzers/controller_analyzer"
38
20
  require_relative "analyzers/model_analyzer"
@@ -42,51 +24,24 @@ module RailsForge
42
24
  require_relative "analyzers/refactor_analyzer"
43
25
  require_relative "analyzers/security_analyzer"
44
26
  require_relative "analyzers/performance_analyzer"
45
-
46
- # Load refactors and plugins
47
- require_relative "refactors/refactor_controller"
48
- require_relative "plugins"
49
-
50
- # Load legacy modules
51
- require_relative "mailer_generator"
52
- require_relative "feature_generator"
53
- require_relative "api_generator"
54
- require_relative "audit"
55
27
  require_relative "doctor"
56
- require_relative "graph"
57
- require_relative "wizard"
58
- require_relative "wizard_tui"
59
- require_relative "config"
28
+ require_relative "diff"
29
+ require_relative "cli"
60
30
 
61
- # Dispatch to full CLI
62
31
  CLI.start([command] + args)
63
32
  end
64
33
  end
65
34
 
66
35
  def self.display_help
67
36
  puts <<~HELP
68
- RailsForge - A Rails development toolkit
37
+ RailsForge Rails application health diagnostics
69
38
 
70
39
  Usage:
71
- railsforge new <app_name> Create a new Rails app
72
- railsforge generate service <name> Generate a service object
73
- railsforge generate query <name> Generate a query object
74
- railsforge generate job <name> Generate a job object
75
- railsforge generate demo <name> Generate a demo project
76
- railsforge generate devops Generate Docker/CI-CD configs
77
- railsforge generate monitoring Generate Sentry/Lograge configs
78
- railsforge generate test <name> Generate test files
79
- railsforge analyze Analyze code quality
80
- railsforge doctor Full project health check
81
- railsforge audit Architecture audit
82
- railsforge graph Dependency graph
83
- railsforge refactor Refactoring suggestions
84
- railsforge wizard Interactive project setup
85
- railsforge plugins Plugin management
86
- railsforge config Configuration
87
- railsforge profiles List available profiles
88
- railsforge --version, -v Show version
89
- railsforge --help, -h Show this help
40
+ railsforge doctor Run full diagnostic scan
41
+ railsforge doctor --format=json Output as JSON
42
+ railsforge diff <baseline.json> <current> Show only new issues
43
+ railsforge --version Show version
44
+ railsforge --help Show this help
90
45
  HELP
91
46
  end
92
47
  end
@@ -0,0 +1,57 @@
1
+ # Diff — compare two RailsForge JSON outputs and return only new issues.
2
+ #
3
+ # Usage:
4
+ # new_issues = RailsForge::Diff.run("baseline.json", "current.json")
5
+ #
6
+ # Two issues are considered equal when analyzer + type + file + line + message
7
+ # all match. Severity and suggestion are intentionally ignored.
8
+
9
+ require "json"
10
+
11
+ module RailsForge
12
+ class Diff
13
+ class DiffError < StandardError; end
14
+
15
+ def self.run(baseline_path, current_path)
16
+ baseline = load_issues(baseline_path)
17
+ current = load_issues(current_path)
18
+
19
+ baseline_fingerprints = baseline.map { |i| fingerprint(i) }
20
+
21
+ current.reject { |i| baseline_fingerprints.include?(fingerprint(i)) }
22
+ end
23
+
24
+ class << self
25
+ private
26
+
27
+ def load_issues(path)
28
+ raise DiffError, "File not found: #{path}" unless File.exist?(path)
29
+
30
+ raw = JSON.parse(File.read(path))
31
+ raise DiffError, "Invalid format: missing 'issues' key in #{path}" unless raw.key?("issues")
32
+
33
+ raw["issues"].map do |h|
34
+ RailsForge::Issue.new(
35
+ analyzer: h["analyzer"]&.to_sym,
36
+ type: h["type"]&.to_sym,
37
+ severity: h["severity"],
38
+ file: h["file"],
39
+ line: h["line"],
40
+ message: h["message"],
41
+ suggestion: h["suggestion"]
42
+ )
43
+ end
44
+ end
45
+
46
+ def fingerprint(issue)
47
+ [
48
+ issue.analyzer.to_s,
49
+ issue.type.to_s,
50
+ issue.file.to_s,
51
+ issue.line.to_s,
52
+ issue.message.to_s
53
+ ]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,250 +1,77 @@
1
- # Doctor module for RailsForge
2
- # Provides a comprehensive project health report
1
+ # Doctor RailsForge v2 core orchestrator
2
+ #
3
+ # Responsibilities: run analyzers, collect issues, compute score, return result.
4
+ # No output. No formatting. No printing.
5
+ #
6
+ # Final flow:
7
+ # CLI → Doctor → Raw Result → Formatter → Output
3
8
 
4
9
  module RailsForge
5
- # Doctor class provides a full project health check
6
10
  class Doctor
7
- # Initialize the Doctor analyzer
11
+ ANALYZER_MAP = {
12
+ controller: ->(p) { Analyzers::ControllerAnalyzer.analyze(p) },
13
+ model: ->(p) { Analyzers::ModelAnalyzer.analyze(p) },
14
+ database: ->(p) { Analyzers::DatabaseAnalyzer.analyze(p) },
15
+ spec: ->(p) { Analyzers::SpecAnalyzer.analyze(p) },
16
+ security: ->(p) { Analyzers::SecurityAnalyzer.analyze(p) },
17
+ performance: ->(p) { Analyzers::PerformanceAnalyzer.analyze(p) },
18
+ refactor: ->(p) { Analyzers::RefactorAnalyzer.analyze(p) }
19
+ }.freeze
20
+
21
+ SEVERITY_WEIGHTS = {
22
+ "critical" => 20,
23
+ "high" => 10,
24
+ "medium" => 5,
25
+ "low" => 2,
26
+ "info" => 0
27
+ }.freeze
28
+
29
+ class DoctorError < StandardError; end
30
+
8
31
  def initialize(base_path = nil)
9
- @base_path = base_path || find_rails_app_path
32
+ @base_path = base_path || Analyzers::BaseAnalyzer.find_rails_app_path
10
33
  raise DoctorError, "Not in a Rails application directory" unless @base_path
11
34
 
12
- @results = {
13
- controllers: [],
14
- models: [],
15
- database: [],
16
- specs: [],
17
- refactors: [],
18
- metrics: {}
19
- }
35
+ @issues = []
36
+ @metrics = {}
37
+ @errors = {}
20
38
  end
21
39
 
22
- # Run all analyzers and generate report
23
- # @return [Hash] Complete health check results
40
+ # Run all analyzers and return raw result.
41
+ #
42
+ # @return [Hash] { score: Integer, issues: Array<Issue>, metrics: Hash }
24
43
  def run
25
- puts "Running RailsForge Doctor..."
26
- puts ""
44
+ collect_issues
45
+ collect_metrics
27
46
 
28
- # Run all analyzers
29
- run_analyzers
30
-
31
- # Calculate score
32
- @results[:score] = calculate_score
33
-
34
- # Print report
35
- print_report
36
-
37
- @results
47
+ {
48
+ score: calculate_score,
49
+ issues: @issues,
50
+ metrics: @metrics
51
+ }
38
52
  end
39
53
 
40
54
  private
41
55
 
42
- # Run all available analyzers
43
- def run_analyzers
44
- # Controller analysis
45
- begin
46
- @results[:controllers] = RailsForge::Analyzers::RefactorAnalyzer.analyze_controllers(@base_path)
47
- rescue => e
48
- @results[:controllers] = []
49
- end
50
-
51
- # Model analysis
52
- begin
53
- @results[:models] = RailsForge::Analyzers::RefactorAnalyzer.analyze_models(@base_path)
54
- rescue => e
55
- @results[:models] = []
56
- end
57
-
58
- # Database analysis
59
- begin
60
- @results[:database] = RailsForge::Analyzers::DatabaseAnalyzer.analyze(@base_path)
61
- rescue => e
62
- @results[:database] = []
63
- end
64
-
65
- # Spec analysis
66
- begin
67
- @results[:specs] = RailsForge::Analyzers::SpecAnalyzer.analyze(@base_path)
68
- rescue => e
69
- @results[:specs] = []
70
- end
71
-
72
- # Refactor suggestions (from RefactorAnalyzer)
73
- begin
74
- @results[:refactors] = []
75
- @results[:controllers].each do |ctrl|
76
- @results[:refactors].concat(ctrl[:suggestions] || [])
77
- end
78
- @results[:models].each do |model|
79
- @results[:refactors].concat(model[:suggestions] || [])
80
- end
56
+ def collect_issues
57
+ ANALYZER_MAP.each do |name, callable|
58
+ results = callable.call(@base_path)
59
+ @issues.concat(Array(results))
81
60
  rescue => e
82
- @results[:refactors] = []
61
+ @errors[name] = e.message
83
62
  end
63
+ end
84
64
 
85
- # Metrics
86
- begin
87
- @results[:metrics] = RailsForge::Analyzers::MetricsAnalyzer.analyze(@base_path)
88
- rescue => e
89
- @results[:metrics] = {}
90
- end
65
+ def collect_metrics
66
+ @metrics = Analyzers::MetricsAnalyzer.analyze(@base_path)
67
+ rescue
68
+ @metrics = {}
91
69
  end
92
70
 
93
- # Calculate architecture score
94
- # @return [Integer] Score out of 100
95
71
  def calculate_score
96
72
  score = 100
97
-
98
- # Subtract 5 points per fat controller
99
- fat_controllers = @results[:controllers].count { |c| c[:needs_refactoring] }
100
- score -= fat_controllers * 5
101
-
102
- # Subtract 5 points per fat model
103
- fat_models = @results[:models].count { |m| m[:needs_refactoring] }
104
- score -= fat_models * 5
105
-
106
- # Subtract 3 points per missing spec
107
- missing_specs = @results[:specs].count { |s| s[:has_spec] == false }
108
- score -= missing_specs * 3
109
-
110
- # Subtract 2 points per missing index (database issues)
111
- missing_indexes = @results[:database].count { |d| d[:type] == :index }
112
- score -= missing_indexes * 2
113
-
114
- # Subtract 2 points per long method
115
- long_methods = count_long_methods
116
- score -= long_methods * 2
117
-
73
+ @issues.each { |issue| score -= SEVERITY_WEIGHTS.fetch(issue.severity.to_s, 0) }
118
74
  [score, 0].max
119
75
  end
120
-
121
- # Count long methods in controllers and models
122
- # @return [Integer] Number of long methods
123
- def count_long_methods
124
- count = 0
125
-
126
- @results[:controllers].each do |ctrl|
127
- methods = ctrl[:methods] || []
128
- count += methods.count { |m| m[:lines].to_i > 15 }
129
- end
130
-
131
- @results[:models].each do |model|
132
- methods = model[:methods] || []
133
- count += methods.count { |m| m[:lines].to_i > 15 }
134
- end
135
-
136
- count
137
- end
138
-
139
- # Print formatted report
140
- def print_report
141
- score = @results[:score]
142
- score_color = score >= 80 ? :green : score >= 50 ? :yellow : :red
143
-
144
- puts "RailsForge Doctor Report"
145
- puts "────────────────────────"
146
- puts ""
147
- puts "Architecture Score: #{colorize(score, score_color)}/100"
148
- puts ""
149
-
150
- # Controllers
151
- puts "Controllers:"
152
- if @results[:controllers].empty?
153
- puts " ✓ No issues found"
154
- else
155
- @results[:controllers].each do |ctrl|
156
- issues = ctrl[:issues] || []
157
- if issues.any?
158
- puts " ⚠ #{ctrl[:file]}: #{issues.join(', ')}"
159
- end
160
- end
161
- end
162
- puts ""
163
-
164
- # Models
165
- puts "Models:"
166
- if @results[:models].empty?
167
- puts " ✓ No issues found"
168
- else
169
- @results[:models].each do |model|
170
- issues = model[:issues] || []
171
- if issues.any?
172
- puts " ⚠ #{model[:file]}: #{issues.join(', ')}"
173
- end
174
- end
175
- end
176
- puts ""
177
-
178
- # Database
179
- puts "Database:"
180
- if @results[:database].empty?
181
- puts " ✓ No issues found"
182
- else
183
- @results[:database].first(5).each do |db|
184
- puts " ⚠ #{db[:table]}: #{db[:issue]}"
185
- end
186
- puts " ... and #{@results[:database].count - 5} more" if @results[:database].count > 5
187
- end
188
- puts ""
189
-
190
- # Specs
191
- missing_specs = @results[:specs].select { |s| s[:has_spec] == false }
192
- puts "Specs:"
193
- if missing_specs.empty?
194
- puts " ✓ All components have specs"
195
- else
196
- puts " ⚠ #{missing_specs.count} missing specs:"
197
- missing_specs.first(5).each do |spec|
198
- puts " - #{spec[:type]}: #{spec[:name]}"
199
- end
200
- puts " ... and #{missing_specs.count - 5} more" if missing_specs.count > 5
201
- end
202
- puts ""
203
-
204
- # Suggestions
205
- puts "Suggestions:"
206
- if @results[:refactors].empty?
207
- puts " ✓ No refactoring suggestions"
208
- else
209
- @results[:refactors].first(10).each do |suggestion|
210
- puts " • #{suggestion}"
211
- end
212
- puts " ... and #{@results[:refactors].count - 10} more" if @results[:refactors].count > 10
213
- end
214
- puts ""
215
- end
216
-
217
- # Find Rails app path
218
- # @return [String, nil] Rails app root path
219
- def find_rails_app_path
220
- path = Dir.pwd
221
- max_depth = 10
222
-
223
- max_depth.times do
224
- return path if File.exist?(File.join(path, "config", "application.rb"))
225
- parent = File.dirname(path)
226
- break if parent == path
227
- path = parent
228
- end
229
-
230
- nil
231
- end
232
-
233
- # Colorize output for terminal
234
- # @param text [String] Text to colorize
235
- # @param color [Symbol] Color name
236
- # @return [String] Colorized text
237
- def colorize(text, color)
238
- colors = {
239
- green: "\e[32m",
240
- yellow: "\e[33m",
241
- red: "\e[31m",
242
- reset: "\e[0m"
243
- }
244
- "#{colors[color]}#{text}#{colors[:reset]}"
245
- end
246
-
247
- # Error class for doctor issues
248
- class DoctorError < StandardError; end
249
76
  end
250
77
  end
@@ -0,0 +1,128 @@
1
+ # Formatter — RailsForge v2 output renderer
2
+ #
3
+ # Responsibility: take raw results and produce human-readable or
4
+ # machine-readable output. No analysis, no scoring, no side effects.
5
+ #
6
+ # Principle: Doctor decides what is true. Formatter decides how it looks.
7
+
8
+ require "json"
9
+
10
+ module RailsForge
11
+ module Formatter
12
+ SEVERITY_ORDER = %w[critical high medium low info].freeze
13
+
14
+ # Human-readable health report (from doctor).
15
+ #
16
+ # @param issues [Array<RailsForge::Issue>]
17
+ # @param score [Integer] 0–100
18
+ # @return [String]
19
+ def self.render_text(issues, score)
20
+ lines = []
21
+ score_color = score >= 80 ? :green : score >= 60 ? :yellow : :red
22
+
23
+ lines << "RailsForge Health Report"
24
+ lines << "─" * 50
25
+ lines << "Score: #{colorize("#{score}/100", score_color)}"
26
+ lines << "Issues: #{issues.count} total"
27
+ lines << ""
28
+ lines.concat(render_issues(issues))
29
+ lines.join("\n")
30
+ end
31
+
32
+ # Human-readable diff report (from diff command).
33
+ # Shows only new issues; no score.
34
+ #
35
+ # @param new_issues [Array<RailsForge::Issue>]
36
+ # @return [String]
37
+ def self.render_diff(new_issues)
38
+ lines = []
39
+ lines << "RailsForge Diff"
40
+ lines << "─" * 50
41
+ lines << "New issues: #{new_issues.count}"
42
+ lines << ""
43
+ lines.concat(render_issues(new_issues))
44
+ lines.join("\n")
45
+ end
46
+
47
+ # Machine-readable JSON report.
48
+ #
49
+ # @param issues [Array<RailsForge::Issue>]
50
+ # @param score [Integer] 0–100
51
+ # @param metrics [Hash]
52
+ # @return [String] pretty-printed JSON
53
+ def self.render_json(issues, score, metrics = {})
54
+ counts = issues.group_by { |i| i.severity.to_s }.transform_values(&:count)
55
+
56
+ # All severity keys always present and in defined order, even if zero.
57
+ by_severity = SEVERITY_ORDER.each_with_object({}) { |sev, h| h[sev] = counts.fetch(sev, 0) }
58
+
59
+ output = {
60
+ score: score,
61
+ issues: issues.map(&:to_h),
62
+ summary: {
63
+ total: issues.count,
64
+ by_severity: by_severity
65
+ },
66
+ metrics: metrics
67
+ }
68
+
69
+ JSON.pretty_generate(output)
70
+ end
71
+
72
+ # private helpers
73
+
74
+ # Renders severity-grouped issue list. Shared by render_text and render_diff.
75
+ #
76
+ # @param issues [Array<RailsForge::Issue>]
77
+ # @return [Array<String>] lines (no trailing newline join — caller decides)
78
+ def self.render_issues(issues)
79
+ lines = []
80
+ grouped = issues.group_by { |i| i.severity.to_s }
81
+ any_issues = false
82
+
83
+ SEVERITY_ORDER.each do |sev|
84
+ sev_issues = grouped[sev]
85
+ next unless sev_issues&.any?
86
+
87
+ any_issues = true
88
+ lines << colorize("#{sev.upcase} (#{sev_issues.count})", severity_color(sev))
89
+
90
+ sev_issues.group_by(&:analyzer).each do |analyzer, group|
91
+ lines << " [#{analyzer}]"
92
+ group.each do |issue|
93
+ loc = issue.file ? "#{issue.file}#{issue.line ? ":#{issue.line}" : ""}" : nil
94
+ lines << " #{[loc, issue.message].compact.join(" — ")}"
95
+ lines << " → #{issue.suggestion}" if issue.suggestion
96
+ end
97
+ end
98
+ lines << ""
99
+ end
100
+
101
+ unless any_issues
102
+ lines << colorize("✓ No new issues", :green)
103
+ lines << ""
104
+ end
105
+
106
+ lines
107
+ end
108
+ private_class_method :render_issues
109
+
110
+ def self.colorize(text, color)
111
+ codes = { green: "\e[32m", yellow: "\e[33m", red: "\e[31m",
112
+ cyan: "\e[36m", magenta: "\e[35m", reset: "\e[0m" }
113
+ "#{codes[color]}#{text}#{codes[:reset]}"
114
+ end
115
+ private_class_method :colorize
116
+
117
+ def self.severity_color(sev)
118
+ case sev
119
+ when "critical" then :magenta
120
+ when "high" then :red
121
+ when "medium" then :yellow
122
+ when "low" then :cyan
123
+ else :reset
124
+ end
125
+ end
126
+ private_class_method :severity_color
127
+ end
128
+ end
@@ -0,0 +1,23 @@
1
+ # Unified Issue model for RailsForge v2
2
+ # All analyzers return Array<RailsForge::Issue>
3
+
4
+ module RailsForge
5
+ Issue = Struct.new(:analyzer, :type, :severity, :file, :line, :message, :suggestion, keyword_init: true) do
6
+ def to_h
7
+ {
8
+ analyzer: analyzer,
9
+ type: type,
10
+ severity: severity || "info",
11
+ file: file,
12
+ line: line,
13
+ message: message,
14
+ suggestion: suggestion
15
+ }
16
+ end
17
+
18
+ def to_json(*)
19
+ require "json"
20
+ to_h.to_json
21
+ end
22
+ end
23
+ end
@@ -1,60 +1,16 @@
1
- # RailsForge modular loader
2
- # This file centralizes all require statements with lazy loading
1
+ # RailsForge loader
2
+ # Loads only what is needed for the v2 doctor pipeline.
3
3
 
4
4
  require "fileutils"
5
5
 
6
- # Get the lib directory path
7
- LIB_DIR = File.dirname(__FILE__)
8
-
9
- # Core modules - always needed
10
6
  require_relative "version"
11
-
12
- # Minimal CLI loader for --help and --version (fast)
7
+ require_relative "issue"
8
+ require_relative "formatter"
13
9
  require_relative "cli_minimal"
14
10
 
15
- # Lazy-load everything else
16
11
  module RailsForge
17
12
  class << self
18
- # Lazy-load generators
19
- def require_generator(name)
20
- require_relative "generators/#{name}"
21
- end
22
-
23
- # Lazy-load analyzers
24
- def require_analyzer(name)
25
- require_relative "analyzers/#{name}"
26
- end
27
-
28
- # Lazy-load other modules
29
- def require_module(name)
30
- require_relative name
31
- end
32
-
33
- # Preload all (for commands that need everything)
34
13
  def preload_all!
35
- require_relative "profiles"
36
- require_relative "template_loader"
37
-
38
- # Define Generators module first to ensure proper namespace
39
- require_relative "generators/base_generator"
40
-
41
- # Generators
42
- require_relative "generators/service_generator"
43
- require_relative "generators/job_generator"
44
- require_relative "generators/query_generator"
45
- require_relative "generators/form_generator"
46
- require_relative "generators/presenter_generator"
47
- require_relative "generators/policy_generator"
48
- require_relative "generators/serializer_generator"
49
- require_relative "generators/api_generator"
50
- require_relative "generators/view_component_generator"
51
- require_relative "generators/stimulus_controller_generator"
52
- require_relative "generators/demo_generator"
53
- require_relative "generators/devops_generator"
54
- require_relative "generators/monitoring_generator"
55
- require_relative "generators/test_generator"
56
-
57
- # Analyzers
58
14
  require_relative "analyzers/base_analyzer"
59
15
  require_relative "analyzers/controller_analyzer"
60
16
  require_relative "analyzers/model_analyzer"
@@ -64,23 +20,8 @@ module RailsForge
64
20
  require_relative "analyzers/refactor_analyzer"
65
21
  require_relative "analyzers/security_analyzer"
66
22
  require_relative "analyzers/performance_analyzer"
67
-
68
- # Refactors & plugins
69
- require_relative "refactors/refactor_controller"
70
- require_relative "plugins"
71
-
72
- # Legacy (modules with class methods)
73
- require_relative "mailer_generator"
74
- require_relative "feature_generator"
75
- require_relative "audit"
76
23
  require_relative "doctor"
77
- require_relative "graph"
78
- require_relative "wizard"
79
- require_relative "wizard_tui"
80
- require_relative "config"
81
- require_relative "generator"
82
-
83
- # CLI
24
+ require_relative "diff"
84
25
  require_relative "cli"
85
26
  end
86
27
  end