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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +210 -0
  4. data/bin/aidp +5 -0
  5. data/lib/aidp/analyze/agent_personas.rb +71 -0
  6. data/lib/aidp/analyze/agent_tool_executor.rb +445 -0
  7. data/lib/aidp/analyze/data_retention_manager.rb +426 -0
  8. data/lib/aidp/analyze/database.rb +243 -0
  9. data/lib/aidp/analyze/dependencies.rb +335 -0
  10. data/lib/aidp/analyze/error_handler.rb +486 -0
  11. data/lib/aidp/analyze/export_manager.rb +425 -0
  12. data/lib/aidp/analyze/feature_analyzer.rb +397 -0
  13. data/lib/aidp/analyze/focus_guidance.rb +517 -0
  14. data/lib/aidp/analyze/incremental_analyzer.rb +543 -0
  15. data/lib/aidp/analyze/language_analysis_strategies.rb +897 -0
  16. data/lib/aidp/analyze/large_analysis_progress.rb +504 -0
  17. data/lib/aidp/analyze/memory_manager.rb +365 -0
  18. data/lib/aidp/analyze/parallel_processor.rb +460 -0
  19. data/lib/aidp/analyze/performance_optimizer.rb +694 -0
  20. data/lib/aidp/analyze/prioritizer.rb +402 -0
  21. data/lib/aidp/analyze/progress.rb +75 -0
  22. data/lib/aidp/analyze/progress_visualizer.rb +320 -0
  23. data/lib/aidp/analyze/report_generator.rb +582 -0
  24. data/lib/aidp/analyze/repository_chunker.rb +702 -0
  25. data/lib/aidp/analyze/ruby_maat_integration.rb +572 -0
  26. data/lib/aidp/analyze/runner.rb +245 -0
  27. data/lib/aidp/analyze/static_analysis_detector.rb +577 -0
  28. data/lib/aidp/analyze/steps.rb +53 -0
  29. data/lib/aidp/analyze/storage.rb +600 -0
  30. data/lib/aidp/analyze/tool_configuration.rb +456 -0
  31. data/lib/aidp/analyze/tool_modernization.rb +750 -0
  32. data/lib/aidp/execute/progress.rb +76 -0
  33. data/lib/aidp/execute/runner.rb +135 -0
  34. data/lib/aidp/execute/steps.rb +113 -0
  35. data/lib/aidp/shared/cli.rb +117 -0
  36. data/lib/aidp/shared/config.rb +35 -0
  37. data/lib/aidp/shared/project_detector.rb +119 -0
  38. data/lib/aidp/shared/providers/anthropic.rb +26 -0
  39. data/lib/aidp/shared/providers/base.rb +17 -0
  40. data/lib/aidp/shared/providers/cursor.rb +102 -0
  41. data/lib/aidp/shared/providers/gemini.rb +26 -0
  42. data/lib/aidp/shared/providers/macos_ui.rb +26 -0
  43. data/lib/aidp/shared/sync.rb +15 -0
  44. data/lib/aidp/shared/util.rb +41 -0
  45. data/lib/aidp/shared/version.rb +7 -0
  46. data/lib/aidp/shared/workspace.rb +21 -0
  47. data/lib/aidp.rb +53 -0
  48. data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +100 -0
  49. data/templates/ANALYZE/02_ARCHITECTURE_ANALYSIS.md +151 -0
  50. data/templates/ANALYZE/03_TEST_ANALYSIS.md +182 -0
  51. data/templates/ANALYZE/04_FUNCTIONALITY_ANALYSIS.md +200 -0
  52. data/templates/ANALYZE/05_DOCUMENTATION_ANALYSIS.md +202 -0
  53. data/templates/ANALYZE/06_STATIC_ANALYSIS.md +233 -0
  54. data/templates/ANALYZE/07_REFACTORING_RECOMMENDATIONS.md +316 -0
  55. data/templates/COMMON/AGENT_BASE.md +129 -0
  56. data/templates/COMMON/CONVENTIONS.md +19 -0
  57. data/templates/COMMON/TEMPLATES/ADR_TEMPLATE.md +21 -0
  58. data/templates/COMMON/TEMPLATES/DOMAIN_CHARTER.md +27 -0
  59. data/templates/COMMON/TEMPLATES/EVENT_EXAMPLE.yaml +16 -0
  60. data/templates/COMMON/TEMPLATES/MERMAID_C4.md +46 -0
  61. data/templates/COMMON/TEMPLATES/OPENAPI_STUB.yaml +11 -0
  62. data/templates/EXECUTE/00_PRD.md +36 -0
  63. data/templates/EXECUTE/01_NFRS.md +27 -0
  64. data/templates/EXECUTE/02A_ARCH_GATE_QUESTIONS.md +13 -0
  65. data/templates/EXECUTE/02_ARCHITECTURE.md +42 -0
  66. data/templates/EXECUTE/03_ADR_FACTORY.md +22 -0
  67. data/templates/EXECUTE/04_DOMAIN_DECOMPOSITION.md +24 -0
  68. data/templates/EXECUTE/05_CONTRACTS.md +27 -0
  69. data/templates/EXECUTE/06_THREAT_MODEL.md +23 -0
  70. data/templates/EXECUTE/07_TEST_PLAN.md +24 -0
  71. data/templates/EXECUTE/08_TASKS.md +29 -0
  72. data/templates/EXECUTE/09_SCAFFOLDING_DEVEX.md +25 -0
  73. data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +30 -0
  74. data/templates/EXECUTE/11_STATIC_ANALYSIS.md +22 -0
  75. data/templates/EXECUTE/12_OBSERVABILITY_SLOS.md +21 -0
  76. data/templates/EXECUTE/13_DELIVERY_ROLLOUT.md +21 -0
  77. data/templates/EXECUTE/14_DOCS_PORTAL.md +23 -0
  78. data/templates/EXECUTE/15_POST_RELEASE.md +25 -0
  79. metadata +301 -0
@@ -0,0 +1,426 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "yaml"
5
+ require_relative "storage"
6
+
7
+ module Aidp
8
+ module Analyze
9
+ class DataRetentionManager
10
+ # Retention policies
11
+ RETENTION_POLICIES = {
12
+ "metrics" => "indefinite",
13
+ "analysis_results" => "configurable",
14
+ "execution_logs" => "configurable",
15
+ "temporary_data" => "immediate",
16
+ "embeddings" => "indefinite"
17
+ }.freeze
18
+
19
+ # Default retention periods (in days)
20
+ DEFAULT_RETENTION_PERIODS = {
21
+ "analysis_results" => 90, # 3 months
22
+ "execution_logs" => 30, # 1 month
23
+ "temporary_data" => 1 # 1 day
24
+ }.freeze
25
+
26
+ def initialize(project_dir = Dir.pwd, config = {})
27
+ @project_dir = project_dir
28
+ @config = config
29
+ @storage = config[:storage] || Aidp::AnalysisStorage.new(project_dir)
30
+ @retention_config = load_retention_config
31
+ end
32
+
33
+ # Apply retention policies
34
+ def apply_retention_policies(options = {})
35
+ dry_run = options[:dry_run] || false
36
+ options[:force] || false
37
+
38
+ results = {
39
+ cleaned_data: {},
40
+ retained_data: {},
41
+ errors: []
42
+ }
43
+
44
+ # Process each data type
45
+ RETENTION_POLICIES.each do |data_type, policy|
46
+ case policy
47
+ when "indefinite"
48
+ results[:retained_data][data_type] = retain_indefinitely(data_type)
49
+ when "configurable"
50
+ results[:cleaned_data][data_type] = apply_configurable_retention(data_type, dry_run)
51
+ when "immediate"
52
+ results[:cleaned_data][data_type] = clean_immediately(data_type, dry_run)
53
+ end
54
+ rescue => e
55
+ results[:errors] << {
56
+ data_type: data_type,
57
+ error: e.message
58
+ }
59
+ end
60
+
61
+ results
62
+ end
63
+
64
+ # Handle force/rerun operations
65
+ def handle_force_rerun(execution_id, step_name, options = {})
66
+ operation = options[:operation] || "force"
67
+
68
+ case operation
69
+ when "force"
70
+ handle_force_operation(execution_id, step_name, options)
71
+ when "rerun"
72
+ handle_rerun_operation(execution_id, step_name, options)
73
+ else
74
+ raise "Unknown operation: #{operation}"
75
+ end
76
+ end
77
+
78
+ # Clean old data based on retention policies
79
+ def clean_old_data(options = {})
80
+ dry_run = options[:dry_run] || false
81
+ data_types = options[:data_types] || RETENTION_POLICIES.keys
82
+
83
+ results = {
84
+ cleaned: {},
85
+ errors: []
86
+ }
87
+
88
+ data_types.each do |data_type|
89
+ next unless RETENTION_POLICIES[data_type] == "configurable"
90
+
91
+ begin
92
+ cleaned = clean_data_by_type(data_type, dry_run)
93
+ results[:cleaned][data_type] = cleaned
94
+ rescue => e
95
+ results[:errors] << {
96
+ data_type: data_type,
97
+ error: e.message
98
+ }
99
+ end
100
+ end
101
+
102
+ results
103
+ end
104
+
105
+ # Get retention statistics
106
+ def get_retention_statistics
107
+ stats = {
108
+ policies: RETENTION_POLICIES,
109
+ config: @retention_config,
110
+ data_sizes: {},
111
+ retention_status: {}
112
+ }
113
+
114
+ # Calculate data sizes
115
+ RETENTION_POLICIES.keys.each do |data_type|
116
+ stats[:data_sizes][data_type] = calculate_data_size(data_type)
117
+ stats[:retention_status][data_type] = get_retention_status(data_type)
118
+ end
119
+
120
+ stats
121
+ end
122
+
123
+ # Export data with retention metadata
124
+ def export_data_with_retention(data_type, options = {})
125
+ include_retention_info = options[:include_retention_info] || true
126
+
127
+ data = export_data(data_type, options)
128
+
129
+ if include_retention_info
130
+ data[:retention_info] = {
131
+ policy: RETENTION_POLICIES[data_type],
132
+ retention_period: @retention_config[data_type],
133
+ last_cleaned: get_last_cleaned_date(data_type),
134
+ next_cleanup: calculate_next_cleanup(data_type)
135
+ }
136
+ end
137
+
138
+ data
139
+ end
140
+
141
+ # Set retention policy for a data type
142
+ def set_retention_policy(data_type, policy, options = {})
143
+ raise "Unknown data type: #{data_type}" unless RETENTION_POLICIES.key?(data_type)
144
+
145
+ case policy
146
+ when "indefinite"
147
+ @retention_config[data_type] = {policy: "indefinite"}
148
+ when "configurable"
149
+ retention_period = options[:retention_period] || DEFAULT_RETENTION_PERIODS[data_type]
150
+ @retention_config[data_type] = {
151
+ policy: "configurable",
152
+ retention_period: retention_period
153
+ }
154
+ when "immediate"
155
+ @retention_config[data_type] = {policy: "immediate"}
156
+ else
157
+ raise "Unknown retention policy: #{policy}"
158
+ end
159
+
160
+ save_retention_config
161
+
162
+ {
163
+ data_type: data_type,
164
+ policy: policy,
165
+ config: @retention_config[data_type]
166
+ }
167
+ end
168
+
169
+ # Get data retention status
170
+ def get_data_retention_status(data_type)
171
+ {
172
+ data_type: data_type,
173
+ policy: RETENTION_POLICIES[data_type],
174
+ config: @retention_config[data_type],
175
+ data_size: calculate_data_size(data_type),
176
+ last_cleaned: get_last_cleaned_date(data_type),
177
+ next_cleanup: calculate_next_cleanup(data_type),
178
+ retention_status: get_retention_status(data_type)
179
+ }
180
+ end
181
+
182
+ private
183
+
184
+ def load_retention_config
185
+ config_file = File.join(@project_dir, ".aidp-retention-config.yml")
186
+
187
+ if File.exist?(config_file)
188
+ YAML.load_file(config_file) || DEFAULT_RETENTION_PERIODS
189
+ else
190
+ DEFAULT_RETENTION_PERIODS
191
+ end
192
+ end
193
+
194
+ def save_retention_config
195
+ config_file = File.join(@project_dir, ".aidp-retention-config.yml")
196
+ File.write(config_file, YAML.dump(@retention_config))
197
+ end
198
+
199
+ def retain_indefinitely(data_type)
200
+ {
201
+ data_type: data_type,
202
+ action: "retained",
203
+ reason: "Indefinite retention policy",
204
+ timestamp: Time.now
205
+ }
206
+ end
207
+
208
+ def apply_configurable_retention(data_type, dry_run)
209
+ retention_period = @retention_config[data_type] || DEFAULT_RETENTION_PERIODS[data_type]
210
+ cutoff_date = Time.now - (retention_period * 24 * 60 * 60)
211
+
212
+ # Get old data
213
+ old_data = get_old_data(data_type, cutoff_date)
214
+
215
+ if dry_run
216
+ {
217
+ data_type: data_type,
218
+ action: "would_clean",
219
+ records_to_clean: old_data.length,
220
+ cutoff_date: cutoff_date,
221
+ dry_run: true
222
+ }
223
+ else
224
+ # Actually clean the data
225
+ cleaned_count = clean_data(data_type, old_data)
226
+
227
+ {
228
+ data_type: data_type,
229
+ action: "cleaned",
230
+ records_cleaned: cleaned_count,
231
+ cutoff_date: cutoff_date,
232
+ timestamp: Time.now
233
+ }
234
+ end
235
+ end
236
+
237
+ def clean_immediately(data_type, dry_run)
238
+ if dry_run
239
+ {
240
+ data_type: data_type,
241
+ action: "would_clean_immediately",
242
+ dry_run: true
243
+ }
244
+ else
245
+ # Clean all data of this type
246
+ cleaned_count = clean_all_data(data_type)
247
+
248
+ {
249
+ data_type: data_type,
250
+ action: "cleaned_immediately",
251
+ records_cleaned: cleaned_count,
252
+ timestamp: Time.now
253
+ }
254
+ end
255
+ end
256
+
257
+ def handle_force_operation(execution_id, step_name, options)
258
+ # Force operation: overwrite main data, retain metrics
259
+ results = {
260
+ operation: "force",
261
+ execution_id: execution_id,
262
+ step_name: step_name,
263
+ actions: []
264
+ }
265
+
266
+ # Overwrite analysis results
267
+ if options[:analysis_data]
268
+ @storage.force_overwrite(execution_id, step_name, options[:analysis_data])
269
+ results[:actions] << "overwrote_analysis_data"
270
+ end
271
+
272
+ # Retain metrics (indefinite retention)
273
+ results[:actions] << "retained_metrics"
274
+
275
+ results
276
+ end
277
+
278
+ def handle_rerun_operation(execution_id, step_name, options)
279
+ # Rerun operation: overwrite main data, retain metrics
280
+ results = {
281
+ operation: "rerun",
282
+ execution_id: execution_id,
283
+ step_name: step_name,
284
+ actions: []
285
+ }
286
+
287
+ # Overwrite analysis results
288
+ if options[:analysis_data]
289
+ @storage.force_overwrite(execution_id, step_name, options[:analysis_data])
290
+ results[:actions] << "overwrote_analysis_data"
291
+ end
292
+
293
+ # Retain metrics (indefinite retention)
294
+ results[:actions] << "retained_metrics"
295
+
296
+ results
297
+ end
298
+
299
+ def get_old_data(data_type, cutoff_date)
300
+ case data_type
301
+ when "analysis_results"
302
+ get_old_analysis_results(cutoff_date)
303
+ when "execution_logs"
304
+ get_old_execution_logs(cutoff_date)
305
+ when "temporary_data"
306
+ get_old_temporary_data(cutoff_date)
307
+ else
308
+ []
309
+ end
310
+ end
311
+
312
+ def get_old_analysis_results(cutoff_date)
313
+ # Get analysis results older than cutoff date
314
+ # This would query the database for old records
315
+ []
316
+ end
317
+
318
+ def get_old_execution_logs(cutoff_date)
319
+ # Get execution logs older than cutoff date
320
+ []
321
+ end
322
+
323
+ def get_old_temporary_data(cutoff_date)
324
+ # Get temporary data older than cutoff date
325
+ []
326
+ end
327
+
328
+ def clean_data(data_type, old_data)
329
+ case data_type
330
+ when "analysis_results"
331
+ clean_analysis_results(old_data)
332
+ when "execution_logs"
333
+ clean_execution_logs(old_data)
334
+ when "temporary_data"
335
+ clean_temporary_data(old_data)
336
+ else
337
+ 0
338
+ end
339
+ end
340
+
341
+ def clean_all_data(data_type)
342
+ case data_type
343
+ when "temporary_data"
344
+ clean_all_temporary_data
345
+ else
346
+ 0
347
+ end
348
+ end
349
+
350
+ def clean_analysis_results(old_data)
351
+ # Clean old analysis results from database
352
+ # This would delete records from the database
353
+ old_data.length
354
+ end
355
+
356
+ def clean_execution_logs(old_data)
357
+ # Clean old execution logs
358
+ old_data.length
359
+ end
360
+
361
+ def clean_temporary_data(old_data)
362
+ # Clean old temporary data
363
+ old_data.length
364
+ end
365
+
366
+ def clean_all_temporary_data
367
+ # Clean all temporary data
368
+ 0
369
+ end
370
+
371
+ def calculate_data_size(data_type)
372
+ case data_type
373
+ when "metrics"
374
+ @storage.get_analysis_statistics[:total_metrics] || 0
375
+ when "analysis_results"
376
+ @storage.get_analysis_statistics[:total_steps] || 0
377
+ when "execution_logs"
378
+ @storage.get_execution_history.length
379
+ else
380
+ 0
381
+ end
382
+ end
383
+
384
+ def get_retention_status(data_type)
385
+ policy = RETENTION_POLICIES[data_type]
386
+ config = @retention_config[data_type]
387
+
388
+ {
389
+ policy: policy,
390
+ config: config,
391
+ status: "active"
392
+ }
393
+ end
394
+
395
+ def get_last_cleaned_date(data_type)
396
+ # This would be stored in a metadata table
397
+ nil
398
+ end
399
+
400
+ def calculate_next_cleanup(data_type)
401
+ policy = RETENTION_POLICIES[data_type]
402
+
403
+ case policy
404
+ when "indefinite"
405
+ nil
406
+ when "configurable"
407
+ retention_period = @retention_config[data_type] || DEFAULT_RETENTION_PERIODS[data_type]
408
+ Time.now + (retention_period * 24 * 60 * 60)
409
+ when "immediate"
410
+ Time.now
411
+ end
412
+ end
413
+
414
+ def export_data(data_type, options)
415
+ case data_type
416
+ when "metrics"
417
+ @storage.export_data("json", options)
418
+ when "analysis_results"
419
+ @storage.export_data("json", options)
420
+ else
421
+ {error: "Unknown data type: #{data_type}"}
422
+ end
423
+ end
424
+ end
425
+ end
426
+ end
@@ -0,0 +1,243 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+ require "json"
5
+ require "fileutils"
6
+
7
+ module Aidp
8
+ class AnalysisDatabase
9
+ def initialize(project_dir = Dir.pwd)
10
+ @project_dir = project_dir
11
+ @db_path = File.join(project_dir, ".aidp-analysis.db")
12
+ ensure_database_exists
13
+ end
14
+
15
+ # Store analysis results with retention policies
16
+ def store_analysis_result(step_name, data, metadata = {})
17
+ db = connect
18
+
19
+ # Store the main analysis result
20
+ db.execute(
21
+ "INSERT OR REPLACE INTO analysis_results (step_name, data, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
22
+ [step_name, data.to_json, metadata.to_json, Time.now.iso8601, Time.now.iso8601]
23
+ )
24
+
25
+ # Store metrics for indefinite retention
26
+ store_metrics(step_name, metadata[:metrics]) if metadata[:metrics]
27
+
28
+ db.close
29
+ end
30
+
31
+ # Store metrics that should be retained indefinitely
32
+ def store_metrics(step_name, metrics)
33
+ db = connect
34
+
35
+ metrics.each do |metric_name, value|
36
+ db.execute(
37
+ "INSERT OR REPLACE INTO analysis_metrics (step_name, metric_name, value, recorded_at) VALUES (?, ?, ?, ?)",
38
+ [step_name, metric_name.to_s, value.to_json, Time.now.iso8601]
39
+ )
40
+ end
41
+
42
+ db.close
43
+ end
44
+
45
+ # Store embedding vectors for future semantic analysis
46
+ def store_embeddings(step_name, embeddings_data)
47
+ db = connect
48
+
49
+ db.execute(
50
+ "INSERT OR REPLACE INTO embeddings (step_name, embeddings_data, created_at) VALUES (?, ?, ?)",
51
+ [step_name, embeddings_data.to_json, Time.now.iso8601]
52
+ )
53
+
54
+ db.close
55
+ end
56
+
57
+ # Retrieve analysis results
58
+ def get_analysis_result(step_name)
59
+ db = connect
60
+ result = db.execute("SELECT data, metadata, created_at, updated_at FROM analysis_results WHERE step_name = ?",
61
+ [step_name]).first
62
+ db.close
63
+
64
+ return nil unless result
65
+
66
+ {
67
+ data: JSON.parse(result[0]),
68
+ metadata: JSON.parse(result[1]),
69
+ created_at: result[2],
70
+ updated_at: result[3]
71
+ }
72
+ end
73
+
74
+ # Retrieve metrics for a step
75
+ def get_metrics(step_name)
76
+ db = connect
77
+ results = db.execute(
78
+ "SELECT metric_name, value, recorded_at FROM analysis_metrics WHERE step_name = ? ORDER BY recorded_at DESC", [step_name]
79
+ )
80
+ db.close
81
+
82
+ results.map do |row|
83
+ {
84
+ metric_name: row[0],
85
+ value: JSON.parse(row[1]),
86
+ recorded_at: row[2]
87
+ }
88
+ end
89
+ end
90
+
91
+ # Get all metrics for trend analysis
92
+ def get_all_metrics
93
+ db = connect
94
+ results = db.execute("SELECT step_name, metric_name, value, recorded_at FROM analysis_metrics ORDER BY recorded_at DESC")
95
+ db.close
96
+
97
+ results.map do |row|
98
+ {
99
+ step_name: row[0],
100
+ metric_name: row[1],
101
+ value: JSON.parse(row[2]),
102
+ recorded_at: row[2]
103
+ }
104
+ end
105
+ end
106
+
107
+ # Force overwrite analysis data (for --force/--rerun flags)
108
+ def force_overwrite(step_name, data, metadata = {})
109
+ db = connect
110
+
111
+ # Delete existing data
112
+ db.execute("DELETE FROM analysis_results WHERE step_name = ?", [step_name])
113
+ db.execute("DELETE FROM embeddings WHERE step_name = ?", [step_name])
114
+
115
+ # Store new data
116
+ db.execute(
117
+ "INSERT INTO analysis_results (step_name, data, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
118
+ [step_name, data.to_json, metadata.to_json, Time.now.iso8601, Time.now.iso8601]
119
+ )
120
+
121
+ # Store metrics (these are retained indefinitely)
122
+ store_metrics(step_name, metadata[:metrics]) if metadata[:metrics]
123
+
124
+ db.close
125
+ end
126
+
127
+ # Delete analysis data (for user cleanup)
128
+ def delete_analysis_data(step_name)
129
+ db = connect
130
+ db.execute("DELETE FROM analysis_results WHERE step_name = ?", [step_name])
131
+ db.execute("DELETE FROM embeddings WHERE step_name = ?", [step_name])
132
+ # NOTE: metrics are NOT deleted as they should be retained indefinitely
133
+ db.close
134
+ end
135
+
136
+ # Export data in different formats
137
+ def export_data(step_name, format = "json")
138
+ result = get_analysis_result(step_name)
139
+ return nil unless result
140
+
141
+ case format.downcase
142
+ when "json"
143
+ result.to_json
144
+ when "csv"
145
+ export_to_csv(result)
146
+ else
147
+ raise "Unsupported export format: #{format}"
148
+ end
149
+ end
150
+
151
+ # Get database statistics
152
+ def get_statistics
153
+ db = connect
154
+
155
+ stats = {
156
+ total_analysis_results: db.execute("SELECT COUNT(*) FROM analysis_results").first[0],
157
+ total_metrics: db.execute("SELECT COUNT(*) FROM analysis_metrics").first[0],
158
+ total_embeddings: db.execute("SELECT COUNT(*) FROM embeddings").first[0],
159
+ steps_analyzed: db.execute("SELECT DISTINCT step_name FROM analysis_results").map { |row| row[0] },
160
+ oldest_metric: db.execute("SELECT MIN(recorded_at) FROM analysis_metrics").first[0],
161
+ newest_metric: db.execute("SELECT MAX(recorded_at) FROM analysis_metrics").first[0]
162
+ }
163
+
164
+ db.close
165
+ stats
166
+ end
167
+
168
+ private
169
+
170
+ def ensure_database_exists
171
+ return if File.exist?(@db_path)
172
+
173
+ db = SQLite3::Database.new(@db_path)
174
+
175
+ # Create analysis_results table
176
+ db.execute(<<~SQL)
177
+ CREATE TABLE analysis_results (
178
+ step_name TEXT PRIMARY KEY,
179
+ data TEXT NOT NULL,
180
+ metadata TEXT,
181
+ created_at TEXT NOT NULL,
182
+ updated_at TEXT NOT NULL
183
+ )
184
+ SQL
185
+
186
+ # Create analysis_metrics table (indefinite retention)
187
+ db.execute(<<~SQL)
188
+ CREATE TABLE analysis_metrics (
189
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
190
+ step_name TEXT NOT NULL,
191
+ metric_name TEXT NOT NULL,
192
+ value TEXT NOT NULL,
193
+ recorded_at TEXT NOT NULL,
194
+ UNIQUE(step_name, metric_name, recorded_at)
195
+ )
196
+ SQL
197
+
198
+ # Create embeddings table (for future semantic analysis)
199
+ db.execute(<<~SQL)
200
+ CREATE TABLE embeddings (
201
+ step_name TEXT PRIMARY KEY,
202
+ embeddings_data TEXT NOT NULL,
203
+ created_at TEXT NOT NULL
204
+ )
205
+ SQL
206
+
207
+ # Create indexes for better performance
208
+ db.execute("CREATE INDEX idx_analysis_metrics_step_name ON analysis_metrics(step_name)")
209
+ db.execute("CREATE INDEX idx_analysis_metrics_recorded_at ON analysis_metrics(recorded_at)")
210
+ db.execute("CREATE INDEX idx_analysis_results_updated_at ON analysis_results(updated_at)")
211
+
212
+ db.close
213
+ end
214
+
215
+ def connect
216
+ SQLite3::Database.new(@db_path)
217
+ end
218
+
219
+ def export_to_csv(data)
220
+ require "csv"
221
+
222
+ # For now, export a simplified CSV format
223
+ # This can be enhanced based on specific data structures
224
+ CSV.generate do |csv|
225
+ csv << %w[Field Value]
226
+ csv << ["created_at", data[:created_at]]
227
+ csv << ["updated_at", data[:updated_at]]
228
+
229
+ # Add metadata fields
230
+ data[:metadata].each do |key, value|
231
+ csv << ["metadata_#{key}", value]
232
+ end
233
+
234
+ # Add data summary
235
+ if data[:data].is_a?(Hash)
236
+ data[:data].each do |key, value|
237
+ csv << ["data_#{key}", value.to_s[0..100]] # Truncate long values
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end