code_qualia 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 24203fd574ea29e81bf9ff29ce73cdacc4a9e88b4cdfe1a67b240dde959c825e
4
+ data.tar.gz: 5fba6f527637a62f966289d360e830c216b3645e09c7ba6f7cc7ae85f52ec9db
5
+ SHA512:
6
+ metadata.gz: 9cf345920c0150b7b9fff683d292f2b6e3b464a83142b8001763af49692df9cb58a2d2b8c1d44ed63860d6651247c2191e3c05d8153b23dec29a7c71c063158d
7
+ data.tar.gz: 65b17c91e1e0c8c360a9a0a52709dd3c75311b412002d0478498ee8c17a3defedc9337d3c980c8c5dbc33a335b1cd77541c0d08d41d7a7398ca2193ec139d55f
data/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # Code Qualia
2
+
3
+ **A tool for communicating developer intuition and code quality perception to AI through configurable parameters**
4
+
5
+ Code Qualia helps developers express their subjective understanding and feelings about code quality to AI systems. By combining quantitative metrics (coverage, complexity, git activity) with configurable weights that reflect your development priorities and intuitions, it creates a "quality fingerprint" that AI can understand and use for better code analysis and recommendations.
6
+
7
+ ## 🎯 Key Features
8
+
9
+ - **Developer Intuition Translation**: Convert your subjective code quality perceptions into quantifiable parameters
10
+ - **Configurable Quality Weights**: Express what matters most to you - complexity, coverage, change frequency, or directory importance
11
+ - **AI-Ready Output**: Generate structured data that AI systems can use to understand your code priorities
12
+ - **Multiple Data Sources**: Integrates with SimpleCov, RuboCop, and Git to capture comprehensive code context
13
+ - **Flexible Interface**: CLI tool with JSON, CSV, and human-readable output formats
14
+
15
+ ## 🚀 Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'code_qualia', group: [:development, :test]
21
+ ```
22
+
23
+ And then execute:
24
+ ```bash
25
+ bundle install
26
+ ```
27
+
28
+ ## 📋 Usage
29
+
30
+ ### Setup
31
+
32
+ First, generate a configuration file for your project:
33
+
34
+ ```bash
35
+ # Auto-detect project type and generate qualia.yml
36
+ bundle exec code-qualia install
37
+ ```
38
+
39
+ This command automatically detects whether you're working with a Rails application or a Ruby gem and generates an appropriate configuration file.
40
+
41
+ ### Basic Analysis
42
+
43
+ ```bash
44
+ # Analyze top 3 methods (default)
45
+ bundle exec code-qualia generate
46
+
47
+ # Analyze top 10 methods
48
+ bundle exec code-qualia generate --top-n 10
49
+
50
+ # Use custom config file
51
+ bundle exec code-qualia generate --config custom_qualia.yml
52
+
53
+ # Output in different formats
54
+ bundle exec code-qualia generate --format json
55
+ bundle exec code-qualia generate --format csv
56
+ bundle exec code-qualia generate --format table
57
+ ```
58
+
59
+ ### Sample Output
60
+
61
+ ```
62
+ 📊 Top 3 methods requiring test coverage:
63
+
64
+ 1. app/models/user.rb:21
65
+ Method: can_access_feature?
66
+ Priority Score: 18.6
67
+ Coverage: 50.0%
68
+ Complexity: 7
69
+ Git Commits: 0
70
+
71
+ 2. app/models/user.rb:35
72
+ Method: calculate_discount
73
+ Priority Score: 11.4
74
+ Coverage: 50.0%
75
+ Complexity: 4
76
+ Git Commits: 0
77
+
78
+ 3. packs/users/app/models/users/user_profile.rb:5
79
+ Method: display_name
80
+ Priority Score: 7.8
81
+ Coverage: 0.0%
82
+ Complexity: 5
83
+ Git Commits: 0
84
+ ```
85
+
86
+ ## ⚙️ Configuration
87
+
88
+ Create a `qualia.yml` file in your project root:
89
+
90
+ ```yaml
91
+ # Quality indicators (code issues that need fixing)
92
+ quality_weights:
93
+ test_coverage: 1.5 # Weight for test coverage (lower coverage = higher priority)
94
+ cyclomatic_complexity: 1.0 # Weight for cyclomatic complexity (higher complexity = higher priority)
95
+
96
+ # Importance indicators (how critical the code is)
97
+ importance_weights:
98
+ change_frequency: 0.8 # Weight for git change frequency (more changes = higher importance)
99
+ architectural_importance: 1.2 # Weight for architectural importance (critical paths = higher importance)
100
+
101
+ # Path-based architectural importance weights
102
+ architectural_weights:
103
+ - path: "app/models/**/*.rb"
104
+ weight: 2.0 # Models are critical for business logic
105
+ - path: "app/services/**/*.rb"
106
+ weight: 1.8 # Services contain complex business logic
107
+ - path: "app/controllers/**/*.rb"
108
+ weight: 1.5 # Controllers handle user interactions
109
+ - path: "lib/**/*.rb"
110
+ weight: 1.0 # Library code standard weight
111
+
112
+ # Files to exclude from analysis
113
+ exclude:
114
+ - "app/helpers/**/*"
115
+ - "config/**/*"
116
+ - "db/**/*"
117
+
118
+ # Days of git history to analyze
119
+ git_history_days: 90
120
+ ```
121
+
122
+ ## 🔧 Requirements
123
+
124
+ - **SimpleCov**: For test coverage data
125
+ - **RuboCop**: For code complexity analysis
126
+ - **Git**: For file change history
127
+
128
+
129
+ ## 🏗️ How It Works
130
+
131
+ Code Qualia calculates a priority score for each method using a multiplicative approach that separates quality issues from code importance:
132
+
133
+ **FinalScore = QualityScore × ImportanceScore**
134
+
135
+ Where:
136
+ - **QualityScore** = `(W_test_coverage × TestCoverageFactor) + (W_cyclomatic_complexity × ComplexityFactor)`
137
+ - **ImportanceScore** = `(W_change_frequency × ChangeFrequencyFactor) + (W_architectural_importance × ArchitecturalFactor)`
138
+
139
+ **Quality Indicators** (code issues that need fixing):
140
+ - **TestCoverageFactor**: `(1.0 - coverage_rate)` - lower coverage = higher quality risk
141
+ - **ComplexityFactor**: Cyclomatic complexity from RuboCop - higher complexity = higher quality risk
142
+
143
+ **Importance Indicators** (how critical the code is):
144
+ - **ChangeFrequencyFactor**: Number of commits in specified time period - more changes = higher importance
145
+ - **ArchitecturalFactor**: Weight based on file location (configurable) - critical paths = higher importance
146
+
147
+ This approach ensures that both quality issues AND importance must be present for a method to rank highly, providing more logical prioritization.
148
+
149
+
150
+ ## 🧪 Development
151
+
152
+ ### Running Tests
153
+
154
+ ```bash
155
+ # Run all tests
156
+ bundle exec rspec
157
+
158
+ # Run integration tests only
159
+ bundle exec rspec spec/integration/
160
+ ```
161
+
162
+ ### Smoke Testing
163
+
164
+ Code Qualia includes a comprehensive smoke test suite that validates functionality against a sample Rails application in the `smoke/sample_app` directory.
165
+
166
+ ## 📊 Output Formats
167
+
168
+ Code Qualia supports multiple output formats for flexibility:
169
+
170
+ ### Human-readable (default)
171
+ ```
172
+ 📊 Top 3 methods requiring test coverage:
173
+
174
+ 1. app/models/user.rb:21
175
+ Method: can_access_feature?
176
+ Priority Score: 10.15
177
+ Coverage: 50.0%
178
+ Complexity: 7
179
+ Git Commits: 0
180
+
181
+ 2. packs/users/app/models/users/user_profile.rb:5
182
+ Method: display_name
183
+ Priority Score: 7.7
184
+ Coverage: 0.0%
185
+ Complexity: 5
186
+ Git Commits: 0
187
+ ```
188
+
189
+ ### JSON Format
190
+ ```json
191
+ [
192
+ {
193
+ "file_path": "app/models/user.rb",
194
+ "class_name": "User",
195
+ "method_name": "can_access_feature?",
196
+ "line_number": 21,
197
+ "score": 10.15,
198
+ "details": {
199
+ "coverage": 0.5,
200
+ "complexity": 7,
201
+ "git_commits": 0
202
+ }
203
+ },
204
+ {
205
+ "file_path": "packs/users/app/models/users/user_profile.rb",
206
+ "class_name": "UserProfile",
207
+ "method_name": "display_name",
208
+ "line_number": 5,
209
+ "score": 7.7,
210
+ "details": {
211
+ "coverage": 0.0,
212
+ "complexity": 5,
213
+ "git_commits": 0
214
+ }
215
+ }
216
+ ]
217
+ ```
218
+
219
+ ### CSV Format
220
+ ```csv
221
+ file_path,method_name,line_number,score,coverage,complexity,git_commits
222
+ app/models/user.rb,can_access_feature?,21,10.15,50.0,7,0
223
+ packs/users/app/models/users/user_profile.rb,display_name,5,7.7,0.0,5,0
224
+ ```
225
+
226
+ ### Table Format
227
+ ```
228
+ +----------------------------------------------+---------------------+------+-------+----------+------------+----------+
229
+ | File | Method | Line | Score | Coverage | Complexity | Commits |
230
+ +----------------------------------------------+---------------------+------+-------+----------+------------+----------+
231
+ | app/models/user.rb | can_access_feature? | 21 | 10.15 | 50.0% | 7 | 0 |
232
+ | packs/users/app/models/users/user_profile.rb | display_name | 5 | 7.70 | 0.0% | 5 | 0 |
233
+ +----------------------------------------------+---------------------+------+-------+----------+------------+----------+
234
+ ```
235
+
236
+ All formats can be redirected to files using standard Unix redirection:
237
+ ```bash
238
+ bundle exec code-qualia generate --format json > analysis.json
239
+ bundle exec code-qualia generate --format csv > analysis.csv
240
+ ```
241
+
242
+ ## 🤝 Contributing
243
+
244
+ 1. Fork the repository
245
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
246
+ 3. Make your changes
247
+ 4. Run the test suite (`bundle exec rspec`)
248
+ 5. Commit your changes (`git commit -am 'Add amazing feature'`)
249
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
250
+ 7. Open a Pull Request
251
+
252
+ ## 📄 License
253
+
254
+ This project is licensed under the MIT License.
data/bin/code-qualia ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'pathname'
5
+
6
+ # Add lib directory to load path
7
+ lib_path = Pathname.new(__FILE__).dirname.parent.join('lib')
8
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path.to_s)
9
+
10
+ require 'code_qualia'
11
+
12
+ CodeQualia::CLI.new(ARGV).run
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'fileutils'
5
+
6
+ # Script to update smoke test expected outputs
7
+ class SmokeTestUpdater
8
+ def initialize
9
+ @root_dir = File.expand_path('..', __dir__)
10
+ @smoke_dir = File.join(@root_dir, 'smoke')
11
+ @expected_outputs_dir = File.join(@root_dir, 'expected_outputs')
12
+ end
13
+
14
+ def run
15
+ puts '🔄 Updating smoke test expected outputs...'
16
+
17
+ smoke_projects.each do |project|
18
+ puts "\n📁 Processing #{project[:name]}..."
19
+ update_project_outputs(project)
20
+ end
21
+
22
+ puts "\n✅ All smoke test expected outputs have been updated!"
23
+ end
24
+
25
+ private
26
+
27
+ def smoke_projects
28
+ projects = []
29
+ Dir.entries(@smoke_dir).each do |entry|
30
+ project_path = File.join(@smoke_dir, entry)
31
+ expected_path = File.join(@expected_outputs_dir, entry)
32
+
33
+ # Only include directories that have a Gemfile
34
+ next unless File.directory?(project_path) &&
35
+ !entry.start_with?('.') &&
36
+ File.exist?(File.join(project_path, 'Gemfile'))
37
+
38
+ projects << {
39
+ name: entry,
40
+ dir: project_path,
41
+ expected_path: expected_path,
42
+ is_rails: File.exist?(File.join(project_path, 'config', 'application.rb'))
43
+ }
44
+ end
45
+ projects
46
+ end
47
+
48
+ def update_project_outputs(project)
49
+ # Ensure expected outputs directory exists
50
+ FileUtils.mkdir_p(project[:expected_path])
51
+
52
+ # Generate coverage data if needed
53
+ generate_coverage_data(project)
54
+
55
+ # Generate outputs for each format
56
+ %w[human json csv table].each do |format|
57
+ puts " 📊 Generating #{format} format..."
58
+
59
+ command_output = if project[:is_rails]
60
+ # For Rails projects, run from within the project directory
61
+ `cd #{project[:dir]} && bundle exec code-qualia generate --format #{format} --top-n 10 2>&1`
62
+ else
63
+ # For non-Rails projects, run from project root with --directory option
64
+ `bundle exec code-qualia generate --format #{format} --directory #{project[:dir]} --top-n 10 2>&1`
65
+ end
66
+
67
+ if $?.success?
68
+ file_extension = if format == 'json'
69
+ 'json'
70
+ else
71
+ (format == 'csv' ? 'csv' : 'txt')
72
+ end
73
+ output_file = File.join(project[:expected_path], "output_#{format}.#{file_extension}")
74
+ File.write(output_file, command_output)
75
+ puts " ✓ Saved to #{File.basename(output_file)}"
76
+ else
77
+ puts " ❌ Failed to generate #{format} format for #{project[:name]}"
78
+ puts " Error output: #{command_output}"
79
+ end
80
+ end
81
+
82
+ # Clean up generated files
83
+ cleanup_generated_files(project)
84
+ end
85
+
86
+ def generate_coverage_data(project)
87
+ puts ' 🧪 Generating coverage data...'
88
+ Dir.chdir(project[:dir]) do
89
+ if project[:is_rails]
90
+ # For Rails projects, run RSpec to generate coverage
91
+ `bundle exec rspec 2>/dev/null || true`
92
+ elsif File.exist?('spec/spec_helper.rb')
93
+ `bundle exec rspec --require spec_helper 2>/dev/null || true`
94
+ elsif File.exist?('test/test_helper.rb')
95
+ `bundle exec rake test 2>/dev/null || true`
96
+ end
97
+ end
98
+ end
99
+
100
+ def cleanup_generated_files(project)
101
+ generated_json_path = File.join(project[:dir], 'code_qualia_analysis.json')
102
+ File.delete(generated_json_path) if File.exist?(generated_json_path)
103
+ end
104
+ end
105
+
106
+ # Run the updater
107
+ SmokeTestUpdater.new.run if __FILE__ == $0
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'json'
5
+ require_relative '../code_qualia'
6
+
7
+ module CodeQualia
8
+ class CLI
9
+ def initialize(argv)
10
+ @argv = argv
11
+ @options = {
12
+ top_n: 3,
13
+ config: './qualia.yml',
14
+ directory: Dir.pwd,
15
+ format: 'human',
16
+ verbose: false
17
+ }
18
+ end
19
+
20
+ def run
21
+ parse_options
22
+
23
+ case @command
24
+ when 'generate'
25
+ generate_analysis
26
+ when 'install'
27
+ install_config
28
+ else
29
+ show_help
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def parse_options
36
+ parser = OptionParser.new do |opts|
37
+ opts.banner = 'Usage: code-qualia [command] [options]'
38
+ opts.separator ''
39
+ opts.separator 'Commands:'
40
+ opts.separator ' generate Analyze codebase and generate test recommendations'
41
+ opts.separator ' install Setup configuration file for your project'
42
+ opts.separator ''
43
+ opts.separator 'Options:'
44
+
45
+ opts.on('--top-n N', Integer, 'Number of top priority methods to analyze (default: 3)') do |n|
46
+ @options[:top_n] = n
47
+ end
48
+
49
+ opts.on('--config PATH', String, 'Path to configuration file (default: ./qualia.yml)') do |path|
50
+ @options[:config] = path
51
+ end
52
+
53
+ opts.on('--directory DIR', String, 'Directory to analyze (default: current directory)') do |dir|
54
+ @options[:directory] = dir
55
+ end
56
+
57
+ opts.on('--format FORMAT', String, 'Output format: human, json, csv, table (default: human)') do |format|
58
+ unless %w[human json csv table].include?(format)
59
+ puts "Error: Invalid format '#{format}'. Valid formats: human, json, csv, table"
60
+ exit 1
61
+ end
62
+ @options[:format] = format
63
+ end
64
+
65
+ opts.on('-v', '--verbose', 'Enable verbose logging') do
66
+ @options[:verbose] = true
67
+ end
68
+
69
+ opts.on('-h', '--help', 'Show this help message') do
70
+ puts opts
71
+ exit
72
+ end
73
+ end
74
+
75
+ parser.parse!(@argv)
76
+ @command = @argv.shift
77
+ end
78
+
79
+ def generate_analysis
80
+ original_dir = Dir.pwd
81
+
82
+ begin
83
+ # Change to target directory for analysis
84
+ Dir.chdir(@options[:directory])
85
+
86
+ results = CodeQualia.analyze(@options[:config], verbose: @options[:verbose])
87
+
88
+ if results.empty?
89
+ case @options[:format]
90
+ when 'json'
91
+ puts '[]'
92
+ when 'csv'
93
+ puts 'file_path,method_name,line_number,score,coverage,complexity,git_commits'
94
+ when 'table'
95
+ puts 'No methods found that need additional testing.'
96
+ else
97
+ puts '✅ No methods found that need additional testing.'
98
+ end
99
+ return
100
+ end
101
+
102
+ top_results = results.take(@options[:top_n])
103
+
104
+ case @options[:format]
105
+ when 'json'
106
+ output_json_format(top_results)
107
+ when 'csv'
108
+ output_csv_format(top_results)
109
+ when 'table'
110
+ output_table_format(top_results)
111
+ else # 'human'
112
+ output_human_format(top_results)
113
+ end
114
+ rescue CodeQualia::Error => e
115
+ puts "❌ Error: #{e.message}"
116
+ exit 1
117
+ rescue StandardError => e
118
+ puts "❌ Unexpected error: #{e.message}"
119
+ exit 1
120
+ ensure
121
+ Dir.chdir(original_dir)
122
+ end
123
+ end
124
+
125
+ def output_human_format(results)
126
+ puts "📊 Top #{results.length} methods requiring test coverage:\n\n"
127
+
128
+ results.each_with_index do |method, index|
129
+ puts "#{index + 1}. #{method[:file_path]}:#{method[:line_number]}"
130
+ puts " Method: #{method[:method_name]}"
131
+ puts " Priority Score: #{method[:score]}"
132
+ puts " Coverage: #{(method[:details][:coverage] * 100).round(1)}%"
133
+ puts " Complexity: #{method[:details][:complexity]}"
134
+ puts " Git Commits: #{method[:details][:git_commits]}"
135
+ puts ''
136
+ end
137
+ end
138
+
139
+ def output_json_format(results)
140
+ json_output = results.map do |method|
141
+ {
142
+ file_path: method[:file_path],
143
+ class_name: extract_class_name(method[:file_path]),
144
+ method_name: method[:method_name],
145
+ line_number: method[:line_number],
146
+ score: method[:score],
147
+ details: method[:details]
148
+ }
149
+ end
150
+
151
+ puts JSON.pretty_generate(json_output)
152
+ end
153
+
154
+ def output_csv_format(results)
155
+ puts 'file_path,method_name,line_number,score,coverage,complexity,git_commits'
156
+
157
+ results.each do |method|
158
+ coverage_percent = (method[:details][:coverage] * 100).round(1)
159
+ puts "#{method[:file_path]},#{method[:method_name]},#{method[:line_number]},#{method[:score]},#{coverage_percent},#{method[:details][:complexity]},#{method[:details][:git_commits]}"
160
+ end
161
+ end
162
+
163
+ def output_table_format(results)
164
+ require 'io/console'
165
+
166
+ # Calculate column widths
167
+ max_file = results.map { |r| r[:file_path].length }.max || 20
168
+ max_method = [results.map { |r| r[:method_name].length }.max, 20].min
169
+
170
+ # Header
171
+ puts "+#{'-' * (max_file + 2)}+#{'-' * (max_method + 2)}+------+-------+----------+------------+----------+"
172
+ printf "| %-#{max_file}s | %-#{max_method}s | Line | Score | Coverage | Complexity | Commits |\n", 'File', 'Method'
173
+ puts "+#{'-' * (max_file + 2)}+#{'-' * (max_method + 2)}+------+-------+----------+------------+----------+"
174
+
175
+ # Data rows
176
+ results.each do |method|
177
+ file_name = method[:file_path]
178
+ method_name = method[:method_name].length > max_method ? method[:method_name][0...max_method - 1] + '…' : method[:method_name]
179
+ coverage_percent = (method[:details][:coverage] * 100).round(1)
180
+
181
+ printf "| %-#{max_file}s | %-#{max_method}s | %4d | %5.2f | %7.1f%% | %10d | %8d |\n",
182
+ file_name, method_name, method[:line_number], method[:score],
183
+ coverage_percent, method[:details][:complexity], method[:details][:git_commits]
184
+ end
185
+
186
+ puts "+#{'-' * (max_file + 2)}+#{'-' * (max_method + 2)}+------+-------+----------+------------+----------+"
187
+ end
188
+
189
+ def extract_class_name(file_path)
190
+ # Simple heuristic to extract class name from file path
191
+ File.basename(file_path, '.rb').split('_').map(&:capitalize).join
192
+ end
193
+
194
+ def install_config
195
+ installer = CodeQualia::ConfigInstaller.new(@options[:directory])
196
+ installer.install
197
+ rescue CodeQualia::Error => e
198
+ puts "❌ Error: #{e.message}"
199
+ exit 1
200
+ rescue StandardError => e
201
+ puts "❌ Unexpected error: #{e.message}"
202
+ exit 1
203
+ end
204
+
205
+ def show_help
206
+ puts 'code-qualia - AI-powered test coverage analysis tool'
207
+ puts ''
208
+ puts 'Usage: code-qualia [command] [options]'
209
+ puts ''
210
+ puts 'Commands:'
211
+ puts ' generate Analyze codebase and generate test recommendations'
212
+ puts ' install Setup configuration file for your project'
213
+ puts ''
214
+ puts "Run 'code-qualia [command] --help' for more information."
215
+ end
216
+ end
217
+ end