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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -0
  3. data/lib/aidp/analyze/kb_inspector.rb +2 -3
  4. data/lib/aidp/analyze/progress.rb +5 -10
  5. data/lib/aidp/cli/mcp_dashboard.rb +1 -1
  6. data/lib/aidp/cli.rb +64 -29
  7. data/lib/aidp/config.rb +9 -14
  8. data/lib/aidp/execute/progress.rb +5 -8
  9. data/lib/aidp/execute/prompt_manager.rb +128 -1
  10. data/lib/aidp/execute/repl_macros.rb +555 -0
  11. data/lib/aidp/execute/work_loop_runner.rb +108 -1
  12. data/lib/aidp/harness/ai_decision_engine.rb +376 -0
  13. data/lib/aidp/harness/capability_registry.rb +273 -0
  14. data/lib/aidp/harness/config_loader.rb +2 -2
  15. data/lib/aidp/harness/config_schema.rb +305 -1
  16. data/lib/aidp/harness/configuration.rb +452 -0
  17. data/lib/aidp/harness/enhanced_runner.rb +23 -8
  18. data/lib/aidp/harness/error_handler.rb +12 -5
  19. data/lib/aidp/harness/provider_factory.rb +0 -2
  20. data/lib/aidp/harness/provider_manager.rb +4 -19
  21. data/lib/aidp/harness/runner.rb +9 -3
  22. data/lib/aidp/harness/state/persistence.rb +9 -10
  23. data/lib/aidp/harness/state/workflow_state.rb +3 -2
  24. data/lib/aidp/harness/state_manager.rb +33 -97
  25. data/lib/aidp/harness/status_display.rb +22 -12
  26. data/lib/aidp/harness/thinking_depth_manager.rb +335 -0
  27. data/lib/aidp/harness/ui/enhanced_tui.rb +3 -4
  28. data/lib/aidp/harness/user_interface.rb +11 -6
  29. data/lib/aidp/harness/zfc_condition_detector.rb +395 -0
  30. data/lib/aidp/init/devcontainer_generator.rb +274 -0
  31. data/lib/aidp/init/runner.rb +37 -10
  32. data/lib/aidp/init.rb +1 -0
  33. data/lib/aidp/jobs/background_runner.rb +7 -1
  34. data/lib/aidp/logger.rb +1 -1
  35. data/lib/aidp/message_display.rb +9 -2
  36. data/lib/aidp/prompt_optimization/context_composer.rb +286 -0
  37. data/lib/aidp/prompt_optimization/optimizer.rb +335 -0
  38. data/lib/aidp/prompt_optimization/prompt_builder.rb +309 -0
  39. data/lib/aidp/prompt_optimization/relevance_scorer.rb +256 -0
  40. data/lib/aidp/prompt_optimization/source_code_fragmenter.rb +308 -0
  41. data/lib/aidp/prompt_optimization/style_guide_indexer.rb +240 -0
  42. data/lib/aidp/prompt_optimization/template_indexer.rb +250 -0
  43. data/lib/aidp/provider_manager.rb +0 -2
  44. data/lib/aidp/providers/anthropic.rb +20 -1
  45. data/lib/aidp/providers/base.rb +4 -4
  46. data/lib/aidp/providers/codex.rb +1 -1
  47. data/lib/aidp/providers/cursor.rb +1 -1
  48. data/lib/aidp/providers/gemini.rb +1 -1
  49. data/lib/aidp/providers/github_copilot.rb +1 -1
  50. data/lib/aidp/providers/opencode.rb +1 -1
  51. data/lib/aidp/setup/wizard.rb +299 -4
  52. data/lib/aidp/skills/wizard/prompter.rb +2 -2
  53. data/lib/aidp/utils/devcontainer_detector.rb +166 -0
  54. data/lib/aidp/version.rb +1 -1
  55. data/lib/aidp/watch/build_processor.rb +72 -6
  56. data/lib/aidp/watch/plan_generator.rb +1 -1
  57. data/lib/aidp/watch/repository_client.rb +15 -10
  58. data/lib/aidp/workflows/guided_agent.rb +2 -312
  59. data/lib/aidp/workstream_executor.rb +8 -2
  60. data/lib/aidp.rb +0 -1
  61. data/templates/aidp.yml.example +128 -0
  62. metadata +14 -2
  63. 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 test_mode?
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 test_mode? || !has_state?
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 test_mode?
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 test_mode?
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 test_mode?
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
- @progress_tracker = create_progress_tracker
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
- # In test mode, always return false to avoid file operations
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
- # In test mode, return empty state to avoid file locking issues
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
- # In test mode, skip file operations to avoid file locking issues
63
- if ENV["RACK_ENV"] == "test" || defined?(RSpec)
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
- # In test mode, skip file operations to avoid hanging
85
- return if ENV["RACK_ENV"] == "test" || defined?(RSpec)
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
- if ENV["RACK_ENV"] == "test" || defined?(RSpec)
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
- # In test mode, skip file locking to avoid concurrency issues
646
- if ENV["RACK_ENV"] == "test" || defined?(RSpec)
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 with Async for better concurrency
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
- unless lock_acquired
671
- raise "Could not acquire state lock within #{timeout} seconds"
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
- unless ENV["RACK_ENV"] == "test" || defined?(RSpec)
58
- require "concurrent"
59
- @status_future = Concurrent::Future.execute do
60
- while @running
61
- begin
62
- collect_status_data
63
- display_status
64
- check_alerts
65
- sleep(@update_interval)
66
- rescue => e
67
- handle_display_error(e)
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