railsforge 1.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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +528 -0
  4. data/bin/railsforge +8 -0
  5. data/lib/railsforge/analyzers/base_analyzer.rb +41 -0
  6. data/lib/railsforge/analyzers/controller_analyzer.rb +83 -0
  7. data/lib/railsforge/analyzers/database_analyzer.rb +55 -0
  8. data/lib/railsforge/analyzers/metrics_analyzer.rb +55 -0
  9. data/lib/railsforge/analyzers/model_analyzer.rb +74 -0
  10. data/lib/railsforge/analyzers/performance_analyzer.rb +161 -0
  11. data/lib/railsforge/analyzers/refactor_analyzer.rb +118 -0
  12. data/lib/railsforge/analyzers/security_analyzer.rb +169 -0
  13. data/lib/railsforge/analyzers/spec_analyzer.rb +58 -0
  14. data/lib/railsforge/api_generator.rb +397 -0
  15. data/lib/railsforge/audit.rb +289 -0
  16. data/lib/railsforge/cli.rb +671 -0
  17. data/lib/railsforge/config.rb +181 -0
  18. data/lib/railsforge/database_analyzer.rb +300 -0
  19. data/lib/railsforge/doctor.rb +250 -0
  20. data/lib/railsforge/feature_generator.rb +560 -0
  21. data/lib/railsforge/generator.rb +313 -0
  22. data/lib/railsforge/generators/base_generator.rb +70 -0
  23. data/lib/railsforge/generators/demo_generator.rb +307 -0
  24. data/lib/railsforge/generators/devops_generator.rb +287 -0
  25. data/lib/railsforge/generators/monitoring_generator.rb +134 -0
  26. data/lib/railsforge/generators/service_generator.rb +122 -0
  27. data/lib/railsforge/generators/stimulus_controller_generator.rb +129 -0
  28. data/lib/railsforge/generators/test_generator.rb +289 -0
  29. data/lib/railsforge/generators/view_component_generator.rb +169 -0
  30. data/lib/railsforge/graph.rb +270 -0
  31. data/lib/railsforge/loader.rb +56 -0
  32. data/lib/railsforge/mailer_generator.rb +191 -0
  33. data/lib/railsforge/plugins/plugin_loader.rb +60 -0
  34. data/lib/railsforge/plugins.rb +30 -0
  35. data/lib/railsforge/profiles/admin_app.yml +49 -0
  36. data/lib/railsforge/profiles/api_only.yml +47 -0
  37. data/lib/railsforge/profiles/blog.yml +47 -0
  38. data/lib/railsforge/profiles/standard.yml +44 -0
  39. data/lib/railsforge/profiles.rb +99 -0
  40. data/lib/railsforge/refactor_analyzer.rb +401 -0
  41. data/lib/railsforge/refactor_controller.rb +277 -0
  42. data/lib/railsforge/refactors/refactor_controller.rb +117 -0
  43. data/lib/railsforge/template_loader.rb +105 -0
  44. data/lib/railsforge/templates/v1/form/spec_template.rb +18 -0
  45. data/lib/railsforge/templates/v1/form/template.rb +28 -0
  46. data/lib/railsforge/templates/v1/job/spec_template.rb +17 -0
  47. data/lib/railsforge/templates/v1/job/template.rb +13 -0
  48. data/lib/railsforge/templates/v1/policy/spec_template.rb +41 -0
  49. data/lib/railsforge/templates/v1/policy/template.rb +57 -0
  50. data/lib/railsforge/templates/v1/presenter/spec_template.rb +12 -0
  51. data/lib/railsforge/templates/v1/presenter/template.rb +13 -0
  52. data/lib/railsforge/templates/v1/query/spec_template.rb +12 -0
  53. data/lib/railsforge/templates/v1/query/template.rb +16 -0
  54. data/lib/railsforge/templates/v1/serializer/spec_template.rb +13 -0
  55. data/lib/railsforge/templates/v1/serializer/template.rb +11 -0
  56. data/lib/railsforge/templates/v1/service/spec_template.rb +12 -0
  57. data/lib/railsforge/templates/v1/service/template.rb +25 -0
  58. data/lib/railsforge/templates/v1/stimulus_controller/template.rb +35 -0
  59. data/lib/railsforge/templates/v1/view_component/template.rb +24 -0
  60. data/lib/railsforge/templates/v2/job/template.rb +49 -0
  61. data/lib/railsforge/templates/v2/query/template.rb +66 -0
  62. data/lib/railsforge/templates/v2/service/spec_template.rb +33 -0
  63. data/lib/railsforge/templates/v2/service/template.rb +71 -0
  64. data/lib/railsforge/templates/v3/job/template.rb +72 -0
  65. data/lib/railsforge/templates/v3/query/spec_template.rb +54 -0
  66. data/lib/railsforge/templates/v3/query/template.rb +115 -0
  67. data/lib/railsforge/templates/v3/service/spec_template.rb +51 -0
  68. data/lib/railsforge/templates/v3/service/template.rb +84 -0
  69. data/lib/railsforge/version.rb +5 -0
  70. data/lib/railsforge/wizard.rb +265 -0
  71. data/lib/railsforge/wizard_tui.rb +286 -0
  72. data/lib/railsforge.rb +13 -0
  73. metadata +216 -0
@@ -0,0 +1,55 @@
1
+ # Database analyzer for RailsForge
2
+ # Scans database schema for issues
3
+
4
+ require_relative 'base_analyzer'
5
+
6
+ module RailsForge
7
+ module Analyzers
8
+ # DatabaseAnalyzer scans database/schema for issues
9
+ class DatabaseAnalyzer < BaseAnalyzer
10
+ class DatabaseError < StandardError; end
11
+
12
+ # Analyze database
13
+ def self.analyze(base_path = nil)
14
+ base_path ||= find_rails_app_path
15
+ raise DatabaseError, "Not in a Rails application" unless base_path
16
+
17
+ results = []
18
+ schema_file = File.join(base_path, "db", "schema.rb")
19
+
20
+ return results unless File.exist?(schema_file)
21
+
22
+ content = File.read(schema_file)
23
+ tables = content.scan(/create_table\s+"(\w+)"/).flatten
24
+
25
+ tables.each do |table|
26
+ table_section = content[/create_table\s+"#{table}".*?(?=create_table|\z)/m]
27
+ if table_section && table_section.include?("t.datetime")
28
+ results << {
29
+ type: :index,
30
+ table: table,
31
+ issue: "Consider adding index on datetime columns",
32
+ suggestion: "add_index :#{table}, :created_at"
33
+ }
34
+ end
35
+ end
36
+
37
+ results
38
+ end
39
+
40
+ def self.print_report(results)
41
+ puts "\nDatabase Analysis"
42
+ puts "-" * 40
43
+
44
+ if results.empty?
45
+ puts "✓ No issues found"
46
+ return
47
+ end
48
+
49
+ results.each do |result|
50
+ puts "⚠ #{result[:table]}: #{result[:issue]}"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ # Metrics analyzer for RailsForge
2
+ # Provides comprehensive metrics analysis
3
+
4
+ require_relative 'base_analyzer'
5
+
6
+ module RailsForge
7
+ module Analyzers
8
+ # MetricsAnalyzer provides code metrics
9
+ class MetricsAnalyzer < BaseAnalyzer
10
+ GENERATOR_TYPES = %w[services queries forms presenters policies serializers jobs].freeze
11
+
12
+ # Analyze metrics
13
+ def self.analyze(base_path = nil)
14
+ base_path ||= find_rails_app_path
15
+ return {} unless base_path
16
+
17
+ {
18
+ generator_counts: count_files(base_path),
19
+ total_files: count_all_files(base_path),
20
+ services_count: count_in_folder(base_path, "app/services"),
21
+ queries_count: count_in_folder(base_path, "app/queries"),
22
+ jobs_count: count_in_folder(base_path, "app/jobs")
23
+ }
24
+ end
25
+
26
+ def self.count_files(base_path)
27
+ counts = {}
28
+ GENERATOR_TYPES.each do |type|
29
+ folder = "app/#{type.chop}"
30
+ folder = "app/jobs" if type == "jobs"
31
+ counts[type] = count_in_folder(base_path, folder)
32
+ end
33
+ counts
34
+ end
35
+
36
+ def self.count_in_folder(base_path, folder)
37
+ path = File.join(base_path, folder)
38
+ Dir.exist?(path) ? Dir.glob(File.join(path, "**", "*.rb")).count : 0
39
+ end
40
+
41
+ def self.count_all_files(base_path)
42
+ Dir.glob(File.join(base_path, "app", "**", "*.rb")).count
43
+ end
44
+
45
+ def self.print_results(results)
46
+ puts "\nMetrics Analysis"
47
+ puts "-" * 40
48
+ puts "Total Files: #{results[:total_files]}"
49
+ puts "Services: #{results[:services_count]}"
50
+ puts "Queries: #{results[:queries_count]}"
51
+ puts "Jobs: #{results[:jobs_count]}"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,74 @@
1
+ # Model analyzer for RailsForge
2
+ # Scans models for code quality issues
3
+
4
+ require_relative 'base_analyzer'
5
+
6
+ module RailsForge
7
+ module Analyzers
8
+ # ModelAnalyzer scans models for issues
9
+ class ModelAnalyzer < BaseAnalyzer
10
+ MAX_METHODS = 15
11
+ MAX_LINES = 200
12
+
13
+ # Analyze models
14
+ def self.analyze(base_path = nil)
15
+ base_path ||= find_rails_app_path
16
+ return [] unless base_path
17
+
18
+ models_dir = File.join(base_path, "app", "models")
19
+ return [] unless Dir.exist?(models_dir)
20
+
21
+ results = []
22
+ Dir.glob(File.join(models_dir, "**", "*.rb")).each do |file|
23
+ next if file.end_with?("_application.rb")
24
+
25
+ content = File.read(file)
26
+ lines = content.lines.count
27
+ methods = content.scan(/def \w+/).count
28
+
29
+ issues = []
30
+ issues << "Exceeds #{MAX_LINES} lines" if lines > MAX_LINES
31
+ issues << "Has #{methods} methods" if methods > MAX_METHODS
32
+
33
+ if issues.any?
34
+ results << {
35
+ file: File.basename(file),
36
+ path: file,
37
+ lines: lines,
38
+ methods: methods,
39
+ issues: issues
40
+ }
41
+ end
42
+ end
43
+
44
+ results
45
+ end
46
+
47
+ def self.print_results(results)
48
+ puts "\nModel Analysis"
49
+ puts "-" * 40
50
+
51
+ if results.empty?
52
+ puts "✓ No issues found"
53
+ return
54
+ end
55
+
56
+ results.each do |result|
57
+ puts "⚠ #{result[:file]}"
58
+ result[:issues].each { |issue| puts " - #{issue}" }
59
+ end
60
+ end
61
+
62
+ def self.find_rails_app_path
63
+ path = Dir.pwd
64
+ 10.times do
65
+ return path if File.exist?(File.join(path, "config", "application.rb"))
66
+ parent = File.dirname(path)
67
+ break if parent == path
68
+ path = parent
69
+ end
70
+ nil
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,161 @@
1
+ # Performance analyzer for RailsForge
2
+ # Checks for common performance issues
3
+
4
+ require 'fileutils'
5
+
6
+ module RailsForge
7
+ module Analyzers
8
+ # PerformanceAnalyzer scans code for performance problems
9
+ class PerformanceAnalyzer < BaseAnalyzer
10
+ # Performance issue types
11
+ ISSUE_TYPES = {
12
+ n_plus_one: "N+1 Query",
13
+ missing_index: "Missing Database Index",
14
+ slow_method: "Slow Method",
15
+ inefficient_query: "Inefficient Query",
16
+ cache_miss: "Cache Miss",
17
+ eager_loading: "Unnecessary Eager Loading"
18
+ }.freeze
19
+
20
+ # Analyze Rails app for performance issues
21
+ # @param base_path [String] Rails app root path
22
+ # @return [Array<Hash>] Performance issues found
23
+ def self.analyze(base_path = nil)
24
+ base_path ||= find_rails_app_path
25
+ return [] unless base_path
26
+
27
+ new(base_path).find_issues
28
+ end
29
+
30
+ # Initialize analyzer
31
+ # @param base_path [String] Rails app root path
32
+ def initialize(base_path)
33
+ @base_path = base_path
34
+ @issues = []
35
+ end
36
+
37
+ # Find all performance issues
38
+ # @return [Array<Hash>] Issues found
39
+ def find_issues
40
+ check_n_plus_one
41
+ check_missing_indexes
42
+ check_slow_methods
43
+ check_inefficient_queries
44
+
45
+ @issues
46
+ end
47
+
48
+ # Print performance report
49
+ # @param issues [Array<Hash>] Issues to print
50
+ def self.print_report(issues)
51
+ puts "Performance Analysis"
52
+ puts "=" * 50
53
+
54
+ if issues.empty?
55
+ puts "✓ No performance issues found"
56
+ return
57
+ end
58
+
59
+ puts "Found #{issues.count} performance issue(s):"
60
+ puts ""
61
+
62
+ issues.each do |issue|
63
+ puts "⚠ #{issue[:type]}"
64
+ puts " File: #{issue[:file]}"
65
+ puts " Line: #{issue[:line]}"
66
+ puts " Issue: #{issue[:message]}"
67
+ puts ""
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ # Check for N+1 query problems
74
+ def check_n_plus_one
75
+ # Look for loops with database queries inside
76
+ patterns = [
77
+ /\.each\s+do.*\.find/,
78
+ /\.map.*\.where/,
79
+ /@.*\.all\.each/
80
+ ]
81
+
82
+ scan_files("app/controllers/**/*.rb", patterns, :n_plus_one)
83
+ scan_files("app/views/**/*.erb", patterns, :n_plus_one)
84
+ end
85
+
86
+ # Check for missing database indexes
87
+ def check_missing_indexes
88
+ # Look for foreign keys without indexes
89
+ patterns = [
90
+ /belongs_to\s+:\w+/,
91
+ /add_reference/
92
+ ]
93
+
94
+ scan_files("db/migrate/**/*.rb", patterns, :missing_index)
95
+ end
96
+
97
+ # Check for slow methods
98
+ def check_slow_methods
99
+ # Look for potentially slow operations
100
+ patterns = [
101
+ /\.each\s+do\s*\n.*\.save/,
102
+ /Process\.spawn/,
103
+ /\.read/,
104
+ /\.open\(.*\)/
105
+ ]
106
+
107
+ scan_files("app/**/*.rb", patterns, :slow_method)
108
+ end
109
+
110
+ # Check for inefficient queries
111
+ def check_inefficient_queries
112
+ # Look for inefficient query patterns
113
+ patterns = [
114
+ /\.order\(.*\)\.last/,
115
+ /\.where\(.*\)\.first\! /,
116
+ /\.all\.to_a/,
117
+ /\.select\(.+\)\.map\//
118
+ ]
119
+
120
+ scan_files("app/models/**/*.rb", patterns, :inefficient_query)
121
+ scan_files("app/controllers/**/*.rb", patterns, :inefficient_query)
122
+ end
123
+
124
+ # Scan files for patterns
125
+ def scan_files(glob, patterns, issue_type)
126
+ Dir.glob(File.join(@base_path, glob)).each do |file|
127
+ content = File.read(file)
128
+ lines = content.lines
129
+
130
+ lines.each_with_index do |line, index|
131
+ patterns.each do |pattern|
132
+ if line =~ pattern
133
+ @issues << {
134
+ type: ISSUE_TYPES[issue_type],
135
+ file: file.gsub(@base_path, ""),
136
+ line: index + 1,
137
+ message: line.strip,
138
+ severity: severity_for(issue_type)
139
+ }
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ # Get severity for issue type
147
+ def severity_for(issue_type)
148
+ case issue_type
149
+ when :n_plus_one
150
+ "high"
151
+ when :missing_index
152
+ "high"
153
+ when :inefficient_query
154
+ "medium"
155
+ else
156
+ "low"
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,118 @@
1
+ # Refactor analyzer for RailsForge
2
+ # Provides refactoring suggestions
3
+
4
+ require_relative 'base_analyzer'
5
+
6
+ module RailsForge
7
+ module Analyzers
8
+ # RefactorAnalyzer provides refactoring suggestions
9
+ class RefactorAnalyzer < BaseAnalyzer
10
+ class RefactorError < StandardError; end
11
+
12
+ CONTROLLER_MAX_LINES = 150
13
+ CONTROLLER_MAX_METHODS = 10
14
+ MODEL_MAX_LINES = 200
15
+ MODEL_MAX_METHOD_LINES = 15
16
+
17
+ # Analyze controllers
18
+ def self.analyze_controllers(base_path = nil)
19
+ base_path ||= find_rails_app_path
20
+ raise RefactorError, "Not in a Rails app" unless base_path
21
+
22
+ controllers_dir = File.join(base_path, "app", "controllers")
23
+ return [] unless Dir.exist?(controllers_dir)
24
+
25
+ results = []
26
+ Dir.glob(File.join(controllers_dir, "**", "*_controller.rb")).each do |file|
27
+ result = analyze_file(file, :controller)
28
+ results << result if result[:needs_refactoring]
29
+ end
30
+ results
31
+ end
32
+
33
+ # Analyze models
34
+ def self.analyze_models(base_path = nil)
35
+ base_path ||= find_rails_app_path
36
+ raise RefactorError, "Not in a Rails app" unless base_path
37
+
38
+ models_dir = File.join(base_path, "app", "models")
39
+ return [] unless Dir.exist?(models_dir)
40
+
41
+ results = []
42
+ Dir.glob(File.join(models_dir, "**", "*.rb")).each do |file|
43
+ next if file.end_with?("_application.rb")
44
+ result = analyze_file(file, :model)
45
+ results << result if result[:needs_refactoring]
46
+ end
47
+ results
48
+ end
49
+
50
+ def self.analyze_file(file_path, type)
51
+ content = File.read(file_path)
52
+ lines = content.lines.count
53
+ methods = extract_methods(content)
54
+
55
+ issues = []
56
+ suggestions = []
57
+
58
+ if type == :controller
59
+ issues << "Exceeds #{CONTROLLER_MAX_LINES} lines" if lines > CONTROLLER_MAX_LINES
60
+ issues << "Has #{methods.count} methods" if methods.count > CONTROLLER_MAX_METHODS
61
+ suggestions << "Consider moving business logic to Service object" if lines > CONTROLLER_MAX_LINES
62
+ else
63
+ issues << "Exceeds #{MODEL_MAX_LINES} lines" if lines > MODEL_MAX_LINES
64
+ end
65
+
66
+ methods.each do |method|
67
+ suggestions << "Method `#{method[:name]}` has #{method[:lines]} lines - consider extracting" if method[:lines] > MODEL_MAX_METHOD_LINES
68
+ end
69
+
70
+ {
71
+ type: type,
72
+ file: File.basename(file_path),
73
+ path: file_path,
74
+ lines: lines,
75
+ methods: methods,
76
+ issues: issues,
77
+ suggestions: suggestions,
78
+ needs_refactoring: issues.any? || suggestions.any?
79
+ }
80
+ end
81
+
82
+ def self.extract_methods(content)
83
+ methods = []
84
+ in_method = false
85
+ method_lines = []
86
+
87
+ content.lines.each do |line|
88
+ if line =~ /\bdef\s+(\w+)/
89
+ methods << { name: $1, lines: method_lines.count } if in_method
90
+ in_method = true
91
+ method_lines = [line]
92
+ elsif in_method
93
+ method_lines << line
94
+ methods << { name: methods.last[:name], lines: method_lines.count } if line.strip == "end" && method_lines.count > 1
95
+ end
96
+ end
97
+
98
+ methods
99
+ end
100
+
101
+ def self.print_report(results)
102
+ puts "\nRefactoring Suggestions"
103
+ puts "-" * 40
104
+
105
+ if results.empty?
106
+ puts "✓ No refactoring needed"
107
+ return
108
+ end
109
+
110
+ results.each do |result|
111
+ puts "⚠ #{result[:file]}"
112
+ result[:issues].each { |i| puts " - #{i}" }
113
+ result[:suggestions].each { |s| puts " → #{s}" }
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,169 @@
1
+ # Security analyzer for RailsForge
2
+ # Checks for common security vulnerabilities
3
+
4
+ require 'fileutils'
5
+
6
+ module RailsForge
7
+ module Analyzers
8
+ # SecurityAnalyzer scans code for security issues
9
+ class SecurityAnalyzer < BaseAnalyzer
10
+ # Security issue types
11
+ ISSUE_TYPES = {
12
+ sql_injection: "SQL Injection",
13
+ mass_assignment: "Unsafe Mass Assignment",
14
+ xss: "Cross-Site Scripting (XSS)",
15
+ sensitive_data: "Sensitive Data Exposure",
16
+ weak_crypto: "Weak Cryptography",
17
+ command_injection: "Command Injection"
18
+ }.freeze
19
+
20
+ # Analyze Rails app for security issues
21
+ # @param base_path [String] Rails app root path
22
+ # @return [Array<Hash>] Security issues found
23
+ def self.analyze(base_path = nil)
24
+ base_path ||= find_rails_app_path
25
+ return [] unless base_path
26
+
27
+ new(base_path).find_issues
28
+ end
29
+
30
+ # Initialize analyzer
31
+ # @param base_path [String] Rails app root path
32
+ def initialize(base_path)
33
+ @base_path = base_path
34
+ @issues = []
35
+ end
36
+
37
+ # Find all security issues
38
+ # @return [Array<Hash>] Issues found
39
+ def find_issues
40
+ check_sql_injection
41
+ check_mass_assignment
42
+ check_xss
43
+ check_sensitive_data
44
+ check_weak_crypto
45
+
46
+ @issues
47
+ end
48
+
49
+ # Print security report
50
+ # @param issues [Array<Hash>] Issues to print
51
+ def self.print_report(issues)
52
+ puts "Security Analysis"
53
+ puts "=" * 50
54
+
55
+ if issues.empty?
56
+ puts "✓ No security issues found"
57
+ return
58
+ end
59
+
60
+ puts "Found #{issues.count} security issue(s):"
61
+ puts ""
62
+
63
+ issues.each do |issue|
64
+ puts "⚠ #{issue[:type]}"
65
+ puts " File: #{issue[:file]}"
66
+ puts " Line: #{issue[:line]}"
67
+ puts " Issue: #{issue[:message]}"
68
+ puts ""
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ # Check for SQL injection vulnerabilities
75
+ def check_sql_injection
76
+ patterns = [
77
+ /\.where\s*\(\s*params\./,
78
+ /find_by\(.*\#\{.*\}/,
79
+ /execute\s*\(.*\#\{.*\}/
80
+ ]
81
+
82
+ scan_files("app/models/**/*.rb", patterns, :sql_injection)
83
+ scan_files("app/controllers/**/*.rb", patterns, :sql_injection)
84
+ end
85
+
86
+ # Check for unsafe mass assignment
87
+ def check_mass_assignment
88
+ patterns = [
89
+ /attr_accessible/,
90
+ /attr_protected/
91
+ ]
92
+
93
+ scan_files("app/models/**/*.rb", patterns, :mass_assignment)
94
+ end
95
+
96
+ # Check for XSS vulnerabilities
97
+ def check_xss
98
+ patterns = [
99
+ /raw\s*\(/,
100
+ /html_safe\s*$/,
101
+ /\<\%=\s*[^>]+without/
102
+ ]
103
+
104
+ scan_files("app/views/**/*.erb", patterns, :xss)
105
+ scan_files("app/helpers/**/*.rb", patterns, :xss)
106
+ end
107
+
108
+ # Check for sensitive data exposure
109
+ def check_sensitive_data
110
+ patterns = [
111
+ /password/,
112
+ /secret/,
113
+ /token/,
114
+ /api_key/,
115
+ /private_key/
116
+ ]
117
+
118
+ scan_files("app/models/**/*.rb", patterns, :sensitive_data)
119
+ scan_files("config/**/*.rb", patterns, :sensitive_data)
120
+ end
121
+
122
+ # Check for weak cryptography
123
+ def check_weak_crypto
124
+ patterns = [
125
+ /MD5/,
126
+ /SHA1/,
127
+ /DES\.encrypt/,
128
+ /\.encrypt\s+[^:]+$/
129
+ ]
130
+
131
+ scan_files("app/**/*.rb", patterns, :weak_crypto)
132
+ end
133
+
134
+ # Scan files for patterns
135
+ def scan_files(glob, patterns, issue_type)
136
+ Dir.glob(File.join(@base_path, glob)).each do |file|
137
+ content = File.read(file)
138
+ lines = content.lines
139
+
140
+ lines.each_with_index do |line, index|
141
+ patterns.each do |pattern|
142
+ if line =~ pattern
143
+ @issues << {
144
+ type: ISSUE_TYPES[issue_type],
145
+ file: file.gsub(@base_path, ""),
146
+ line: index + 1,
147
+ message: line.strip,
148
+ severity: severity_for(issue_type)
149
+ }
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # Get severity for issue type
157
+ def severity_for(issue_type)
158
+ case issue_type
159
+ when :sql_injection, :command_injection
160
+ "high"
161
+ when :mass_assignment, :xss
162
+ "medium"
163
+ else
164
+ "low"
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,58 @@
1
+ # Spec analyzer for RailsForge
2
+ # Checks for missing spec files
3
+
4
+ require_relative 'base_analyzer'
5
+
6
+ module RailsForge
7
+ module Analyzers
8
+ # SpecAnalyzer checks spec coverage
9
+ class SpecAnalyzer < BaseAnalyzer
10
+ GENERATOR_TYPES = %w[services queries jobs forms presenters policies serializers].freeze
11
+
12
+ # Analyze spec coverage
13
+ def self.analyze(base_path = nil)
14
+ base_path ||= find_rails_app_path
15
+ return [] unless base_path
16
+
17
+ results = []
18
+ GENERATOR_TYPES.each do |type|
19
+ app_folder = "app/#{type.chop}"
20
+ spec_folder = "spec/#{type}"
21
+
22
+ app_path = File.join(base_path, app_folder)
23
+ spec_path = File.join(base_path, spec_folder)
24
+
25
+ next unless Dir.exist?(app_path)
26
+
27
+ app_files = Dir.glob(File.join(app_path, "**", "*.rb")).map { |f| File.basename(f, ".rb") }
28
+ spec_files = Dir.exist?(spec_path) ? Dir.glob(File.join(spec_path, "**", "*_spec.rb")).map { |f| File.basename(f, "_spec.rb") } : []
29
+
30
+ (app_files - spec_files).each do |missing|
31
+ results << {
32
+ type: type.chop,
33
+ name: missing,
34
+ has_spec: false
35
+ }
36
+ end
37
+ end
38
+
39
+ results
40
+ end
41
+
42
+ def self.print_results(results)
43
+ puts "\nSpec Coverage Analysis"
44
+ puts "-" * 40
45
+
46
+ if results.empty?
47
+ puts "✓ All components have specs"
48
+ return
49
+ end
50
+
51
+ puts "⚠ #{results.count} missing specs:"
52
+ results.first(10).each do |result|
53
+ puts " - #{result[:type]}: #{result[:name]}"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end