aidp 0.3.0 → 0.7.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/README.md +191 -5
- data/lib/aidp/analysis/kb_inspector.rb +456 -0
- data/lib/aidp/analysis/seams.rb +188 -0
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +493 -0
- data/lib/aidp/analysis/tree_sitter_scan.rb +703 -0
- data/lib/aidp/analyze/agent_personas.rb +1 -1
- data/lib/aidp/analyze/agent_tool_executor.rb +5 -11
- data/lib/aidp/analyze/data_retention_manager.rb +0 -5
- data/lib/aidp/analyze/database.rb +99 -82
- data/lib/aidp/analyze/error_handler.rb +12 -79
- data/lib/aidp/analyze/export_manager.rb +0 -7
- data/lib/aidp/analyze/focus_guidance.rb +2 -2
- data/lib/aidp/analyze/incremental_analyzer.rb +1 -11
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -5
- data/lib/aidp/analyze/memory_manager.rb +34 -60
- data/lib/aidp/analyze/metrics_storage.rb +336 -0
- data/lib/aidp/analyze/parallel_processor.rb +0 -6
- data/lib/aidp/analyze/performance_optimizer.rb +0 -3
- data/lib/aidp/analyze/prioritizer.rb +2 -2
- data/lib/aidp/analyze/repository_chunker.rb +14 -21
- data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
- data/lib/aidp/analyze/runner.rb +107 -191
- data/lib/aidp/analyze/steps.rb +35 -30
- data/lib/aidp/analyze/storage.rb +233 -178
- data/lib/aidp/analyze/tool_configuration.rb +21 -36
- data/lib/aidp/cli/jobs_command.rb +489 -0
- data/lib/aidp/cli/terminal_io.rb +52 -0
- data/lib/aidp/cli.rb +160 -45
- data/lib/aidp/core_ext/class_attribute.rb +36 -0
- data/lib/aidp/database/pg_adapter.rb +148 -0
- data/lib/aidp/database_config.rb +69 -0
- data/lib/aidp/database_connection.rb +72 -0
- data/lib/aidp/execute/runner.rb +65 -92
- data/lib/aidp/execute/steps.rb +81 -82
- data/lib/aidp/job_manager.rb +41 -0
- data/lib/aidp/jobs/base_job.rb +45 -0
- data/lib/aidp/jobs/provider_execution_job.rb +83 -0
- data/lib/aidp/provider_manager.rb +25 -0
- data/lib/aidp/providers/agent_supervisor.rb +348 -0
- data/lib/aidp/providers/anthropic.rb +160 -3
- data/lib/aidp/providers/base.rb +153 -6
- data/lib/aidp/providers/cursor.rb +245 -43
- data/lib/aidp/providers/gemini.rb +164 -3
- data/lib/aidp/providers/supervised_base.rb +317 -0
- data/lib/aidp/providers/supervised_cursor.rb +22 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +31 -34
- data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
- data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
- metadata +91 -36
@@ -0,0 +1,336 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pg"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Aidp
|
7
|
+
module Analyze
|
8
|
+
class MetricsStorage
|
9
|
+
# Database schema version
|
10
|
+
SCHEMA_VERSION = 1
|
11
|
+
|
12
|
+
def initialize(project_dir = Dir.pwd, db_config = nil)
|
13
|
+
@project_dir = project_dir
|
14
|
+
@db_config = db_config || default_db_config
|
15
|
+
@db = nil
|
16
|
+
|
17
|
+
ensure_database_exists
|
18
|
+
end
|
19
|
+
|
20
|
+
# Store step execution metrics
|
21
|
+
def store_step_metrics(step_name, provider_name, duration, success, metadata = {})
|
22
|
+
ensure_connection
|
23
|
+
|
24
|
+
timestamp = Time.now
|
25
|
+
|
26
|
+
result = @db.exec_params(
|
27
|
+
"INSERT INTO step_executions (step_name, provider_name, duration, success, metadata, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id",
|
28
|
+
[step_name, provider_name, duration, success, metadata.to_json, timestamp]
|
29
|
+
)
|
30
|
+
|
31
|
+
{
|
32
|
+
id: result[0]["id"],
|
33
|
+
step_name: step_name,
|
34
|
+
provider_name: provider_name,
|
35
|
+
duration: duration,
|
36
|
+
success: success,
|
37
|
+
stored_at: timestamp
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Store provider activity metrics
|
42
|
+
def store_provider_activity(provider_name, step_name, activity_summary)
|
43
|
+
ensure_connection
|
44
|
+
|
45
|
+
timestamp = Time.now
|
46
|
+
|
47
|
+
result = @db.exec_params(
|
48
|
+
"INSERT INTO provider_activities (provider_name, step_name, start_time, end_time, duration, final_state, stuck_detected, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id",
|
49
|
+
[
|
50
|
+
provider_name,
|
51
|
+
step_name,
|
52
|
+
activity_summary[:start_time],
|
53
|
+
activity_summary[:end_time],
|
54
|
+
activity_summary[:duration],
|
55
|
+
activity_summary[:final_state].to_s,
|
56
|
+
activity_summary[:stuck_detected],
|
57
|
+
timestamp
|
58
|
+
]
|
59
|
+
)
|
60
|
+
|
61
|
+
{
|
62
|
+
id: result[0]["id"],
|
63
|
+
provider_name: provider_name,
|
64
|
+
step_name: step_name,
|
65
|
+
stored_at: timestamp
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get step execution statistics
|
70
|
+
def get_step_statistics(step_name = nil, provider_name = nil, limit = 100)
|
71
|
+
ensure_connection
|
72
|
+
|
73
|
+
query = "SELECT * FROM step_executions WHERE 1=1"
|
74
|
+
params = []
|
75
|
+
param_index = 1
|
76
|
+
|
77
|
+
if step_name
|
78
|
+
query += " AND step_name = $#{param_index}"
|
79
|
+
params << step_name
|
80
|
+
param_index += 1
|
81
|
+
end
|
82
|
+
|
83
|
+
if provider_name
|
84
|
+
query += " AND provider_name = $#{param_index}"
|
85
|
+
params << provider_name
|
86
|
+
param_index += 1
|
87
|
+
end
|
88
|
+
|
89
|
+
query += " ORDER BY created_at DESC LIMIT $#{param_index}"
|
90
|
+
params << limit
|
91
|
+
|
92
|
+
results = @db.exec_params(query, params)
|
93
|
+
results.map { |row| parse_step_execution(row) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get provider activity statistics
|
97
|
+
def get_provider_activity_statistics(provider_name = nil, step_name = nil, limit = 100)
|
98
|
+
ensure_connection
|
99
|
+
|
100
|
+
query = "SELECT * FROM provider_activities WHERE 1=1"
|
101
|
+
params = []
|
102
|
+
param_index = 1
|
103
|
+
|
104
|
+
if provider_name
|
105
|
+
query += " AND provider_name = $#{param_index}"
|
106
|
+
params << provider_name
|
107
|
+
param_index += 1
|
108
|
+
end
|
109
|
+
|
110
|
+
if step_name
|
111
|
+
query += " AND step_name = $#{param_index}"
|
112
|
+
params << step_name
|
113
|
+
param_index += 1
|
114
|
+
end
|
115
|
+
|
116
|
+
query += " ORDER BY created_at DESC LIMIT $#{param_index}"
|
117
|
+
params << limit
|
118
|
+
|
119
|
+
results = @db.exec_params(query, params)
|
120
|
+
results.map { |row| parse_provider_activity(row) }
|
121
|
+
end
|
122
|
+
|
123
|
+
# Calculate timeout recommendations based on p95 of execution times
|
124
|
+
def calculate_timeout_recommendations
|
125
|
+
ensure_connection
|
126
|
+
|
127
|
+
recommendations = {}
|
128
|
+
|
129
|
+
# Get all step names
|
130
|
+
step_names = @db.exec("SELECT DISTINCT step_name FROM step_executions WHERE success = true")
|
131
|
+
|
132
|
+
step_names.each do |row|
|
133
|
+
step_name = row["step_name"]
|
134
|
+
|
135
|
+
# Get successful executions for this step
|
136
|
+
durations = @db.exec_params(
|
137
|
+
"SELECT duration FROM step_executions WHERE step_name = $1 AND success = true ORDER BY duration",
|
138
|
+
[step_name]
|
139
|
+
).map { |r| r["duration"].to_f }
|
140
|
+
|
141
|
+
next if durations.empty?
|
142
|
+
|
143
|
+
# Calculate p95
|
144
|
+
p95_index = (durations.length * 0.95).ceil - 1
|
145
|
+
p95_duration = durations[p95_index]
|
146
|
+
|
147
|
+
# Round up to nearest second and add 10% buffer
|
148
|
+
recommended_timeout = (p95_duration * 1.1).ceil
|
149
|
+
|
150
|
+
recommendations[step_name] = {
|
151
|
+
p95_duration: p95_duration,
|
152
|
+
recommended_timeout: recommended_timeout,
|
153
|
+
sample_count: durations.length,
|
154
|
+
min_duration: durations.first,
|
155
|
+
max_duration: durations.last,
|
156
|
+
avg_duration: durations.sum.to_f / durations.length
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
recommendations
|
161
|
+
end
|
162
|
+
|
163
|
+
# Get overall metrics summary
|
164
|
+
def get_metrics_summary
|
165
|
+
ensure_connection
|
166
|
+
|
167
|
+
summary = {}
|
168
|
+
|
169
|
+
# Total executions
|
170
|
+
total_executions = @db.exec("SELECT COUNT(*) FROM step_executions").first["count"].to_i
|
171
|
+
summary[:total_executions] = total_executions
|
172
|
+
|
173
|
+
# Successful executions
|
174
|
+
successful_executions = @db.exec("SELECT COUNT(*) FROM step_executions WHERE success = true").first["count"].to_i
|
175
|
+
summary[:successful_executions] = successful_executions
|
176
|
+
|
177
|
+
# Success rate
|
178
|
+
summary[:success_rate] = (total_executions > 0) ? (successful_executions.to_f / total_executions * 100).round(2) : 0
|
179
|
+
|
180
|
+
# Average duration
|
181
|
+
avg_duration = @db.exec("SELECT AVG(duration) FROM step_executions WHERE success = true").first["avg"]
|
182
|
+
summary[:average_duration] = avg_duration ? avg_duration.to_f.round(2) : 0
|
183
|
+
|
184
|
+
# Stuck detections
|
185
|
+
stuck_count = @db.exec("SELECT COUNT(*) FROM provider_activities WHERE stuck_detected = true").first["count"].to_i
|
186
|
+
summary[:stuck_detections] = stuck_count
|
187
|
+
|
188
|
+
# Date range
|
189
|
+
date_range = @db.exec("SELECT MIN(created_at), MAX(created_at) FROM step_executions").first
|
190
|
+
if date_range && date_range["min"]
|
191
|
+
summary[:date_range] = {
|
192
|
+
start: Time.parse(date_range["min"]),
|
193
|
+
end: Time.parse(date_range["max"])
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
summary
|
198
|
+
end
|
199
|
+
|
200
|
+
# Clean up old metrics data
|
201
|
+
def cleanup_old_metrics(retention_days = 30)
|
202
|
+
ensure_connection
|
203
|
+
|
204
|
+
cutoff_time = Time.now - (retention_days * 24 * 60 * 60)
|
205
|
+
|
206
|
+
# Delete old step executions
|
207
|
+
deleted_executions = @db.exec_params(
|
208
|
+
"DELETE FROM step_executions WHERE created_at < $1 RETURNING id",
|
209
|
+
[cutoff_time]
|
210
|
+
).ntuples
|
211
|
+
|
212
|
+
# Delete old provider activities
|
213
|
+
deleted_activities = @db.exec_params(
|
214
|
+
"DELETE FROM provider_activities WHERE created_at < $1 RETURNING id",
|
215
|
+
[cutoff_time]
|
216
|
+
).ntuples
|
217
|
+
|
218
|
+
{
|
219
|
+
deleted_executions: deleted_executions,
|
220
|
+
deleted_activities: deleted_activities,
|
221
|
+
cutoff_time: cutoff_time
|
222
|
+
}
|
223
|
+
end
|
224
|
+
|
225
|
+
# Export metrics data
|
226
|
+
def export_metrics(format = :json)
|
227
|
+
ensure_connection
|
228
|
+
|
229
|
+
case format
|
230
|
+
when :json
|
231
|
+
{
|
232
|
+
step_executions: get_step_statistics(nil, nil, 1000),
|
233
|
+
provider_activities: get_provider_activity_statistics(nil, nil, 1000),
|
234
|
+
summary: get_metrics_summary,
|
235
|
+
recommendations: calculate_timeout_recommendations,
|
236
|
+
exported_at: Time.now.iso8601
|
237
|
+
}
|
238
|
+
when :csv
|
239
|
+
# TODO: Implement CSV export
|
240
|
+
raise NotImplementedError, "CSV export not yet implemented"
|
241
|
+
else
|
242
|
+
raise ArgumentError, "Unsupported export format: #{format}"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
def default_db_config
|
249
|
+
{
|
250
|
+
host: ENV["AIDP_DB_HOST"] || "localhost",
|
251
|
+
port: ENV["AIDP_DB_PORT"] || 5432,
|
252
|
+
dbname: ENV["AIDP_DB_NAME"] || "aidp",
|
253
|
+
user: ENV["AIDP_DB_USER"] || ENV["USER"],
|
254
|
+
password: ENV["AIDP_DB_PASSWORD"]
|
255
|
+
}
|
256
|
+
end
|
257
|
+
|
258
|
+
def ensure_connection
|
259
|
+
return if @db
|
260
|
+
|
261
|
+
@db = PG.connect(@db_config)
|
262
|
+
@db.type_map_for_results = PG::BasicTypeMapForResults.new(@db)
|
263
|
+
end
|
264
|
+
|
265
|
+
def ensure_database_exists
|
266
|
+
ensure_connection
|
267
|
+
|
268
|
+
# Create step_executions table if it doesn't exist
|
269
|
+
@db.exec(<<~SQL)
|
270
|
+
CREATE TABLE IF NOT EXISTS step_executions (
|
271
|
+
id SERIAL PRIMARY KEY,
|
272
|
+
step_name TEXT NOT NULL,
|
273
|
+
provider_name TEXT NOT NULL,
|
274
|
+
duration REAL NOT NULL,
|
275
|
+
success BOOLEAN NOT NULL,
|
276
|
+
metadata JSONB,
|
277
|
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL
|
278
|
+
)
|
279
|
+
SQL
|
280
|
+
|
281
|
+
# Create provider_activities table if it doesn't exist
|
282
|
+
@db.exec(<<~SQL)
|
283
|
+
CREATE TABLE IF NOT EXISTS provider_activities (
|
284
|
+
id SERIAL PRIMARY KEY,
|
285
|
+
provider_name TEXT NOT NULL,
|
286
|
+
step_name TEXT NOT NULL,
|
287
|
+
start_time TIMESTAMP WITH TIME ZONE,
|
288
|
+
end_time TIMESTAMP WITH TIME ZONE,
|
289
|
+
duration REAL,
|
290
|
+
final_state TEXT,
|
291
|
+
stuck_detected BOOLEAN DEFAULT FALSE,
|
292
|
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL
|
293
|
+
)
|
294
|
+
SQL
|
295
|
+
|
296
|
+
# Create indexes separately
|
297
|
+
@db.exec("CREATE INDEX IF NOT EXISTS idx_step_executions_step_name ON step_executions(step_name)")
|
298
|
+
@db.exec("CREATE INDEX IF NOT EXISTS idx_step_executions_provider_name ON step_executions(provider_name)")
|
299
|
+
@db.exec("CREATE INDEX IF NOT EXISTS idx_step_executions_created_at ON step_executions(created_at)")
|
300
|
+
@db.exec("CREATE INDEX IF NOT EXISTS idx_provider_activities_provider_name ON provider_activities(provider_name)")
|
301
|
+
@db.exec("CREATE INDEX IF NOT EXISTS idx_provider_activities_step_name ON provider_activities(step_name)")
|
302
|
+
@db.exec("CREATE INDEX IF NOT EXISTS idx_provider_activities_created_at ON provider_activities(created_at)")
|
303
|
+
|
304
|
+
# Create metrics_schema_version table if it doesn't exist
|
305
|
+
@db.exec("CREATE TABLE IF NOT EXISTS metrics_schema_version (version INTEGER NOT NULL)")
|
306
|
+
@db.exec_params("INSERT INTO metrics_schema_version (version) VALUES ($1) ON CONFLICT DO NOTHING", [SCHEMA_VERSION])
|
307
|
+
end
|
308
|
+
|
309
|
+
def parse_step_execution(row)
|
310
|
+
{
|
311
|
+
id: row["id"].to_i,
|
312
|
+
step_name: row["step_name"],
|
313
|
+
provider_name: row["provider_name"],
|
314
|
+
duration: row["duration"].to_f,
|
315
|
+
success: row["success"],
|
316
|
+
metadata: row["metadata"] ? JSON.parse(row["metadata"]) : {},
|
317
|
+
created_at: Time.parse(row["created_at"])
|
318
|
+
}
|
319
|
+
end
|
320
|
+
|
321
|
+
def parse_provider_activity(row)
|
322
|
+
{
|
323
|
+
id: row["id"].to_i,
|
324
|
+
provider_name: row["provider_name"],
|
325
|
+
step_name: row["step_name"],
|
326
|
+
start_time: row["start_time"] ? Time.parse(row["start_time"]) : nil,
|
327
|
+
end_time: row["end_time"] ? Time.parse(row["end_time"]) : nil,
|
328
|
+
duration: row["duration"].to_f,
|
329
|
+
final_state: row["final_state"]&.to_sym,
|
330
|
+
stuck_detected: row["stuck_detected"],
|
331
|
+
created_at: Time.parse(row["created_at"])
|
332
|
+
}
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
@@ -12,9 +12,9 @@ module Aidp
|
|
12
12
|
@feature_analyzer = Aidp::Analyze::FeatureAnalyzer.new(project_dir)
|
13
13
|
end
|
14
14
|
|
15
|
-
# Generate prioritized analysis recommendations based on
|
15
|
+
# Generate prioritized analysis recommendations based on ruby-maat data
|
16
16
|
def generate_prioritized_recommendations
|
17
|
-
# Get
|
17
|
+
# Get ruby-maat analysis data
|
18
18
|
code_maat_data = @code_maat.run_comprehensive_analysis
|
19
19
|
|
20
20
|
# Get feature analysis data
|
@@ -264,29 +264,22 @@ module Aidp
|
|
264
264
|
status: "running"
|
265
265
|
}
|
266
266
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
results[:data] = analyze_feature_chunk(chunk, analysis_type, options)
|
278
|
-
end
|
279
|
-
|
280
|
-
results[:status] = "completed"
|
281
|
-
results[:end_time] = Time.now
|
282
|
-
results[:duration] = results[:end_time] - results[:start_time]
|
283
|
-
rescue => e
|
284
|
-
results[:status] = "failed"
|
285
|
-
results[:error] = e.message
|
286
|
-
results[:end_time] = Time.now
|
287
|
-
results[:duration] = results[:end_time] - results[:start_time]
|
267
|
+
# Perform analysis based on chunk type
|
268
|
+
case chunk[:strategy]
|
269
|
+
when "time_based"
|
270
|
+
results[:data] = analyze_time_chunk(chunk, analysis_type, options)
|
271
|
+
when "commit_count"
|
272
|
+
results[:data] = analyze_commit_chunk(chunk, analysis_type, options)
|
273
|
+
when "size_based"
|
274
|
+
results[:data] = analyze_size_chunk(chunk, analysis_type, options)
|
275
|
+
when "feature_based"
|
276
|
+
results[:data] = analyze_feature_chunk(chunk, analysis_type, options)
|
288
277
|
end
|
289
278
|
|
279
|
+
results[:status] = "completed"
|
280
|
+
results[:end_time] = Time.now
|
281
|
+
results[:duration] = results[:end_time] - results[:start_time]
|
282
|
+
|
290
283
|
results
|
291
284
|
end
|
292
285
|
|
@@ -171,112 +171,16 @@ module Aidp
|
|
171
171
|
# Write the output to the specified file
|
172
172
|
File.write(output_file, stdout)
|
173
173
|
else
|
174
|
-
#
|
175
|
-
|
176
|
-
|
174
|
+
# Raise proper error instead of falling back to fake data
|
175
|
+
error_msg = "RubyMaat analysis failed for #{analysis_type}: #{stderr.strip}"
|
176
|
+
error_msg += "\n\nTo install ruby-maat, run: gem install ruby-maat"
|
177
|
+
error_msg += "\nOr add it to your Gemfile: gem 'ruby-maat'"
|
178
|
+
raise error_msg
|
177
179
|
end
|
178
180
|
|
179
181
|
output_file
|
180
182
|
end
|
181
183
|
|
182
|
-
def mock_ruby_maat_analysis(analysis_type, input_file, output_file)
|
183
|
-
# Parse the Git log to generate mock analysis data
|
184
|
-
git_log_content = File.read(input_file)
|
185
|
-
|
186
|
-
case analysis_type
|
187
|
-
when "churn"
|
188
|
-
generate_mock_churn_data(git_log_content, output_file)
|
189
|
-
when "coupling"
|
190
|
-
generate_mock_coupling_data(git_log_content, output_file)
|
191
|
-
when "authorship"
|
192
|
-
generate_mock_authorship_data(git_log_content, output_file)
|
193
|
-
when "summary"
|
194
|
-
generate_mock_summary_data(git_log_content, output_file)
|
195
|
-
else
|
196
|
-
raise "Unknown analysis type: #{analysis_type}"
|
197
|
-
end
|
198
|
-
|
199
|
-
output_file
|
200
|
-
end
|
201
|
-
|
202
|
-
def generate_mock_churn_data(git_log_content, output_file)
|
203
|
-
# Extract file names from Git log and generate mock churn data
|
204
|
-
files = extract_files_from_git_log(git_log_content)
|
205
|
-
|
206
|
-
csv_content = "entity,n-revs,n-lines-added,n-lines-deleted\n"
|
207
|
-
files.each_with_index do |file, index|
|
208
|
-
changes = rand(1..20)
|
209
|
-
additions = rand(0..changes * 10)
|
210
|
-
deletions = rand(0..changes * 5)
|
211
|
-
csv_content += "#{file},#{changes},#{additions},#{deletions}\n"
|
212
|
-
end
|
213
|
-
|
214
|
-
File.write(output_file, csv_content)
|
215
|
-
end
|
216
|
-
|
217
|
-
def generate_mock_coupling_data(git_log_content, output_file)
|
218
|
-
# Generate mock coupling data between files
|
219
|
-
files = extract_files_from_git_log(git_log_content)
|
220
|
-
|
221
|
-
csv_content = "entity,coupled,degree,average-revs\n"
|
222
|
-
files.each_slice(2) do |file1, file2|
|
223
|
-
next unless file2
|
224
|
-
|
225
|
-
shared_changes = rand(1..10)
|
226
|
-
rand(0.1..1.0).round(2)
|
227
|
-
avg_revs = rand(1..5)
|
228
|
-
csv_content += "#{file1},#{file2},#{shared_changes},#{avg_revs}\n"
|
229
|
-
end
|
230
|
-
|
231
|
-
File.write(output_file, csv_content)
|
232
|
-
end
|
233
|
-
|
234
|
-
def generate_mock_authorship_data(git_log_content, output_file)
|
235
|
-
# Generate mock authorship data
|
236
|
-
files = extract_files_from_git_log(git_log_content)
|
237
|
-
authors = %w[Alice Bob Charlie Diana Eve]
|
238
|
-
|
239
|
-
csv_content = "entity,n-authors,revs\n"
|
240
|
-
files.each do |file|
|
241
|
-
author_count = rand(1..3)
|
242
|
-
file_authors = authors.sample(author_count)
|
243
|
-
revs = rand(1..15)
|
244
|
-
csv_content += "#{file},\"#{file_authors.join(";")}\",#{revs}\n"
|
245
|
-
end
|
246
|
-
|
247
|
-
File.write(output_file, csv_content)
|
248
|
-
end
|
249
|
-
|
250
|
-
def generate_mock_summary_data(git_log_content, output_file)
|
251
|
-
# Generate mock summary data
|
252
|
-
summary_content = <<~SUMMARY
|
253
|
-
Number of commits: 42
|
254
|
-
Number of entities: 15
|
255
|
-
Number of authors: 5
|
256
|
-
First commit: 2023-01-01
|
257
|
-
Last commit: 2024-01-01
|
258
|
-
Total lines added: 1250
|
259
|
-
Total lines deleted: 450
|
260
|
-
SUMMARY
|
261
|
-
|
262
|
-
File.write(output_file, summary_content)
|
263
|
-
end
|
264
|
-
|
265
|
-
def extract_files_from_git_log(git_log_content)
|
266
|
-
# Extract file names from Git log content
|
267
|
-
files = []
|
268
|
-
git_log_content.lines.each do |line|
|
269
|
-
# Look for lines that contain file paths (not commit info)
|
270
|
-
next unless line.match?(/\d+\s+\d+\s+[^\s]+$/)
|
271
|
-
|
272
|
-
parts = line.strip.split(/\s+/)
|
273
|
-
files << parts[2] if parts.length >= 3 && parts[2] != "-"
|
274
|
-
end
|
275
|
-
|
276
|
-
# Return unique files, limited to a reasonable number
|
277
|
-
files.uniq.first(20)
|
278
|
-
end
|
279
|
-
|
280
184
|
# Check if repository is large enough to require chunking
|
281
185
|
def large_repository?(git_log_file)
|
282
186
|
return false unless File.exist?(git_log_file)
|
@@ -477,7 +381,7 @@ module Aidp
|
|
477
381
|
report_file = File.join(@project_dir, "code_maat_analysis_report.md")
|
478
382
|
|
479
383
|
report = <<~REPORT
|
480
|
-
#
|
384
|
+
# Ruby-maat Analysis Report
|
481
385
|
|
482
386
|
Generated on: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}
|
483
387
|
Project: #{File.basename(@project_dir)}
|