aidp 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 +7 -0
- data/LICENSE +21 -0
- data/README.md +210 -0
- data/bin/aidp +5 -0
- data/lib/aidp/analyze/agent_personas.rb +71 -0
- data/lib/aidp/analyze/agent_tool_executor.rb +445 -0
- data/lib/aidp/analyze/data_retention_manager.rb +426 -0
- data/lib/aidp/analyze/database.rb +243 -0
- data/lib/aidp/analyze/dependencies.rb +335 -0
- data/lib/aidp/analyze/error_handler.rb +486 -0
- data/lib/aidp/analyze/export_manager.rb +425 -0
- data/lib/aidp/analyze/feature_analyzer.rb +397 -0
- data/lib/aidp/analyze/focus_guidance.rb +517 -0
- data/lib/aidp/analyze/incremental_analyzer.rb +543 -0
- data/lib/aidp/analyze/language_analysis_strategies.rb +897 -0
- data/lib/aidp/analyze/large_analysis_progress.rb +504 -0
- data/lib/aidp/analyze/memory_manager.rb +365 -0
- data/lib/aidp/analyze/parallel_processor.rb +460 -0
- data/lib/aidp/analyze/performance_optimizer.rb +694 -0
- data/lib/aidp/analyze/prioritizer.rb +402 -0
- data/lib/aidp/analyze/progress.rb +75 -0
- data/lib/aidp/analyze/progress_visualizer.rb +320 -0
- data/lib/aidp/analyze/report_generator.rb +582 -0
- data/lib/aidp/analyze/repository_chunker.rb +702 -0
- data/lib/aidp/analyze/ruby_maat_integration.rb +572 -0
- data/lib/aidp/analyze/runner.rb +245 -0
- data/lib/aidp/analyze/static_analysis_detector.rb +577 -0
- data/lib/aidp/analyze/steps.rb +53 -0
- data/lib/aidp/analyze/storage.rb +600 -0
- data/lib/aidp/analyze/tool_configuration.rb +456 -0
- data/lib/aidp/analyze/tool_modernization.rb +750 -0
- data/lib/aidp/execute/progress.rb +76 -0
- data/lib/aidp/execute/runner.rb +135 -0
- data/lib/aidp/execute/steps.rb +113 -0
- data/lib/aidp/shared/cli.rb +117 -0
- data/lib/aidp/shared/config.rb +35 -0
- data/lib/aidp/shared/project_detector.rb +119 -0
- data/lib/aidp/shared/providers/anthropic.rb +26 -0
- data/lib/aidp/shared/providers/base.rb +17 -0
- data/lib/aidp/shared/providers/cursor.rb +102 -0
- data/lib/aidp/shared/providers/gemini.rb +26 -0
- data/lib/aidp/shared/providers/macos_ui.rb +26 -0
- data/lib/aidp/shared/sync.rb +15 -0
- data/lib/aidp/shared/util.rb +41 -0
- data/lib/aidp/shared/version.rb +7 -0
- data/lib/aidp/shared/workspace.rb +21 -0
- data/lib/aidp.rb +53 -0
- data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +100 -0
- data/templates/ANALYZE/02_ARCHITECTURE_ANALYSIS.md +151 -0
- data/templates/ANALYZE/03_TEST_ANALYSIS.md +182 -0
- data/templates/ANALYZE/04_FUNCTIONALITY_ANALYSIS.md +200 -0
- data/templates/ANALYZE/05_DOCUMENTATION_ANALYSIS.md +202 -0
- data/templates/ANALYZE/06_STATIC_ANALYSIS.md +233 -0
- data/templates/ANALYZE/07_REFACTORING_RECOMMENDATIONS.md +316 -0
- data/templates/COMMON/AGENT_BASE.md +129 -0
- data/templates/COMMON/CONVENTIONS.md +19 -0
- data/templates/COMMON/TEMPLATES/ADR_TEMPLATE.md +21 -0
- data/templates/COMMON/TEMPLATES/DOMAIN_CHARTER.md +27 -0
- data/templates/COMMON/TEMPLATES/EVENT_EXAMPLE.yaml +16 -0
- data/templates/COMMON/TEMPLATES/MERMAID_C4.md +46 -0
- data/templates/COMMON/TEMPLATES/OPENAPI_STUB.yaml +11 -0
- data/templates/EXECUTE/00_PRD.md +36 -0
- data/templates/EXECUTE/01_NFRS.md +27 -0
- data/templates/EXECUTE/02A_ARCH_GATE_QUESTIONS.md +13 -0
- data/templates/EXECUTE/02_ARCHITECTURE.md +42 -0
- data/templates/EXECUTE/03_ADR_FACTORY.md +22 -0
- data/templates/EXECUTE/04_DOMAIN_DECOMPOSITION.md +24 -0
- data/templates/EXECUTE/05_CONTRACTS.md +27 -0
- data/templates/EXECUTE/06_THREAT_MODEL.md +23 -0
- data/templates/EXECUTE/07_TEST_PLAN.md +24 -0
- data/templates/EXECUTE/08_TASKS.md +29 -0
- data/templates/EXECUTE/09_SCAFFOLDING_DEVEX.md +25 -0
- data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +30 -0
- data/templates/EXECUTE/11_STATIC_ANALYSIS.md +22 -0
- data/templates/EXECUTE/12_OBSERVABILITY_SLOS.md +21 -0
- data/templates/EXECUTE/13_DELIVERY_ROLLOUT.md +21 -0
- data/templates/EXECUTE/14_DOCS_PORTAL.md +23 -0
- data/templates/EXECUTE/15_POST_RELEASE.md +25 -0
- metadata +301 -0
@@ -0,0 +1,397 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
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)
|
26
|
+
|
27
|
+
features
|
28
|
+
end
|
29
|
+
|
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
|
39
|
+
|
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
|
61
|
+
|
62
|
+
private
|
63
|
+
|
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
|
+
]
|
71
|
+
|
72
|
+
feature_patterns.each do |pattern|
|
73
|
+
pattern_path = File.join(@project_dir, pattern)
|
74
|
+
next unless Dir.exist?(pattern_path)
|
75
|
+
|
76
|
+
Dir.entries(pattern_path).each do |entry|
|
77
|
+
next if entry.start_with?(".") || entry == ".."
|
78
|
+
|
79
|
+
feature_path = File.join(pattern_path, entry)
|
80
|
+
next unless Dir.exist?(feature_path)
|
81
|
+
|
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
|
+
}
|
89
|
+
end
|
90
|
+
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
|
+
|
109
|
+
feature_name = extract_feature_name(file, content, indicator)
|
110
|
+
features << {
|
111
|
+
name: feature_name,
|
112
|
+
type: "file",
|
113
|
+
path: file,
|
114
|
+
category: infer_category_from_file(file, content),
|
115
|
+
complexity: estimate_file_complexity(content)
|
116
|
+
}
|
117
|
+
feature_found = true
|
118
|
+
break
|
119
|
+
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
|
+
end
|
133
|
+
end
|
134
|
+
|
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)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
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)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
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"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def determine_specialized_agents(feature)
|
170
|
+
agents = []
|
171
|
+
|
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
|
177
|
+
|
178
|
+
agents.uniq
|
179
|
+
end
|
180
|
+
|
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"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
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"]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
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+/, "_")
|
221
|
+
|
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
|
228
|
+
|
229
|
+
def generate_coordination_notes(feature, agents)
|
230
|
+
notes = []
|
231
|
+
|
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(", ")}"
|
237
|
+
|
238
|
+
notes.join("\n")
|
239
|
+
end
|
240
|
+
|
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"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
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"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
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"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
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 }
|
291
|
+
|
292
|
+
complexity = (file_count * 0.3 + total_lines / 100.0 * 0.7).clamp(0, 10)
|
293
|
+
(complexity / 10.0).round(2)
|
294
|
+
end
|
295
|
+
|
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
|
302
|
+
|
303
|
+
def find_feature_dependencies(feature)
|
304
|
+
# Simplified dependency detection
|
305
|
+
dependencies = []
|
306
|
+
|
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]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
dependencies
|
316
|
+
end
|
317
|
+
|
318
|
+
def find_feature_dependents(feature, all_features)
|
319
|
+
# Find features that depend on this feature
|
320
|
+
dependents = []
|
321
|
+
|
322
|
+
all_features.each do |other_feature|
|
323
|
+
dependents << other_feature[:name] if other_feature[:dependencies]&.include?(feature[:name])
|
324
|
+
end
|
325
|
+
|
326
|
+
dependents
|
327
|
+
end
|
328
|
+
|
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
|
333
|
+
|
334
|
+
coupling = (dependency_count + dependent_count) / 10.0
|
335
|
+
coupling.clamp(0, 1).round(2)
|
336
|
+
end
|
337
|
+
|
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
|
342
|
+
|
343
|
+
priority = (business_value * 0.7 + complexity * 0.3)
|
344
|
+
priority.clamp(0, 1).round(2)
|
345
|
+
end
|
346
|
+
|
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
|
363
|
+
|
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
|
369
|
+
end
|
370
|
+
|
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
|
378
|
+
|
379
|
+
technical_debt = (complexity * 0.6 + coupling * 0.4)
|
380
|
+
technical_debt.clamp(0, 1).round(2)
|
381
|
+
end
|
382
|
+
|
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
|
390
|
+
File.basename(file, ".*").capitalize
|
391
|
+
end
|
392
|
+
rescue
|
393
|
+
# Fallback to filename if extraction fails
|
394
|
+
File.basename(file, ".*").capitalize
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|