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,211 +1,100 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "open3"
4
3
  require "timeout"
5
4
  require_relative "base"
6
5
  require_relative "../util"
6
+ require_relative "../debug_mixin"
7
7
 
8
8
  module Aidp
9
9
  module Providers
10
10
  class Cursor < Base
11
+ include Aidp::DebugMixin
12
+
11
13
  def self.available?
12
14
  !!Aidp::Util.which("cursor-agent")
13
15
  end
14
16
 
15
- def name = "cursor"
17
+ def name
18
+ "cursor"
19
+ end
16
20
 
17
21
  def send(prompt:, session: nil)
18
22
  raise "cursor-agent not available" unless self.class.available?
19
23
 
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
24
  # Smart timeout calculation
33
25
  timeout_seconds = calculate_timeout
34
26
 
35
- puts "⏱️ Timeout set to #{timeout_seconds} seconds"
27
+ debug_provider("cursor", "Starting execution", {timeout: timeout_seconds})
28
+ debug_log("📝 Sending prompt to cursor-agent (length: #{prompt.length})", level: :info)
36
29
 
37
30
  # Set up activity monitoring
38
31
  setup_activity_monitoring("cursor-agent", method(:activity_callback))
39
32
  record_activity("Starting cursor-agent execution")
40
33
 
41
- # Start activity display thread
34
+ # Start activity display thread with timeout
42
35
  activity_display_thread = Thread.new do
36
+ start_time = Time.now
43
37
  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
38
+ sleep 0.5 # Update every 500ms to reduce spam
39
+ elapsed = Time.now - start_time
54
40
 
55
- # Read stdout and stderr synchronously for better reliability
56
- output = ""
57
- error_output = ""
41
+ # Break if we've been running too long or state changed
42
+ break if elapsed > timeout_seconds || @activity_state == :completed || @activity_state == :failed
58
43
 
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
- end
44
+ print_activity_status(elapsed)
125
45
  end
46
+ end
126
47
 
127
- # Wait for process to complete with timeout
48
+ begin
49
+ # Use debug_execute_command for better debugging
50
+ # Try agent command first (better for large prompts), fallback to -p mode
128
51
  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
139
- end
140
-
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
52
+ result = debug_execute_command("cursor-agent", args: ["agent"], input: prompt, timeout: timeout_seconds)
146
53
  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
174
- end
54
+ # Fallback to -p mode if agent command fails
55
+ debug_log("🔄 Falling back to -p mode: #{e.message}", level: :warn)
56
+ result = debug_execute_command("cursor-agent", args: ["-p"], input: prompt, timeout: timeout_seconds)
175
57
  end
176
58
 
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
59
+ # Log the results
60
+ debug_command("cursor-agent", args: ["-p"], input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
181
61
 
182
62
  # Stop activity display
183
- activity_display_thread.join
184
-
63
+ activity_display_thread.kill if activity_display_thread.alive?
64
+ activity_display_thread.join(0.1) # Give it 100ms to finish
185
65
  clear_activity_status
186
- if exit_status.success?
66
+
67
+ if result.exit_status == 0
187
68
  mark_completed
188
- output
69
+ result.out
189
70
  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}"
71
+ mark_failed("cursor-agent failed with exit code #{result.exit_status}")
72
+ debug_error(StandardError.new("cursor-agent failed"), {exit_code: result.exit_status, stderr: result.err})
73
+ raise "cursor-agent failed with exit code #{result.exit_status}: #{result.err}"
192
74
  end
75
+ rescue => e
76
+ # Stop activity display
77
+ activity_display_thread.kill if activity_display_thread.alive?
78
+ activity_display_thread.join(0.1) # Give it 100ms to finish
79
+ clear_activity_status
80
+ mark_failed("cursor-agent execution failed: #{e.message}")
81
+ debug_error(e, {provider: "cursor", prompt_length: prompt.length})
82
+ raise
193
83
  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
84
  end
203
85
 
204
86
  private
205
87
 
206
- def print_activity_status
207
- # Print activity status during cursor execution
208
- print "🔄 cursor-agent is running..."
88
+ def print_activity_status(elapsed)
89
+ # Print activity status during cursor execution with elapsed time
90
+ minutes = (elapsed / 60).to_i
91
+ seconds = (elapsed % 60).to_i
92
+
93
+ if minutes > 0
94
+ print "\r🔄 cursor-agent is running... (#{minutes}m #{seconds}s)"
95
+ else
96
+ print "\r🔄 cursor-agent is running... (#{seconds}s)"
97
+ end
209
98
  $stdout.flush
210
99
  end
211
100
 
@@ -244,25 +133,7 @@ module Aidp
244
133
  end
245
134
 
246
135
  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
136
+ # Timeout recommendations based on step type patterns
266
137
  step_name = ENV["AIDP_CURRENT_STEP"] || ""
267
138
 
268
139
  case step_name
@@ -1,111 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "base"
4
+ require_relative "../debug_mixin"
4
5
 
5
6
  module Aidp
6
7
  module Providers
7
8
  class Gemini < Base
9
+ include Aidp::DebugMixin
10
+
8
11
  def self.available?
9
12
  !!Aidp::Util.which("gemini")
10
13
  end
11
14
 
12
- def name = "gemini"
15
+ def name
16
+ "gemini"
17
+ end
13
18
 
14
19
  def send(prompt:, session: nil)
15
20
  raise "gemini CLI not available" unless self.class.available?
16
21
 
17
- require "open3"
18
-
19
- # Use Gemini CLI for non-interactive mode
20
- cmd = ["gemini", "--print"]
21
-
22
- puts "📝 Sending prompt to gemini..."
23
-
24
22
  # Smart timeout calculation
25
23
  timeout_seconds = calculate_timeout
26
24
 
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
25
+ debug_provider("gemini", "Starting execution", {timeout: timeout_seconds})
26
+ debug_log("📝 Sending prompt to gemini...", level: :info)
27
+
28
+ begin
29
+ # Use debug_execute_command for better debugging
30
+ result = debug_execute_command("gemini", args: ["--print"], input: prompt, timeout: timeout_seconds)
31
+
32
+ # Log the results
33
+ debug_command("gemini", args: ["--print"], input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
62
34
 
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
35
+ if result.exit_status == 0
36
+ result.out
37
+ else
38
+ debug_error(StandardError.new("gemini failed"), {exit_code: result.exit_status, stderr: result.err})
39
+ raise "gemini failed with exit code #{result.exit_status}: #{result.err}"
108
40
  end
41
+ rescue => e
42
+ debug_error(e, {provider: "gemini", prompt_length: prompt.length})
43
+ raise
109
44
  end
110
45
  end
111
46
 
@@ -140,25 +75,7 @@ module Aidp
140
75
  end
141
76
 
142
77
  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
78
+ # Timeout recommendations based on step type patterns
162
79
  step_name = ENV["AIDP_CURRENT_STEP"] || ""
163
80
 
164
81
  case step_name
@@ -1,23 +1,117 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "base"
4
+ require_relative "../debug_mixin"
4
5
 
5
6
  module Aidp
6
7
  module Providers
7
8
  class MacOSUI < Base
9
+ include Aidp::DebugMixin
10
+
8
11
  def self.available?
9
12
  RUBY_PLATFORM.include?("darwin")
10
13
  end
11
14
 
12
- def name = "macos"
15
+ def name
16
+ "macos"
17
+ end
13
18
 
14
19
  def send(prompt:, session: nil)
15
20
  raise "macOS UI not available on this platform" unless self.class.available?
16
21
 
17
- # Use macOS UI for interactive mode
18
- cmd = ["osascript", "-e", "display dialog \"#{prompt}\" with title \"Aidp\" buttons {\"OK\"} default button \"OK\""]
19
- system(*cmd)
20
- :ok
22
+ debug_provider("macos", "Starting Cursor interaction", {prompt_length: prompt.length})
23
+
24
+ # Try to use Cursor's chat interface via AppleScript
25
+ result = interact_with_cursor(prompt)
26
+
27
+ if result[:success]
28
+ debug_log("✅ Successfully sent prompt to Cursor", level: :info)
29
+ result[:response]
30
+ else
31
+ debug_log("❌ Failed to interact with Cursor: #{result[:error]}", level: :warn)
32
+ # Fallback to simple dialog
33
+ fallback_dialog(prompt)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def interact_with_cursor(prompt)
40
+ # Create a temporary script file for the prompt with proper encoding
41
+ temp_file = "/tmp/aidp_cursor_prompt.txt"
42
+ File.write(temp_file, prompt, encoding: "UTF-8")
43
+
44
+ # AppleScript to interact with Cursor - use properly escaped prompt to avoid injection
45
+ escaped_prompt = escape_for_applescript(prompt)
46
+ script = <<~APPLESCRIPT
47
+ tell application "Cursor"
48
+ activate
49
+ end tell
50
+
51
+ delay 1
52
+
53
+ tell application "System Events"
54
+ -- Open chat panel (Cmd+L)
55
+ keystroke "l" using command down
56
+ delay 2
57
+
58
+ -- Type the prompt (properly escaped)
59
+ keystroke "#{escaped_prompt}"
60
+ delay 1
61
+
62
+ -- Send the message (Enter)
63
+ keystroke (ASCII character 13)
64
+ delay 3
65
+
66
+ -- Try to get response (this is tricky without accessibility permissions)
67
+ -- For now, we'll just return success
68
+ return "Prompt sent to Cursor chat"
69
+ end tell
70
+ APPLESCRIPT
71
+
72
+ begin
73
+ # Use Open3 to safely execute AppleScript without shell injection
74
+ require "open3"
75
+
76
+ # Write AppleScript to temporary file to avoid command line issues
77
+ script_file = "/tmp/aidp_cursor_script.scpt"
78
+ File.write(script_file, script, encoding: "UTF-8")
79
+
80
+ stdout, stderr, status = Open3.capture3("osascript", script_file)
81
+
82
+ if status.success?
83
+ {success: true, response: stdout.strip}
84
+ else
85
+ {success: false, error: stderr.strip}
86
+ end
87
+ rescue => e
88
+ {success: false, error: e.message}
89
+ ensure
90
+ File.delete(temp_file) if File.exist?(temp_file)
91
+ File.delete(script_file) if File.exist?(script_file)
92
+ end
93
+ end
94
+
95
+ def escape_for_applescript(text)
96
+ # Escape special characters for AppleScript
97
+ # Must escape backslashes first to avoid double-escaping
98
+ text.gsub("\\", "\\\\").gsub('"', '\\"').gsub("'", "\\'").gsub("\n", "\\n")
99
+ end
100
+
101
+ def fallback_dialog(prompt)
102
+ # Fallback to simple dialog
103
+ truncated_prompt = (prompt.length > 200) ? prompt[0..200] + "..." : prompt
104
+
105
+ script = <<~APPLESCRIPT
106
+ display dialog "#{escape_for_applescript(truncated_prompt)}" with title "Aidp - Cursor Integration" buttons {"OK", "Open Cursor"} default button "Open Cursor"
107
+ set buttonPressed to button returned of result
108
+ if buttonPressed is "Open Cursor" then
109
+ tell application "Cursor" to activate
110
+ end if
111
+ APPLESCRIPT
112
+
113
+ system("osascript", "-e", script)
114
+ "Dialog shown - please use Cursor manually"
21
115
  end
22
116
  end
23
117
  end