aidp 0.32.0 → 0.33.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aidp/analyze/feature_analyzer.rb +322 -320
  3. data/lib/aidp/auto_update/coordinator.rb +97 -7
  4. data/lib/aidp/auto_update.rb +0 -12
  5. data/lib/aidp/cli/devcontainer_commands.rb +0 -5
  6. data/lib/aidp/cli.rb +2 -1
  7. data/lib/aidp/comment_consolidator.rb +78 -0
  8. data/lib/aidp/concurrency.rb +0 -3
  9. data/lib/aidp/config.rb +0 -1
  10. data/lib/aidp/config_paths.rb +71 -0
  11. data/lib/aidp/execute/work_loop_runner.rb +324 -15
  12. data/lib/aidp/harness/ai_filter_factory.rb +285 -0
  13. data/lib/aidp/harness/config_schema.rb +97 -1
  14. data/lib/aidp/harness/config_validator.rb +1 -1
  15. data/lib/aidp/harness/configuration.rb +61 -5
  16. data/lib/aidp/harness/filter_definition.rb +212 -0
  17. data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
  18. data/lib/aidp/harness/output_filter.rb +50 -25
  19. data/lib/aidp/harness/output_filter_config.rb +129 -0
  20. data/lib/aidp/harness/provider_manager.rb +90 -2
  21. data/lib/aidp/harness/runner.rb +0 -11
  22. data/lib/aidp/harness/test_runner.rb +179 -41
  23. data/lib/aidp/harness/thinking_depth_manager.rb +16 -0
  24. data/lib/aidp/harness/ui/navigation/submenu.rb +0 -2
  25. data/lib/aidp/loader.rb +195 -0
  26. data/lib/aidp/metadata/compiler.rb +29 -17
  27. data/lib/aidp/metadata/query.rb +1 -1
  28. data/lib/aidp/metadata/scanner.rb +8 -1
  29. data/lib/aidp/metadata/tool_metadata.rb +13 -13
  30. data/lib/aidp/metadata/validator.rb +10 -0
  31. data/lib/aidp/metadata.rb +16 -0
  32. data/lib/aidp/pr_worktree_manager.rb +2 -2
  33. data/lib/aidp/provider_manager.rb +1 -7
  34. data/lib/aidp/setup/wizard.rb +279 -9
  35. data/lib/aidp/skills.rb +0 -5
  36. data/lib/aidp/storage/csv_storage.rb +3 -0
  37. data/lib/aidp/style_guide/selector.rb +360 -0
  38. data/lib/aidp/tooling_detector.rb +283 -16
  39. data/lib/aidp/version.rb +1 -1
  40. data/lib/aidp/watch/change_request_processor.rb +152 -14
  41. data/lib/aidp/watch/repository_client.rb +41 -0
  42. data/lib/aidp/watch/runner.rb +29 -18
  43. data/lib/aidp/watch.rb +5 -7
  44. data/lib/aidp/workstream_cleanup.rb +0 -2
  45. data/lib/aidp/workstream_executor.rb +0 -4
  46. data/lib/aidp/worktree.rb +0 -1
  47. data/lib/aidp.rb +21 -106
  48. metadata +72 -36
  49. data/lib/aidp/config/paths.rb +0 -131
@@ -3,110 +3,126 @@
3
3
  require "fileutils"
4
4
 
5
5
  module Aidp
6
- class FeatureAnalyzer
7
- def initialize(project_dir = Dir.pwd)
8
- @project_dir = project_dir
9
- end
10
-
11
- # Detect and categorize features in the codebase
12
- def detect_features
13
- features = []
14
-
15
- # Scan directories for potential features
16
- scan_directories_for_features(features)
17
-
18
- # Scan files for feature indicators
19
- scan_files_for_features(features)
20
-
21
- # Analyze feature relationships
22
- analyze_feature_relationships(features)
23
-
24
- # Categorize features
25
- categorize_features(features)
6
+ module Analyze
7
+ class FeatureAnalyzer
8
+ def initialize(project_dir = Dir.pwd)
9
+ @project_dir = project_dir
10
+ end
26
11
 
27
- features
28
- end
12
+ # Detect and categorize features in the codebase
13
+ def detect_features
14
+ features = []
29
15
 
30
- # Get feature-specific agent recommendations
31
- def get_feature_agent_recommendations(feature)
32
- {
33
- feature: feature[:name],
34
- primary_agent: determine_primary_agent(feature),
35
- specialized_agents: determine_specialized_agents(feature),
36
- analysis_priority: determine_analysis_priority(feature)
37
- }
38
- end
16
+ # Scan directories for potential features
17
+ scan_directories_for_features(features)
39
18
 
40
- # Coordinate multi-agent analysis for a feature
41
- def coordinate_feature_analysis(feature)
42
- agents = get_feature_agent_recommendations(feature)
43
-
44
- {
45
- feature: feature[:name],
46
- primary_analysis: {
47
- agent: agents[:primary_agent],
48
- focus_areas: get_agent_focus_areas(agents[:primary_agent]),
49
- output_files: generate_output_files(feature, agents[:primary_agent])
50
- },
51
- specialized_analyses: agents[:specialized_agents].map do |agent|
52
- {
53
- agent: agent,
54
- focus_areas: get_agent_focus_areas(agent),
55
- output_files: generate_output_files(feature, agent)
56
- }
57
- end,
58
- coordination_notes: generate_coordination_notes(feature, agents)
59
- }
60
- end
19
+ # Scan files for feature indicators
20
+ scan_files_for_features(features)
61
21
 
62
- private
22
+ # Analyze feature relationships
23
+ analyze_feature_relationships(features)
63
24
 
64
- def scan_directories_for_features(features)
65
- # Common feature directory patterns
66
- feature_patterns = [
67
- "features/", "modules/", "components/", "services/",
68
- "controllers/", "models/", "views/", "api/",
69
- "handlers/", "processors/", "managers/", "utils/"
70
- ]
25
+ # Categorize features
26
+ categorize_features(features)
71
27
 
72
- feature_patterns.each do |pattern|
73
- pattern_path = File.join(@project_dir, pattern)
74
- next unless Dir.exist?(pattern_path)
28
+ features
29
+ end
75
30
 
76
- Dir.entries(pattern_path).each do |entry|
77
- next if entry.start_with?(".") || entry == ".."
31
+ # Get feature-specific agent recommendations
32
+ def get_feature_agent_recommendations(feature)
33
+ {
34
+ feature: feature[:name],
35
+ primary_agent: determine_primary_agent(feature),
36
+ specialized_agents: determine_specialized_agents(feature),
37
+ analysis_priority: determine_analysis_priority(feature)
38
+ }
39
+ end
78
40
 
79
- feature_path = File.join(pattern_path, entry)
80
- next unless Dir.exist?(feature_path)
41
+ # Coordinate multi-agent analysis for a feature
42
+ def coordinate_feature_analysis(feature)
43
+ agents = get_feature_agent_recommendations(feature)
44
+
45
+ {
46
+ feature: feature[:name],
47
+ primary_analysis: {
48
+ agent: agents[:primary_agent],
49
+ focus_areas: get_agent_focus_areas(agents[:primary_agent]),
50
+ output_files: generate_output_files(feature, agents[:primary_agent])
51
+ },
52
+ specialized_analyses: agents[:specialized_agents].map do |agent|
53
+ {
54
+ agent: agent,
55
+ focus_areas: get_agent_focus_areas(agent),
56
+ output_files: generate_output_files(feature, agent)
57
+ }
58
+ end,
59
+ coordination_notes: generate_coordination_notes(feature, agents)
60
+ }
61
+ end
81
62
 
82
- features << {
83
- name: entry || "unknown",
84
- type: "directory",
85
- path: feature_path,
86
- category: infer_category_from_path(pattern, entry),
87
- complexity: estimate_complexity(feature_path)
88
- }
63
+ private
64
+
65
+ def scan_directories_for_features(features)
66
+ # Common feature directory patterns
67
+ feature_patterns = [
68
+ "features/", "modules/", "components/", "services/",
69
+ "controllers/", "models/", "views/", "api/",
70
+ "handlers/", "processors/", "managers/", "utils/"
71
+ ]
72
+
73
+ feature_patterns.each do |pattern|
74
+ pattern_path = File.join(@project_dir, pattern)
75
+ next unless Dir.exist?(pattern_path)
76
+
77
+ Dir.entries(pattern_path).each do |entry|
78
+ next if entry.start_with?(".") || entry == ".."
79
+
80
+ feature_path = File.join(pattern_path, entry)
81
+ next unless Dir.exist?(feature_path)
82
+
83
+ features << {
84
+ name: entry || "unknown",
85
+ type: "directory",
86
+ path: feature_path,
87
+ category: infer_category_from_path(pattern, entry),
88
+ complexity: estimate_complexity(feature_path)
89
+ }
90
+ end
89
91
  end
90
92
  end
91
- end
92
-
93
- def scan_files_for_features(features)
94
- # Scan for feature indicators in files
95
- feature_indicators = [
96
- "class.*Controller", "class.*Service", "class.*Manager",
97
- "module.*Feature", "def.*feature", "function.*feature"
98
- ]
99
-
100
- Dir.glob(File.join(@project_dir, "**", "*.rb")).each do |file|
101
- next if file.include?("spec/") || file.include?("test/")
102
-
103
- content = File.read(file)
104
- feature_found = false
105
-
106
- feature_indicators.each do |indicator|
107
- next unless content.match?(/#{indicator}/i)
108
93
 
109
- feature_name = extract_feature_name(file, content, indicator)
94
+ def scan_files_for_features(features)
95
+ # Scan for feature indicators in files
96
+ feature_indicators = [
97
+ "class.*Controller", "class.*Service", "class.*Manager",
98
+ "module.*Feature", "def.*feature", "function.*feature"
99
+ ]
100
+
101
+ Dir.glob(File.join(@project_dir, "**", "*.rb")).each do |file|
102
+ next if file.include?("spec/") || file.include?("test/")
103
+
104
+ content = File.read(file)
105
+ feature_found = false
106
+
107
+ feature_indicators.each do |indicator|
108
+ next unless content.match?(/#{indicator}/i)
109
+
110
+ feature_name = extract_feature_name(file, content, indicator)
111
+ features << {
112
+ name: feature_name,
113
+ type: "file",
114
+ path: file,
115
+ category: infer_category_from_file(file, content),
116
+ complexity: estimate_file_complexity(content)
117
+ }
118
+ feature_found = true
119
+ break
120
+ end
121
+
122
+ # If no specific pattern matched, add the file as a feature using filename
123
+ next if feature_found
124
+
125
+ feature_name = File.basename(file, ".*").capitalize
110
126
  features << {
111
127
  name: feature_name,
112
128
  type: "file",
@@ -114,284 +130,270 @@ module Aidp
114
130
  category: infer_category_from_file(file, content),
115
131
  complexity: estimate_file_complexity(content)
116
132
  }
117
- feature_found = true
118
- break
119
133
  end
120
-
121
- # If no specific pattern matched, add the file as a feature using filename
122
- next if feature_found
123
-
124
- feature_name = File.basename(file, ".*").capitalize
125
- features << {
126
- name: feature_name,
127
- type: "file",
128
- path: file,
129
- category: infer_category_from_file(file, content),
130
- complexity: estimate_file_complexity(content)
131
- }
132
134
  end
133
- end
134
135
 
135
- def analyze_feature_relationships(features)
136
- features.each do |feature|
137
- feature[:dependencies] = find_feature_dependencies(feature)
138
- feature[:dependents] = find_feature_dependents(feature, features)
139
- feature[:coupling] = calculate_coupling_score(feature)
136
+ def analyze_feature_relationships(features)
137
+ features.each do |feature|
138
+ feature[:dependencies] = find_feature_dependencies(feature)
139
+ feature[:dependents] = find_feature_dependents(feature, features)
140
+ feature[:coupling] = calculate_coupling_score(feature)
141
+ end
140
142
  end
141
- end
142
143
 
143
- def categorize_features(features)
144
- features.each do |feature|
145
- feature[:category] ||= infer_category(feature)
146
- feature[:priority] = calculate_priority(feature)
147
- feature[:business_value] = estimate_business_value(feature)
148
- feature[:technical_debt] = estimate_technical_debt(feature)
144
+ def categorize_features(features)
145
+ features.each do |feature|
146
+ feature[:category] ||= infer_category(feature)
147
+ feature[:priority] = calculate_priority(feature)
148
+ feature[:business_value] = estimate_business_value(feature)
149
+ feature[:technical_debt] = estimate_technical_debt(feature)
150
+ end
149
151
  end
150
- end
151
152
 
152
- def determine_primary_agent(feature)
153
- case feature[:category]
154
- when "core_business"
155
- "Functionality Analyst"
156
- when "api"
157
- "Architecture Analyst"
158
- when "data"
159
- "Functionality Analyst"
160
- when "ui"
161
- "Functionality Analyst"
162
- when "utility"
163
- "Static Analysis Expert"
164
- else
165
- "Functionality Analyst"
153
+ def determine_primary_agent(feature)
154
+ case feature[:category]
155
+ when "core_business"
156
+ "Functionality Analyst"
157
+ when "api"
158
+ "Architecture Analyst"
159
+ when "data"
160
+ "Functionality Analyst"
161
+ when "ui"
162
+ "Functionality Analyst"
163
+ when "utility"
164
+ "Static Analysis Expert"
165
+ else
166
+ "Functionality Analyst"
167
+ end
166
168
  end
167
- end
168
169
 
169
- def determine_specialized_agents(feature)
170
- agents = []
170
+ def determine_specialized_agents(feature)
171
+ agents = []
171
172
 
172
- # Add specialized agents based on feature characteristics
173
- agents << "Test Analyst" if feature[:complexity] > 5
174
- agents << "Architecture Analyst" if feature[:coupling] > 0.7
175
- agents << "Documentation Analyst" if feature[:business_value] > 0.8
176
- agents << "Refactoring Specialist" if feature[:technical_debt] > 0.6
173
+ # Add specialized agents based on feature characteristics
174
+ agents << "Test Analyst" if feature[:complexity] > 5
175
+ agents << "Architecture Analyst" if feature[:coupling] > 0.7
176
+ agents << "Documentation Analyst" if feature[:business_value] > 0.8
177
+ agents << "Refactoring Specialist" if feature[:technical_debt] > 0.6
177
178
 
178
- agents.uniq
179
- end
179
+ agents.uniq
180
+ end
180
181
 
181
- def determine_analysis_priority(feature)
182
- # Calculate priority based on business value, complexity, and technical debt
183
- priority_score = (
184
- feature[:business_value] * 0.4 +
185
- feature[:complexity] * 0.3 +
186
- feature[:technical_debt] * 0.3
187
- )
188
-
189
- case priority_score
190
- when 0.8..1.0
191
- "high"
192
- when 0.5..0.8
193
- "medium"
194
- else
195
- "low"
182
+ def determine_analysis_priority(feature)
183
+ # Calculate priority based on business value, complexity, and technical debt
184
+ priority_score = (
185
+ feature[:business_value] * 0.4 +
186
+ feature[:complexity] * 0.3 +
187
+ feature[:technical_debt] * 0.3
188
+ )
189
+
190
+ case priority_score
191
+ when 0.8..1.0
192
+ "high"
193
+ when 0.5..0.8
194
+ "medium"
195
+ else
196
+ "low"
197
+ end
196
198
  end
197
- end
198
199
 
199
- def get_agent_focus_areas(agent_name)
200
- case agent_name
201
- when "Functionality Analyst"
202
- %w[feature_mapping complexity_analysis dead_code_identification]
203
- when "Architecture Analyst"
204
- %w[dependency_analysis coupling_assessment design_patterns]
205
- when "Test Analyst"
206
- %w[test_coverage test_quality testing_gaps]
207
- when "Documentation Analyst"
208
- %w[documentation_gaps documentation_quality user_needs]
209
- when "Static Analysis Expert"
210
- %w[code_quality tool_integration best_practices]
211
- when "Refactoring Specialist"
212
- %w[technical_debt code_smells refactoring_opportunities]
213
- else
214
- ["general_analysis"]
200
+ def get_agent_focus_areas(agent_name)
201
+ case agent_name
202
+ when "Functionality Analyst"
203
+ %w[feature_mapping complexity_analysis dead_code_identification]
204
+ when "Architecture Analyst"
205
+ %w[dependency_analysis coupling_assessment design_patterns]
206
+ when "Test Analyst"
207
+ %w[test_coverage test_quality testing_gaps]
208
+ when "Documentation Analyst"
209
+ %w[documentation_gaps documentation_quality user_needs]
210
+ when "Static Analysis Expert"
211
+ %w[code_quality tool_integration best_practices]
212
+ when "Refactoring Specialist"
213
+ %w[technical_debt code_smells refactoring_opportunities]
214
+ else
215
+ ["general_analysis"]
216
+ end
215
217
  end
216
- end
217
218
 
218
- def generate_output_files(feature, agent_name)
219
- base_name = (feature[:name] || "unknown").downcase.gsub(/[^a-z0-9]/, "_")
220
- agent_suffix = agent_name.downcase.gsub(/\s+/, "_")
219
+ def generate_output_files(feature, agent_name)
220
+ base_name = (feature[:name] || "unknown").downcase.gsub(/[^a-z0-9]/, "_")
221
+ agent_suffix = agent_name.downcase.gsub(/\s+/, "_")
221
222
 
222
- {
223
- primary: "docs/#{base_name}_#{agent_suffix}_analysis.md",
224
- secondary: "docs/#{base_name}_#{agent_suffix}_details.md",
225
- data: "docs/#{base_name}_#{agent_suffix}_data.json"
226
- }
227
- end
223
+ {
224
+ primary: "docs/#{base_name}_#{agent_suffix}_analysis.md",
225
+ secondary: "docs/#{base_name}_#{agent_suffix}_details.md",
226
+ data: "docs/#{base_name}_#{agent_suffix}_data.json"
227
+ }
228
+ end
228
229
 
229
- def generate_coordination_notes(feature, agents)
230
- notes = []
230
+ def generate_coordination_notes(feature, agents)
231
+ notes = []
231
232
 
232
- notes << "Feature: #{feature[:name] || "unknown"} (#{feature[:category] || "unknown"})"
233
- notes << "Primary Agent: #{agents[:primary_agent]}"
234
- notes << "Specialized Agents: #{agents[:specialized_agents].join(", ")}"
235
- notes << "Priority: #{agents[:analysis_priority]}"
236
- notes << "Focus Areas: #{get_agent_focus_areas(agents[:primary_agent]).join(", ")}"
233
+ notes << "Feature: #{feature[:name] || "unknown"} (#{feature[:category] || "unknown"})"
234
+ notes << "Primary Agent: #{agents[:primary_agent]}"
235
+ notes << "Specialized Agents: #{agents[:specialized_agents].join(", ")}"
236
+ notes << "Priority: #{agents[:analysis_priority]}"
237
+ notes << "Focus Areas: #{get_agent_focus_areas(agents[:primary_agent]).join(", ")}"
237
238
 
238
- notes.join("\n")
239
- end
239
+ notes.join("\n")
240
+ end
240
241
 
241
- def infer_category_from_path(pattern, entry)
242
- case pattern
243
- when "controllers/"
244
- "api"
245
- when "models/"
246
- "data"
247
- when "views/"
248
- "ui"
249
- when "services/"
250
- "core_business"
251
- when "utils/"
252
- "utility"
253
- else
254
- "core_business"
242
+ def infer_category_from_path(pattern, entry)
243
+ case pattern
244
+ when "controllers/"
245
+ "api"
246
+ when "models/"
247
+ "data"
248
+ when "views/"
249
+ "ui"
250
+ when "services/"
251
+ "core_business"
252
+ when "utils/"
253
+ "utility"
254
+ else
255
+ "core_business"
256
+ end
255
257
  end
256
- end
257
258
 
258
- def infer_category_from_file(file, content)
259
- if content.match?(/class.*Controller/i)
260
- "api"
261
- elsif content.match?(/class.*Model/i)
262
- "data"
263
- elsif content.match?(/class.*Service/i)
264
- "core_business"
265
- elsif content.match?(/class.*Util/i)
266
- "utility"
267
- else
268
- "core_business"
259
+ def infer_category_from_file(file, content)
260
+ if content.match?(/class.*Controller/i)
261
+ "api"
262
+ elsif content.match?(/class.*Model/i)
263
+ "data"
264
+ elsif content.match?(/class.*Service/i)
265
+ "core_business"
266
+ elsif content.match?(/class.*Util/i)
267
+ "utility"
268
+ else
269
+ "core_business"
270
+ end
269
271
  end
270
- end
271
272
 
272
- def infer_category(feature)
273
- # Default categorization logic
274
- if feature[:name]&.match?(/controller|api|endpoint/i)
275
- "api"
276
- elsif feature[:name]&.match?(/model|data|entity/i)
277
- "data"
278
- elsif feature[:name]&.match?(/view|ui|component/i)
279
- "ui"
280
- elsif feature[:name]&.match?(/util|helper|tool/i)
281
- "utility"
282
- else
283
- "core_business"
273
+ def infer_category(feature)
274
+ # Default categorization logic
275
+ if feature[:name]&.match?(/controller|api|endpoint/i)
276
+ "api"
277
+ elsif feature[:name]&.match?(/model|data|entity/i)
278
+ "data"
279
+ elsif feature[:name]&.match?(/view|ui|component/i)
280
+ "ui"
281
+ elsif feature[:name]&.match?(/util|helper|tool/i)
282
+ "utility"
283
+ else
284
+ "core_business"
285
+ end
284
286
  end
285
- end
286
287
 
287
- def estimate_complexity(path)
288
- # Simple complexity estimation based on file count and size
289
- file_count = Dir.glob(File.join(path, "**", "*.rb")).count
290
- total_lines = Dir.glob(File.join(path, "**", "*.rb")).sum { |f| File.readlines(f).count }
288
+ def estimate_complexity(path)
289
+ # Simple complexity estimation based on file count and size
290
+ file_count = Dir.glob(File.join(path, "**", "*.rb")).count
291
+ total_lines = Dir.glob(File.join(path, "**", "*.rb")).sum { |f| File.readlines(f).count }
291
292
 
292
- complexity = (file_count * 0.3 + total_lines / 100.0 * 0.7).clamp(0, 10)
293
- (complexity / 10.0).round(2)
294
- end
293
+ complexity = (file_count * 0.3 + total_lines / 100.0 * 0.7).clamp(0, 10)
294
+ (complexity / 10.0).round(2)
295
+ end
295
296
 
296
- def estimate_file_complexity(content)
297
- # Simple complexity estimation based on lines of code
298
- lines = content.lines.count
299
- complexity = (lines / 50.0).clamp(0, 10)
300
- (complexity / 10.0).round(2)
301
- end
297
+ def estimate_file_complexity(content)
298
+ # Simple complexity estimation based on lines of code
299
+ lines = content.lines.count
300
+ complexity = (lines / 50.0).clamp(0, 10)
301
+ (complexity / 10.0).round(2)
302
+ end
302
303
 
303
- def find_feature_dependencies(feature)
304
- # Simplified dependency detection
305
- dependencies = []
304
+ def find_feature_dependencies(feature)
305
+ # Simplified dependency detection
306
+ dependencies = []
306
307
 
307
- if feature[:type] == "file"
308
- content = File.read(feature[:path])
309
- # Look for require/import statements
310
- content.scan(/require\s+['"]([^'"]+)['"]/).each do |match|
311
- dependencies << match[0]
308
+ if feature[:type] == "file"
309
+ content = File.read(feature[:path])
310
+ # Look for require/import statements
311
+ content.scan(/require\s+['"]([^'"]+)['"]/).each do |match|
312
+ dependencies << match[0]
313
+ end
312
314
  end
315
+
316
+ dependencies
313
317
  end
314
318
 
315
- dependencies
316
- end
319
+ def find_feature_dependents(feature, all_features)
320
+ # Find features that depend on this feature
321
+ dependents = []
317
322
 
318
- def find_feature_dependents(feature, all_features)
319
- # Find features that depend on this feature
320
- dependents = []
323
+ all_features.each do |other_feature|
324
+ dependents << other_feature[:name] if other_feature[:dependencies]&.include?(feature[:name])
325
+ end
321
326
 
322
- all_features.each do |other_feature|
323
- dependents << other_feature[:name] if other_feature[:dependencies]&.include?(feature[:name])
327
+ dependents
324
328
  end
325
329
 
326
- dependents
327
- end
330
+ def calculate_coupling_score(feature)
331
+ # Calculate coupling based on dependencies and dependents
332
+ dependency_count = feature[:dependencies]&.count || 0
333
+ dependent_count = feature[:dependents]&.count || 0
328
334
 
329
- def calculate_coupling_score(feature)
330
- # Calculate coupling based on dependencies and dependents
331
- dependency_count = feature[:dependencies]&.count || 0
332
- dependent_count = feature[:dependents]&.count || 0
335
+ coupling = (dependency_count + dependent_count) / 10.0
336
+ coupling.clamp(0, 1).round(2)
337
+ end
333
338
 
334
- coupling = (dependency_count + dependent_count) / 10.0
335
- coupling.clamp(0, 1).round(2)
336
- end
339
+ def calculate_priority(feature)
340
+ # Calculate priority based on business value and complexity
341
+ business_value = feature[:business_value] || 0.5
342
+ complexity = feature[:complexity] || 0.5
337
343
 
338
- def calculate_priority(feature)
339
- # Calculate priority based on business value and complexity
340
- business_value = feature[:business_value] || 0.5
341
- complexity = feature[:complexity] || 0.5
344
+ priority = (business_value * 0.7 + complexity * 0.3)
345
+ priority.clamp(0, 1).round(2)
346
+ end
342
347
 
343
- priority = (business_value * 0.7 + complexity * 0.3)
344
- priority.clamp(0, 1).round(2)
345
- end
348
+ def estimate_business_value(feature)
349
+ # Simple business value estimation based on category and name
350
+ base_value = case feature[:category]
351
+ when "core_business"
352
+ 0.9
353
+ when "api"
354
+ 0.8
355
+ when "data"
356
+ 0.7
357
+ when "ui"
358
+ 0.6
359
+ when "utility"
360
+ 0.3
361
+ else
362
+ 0.5
363
+ end
346
364
 
347
- def estimate_business_value(feature)
348
- # Simple business value estimation based on category and name
349
- base_value = case feature[:category]
350
- when "core_business"
351
- 0.9
352
- when "api"
353
- 0.8
354
- when "data"
355
- 0.7
356
- when "ui"
357
- 0.6
358
- when "utility"
359
- 0.3
360
- else
361
- 0.5
362
- end
365
+ # Adjust based on feature name indicators
366
+ if feature[:name]&.match?(/user|auth|payment|order/i)
367
+ base_value += 0.1
368
+ elsif feature[:name]&.match?(/util|helper|tool/i)
369
+ base_value -= 0.1
370
+ end
363
371
 
364
- # Adjust based on feature name indicators
365
- if feature[:name]&.match?(/user|auth|payment|order/i)
366
- base_value += 0.1
367
- elsif feature[:name]&.match?(/util|helper|tool/i)
368
- base_value -= 0.1
372
+ base_value.clamp(0, 1).round(2)
369
373
  end
370
374
 
371
- base_value.clamp(0, 1).round(2)
372
- end
373
-
374
- def estimate_technical_debt(feature)
375
- # Simple technical debt estimation based on complexity and coupling
376
- complexity = feature[:complexity] || 0.5
377
- coupling = feature[:coupling] || 0.5
375
+ def estimate_technical_debt(feature)
376
+ # Simple technical debt estimation based on complexity and coupling
377
+ complexity = feature[:complexity] || 0.5
378
+ coupling = feature[:coupling] || 0.5
378
379
 
379
- technical_debt = (complexity * 0.6 + coupling * 0.4)
380
- technical_debt.clamp(0, 1).round(2)
381
- end
380
+ technical_debt = (complexity * 0.6 + coupling * 0.4)
381
+ technical_debt.clamp(0, 1).round(2)
382
+ end
382
383
 
383
- def extract_feature_name(file, content, indicator)
384
- # Extract feature name from file content
385
- if content.match?(/class\s+(\w+)/)
386
- ::Regexp.last_match(1)
387
- elsif content.match?(/module\s+(\w+)/)
388
- ::Regexp.last_match(1)
389
- else
384
+ def extract_feature_name(file, content, indicator)
385
+ # Extract feature name from file content
386
+ if content.match?(/class\s+(\w+)/)
387
+ ::Regexp.last_match(1)
388
+ elsif content.match?(/module\s+(\w+)/)
389
+ ::Regexp.last_match(1)
390
+ else
391
+ File.basename(file, ".*").capitalize
392
+ end
393
+ rescue
394
+ # Fallback to filename if extraction fails
390
395
  File.basename(file, ".*").capitalize
391
396
  end
392
- rescue
393
- # Fallback to filename if extraction fails
394
- File.basename(file, ".*").capitalize
395
397
  end
396
398
  end
397
399
  end