aidp 0.17.0 → 0.17.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.
- checksums.yaml +4 -4
- data/lib/aidp/analyze/kb_inspector.rb +2 -3
- data/lib/aidp/analyze/progress.rb +5 -10
- data/lib/aidp/cli/mcp_dashboard.rb +1 -1
- data/lib/aidp/cli.rb +21 -27
- data/lib/aidp/execute/progress.rb +5 -8
- data/lib/aidp/harness/config_loader.rb +2 -2
- data/lib/aidp/harness/enhanced_runner.rb +16 -7
- data/lib/aidp/harness/error_handler.rb +12 -5
- data/lib/aidp/harness/provider_manager.rb +4 -19
- data/lib/aidp/harness/runner.rb +2 -2
- data/lib/aidp/harness/state/persistence.rb +9 -10
- data/lib/aidp/harness/state/workflow_state.rb +3 -2
- data/lib/aidp/harness/state_manager.rb +33 -97
- data/lib/aidp/harness/status_display.rb +22 -12
- data/lib/aidp/harness/ui/enhanced_tui.rb +3 -4
- data/lib/aidp/harness/user_interface.rb +11 -6
- data/lib/aidp/jobs/background_runner.rb +7 -1
- data/lib/aidp/logger.rb +1 -1
- data/lib/aidp/message_display.rb +9 -2
- data/lib/aidp/providers/anthropic.rb +1 -1
- data/lib/aidp/providers/base.rb +4 -4
- data/lib/aidp/providers/codex.rb +1 -1
- data/lib/aidp/providers/cursor.rb +1 -1
- data/lib/aidp/providers/gemini.rb +1 -1
- data/lib/aidp/providers/github_copilot.rb +1 -1
- data/lib/aidp/providers/macos_ui.rb +1 -1
- data/lib/aidp/providers/opencode.rb +1 -1
- data/lib/aidp/skills/wizard/prompter.rb +2 -2
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/plan_generator.rb +1 -1
- data/lib/aidp/watch/repository_client.rb +13 -9
- data/lib/aidp/workflows/guided_agent.rb +2 -312
- data/lib/aidp/workstream_executor.rb +8 -2
- metadata +1 -1
|
@@ -11,19 +11,20 @@ module Aidp
|
|
|
11
11
|
module Harness
|
|
12
12
|
# Manages harness-specific state and persistence, extending existing progress tracking
|
|
13
13
|
class StateManager
|
|
14
|
-
def initialize(project_dir, mode)
|
|
14
|
+
def initialize(project_dir, mode, skip_persistence: false)
|
|
15
15
|
@project_dir = project_dir
|
|
16
16
|
@mode = mode
|
|
17
17
|
@state_dir = File.join(project_dir, ".aidp", "harness")
|
|
18
18
|
@state_file = File.join(@state_dir, "#{mode}_state.json")
|
|
19
19
|
@lock_file = File.join(@state_dir, "#{mode}_state.lock")
|
|
20
|
+
@skip_persistence = skip_persistence
|
|
21
|
+
@memory_state = {} if @skip_persistence # In-memory state for tests
|
|
20
22
|
|
|
21
|
-
# Initialize the appropriate progress tracker
|
|
22
23
|
case mode
|
|
23
24
|
when :analyze
|
|
24
|
-
@progress_tracker = Aidp::Analyze::Progress.new(project_dir)
|
|
25
|
+
@progress_tracker = Aidp::Analyze::Progress.new(project_dir, skip_persistence: @skip_persistence)
|
|
25
26
|
when :execute
|
|
26
|
-
@progress_tracker = Aidp::Execute::Progress.new(project_dir)
|
|
27
|
+
@progress_tracker = Aidp::Execute::Progress.new(project_dir, skip_persistence: @skip_persistence)
|
|
27
28
|
else
|
|
28
29
|
raise ArgumentError, "Unsupported mode: #{mode}"
|
|
29
30
|
end
|
|
@@ -33,21 +34,14 @@ module Aidp
|
|
|
33
34
|
|
|
34
35
|
# Check if state exists
|
|
35
36
|
def has_state?
|
|
36
|
-
|
|
37
|
-
return false if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
38
|
-
|
|
37
|
+
return false if @skip_persistence
|
|
39
38
|
File.exist?(@state_file)
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
# Load existing state
|
|
43
42
|
def load_state
|
|
44
|
-
|
|
45
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
46
|
-
return {}
|
|
47
|
-
end
|
|
48
|
-
|
|
43
|
+
return @memory_state if @skip_persistence
|
|
49
44
|
return {} unless has_state?
|
|
50
|
-
|
|
51
45
|
with_lock do
|
|
52
46
|
content = File.read(@state_file)
|
|
53
47
|
JSON.parse(content, symbolize_names: true)
|
|
@@ -59,20 +53,16 @@ module Aidp
|
|
|
59
53
|
|
|
60
54
|
# Save current state
|
|
61
55
|
def save_state(state_data)
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
if @skip_persistence
|
|
57
|
+
@memory_state = state_data
|
|
64
58
|
return
|
|
65
59
|
end
|
|
66
|
-
|
|
67
60
|
with_lock do
|
|
68
|
-
# Add metadata
|
|
69
61
|
state_with_metadata = state_data.merge(
|
|
70
62
|
mode: @mode,
|
|
71
63
|
project_dir: @project_dir,
|
|
72
64
|
saved_at: Time.now.iso8601
|
|
73
65
|
)
|
|
74
|
-
|
|
75
|
-
# Write to temporary file first, then rename (atomic operation)
|
|
76
66
|
temp_file = "#{@state_file}.tmp"
|
|
77
67
|
File.write(temp_file, JSON.pretty_generate(state_with_metadata))
|
|
78
68
|
File.rename(temp_file, @state_file)
|
|
@@ -81,9 +71,10 @@ module Aidp
|
|
|
81
71
|
|
|
82
72
|
# Clear state (for fresh start)
|
|
83
73
|
def clear_state
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
if @skip_persistence
|
|
75
|
+
@memory_state = {}
|
|
76
|
+
return
|
|
77
|
+
end
|
|
87
78
|
with_lock do
|
|
88
79
|
File.delete(@state_file) if File.exist?(@state_file)
|
|
89
80
|
end
|
|
@@ -91,11 +82,7 @@ module Aidp
|
|
|
91
82
|
|
|
92
83
|
# Get state metadata
|
|
93
84
|
def state_metadata
|
|
94
|
-
# In test mode, return empty metadata to avoid file operations
|
|
95
|
-
return {} if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
96
|
-
|
|
97
85
|
return {} unless has_state?
|
|
98
|
-
|
|
99
86
|
state = load_state
|
|
100
87
|
{
|
|
101
88
|
mode: state[:mode],
|
|
@@ -216,21 +203,6 @@ module Aidp
|
|
|
216
203
|
|
|
217
204
|
# Export state for debugging
|
|
218
205
|
def export_state
|
|
219
|
-
# In test mode, include test variables
|
|
220
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
221
|
-
test_state = {
|
|
222
|
-
current_workstream: @test_workstream,
|
|
223
|
-
workstream_path: @test_workstream_path,
|
|
224
|
-
workstream_branch: @test_workstream_branch
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
state_file: @state_file,
|
|
228
|
-
has_state: false,
|
|
229
|
-
metadata: {},
|
|
230
|
-
state: test_state
|
|
231
|
-
}
|
|
232
|
-
end
|
|
233
|
-
|
|
234
206
|
{
|
|
235
207
|
state_file: @state_file,
|
|
236
208
|
has_state: has_state?,
|
|
@@ -309,11 +281,7 @@ module Aidp
|
|
|
309
281
|
@progress_tracker.reset
|
|
310
282
|
clear_state
|
|
311
283
|
# Also clear test workstream variables
|
|
312
|
-
|
|
313
|
-
@test_workstream = nil
|
|
314
|
-
@test_workstream_path = nil
|
|
315
|
-
@test_workstream_branch = nil
|
|
316
|
-
end
|
|
284
|
+
# Test-only instance vars removed; rely on persistence skip flag for isolation
|
|
317
285
|
end
|
|
318
286
|
|
|
319
287
|
# Get progress summary
|
|
@@ -472,11 +440,6 @@ module Aidp
|
|
|
472
440
|
|
|
473
441
|
# Get current workstream slug
|
|
474
442
|
def current_workstream
|
|
475
|
-
# In test mode, use instance variable
|
|
476
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
477
|
-
return @test_workstream
|
|
478
|
-
end
|
|
479
|
-
|
|
480
443
|
state = load_state
|
|
481
444
|
state[:current_workstream]
|
|
482
445
|
end
|
|
@@ -498,14 +461,6 @@ module Aidp
|
|
|
498
461
|
ws = Aidp::Worktree.info(slug: slug, project_dir: @project_dir)
|
|
499
462
|
return false unless ws
|
|
500
463
|
|
|
501
|
-
# In test mode, use instance variables
|
|
502
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
503
|
-
@test_workstream = slug
|
|
504
|
-
@test_workstream_path = ws[:path]
|
|
505
|
-
@test_workstream_branch = ws[:branch]
|
|
506
|
-
return true
|
|
507
|
-
end
|
|
508
|
-
|
|
509
464
|
update_state(
|
|
510
465
|
current_workstream: slug,
|
|
511
466
|
workstream_path: ws[:path],
|
|
@@ -516,14 +471,6 @@ module Aidp
|
|
|
516
471
|
|
|
517
472
|
# Clear current workstream (switch back to main project)
|
|
518
473
|
def clear_workstream
|
|
519
|
-
# In test mode, use instance variables
|
|
520
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
521
|
-
@test_workstream = nil
|
|
522
|
-
@test_workstream_path = nil
|
|
523
|
-
@test_workstream_branch = nil
|
|
524
|
-
return
|
|
525
|
-
end
|
|
526
|
-
|
|
527
474
|
update_state(
|
|
528
475
|
current_workstream: nil,
|
|
529
476
|
workstream_path: nil,
|
|
@@ -533,15 +480,6 @@ module Aidp
|
|
|
533
480
|
|
|
534
481
|
# Get workstream metadata
|
|
535
482
|
def workstream_metadata
|
|
536
|
-
# In test mode, use instance variables
|
|
537
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
538
|
-
return {
|
|
539
|
-
slug: @test_workstream,
|
|
540
|
-
path: @test_workstream_path,
|
|
541
|
-
branch: @test_workstream_branch
|
|
542
|
-
}
|
|
543
|
-
end
|
|
544
|
-
|
|
545
483
|
state = load_state
|
|
546
484
|
{
|
|
547
485
|
slug: state[:current_workstream],
|
|
@@ -642,34 +580,31 @@ module Aidp
|
|
|
642
580
|
end
|
|
643
581
|
|
|
644
582
|
def with_lock(&_block)
|
|
645
|
-
#
|
|
646
|
-
if
|
|
583
|
+
# Skip locking entirely when persistence disabled
|
|
584
|
+
if @skip_persistence
|
|
647
585
|
yield
|
|
648
586
|
return
|
|
649
587
|
end
|
|
650
588
|
|
|
651
|
-
# Improved file-based locking
|
|
589
|
+
# Improved file-based locking using exponential backoff
|
|
590
|
+
require_relative "../concurrency"
|
|
652
591
|
lock_acquired = false
|
|
653
|
-
timeout = 30 # 30 seconds in production
|
|
654
|
-
|
|
655
|
-
start_time = Time.now
|
|
656
|
-
while (Time.now - start_time) < timeout
|
|
657
|
-
begin
|
|
658
|
-
# Try to acquire lock
|
|
659
|
-
File.open(@lock_file, File::CREAT | File::EXCL | File::WRONLY) do |_lock|
|
|
660
|
-
lock_acquired = true
|
|
661
|
-
yield
|
|
662
|
-
break
|
|
663
|
-
end
|
|
664
|
-
rescue Errno::EEXIST
|
|
665
|
-
# Lock file exists, wait briefly and retry
|
|
666
|
-
sleep(0.1)
|
|
667
|
-
end
|
|
668
|
-
end
|
|
669
592
|
|
|
670
|
-
|
|
671
|
-
|
|
593
|
+
Aidp::Concurrency::Backoff.retry(
|
|
594
|
+
max_attempts: 300, # 300 attempts * ~0.1s = ~30s max
|
|
595
|
+
base: 0.1,
|
|
596
|
+
max_delay: 1.0,
|
|
597
|
+
strategy: :exponential,
|
|
598
|
+
on: [Errno::EEXIST]
|
|
599
|
+
) do
|
|
600
|
+
# Try to acquire lock
|
|
601
|
+
File.open(@lock_file, File::CREAT | File::EXCL | File::WRONLY) do |_lock|
|
|
602
|
+
lock_acquired = true
|
|
603
|
+
yield
|
|
604
|
+
end
|
|
672
605
|
end
|
|
606
|
+
rescue Aidp::Concurrency::MaxAttemptsExceededError
|
|
607
|
+
raise "Could not acquire state lock within timeout"
|
|
673
608
|
ensure
|
|
674
609
|
# Clean up lock file
|
|
675
610
|
File.delete(@lock_file) if lock_acquired && File.exist?(@lock_file)
|
|
@@ -677,6 +612,7 @@ module Aidp
|
|
|
677
612
|
|
|
678
613
|
# Clean up stale lock files (older than 30 seconds)
|
|
679
614
|
def cleanup_stale_lock
|
|
615
|
+
return if @skip_persistence
|
|
680
616
|
return unless File.exist?(@lock_file)
|
|
681
617
|
|
|
682
618
|
begin
|
|
@@ -46,7 +46,7 @@ module Aidp
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# Start real-time status updates
|
|
49
|
-
def start_status_updates(display_mode = :compact)
|
|
49
|
+
def start_status_updates(display_mode = :compact, async_updates: true)
|
|
50
50
|
return if @running
|
|
51
51
|
|
|
52
52
|
@running = true
|
|
@@ -54,20 +54,30 @@ module Aidp
|
|
|
54
54
|
@display_mode = display_mode
|
|
55
55
|
@last_update = Time.now
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
if async_updates
|
|
58
|
+
begin
|
|
59
|
+
require "concurrent"
|
|
60
|
+
@status_future = Concurrent::Future.execute do
|
|
61
|
+
while @running
|
|
62
|
+
begin
|
|
63
|
+
collect_status_data
|
|
64
|
+
display_status
|
|
65
|
+
check_alerts
|
|
66
|
+
sleep(@update_interval)
|
|
67
|
+
rescue => e
|
|
68
|
+
handle_display_error(e)
|
|
69
|
+
end
|
|
68
70
|
end
|
|
69
71
|
end
|
|
72
|
+
rescue LoadError
|
|
73
|
+
# Fallback: perform single synchronous update if concurrent not available
|
|
74
|
+
collect_status_data
|
|
75
|
+
display_status
|
|
70
76
|
end
|
|
77
|
+
else
|
|
78
|
+
# Synchronous single update mode (useful for tests)
|
|
79
|
+
collect_status_data
|
|
80
|
+
display_status
|
|
71
81
|
end
|
|
72
82
|
end
|
|
73
83
|
|
|
@@ -20,16 +20,15 @@ module Aidp
|
|
|
20
20
|
|
|
21
21
|
class DisplayError < TUIError; end
|
|
22
22
|
|
|
23
|
-
def initialize(prompt: TTY::Prompt.new)
|
|
23
|
+
def initialize(prompt: TTY::Prompt.new, tty: $stdin)
|
|
24
24
|
@cursor = TTY::Cursor
|
|
25
25
|
@screen = TTY::Screen
|
|
26
26
|
@pastel = Pastel.new
|
|
27
27
|
@prompt = prompt
|
|
28
28
|
|
|
29
29
|
# Headless (non-interactive) detection for test/CI environments:
|
|
30
|
-
# -
|
|
31
|
-
|
|
32
|
-
@headless = !!(defined?(RSpec) || ENV["RSPEC_RUNNING"] || $stdin.nil? || !$stdin.tty?)
|
|
30
|
+
# - STDIN not a TTY (captured by PTY/tmux harness or test environment)
|
|
31
|
+
@headless = !!(tty.nil? || !tty.tty?)
|
|
33
32
|
|
|
34
33
|
@current_mode = nil
|
|
35
34
|
@workflow_active = false
|
|
@@ -2052,17 +2052,22 @@ module Aidp
|
|
|
2052
2052
|
# ============================================================================
|
|
2053
2053
|
|
|
2054
2054
|
# Start the control interface
|
|
2055
|
-
def start_control_interface
|
|
2055
|
+
def start_control_interface(async_control: true)
|
|
2056
2056
|
return unless @control_interface_enabled
|
|
2057
2057
|
|
|
2058
2058
|
@control_mutex.synchronize do
|
|
2059
2059
|
return if @control_future&.pending?
|
|
2060
2060
|
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2061
|
+
if async_control
|
|
2062
|
+
begin
|
|
2063
|
+
require "concurrent"
|
|
2064
|
+
@control_future = Concurrent::Future.execute { control_interface_loop }
|
|
2065
|
+
rescue LoadError
|
|
2066
|
+
# Fallback: run a single synchronous loop iteration if concurrent not available
|
|
2067
|
+
control_interface_loop_iteration
|
|
2068
|
+
end
|
|
2069
|
+
else
|
|
2070
|
+
control_interface_loop_iteration
|
|
2066
2071
|
end
|
|
2067
2072
|
end
|
|
2068
2073
|
|
|
@@ -17,12 +17,18 @@ module Aidp
|
|
|
17
17
|
|
|
18
18
|
attr_reader :project_dir, :jobs_dir
|
|
19
19
|
|
|
20
|
-
def initialize(project_dir = Dir.pwd)
|
|
20
|
+
def initialize(project_dir = Dir.pwd, suppress_display: false)
|
|
21
21
|
@project_dir = project_dir
|
|
22
22
|
@jobs_dir = File.join(project_dir, ".aidp", "jobs")
|
|
23
|
+
@suppress_display = suppress_display
|
|
23
24
|
ensure_jobs_directory
|
|
24
25
|
end
|
|
25
26
|
|
|
27
|
+
def display_message(msg, type: :info)
|
|
28
|
+
return if @suppress_display
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
26
32
|
# Start a background job
|
|
27
33
|
# Returns job_id
|
|
28
34
|
def start(mode, options = {})
|
data/lib/aidp/logger.rb
CHANGED
|
@@ -114,7 +114,7 @@ module Aidp
|
|
|
114
114
|
logger
|
|
115
115
|
rescue => e
|
|
116
116
|
# Fall back to STDERR if file logging fails
|
|
117
|
-
warn "[AIDP Logger] Failed to create log file at #{path}: #{e.message}. Falling back to STDERR."
|
|
117
|
+
Kernel.warn "[AIDP Logger] Failed to create log file at #{path}: #{e.message}. Falling back to STDERR."
|
|
118
118
|
logger = ::Logger.new($stderr)
|
|
119
119
|
logger.level = ::Logger::DEBUG
|
|
120
120
|
logger.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
|
data/lib/aidp/message_display.rb
CHANGED
|
@@ -39,9 +39,16 @@ module Aidp
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
module ClassMethods
|
|
42
|
-
# Class-level display helper (
|
|
42
|
+
# Class-level display helper (uses fresh prompt to respect $stdout changes)
|
|
43
43
|
def display_message(message, type: :info)
|
|
44
|
-
|
|
44
|
+
class_message_display_prompt.say(message, color: COLOR_MAP.fetch(type, :white))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# Don't memoize - create fresh prompt each time to respect $stdout redirection in tests
|
|
50
|
+
def class_message_display_prompt
|
|
51
|
+
TTY::Prompt.new
|
|
45
52
|
end
|
|
46
53
|
end
|
|
47
54
|
end
|
data/lib/aidp/providers/base.rb
CHANGED
|
@@ -71,8 +71,8 @@ module Aidp
|
|
|
71
71
|
name
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
def
|
|
75
|
-
raise NotImplementedError, "#{self.class} must implement #
|
|
74
|
+
def send_message(prompt:, session: nil)
|
|
75
|
+
raise NotImplementedError, "#{self.class} must implement #send_message"
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# Fetch MCP servers configured for this provider
|
|
@@ -279,8 +279,8 @@ module Aidp
|
|
|
279
279
|
error_message = nil
|
|
280
280
|
|
|
281
281
|
begin
|
|
282
|
-
# Call the original
|
|
283
|
-
result =
|
|
282
|
+
# Call the original send_message method
|
|
283
|
+
result = send_message(prompt: prompt, session: session)
|
|
284
284
|
success = true
|
|
285
285
|
|
|
286
286
|
# Extract token usage and cost if available
|
data/lib/aidp/providers/codex.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Aidp
|
|
|
16
16
|
"macos"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def
|
|
19
|
+
def send_message(prompt:, session: nil)
|
|
20
20
|
raise "macOS UI not available on this platform" unless self.class.available?
|
|
21
21
|
|
|
22
22
|
debug_provider("macos", "Starting Cursor interaction", {prompt_length: prompt.length})
|
data/lib/aidp/version.rb
CHANGED
|
@@ -67,7 +67,7 @@ module Aidp
|
|
|
67
67
|
|
|
68
68
|
def generate_with_provider(provider, issue)
|
|
69
69
|
payload = build_prompt(issue)
|
|
70
|
-
response = provider.
|
|
70
|
+
response = provider.send_message(prompt: payload)
|
|
71
71
|
parsed = parse_structured_response(response)
|
|
72
72
|
|
|
73
73
|
return parsed if parsed
|
|
@@ -11,6 +11,16 @@ module Aidp
|
|
|
11
11
|
# (works for private repositories) and falls back to public REST endpoints
|
|
12
12
|
# when the CLI is unavailable.
|
|
13
13
|
class RepositoryClient
|
|
14
|
+
# Binary availability checker for testing
|
|
15
|
+
class BinaryChecker
|
|
16
|
+
def gh_cli_available?
|
|
17
|
+
_stdout, _stderr, status = Open3.capture3("gh", "--version")
|
|
18
|
+
status.success?
|
|
19
|
+
rescue Errno::ENOENT
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
14
24
|
attr_reader :owner, :repo
|
|
15
25
|
|
|
16
26
|
def self.parse_issues_url(issues_url)
|
|
@@ -24,10 +34,11 @@ module Aidp
|
|
|
24
34
|
end
|
|
25
35
|
end
|
|
26
36
|
|
|
27
|
-
def initialize(owner:, repo:, gh_available: nil)
|
|
37
|
+
def initialize(owner:, repo:, gh_available: nil, binary_checker: BinaryChecker.new)
|
|
28
38
|
@owner = owner
|
|
29
39
|
@repo = repo
|
|
30
|
-
@
|
|
40
|
+
@binary_checker = binary_checker
|
|
41
|
+
@gh_available = gh_available.nil? ? @binary_checker.gh_cli_available? : gh_available
|
|
31
42
|
end
|
|
32
43
|
|
|
33
44
|
def gh_available?
|
|
@@ -56,13 +67,6 @@ module Aidp
|
|
|
56
67
|
|
|
57
68
|
private
|
|
58
69
|
|
|
59
|
-
def gh_cli_available?
|
|
60
|
-
_stdout, _stderr, status = Open3.capture3("gh", "--version")
|
|
61
|
-
status.success?
|
|
62
|
-
rescue Errno::ENOENT
|
|
63
|
-
false
|
|
64
|
-
end
|
|
65
|
-
|
|
66
70
|
def list_issues_via_gh(labels:, state:)
|
|
67
71
|
json_fields = %w[number title labels updatedAt state url assignees]
|
|
68
72
|
cmd = ["gh", "issue", "list", "--repo", full_repo, "--state", state, "--json", json_fields.join(",")]
|