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.
- checksums.yaml +4 -4
- data/lib/aidp/analyze/feature_analyzer.rb +322 -320
- data/lib/aidp/auto_update/coordinator.rb +97 -7
- data/lib/aidp/auto_update.rb +0 -12
- data/lib/aidp/cli/devcontainer_commands.rb +0 -5
- data/lib/aidp/cli.rb +2 -1
- data/lib/aidp/comment_consolidator.rb +78 -0
- data/lib/aidp/concurrency.rb +0 -3
- data/lib/aidp/config.rb +0 -1
- data/lib/aidp/config_paths.rb +71 -0
- data/lib/aidp/execute/work_loop_runner.rb +324 -15
- data/lib/aidp/harness/ai_filter_factory.rb +285 -0
- data/lib/aidp/harness/config_schema.rb +97 -1
- data/lib/aidp/harness/config_validator.rb +1 -1
- data/lib/aidp/harness/configuration.rb +61 -5
- data/lib/aidp/harness/filter_definition.rb +212 -0
- data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
- data/lib/aidp/harness/output_filter.rb +50 -25
- data/lib/aidp/harness/output_filter_config.rb +129 -0
- data/lib/aidp/harness/provider_manager.rb +90 -2
- data/lib/aidp/harness/runner.rb +0 -11
- data/lib/aidp/harness/test_runner.rb +179 -41
- data/lib/aidp/harness/thinking_depth_manager.rb +16 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +0 -2
- data/lib/aidp/loader.rb +195 -0
- data/lib/aidp/metadata/compiler.rb +29 -17
- data/lib/aidp/metadata/query.rb +1 -1
- data/lib/aidp/metadata/scanner.rb +8 -1
- data/lib/aidp/metadata/tool_metadata.rb +13 -13
- data/lib/aidp/metadata/validator.rb +10 -0
- data/lib/aidp/metadata.rb +16 -0
- data/lib/aidp/pr_worktree_manager.rb +2 -2
- data/lib/aidp/provider_manager.rb +1 -7
- data/lib/aidp/setup/wizard.rb +279 -9
- data/lib/aidp/skills.rb +0 -5
- data/lib/aidp/storage/csv_storage.rb +3 -0
- data/lib/aidp/style_guide/selector.rb +360 -0
- data/lib/aidp/tooling_detector.rb +283 -16
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/change_request_processor.rb +152 -14
- data/lib/aidp/watch/repository_client.rb +41 -0
- data/lib/aidp/watch/runner.rb +29 -18
- data/lib/aidp/watch.rb +5 -7
- data/lib/aidp/workstream_cleanup.rb +0 -2
- data/lib/aidp/workstream_executor.rb +0 -4
- data/lib/aidp/worktree.rb +0 -1
- data/lib/aidp.rb +21 -106
- metadata +72 -36
- data/lib/aidp/config/paths.rb +0 -131
|
@@ -3,110 +3,126 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
|
|
5
5
|
module Aidp
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
12
|
+
# Detect and categorize features in the codebase
|
|
13
|
+
def detect_features
|
|
14
|
+
features = []
|
|
29
15
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
22
|
+
# Analyze feature relationships
|
|
23
|
+
analyze_feature_relationships(features)
|
|
63
24
|
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
next unless Dir.exist?(pattern_path)
|
|
28
|
+
features
|
|
29
|
+
end
|
|
75
30
|
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
170
|
-
|
|
170
|
+
def determine_specialized_agents(feature)
|
|
171
|
+
agents = []
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
179
|
-
|
|
179
|
+
agents.uniq
|
|
180
|
+
end
|
|
180
181
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
230
|
+
def generate_coordination_notes(feature, agents)
|
|
231
|
+
notes = []
|
|
231
232
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
239
|
-
|
|
239
|
+
notes.join("\n")
|
|
240
|
+
end
|
|
240
241
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
def find_feature_dependencies(feature)
|
|
305
|
+
# Simplified dependency detection
|
|
306
|
+
dependencies = []
|
|
306
307
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
316
|
-
|
|
319
|
+
def find_feature_dependents(feature, all_features)
|
|
320
|
+
# Find features that depend on this feature
|
|
321
|
+
dependents = []
|
|
317
322
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
323
|
+
all_features.each do |other_feature|
|
|
324
|
+
dependents << other_feature[:name] if other_feature[:dependencies]&.include?(feature[:name])
|
|
325
|
+
end
|
|
321
326
|
|
|
322
|
-
|
|
323
|
-
dependents << other_feature[:name] if other_feature[:dependencies]&.include?(feature[:name])
|
|
327
|
+
dependents
|
|
324
328
|
end
|
|
325
329
|
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
380
|
+
technical_debt = (complexity * 0.6 + coupling * 0.4)
|
|
381
|
+
technical_debt.clamp(0, 1).round(2)
|
|
382
|
+
end
|
|
382
383
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|