aidp 0.1.0 → 0.5.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 +59 -4
- data/bin/aidp +2 -2
- data/lib/aidp/analyze/agent_personas.rb +1 -1
- data/lib/aidp/analyze/data_retention_manager.rb +2 -2
- data/lib/aidp/analyze/database.rb +99 -82
- data/lib/aidp/analyze/error_handler.rb +12 -76
- data/lib/aidp/analyze/focus_guidance.rb +2 -2
- data/lib/aidp/analyze/large_analysis_progress.rb +2 -2
- data/lib/aidp/analyze/metrics_storage.rb +336 -0
- data/lib/aidp/analyze/prioritizer.rb +4 -4
- data/lib/aidp/analyze/repository_chunker.rb +15 -13
- data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
- data/lib/aidp/analyze/runner.rb +107 -191
- data/lib/aidp/analyze/steps.rb +29 -30
- data/lib/aidp/analyze/storage.rb +234 -172
- data/lib/aidp/cli/jobs_command.rb +489 -0
- data/lib/aidp/cli/terminal_io.rb +52 -0
- data/lib/aidp/cli.rb +227 -0
- data/lib/aidp/config.rb +33 -0
- 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/database_migration.rb +158 -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 +47 -0
- data/lib/aidp/jobs/provider_execution_job.rb +96 -0
- data/lib/aidp/project_detector.rb +117 -0
- data/lib/aidp/provider_manager.rb +25 -0
- data/lib/aidp/providers/agent_supervisor.rb +348 -0
- data/lib/aidp/providers/anthropic.rb +187 -0
- data/lib/aidp/providers/base.rb +162 -0
- data/lib/aidp/providers/cursor.rb +304 -0
- data/lib/aidp/providers/gemini.rb +187 -0
- data/lib/aidp/providers/macos_ui.rb +24 -0
- data/lib/aidp/providers/supervised_base.rb +317 -0
- data/lib/aidp/providers/supervised_cursor.rb +22 -0
- data/lib/aidp/sync.rb +13 -0
- data/lib/aidp/util.rb +39 -0
- data/lib/aidp/{shared/version.rb → version.rb} +1 -3
- data/lib/aidp/workspace.rb +19 -0
- data/lib/aidp.rb +36 -45
- data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
- metadata +89 -45
- data/lib/aidp/shared/cli.rb +0 -117
- data/lib/aidp/shared/config.rb +0 -35
- data/lib/aidp/shared/project_detector.rb +0 -119
- data/lib/aidp/shared/providers/anthropic.rb +0 -26
- data/lib/aidp/shared/providers/base.rb +0 -17
- data/lib/aidp/shared/providers/cursor.rb +0 -102
- data/lib/aidp/shared/providers/gemini.rb +0 -26
- data/lib/aidp/shared/providers/macos_ui.rb +0 -26
- data/lib/aidp/shared/sync.rb +0 -15
- data/lib/aidp/shared/util.rb +0 -41
- data/lib/aidp/shared/workspace.rb +0 -21
@@ -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
|
@@ -281,9 +281,9 @@ module Aidp
|
|
281
281
|
|
282
282
|
# Adjust for authorship patterns
|
283
283
|
if authorship_file[:author_count] == 1
|
284
|
-
base_score *= 1.5
|
284
|
+
base_score *= 1.5 # Knowledge silo penalty
|
285
285
|
elsif authorship_file[:author_count] > 3
|
286
|
-
base_score *= 1.2
|
286
|
+
base_score *= 1.2 # Coordination complexity penalty
|
287
287
|
end
|
288
288
|
|
289
289
|
base_score
|
@@ -13,15 +13,15 @@ module Aidp
|
|
13
13
|
DEFAULT_CHUNK_CONFIG = {
|
14
14
|
"time_based" => {
|
15
15
|
"chunk_size" => "30d", # 30 days
|
16
|
-
"overlap" => "7d"
|
16
|
+
"overlap" => "7d" # 7 days overlap
|
17
17
|
},
|
18
18
|
"commit_count" => {
|
19
|
-
"chunk_size" => 1000,
|
20
|
-
"overlap" => 100
|
19
|
+
"chunk_size" => 1000, # 1000 commits per chunk
|
20
|
+
"overlap" => 100 # 100 commits overlap
|
21
21
|
},
|
22
22
|
"size_based" => {
|
23
23
|
"chunk_size" => "100MB", # 100MB per chunk
|
24
|
-
"overlap" => "10MB"
|
24
|
+
"overlap" => "10MB" # 10MB overlap
|
25
25
|
},
|
26
26
|
"feature_based" => {
|
27
27
|
"max_files_per_chunk" => 500,
|
@@ -358,14 +358,15 @@ module Aidp
|
|
358
358
|
|
359
359
|
def parse_time_duration(duration_str)
|
360
360
|
# Parse duration strings like "30d", "7d", "1w", etc.
|
361
|
-
|
362
|
-
|
361
|
+
# Use anchored patterns with limited digit repetition to prevent ReDoS
|
362
|
+
case duration_str.to_s.strip
|
363
|
+
when /\A(\d{1,6})d\z/
|
363
364
|
::Regexp.last_match(1).to_i * 24 * 60 * 60
|
364
|
-
when
|
365
|
+
when /\A(\d{1,6})w\z/
|
365
366
|
::Regexp.last_match(1).to_i * 7 * 24 * 60 * 60
|
366
|
-
when
|
367
|
+
when /\A(\d{1,6})m\z/
|
367
368
|
::Regexp.last_match(1).to_i * 30 * 24 * 60 * 60
|
368
|
-
when
|
369
|
+
when /\A(\d{1,6})y\z/
|
369
370
|
::Regexp.last_match(1).to_i * 365 * 24 * 60 * 60
|
370
371
|
else
|
371
372
|
30 * 24 * 60 * 60 # Default to 30 days
|
@@ -374,12 +375,13 @@ module Aidp
|
|
374
375
|
|
375
376
|
def parse_size(size_str)
|
376
377
|
# Parse size strings like "100MB", "1GB", etc.
|
377
|
-
|
378
|
-
|
378
|
+
# Use anchored patterns with limited digit repetition to prevent ReDoS
|
379
|
+
case size_str.to_s.strip
|
380
|
+
when /\A(\d{1,10})KB\z/i
|
379
381
|
::Regexp.last_match(1).to_i * 1024
|
380
|
-
when
|
382
|
+
when /\A(\d{1,10})MB\z/i
|
381
383
|
::Regexp.last_match(1).to_i * 1024 * 1024
|
382
|
-
when
|
384
|
+
when /\A(\d{1,10})GB\z/i
|
383
385
|
::Regexp.last_match(1).to_i * 1024 * 1024 * 1024
|
384
386
|
else
|
385
387
|
100 * 1024 * 1024 # Default to 100MB
|
@@ -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)}
|