aidp 0.17.0 → 0.18.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 +69 -0
- 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 +64 -29
- data/lib/aidp/config.rb +9 -14
- data/lib/aidp/execute/progress.rb +5 -8
- data/lib/aidp/execute/prompt_manager.rb +128 -1
- data/lib/aidp/execute/repl_macros.rb +555 -0
- data/lib/aidp/execute/work_loop_runner.rb +108 -1
- data/lib/aidp/harness/ai_decision_engine.rb +376 -0
- data/lib/aidp/harness/capability_registry.rb +273 -0
- data/lib/aidp/harness/config_loader.rb +2 -2
- data/lib/aidp/harness/config_schema.rb +305 -1
- data/lib/aidp/harness/configuration.rb +452 -0
- data/lib/aidp/harness/enhanced_runner.rb +23 -8
- data/lib/aidp/harness/error_handler.rb +12 -5
- data/lib/aidp/harness/provider_factory.rb +0 -2
- data/lib/aidp/harness/provider_manager.rb +4 -19
- data/lib/aidp/harness/runner.rb +9 -3
- 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/thinking_depth_manager.rb +335 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +3 -4
- data/lib/aidp/harness/user_interface.rb +11 -6
- data/lib/aidp/harness/zfc_condition_detector.rb +395 -0
- data/lib/aidp/init/devcontainer_generator.rb +274 -0
- data/lib/aidp/init/runner.rb +37 -10
- data/lib/aidp/init.rb +1 -0
- 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/prompt_optimization/context_composer.rb +286 -0
- data/lib/aidp/prompt_optimization/optimizer.rb +335 -0
- data/lib/aidp/prompt_optimization/prompt_builder.rb +309 -0
- data/lib/aidp/prompt_optimization/relevance_scorer.rb +256 -0
- data/lib/aidp/prompt_optimization/source_code_fragmenter.rb +308 -0
- data/lib/aidp/prompt_optimization/style_guide_indexer.rb +240 -0
- data/lib/aidp/prompt_optimization/template_indexer.rb +250 -0
- data/lib/aidp/provider_manager.rb +0 -2
- data/lib/aidp/providers/anthropic.rb +20 -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/opencode.rb +1 -1
- data/lib/aidp/setup/wizard.rb +299 -4
- data/lib/aidp/skills/wizard/prompter.rb +2 -2
- data/lib/aidp/utils/devcontainer_detector.rb +166 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +72 -6
- data/lib/aidp/watch/plan_generator.rb +1 -1
- data/lib/aidp/watch/repository_client.rb +15 -10
- data/lib/aidp/workflows/guided_agent.rb +2 -312
- data/lib/aidp/workstream_executor.rb +8 -2
- data/lib/aidp.rb +0 -1
- data/templates/aidp.yml.example +128 -0
- metadata +14 -2
- data/lib/aidp/providers/macos_ui.rb +0 -102
|
@@ -8,22 +8,25 @@ module Aidp
|
|
|
8
8
|
module State
|
|
9
9
|
# Handles file I/O and persistence for state management
|
|
10
10
|
class Persistence
|
|
11
|
-
def initialize(project_dir, mode)
|
|
11
|
+
def initialize(project_dir, mode, skip_persistence: false)
|
|
12
12
|
@project_dir = project_dir
|
|
13
13
|
@mode = mode
|
|
14
14
|
@state_dir = File.join(project_dir, ".aidp", "harness")
|
|
15
15
|
@state_file = File.join(@state_dir, "#{mode}_state.json")
|
|
16
16
|
@lock_file = File.join(@state_dir, "#{mode}_state.lock")
|
|
17
|
+
# Use explicit skip_persistence flag for dependency injection
|
|
18
|
+
# Callers should set skip_persistence: true for test/dry-run scenarios
|
|
19
|
+
@skip_persistence = skip_persistence
|
|
17
20
|
ensure_state_directory
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def has_state?
|
|
21
|
-
return false if
|
|
24
|
+
return false if @skip_persistence
|
|
22
25
|
File.exist?(@state_file)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def load_state
|
|
26
|
-
return {} if
|
|
29
|
+
return {} if @skip_persistence || !has_state?
|
|
27
30
|
|
|
28
31
|
with_lock do
|
|
29
32
|
content = File.read(@state_file)
|
|
@@ -35,7 +38,7 @@ module Aidp
|
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
def save_state(state_data)
|
|
38
|
-
return if
|
|
41
|
+
return if @skip_persistence
|
|
39
42
|
|
|
40
43
|
with_lock do
|
|
41
44
|
state_with_metadata = add_metadata(state_data)
|
|
@@ -44,7 +47,7 @@ module Aidp
|
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
def clear_state
|
|
47
|
-
return if
|
|
50
|
+
return if @skip_persistence
|
|
48
51
|
|
|
49
52
|
with_lock do
|
|
50
53
|
File.delete(@state_file) if File.exist?(@state_file)
|
|
@@ -53,10 +56,6 @@ module Aidp
|
|
|
53
56
|
|
|
54
57
|
private
|
|
55
58
|
|
|
56
|
-
def test_mode?
|
|
57
|
-
ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
59
|
def add_metadata(state_data)
|
|
61
60
|
state_data.merge(
|
|
62
61
|
mode: @mode,
|
|
@@ -76,7 +75,7 @@ module Aidp
|
|
|
76
75
|
end
|
|
77
76
|
|
|
78
77
|
def with_lock(&block)
|
|
79
|
-
return yield if
|
|
78
|
+
return yield if @skip_persistence
|
|
80
79
|
|
|
81
80
|
acquire_lock_with_timeout(&block)
|
|
82
81
|
ensure
|
|
@@ -10,11 +10,12 @@ module Aidp
|
|
|
10
10
|
module State
|
|
11
11
|
# Manages workflow-specific state and progress tracking
|
|
12
12
|
class WorkflowState
|
|
13
|
-
def initialize(persistence, project_dir, mode)
|
|
13
|
+
def initialize(persistence, project_dir, mode, progress_tracker_factory: nil)
|
|
14
14
|
@persistence = persistence
|
|
15
15
|
@project_dir = project_dir
|
|
16
16
|
@mode = mode
|
|
17
|
-
@
|
|
17
|
+
@progress_tracker_factory = progress_tracker_factory
|
|
18
|
+
@progress_tracker = @progress_tracker_factory ? @progress_tracker_factory.call : create_progress_tracker
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def completed_steps
|
|
@@ -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
|
|