aidp 0.1.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +59 -4
  3. data/bin/aidp +2 -2
  4. data/lib/aidp/analyze/agent_personas.rb +1 -1
  5. data/lib/aidp/analyze/data_retention_manager.rb +2 -2
  6. data/lib/aidp/analyze/database.rb +99 -82
  7. data/lib/aidp/analyze/error_handler.rb +12 -76
  8. data/lib/aidp/analyze/focus_guidance.rb +2 -2
  9. data/lib/aidp/analyze/large_analysis_progress.rb +2 -2
  10. data/lib/aidp/analyze/metrics_storage.rb +336 -0
  11. data/lib/aidp/analyze/prioritizer.rb +4 -4
  12. data/lib/aidp/analyze/repository_chunker.rb +15 -13
  13. data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
  14. data/lib/aidp/analyze/runner.rb +107 -191
  15. data/lib/aidp/analyze/steps.rb +29 -30
  16. data/lib/aidp/analyze/storage.rb +234 -172
  17. data/lib/aidp/cli/jobs_command.rb +489 -0
  18. data/lib/aidp/cli/terminal_io.rb +52 -0
  19. data/lib/aidp/cli.rb +227 -0
  20. data/lib/aidp/config.rb +33 -0
  21. data/lib/aidp/core_ext/class_attribute.rb +36 -0
  22. data/lib/aidp/database/pg_adapter.rb +148 -0
  23. data/lib/aidp/database_config.rb +69 -0
  24. data/lib/aidp/database_connection.rb +72 -0
  25. data/lib/aidp/database_migration.rb +158 -0
  26. data/lib/aidp/execute/runner.rb +65 -92
  27. data/lib/aidp/execute/steps.rb +81 -82
  28. data/lib/aidp/job_manager.rb +41 -0
  29. data/lib/aidp/jobs/base_job.rb +47 -0
  30. data/lib/aidp/jobs/provider_execution_job.rb +96 -0
  31. data/lib/aidp/project_detector.rb +117 -0
  32. data/lib/aidp/provider_manager.rb +25 -0
  33. data/lib/aidp/providers/agent_supervisor.rb +348 -0
  34. data/lib/aidp/providers/anthropic.rb +187 -0
  35. data/lib/aidp/providers/base.rb +162 -0
  36. data/lib/aidp/providers/cursor.rb +304 -0
  37. data/lib/aidp/providers/gemini.rb +187 -0
  38. data/lib/aidp/providers/macos_ui.rb +24 -0
  39. data/lib/aidp/providers/supervised_base.rb +317 -0
  40. data/lib/aidp/providers/supervised_cursor.rb +22 -0
  41. data/lib/aidp/sync.rb +13 -0
  42. data/lib/aidp/util.rb +39 -0
  43. data/lib/aidp/{shared/version.rb → version.rb} +1 -3
  44. data/lib/aidp/workspace.rb +19 -0
  45. data/lib/aidp.rb +36 -45
  46. data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
  47. metadata +89 -45
  48. data/lib/aidp/shared/cli.rb +0 -117
  49. data/lib/aidp/shared/config.rb +0 -35
  50. data/lib/aidp/shared/project_detector.rb +0 -119
  51. data/lib/aidp/shared/providers/anthropic.rb +0 -26
  52. data/lib/aidp/shared/providers/base.rb +0 -17
  53. data/lib/aidp/shared/providers/cursor.rb +0 -102
  54. data/lib/aidp/shared/providers/gemini.rb +0 -26
  55. data/lib/aidp/shared/providers/macos_ui.rb +0 -26
  56. data/lib/aidp/shared/sync.rb +0 -15
  57. data/lib/aidp/shared/util.rb +0 -41
  58. data/lib/aidp/shared/workspace.rb +0 -21
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Aidp
6
+ module Providers
7
+ class Anthropic < Base
8
+ def self.available?
9
+ !!Aidp::Util.which("claude")
10
+ end
11
+
12
+ def name = "anthropic"
13
+
14
+ def send(prompt:, session: nil)
15
+ raise "claude CLI not available" unless self.class.available?
16
+
17
+ require "open3"
18
+
19
+ # Use Claude CLI for non-interactive mode
20
+ cmd = ["claude", "--print"]
21
+
22
+ puts "šŸ“ Sending prompt to claude..."
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 "āš ļø claude 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 claude..."
53
+ Process.kill("TERM", wait.pid)
54
+ raise Interrupt, "User aborted claude 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 "āœ… Claude analysis completed"
76
+ mark_completed
77
+ return output.empty? ? :ok : output
78
+ else
79
+ error_output = stderr.read
80
+ mark_failed("claude failed with exit code #{result.exitstatus}: #{error_output}")
81
+ raise "claude 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("claude timed out after #{timeout_seconds} seconds")
96
+ raise Timeout::Error, "claude 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("claude 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_ANTHROPIC_TIMEOUT"]
129
+ return ENV["AIDP_ANTHROPIC_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
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aidp
4
+ module Providers
5
+ class Base
6
+ # Activity indicator states
7
+ ACTIVITY_STATES = {
8
+ idle: "ā³",
9
+ working: "šŸ”„",
10
+ stuck: "āš ļø",
11
+ completed: "āœ…",
12
+ failed: "āŒ"
13
+ }.freeze
14
+
15
+ # Default timeout for stuck detection (2 minutes)
16
+ DEFAULT_STUCK_TIMEOUT = 120
17
+
18
+ attr_reader :activity_state, :last_activity_time, :start_time, :step_name
19
+
20
+ def initialize
21
+ @activity_state = :idle
22
+ @last_activity_time = Time.now
23
+ @start_time = nil
24
+ @step_name = nil
25
+ @activity_callback = nil
26
+ @stuck_timeout = DEFAULT_STUCK_TIMEOUT
27
+ @output_count = 0
28
+ @last_output_time = Time.now
29
+ @job_context = nil
30
+ end
31
+
32
+ def name
33
+ raise NotImplementedError, "#{self.class} must implement #name"
34
+ end
35
+
36
+ def send(prompt:, session: nil)
37
+ raise NotImplementedError, "#{self.class} must implement #send"
38
+ end
39
+
40
+ # Set job context for background execution
41
+ def set_job_context(job_id:, execution_id:, job_manager:)
42
+ @job_context = {
43
+ job_id: job_id,
44
+ execution_id: execution_id,
45
+ job_manager: job_manager
46
+ }
47
+ end
48
+
49
+ # Set up activity monitoring for a step
50
+ def setup_activity_monitoring(step_name, activity_callback = nil, stuck_timeout = nil)
51
+ @step_name = step_name
52
+ @activity_callback = activity_callback
53
+ @stuck_timeout = stuck_timeout || DEFAULT_STUCK_TIMEOUT
54
+ @start_time = Time.now
55
+ @last_activity_time = @start_time
56
+ @output_count = 0
57
+ @last_output_time = @start_time
58
+ update_activity_state(:working)
59
+ end
60
+
61
+ # Update activity state and notify callback
62
+ def update_activity_state(state, message = nil)
63
+ @activity_state = state
64
+ @last_activity_time = Time.now if state == :working
65
+
66
+ # Log state change to job if in background mode
67
+ if @job_context
68
+ level = case state
69
+ when :completed then "info"
70
+ when :failed then "error"
71
+ else "debug"
72
+ end
73
+
74
+ log_to_job(message || "Provider state changed to #{state}", level)
75
+ end
76
+
77
+ @activity_callback&.call(state, message, self)
78
+ end
79
+
80
+ # Check if provider appears to be stuck
81
+ def stuck?
82
+ return false unless @activity_state == :working
83
+
84
+ time_since_activity = Time.now - @last_activity_time
85
+ time_since_activity > @stuck_timeout
86
+ end
87
+
88
+ # Get current execution time
89
+ def execution_time
90
+ return 0 unless @start_time
91
+ Time.now - @start_time
92
+ end
93
+
94
+ # Get time since last activity
95
+ def time_since_last_activity
96
+ Time.now - @last_activity_time
97
+ end
98
+
99
+ # Record activity (called when provider produces output)
100
+ def record_activity(message = nil)
101
+ @output_count += 1
102
+ @last_output_time = Time.now
103
+ update_activity_state(:working, message)
104
+ end
105
+
106
+ # Mark as completed
107
+ def mark_completed
108
+ update_activity_state(:completed)
109
+ end
110
+
111
+ # Mark as failed
112
+ def mark_failed(error_message = nil)
113
+ update_activity_state(:failed, error_message)
114
+ end
115
+
116
+ # Get activity summary for metrics
117
+ def activity_summary
118
+ {
119
+ provider: name,
120
+ step_name: @step_name,
121
+ start_time: @start_time&.iso8601,
122
+ end_time: Time.now.iso8601,
123
+ duration: execution_time,
124
+ final_state: @activity_state,
125
+ stuck_detected: stuck?,
126
+ output_count: @output_count
127
+ }
128
+ end
129
+
130
+ # Check if provider supports activity monitoring
131
+ def supports_activity_monitoring?
132
+ true # Default to true, override in subclasses if needed
133
+ end
134
+
135
+ # Get stuck timeout for this provider
136
+ attr_reader :stuck_timeout
137
+
138
+ protected
139
+
140
+ # Log message to job if in background mode
141
+ def log_to_job(message, level = "info", metadata = {})
142
+ return unless @job_context && @job_context[:job_manager]
143
+
144
+ metadata = metadata.merge(
145
+ provider: name,
146
+ step_name: @step_name,
147
+ activity_state: @activity_state,
148
+ execution_time: execution_time,
149
+ output_count: @output_count
150
+ )
151
+
152
+ @job_context[:job_manager].log_message(
153
+ @job_context[:job_id],
154
+ @job_context[:execution_id],
155
+ message,
156
+ level,
157
+ metadata
158
+ )
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "timeout"
5
+ require_relative "base"
6
+ require_relative "../util"
7
+
8
+ module Aidp
9
+ module Providers
10
+ class Cursor < Base
11
+ def self.available?
12
+ !!Aidp::Util.which("cursor-agent")
13
+ end
14
+
15
+ def name = "cursor"
16
+
17
+ def send(prompt:, session: nil)
18
+ raise "cursor-agent not available" unless self.class.available?
19
+
20
+ # Always use non-interactive mode with -p flag
21
+ cmd = ["cursor-agent", "-p"]
22
+ puts "šŸ“ Sending prompt to cursor-agent"
23
+
24
+ # Enable debug output if requested
25
+ if ENV["AIDP_DEBUG"]
26
+ puts "šŸ” Debug mode enabled - showing cursor-agent output"
27
+ end
28
+
29
+ # Setup logging if log file is specified
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
48
+ end
49
+
50
+ Open3.popen3(*cmd) do |stdin, stdout, stderr, wait|
51
+ # Send the prompt to stdin
52
+ stdin.puts prompt
53
+ stdin.close
54
+
55
+ # Read stdout and stderr synchronously for better reliability
56
+ output = ""
57
+ error_output = ""
58
+
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
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]}...")
72
+ end
73
+ rescue IOError => e
74
+ puts "šŸ“¤ stdout stream closed: #{e.message}" if ENV["AIDP_DEBUG"]
75
+ end
76
+
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
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]}...")
90
+ end
91
+ rescue IOError => e
92
+ puts "āŒ stderr stream closed: #{e.message}" if ENV["AIDP_DEBUG"]
93
+ end
94
+
95
+ # Start activity monitoring thread
96
+ activity_thread = Thread.new do
97
+ loop do
98
+ sleep 10 # Check every 10 seconds
99
+
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
120
+ end
121
+
122
+ # Stop checking if the process is done
123
+ break if wait.value
124
+ rescue
125
+ break
126
+ end
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
141
+ end
142
+
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
176
+ end
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}"
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end