aidp 0.5.0 → 0.8.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +128 -151
  3. data/bin/aidp +1 -1
  4. data/lib/aidp/analysis/kb_inspector.rb +471 -0
  5. data/lib/aidp/analysis/seams.rb +159 -0
  6. data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +480 -0
  7. data/lib/aidp/analysis/tree_sitter_scan.rb +686 -0
  8. data/lib/aidp/analyze/error_handler.rb +2 -78
  9. data/lib/aidp/analyze/json_file_storage.rb +292 -0
  10. data/lib/aidp/analyze/progress.rb +12 -0
  11. data/lib/aidp/analyze/progress_visualizer.rb +12 -17
  12. data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
  13. data/lib/aidp/analyze/runner.rb +256 -87
  14. data/lib/aidp/analyze/steps.rb +6 -0
  15. data/lib/aidp/cli/jobs_command.rb +103 -435
  16. data/lib/aidp/cli.rb +317 -191
  17. data/lib/aidp/config.rb +298 -10
  18. data/lib/aidp/debug_logger.rb +195 -0
  19. data/lib/aidp/debug_mixin.rb +187 -0
  20. data/lib/aidp/execute/progress.rb +9 -0
  21. data/lib/aidp/execute/runner.rb +221 -40
  22. data/lib/aidp/execute/steps.rb +17 -7
  23. data/lib/aidp/execute/workflow_selector.rb +211 -0
  24. data/lib/aidp/harness/completion_checker.rb +268 -0
  25. data/lib/aidp/harness/condition_detector.rb +1526 -0
  26. data/lib/aidp/harness/config_loader.rb +373 -0
  27. data/lib/aidp/harness/config_manager.rb +382 -0
  28. data/lib/aidp/harness/config_schema.rb +1006 -0
  29. data/lib/aidp/harness/config_validator.rb +355 -0
  30. data/lib/aidp/harness/configuration.rb +477 -0
  31. data/lib/aidp/harness/enhanced_runner.rb +494 -0
  32. data/lib/aidp/harness/error_handler.rb +616 -0
  33. data/lib/aidp/harness/provider_config.rb +423 -0
  34. data/lib/aidp/harness/provider_factory.rb +306 -0
  35. data/lib/aidp/harness/provider_manager.rb +1269 -0
  36. data/lib/aidp/harness/provider_type_checker.rb +88 -0
  37. data/lib/aidp/harness/runner.rb +411 -0
  38. data/lib/aidp/harness/state/errors.rb +28 -0
  39. data/lib/aidp/harness/state/metrics.rb +219 -0
  40. data/lib/aidp/harness/state/persistence.rb +128 -0
  41. data/lib/aidp/harness/state/provider_state.rb +132 -0
  42. data/lib/aidp/harness/state/ui_state.rb +68 -0
  43. data/lib/aidp/harness/state/workflow_state.rb +123 -0
  44. data/lib/aidp/harness/state_manager.rb +586 -0
  45. data/lib/aidp/harness/status_display.rb +888 -0
  46. data/lib/aidp/harness/ui/base.rb +16 -0
  47. data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
  48. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
  49. data/lib/aidp/harness/ui/error_handler.rb +132 -0
  50. data/lib/aidp/harness/ui/frame_manager.rb +361 -0
  51. data/lib/aidp/harness/ui/job_monitor.rb +500 -0
  52. data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
  53. data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
  54. data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
  55. data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
  56. data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
  57. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
  58. data/lib/aidp/harness/ui/progress_display.rb +280 -0
  59. data/lib/aidp/harness/ui/question_collector.rb +141 -0
  60. data/lib/aidp/harness/ui/spinner_group.rb +184 -0
  61. data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
  62. data/lib/aidp/harness/ui/status_manager.rb +312 -0
  63. data/lib/aidp/harness/ui/status_widget.rb +280 -0
  64. data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
  65. data/lib/aidp/harness/user_interface.rb +2381 -0
  66. data/lib/aidp/provider_manager.rb +131 -7
  67. data/lib/aidp/providers/anthropic.rb +28 -109
  68. data/lib/aidp/providers/base.rb +170 -0
  69. data/lib/aidp/providers/cursor.rb +52 -183
  70. data/lib/aidp/providers/gemini.rb +24 -109
  71. data/lib/aidp/providers/macos_ui.rb +99 -5
  72. data/lib/aidp/providers/opencode.rb +194 -0
  73. data/lib/aidp/storage/csv_storage.rb +172 -0
  74. data/lib/aidp/storage/file_manager.rb +214 -0
  75. data/lib/aidp/storage/json_storage.rb +140 -0
  76. data/lib/aidp/version.rb +1 -1
  77. data/lib/aidp.rb +56 -35
  78. data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
  79. data/templates/COMMON/AGENT_BASE.md +11 -0
  80. data/templates/EXECUTE/00_PRD.md +4 -4
  81. data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
  82. data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
  83. data/templates/EXECUTE/08_TASKS.md +4 -4
  84. data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
  85. data/templates/README.md +279 -0
  86. data/templates/aidp-development.yml.example +373 -0
  87. data/templates/aidp-minimal.yml.example +48 -0
  88. data/templates/aidp-production.yml.example +475 -0
  89. data/templates/aidp.yml.example +598 -0
  90. metadata +106 -64
  91. data/lib/aidp/analyze/agent_personas.rb +0 -71
  92. data/lib/aidp/analyze/agent_tool_executor.rb +0 -445
  93. data/lib/aidp/analyze/data_retention_manager.rb +0 -426
  94. data/lib/aidp/analyze/database.rb +0 -260
  95. data/lib/aidp/analyze/dependencies.rb +0 -335
  96. data/lib/aidp/analyze/export_manager.rb +0 -425
  97. data/lib/aidp/analyze/focus_guidance.rb +0 -517
  98. data/lib/aidp/analyze/incremental_analyzer.rb +0 -543
  99. data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
  100. data/lib/aidp/analyze/large_analysis_progress.rb +0 -504
  101. data/lib/aidp/analyze/memory_manager.rb +0 -365
  102. data/lib/aidp/analyze/metrics_storage.rb +0 -336
  103. data/lib/aidp/analyze/parallel_processor.rb +0 -460
  104. data/lib/aidp/analyze/performance_optimizer.rb +0 -694
  105. data/lib/aidp/analyze/repository_chunker.rb +0 -704
  106. data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
  107. data/lib/aidp/analyze/storage.rb +0 -662
  108. data/lib/aidp/analyze/tool_configuration.rb +0 -456
  109. data/lib/aidp/analyze/tool_modernization.rb +0 -750
  110. data/lib/aidp/database/pg_adapter.rb +0 -148
  111. data/lib/aidp/database_config.rb +0 -69
  112. data/lib/aidp/database_connection.rb +0 -72
  113. data/lib/aidp/database_migration.rb +0 -158
  114. data/lib/aidp/job_manager.rb +0 -41
  115. data/lib/aidp/jobs/base_job.rb +0 -47
  116. data/lib/aidp/jobs/provider_execution_job.rb +0 -96
  117. data/lib/aidp/project_detector.rb +0 -117
  118. data/lib/aidp/providers/agent_supervisor.rb +0 -348
  119. data/lib/aidp/providers/supervised_base.rb +0 -317
  120. data/lib/aidp/providers/supervised_cursor.rb +0 -22
  121. data/lib/aidp/sync.rb +0 -13
  122. data/lib/aidp/workspace.rb +0 -19
@@ -1,426 +0,0 @@
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
@@ -1,260 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pg"
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
- ensure_database_exists
12
- end
13
-
14
- # Store analysis results with retention policies
15
- def store_analysis_result(step_name, data, metadata = {})
16
- db = connect
17
-
18
- # Store the main analysis result
19
- db.exec_params(
20
- <<~SQL,
21
- INSERT INTO analysis_results (step_name, data, metadata, created_at, updated_at)
22
- VALUES ($1, $2, $3, $4, $5)
23
- ON CONFLICT (step_name)
24
- DO UPDATE SET
25
- data = EXCLUDED.data,
26
- metadata = EXCLUDED.metadata,
27
- updated_at = EXCLUDED.updated_at
28
- SQL
29
- [step_name, data.to_json, metadata.to_json, Time.now, Time.now]
30
- )
31
-
32
- # Store metrics for indefinite retention
33
- store_metrics(step_name, metadata[:metrics]) if metadata[:metrics]
34
- end
35
-
36
- # Store metrics that should be retained indefinitely
37
- def store_metrics(step_name, metrics)
38
- db = connect
39
-
40
- metrics.each do |metric_name, value|
41
- db.exec_params(
42
- <<~SQL,
43
- INSERT INTO analysis_metrics (step_name, metric_name, value, recorded_at)
44
- VALUES ($1, $2, $3, $4)
45
- ON CONFLICT (step_name, metric_name, recorded_at)
46
- DO UPDATE SET value = EXCLUDED.value
47
- SQL
48
- [step_name, metric_name.to_s, value.to_json, Time.now]
49
- )
50
- end
51
- end
52
-
53
- # Store embedding vectors for future semantic analysis
54
- def store_embeddings(step_name, embeddings_data)
55
- db = connect
56
-
57
- db.exec_params(
58
- <<~SQL,
59
- INSERT INTO embeddings (step_name, embeddings_data, created_at)
60
- VALUES ($1, $2, $3)
61
- ON CONFLICT (step_name)
62
- DO UPDATE SET
63
- embeddings_data = EXCLUDED.embeddings_data,
64
- created_at = EXCLUDED.created_at
65
- SQL
66
- [step_name, embeddings_data.to_json, Time.now]
67
- )
68
- end
69
-
70
- # Retrieve analysis results
71
- def get_analysis_result(step_name)
72
- db = connect
73
- result = db.exec_params(
74
- "SELECT data, metadata, created_at, updated_at FROM analysis_results WHERE step_name = $1",
75
- [step_name]
76
- ).first
77
-
78
- return nil unless result
79
-
80
- {
81
- data: JSON.parse(result["data"]),
82
- metadata: JSON.parse(result["metadata"]),
83
- created_at: result["created_at"],
84
- updated_at: result["updated_at"]
85
- }
86
- end
87
-
88
- # Retrieve metrics for a step
89
- def get_metrics(step_name)
90
- db = connect
91
- results = db.exec_params(
92
- "SELECT metric_name, value, recorded_at FROM analysis_metrics WHERE step_name = $1 ORDER BY recorded_at DESC",
93
- [step_name]
94
- )
95
-
96
- results.map do |row|
97
- {
98
- metric_name: row["metric_name"],
99
- value: JSON.parse(row["value"]),
100
- recorded_at: row["recorded_at"]
101
- }
102
- end
103
- end
104
-
105
- # Get all metrics for trend analysis
106
- def get_all_metrics
107
- db = connect
108
- results = db.exec("SELECT step_name, metric_name, value, recorded_at FROM analysis_metrics ORDER BY recorded_at DESC")
109
-
110
- results.map do |row|
111
- {
112
- step_name: row["step_name"],
113
- metric_name: row["metric_name"],
114
- value: JSON.parse(row["value"]),
115
- recorded_at: row["recorded_at"]
116
- }
117
- end
118
- end
119
-
120
- # Force overwrite analysis data (for --force/--rerun flags)
121
- def force_overwrite(step_name, data, metadata = {})
122
- db = connect
123
-
124
- # Delete existing data
125
- db.exec_params("DELETE FROM analysis_results WHERE step_name = $1", [step_name])
126
- db.exec_params("DELETE FROM embeddings WHERE step_name = $1", [step_name])
127
-
128
- # Store new data
129
- db.exec_params(
130
- <<~SQL,
131
- INSERT INTO analysis_results (step_name, data, metadata, created_at, updated_at)
132
- VALUES ($1, $2, $3, $4, $5)
133
- SQL
134
- [step_name, data.to_json, metadata.to_json, Time.now, Time.now]
135
- )
136
-
137
- # Store metrics (these are retained indefinitely)
138
- store_metrics(step_name, metadata[:metrics]) if metadata[:metrics]
139
- end
140
-
141
- # Delete analysis data (for user cleanup)
142
- def delete_analysis_data(step_name)
143
- db = connect
144
- db.exec_params("DELETE FROM analysis_results WHERE step_name = $1", [step_name])
145
- db.exec_params("DELETE FROM embeddings WHERE step_name = $1", [step_name])
146
- # NOTE: metrics are NOT deleted as they should be retained indefinitely
147
- end
148
-
149
- # Export data in different formats
150
- def export_data(step_name, format = "json")
151
- result = get_analysis_result(step_name)
152
- return nil unless result
153
-
154
- case format.downcase
155
- when "json"
156
- result.to_json
157
- when "csv"
158
- export_to_csv(result)
159
- else
160
- raise "Unsupported export format: #{format}"
161
- end
162
- end
163
-
164
- # Get database statistics
165
- def get_statistics
166
- db = connect
167
-
168
- {
169
- total_analysis_results: db.exec("SELECT COUNT(*) FROM analysis_results").first["count"].to_i,
170
- total_metrics: db.exec("SELECT COUNT(*) FROM analysis_metrics").first["count"].to_i,
171
- total_embeddings: db.exec("SELECT COUNT(*) FROM embeddings").first["count"].to_i,
172
- steps_analyzed: db.exec("SELECT DISTINCT step_name FROM analysis_results").map { |row| row["step_name"] },
173
- oldest_metric: db.exec("SELECT MIN(recorded_at) FROM analysis_metrics").first["min"],
174
- newest_metric: db.exec("SELECT MAX(recorded_at) FROM analysis_metrics").first["max"]
175
- }
176
- end
177
-
178
- private
179
-
180
- def ensure_database_exists
181
- db = connect
182
- create_schema(db)
183
- end
184
-
185
- def connect
186
- @db ||= PG.connect(
187
- host: ENV["AIDP_DB_HOST"] || "localhost",
188
- port: ENV["AIDP_DB_PORT"] || 5432,
189
- dbname: ENV["AIDP_DB_NAME"] || "aidp",
190
- user: ENV["AIDP_DB_USER"] || ENV["USER"],
191
- password: ENV["AIDP_DB_PASSWORD"]
192
- )
193
- @db.type_map_for_results = PG::BasicTypeMapForResults.new(@db)
194
- @db
195
- end
196
-
197
- def create_schema(db)
198
- # Create analysis_results table
199
- db.exec(<<~SQL)
200
- CREATE TABLE IF NOT EXISTS analysis_results (
201
- step_name TEXT PRIMARY KEY,
202
- data JSONB NOT NULL,
203
- metadata JSONB,
204
- created_at TIMESTAMP WITH TIME ZONE NOT NULL,
205
- updated_at TIMESTAMP WITH TIME ZONE NOT NULL
206
- )
207
- SQL
208
-
209
- # Create analysis_metrics table (indefinite retention)
210
- db.exec(<<~SQL)
211
- CREATE TABLE IF NOT EXISTS analysis_metrics (
212
- id SERIAL PRIMARY KEY,
213
- step_name TEXT NOT NULL,
214
- metric_name TEXT NOT NULL,
215
- value JSONB NOT NULL,
216
- recorded_at TIMESTAMP WITH TIME ZONE NOT NULL,
217
- UNIQUE(step_name, metric_name, recorded_at)
218
- )
219
- SQL
220
-
221
- # Create embeddings table (for future semantic analysis)
222
- db.exec(<<~SQL)
223
- CREATE TABLE IF NOT EXISTS embeddings (
224
- step_name TEXT PRIMARY KEY,
225
- embeddings_data JSONB NOT NULL,
226
- created_at TIMESTAMP WITH TIME ZONE NOT NULL
227
- )
228
- SQL
229
-
230
- # Create indexes for better performance
231
- db.exec("CREATE INDEX IF NOT EXISTS idx_analysis_metrics_step_name ON analysis_metrics(step_name)")
232
- db.exec("CREATE INDEX IF NOT EXISTS idx_analysis_metrics_recorded_at ON analysis_metrics(recorded_at)")
233
- db.exec("CREATE INDEX IF NOT EXISTS idx_analysis_results_updated_at ON analysis_results(updated_at)")
234
- end
235
-
236
- def export_to_csv(data)
237
- require "csv"
238
-
239
- # For now, export a simplified CSV format
240
- # This can be enhanced based on specific data structures
241
- CSV.generate do |csv|
242
- csv << %w[Field Value]
243
- csv << ["created_at", data[:created_at]]
244
- csv << ["updated_at", data[:updated_at]]
245
-
246
- # Add metadata fields
247
- data[:metadata].each do |key, value|
248
- csv << ["metadata_#{key}", value]
249
- end
250
-
251
- # Add data summary
252
- if data[:data].is_a?(Hash)
253
- data[:data].each do |key, value|
254
- csv << ["data_#{key}", value.to_s[0..100]] # Truncate long values
255
- end
256
- end
257
- end
258
- end
259
- end
260
- end