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.
- checksums.yaml +4 -4
- data/README.md +191 -5
- data/lib/aidp/analysis/kb_inspector.rb +456 -0
- data/lib/aidp/analysis/seams.rb +188 -0
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +493 -0
- data/lib/aidp/analysis/tree_sitter_scan.rb +703 -0
- data/lib/aidp/analyze/agent_personas.rb +1 -1
- data/lib/aidp/analyze/agent_tool_executor.rb +5 -11
- data/lib/aidp/analyze/data_retention_manager.rb +0 -5
- data/lib/aidp/analyze/database.rb +99 -82
- data/lib/aidp/analyze/error_handler.rb +12 -79
- data/lib/aidp/analyze/export_manager.rb +0 -7
- data/lib/aidp/analyze/focus_guidance.rb +2 -2
- data/lib/aidp/analyze/incremental_analyzer.rb +1 -11
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -5
- data/lib/aidp/analyze/memory_manager.rb +34 -60
- data/lib/aidp/analyze/metrics_storage.rb +336 -0
- data/lib/aidp/analyze/parallel_processor.rb +0 -6
- data/lib/aidp/analyze/performance_optimizer.rb +0 -3
- data/lib/aidp/analyze/prioritizer.rb +2 -2
- data/lib/aidp/analyze/repository_chunker.rb +14 -21
- data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
- data/lib/aidp/analyze/runner.rb +107 -191
- data/lib/aidp/analyze/steps.rb +35 -30
- data/lib/aidp/analyze/storage.rb +233 -178
- data/lib/aidp/analyze/tool_configuration.rb +21 -36
- data/lib/aidp/cli/jobs_command.rb +489 -0
- data/lib/aidp/cli/terminal_io.rb +52 -0
- data/lib/aidp/cli.rb +160 -45
- data/lib/aidp/core_ext/class_attribute.rb +36 -0
- data/lib/aidp/database/pg_adapter.rb +148 -0
- data/lib/aidp/database_config.rb +69 -0
- data/lib/aidp/database_connection.rb +72 -0
- data/lib/aidp/execute/runner.rb +65 -92
- data/lib/aidp/execute/steps.rb +81 -82
- data/lib/aidp/job_manager.rb +41 -0
- data/lib/aidp/jobs/base_job.rb +45 -0
- data/lib/aidp/jobs/provider_execution_job.rb +83 -0
- data/lib/aidp/provider_manager.rb +25 -0
- data/lib/aidp/providers/agent_supervisor.rb +348 -0
- data/lib/aidp/providers/anthropic.rb +160 -3
- data/lib/aidp/providers/base.rb +153 -6
- data/lib/aidp/providers/cursor.rb +245 -43
- data/lib/aidp/providers/gemini.rb +164 -3
- data/lib/aidp/providers/supervised_base.rb +317 -0
- data/lib/aidp/providers/supervised_cursor.rb +22 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +31 -34
- data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
- data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
- metadata +91 -36
data/lib/aidp/cli.rb
CHANGED
@@ -8,7 +8,26 @@ module Aidp
|
|
8
8
|
desc "execute [STEP]", "Run execute mode step(s)"
|
9
9
|
option :force, type: :boolean, desc: "Force execution even if dependencies are not met"
|
10
10
|
option :rerun, type: :boolean, desc: "Re-run a completed step"
|
11
|
+
option :approve, type: :string, desc: "Approve a completed execute gate step"
|
12
|
+
option :reset, type: :boolean, desc: "Reset execute mode progress"
|
11
13
|
def execute(project_dir = Dir.pwd, step_name = nil, custom_options = {})
|
14
|
+
# Handle reset flag
|
15
|
+
if options[:reset] || options["reset"]
|
16
|
+
progress = Aidp::Execute::Progress.new(project_dir)
|
17
|
+
progress.reset
|
18
|
+
puts "🔄 Reset execute mode progress"
|
19
|
+
return {status: "success", message: "Progress reset"}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Handle approve flag
|
23
|
+
if options[:approve] || options["approve"]
|
24
|
+
step_name = options[:approve] || options["approve"]
|
25
|
+
progress = Aidp::Execute::Progress.new(project_dir)
|
26
|
+
progress.mark_step_completed(step_name)
|
27
|
+
puts "✅ Approved execute step: #{step_name}"
|
28
|
+
return {status: "success", step: step_name}
|
29
|
+
end
|
30
|
+
|
12
31
|
if step_name
|
13
32
|
runner = Aidp::Execute::Runner.new(project_dir)
|
14
33
|
# Merge Thor options with custom options
|
@@ -34,7 +53,71 @@ module Aidp
|
|
34
53
|
DESC
|
35
54
|
option :force, type: :boolean, desc: "Force execution even if dependencies are not met"
|
36
55
|
option :rerun, type: :boolean, desc: "Re-run a completed step"
|
37
|
-
|
56
|
+
option :background, type: :boolean, desc: "Run analysis in background jobs (requires database setup)"
|
57
|
+
option :approve, type: :string, desc: "Approve a completed analyze gate step"
|
58
|
+
option :reset, type: :boolean, desc: "Reset analyze mode progress"
|
59
|
+
def analyze(*args)
|
60
|
+
# Handle reset flag
|
61
|
+
if options[:reset] || options["reset"]
|
62
|
+
project_dir = Dir.pwd
|
63
|
+
progress = Aidp::Analyze::Progress.new(project_dir)
|
64
|
+
progress.reset
|
65
|
+
puts "🔄 Reset analyze mode progress"
|
66
|
+
return {status: "success", message: "Progress reset"}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Handle approve flag
|
70
|
+
if options[:approve] || options["approve"]
|
71
|
+
project_dir = Dir.pwd
|
72
|
+
step_name = options[:approve] || options["approve"]
|
73
|
+
progress = Aidp::Analyze::Progress.new(project_dir)
|
74
|
+
progress.mark_step_completed(step_name)
|
75
|
+
puts "✅ Approved analyze step: #{step_name}"
|
76
|
+
return {status: "success", step: step_name}
|
77
|
+
end
|
78
|
+
|
79
|
+
# Handle both old and new calling patterns for backwards compatibility
|
80
|
+
case args.length
|
81
|
+
when 0
|
82
|
+
# analyze() - list steps
|
83
|
+
project_dir = Dir.pwd
|
84
|
+
step_name = nil
|
85
|
+
custom_options = {}
|
86
|
+
when 1
|
87
|
+
# analyze(step_name) - new CLI pattern
|
88
|
+
if args[0].is_a?(String) && Dir.exist?(args[0])
|
89
|
+
# analyze(project_dir) - old test pattern
|
90
|
+
project_dir = args[0]
|
91
|
+
step_name = nil
|
92
|
+
else
|
93
|
+
# analyze(step_name) - new CLI pattern
|
94
|
+
project_dir = Dir.pwd
|
95
|
+
step_name = args[0]
|
96
|
+
end
|
97
|
+
custom_options = {}
|
98
|
+
when 2
|
99
|
+
# analyze(project_dir, step_name) - old test pattern
|
100
|
+
# or analyze(step_name, options) - new CLI pattern
|
101
|
+
if Dir.exist?(args[0])
|
102
|
+
# analyze(project_dir, step_name)
|
103
|
+
project_dir = args[0]
|
104
|
+
step_name = args[1]
|
105
|
+
custom_options = {}
|
106
|
+
else
|
107
|
+
# analyze(step_name, options)
|
108
|
+
project_dir = Dir.pwd
|
109
|
+
step_name = args[0]
|
110
|
+
custom_options = args[1] || {}
|
111
|
+
end
|
112
|
+
when 3
|
113
|
+
# analyze(project_dir, step_name, options) - old test pattern
|
114
|
+
project_dir = args[0]
|
115
|
+
step_name = args[1]
|
116
|
+
custom_options = args[2] || {}
|
117
|
+
else
|
118
|
+
raise ArgumentError, "Wrong number of arguments (given #{args.length}, expected 0..3)"
|
119
|
+
end
|
120
|
+
|
38
121
|
progress = Aidp::Analyze::Progress.new(project_dir)
|
39
122
|
|
40
123
|
if step_name
|
@@ -45,7 +128,19 @@ module Aidp
|
|
45
128
|
runner = Aidp::Analyze::Runner.new(project_dir)
|
46
129
|
# Merge Thor options with custom options
|
47
130
|
all_options = options.merge(custom_options)
|
48
|
-
runner.run_step(resolved_step, all_options)
|
131
|
+
result = runner.run_step(resolved_step, all_options)
|
132
|
+
|
133
|
+
# Display the result
|
134
|
+
if result[:status] == "completed"
|
135
|
+
puts "✅ Step '#{resolved_step}' completed successfully"
|
136
|
+
puts " Provider: #{result[:provider]}"
|
137
|
+
puts " Message: #{result[:message]}" if result[:message]
|
138
|
+
elsif result[:status] == "error"
|
139
|
+
puts "❌ Step '#{resolved_step}' failed"
|
140
|
+
puts " Error: #{result[:error]}" if result[:error]
|
141
|
+
end
|
142
|
+
|
143
|
+
result
|
49
144
|
else
|
50
145
|
puts "❌ Step '#{step_name}' not found or not available"
|
51
146
|
puts "\nAvailable steps:"
|
@@ -72,49 +167,6 @@ module Aidp
|
|
72
167
|
end
|
73
168
|
end
|
74
169
|
|
75
|
-
desc "analyze-approve STEP", "Approve a completed analyze gate step"
|
76
|
-
def analyze_approve(project_dir = Dir.pwd, step_name = nil)
|
77
|
-
progress = Aidp::Analyze::Progress.new(project_dir)
|
78
|
-
progress.mark_step_completed(step_name)
|
79
|
-
puts "✅ Approved analyze step: #{step_name}"
|
80
|
-
{status: "success", step: step_name}
|
81
|
-
end
|
82
|
-
|
83
|
-
desc "analyze-reset", "Reset analyze mode progress"
|
84
|
-
def analyze_reset(project_dir = Dir.pwd)
|
85
|
-
progress = Aidp::Analyze::Progress.new(project_dir)
|
86
|
-
progress.reset
|
87
|
-
puts "🔄 Reset analyze mode progress"
|
88
|
-
{status: "success", message: "Progress reset"}
|
89
|
-
end
|
90
|
-
|
91
|
-
desc "execute-approve STEP", "Approve a completed execute gate step"
|
92
|
-
def execute_approve(project_dir = Dir.pwd, step_name = nil)
|
93
|
-
progress = Aidp::Execute::Progress.new(project_dir)
|
94
|
-
progress.mark_step_completed(step_name)
|
95
|
-
puts "✅ Approved execute step: #{step_name}"
|
96
|
-
{status: "success", step: step_name}
|
97
|
-
end
|
98
|
-
|
99
|
-
desc "execute-reset", "Reset execute mode progress"
|
100
|
-
def execute_reset(project_dir = Dir.pwd)
|
101
|
-
progress = Aidp::Execute::Progress.new(project_dir)
|
102
|
-
progress.reset
|
103
|
-
puts "🔄 Reset execute mode progress"
|
104
|
-
{status: "success", message: "Progress reset"}
|
105
|
-
end
|
106
|
-
|
107
|
-
# Backward compatibility aliases
|
108
|
-
desc "approve STEP", "Approve a completed execute gate step (alias for execute-approve)"
|
109
|
-
def approve(project_dir = Dir.pwd, step_name = nil)
|
110
|
-
execute_approve(project_dir, step_name)
|
111
|
-
end
|
112
|
-
|
113
|
-
desc "reset", "Reset execute mode progress (alias for execute-reset)"
|
114
|
-
def reset(project_dir = Dir.pwd)
|
115
|
-
execute_reset(project_dir)
|
116
|
-
end
|
117
|
-
|
118
170
|
desc "status", "Show current progress for both modes"
|
119
171
|
def status
|
120
172
|
puts "\n📊 AI Dev Pipeline Status"
|
@@ -137,6 +189,69 @@ module Aidp
|
|
137
189
|
end
|
138
190
|
end
|
139
191
|
|
192
|
+
desc "jobs", "Show and manage background jobs"
|
193
|
+
def jobs
|
194
|
+
require_relative "cli/jobs_command"
|
195
|
+
command = Aidp::CLI::JobsCommand.new
|
196
|
+
command.run
|
197
|
+
end
|
198
|
+
|
199
|
+
desc "analyze code", "Run Tree-sitter static analysis to build knowledge base"
|
200
|
+
option :langs, type: :string, desc: "Comma-separated list of languages to analyze (default: ruby)"
|
201
|
+
option :threads, type: :numeric, desc: "Number of threads for parallel processing (default: CPU count)"
|
202
|
+
option :rebuild, type: :boolean, desc: "Rebuild knowledge base from scratch"
|
203
|
+
option :kb_dir, type: :string, desc: "Knowledge base directory (default: .aidp/kb)"
|
204
|
+
def analyze_code
|
205
|
+
require_relative "analysis/tree_sitter_scan"
|
206
|
+
|
207
|
+
langs = options[:langs] ? options[:langs].split(",").map(&:strip) : %w[ruby]
|
208
|
+
threads = options[:threads] || Etc.nprocessors
|
209
|
+
kb_dir = options[:kb_dir] || ".aidp/kb"
|
210
|
+
|
211
|
+
if options[:rebuild]
|
212
|
+
kb_path = File.expand_path(kb_dir, Dir.pwd)
|
213
|
+
FileUtils.rm_rf(kb_path) if File.exist?(kb_path)
|
214
|
+
puts "🗑️ Rebuilt knowledge base directory"
|
215
|
+
end
|
216
|
+
|
217
|
+
scanner = Aidp::Analysis::TreeSitterScan.new(
|
218
|
+
root: Dir.pwd,
|
219
|
+
kb_dir: kb_dir,
|
220
|
+
langs: langs,
|
221
|
+
threads: threads
|
222
|
+
)
|
223
|
+
|
224
|
+
scanner.run
|
225
|
+
end
|
226
|
+
|
227
|
+
desc "kb show [TYPE]", "Show knowledge base contents"
|
228
|
+
option :format, type: :string, desc: "Output format (json, table, summary)"
|
229
|
+
option :kb_dir, type: :string, desc: "Knowledge base directory (default: .aidp/kb)"
|
230
|
+
def kb_show(type = "summary")
|
231
|
+
require_relative "analysis/kb_inspector"
|
232
|
+
|
233
|
+
kb_dir = options[:kb_dir] || ".aidp/kb"
|
234
|
+
format = options[:format] || "summary"
|
235
|
+
|
236
|
+
inspector = Aidp::Analysis::KBInspector.new(kb_dir)
|
237
|
+
inspector.show(type, format: format)
|
238
|
+
end
|
239
|
+
|
240
|
+
desc "kb graph [TYPE]", "Generate graph visualization from knowledge base"
|
241
|
+
option :format, type: :string, desc: "Graph format (dot, json, mermaid)"
|
242
|
+
option :output, type: :string, desc: "Output file path"
|
243
|
+
option :kb_dir, type: :string, desc: "Knowledge base directory (default: .aidp/kb)"
|
244
|
+
def kb_graph(type = "imports")
|
245
|
+
require_relative "analysis/kb_inspector"
|
246
|
+
|
247
|
+
kb_dir = options[:kb_dir] || ".aidp/kb"
|
248
|
+
format = options[:format] || "dot"
|
249
|
+
output = options[:output]
|
250
|
+
|
251
|
+
inspector = Aidp::Analysis::KBInspector.new(kb_dir)
|
252
|
+
inspector.generate_graph(type, format: format, output: output)
|
253
|
+
end
|
254
|
+
|
140
255
|
desc "version", "Show version information"
|
141
256
|
def version
|
142
257
|
puts "Aidp version #{Aidp::VERSION}"
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module CoreExt
|
5
|
+
module ClassAttribute
|
6
|
+
def class_attribute(*attrs)
|
7
|
+
attrs.each do |name|
|
8
|
+
# Define class instance variable
|
9
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
10
|
+
@#{name} = nil
|
11
|
+
|
12
|
+
def self.#{name}
|
13
|
+
return @#{name} if defined?(@#{name})
|
14
|
+
return superclass.#{name} if superclass.respond_to?(:#{name})
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.#{name}=(val)
|
19
|
+
@#{name} = val
|
20
|
+
end
|
21
|
+
|
22
|
+
def #{name}
|
23
|
+
self.class.#{name}
|
24
|
+
end
|
25
|
+
|
26
|
+
def #{name}=(val)
|
27
|
+
raise "#{name} is a class attribute, cannot be set on instance"
|
28
|
+
end
|
29
|
+
RUBY
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Class.include Aidp::CoreExt::ClassAttribute
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module Database
|
5
|
+
class PgAdapter
|
6
|
+
def initialize(connection)
|
7
|
+
@connection = connection
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute(sql, params = [])
|
11
|
+
result = @connection.exec_params(sql, params)
|
12
|
+
result.to_a.map { |row| row.transform_keys(&:to_sym) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def in_transaction?
|
16
|
+
@connection.transaction_status != PG::PQTRANS_IDLE
|
17
|
+
end
|
18
|
+
|
19
|
+
def checkout
|
20
|
+
yield self
|
21
|
+
end
|
22
|
+
|
23
|
+
def after_commit
|
24
|
+
yield
|
25
|
+
end
|
26
|
+
|
27
|
+
def server_version
|
28
|
+
@connection.server_version
|
29
|
+
end
|
30
|
+
|
31
|
+
def transaction_status
|
32
|
+
@connection.transaction_status
|
33
|
+
end
|
34
|
+
|
35
|
+
def transaction
|
36
|
+
@connection.transaction do
|
37
|
+
yield
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def quote_table_name(name)
|
42
|
+
"\"#{name}\""
|
43
|
+
end
|
44
|
+
|
45
|
+
def quote_identifier(name)
|
46
|
+
"\"#{name}\""
|
47
|
+
end
|
48
|
+
|
49
|
+
def quote_string(string)
|
50
|
+
"'#{string.gsub("'", "''")}'"
|
51
|
+
end
|
52
|
+
|
53
|
+
def quote_date(date)
|
54
|
+
date.strftime("%Y-%m-%d")
|
55
|
+
end
|
56
|
+
|
57
|
+
def quote_time(time)
|
58
|
+
time.strftime("%Y-%m-%d %H:%M:%S.%6N %z")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Additional methods required by Que
|
62
|
+
def async_connection
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def wait_for_notify(timeout = nil)
|
67
|
+
@connection.wait_for_notify(timeout)
|
68
|
+
end
|
69
|
+
|
70
|
+
def listen(channel)
|
71
|
+
@connection.exec("LISTEN #{quote_identifier(channel)}")
|
72
|
+
end
|
73
|
+
|
74
|
+
def unlisten(channel)
|
75
|
+
@connection.exec("UNLISTEN #{quote_identifier(channel)}")
|
76
|
+
end
|
77
|
+
|
78
|
+
def unlisten_all
|
79
|
+
@connection.exec("UNLISTEN *")
|
80
|
+
end
|
81
|
+
|
82
|
+
def notifications
|
83
|
+
@connection.notifications
|
84
|
+
end
|
85
|
+
|
86
|
+
def reset
|
87
|
+
@connection.reset
|
88
|
+
end
|
89
|
+
|
90
|
+
def type_map_for_queries
|
91
|
+
@connection.type_map_for_queries
|
92
|
+
end
|
93
|
+
|
94
|
+
def type_map_for_results
|
95
|
+
@connection.type_map_for_results
|
96
|
+
end
|
97
|
+
|
98
|
+
# Additional methods for Que compatibility
|
99
|
+
def adapter_name
|
100
|
+
"pg"
|
101
|
+
end
|
102
|
+
|
103
|
+
def active?
|
104
|
+
@connection.status == PG::CONNECTION_OK
|
105
|
+
end
|
106
|
+
|
107
|
+
def disconnect!
|
108
|
+
@connection.close
|
109
|
+
end
|
110
|
+
|
111
|
+
def reconnect!
|
112
|
+
@connection.reset
|
113
|
+
end
|
114
|
+
|
115
|
+
def raw_connection
|
116
|
+
@connection
|
117
|
+
end
|
118
|
+
|
119
|
+
def schema_search_path
|
120
|
+
execute("SHOW search_path")[0][:search_path]
|
121
|
+
end
|
122
|
+
|
123
|
+
def schema_search_path=(path)
|
124
|
+
execute("SET search_path TO #{path}")
|
125
|
+
end
|
126
|
+
|
127
|
+
def table_exists?(name)
|
128
|
+
execute(<<~SQL, [name]).any?
|
129
|
+
SELECT 1
|
130
|
+
FROM pg_tables
|
131
|
+
WHERE tablename = $1
|
132
|
+
SQL
|
133
|
+
end
|
134
|
+
|
135
|
+
def advisory_lock(id)
|
136
|
+
execute("SELECT pg_advisory_lock($1)", [id])
|
137
|
+
end
|
138
|
+
|
139
|
+
def advisory_unlock(id)
|
140
|
+
execute("SELECT pg_advisory_unlock($1)", [id])
|
141
|
+
end
|
142
|
+
|
143
|
+
def advisory_unlock_all
|
144
|
+
execute("SELECT pg_advisory_unlock_all()")
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Aidp
|
7
|
+
class DatabaseConfig
|
8
|
+
DEFAULT_CONFIG = {
|
9
|
+
"database" => {
|
10
|
+
"adapter" => "postgresql",
|
11
|
+
"host" => "localhost",
|
12
|
+
"port" => 5432,
|
13
|
+
"database" => "aidp",
|
14
|
+
"username" => ENV["USER"],
|
15
|
+
"password" => nil,
|
16
|
+
"pool" => 5,
|
17
|
+
"timeout" => 5000
|
18
|
+
}
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def self.load(project_dir = Dir.pwd)
|
22
|
+
new(project_dir).load
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(project_dir)
|
26
|
+
@project_dir = project_dir
|
27
|
+
@config_file = File.join(project_dir, ".aidp-config.yml")
|
28
|
+
end
|
29
|
+
|
30
|
+
def load
|
31
|
+
ensure_config_exists
|
32
|
+
config = YAML.load_file(@config_file)
|
33
|
+
validate_config(config)
|
34
|
+
config["database"]
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def ensure_config_exists
|
40
|
+
return if File.exist?(@config_file)
|
41
|
+
|
42
|
+
# Create config directory if it doesn't exist
|
43
|
+
FileUtils.mkdir_p(File.dirname(@config_file))
|
44
|
+
|
45
|
+
# Write default config
|
46
|
+
File.write(@config_file, YAML.dump(DEFAULT_CONFIG))
|
47
|
+
|
48
|
+
puts "Created default database configuration at #{@config_file}"
|
49
|
+
puts "Please update the configuration with your database settings"
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_config(config)
|
53
|
+
unless config.is_a?(Hash) && config["database"].is_a?(Hash)
|
54
|
+
raise "Invalid configuration format in #{@config_file}"
|
55
|
+
end
|
56
|
+
|
57
|
+
required_keys = %w[adapter host port database username]
|
58
|
+
missing_keys = required_keys - config["database"].keys
|
59
|
+
|
60
|
+
unless missing_keys.empty?
|
61
|
+
raise "Missing required configuration keys: #{missing_keys.join(", ")}"
|
62
|
+
end
|
63
|
+
|
64
|
+
unless config["database"]["adapter"] == "postgresql"
|
65
|
+
raise "Only PostgreSQL is supported as a database adapter"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pg"
|
4
|
+
require "que"
|
5
|
+
require "sequel"
|
6
|
+
|
7
|
+
module Aidp
|
8
|
+
class DatabaseConnection
|
9
|
+
class << self
|
10
|
+
def initialize_mutex
|
11
|
+
@mutex ||= Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def establish_connection
|
15
|
+
initialize_mutex
|
16
|
+
@mutex.synchronize do
|
17
|
+
# Return existing connection if already established
|
18
|
+
return @connection if @connection && !@connection.finished?
|
19
|
+
@connection = PG.connect(connection_params)
|
20
|
+
@sequel_db = Sequel.connect(
|
21
|
+
adapter: "postgres",
|
22
|
+
host: ENV["AIDP_DB_HOST"] || "localhost",
|
23
|
+
port: (ENV["AIDP_DB_PORT"] || 5432).to_i,
|
24
|
+
database: ENV["AIDP_DB_NAME"] || "aidp",
|
25
|
+
user: ENV["AIDP_DB_USER"] || ENV["USER"],
|
26
|
+
password: ENV["AIDP_DB_PASSWORD"]
|
27
|
+
)
|
28
|
+
Que.connection = @sequel_db
|
29
|
+
Que.migrate!(version: Que::Migrations::CURRENT_VERSION)
|
30
|
+
@connection
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def connection
|
35
|
+
return @connection if @connection && !@connection.finished?
|
36
|
+
establish_connection
|
37
|
+
end
|
38
|
+
|
39
|
+
def disconnect
|
40
|
+
initialize_mutex
|
41
|
+
@mutex.synchronize do
|
42
|
+
return unless @connection
|
43
|
+
|
44
|
+
# Safely disconnect in reverse order
|
45
|
+
begin
|
46
|
+
Que.connection = nil
|
47
|
+
rescue => e
|
48
|
+
# Log but don't fail on Que disconnection issues
|
49
|
+
puts "Warning: Error setting Que.connection to nil: #{e.message}" if ENV["AIDP_DEBUG"]
|
50
|
+
end
|
51
|
+
|
52
|
+
@sequel_db&.disconnect
|
53
|
+
@connection&.close
|
54
|
+
@connection = nil
|
55
|
+
@sequel_db = nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def connection_params
|
62
|
+
{
|
63
|
+
host: ENV["AIDP_DB_HOST"] || "localhost",
|
64
|
+
port: (ENV["AIDP_DB_PORT"] || 5432).to_i,
|
65
|
+
dbname: ENV["AIDP_DB_NAME"] || "aidp",
|
66
|
+
user: ENV["AIDP_DB_USER"] || ENV["USER"],
|
67
|
+
password: ENV["AIDP_DB_PASSWORD"]
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|