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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +191 -5
  3. data/lib/aidp/analysis/kb_inspector.rb +456 -0
  4. data/lib/aidp/analysis/seams.rb +188 -0
  5. data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +493 -0
  6. data/lib/aidp/analysis/tree_sitter_scan.rb +703 -0
  7. data/lib/aidp/analyze/agent_personas.rb +1 -1
  8. data/lib/aidp/analyze/agent_tool_executor.rb +5 -11
  9. data/lib/aidp/analyze/data_retention_manager.rb +0 -5
  10. data/lib/aidp/analyze/database.rb +99 -82
  11. data/lib/aidp/analyze/error_handler.rb +12 -79
  12. data/lib/aidp/analyze/export_manager.rb +0 -7
  13. data/lib/aidp/analyze/focus_guidance.rb +2 -2
  14. data/lib/aidp/analyze/incremental_analyzer.rb +1 -11
  15. data/lib/aidp/analyze/large_analysis_progress.rb +0 -5
  16. data/lib/aidp/analyze/memory_manager.rb +34 -60
  17. data/lib/aidp/analyze/metrics_storage.rb +336 -0
  18. data/lib/aidp/analyze/parallel_processor.rb +0 -6
  19. data/lib/aidp/analyze/performance_optimizer.rb +0 -3
  20. data/lib/aidp/analyze/prioritizer.rb +2 -2
  21. data/lib/aidp/analyze/repository_chunker.rb +14 -21
  22. data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
  23. data/lib/aidp/analyze/runner.rb +107 -191
  24. data/lib/aidp/analyze/steps.rb +35 -30
  25. data/lib/aidp/analyze/storage.rb +233 -178
  26. data/lib/aidp/analyze/tool_configuration.rb +21 -36
  27. data/lib/aidp/cli/jobs_command.rb +489 -0
  28. data/lib/aidp/cli/terminal_io.rb +52 -0
  29. data/lib/aidp/cli.rb +160 -45
  30. data/lib/aidp/core_ext/class_attribute.rb +36 -0
  31. data/lib/aidp/database/pg_adapter.rb +148 -0
  32. data/lib/aidp/database_config.rb +69 -0
  33. data/lib/aidp/database_connection.rb +72 -0
  34. data/lib/aidp/execute/runner.rb +65 -92
  35. data/lib/aidp/execute/steps.rb +81 -82
  36. data/lib/aidp/job_manager.rb +41 -0
  37. data/lib/aidp/jobs/base_job.rb +45 -0
  38. data/lib/aidp/jobs/provider_execution_job.rb +83 -0
  39. data/lib/aidp/provider_manager.rb +25 -0
  40. data/lib/aidp/providers/agent_supervisor.rb +348 -0
  41. data/lib/aidp/providers/anthropic.rb +160 -3
  42. data/lib/aidp/providers/base.rb +153 -6
  43. data/lib/aidp/providers/cursor.rb +245 -43
  44. data/lib/aidp/providers/gemini.rb +164 -3
  45. data/lib/aidp/providers/supervised_base.rb +317 -0
  46. data/lib/aidp/providers/supervised_cursor.rb +22 -0
  47. data/lib/aidp/version.rb +1 -1
  48. data/lib/aidp.rb +31 -34
  49. data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
  50. data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
  51. metadata +91 -36
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sqlite3"
3
+ require "pg"
4
4
  require "json"
5
5
  require "yaml"
6
6
 
@@ -12,7 +12,6 @@ module Aidp
12
12
  def initialize(project_dir = Dir.pwd, config = {})
13
13
  @project_dir = project_dir
14
14
  @config = config
15
- @db_path = config[:db_path] || File.join(project_dir, ".aidp-analysis.db")
16
15
  @db = nil
17
16
 
18
17
  ensure_database_exists
@@ -36,14 +35,24 @@ module Aidp
36
35
  }
37
36
 
38
37
  # Insert or update analysis result
39
- @db.execute(
40
- "INSERT OR REPLACE INTO analysis_results (execution_id, step_name, data, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
41
- execution_id,
42
- step_name,
43
- JSON.generate(data),
44
- JSON.generate(analysis_data[:metadata]),
45
- timestamp.to_i,
46
- timestamp.to_i
38
+ @db.exec_params(
39
+ <<~SQL,
40
+ INSERT INTO analysis_results (execution_id, step_name, data, metadata, created_at, updated_at)
41
+ VALUES ($1, $2, $3, $4, $5, $6)
42
+ ON CONFLICT (execution_id, step_name)
43
+ DO UPDATE SET
44
+ data = EXCLUDED.data,
45
+ metadata = EXCLUDED.metadata,
46
+ updated_at = EXCLUDED.updated_at
47
+ SQL
48
+ [
49
+ execution_id,
50
+ step_name,
51
+ data.to_json,
52
+ analysis_data[:metadata].to_json,
53
+ timestamp,
54
+ timestamp
55
+ ]
47
56
  )
48
57
 
49
58
  # Store metrics with indefinite retention
@@ -55,13 +64,6 @@ module Aidp
55
64
  stored_at: timestamp,
56
65
  success: true
57
66
  }
58
- rescue => e
59
- {
60
- success: false,
61
- error: e.message,
62
- execution_id: execution_id,
63
- step_name: step_name
64
- }
65
67
  end
66
68
 
67
69
  # Store metrics with indefinite retention
@@ -72,14 +74,19 @@ module Aidp
72
74
  metrics = extract_metrics(data)
73
75
 
74
76
  metrics.each do |metric_name, metric_value|
75
- @db.execute(
76
- "INSERT INTO metrics (execution_id, step_name, metric_name, metric_value, metric_type, created_at) VALUES (?, ?, ?, ?, ?, ?)",
77
- execution_id,
78
- step_name,
79
- metric_name,
80
- metric_value.to_s,
81
- metric_value.class.name,
82
- timestamp.to_i
77
+ @db.exec_params(
78
+ <<~SQL,
79
+ INSERT INTO metrics (execution_id, step_name, metric_name, metric_value, metric_type, created_at)
80
+ VALUES ($1, $2, $3, $4, $5, $6)
81
+ SQL
82
+ [
83
+ execution_id,
84
+ step_name,
85
+ metric_name,
86
+ metric_value.to_s,
87
+ metric_value.class.name,
88
+ timestamp
89
+ ]
83
90
  )
84
91
  end
85
92
 
@@ -93,28 +100,35 @@ module Aidp
93
100
 
94
101
  result = if step_name
95
102
  # Get specific step result
96
- @db.execute(
97
- "SELECT * FROM analysis_results WHERE execution_id = ? AND step_name = ? ORDER BY updated_at DESC LIMIT 1",
98
- execution_id,
99
- step_name
100
- ).first
103
+ @db.exec_params(
104
+ <<~SQL,
105
+ SELECT * FROM analysis_results
106
+ WHERE execution_id = $1 AND step_name = $2
107
+ ORDER BY updated_at DESC
108
+ LIMIT 1
109
+ SQL
110
+ [execution_id, step_name]
111
+ )
101
112
  else
102
113
  # Get all results for execution
103
- @db.execute(
104
- "SELECT * FROM analysis_results WHERE execution_id = ? ORDER BY updated_at DESC",
105
- execution_id
114
+ @db.exec_params(
115
+ <<~SQL,
116
+ SELECT * FROM analysis_results
117
+ WHERE execution_id = $1
118
+ ORDER BY updated_at DESC
119
+ SQL
120
+ [execution_id]
106
121
  )
107
122
  end
108
123
 
109
- return nil unless result
124
+ return nil if result.ntuples.zero?
110
125
 
111
- if result.is_a?(Array) && result.length > 1
126
+ if result.ntuples > 1
112
127
  # Multiple results
113
128
  result.map { |row| parse_analysis_result(row) }
114
129
  else
115
130
  # Single result
116
- row = result.is_a?(Array) ? result.first : result
117
- parse_analysis_result(row)
131
+ parse_analysis_result(result[0])
118
132
  end
119
133
  end
120
134
 
@@ -124,26 +138,31 @@ module Aidp
124
138
 
125
139
  query = "SELECT * FROM metrics WHERE 1=1"
126
140
  params = []
141
+ param_index = 1
127
142
 
128
143
  if execution_id
129
- query += " AND execution_id = ?"
144
+ query += " AND execution_id = $#{param_index}"
130
145
  params << execution_id
146
+ param_index += 1
131
147
  end
132
148
 
133
149
  if step_name
134
- query += " AND step_name = ?"
150
+ query += " AND step_name = $#{param_index}"
135
151
  params << step_name
152
+ param_index += 1
136
153
  end
137
154
 
138
155
  if metric_name
139
- query += " AND metric_name = ?"
156
+ query += " AND metric_name = $#{param_index}"
140
157
  params << metric_name
158
+ param_index += 1
141
159
  end
142
160
 
143
- query += " ORDER BY created_at DESC LIMIT ?"
161
+ query += " ORDER BY created_at DESC"
162
+ query += " LIMIT $#{param_index}"
144
163
  params << limit
145
164
 
146
- results = @db.execute(query, *params)
165
+ results = @db.exec_params(query, params)
147
166
  results.map { |row| parse_metric(row) }
148
167
  end
149
168
 
@@ -153,25 +172,29 @@ module Aidp
153
172
 
154
173
  query = "SELECT * FROM aggregated_metrics WHERE 1=1"
155
174
  params = []
175
+ param_index = 1
156
176
 
157
177
  if execution_id
158
- query += " AND execution_id = ?"
178
+ query += " AND execution_id = $#{param_index}"
159
179
  params << execution_id
180
+ param_index += 1
160
181
  end
161
182
 
162
183
  if step_name
163
- query += " AND step_name = ?"
184
+ query += " AND step_name = $#{param_index}"
164
185
  params << step_name
186
+ param_index += 1
165
187
  end
166
188
 
167
189
  if metric_name
168
- query += " AND metric_name = ?"
190
+ query += " AND metric_name = $#{param_index}"
169
191
  params << metric_name
192
+ param_index + 1
170
193
  end
171
194
 
172
195
  query += " ORDER BY created_at DESC"
173
196
 
174
- results = @db.execute(query, *params)
197
+ results = @db.exec_params(query, params)
175
198
  results.map { |row| parse_aggregated_metric(row) }
176
199
  end
177
200
 
@@ -179,9 +202,14 @@ module Aidp
179
202
  def get_execution_history(limit = 50)
180
203
  ensure_connection
181
204
 
182
- results = @db.execute(
183
- "SELECT DISTINCT execution_id, step_name, created_at, updated_at FROM analysis_results ORDER BY created_at DESC LIMIT ?",
184
- limit
205
+ results = @db.exec_params(
206
+ <<~SQL,
207
+ SELECT DISTINCT execution_id, step_name, created_at, updated_at
208
+ FROM analysis_results
209
+ ORDER BY created_at DESC
210
+ LIMIT $1
211
+ SQL
212
+ [limit]
185
213
  )
186
214
 
187
215
  results.map { |row| parse_execution_history(row) }
@@ -194,30 +222,34 @@ module Aidp
194
222
  stats = {}
195
223
 
196
224
  # Total executions
197
- total_executions = @db.execute("SELECT COUNT(DISTINCT execution_id) FROM analysis_results").first[0]
225
+ total_executions = @db.exec("SELECT COUNT(DISTINCT execution_id) FROM analysis_results").first["count"].to_i
198
226
  stats[:total_executions] = total_executions
199
227
 
200
228
  # Total steps
201
- total_steps = @db.execute("SELECT COUNT(*) FROM analysis_results").first[0]
229
+ total_steps = @db.exec("SELECT COUNT(*) FROM analysis_results").first["count"].to_i
202
230
  stats[:total_steps] = total_steps
203
231
 
204
232
  # Steps by type
205
- steps_by_type = @db.execute("SELECT step_name, COUNT(*) FROM analysis_results GROUP BY step_name")
206
- stats[:steps_by_type] = steps_by_type.to_h
233
+ steps_by_type = @db.exec("SELECT step_name, COUNT(*) FROM analysis_results GROUP BY step_name")
234
+ stats[:steps_by_type] = steps_by_type.each_with_object({}) do |row, hash|
235
+ hash[row["step_name"]] = row["count"].to_i
236
+ end
207
237
 
208
238
  # Total metrics
209
- total_metrics = @db.execute("SELECT COUNT(*) FROM metrics").first[0]
239
+ total_metrics = @db.exec("SELECT COUNT(*) FROM metrics").first["count"].to_i
210
240
  stats[:total_metrics] = total_metrics
211
241
 
212
242
  # Metrics by type
213
- metrics_by_type = @db.execute("SELECT metric_name, COUNT(*) FROM metrics GROUP BY metric_name")
214
- stats[:metrics_by_type] = metrics_by_type.to_h
243
+ metrics_by_type = @db.exec("SELECT metric_name, COUNT(*) FROM metrics GROUP BY metric_name")
244
+ stats[:metrics_by_type] = metrics_by_type.each_with_object({}) do |row, hash|
245
+ hash[row["metric_name"]] = row["count"].to_i
246
+ end
215
247
 
216
248
  # Date range
217
- date_range = @db.execute("SELECT MIN(created_at), MAX(created_at) FROM analysis_results").first
249
+ date_range = @db.exec("SELECT MIN(created_at), MAX(created_at) FROM analysis_results").first
218
250
  stats[:date_range] = {
219
- earliest: date_range[0] ? Time.at(date_range[0]) : nil,
220
- latest: date_range[1] ? Time.at(date_range[1]) : nil
251
+ earliest: date_range["min"] ? Time.parse(date_range["min"]) : nil,
252
+ latest: date_range["max"] ? Time.parse(date_range["max"]) : nil
221
253
  }
222
254
 
223
255
  stats
@@ -228,10 +260,9 @@ module Aidp
228
260
  ensure_connection
229
261
 
230
262
  # Delete existing analysis result
231
- @db.execute(
232
- "DELETE FROM analysis_results WHERE execution_id = ? AND step_name = ?",
233
- execution_id,
234
- step_name
263
+ @db.exec_params(
264
+ "DELETE FROM analysis_results WHERE execution_id = $1 AND step_name = $2",
265
+ [execution_id, step_name]
235
266
  )
236
267
 
237
268
  # Store new analysis result
@@ -243,17 +274,16 @@ module Aidp
243
274
  ensure_connection
244
275
 
245
276
  if execution_id && step_name
246
- @db.execute(
247
- "DELETE FROM analysis_results WHERE execution_id = ? AND step_name = ?",
248
- execution_id,
249
- step_name
277
+ @db.exec_params(
278
+ "DELETE FROM analysis_results WHERE execution_id = $1 AND step_name = $2",
279
+ [execution_id, step_name]
250
280
  )
251
281
  elsif execution_id
252
- @db.execute("DELETE FROM analysis_results WHERE execution_id = ?", execution_id)
282
+ @db.exec_params("DELETE FROM analysis_results WHERE execution_id = $1", [execution_id])
253
283
  elsif step_name
254
- @db.execute("DELETE FROM analysis_results WHERE step_name = ?", step_name)
284
+ @db.exec_params("DELETE FROM analysis_results WHERE step_name = $1", [step_name])
255
285
  else
256
- @db.execute("DELETE FROM analysis_results")
286
+ @db.exec("DELETE FROM analysis_results")
257
287
  end
258
288
 
259
289
  {success: true, deleted_execution_id: execution_id, deleted_step_name: step_name}
@@ -295,27 +325,43 @@ module Aidp
295
325
 
296
326
  # Import analysis results
297
327
  parsed_data["analysis_results"]&.each do |result|
298
- @db.execute(
299
- "INSERT OR REPLACE INTO analysis_results (execution_id, step_name, data, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
300
- result["execution_id"],
301
- result["step_name"],
302
- result["data"],
303
- result["metadata"],
304
- result["created_at"],
305
- result["updated_at"]
328
+ @db.exec_params(
329
+ <<~SQL,
330
+ INSERT INTO analysis_results (execution_id, step_name, data, metadata, created_at, updated_at)
331
+ VALUES ($1, $2, $3, $4, $5, $6)
332
+ ON CONFLICT (execution_id, step_name)
333
+ DO UPDATE SET
334
+ data = EXCLUDED.data,
335
+ metadata = EXCLUDED.metadata,
336
+ updated_at = EXCLUDED.updated_at
337
+ SQL
338
+ [
339
+ result["execution_id"],
340
+ result["step_name"],
341
+ result["data"],
342
+ result["metadata"],
343
+ result["created_at"],
344
+ result["updated_at"]
345
+ ]
306
346
  )
307
347
  end
308
348
 
309
349
  # Import metrics
310
350
  parsed_data["metrics"]&.each do |metric|
311
- @db.execute(
312
- "INSERT OR REPLACE INTO metrics (execution_id, step_name, metric_name, metric_value, metric_type, created_at) VALUES (?, ?, ?, ?, ?, ?)",
313
- metric["execution_id"],
314
- metric["step_name"],
315
- metric["metric_name"],
316
- metric["metric_value"],
317
- metric["metric_type"],
318
- metric["created_at"]
351
+ @db.exec_params(
352
+ <<~SQL,
353
+ INSERT INTO metrics (execution_id, step_name, metric_name, metric_value, metric_type, created_at)
354
+ VALUES ($1, $2, $3, $4, $5, $6)
355
+ ON CONFLICT DO NOTHING
356
+ SQL
357
+ [
358
+ metric["execution_id"],
359
+ metric["step_name"],
360
+ metric["metric_name"],
361
+ metric["metric_value"],
362
+ metric["metric_type"],
363
+ metric["created_at"]
364
+ ]
319
365
  )
320
366
  end
321
367
 
@@ -331,71 +377,78 @@ module Aidp
331
377
  private
332
378
 
333
379
  def ensure_database_exists
334
- return if File.exist?(@db_path)
335
-
336
- @db = SQLite3::Database.new(@db_path)
380
+ ensure_connection
337
381
  create_schema
338
382
  end
339
383
 
340
384
  def ensure_connection
341
- @db ||= SQLite3::Database.new(@db_path)
385
+ return if @db
386
+
387
+ @db = PG.connect(
388
+ host: ENV["AIDP_DB_HOST"] || "localhost",
389
+ port: ENV["AIDP_DB_PORT"] || 5432,
390
+ dbname: ENV["AIDP_DB_NAME"] || "aidp",
391
+ user: ENV["AIDP_DB_USER"] || ENV["USER"],
392
+ password: ENV["AIDP_DB_PASSWORD"]
393
+ )
394
+ @db.type_map_for_results = PG::BasicTypeMapForResults.new(@db)
342
395
  end
343
396
 
344
397
  def create_schema
345
398
  # Create analysis_results table
346
- @db.execute(<<~SQL)
399
+ @db.exec(<<~SQL)
347
400
  CREATE TABLE IF NOT EXISTS analysis_results (
348
- id INTEGER PRIMARY KEY AUTOINCREMENT,
401
+ id SERIAL PRIMARY KEY,
349
402
  execution_id TEXT NOT NULL,
350
403
  step_name TEXT NOT NULL,
351
- data TEXT NOT NULL,
352
- metadata TEXT,
353
- created_at INTEGER NOT NULL,
354
- updated_at INTEGER NOT NULL,
404
+ data JSONB NOT NULL,
405
+ metadata JSONB,
406
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL,
407
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
355
408
  UNIQUE(execution_id, step_name)
356
409
  )
357
410
  SQL
358
411
 
359
412
  # Create metrics table (indefinite retention)
360
- @db.execute(<<~SQL)
413
+ @db.exec(<<~SQL)
361
414
  CREATE TABLE IF NOT EXISTS metrics (
362
- id INTEGER PRIMARY KEY AUTOINCREMENT,
415
+ id SERIAL PRIMARY KEY,
363
416
  execution_id TEXT NOT NULL,
364
417
  step_name TEXT NOT NULL,
365
418
  metric_name TEXT NOT NULL,
366
419
  metric_value TEXT NOT NULL,
367
420
  metric_type TEXT NOT NULL,
368
- created_at INTEGER NOT NULL
421
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL
369
422
  )
370
423
  SQL
371
424
 
372
425
  # Create aggregated_metrics table
373
- @db.execute(<<~SQL)
426
+ @db.exec(<<~SQL)
374
427
  CREATE TABLE IF NOT EXISTS aggregated_metrics (
375
- id INTEGER PRIMARY KEY AUTOINCREMENT,
428
+ id SERIAL PRIMARY KEY,
376
429
  execution_id TEXT NOT NULL,
377
430
  step_name TEXT NOT NULL,
378
431
  metric_name TEXT NOT NULL,
379
- min_value REAL,
380
- max_value REAL,
381
- avg_value REAL,
432
+ min_value DOUBLE PRECISION,
433
+ max_value DOUBLE PRECISION,
434
+ avg_value DOUBLE PRECISION,
382
435
  count INTEGER NOT NULL,
383
- created_at INTEGER NOT NULL
436
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL
384
437
  )
385
438
  SQL
386
439
 
387
440
  # Create indexes
388
- @db.execute("CREATE INDEX IF NOT EXISTS idx_analysis_results_execution_id ON analysis_results(execution_id)")
389
- @db.execute("CREATE INDEX IF NOT EXISTS idx_analysis_results_step_name ON analysis_results(step_name)")
390
- @db.execute("CREATE INDEX IF NOT EXISTS idx_analysis_results_created_at ON analysis_results(created_at)")
391
- @db.execute("CREATE INDEX IF NOT EXISTS idx_metrics_execution_id ON metrics(execution_id)")
392
- @db.execute("CREATE INDEX IF NOT EXISTS idx_metrics_step_name ON metrics(step_name)")
393
- @db.execute("CREATE INDEX IF NOT EXISTS idx_metrics_metric_name ON metrics(metric_name)")
394
- @db.execute("CREATE INDEX IF NOT EXISTS idx_metrics_created_at ON metrics(created_at)")
441
+ @db.exec("CREATE INDEX IF NOT EXISTS idx_analysis_results_execution_id ON analysis_results(execution_id)")
442
+ @db.exec("CREATE INDEX IF NOT EXISTS idx_analysis_results_step_name ON analysis_results(step_name)")
443
+ @db.exec("CREATE INDEX IF NOT EXISTS idx_analysis_results_created_at ON analysis_results(created_at)")
444
+ @db.exec("CREATE INDEX IF NOT EXISTS idx_metrics_execution_id ON metrics(execution_id)")
445
+ @db.exec("CREATE INDEX IF NOT EXISTS idx_metrics_step_name ON metrics(step_name)")
446
+ @db.exec("CREATE INDEX IF NOT EXISTS idx_metrics_metric_name ON metrics(metric_name)")
447
+ @db.exec("CREATE INDEX IF NOT EXISTS idx_metrics_created_at ON metrics(created_at)")
395
448
 
396
449
  # Store schema version
397
- @db.execute("CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)")
398
- @db.execute("INSERT OR REPLACE INTO schema_version (version) VALUES (?)", SCHEMA_VERSION)
450
+ @db.exec("CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)")
451
+ @db.exec_params("INSERT INTO schema_version (version) VALUES ($1) ON CONFLICT DO NOTHING", [SCHEMA_VERSION])
399
452
  end
400
453
 
401
454
  def generate_execution_id
@@ -436,41 +489,37 @@ module Aidp
436
489
  next unless metric_value.is_a?(Numeric)
437
490
 
438
491
  # Get existing aggregated metric
439
- existing = @db.execute(
440
- "SELECT * FROM aggregated_metrics WHERE execution_id = ? AND step_name = ? AND metric_name = ?",
441
- execution_id,
442
- step_name,
443
- metric_name
492
+ existing = @db.exec_params(
493
+ <<~SQL,
494
+ SELECT * FROM aggregated_metrics
495
+ WHERE execution_id = $1 AND step_name = $2 AND metric_name = $3
496
+ SQL
497
+ [execution_id, step_name, metric_name]
444
498
  ).first
445
499
 
446
500
  if existing
447
501
  # Update existing aggregated metric
448
- count = existing[7] + 1
449
- min_value = [existing[3], metric_value].min
450
- max_value = [existing[4], metric_value].max
451
- avg_value = ((existing[5] * existing[7]) + metric_value) / count
452
-
453
- @db.execute(
454
- "UPDATE aggregated_metrics SET min_value = ?, max_value = ?, avg_value = ?, count = ?, created_at = ? WHERE id = ?",
455
- min_value,
456
- max_value,
457
- avg_value,
458
- count,
459
- timestamp.to_i,
460
- existing[0]
502
+ count = existing["count"].to_i + 1
503
+ min_value = [existing["min_value"].to_f, metric_value].min
504
+ max_value = [existing["max_value"].to_f, metric_value].max
505
+ avg_value = ((existing["avg_value"].to_f * existing["count"].to_i) + metric_value) / count
506
+
507
+ @db.exec_params(
508
+ <<~SQL,
509
+ UPDATE aggregated_metrics
510
+ SET min_value = $1, max_value = $2, avg_value = $3, count = $4, created_at = $5
511
+ WHERE id = $6
512
+ SQL
513
+ [min_value, max_value, avg_value, count, timestamp, existing["id"]]
461
514
  )
462
515
  else
463
516
  # Create new aggregated metric
464
- @db.execute(
465
- "INSERT INTO aggregated_metrics (execution_id, step_name, metric_name, min_value, max_value, avg_value, count, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
466
- execution_id,
467
- step_name,
468
- metric_name,
469
- metric_value,
470
- metric_value,
471
- metric_value,
472
- 1,
473
- timestamp.to_i
517
+ @db.exec_params(
518
+ <<~SQL,
519
+ INSERT INTO aggregated_metrics (execution_id, step_name, metric_name, min_value, max_value, avg_value, count, created_at)
520
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
521
+ SQL
522
+ [execution_id, step_name, metric_name, metric_value, metric_value, metric_value, 1, timestamp]
474
523
  )
475
524
  end
476
525
  end
@@ -480,13 +529,13 @@ module Aidp
480
529
  return nil unless row
481
530
 
482
531
  {
483
- id: row[0],
484
- execution_id: row[1],
485
- step_name: row[2],
486
- data: JSON.parse(row[3]),
487
- metadata: JSON.parse(row[4] || "{}"),
488
- created_at: Time.at(row[5]),
489
- updated_at: Time.at(row[6])
532
+ id: row["id"].to_i,
533
+ execution_id: row["execution_id"],
534
+ step_name: row["step_name"],
535
+ data: JSON.parse(row["data"]),
536
+ metadata: JSON.parse(row["metadata"] || "{}"),
537
+ created_at: Time.parse(row["created_at"]),
538
+ updated_at: Time.parse(row["updated_at"])
490
539
  }
491
540
  end
492
541
 
@@ -494,13 +543,13 @@ module Aidp
494
543
  return nil unless row
495
544
 
496
545
  {
497
- id: row[0],
498
- execution_id: row[1],
499
- step_name: row[2],
500
- metric_name: row[3],
501
- metric_value: row[4],
502
- metric_type: row[5],
503
- created_at: Time.at(row[6])
546
+ id: row["id"].to_i,
547
+ execution_id: row["execution_id"],
548
+ step_name: row["step_name"],
549
+ metric_name: row["metric_name"],
550
+ metric_value: row["metric_value"],
551
+ metric_type: row["metric_type"],
552
+ created_at: Time.parse(row["created_at"])
504
553
  }
505
554
  end
506
555
 
@@ -508,15 +557,15 @@ module Aidp
508
557
  return nil unless row
509
558
 
510
559
  {
511
- id: row[0],
512
- execution_id: row[1],
513
- step_name: row[2],
514
- metric_name: row[3],
515
- min_value: row[4],
516
- max_value: row[5],
517
- avg_value: row[6],
518
- count: row[7],
519
- created_at: Time.at(row[8])
560
+ id: row["id"].to_i,
561
+ execution_id: row["execution_id"],
562
+ step_name: row["step_name"],
563
+ metric_name: row["metric_name"],
564
+ min_value: row["min_value"].to_f,
565
+ max_value: row["max_value"].to_f,
566
+ avg_value: row["avg_value"].to_f,
567
+ count: row["count"].to_i,
568
+ created_at: Time.parse(row["created_at"])
520
569
  }
521
570
  end
522
571
 
@@ -524,10 +573,10 @@ module Aidp
524
573
  return nil unless row
525
574
 
526
575
  {
527
- execution_id: row[0],
528
- step_name: row[1],
529
- created_at: Time.at(row[2]),
530
- updated_at: Time.at(row[3])
576
+ execution_id: row["execution_id"],
577
+ step_name: row["step_name"],
578
+ created_at: Time.parse(row["created_at"]),
579
+ updated_at: Time.parse(row["updated_at"])
531
580
  }
532
581
  end
533
582
 
@@ -536,20 +585,22 @@ module Aidp
536
585
 
537
586
  query = "SELECT * FROM analysis_results"
538
587
  params = []
588
+ param_index = 1
539
589
 
540
590
  if options[:execution_id]
541
- query += " WHERE execution_id = ?"
591
+ query += " WHERE execution_id = $#{param_index}"
542
592
  params << options[:execution_id]
593
+ param_index += 1
543
594
  end
544
595
 
545
596
  query += " ORDER BY created_at DESC"
546
597
 
547
598
  if options[:limit]
548
- query += " LIMIT ?"
599
+ query += " LIMIT $#{param_index}"
549
600
  params << options[:limit]
550
601
  end
551
602
 
552
- results = @db.execute(query, *params)
603
+ results = @db.exec_params(query, params)
553
604
  results.map { |row| parse_analysis_result(row) }
554
605
  end
555
606
 
@@ -558,20 +609,22 @@ module Aidp
558
609
 
559
610
  query = "SELECT * FROM metrics"
560
611
  params = []
612
+ param_index = 1
561
613
 
562
614
  if options[:execution_id]
563
- query += " WHERE execution_id = ?"
615
+ query += " WHERE execution_id = $#{param_index}"
564
616
  params << options[:execution_id]
617
+ param_index += 1
565
618
  end
566
619
 
567
620
  query += " ORDER BY created_at DESC"
568
621
 
569
622
  if options[:limit]
570
- query += " LIMIT ?"
623
+ query += " LIMIT $#{param_index}"
571
624
  params << options[:limit]
572
625
  end
573
626
 
574
- results = @db.execute(query, *params)
627
+ results = @db.exec_params(query, params)
575
628
  results.map { |row| parse_metric(row) }
576
629
  end
577
630
 
@@ -580,20 +633,22 @@ module Aidp
580
633
 
581
634
  query = "SELECT * FROM aggregated_metrics"
582
635
  params = []
636
+ param_index = 1
583
637
 
584
638
  if options[:execution_id]
585
- query += " WHERE execution_id = ?"
639
+ query += " WHERE execution_id = $#{param_index}"
586
640
  params << options[:execution_id]
641
+ param_index += 1
587
642
  end
588
643
 
589
644
  query += " ORDER BY created_at DESC"
590
645
 
591
646
  if options[:limit]
592
- query += " LIMIT ?"
647
+ query += " LIMIT $#{param_index}"
593
648
  params << options[:limit]
594
649
  end
595
650
 
596
- results = @db.execute(query, *params)
651
+ results = @db.exec_params(query, params)
597
652
  results.map { |row| parse_aggregated_metric(row) }
598
653
  end
599
654
  end