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,582 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
require "json"
|
5
|
+
require "yaml"
|
6
|
+
|
7
|
+
module Aidp
|
8
|
+
class ReportGenerator
|
9
|
+
# Default report templates
|
10
|
+
DEFAULT_TEMPLATES = {
|
11
|
+
"analysis_summary" => "templates/COMMON/ANALYSIS_SUMMARY.md.erb",
|
12
|
+
"repository_analysis" => "templates/ANALYZE/REPOSITORY_ANALYSIS.md.erb",
|
13
|
+
"architecture_analysis" => "templates/ANALYZE/ARCHITECTURE_ANALYSIS.md.erb",
|
14
|
+
"functionality_analysis" => "templates/ANALYZE/FUNCTIONALITY_ANALYSIS.md.erb",
|
15
|
+
"static_analysis" => "templates/ANALYZE/STATIC_ANALYSIS.md.erb",
|
16
|
+
"refactoring_recommendations" => "templates/ANALYZE/REFACTORING_RECOMMENDATIONS.md.erb"
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def initialize(project_dir = Dir.pwd, config = {})
|
20
|
+
@project_dir = project_dir
|
21
|
+
@config = config
|
22
|
+
@output_dir = config[:output_dir] || File.join(project_dir, "docs")
|
23
|
+
@templates_dir = config[:templates_dir] || File.join(project_dir, "templates")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Generate comprehensive analysis report
|
27
|
+
def generate_analysis_report(analysis_data, options = {})
|
28
|
+
report_data = prepare_report_data(analysis_data)
|
29
|
+
template_name = options[:template] || "analysis_summary"
|
30
|
+
|
31
|
+
template_path = find_template(template_name)
|
32
|
+
return nil unless template_path
|
33
|
+
|
34
|
+
report_content = render_template(template_path, report_data)
|
35
|
+
output_path = save_report(report_content, template_name, options)
|
36
|
+
|
37
|
+
{
|
38
|
+
content: report_content,
|
39
|
+
path: output_path,
|
40
|
+
template: template_name,
|
41
|
+
generated_at: Time.now
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Generate step-specific report
|
46
|
+
def generate_step_report(step_name, step_data, options = {})
|
47
|
+
report_data = prepare_step_data(step_name, step_data)
|
48
|
+
template_name = "#{step_name}_analysis"
|
49
|
+
|
50
|
+
template_path = find_template(template_name)
|
51
|
+
return nil unless template_path
|
52
|
+
|
53
|
+
report_content = render_template(template_path, report_data)
|
54
|
+
output_path = save_report(report_content, template_name, options)
|
55
|
+
|
56
|
+
{
|
57
|
+
content: report_content,
|
58
|
+
path: output_path,
|
59
|
+
step: step_name,
|
60
|
+
generated_at: Time.now
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Generate executive summary report
|
65
|
+
def generate_executive_summary(analysis_data, options = {})
|
66
|
+
summary_data = prepare_executive_summary_data(analysis_data)
|
67
|
+
template_path = find_template("executive_summary")
|
68
|
+
|
69
|
+
return nil unless template_path
|
70
|
+
|
71
|
+
summary_content = render_template(template_path, summary_data)
|
72
|
+
output_path = save_report(summary_content, "executive_summary", options)
|
73
|
+
|
74
|
+
{
|
75
|
+
content: summary_content,
|
76
|
+
path: output_path,
|
77
|
+
type: "executive_summary",
|
78
|
+
generated_at: Time.now
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
# Generate technical report
|
83
|
+
def generate_technical_report(analysis_data, options = {})
|
84
|
+
technical_data = prepare_technical_report_data(analysis_data)
|
85
|
+
template_path = find_template("technical_report")
|
86
|
+
|
87
|
+
return nil unless template_path
|
88
|
+
|
89
|
+
technical_content = render_template(template_path, technical_data)
|
90
|
+
output_path = save_report(technical_content, "technical_report", options)
|
91
|
+
|
92
|
+
{
|
93
|
+
content: technical_content,
|
94
|
+
path: output_path,
|
95
|
+
type: "technical_report",
|
96
|
+
generated_at: Time.now
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
# Generate comparison report
|
101
|
+
def generate_comparison_report(before_data, after_data, options = {})
|
102
|
+
comparison_data = prepare_comparison_data(before_data, after_data)
|
103
|
+
template_path = find_template("comparison_report")
|
104
|
+
|
105
|
+
return nil unless template_path
|
106
|
+
|
107
|
+
comparison_content = render_template(template_path, comparison_data)
|
108
|
+
output_path = save_report(comparison_content, "comparison_report", options)
|
109
|
+
|
110
|
+
{
|
111
|
+
content: comparison_content,
|
112
|
+
path: output_path,
|
113
|
+
type: "comparison_report",
|
114
|
+
generated_at: Time.now
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Generate custom report from template
|
119
|
+
def generate_custom_report(template_name, data, options = {})
|
120
|
+
template_path = find_template(template_name)
|
121
|
+
return nil unless template_path
|
122
|
+
|
123
|
+
report_content = render_template(template_path, data)
|
124
|
+
output_path = save_report(report_content, template_name, options)
|
125
|
+
|
126
|
+
{
|
127
|
+
content: report_content,
|
128
|
+
path: output_path,
|
129
|
+
template: template_name,
|
130
|
+
generated_at: Time.now
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Generate report index
|
135
|
+
def generate_report_index(reports, options = {})
|
136
|
+
index_data = {
|
137
|
+
reports: reports,
|
138
|
+
generated_at: Time.now,
|
139
|
+
project_name: File.basename(@project_dir)
|
140
|
+
}
|
141
|
+
|
142
|
+
template_path = find_template("report_index")
|
143
|
+
return nil unless template_path
|
144
|
+
|
145
|
+
index_content = render_template(template_path, index_data)
|
146
|
+
output_path = save_report(index_content, "report_index", options)
|
147
|
+
|
148
|
+
{
|
149
|
+
content: index_content,
|
150
|
+
path: output_path,
|
151
|
+
type: "report_index",
|
152
|
+
generated_at: Time.now
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def prepare_report_data(analysis_data)
|
159
|
+
{
|
160
|
+
project_name: File.basename(@project_dir),
|
161
|
+
project_path: @project_dir,
|
162
|
+
analysis_data: analysis_data,
|
163
|
+
generated_at: Time.now,
|
164
|
+
config: @config,
|
165
|
+
metadata: generate_metadata(analysis_data)
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
def prepare_step_data(step_name, step_data)
|
170
|
+
{
|
171
|
+
step_name: step_name,
|
172
|
+
step_data: step_data,
|
173
|
+
project_name: File.basename(@project_dir),
|
174
|
+
project_path: @project_dir,
|
175
|
+
generated_at: Time.now,
|
176
|
+
config: @config
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
def prepare_executive_summary_data(analysis_data)
|
181
|
+
{
|
182
|
+
project_name: File.basename(@project_dir),
|
183
|
+
project_path: @project_dir,
|
184
|
+
generated_at: Time.now,
|
185
|
+
key_findings: extract_key_findings(analysis_data),
|
186
|
+
recommendations: extract_recommendations(analysis_data),
|
187
|
+
risk_assessment: assess_risks(analysis_data),
|
188
|
+
effort_estimation: estimate_effort(analysis_data)
|
189
|
+
}
|
190
|
+
end
|
191
|
+
|
192
|
+
def prepare_technical_report_data(analysis_data)
|
193
|
+
{
|
194
|
+
project_name: File.basename(@project_dir),
|
195
|
+
project_path: @project_dir,
|
196
|
+
generated_at: Time.now,
|
197
|
+
detailed_analysis: analysis_data,
|
198
|
+
technical_debt: calculate_technical_debt(analysis_data),
|
199
|
+
code_quality_metrics: extract_quality_metrics(analysis_data),
|
200
|
+
security_analysis: extract_security_analysis(analysis_data),
|
201
|
+
performance_analysis: extract_performance_analysis(analysis_data)
|
202
|
+
}
|
203
|
+
end
|
204
|
+
|
205
|
+
def prepare_comparison_data(before_data, after_data)
|
206
|
+
{
|
207
|
+
project_name: File.basename(@project_dir),
|
208
|
+
project_path: @project_dir,
|
209
|
+
generated_at: Time.now,
|
210
|
+
before_analysis: before_data,
|
211
|
+
after_analysis: after_data,
|
212
|
+
improvements: calculate_improvements(before_data, after_data),
|
213
|
+
regressions: identify_regressions(before_data, after_data),
|
214
|
+
metrics_comparison: compare_metrics(before_data, after_data)
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
def find_template(template_name)
|
219
|
+
# Check for custom template first
|
220
|
+
custom_template = File.join(@templates_dir, "#{template_name}.md.erb")
|
221
|
+
return custom_template if File.exist?(custom_template)
|
222
|
+
|
223
|
+
# Check default templates
|
224
|
+
default_template = DEFAULT_TEMPLATES[template_name]
|
225
|
+
return File.join(@project_dir, default_template) if default_template && File.exist?(File.join(@project_dir,
|
226
|
+
default_template))
|
227
|
+
|
228
|
+
# Check common templates
|
229
|
+
common_template = File.join(@project_dir, "templates", "COMMON", "#{template_name}.md.erb")
|
230
|
+
return common_template if File.exist?(common_template)
|
231
|
+
|
232
|
+
# Check analyze templates
|
233
|
+
analyze_template = File.join(@project_dir, "templates", "ANALYZE", "#{template_name}.md.erb")
|
234
|
+
return analyze_template if File.exist?(analyze_template)
|
235
|
+
|
236
|
+
nil
|
237
|
+
end
|
238
|
+
|
239
|
+
def render_template(template_path, data)
|
240
|
+
template_content = File.read(template_path)
|
241
|
+
erb = ERB.new(template_content, trim_mode: "-")
|
242
|
+
erb.result(binding)
|
243
|
+
end
|
244
|
+
|
245
|
+
def save_report(content, report_type, options)
|
246
|
+
timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
|
247
|
+
filename = options[:filename] || "#{report_type}_#{timestamp}.md"
|
248
|
+
output_path = File.join(@output_dir, filename)
|
249
|
+
|
250
|
+
# Ensure output directory exists
|
251
|
+
FileUtils.mkdir_p(@output_dir)
|
252
|
+
|
253
|
+
# Write report content
|
254
|
+
File.write(output_path, content)
|
255
|
+
|
256
|
+
output_path
|
257
|
+
end
|
258
|
+
|
259
|
+
def generate_metadata(analysis_data)
|
260
|
+
{
|
261
|
+
total_files_analyzed: count_analyzed_files(analysis_data),
|
262
|
+
analysis_duration: calculate_analysis_duration(analysis_data),
|
263
|
+
tools_used: extract_tools_used(analysis_data),
|
264
|
+
languages_detected: extract_languages(analysis_data),
|
265
|
+
frameworks_detected: extract_frameworks(analysis_data)
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
269
|
+
def extract_key_findings(analysis_data)
|
270
|
+
findings = []
|
271
|
+
|
272
|
+
# Extract findings from different analysis types
|
273
|
+
if analysis_data[:repository_analysis]
|
274
|
+
findings.concat(extract_repository_findings(analysis_data[:repository_analysis]))
|
275
|
+
end
|
276
|
+
|
277
|
+
if analysis_data[:architecture_analysis]
|
278
|
+
findings.concat(extract_architecture_findings(analysis_data[:architecture_analysis]))
|
279
|
+
end
|
280
|
+
|
281
|
+
if analysis_data[:static_analysis]
|
282
|
+
findings.concat(extract_static_analysis_findings(analysis_data[:static_analysis]))
|
283
|
+
end
|
284
|
+
|
285
|
+
# Prioritize findings by severity
|
286
|
+
findings.sort_by { |finding| finding[:severity] || "medium" }
|
287
|
+
end
|
288
|
+
|
289
|
+
def extract_recommendations(analysis_data)
|
290
|
+
recommendations = []
|
291
|
+
|
292
|
+
# Extract recommendations from different analysis types
|
293
|
+
recommendations.concat(analysis_data[:refactoring_recommendations]) if analysis_data[:refactoring_recommendations]
|
294
|
+
|
295
|
+
if analysis_data[:modernization_recommendations]
|
296
|
+
recommendations.concat(analysis_data[:modernization_recommendations])
|
297
|
+
end
|
298
|
+
|
299
|
+
# Prioritize recommendations by impact
|
300
|
+
recommendations.sort_by { |rec| rec[:impact] || "medium" }
|
301
|
+
end
|
302
|
+
|
303
|
+
def assess_risks(analysis_data)
|
304
|
+
risks = []
|
305
|
+
|
306
|
+
# Assess security risks
|
307
|
+
risks.concat(assess_security_risks(analysis_data[:security_analysis])) if analysis_data[:security_analysis]
|
308
|
+
|
309
|
+
# Assess technical debt risks
|
310
|
+
if analysis_data[:technical_debt_analysis]
|
311
|
+
risks.concat(assess_technical_debt_risks(analysis_data[:technical_debt_analysis]))
|
312
|
+
end
|
313
|
+
|
314
|
+
# Assess maintenance risks
|
315
|
+
if analysis_data[:maintenance_analysis]
|
316
|
+
risks.concat(assess_maintenance_risks(analysis_data[:maintenance_analysis]))
|
317
|
+
end
|
318
|
+
|
319
|
+
risks
|
320
|
+
end
|
321
|
+
|
322
|
+
def estimate_effort(analysis_data)
|
323
|
+
effort = {
|
324
|
+
refactoring_effort: estimate_refactoring_effort(analysis_data),
|
325
|
+
modernization_effort: estimate_modernization_effort(analysis_data),
|
326
|
+
testing_effort: estimate_testing_effort(analysis_data),
|
327
|
+
documentation_effort: estimate_documentation_effort(analysis_data)
|
328
|
+
}
|
329
|
+
|
330
|
+
effort[:total_effort] = effort.values.sum
|
331
|
+
effort
|
332
|
+
end
|
333
|
+
|
334
|
+
def calculate_technical_debt(analysis_data)
|
335
|
+
debt = {
|
336
|
+
code_quality_debt: calculate_code_quality_debt(analysis_data),
|
337
|
+
architecture_debt: calculate_architecture_debt(analysis_data),
|
338
|
+
testing_debt: calculate_testing_debt(analysis_data),
|
339
|
+
documentation_debt: calculate_documentation_debt(analysis_data),
|
340
|
+
security_debt: calculate_security_debt(analysis_data)
|
341
|
+
}
|
342
|
+
|
343
|
+
debt[:total_debt] = debt.values.sum
|
344
|
+
debt
|
345
|
+
end
|
346
|
+
|
347
|
+
def extract_quality_metrics(analysis_data)
|
348
|
+
metrics = {}
|
349
|
+
|
350
|
+
if analysis_data[:static_analysis]
|
351
|
+
metrics[:code_quality] = extract_code_quality_metrics(analysis_data[:static_analysis])
|
352
|
+
end
|
353
|
+
|
354
|
+
if analysis_data[:test_coverage]
|
355
|
+
metrics[:test_coverage] = extract_test_coverage_metrics(analysis_data[:test_coverage])
|
356
|
+
end
|
357
|
+
|
358
|
+
if analysis_data[:complexity_analysis]
|
359
|
+
metrics[:complexity] = extract_complexity_metrics(analysis_data[:complexity_analysis])
|
360
|
+
end
|
361
|
+
|
362
|
+
metrics
|
363
|
+
end
|
364
|
+
|
365
|
+
def extract_security_analysis(analysis_data)
|
366
|
+
security = {}
|
367
|
+
|
368
|
+
if analysis_data[:security_scan]
|
369
|
+
security[:vulnerabilities] = analysis_data[:security_scan][:vulnerabilities] || []
|
370
|
+
security[:risk_level] = analysis_data[:security_scan][:risk_level] || "unknown"
|
371
|
+
end
|
372
|
+
|
373
|
+
if analysis_data[:dependency_analysis]
|
374
|
+
security[:dependency_vulnerabilities] = analysis_data[:dependency_analysis][:vulnerabilities] || []
|
375
|
+
end
|
376
|
+
|
377
|
+
security
|
378
|
+
end
|
379
|
+
|
380
|
+
def extract_performance_analysis(analysis_data)
|
381
|
+
performance = {}
|
382
|
+
|
383
|
+
if analysis_data[:performance_analysis]
|
384
|
+
performance[:bottlenecks] = analysis_data[:performance_analysis][:bottlenecks] || []
|
385
|
+
performance[:optimization_opportunities] = analysis_data[:performance_analysis][:optimizations] || []
|
386
|
+
end
|
387
|
+
|
388
|
+
performance
|
389
|
+
end
|
390
|
+
|
391
|
+
def calculate_improvements(before_data, after_data)
|
392
|
+
improvements = []
|
393
|
+
|
394
|
+
# Compare metrics
|
395
|
+
before_metrics = extract_metrics(before_data)
|
396
|
+
after_metrics = extract_metrics(after_data)
|
397
|
+
|
398
|
+
after_metrics.each do |metric, value|
|
399
|
+
before_value = before_metrics[metric]
|
400
|
+
next unless before_value && value > before_value
|
401
|
+
|
402
|
+
improvements << {
|
403
|
+
metric: metric,
|
404
|
+
improvement: value - before_value,
|
405
|
+
percentage: ((value - before_value) / before_value * 100).round(2)
|
406
|
+
}
|
407
|
+
end
|
408
|
+
|
409
|
+
improvements
|
410
|
+
end
|
411
|
+
|
412
|
+
def identify_regressions(before_data, after_data)
|
413
|
+
regressions = []
|
414
|
+
|
415
|
+
# Compare metrics
|
416
|
+
before_metrics = extract_metrics(before_data)
|
417
|
+
after_metrics = extract_metrics(after_data)
|
418
|
+
|
419
|
+
after_metrics.each do |metric, value|
|
420
|
+
before_value = before_metrics[metric]
|
421
|
+
next unless before_value && value < before_value
|
422
|
+
|
423
|
+
regressions << {
|
424
|
+
metric: metric,
|
425
|
+
regression: before_value - value,
|
426
|
+
percentage: ((before_value - value) / before_value * 100).round(2)
|
427
|
+
}
|
428
|
+
end
|
429
|
+
|
430
|
+
regressions
|
431
|
+
end
|
432
|
+
|
433
|
+
def compare_metrics(before_data, after_data)
|
434
|
+
comparison = {}
|
435
|
+
|
436
|
+
before_metrics = extract_metrics(before_data)
|
437
|
+
after_metrics = extract_metrics(after_data)
|
438
|
+
|
439
|
+
all_metrics = (before_metrics.keys + after_metrics.keys).uniq
|
440
|
+
|
441
|
+
all_metrics.each do |metric|
|
442
|
+
comparison[metric] = {
|
443
|
+
before: before_metrics[metric],
|
444
|
+
after: after_metrics[metric],
|
445
|
+
change: (after_metrics[metric] && before_metrics[metric]) ? after_metrics[metric] - before_metrics[metric] : nil
|
446
|
+
}
|
447
|
+
end
|
448
|
+
|
449
|
+
comparison
|
450
|
+
end
|
451
|
+
|
452
|
+
# Helper methods for data extraction
|
453
|
+
def count_analyzed_files(analysis_data)
|
454
|
+
count = 0
|
455
|
+
analysis_data.each_value do |data|
|
456
|
+
count += data[:files_analyzed] if data.is_a?(Hash) && data[:files_analyzed]
|
457
|
+
end
|
458
|
+
count
|
459
|
+
end
|
460
|
+
|
461
|
+
def calculate_analysis_duration(analysis_data)
|
462
|
+
duration = 0
|
463
|
+
analysis_data.each_value do |data|
|
464
|
+
duration += data[:duration] if data.is_a?(Hash) && data[:duration]
|
465
|
+
end
|
466
|
+
duration
|
467
|
+
end
|
468
|
+
|
469
|
+
def extract_tools_used(analysis_data)
|
470
|
+
tools = []
|
471
|
+
analysis_data.each_value do |data|
|
472
|
+
tools.concat(data[:tools_used]) if data.is_a?(Hash) && data[:tools_used]
|
473
|
+
end
|
474
|
+
tools.uniq
|
475
|
+
end
|
476
|
+
|
477
|
+
def extract_languages(analysis_data)
|
478
|
+
languages = []
|
479
|
+
analysis_data.each_value do |data|
|
480
|
+
languages << data[:language] if data.is_a?(Hash) && data[:language]
|
481
|
+
end
|
482
|
+
languages.uniq
|
483
|
+
end
|
484
|
+
|
485
|
+
def extract_frameworks(analysis_data)
|
486
|
+
frameworks = []
|
487
|
+
analysis_data.each_value do |data|
|
488
|
+
frameworks << data[:framework] if data.is_a?(Hash) && data[:framework]
|
489
|
+
end
|
490
|
+
frameworks.uniq
|
491
|
+
end
|
492
|
+
|
493
|
+
def extract_metrics(data)
|
494
|
+
metrics = {}
|
495
|
+
|
496
|
+
if data.is_a?(Hash)
|
497
|
+
data.each do |key, value|
|
498
|
+
if value.is_a?(Numeric)
|
499
|
+
metrics[key] = value
|
500
|
+
elsif value.is_a?(Hash)
|
501
|
+
metrics.merge!(extract_metrics(value))
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
metrics
|
507
|
+
end
|
508
|
+
|
509
|
+
# Placeholder methods for specific analysis extractions
|
510
|
+
def extract_repository_findings(data)
|
511
|
+
data[:findings] || []
|
512
|
+
end
|
513
|
+
|
514
|
+
def extract_architecture_findings(data)
|
515
|
+
data[:findings] || []
|
516
|
+
end
|
517
|
+
|
518
|
+
def extract_static_analysis_findings(data)
|
519
|
+
data[:findings] || []
|
520
|
+
end
|
521
|
+
|
522
|
+
def assess_security_risks(data)
|
523
|
+
data[:risks] || []
|
524
|
+
end
|
525
|
+
|
526
|
+
def assess_technical_debt_risks(data)
|
527
|
+
data[:risks] || []
|
528
|
+
end
|
529
|
+
|
530
|
+
def assess_maintenance_risks(data)
|
531
|
+
data[:risks] || []
|
532
|
+
end
|
533
|
+
|
534
|
+
def estimate_refactoring_effort(data)
|
535
|
+
data[:effort] || 0
|
536
|
+
end
|
537
|
+
|
538
|
+
def estimate_modernization_effort(data)
|
539
|
+
data[:effort] || 0
|
540
|
+
end
|
541
|
+
|
542
|
+
def estimate_testing_effort(data)
|
543
|
+
data[:effort] || 0
|
544
|
+
end
|
545
|
+
|
546
|
+
def estimate_documentation_effort(data)
|
547
|
+
data[:effort] || 0
|
548
|
+
end
|
549
|
+
|
550
|
+
def calculate_code_quality_debt(data)
|
551
|
+
data[:debt] || 0
|
552
|
+
end
|
553
|
+
|
554
|
+
def calculate_architecture_debt(data)
|
555
|
+
data[:debt] || 0
|
556
|
+
end
|
557
|
+
|
558
|
+
def calculate_testing_debt(data)
|
559
|
+
data[:debt] || 0
|
560
|
+
end
|
561
|
+
|
562
|
+
def calculate_documentation_debt(data)
|
563
|
+
data[:debt] || 0
|
564
|
+
end
|
565
|
+
|
566
|
+
def calculate_security_debt(data)
|
567
|
+
data[:debt] || 0
|
568
|
+
end
|
569
|
+
|
570
|
+
def extract_code_quality_metrics(data)
|
571
|
+
data[:metrics] || {}
|
572
|
+
end
|
573
|
+
|
574
|
+
def extract_test_coverage_metrics(data)
|
575
|
+
data[:metrics] || {}
|
576
|
+
end
|
577
|
+
|
578
|
+
def extract_complexity_metrics(data)
|
579
|
+
data[:metrics] || {}
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|