railsforge 1.0.2 → 2.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/README.md +105 -444
- data/lib/railsforge/analyzers/controller_analyzer.rb +29 -55
- data/lib/railsforge/analyzers/database_analyzer.rb +16 -30
- data/lib/railsforge/analyzers/metrics_analyzer.rb +8 -22
- data/lib/railsforge/analyzers/model_analyzer.rb +29 -46
- data/lib/railsforge/analyzers/performance_analyzer.rb +34 -94
- data/lib/railsforge/analyzers/refactor_analyzer.rb +77 -57
- data/lib/railsforge/analyzers/security_analyzer.rb +34 -91
- data/lib/railsforge/analyzers/spec_analyzer.rb +17 -31
- data/lib/railsforge/cli.rb +14 -650
- data/lib/railsforge/cli_minimal.rb +8 -55
- data/lib/railsforge/doctor.rb +52 -225
- data/lib/railsforge/formatter.rb +102 -0
- data/lib/railsforge/issue.rb +23 -0
- data/lib/railsforge/loader.rb +4 -64
- data/lib/railsforge/version.rb +1 -1
- metadata +14 -82
- data/lib/railsforge/api_generator.rb +0 -397
- data/lib/railsforge/audit.rb +0 -289
- data/lib/railsforge/config.rb +0 -181
- data/lib/railsforge/database_analyzer.rb +0 -300
- data/lib/railsforge/feature_generator.rb +0 -560
- data/lib/railsforge/generator.rb +0 -313
- data/lib/railsforge/generators/api_generator.rb +0 -392
- data/lib/railsforge/generators/base_generator.rb +0 -75
- data/lib/railsforge/generators/demo_generator.rb +0 -307
- data/lib/railsforge/generators/devops_generator.rb +0 -287
- data/lib/railsforge/generators/form_generator.rb +0 -180
- data/lib/railsforge/generators/job_generator.rb +0 -176
- data/lib/railsforge/generators/monitoring_generator.rb +0 -134
- data/lib/railsforge/generators/policy_generator.rb +0 -220
- data/lib/railsforge/generators/presenter_generator.rb +0 -173
- data/lib/railsforge/generators/query_generator.rb +0 -174
- data/lib/railsforge/generators/serializer_generator.rb +0 -166
- data/lib/railsforge/generators/service_generator.rb +0 -122
- data/lib/railsforge/generators/stimulus_controller_generator.rb +0 -129
- data/lib/railsforge/generators/test_generator.rb +0 -289
- data/lib/railsforge/generators/view_component_generator.rb +0 -169
- data/lib/railsforge/graph.rb +0 -270
- data/lib/railsforge/mailer_generator.rb +0 -191
- data/lib/railsforge/plugins/plugin_loader.rb +0 -60
- data/lib/railsforge/plugins.rb +0 -30
- data/lib/railsforge/profiles.rb +0 -99
- data/lib/railsforge/refactor_analyzer.rb +0 -401
- data/lib/railsforge/refactor_controller.rb +0 -277
- data/lib/railsforge/refactors/refactor_controller.rb +0 -117
- data/lib/railsforge/template_loader.rb +0 -105
- data/lib/railsforge/templates/v1/form/spec_template.rb +0 -18
- data/lib/railsforge/templates/v1/form/template.rb +0 -28
- data/lib/railsforge/templates/v1/job/spec_template.rb +0 -17
- data/lib/railsforge/templates/v1/job/template.rb +0 -13
- data/lib/railsforge/templates/v1/policy/spec_template.rb +0 -41
- data/lib/railsforge/templates/v1/policy/template.rb +0 -57
- data/lib/railsforge/templates/v1/presenter/spec_template.rb +0 -12
- data/lib/railsforge/templates/v1/presenter/template.rb +0 -13
- data/lib/railsforge/templates/v1/query/spec_template.rb +0 -12
- data/lib/railsforge/templates/v1/query/template.rb +0 -16
- data/lib/railsforge/templates/v1/serializer/spec_template.rb +0 -13
- data/lib/railsforge/templates/v1/serializer/template.rb +0 -11
- data/lib/railsforge/templates/v1/service/spec_template.rb +0 -12
- data/lib/railsforge/templates/v1/service/template.rb +0 -25
- data/lib/railsforge/templates/v1/stimulus_controller/template.rb +0 -35
- data/lib/railsforge/templates/v1/view_component/template.rb +0 -24
- data/lib/railsforge/templates/v2/job/template.rb +0 -49
- data/lib/railsforge/templates/v2/query/template.rb +0 -66
- data/lib/railsforge/templates/v2/service/spec_template.rb +0 -33
- data/lib/railsforge/templates/v2/service/template.rb +0 -71
- data/lib/railsforge/templates/v3/job/template.rb +0 -72
- data/lib/railsforge/templates/v3/query/spec_template.rb +0 -54
- data/lib/railsforge/templates/v3/query/template.rb +0 -115
- data/lib/railsforge/templates/v3/service/spec_template.rb +0 -51
- data/lib/railsforge/templates/v3/service/template.rb +0 -93
- data/lib/railsforge/wizard.rb +0 -265
- data/lib/railsforge/wizard_tui.rb +0 -286
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# Minimal CLI
|
|
2
|
-
#
|
|
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,22 @@ 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 "
|
|
57
|
-
require_relative "wizard"
|
|
58
|
-
require_relative "wizard_tui"
|
|
59
|
-
require_relative "config"
|
|
28
|
+
require_relative "cli"
|
|
60
29
|
|
|
61
|
-
# Dispatch to full CLI
|
|
62
30
|
CLI.start([command] + args)
|
|
63
31
|
end
|
|
64
32
|
end
|
|
65
33
|
|
|
66
34
|
def self.display_help
|
|
67
35
|
puts <<~HELP
|
|
68
|
-
RailsForge
|
|
36
|
+
RailsForge — Rails application health diagnostics
|
|
69
37
|
|
|
70
38
|
Usage:
|
|
71
|
-
railsforge
|
|
72
|
-
railsforge
|
|
73
|
-
railsforge
|
|
74
|
-
railsforge
|
|
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
|
|
39
|
+
railsforge doctor Run full diagnostic scan
|
|
40
|
+
railsforge doctor --format=json Output as JSON
|
|
41
|
+
railsforge --version Show version
|
|
42
|
+
railsforge --help Show this help
|
|
90
43
|
HELP
|
|
91
44
|
end
|
|
92
45
|
end
|
data/lib/railsforge/doctor.rb
CHANGED
|
@@ -1,250 +1,77 @@
|
|
|
1
|
-
# Doctor
|
|
2
|
-
#
|
|
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
|
-
|
|
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
|
-
@
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
23
|
-
#
|
|
40
|
+
# Run all analyzers and return raw result.
|
|
41
|
+
#
|
|
42
|
+
# @return [Hash] { score: Integer, issues: Array<Issue>, metrics: Hash }
|
|
24
43
|
def run
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
collect_issues
|
|
45
|
+
collect_metrics
|
|
27
46
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
@
|
|
61
|
+
@errors[name] = e.message
|
|
83
62
|
end
|
|
63
|
+
end
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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,102 @@
|
|
|
1
|
+
# Formatter — RailsForge v2 output renderer
|
|
2
|
+
#
|
|
3
|
+
# Responsibility: take raw Doctor 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 text report
|
|
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
|
+
|
|
29
|
+
grouped = issues.group_by { |i| i.severity.to_s }
|
|
30
|
+
any_issues = false
|
|
31
|
+
|
|
32
|
+
SEVERITY_ORDER.each do |sev|
|
|
33
|
+
sev_issues = grouped[sev]
|
|
34
|
+
next unless sev_issues&.any?
|
|
35
|
+
|
|
36
|
+
any_issues = true
|
|
37
|
+
lines << colorize("#{sev.upcase} (#{sev_issues.count})", severity_color(sev))
|
|
38
|
+
|
|
39
|
+
sev_issues.group_by(&:analyzer).each do |analyzer, group|
|
|
40
|
+
lines << " [#{analyzer}]"
|
|
41
|
+
group.each do |issue|
|
|
42
|
+
loc = issue.file ? "#{issue.file}#{issue.line ? ":#{issue.line}" : ""}" : nil
|
|
43
|
+
lines << " #{[loc, issue.message].compact.join(" — ")}"
|
|
44
|
+
lines << " → #{issue.suggestion}" if issue.suggestion
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
lines << ""
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
unless any_issues
|
|
51
|
+
lines << colorize("✓ No issues found", :green)
|
|
52
|
+
lines << ""
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
lines.join("\n")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Machine-readable JSON report
|
|
59
|
+
#
|
|
60
|
+
# @param issues [Array<RailsForge::Issue>]
|
|
61
|
+
# @param score [Integer] 0–100
|
|
62
|
+
# @param metrics [Hash]
|
|
63
|
+
# @return [String] pretty-printed JSON
|
|
64
|
+
def self.render_json(issues, score, metrics = {})
|
|
65
|
+
by_severity = issues
|
|
66
|
+
.group_by { |i| i.severity.to_s }
|
|
67
|
+
.transform_values(&:count)
|
|
68
|
+
|
|
69
|
+
output = {
|
|
70
|
+
score: score,
|
|
71
|
+
issues: issues.map(&:to_h),
|
|
72
|
+
summary: {
|
|
73
|
+
total: issues.count,
|
|
74
|
+
by_severity: by_severity
|
|
75
|
+
},
|
|
76
|
+
metrics: metrics
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
JSON.pretty_generate(output)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# private helpers
|
|
83
|
+
|
|
84
|
+
def self.colorize(text, color)
|
|
85
|
+
codes = { green: "\e[32m", yellow: "\e[33m", red: "\e[31m",
|
|
86
|
+
cyan: "\e[36m", magenta: "\e[35m", reset: "\e[0m" }
|
|
87
|
+
"#{codes[color]}#{text}#{codes[:reset]}"
|
|
88
|
+
end
|
|
89
|
+
private_class_method :colorize
|
|
90
|
+
|
|
91
|
+
def self.severity_color(sev)
|
|
92
|
+
case sev
|
|
93
|
+
when "critical" then :magenta
|
|
94
|
+
when "high" then :red
|
|
95
|
+
when "medium" then :yellow
|
|
96
|
+
when "low" then :cyan
|
|
97
|
+
else :reset
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
private_class_method :severity_color
|
|
101
|
+
end
|
|
102
|
+
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
|
data/lib/railsforge/loader.rb
CHANGED
|
@@ -1,60 +1,16 @@
|
|
|
1
|
-
# RailsForge
|
|
2
|
-
#
|
|
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
|
-
|
|
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,7 @@ 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
|
|
84
24
|
require_relative "cli"
|
|
85
25
|
end
|
|
86
26
|
end
|
data/lib/railsforge/version.rb
CHANGED