aidp 0.7.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 +60 -214
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +38 -23
- data/lib/aidp/analysis/seams.rb +2 -31
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +0 -13
- data/lib/aidp/analysis/tree_sitter_scan.rb +3 -20
- data/lib/aidp/analyze/error_handler.rb +2 -75
- 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/cli/jobs_command.rb +100 -432
- data/lib/aidp/cli.rb +309 -239
- 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 -103
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -181
- data/lib/aidp/providers/gemini.rb +24 -107
- 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 +54 -39
- 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 +93 -69
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -439
- data/lib/aidp/analyze/data_retention_manager.rb +0 -421
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -418
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -533
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -499
- data/lib/aidp/analyze/memory_manager.rb +0 -339
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -454
- data/lib/aidp/analyze/performance_optimizer.rb +0 -691
- data/lib/aidp/analyze/repository_chunker.rb +0 -697
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -655
- data/lib/aidp/analyze/tool_configuration.rb +0 -441
- 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/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -45
- data/lib/aidp/jobs/provider_execution_job.rb +0 -83
- 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,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
|
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
|
-
end
|
44
|
+
print_activity_status(elapsed)
|
125
45
|
end
|
46
|
+
end
|
126
47
|
|
127
|
-
|
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
|
-
|
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
|
-
#
|
148
|
-
|
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
|
-
#
|
178
|
-
[
|
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.
|
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
|
-
|
66
|
+
|
67
|
+
if result.exit_status == 0
|
187
68
|
mark_completed
|
188
|
-
|
69
|
+
result.out
|
189
70
|
else
|
190
|
-
mark_failed("cursor-agent failed with exit code #{exit_status
|
191
|
-
|
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
|
-
|
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
|
-
#
|
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
|
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
|
-
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
#
|
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
|
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
|