aidp 0.7.0 → 0.8.1

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -214
  3. data/bin/aidp +1 -1
  4. data/lib/aidp/analysis/kb_inspector.rb +38 -23
  5. data/lib/aidp/analysis/seams.rb +2 -31
  6. data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +1 -13
  7. data/lib/aidp/analysis/tree_sitter_scan.rb +3 -20
  8. data/lib/aidp/analyze/error_handler.rb +2 -75
  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/cli/jobs_command.rb +100 -432
  15. data/lib/aidp/cli.rb +309 -239
  16. data/lib/aidp/config.rb +298 -10
  17. data/lib/aidp/debug_logger.rb +195 -0
  18. data/lib/aidp/debug_mixin.rb +187 -0
  19. data/lib/aidp/execute/progress.rb +9 -0
  20. data/lib/aidp/execute/runner.rb +221 -40
  21. data/lib/aidp/execute/steps.rb +17 -7
  22. data/lib/aidp/execute/workflow_selector.rb +211 -0
  23. data/lib/aidp/harness/completion_checker.rb +268 -0
  24. data/lib/aidp/harness/condition_detector.rb +1526 -0
  25. data/lib/aidp/harness/config_loader.rb +373 -0
  26. data/lib/aidp/harness/config_manager.rb +382 -0
  27. data/lib/aidp/harness/config_schema.rb +1006 -0
  28. data/lib/aidp/harness/config_validator.rb +355 -0
  29. data/lib/aidp/harness/configuration.rb +477 -0
  30. data/lib/aidp/harness/enhanced_runner.rb +494 -0
  31. data/lib/aidp/harness/error_handler.rb +616 -0
  32. data/lib/aidp/harness/provider_config.rb +423 -0
  33. data/lib/aidp/harness/provider_factory.rb +306 -0
  34. data/lib/aidp/harness/provider_manager.rb +1269 -0
  35. data/lib/aidp/harness/provider_type_checker.rb +88 -0
  36. data/lib/aidp/harness/runner.rb +411 -0
  37. data/lib/aidp/harness/state/errors.rb +28 -0
  38. data/lib/aidp/harness/state/metrics.rb +219 -0
  39. data/lib/aidp/harness/state/persistence.rb +128 -0
  40. data/lib/aidp/harness/state/provider_state.rb +132 -0
  41. data/lib/aidp/harness/state/ui_state.rb +68 -0
  42. data/lib/aidp/harness/state/workflow_state.rb +123 -0
  43. data/lib/aidp/harness/state_manager.rb +586 -0
  44. data/lib/aidp/harness/status_display.rb +888 -0
  45. data/lib/aidp/harness/ui/base.rb +16 -0
  46. data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
  47. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
  48. data/lib/aidp/harness/ui/error_handler.rb +132 -0
  49. data/lib/aidp/harness/ui/frame_manager.rb +361 -0
  50. data/lib/aidp/harness/ui/job_monitor.rb +500 -0
  51. data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
  52. data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
  53. data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
  54. data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
  55. data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
  56. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
  57. data/lib/aidp/harness/ui/progress_display.rb +280 -0
  58. data/lib/aidp/harness/ui/question_collector.rb +141 -0
  59. data/lib/aidp/harness/ui/spinner_group.rb +184 -0
  60. data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
  61. data/lib/aidp/harness/ui/status_manager.rb +312 -0
  62. data/lib/aidp/harness/ui/status_widget.rb +280 -0
  63. data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
  64. data/lib/aidp/harness/user_interface.rb +2381 -0
  65. data/lib/aidp/provider_manager.rb +131 -7
  66. data/lib/aidp/providers/anthropic.rb +28 -103
  67. data/lib/aidp/providers/base.rb +170 -0
  68. data/lib/aidp/providers/cursor.rb +52 -181
  69. data/lib/aidp/providers/gemini.rb +24 -107
  70. data/lib/aidp/providers/macos_ui.rb +99 -5
  71. data/lib/aidp/providers/opencode.rb +194 -0
  72. data/lib/aidp/storage/csv_storage.rb +172 -0
  73. data/lib/aidp/storage/file_manager.rb +214 -0
  74. data/lib/aidp/storage/json_storage.rb +140 -0
  75. data/lib/aidp/version.rb +1 -1
  76. data/lib/aidp.rb +54 -39
  77. data/templates/COMMON/AGENT_BASE.md +11 -0
  78. data/templates/EXECUTE/00_PRD.md +4 -4
  79. data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
  80. data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
  81. data/templates/EXECUTE/08_TASKS.md +4 -4
  82. data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
  83. data/templates/README.md +279 -0
  84. data/templates/aidp-development.yml.example +373 -0
  85. data/templates/aidp-minimal.yml.example +48 -0
  86. data/templates/aidp-production.yml.example +475 -0
  87. data/templates/aidp.yml.example +598 -0
  88. metadata +93 -69
  89. data/lib/aidp/analyze/agent_personas.rb +0 -71
  90. data/lib/aidp/analyze/agent_tool_executor.rb +0 -439
  91. data/lib/aidp/analyze/data_retention_manager.rb +0 -421
  92. data/lib/aidp/analyze/database.rb +0 -260
  93. data/lib/aidp/analyze/dependencies.rb +0 -335
  94. data/lib/aidp/analyze/export_manager.rb +0 -418
  95. data/lib/aidp/analyze/focus_guidance.rb +0 -517
  96. data/lib/aidp/analyze/incremental_analyzer.rb +0 -533
  97. data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
  98. data/lib/aidp/analyze/large_analysis_progress.rb +0 -499
  99. data/lib/aidp/analyze/memory_manager.rb +0 -339
  100. data/lib/aidp/analyze/metrics_storage.rb +0 -336
  101. data/lib/aidp/analyze/parallel_processor.rb +0 -454
  102. data/lib/aidp/analyze/performance_optimizer.rb +0 -691
  103. data/lib/aidp/analyze/repository_chunker.rb +0 -697
  104. data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
  105. data/lib/aidp/analyze/storage.rb +0 -655
  106. data/lib/aidp/analyze/tool_configuration.rb +0 -441
  107. data/lib/aidp/analyze/tool_modernization.rb +0 -750
  108. data/lib/aidp/database/pg_adapter.rb +0 -148
  109. data/lib/aidp/database_config.rb +0 -69
  110. data/lib/aidp/database_connection.rb +0 -72
  111. data/lib/aidp/job_manager.rb +0 -41
  112. data/lib/aidp/jobs/base_job.rb +0 -45
  113. data/lib/aidp/jobs/provider_execution_job.rb +0 -83
  114. data/lib/aidp/project_detector.rb +0 -117
  115. data/lib/aidp/providers/agent_supervisor.rb +0 -348
  116. data/lib/aidp/providers/supervised_base.rb +0 -317
  117. data/lib/aidp/providers/supervised_cursor.rb +0 -22
  118. data/lib/aidp/sync.rb +0 -13
  119. data/lib/aidp/workspace.rb +0 -19
@@ -1,421 +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
- end
55
-
56
- results
57
- end
58
-
59
- # Handle force/rerun operations
60
- def handle_force_rerun(execution_id, step_name, options = {})
61
- operation = options[:operation] || "force"
62
-
63
- case operation
64
- when "force"
65
- handle_force_operation(execution_id, step_name, options)
66
- when "rerun"
67
- handle_rerun_operation(execution_id, step_name, options)
68
- else
69
- raise "Unknown operation: #{operation}"
70
- end
71
- end
72
-
73
- # Clean old data based on retention policies
74
- def clean_old_data(options = {})
75
- dry_run = options[:dry_run] || false
76
- data_types = options[:data_types] || RETENTION_POLICIES.keys
77
-
78
- results = {
79
- cleaned: {},
80
- errors: []
81
- }
82
-
83
- data_types.each do |data_type|
84
- next unless RETENTION_POLICIES[data_type] == "configurable"
85
-
86
- begin
87
- cleaned = clean_data_by_type(data_type, dry_run)
88
- results[:cleaned][data_type] = cleaned
89
- rescue => e
90
- results[:errors] << {
91
- data_type: data_type,
92
- error: e.message
93
- }
94
- end
95
- end
96
-
97
- results
98
- end
99
-
100
- # Get retention statistics
101
- def get_retention_statistics
102
- stats = {
103
- policies: RETENTION_POLICIES,
104
- config: @retention_config,
105
- data_sizes: {},
106
- retention_status: {}
107
- }
108
-
109
- # Calculate data sizes
110
- RETENTION_POLICIES.keys.each do |data_type|
111
- stats[:data_sizes][data_type] = calculate_data_size(data_type)
112
- stats[:retention_status][data_type] = get_retention_status(data_type)
113
- end
114
-
115
- stats
116
- end
117
-
118
- # Export data with retention metadata
119
- def export_data_with_retention(data_type, options = {})
120
- include_retention_info = options[:include_retention_info] || true
121
-
122
- data = export_data(data_type, options)
123
-
124
- if include_retention_info
125
- data[:retention_info] = {
126
- policy: RETENTION_POLICIES[data_type],
127
- retention_period: @retention_config[data_type],
128
- last_cleaned: get_last_cleaned_date(data_type),
129
- next_cleanup: calculate_next_cleanup(data_type)
130
- }
131
- end
132
-
133
- data
134
- end
135
-
136
- # Set retention policy for a data type
137
- def set_retention_policy(data_type, policy, options = {})
138
- raise "Unknown data type: #{data_type}" unless RETENTION_POLICIES.key?(data_type)
139
-
140
- case policy
141
- when "indefinite"
142
- @retention_config[data_type] = {policy: "indefinite"}
143
- when "configurable"
144
- retention_period = options[:retention_period] || DEFAULT_RETENTION_PERIODS[data_type]
145
- @retention_config[data_type] = {
146
- policy: "configurable",
147
- retention_period: retention_period
148
- }
149
- when "immediate"
150
- @retention_config[data_type] = {policy: "immediate"}
151
- else
152
- raise "Unknown retention policy: #{policy}"
153
- end
154
-
155
- save_retention_config
156
-
157
- {
158
- data_type: data_type,
159
- policy: policy,
160
- config: @retention_config[data_type]
161
- }
162
- end
163
-
164
- # Get data retention status
165
- def get_data_retention_status(data_type)
166
- {
167
- data_type: data_type,
168
- policy: RETENTION_POLICIES[data_type],
169
- config: @retention_config[data_type],
170
- data_size: calculate_data_size(data_type),
171
- last_cleaned: get_last_cleaned_date(data_type),
172
- next_cleanup: calculate_next_cleanup(data_type),
173
- retention_status: get_retention_status(data_type)
174
- }
175
- end
176
-
177
- private
178
-
179
- def load_retention_config
180
- config_file = File.join(@project_dir, ".aidp-retention-config.yml")
181
-
182
- if File.exist?(config_file)
183
- YAML.load_file(config_file) || DEFAULT_RETENTION_PERIODS
184
- else
185
- DEFAULT_RETENTION_PERIODS
186
- end
187
- end
188
-
189
- def save_retention_config
190
- config_file = File.join(@project_dir, ".aidp-retention-config.yml")
191
- File.write(config_file, YAML.dump(@retention_config))
192
- end
193
-
194
- def retain_indefinitely(data_type)
195
- {
196
- data_type: data_type,
197
- action: "retained",
198
- reason: "Indefinite retention policy",
199
- timestamp: Time.now
200
- }
201
- end
202
-
203
- def apply_configurable_retention(data_type, dry_run)
204
- retention_period = @retention_config[data_type] || DEFAULT_RETENTION_PERIODS[data_type]
205
- cutoff_date = Time.now - (retention_period * 24 * 60 * 60)
206
-
207
- # Get old data
208
- old_data = get_old_data(data_type, cutoff_date)
209
-
210
- if dry_run
211
- {
212
- data_type: data_type,
213
- action: "would_clean",
214
- records_to_clean: old_data.length,
215
- cutoff_date: cutoff_date,
216
- dry_run: true
217
- }
218
- else
219
- # Actually clean the data
220
- cleaned_count = clean_data(data_type, old_data)
221
-
222
- {
223
- data_type: data_type,
224
- action: "cleaned",
225
- records_cleaned: cleaned_count,
226
- cutoff_date: cutoff_date,
227
- timestamp: Time.now
228
- }
229
- end
230
- end
231
-
232
- def clean_immediately(data_type, dry_run)
233
- if dry_run
234
- {
235
- data_type: data_type,
236
- action: "would_clean_immediately",
237
- dry_run: true
238
- }
239
- else
240
- # Clean all data of this type
241
- cleaned_count = clean_all_data(data_type)
242
-
243
- {
244
- data_type: data_type,
245
- action: "cleaned_immediately",
246
- records_cleaned: cleaned_count,
247
- timestamp: Time.now
248
- }
249
- end
250
- end
251
-
252
- def handle_force_operation(execution_id, step_name, options)
253
- # Force operation: overwrite main data, retain metrics
254
- results = {
255
- operation: "force",
256
- execution_id: execution_id,
257
- step_name: step_name,
258
- actions: []
259
- }
260
-
261
- # Overwrite analysis results
262
- if options[:analysis_data]
263
- @storage.force_overwrite(execution_id, step_name, options[:analysis_data])
264
- results[:actions] << "overwrote_analysis_data"
265
- end
266
-
267
- # Retain metrics (indefinite retention)
268
- results[:actions] << "retained_metrics"
269
-
270
- results
271
- end
272
-
273
- def handle_rerun_operation(execution_id, step_name, options)
274
- # Rerun operation: overwrite main data, retain metrics
275
- results = {
276
- operation: "rerun",
277
- execution_id: execution_id,
278
- step_name: step_name,
279
- actions: []
280
- }
281
-
282
- # Overwrite analysis results
283
- if options[:analysis_data]
284
- @storage.force_overwrite(execution_id, step_name, options[:analysis_data])
285
- results[:actions] << "overwrote_analysis_data"
286
- end
287
-
288
- # Retain metrics (indefinite retention)
289
- results[:actions] << "retained_metrics"
290
-
291
- results
292
- end
293
-
294
- def get_old_data(data_type, cutoff_date)
295
- case data_type
296
- when "analysis_results"
297
- get_old_analysis_results(cutoff_date)
298
- when "execution_logs"
299
- get_old_execution_logs(cutoff_date)
300
- when "temporary_data"
301
- get_old_temporary_data(cutoff_date)
302
- else
303
- []
304
- end
305
- end
306
-
307
- def get_old_analysis_results(cutoff_date)
308
- # Get analysis results older than cutoff date
309
- # This would query the database for old records
310
- []
311
- end
312
-
313
- def get_old_execution_logs(cutoff_date)
314
- # Get execution logs older than cutoff date
315
- []
316
- end
317
-
318
- def get_old_temporary_data(cutoff_date)
319
- # Get temporary data older than cutoff date
320
- []
321
- end
322
-
323
- def clean_data(data_type, old_data)
324
- case data_type
325
- when "analysis_results"
326
- clean_analysis_results(old_data)
327
- when "execution_logs"
328
- clean_execution_logs(old_data)
329
- when "temporary_data"
330
- clean_temporary_data(old_data)
331
- else
332
- 0
333
- end
334
- end
335
-
336
- def clean_all_data(data_type)
337
- case data_type
338
- when "temporary_data"
339
- clean_all_temporary_data
340
- else
341
- 0
342
- end
343
- end
344
-
345
- def clean_analysis_results(old_data)
346
- # Clean old analysis results from database
347
- # This would delete records from the database
348
- old_data.length
349
- end
350
-
351
- def clean_execution_logs(old_data)
352
- # Clean old execution logs
353
- old_data.length
354
- end
355
-
356
- def clean_temporary_data(old_data)
357
- # Clean old temporary data
358
- old_data.length
359
- end
360
-
361
- def clean_all_temporary_data
362
- # Clean all temporary data
363
- 0
364
- end
365
-
366
- def calculate_data_size(data_type)
367
- case data_type
368
- when "metrics"
369
- @storage.get_analysis_statistics[:total_metrics] || 0
370
- when "analysis_results"
371
- @storage.get_analysis_statistics[:total_steps] || 0
372
- when "execution_logs"
373
- @storage.get_execution_history.length
374
- else
375
- 0
376
- end
377
- end
378
-
379
- def get_retention_status(data_type)
380
- policy = RETENTION_POLICIES[data_type]
381
- config = @retention_config[data_type]
382
-
383
- {
384
- policy: policy,
385
- config: config,
386
- status: "active"
387
- }
388
- end
389
-
390
- def get_last_cleaned_date(data_type)
391
- # This would be stored in a metadata table
392
- nil
393
- end
394
-
395
- def calculate_next_cleanup(data_type)
396
- policy = RETENTION_POLICIES[data_type]
397
-
398
- case policy
399
- when "indefinite"
400
- nil
401
- when "configurable"
402
- retention_period = @retention_config[data_type] || DEFAULT_RETENTION_PERIODS[data_type]
403
- Time.now + (retention_period * 24 * 60 * 60)
404
- when "immediate"
405
- Time.now
406
- end
407
- end
408
-
409
- def export_data(data_type, options)
410
- case data_type
411
- when "metrics"
412
- @storage.export_data("json", options)
413
- when "analysis_results"
414
- @storage.export_data("json", options)
415
- else
416
- {error: "Unknown data type: #{data_type}"}
417
- end
418
- end
419
- end
420
- end
421
- 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