aidp 0.5.0 → 0.8.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +128 -151
  3. data/bin/aidp +1 -1
  4. data/lib/aidp/analysis/kb_inspector.rb +471 -0
  5. data/lib/aidp/analysis/seams.rb +159 -0
  6. data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +480 -0
  7. data/lib/aidp/analysis/tree_sitter_scan.rb +686 -0
  8. data/lib/aidp/analyze/error_handler.rb +2 -78
  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/analyze/steps.rb +6 -0
  15. data/lib/aidp/cli/jobs_command.rb +103 -435
  16. data/lib/aidp/cli.rb +317 -191
  17. data/lib/aidp/config.rb +298 -10
  18. data/lib/aidp/debug_logger.rb +195 -0
  19. data/lib/aidp/debug_mixin.rb +187 -0
  20. data/lib/aidp/execute/progress.rb +9 -0
  21. data/lib/aidp/execute/runner.rb +221 -40
  22. data/lib/aidp/execute/steps.rb +17 -7
  23. data/lib/aidp/execute/workflow_selector.rb +211 -0
  24. data/lib/aidp/harness/completion_checker.rb +268 -0
  25. data/lib/aidp/harness/condition_detector.rb +1526 -0
  26. data/lib/aidp/harness/config_loader.rb +373 -0
  27. data/lib/aidp/harness/config_manager.rb +382 -0
  28. data/lib/aidp/harness/config_schema.rb +1006 -0
  29. data/lib/aidp/harness/config_validator.rb +355 -0
  30. data/lib/aidp/harness/configuration.rb +477 -0
  31. data/lib/aidp/harness/enhanced_runner.rb +494 -0
  32. data/lib/aidp/harness/error_handler.rb +616 -0
  33. data/lib/aidp/harness/provider_config.rb +423 -0
  34. data/lib/aidp/harness/provider_factory.rb +306 -0
  35. data/lib/aidp/harness/provider_manager.rb +1269 -0
  36. data/lib/aidp/harness/provider_type_checker.rb +88 -0
  37. data/lib/aidp/harness/runner.rb +411 -0
  38. data/lib/aidp/harness/state/errors.rb +28 -0
  39. data/lib/aidp/harness/state/metrics.rb +219 -0
  40. data/lib/aidp/harness/state/persistence.rb +128 -0
  41. data/lib/aidp/harness/state/provider_state.rb +132 -0
  42. data/lib/aidp/harness/state/ui_state.rb +68 -0
  43. data/lib/aidp/harness/state/workflow_state.rb +123 -0
  44. data/lib/aidp/harness/state_manager.rb +586 -0
  45. data/lib/aidp/harness/status_display.rb +888 -0
  46. data/lib/aidp/harness/ui/base.rb +16 -0
  47. data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
  48. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
  49. data/lib/aidp/harness/ui/error_handler.rb +132 -0
  50. data/lib/aidp/harness/ui/frame_manager.rb +361 -0
  51. data/lib/aidp/harness/ui/job_monitor.rb +500 -0
  52. data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
  53. data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
  54. data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
  55. data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
  56. data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
  57. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
  58. data/lib/aidp/harness/ui/progress_display.rb +280 -0
  59. data/lib/aidp/harness/ui/question_collector.rb +141 -0
  60. data/lib/aidp/harness/ui/spinner_group.rb +184 -0
  61. data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
  62. data/lib/aidp/harness/ui/status_manager.rb +312 -0
  63. data/lib/aidp/harness/ui/status_widget.rb +280 -0
  64. data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
  65. data/lib/aidp/harness/user_interface.rb +2381 -0
  66. data/lib/aidp/provider_manager.rb +131 -7
  67. data/lib/aidp/providers/anthropic.rb +28 -109
  68. data/lib/aidp/providers/base.rb +170 -0
  69. data/lib/aidp/providers/cursor.rb +52 -183
  70. data/lib/aidp/providers/gemini.rb +24 -109
  71. data/lib/aidp/providers/macos_ui.rb +99 -5
  72. data/lib/aidp/providers/opencode.rb +194 -0
  73. data/lib/aidp/storage/csv_storage.rb +172 -0
  74. data/lib/aidp/storage/file_manager.rb +214 -0
  75. data/lib/aidp/storage/json_storage.rb +140 -0
  76. data/lib/aidp/version.rb +1 -1
  77. data/lib/aidp.rb +56 -35
  78. data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
  79. data/templates/COMMON/AGENT_BASE.md +11 -0
  80. data/templates/EXECUTE/00_PRD.md +4 -4
  81. data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
  82. data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
  83. data/templates/EXECUTE/08_TASKS.md +4 -4
  84. data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
  85. data/templates/README.md +279 -0
  86. data/templates/aidp-development.yml.example +373 -0
  87. data/templates/aidp-minimal.yml.example +48 -0
  88. data/templates/aidp-production.yml.example +475 -0
  89. data/templates/aidp.yml.example +598 -0
  90. metadata +106 -64
  91. data/lib/aidp/analyze/agent_personas.rb +0 -71
  92. data/lib/aidp/analyze/agent_tool_executor.rb +0 -445
  93. data/lib/aidp/analyze/data_retention_manager.rb +0 -426
  94. data/lib/aidp/analyze/database.rb +0 -260
  95. data/lib/aidp/analyze/dependencies.rb +0 -335
  96. data/lib/aidp/analyze/export_manager.rb +0 -425
  97. data/lib/aidp/analyze/focus_guidance.rb +0 -517
  98. data/lib/aidp/analyze/incremental_analyzer.rb +0 -543
  99. data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
  100. data/lib/aidp/analyze/large_analysis_progress.rb +0 -504
  101. data/lib/aidp/analyze/memory_manager.rb +0 -365
  102. data/lib/aidp/analyze/metrics_storage.rb +0 -336
  103. data/lib/aidp/analyze/parallel_processor.rb +0 -460
  104. data/lib/aidp/analyze/performance_optimizer.rb +0 -694
  105. data/lib/aidp/analyze/repository_chunker.rb +0 -704
  106. data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
  107. data/lib/aidp/analyze/storage.rb +0 -662
  108. data/lib/aidp/analyze/tool_configuration.rb +0 -456
  109. data/lib/aidp/analyze/tool_modernization.rb +0 -750
  110. data/lib/aidp/database/pg_adapter.rb +0 -148
  111. data/lib/aidp/database_config.rb +0 -69
  112. data/lib/aidp/database_connection.rb +0 -72
  113. data/lib/aidp/database_migration.rb +0 -158
  114. data/lib/aidp/job_manager.rb +0 -41
  115. data/lib/aidp/jobs/base_job.rb +0 -47
  116. data/lib/aidp/jobs/provider_execution_job.rb +0 -96
  117. data/lib/aidp/project_detector.rb +0 -117
  118. data/lib/aidp/providers/agent_supervisor.rb +0 -348
  119. data/lib/aidp/providers/supervised_base.rb +0 -317
  120. data/lib/aidp/providers/supervised_cursor.rb +0 -22
  121. data/lib/aidp/sync.rb +0 -13
  122. data/lib/aidp/workspace.rb +0 -19
@@ -1,213 +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
- rescue
125
- break
126
- end
44
+ print_activity_status(elapsed)
127
45
  end
46
+ end
128
47
 
129
- # 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
130
51
  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
52
+ result = debug_execute_command("cursor-agent", args: ["agent"], input: prompt, timeout: timeout_seconds)
148
53
  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
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)
177
57
  end
178
58
 
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
59
+ # Log the results
60
+ debug_command("cursor-agent", args: ["-p"], input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
183
61
 
184
62
  # Stop activity display
185
- activity_display_thread.join
186
-
63
+ activity_display_thread.kill if activity_display_thread.alive?
64
+ activity_display_thread.join(0.1) # Give it 100ms to finish
187
65
  clear_activity_status
188
- if exit_status.success?
66
+
67
+ if result.exit_status == 0
189
68
  mark_completed
190
- output
69
+ result.out
191
70
  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}"
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}"
194
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
195
83
  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
84
  end
205
85
 
206
86
  private
207
87
 
208
- def print_activity_status
209
- # Print activity status during cursor execution
210
- 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
211
98
  $stdout.flush
212
99
  end
213
100
 
@@ -246,25 +133,7 @@ module Aidp
246
133
  end
247
134
 
248
135
  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
136
+ # Timeout recommendations based on step type patterns
268
137
  step_name = ENV["AIDP_CURRENT_STEP"] || ""
269
138
 
270
139
  case step_name
@@ -1,113 +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
- rescue
61
- break
62
- end
63
- 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)
64
34
 
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
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}"
110
40
  end
41
+ rescue => e
42
+ debug_error(e, {provider: "gemini", prompt_length: prompt.length})
43
+ raise
111
44
  end
112
45
  end
113
46
 
@@ -142,25 +75,7 @@ module Aidp
142
75
  end
143
76
 
144
77
  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
78
+ # Timeout recommendations based on step type patterns
164
79
  step_name = ENV["AIDP_CURRENT_STEP"] || ""
165
80
 
166
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