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