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.
- checksums.yaml +4 -4
- data/README.md +128 -151
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +471 -0
- data/lib/aidp/analysis/seams.rb +159 -0
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +480 -0
- data/lib/aidp/analysis/tree_sitter_scan.rb +686 -0
- data/lib/aidp/analyze/error_handler.rb +2 -78
- data/lib/aidp/analyze/json_file_storage.rb +292 -0
- data/lib/aidp/analyze/progress.rb +12 -0
- data/lib/aidp/analyze/progress_visualizer.rb +12 -17
- data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
- data/lib/aidp/analyze/runner.rb +256 -87
- data/lib/aidp/analyze/steps.rb +6 -0
- data/lib/aidp/cli/jobs_command.rb +103 -435
- data/lib/aidp/cli.rb +317 -191
- data/lib/aidp/config.rb +298 -10
- data/lib/aidp/debug_logger.rb +195 -0
- data/lib/aidp/debug_mixin.rb +187 -0
- data/lib/aidp/execute/progress.rb +9 -0
- data/lib/aidp/execute/runner.rb +221 -40
- data/lib/aidp/execute/steps.rb +17 -7
- data/lib/aidp/execute/workflow_selector.rb +211 -0
- data/lib/aidp/harness/completion_checker.rb +268 -0
- data/lib/aidp/harness/condition_detector.rb +1526 -0
- data/lib/aidp/harness/config_loader.rb +373 -0
- data/lib/aidp/harness/config_manager.rb +382 -0
- data/lib/aidp/harness/config_schema.rb +1006 -0
- data/lib/aidp/harness/config_validator.rb +355 -0
- data/lib/aidp/harness/configuration.rb +477 -0
- data/lib/aidp/harness/enhanced_runner.rb +494 -0
- data/lib/aidp/harness/error_handler.rb +616 -0
- data/lib/aidp/harness/provider_config.rb +423 -0
- data/lib/aidp/harness/provider_factory.rb +306 -0
- data/lib/aidp/harness/provider_manager.rb +1269 -0
- data/lib/aidp/harness/provider_type_checker.rb +88 -0
- data/lib/aidp/harness/runner.rb +411 -0
- data/lib/aidp/harness/state/errors.rb +28 -0
- data/lib/aidp/harness/state/metrics.rb +219 -0
- data/lib/aidp/harness/state/persistence.rb +128 -0
- data/lib/aidp/harness/state/provider_state.rb +132 -0
- data/lib/aidp/harness/state/ui_state.rb +68 -0
- data/lib/aidp/harness/state/workflow_state.rb +123 -0
- data/lib/aidp/harness/state_manager.rb +586 -0
- data/lib/aidp/harness/status_display.rb +888 -0
- data/lib/aidp/harness/ui/base.rb +16 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
- data/lib/aidp/harness/ui/error_handler.rb +132 -0
- data/lib/aidp/harness/ui/frame_manager.rb +361 -0
- data/lib/aidp/harness/ui/job_monitor.rb +500 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
- data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
- data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
- data/lib/aidp/harness/ui/progress_display.rb +280 -0
- data/lib/aidp/harness/ui/question_collector.rb +141 -0
- data/lib/aidp/harness/ui/spinner_group.rb +184 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
- data/lib/aidp/harness/ui/status_manager.rb +312 -0
- data/lib/aidp/harness/ui/status_widget.rb +280 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
- data/lib/aidp/harness/user_interface.rb +2381 -0
- data/lib/aidp/provider_manager.rb +131 -7
- data/lib/aidp/providers/anthropic.rb +28 -109
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -183
- data/lib/aidp/providers/gemini.rb +24 -109
- data/lib/aidp/providers/macos_ui.rb +99 -5
- data/lib/aidp/providers/opencode.rb +194 -0
- data/lib/aidp/storage/csv_storage.rb +172 -0
- data/lib/aidp/storage/file_manager.rb +214 -0
- data/lib/aidp/storage/json_storage.rb +140 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +56 -35
- data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
- data/templates/COMMON/AGENT_BASE.md +11 -0
- data/templates/EXECUTE/00_PRD.md +4 -4
- data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
- data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
- data/templates/EXECUTE/08_TASKS.md +4 -4
- data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
- data/templates/README.md +279 -0
- data/templates/aidp-development.yml.example +373 -0
- data/templates/aidp-minimal.yml.example +48 -0
- data/templates/aidp-production.yml.example +475 -0
- data/templates/aidp.yml.example +598 -0
- metadata +106 -64
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -445
- data/lib/aidp/analyze/data_retention_manager.rb +0 -426
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -425
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -543
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -504
- data/lib/aidp/analyze/memory_manager.rb +0 -365
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -460
- data/lib/aidp/analyze/performance_optimizer.rb +0 -694
- data/lib/aidp/analyze/repository_chunker.rb +0 -704
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -662
- data/lib/aidp/analyze/tool_configuration.rb +0 -456
- data/lib/aidp/analyze/tool_modernization.rb +0 -750
- data/lib/aidp/database/pg_adapter.rb +0 -148
- data/lib/aidp/database_config.rb +0 -69
- data/lib/aidp/database_connection.rb +0 -72
- data/lib/aidp/database_migration.rb +0 -158
- data/lib/aidp/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -47
- data/lib/aidp/jobs/provider_execution_job.rb +0 -96
- data/lib/aidp/project_detector.rb +0 -117
- data/lib/aidp/providers/agent_supervisor.rb +0 -348
- data/lib/aidp/providers/supervised_base.rb +0 -317
- data/lib/aidp/providers/supervised_cursor.rb +0 -22
- data/lib/aidp/sync.rb +0 -13
- 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
|
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
|
-
|
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.
|
45
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
150
|
-
|
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
|
-
#
|
180
|
-
[
|
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.
|
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
|
-
|
66
|
+
|
67
|
+
if result.exit_status == 0
|
189
68
|
mark_completed
|
190
|
-
|
69
|
+
result.out
|
191
70
|
else
|
192
|
-
mark_failed("cursor-agent failed with exit code #{exit_status
|
193
|
-
|
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
|
-
|
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
|
-
#
|
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
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
#
|
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
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|