aidp 0.3.0 → 0.5.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 +59 -4
- data/lib/aidp/analyze/agent_personas.rb +1 -1
- data/lib/aidp/analyze/database.rb +99 -82
- data/lib/aidp/analyze/error_handler.rb +12 -76
- data/lib/aidp/analyze/focus_guidance.rb +2 -2
- data/lib/aidp/analyze/metrics_storage.rb +336 -0
- data/lib/aidp/analyze/prioritizer.rb +2 -2
- data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
- data/lib/aidp/analyze/runner.rb +107 -191
- data/lib/aidp/analyze/steps.rb +29 -30
- data/lib/aidp/analyze/storage.rb +233 -171
- data/lib/aidp/cli/jobs_command.rb +489 -0
- data/lib/aidp/cli/terminal_io.rb +52 -0
- data/lib/aidp/cli.rb +104 -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/database_migration.rb +158 -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 +47 -0
- data/lib/aidp/jobs/provider_execution_job.rb +96 -0
- data/lib/aidp/provider_manager.rb +25 -0
- data/lib/aidp/providers/agent_supervisor.rb +348 -0
- data/lib/aidp/providers/anthropic.rb +166 -3
- data/lib/aidp/providers/base.rb +153 -6
- data/lib/aidp/providers/cursor.rb +247 -43
- data/lib/aidp/providers/gemini.rb +166 -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 +25 -34
- data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
- metadata +72 -35
@@ -27,9 +27,24 @@ module Aidp
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# Setup logging if log file is specified
|
30
|
-
log_file = ENV["
|
31
|
-
|
32
|
-
|
30
|
+
log_file = ENV["AIDP_CURSOR_LOG"]
|
31
|
+
|
32
|
+
# Smart timeout calculation
|
33
|
+
timeout_seconds = calculate_timeout
|
34
|
+
|
35
|
+
puts "⏱️ Timeout set to #{timeout_seconds} seconds"
|
36
|
+
|
37
|
+
# Set up activity monitoring
|
38
|
+
setup_activity_monitoring("cursor-agent", method(:activity_callback))
|
39
|
+
record_activity("Starting cursor-agent execution")
|
40
|
+
|
41
|
+
# Start activity display thread
|
42
|
+
activity_display_thread = Thread.new do
|
43
|
+
loop do
|
44
|
+
sleep 0.1 # Update every 100ms for smooth animation
|
45
|
+
print_activity_status
|
46
|
+
break if @activity_state == :completed || @activity_state == :failed
|
47
|
+
end
|
33
48
|
end
|
34
49
|
|
35
50
|
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait|
|
@@ -37,62 +52,251 @@ module Aidp
|
|
37
52
|
stdin.puts prompt
|
38
53
|
stdin.close
|
39
54
|
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
puts prompt_log if ENV["AIDP_DEBUG"]
|
44
|
-
File.write(log_file, "#{Time.now.iso8601} #{prompt_log}\n", mode: "a") if log_file
|
45
|
-
end
|
55
|
+
# Read stdout and stderr synchronously for better reliability
|
56
|
+
output = ""
|
57
|
+
error_output = ""
|
46
58
|
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
puts
|
54
|
-
|
59
|
+
# Read stdout
|
60
|
+
stdout_thread = Thread.new do
|
61
|
+
stdout&.each_line do |line|
|
62
|
+
output += line
|
63
|
+
if ENV["AIDP_DEBUG"]
|
64
|
+
clear_activity_status
|
65
|
+
puts "📤 cursor-agent: #{line.chomp}"
|
66
|
+
$stdout.flush # Force output to display immediately
|
55
67
|
end
|
68
|
+
File.write(log_file, "#{Time.now.iso8601} #{line}\n", mode: "a") if log_file
|
69
|
+
|
70
|
+
# Record activity when we get output
|
71
|
+
record_activity("Received output: #{line.chomp[0..50]}...")
|
56
72
|
end
|
73
|
+
rescue IOError => e
|
74
|
+
puts "📤 stdout stream closed: #{e.message}" if ENV["AIDP_DEBUG"]
|
75
|
+
end
|
57
76
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
77
|
+
# Read stderr
|
78
|
+
stderr_thread = Thread.new do
|
79
|
+
stderr&.each_line do |line|
|
80
|
+
error_output += line
|
81
|
+
if ENV["AIDP_DEBUG"]
|
82
|
+
clear_activity_status
|
83
|
+
puts "❌ cursor-agent error: #{line.chomp}"
|
84
|
+
$stdout.flush # Force output to display immediately
|
63
85
|
end
|
86
|
+
File.write(log_file, "#{Time.now.iso8601} #{line}\n", mode: "a") if log_file
|
87
|
+
|
88
|
+
# Record activity when we get error output
|
89
|
+
record_activity("Error output: #{line.chomp[0..50]}...")
|
64
90
|
end
|
91
|
+
rescue IOError => e
|
92
|
+
puts "❌ stderr stream closed: #{e.message}" if ENV["AIDP_DEBUG"]
|
65
93
|
end
|
66
94
|
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
95
|
+
# Start activity monitoring thread
|
96
|
+
activity_thread = Thread.new do
|
97
|
+
loop do
|
98
|
+
sleep 10 # Check every 10 seconds
|
71
99
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
100
|
+
if stuck?
|
101
|
+
clear_activity_status
|
102
|
+
puts "⚠️ cursor-agent appears stuck (no activity for #{stuck_timeout} seconds)"
|
103
|
+
puts " You can:"
|
104
|
+
puts " 1. Wait longer (press Enter)"
|
105
|
+
puts " 2. Abort (Ctrl+C)"
|
106
|
+
|
107
|
+
# Give user a chance to respond
|
108
|
+
begin
|
109
|
+
Timeout.timeout(30) do
|
110
|
+
gets
|
111
|
+
puts "🔄 Continuing to wait..."
|
112
|
+
end
|
113
|
+
rescue Timeout::Error
|
114
|
+
puts "⏰ No response received, continuing to wait..."
|
115
|
+
rescue Interrupt
|
116
|
+
puts "\n🛑 User requested abort"
|
117
|
+
Process.kill("TERM", wait.pid)
|
118
|
+
break
|
119
|
+
end
|
76
120
|
end
|
77
121
|
|
78
|
-
|
79
|
-
|
122
|
+
# Stop checking if the process is done
|
123
|
+
break if wait.value
|
124
|
+
rescue
|
125
|
+
break
|
80
126
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
127
|
+
end
|
128
|
+
|
129
|
+
# Wait for process to complete with timeout
|
130
|
+
begin
|
131
|
+
# Start a timeout thread that will kill the process if it takes too long
|
132
|
+
timeout_thread = Thread.new do
|
133
|
+
sleep timeout_seconds
|
134
|
+
begin
|
135
|
+
Process.kill("TERM", wait.pid)
|
136
|
+
sleep 2
|
137
|
+
Process.kill("KILL", wait.pid) if wait.value.nil?
|
138
|
+
rescue
|
139
|
+
# Process already terminated
|
140
|
+
end
|
86
141
|
end
|
87
142
|
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
143
|
+
# Wait for the process to complete
|
144
|
+
exit_status = wait.value
|
145
|
+
|
146
|
+
# Cancel the timeout thread since we completed successfully
|
147
|
+
timeout_thread.kill
|
148
|
+
rescue => e
|
149
|
+
# Kill the timeout thread
|
150
|
+
timeout_thread&.kill
|
151
|
+
|
152
|
+
# Check if this was a timeout
|
153
|
+
if e.is_a?(Timeout::Error) || execution_time >= timeout_seconds
|
154
|
+
# Kill the process if it times out
|
155
|
+
begin
|
156
|
+
Process.kill("TERM", wait.pid)
|
157
|
+
sleep 1
|
158
|
+
Process.kill("KILL", wait.pid) if wait.value.nil?
|
159
|
+
rescue
|
160
|
+
# Process already terminated
|
161
|
+
end
|
162
|
+
|
163
|
+
# Wait for output threads to finish (with timeout)
|
164
|
+
[stdout_thread, stderr_thread, activity_thread].each do |thread|
|
165
|
+
thread.join(5) # Wait up to 5 seconds for each thread
|
166
|
+
end
|
167
|
+
|
168
|
+
# Stop activity display
|
169
|
+
activity_display_thread.join
|
170
|
+
|
171
|
+
clear_activity_status
|
172
|
+
mark_failed("cursor-agent timed out after #{timeout_seconds} seconds")
|
173
|
+
raise Timeout::Error, "cursor-agent timed out after #{timeout_seconds} seconds"
|
174
|
+
else
|
175
|
+
raise e
|
93
176
|
end
|
94
|
-
raise Timeout::Error, "cursor-agent timed out after 5 minutes"
|
95
177
|
end
|
178
|
+
|
179
|
+
# Wait for output threads to finish (with timeout)
|
180
|
+
[stdout_thread, stderr_thread, activity_thread].each do |thread|
|
181
|
+
thread.join(5) # Wait up to 5 seconds for each thread
|
182
|
+
end
|
183
|
+
|
184
|
+
# Stop activity display
|
185
|
+
activity_display_thread.join
|
186
|
+
|
187
|
+
clear_activity_status
|
188
|
+
if exit_status.success?
|
189
|
+
mark_completed
|
190
|
+
output
|
191
|
+
else
|
192
|
+
mark_failed("cursor-agent failed with exit code #{exit_status.exitstatus}")
|
193
|
+
raise "cursor-agent failed with exit code #{exit_status.exitstatus}: #{error_output}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
rescue Timeout::Error
|
197
|
+
clear_activity_status
|
198
|
+
mark_failed("cursor-agent timed out after #{timeout_seconds} seconds")
|
199
|
+
raise Timeout::Error, "cursor-agent timed out after #{timeout_seconds} seconds"
|
200
|
+
rescue => e
|
201
|
+
clear_activity_status
|
202
|
+
mark_failed("cursor-agent execution was interrupted: #{e.message}")
|
203
|
+
raise
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
def print_activity_status
|
209
|
+
# Print activity status during cursor execution
|
210
|
+
print "🔄 cursor-agent is running..."
|
211
|
+
$stdout.flush
|
212
|
+
end
|
213
|
+
|
214
|
+
def clear_activity_status
|
215
|
+
# Clear the activity status line
|
216
|
+
print "\r" + " " * 50 + "\r"
|
217
|
+
$stdout.flush
|
218
|
+
end
|
219
|
+
|
220
|
+
def calculate_timeout
|
221
|
+
# Priority order for timeout calculation:
|
222
|
+
# 1. Quick mode (for testing)
|
223
|
+
# 2. Environment variable override
|
224
|
+
# 3. Adaptive timeout based on step type
|
225
|
+
# 4. Default timeout
|
226
|
+
|
227
|
+
if ENV["AIDP_QUICK_MODE"]
|
228
|
+
puts "⚡ Quick mode enabled - 2 minute timeout"
|
229
|
+
return 120
|
230
|
+
end
|
231
|
+
|
232
|
+
if ENV["AIDP_CURSOR_TIMEOUT"]
|
233
|
+
return ENV["AIDP_CURSOR_TIMEOUT"].to_i
|
234
|
+
end
|
235
|
+
|
236
|
+
# Adaptive timeout based on step type
|
237
|
+
step_timeout = get_adaptive_timeout
|
238
|
+
if step_timeout
|
239
|
+
puts "🧠 Using adaptive timeout: #{step_timeout} seconds"
|
240
|
+
return step_timeout
|
241
|
+
end
|
242
|
+
|
243
|
+
# Default timeout (5 minutes for interactive use)
|
244
|
+
puts "📋 Using default timeout: 5 minutes"
|
245
|
+
300
|
246
|
+
end
|
247
|
+
|
248
|
+
def get_adaptive_timeout
|
249
|
+
# Try to get timeout recommendations from metrics storage
|
250
|
+
begin
|
251
|
+
require_relative "../analyze/metrics_storage"
|
252
|
+
storage = Aidp::Analyze::MetricsStorage.new(Dir.pwd)
|
253
|
+
recommendations = storage.calculate_timeout_recommendations
|
254
|
+
|
255
|
+
# Get current step name from environment or context
|
256
|
+
step_name = ENV["AIDP_CURRENT_STEP"] || "unknown"
|
257
|
+
|
258
|
+
if recommendations[step_name]
|
259
|
+
recommended = recommendations[step_name][:recommended_timeout]
|
260
|
+
# Add 20% buffer for safety
|
261
|
+
return (recommended * 1.2).ceil
|
262
|
+
end
|
263
|
+
rescue => e
|
264
|
+
puts "⚠️ Could not get adaptive timeout: #{e.message}" if ENV["AIDP_DEBUG"]
|
265
|
+
end
|
266
|
+
|
267
|
+
# Fallback timeouts based on step type patterns
|
268
|
+
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
269
|
+
|
270
|
+
case step_name
|
271
|
+
when /REPOSITORY_ANALYSIS/
|
272
|
+
180 # 3 minutes - repository analysis can be quick
|
273
|
+
when /ARCHITECTURE_ANALYSIS/
|
274
|
+
600 # 10 minutes - architecture analysis needs more time
|
275
|
+
when /TEST_ANALYSIS/
|
276
|
+
300 # 5 minutes - test analysis is moderate
|
277
|
+
when /FUNCTIONALITY_ANALYSIS/
|
278
|
+
600 # 10 minutes - functionality analysis is complex
|
279
|
+
when /DOCUMENTATION_ANALYSIS/
|
280
|
+
300 # 5 minutes - documentation analysis is moderate
|
281
|
+
when /STATIC_ANALYSIS/
|
282
|
+
450 # 7.5 minutes - static analysis can be intensive
|
283
|
+
when /REFACTORING_RECOMMENDATIONS/
|
284
|
+
600 # 10 minutes - refactoring recommendations are complex
|
285
|
+
else
|
286
|
+
nil # Use default
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def activity_callback(state, message, provider)
|
291
|
+
# This is now handled by the animated display thread
|
292
|
+
# Only print static messages for state changes
|
293
|
+
case state
|
294
|
+
when :stuck
|
295
|
+
puts "\n⚠️ cursor appears stuck: #{message}"
|
296
|
+
when :completed
|
297
|
+
puts "\n✅ cursor completed: #{message}"
|
298
|
+
when :failed
|
299
|
+
puts "\n❌ cursor failed: #{message}"
|
96
300
|
end
|
97
301
|
end
|
98
302
|
end
|
@@ -14,10 +14,173 @@ module Aidp
|
|
14
14
|
def send(prompt:, session: nil)
|
15
15
|
raise "gemini CLI not available" unless self.class.available?
|
16
16
|
|
17
|
+
require "open3"
|
18
|
+
|
17
19
|
# Use Gemini CLI for non-interactive mode
|
18
|
-
cmd = ["gemini", "
|
19
|
-
|
20
|
-
|
20
|
+
cmd = ["gemini", "--print"]
|
21
|
+
|
22
|
+
puts "📝 Sending prompt to gemini..."
|
23
|
+
|
24
|
+
# Smart timeout calculation
|
25
|
+
timeout_seconds = calculate_timeout
|
26
|
+
|
27
|
+
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait|
|
28
|
+
# Send the prompt to stdin
|
29
|
+
stdin.puts prompt
|
30
|
+
stdin.close
|
31
|
+
|
32
|
+
# Start stuck detection thread
|
33
|
+
stuck_detection_thread = Thread.new do
|
34
|
+
loop do
|
35
|
+
sleep 10 # Check every 10 seconds
|
36
|
+
|
37
|
+
if stuck?
|
38
|
+
puts "⚠️ gemini appears stuck (no activity for #{stuck_timeout} seconds)"
|
39
|
+
puts " You can:"
|
40
|
+
puts " 1. Wait longer (press Enter)"
|
41
|
+
puts " 2. Abort (Ctrl+C)"
|
42
|
+
|
43
|
+
# Give user a chance to respond
|
44
|
+
begin
|
45
|
+
Timeout.timeout(30) do
|
46
|
+
gets
|
47
|
+
puts "🔄 Continuing to wait..."
|
48
|
+
end
|
49
|
+
rescue Timeout::Error
|
50
|
+
puts "⏰ No response received, continuing to wait..."
|
51
|
+
rescue Interrupt
|
52
|
+
puts "🛑 Aborting gemini..."
|
53
|
+
Process.kill("TERM", wait.pid)
|
54
|
+
raise Interrupt, "User aborted gemini execution"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Stop checking if the process is done
|
59
|
+
break if wait.value
|
60
|
+
rescue
|
61
|
+
break
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Wait for completion with timeout
|
66
|
+
begin
|
67
|
+
Timeout.timeout(timeout_seconds) do
|
68
|
+
result = wait.value
|
69
|
+
|
70
|
+
# Stop stuck detection thread
|
71
|
+
stuck_detection_thread&.kill
|
72
|
+
|
73
|
+
if result.success?
|
74
|
+
output = stdout.read
|
75
|
+
puts "✅ Gemini analysis completed"
|
76
|
+
mark_completed
|
77
|
+
return output.empty? ? :ok : output
|
78
|
+
else
|
79
|
+
error_output = stderr.read
|
80
|
+
mark_failed("gemini failed with exit code #{result.exitstatus}: #{error_output}")
|
81
|
+
raise "gemini failed with exit code #{result.exitstatus}: #{error_output}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
rescue Timeout::Error
|
85
|
+
# Stop stuck detection thread
|
86
|
+
stuck_detection_thread&.kill
|
87
|
+
|
88
|
+
# Kill the process if it's taking too long
|
89
|
+
begin
|
90
|
+
Process.kill("TERM", wait.pid)
|
91
|
+
rescue
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
mark_failed("gemini timed out after #{timeout_seconds} seconds")
|
96
|
+
raise Timeout::Error, "gemini timed out after #{timeout_seconds} seconds"
|
97
|
+
rescue Interrupt
|
98
|
+
# Stop stuck detection thread
|
99
|
+
stuck_detection_thread&.kill
|
100
|
+
|
101
|
+
# Kill the process
|
102
|
+
begin
|
103
|
+
Process.kill("TERM", wait.pid)
|
104
|
+
rescue
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
mark_failed("gemini execution was interrupted")
|
109
|
+
raise
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def calculate_timeout
|
117
|
+
# Priority order for timeout calculation:
|
118
|
+
# 1. Quick mode (for testing)
|
119
|
+
# 2. Environment variable override
|
120
|
+
# 3. Adaptive timeout based on step type
|
121
|
+
# 4. Default timeout
|
122
|
+
|
123
|
+
if ENV["AIDP_QUICK_MODE"]
|
124
|
+
puts "⚡ Quick mode enabled - 2 minute timeout"
|
125
|
+
return 120
|
126
|
+
end
|
127
|
+
|
128
|
+
if ENV["AIDP_GEMINI_TIMEOUT"]
|
129
|
+
return ENV["AIDP_GEMINI_TIMEOUT"].to_i
|
130
|
+
end
|
131
|
+
|
132
|
+
# Adaptive timeout based on step type
|
133
|
+
step_timeout = get_adaptive_timeout
|
134
|
+
if step_timeout
|
135
|
+
puts "🧠 Using adaptive timeout: #{step_timeout} seconds"
|
136
|
+
return step_timeout
|
137
|
+
end
|
138
|
+
|
139
|
+
# Default timeout (5 minutes for interactive use)
|
140
|
+
puts "📋 Using default timeout: 5 minutes"
|
141
|
+
300
|
142
|
+
end
|
143
|
+
|
144
|
+
def get_adaptive_timeout
|
145
|
+
# Try to get timeout recommendations from metrics storage
|
146
|
+
begin
|
147
|
+
require_relative "../analyze/metrics_storage"
|
148
|
+
storage = Aidp::Analyze::MetricsStorage.new(Dir.pwd)
|
149
|
+
recommendations = storage.calculate_timeout_recommendations
|
150
|
+
|
151
|
+
# Get current step name from environment or context
|
152
|
+
step_name = ENV["AIDP_CURRENT_STEP"] || "unknown"
|
153
|
+
|
154
|
+
if recommendations[step_name]
|
155
|
+
recommended = recommendations[step_name][:recommended_timeout]
|
156
|
+
# Add 20% buffer for safety
|
157
|
+
return (recommended * 1.2).ceil
|
158
|
+
end
|
159
|
+
rescue => e
|
160
|
+
puts "⚠️ Could not get adaptive timeout: #{e.message}" if ENV["AIDP_DEBUG"]
|
161
|
+
end
|
162
|
+
|
163
|
+
# Fallback timeouts based on step type patterns
|
164
|
+
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
165
|
+
|
166
|
+
case step_name
|
167
|
+
when /REPOSITORY_ANALYSIS/
|
168
|
+
180 # 3 minutes - repository analysis can be quick
|
169
|
+
when /ARCHITECTURE_ANALYSIS/
|
170
|
+
600 # 10 minutes - architecture analysis needs more time
|
171
|
+
when /TEST_ANALYSIS/
|
172
|
+
300 # 5 minutes - test analysis is moderate
|
173
|
+
when /FUNCTIONALITY_ANALYSIS/
|
174
|
+
600 # 10 minutes - functionality analysis is complex
|
175
|
+
when /DOCUMENTATION_ANALYSIS/
|
176
|
+
300 # 5 minutes - documentation analysis is moderate
|
177
|
+
when /STATIC_ANALYSIS/
|
178
|
+
450 # 7.5 minutes - static analysis can be intensive
|
179
|
+
when /REFACTORING_RECOMMENDATIONS/
|
180
|
+
600 # 10 minutes - refactoring recommendations are complex
|
181
|
+
else
|
182
|
+
nil # Use default
|
183
|
+
end
|
21
184
|
end
|
22
185
|
end
|
23
186
|
end
|