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
@@ -209,6 +209,86 @@ module Aidp
209
209
  guards_config[:bypass] == true
210
210
  end
211
211
 
212
+ # Get version control configuration
213
+ def version_control_config
214
+ work_loop_config[:version_control] || default_version_control_config
215
+ end
216
+
217
+ # Get VCS tool
218
+ def vcs_tool
219
+ version_control_config[:tool]
220
+ end
221
+
222
+ # Get VCS behavior (stage/commit/nothing)
223
+ def vcs_behavior
224
+ version_control_config[:behavior]
225
+ end
226
+
227
+ # Check if conventional commits are enabled
228
+ def conventional_commits?
229
+ version_control_config[:conventional_commits] == true
230
+ end
231
+
232
+ # Get coverage configuration
233
+ def coverage_config
234
+ work_loop_config[:coverage] || default_coverage_config
235
+ end
236
+
237
+ # Check if coverage is enabled
238
+ def coverage_enabled?
239
+ coverage_config[:enabled] == true
240
+ end
241
+
242
+ # Get coverage tool
243
+ def coverage_tool
244
+ coverage_config[:tool]
245
+ end
246
+
247
+ # Get coverage run command
248
+ def coverage_run_command
249
+ coverage_config[:run_command]
250
+ end
251
+
252
+ # Get coverage report paths
253
+ def coverage_report_paths
254
+ coverage_config[:report_paths] || []
255
+ end
256
+
257
+ # Check if should fail on coverage drop
258
+ def coverage_fail_on_drop?
259
+ coverage_config[:fail_on_drop] == true
260
+ end
261
+
262
+ # Get minimum coverage threshold
263
+ def coverage_minimum
264
+ coverage_config[:minimum_coverage]
265
+ end
266
+
267
+ # Get interactive testing configuration
268
+ def interactive_testing_config
269
+ work_loop_config[:interactive_testing] || default_interactive_testing_config
270
+ end
271
+
272
+ # Check if interactive testing is enabled
273
+ def interactive_testing_enabled?
274
+ interactive_testing_config[:enabled] == true
275
+ end
276
+
277
+ # Get interactive testing app type
278
+ def interactive_testing_app_type
279
+ interactive_testing_config[:app_type]
280
+ end
281
+
282
+ # Get interactive testing tools configuration
283
+ def interactive_testing_tools
284
+ interactive_testing_config[:tools] || {}
285
+ end
286
+
287
+ # Get model family for a provider
288
+ def model_family(provider_name)
289
+ provider_config(provider_name)[:model_family] || "auto"
290
+ end
291
+
212
292
  # Get provider priority
213
293
  def provider_priority(provider_name)
214
294
  provider_config(provider_name)[:priority] || 0
@@ -259,6 +339,62 @@ module Aidp
259
339
  harness_config[:logging] || default_logging_config
260
340
  end
261
341
 
342
+ # Get thinking depth configuration
343
+ def thinking_config
344
+ @config[:thinking] || default_thinking_config
345
+ end
346
+
347
+ # Get default thinking tier
348
+ def default_tier
349
+ thinking_config[:default_tier] || "standard"
350
+ end
351
+
352
+ # Get maximum thinking tier
353
+ def max_tier
354
+ thinking_config[:max_tier] || "standard"
355
+ end
356
+
357
+ # Check if provider switching for tier is allowed
358
+ def allow_provider_switch_for_tier?
359
+ thinking_config[:allow_provider_switch] != false
360
+ end
361
+
362
+ # Get escalation configuration
363
+ def escalation_config
364
+ thinking_config[:escalation] || default_escalation_config
365
+ end
366
+
367
+ # Get fail attempts threshold for escalation
368
+ def escalation_fail_attempts
369
+ escalation_config[:on_fail_attempts] || 2
370
+ end
371
+
372
+ # Get complexity threshold configuration for escalation
373
+ def escalation_complexity_threshold
374
+ escalation_config[:on_complexity_threshold] || {}
375
+ end
376
+
377
+ # Get permissions by tier configuration
378
+ def permissions_by_tier
379
+ thinking_config[:permissions_by_tier] || {}
380
+ end
381
+
382
+ # Get permission level for a tier
383
+ def permission_for_tier(tier)
384
+ permissions_by_tier[tier] || permissions_by_tier[tier.to_sym] || "tools"
385
+ end
386
+
387
+ # Get thinking tier overrides
388
+ def thinking_overrides
389
+ thinking_config[:overrides] || {}
390
+ end
391
+
392
+ # Get tier override for a skill or template
393
+ # @param key [String] skill or template key (e.g., "skill.generate_tests", "template.large_refactor")
394
+ def tier_override_for(key)
395
+ thinking_overrides[key] || thinking_overrides[key.to_sym]
396
+ end
397
+
262
398
  # Get fallback configuration
263
399
  def fallback_config
264
400
  harness_config[:fallback] || default_fallback_config
@@ -341,6 +477,209 @@ module Aidp
341
477
  errors
342
478
  end
343
479
 
480
+ # Check if auto-escalation is enabled
481
+ def auto_escalate?
482
+ thinking_config[:auto_escalate] != false
483
+ end
484
+
485
+ # Get escalation threshold
486
+ def escalation_threshold
487
+ thinking_config[:escalation_threshold] || 2
488
+ end
489
+
490
+ # Get tier overrides
491
+ def tier_overrides
492
+ thinking_config[:overrides] || {}
493
+ end
494
+
495
+ # Get ZFC configuration
496
+ def zfc_config
497
+ @config[:zfc] || default_zfc_config
498
+ end
499
+
500
+ # Check if ZFC is enabled
501
+ def zfc_enabled?
502
+ zfc_config[:enabled] == true
503
+ end
504
+
505
+ # Check if ZFC should fallback to legacy on failure
506
+ def zfc_fallback_to_legacy?
507
+ zfc_config[:fallback_to_legacy] != false
508
+ end
509
+
510
+ # Get ZFC decision configuration
511
+ def zfc_decision_config(decision_type)
512
+ zfc_config.dig(:decisions, decision_type.to_sym) || {}
513
+ end
514
+
515
+ # Check if specific ZFC decision type is enabled
516
+ def zfc_decision_enabled?(decision_type)
517
+ return false unless zfc_enabled?
518
+ decision_config = zfc_decision_config(decision_type)
519
+ decision_config[:enabled] == true
520
+ end
521
+
522
+ # Get ZFC decision tier
523
+ def zfc_decision_tier(decision_type)
524
+ zfc_decision_config(decision_type)[:tier] || "mini"
525
+ end
526
+
527
+ # Get ZFC decision cache TTL
528
+ def zfc_decision_cache_ttl(decision_type)
529
+ zfc_decision_config(decision_type)[:cache_ttl]
530
+ end
531
+
532
+ # Get ZFC decision confidence threshold
533
+ def zfc_decision_confidence_threshold(decision_type)
534
+ zfc_decision_config(decision_type)[:confidence_threshold] || 0.7
535
+ end
536
+
537
+ # Get ZFC cost limits
538
+ def zfc_cost_limits
539
+ zfc_config[:cost_limits] || default_zfc_cost_limits
540
+ end
541
+
542
+ # Get ZFC A/B testing configuration
543
+ def zfc_ab_testing_config
544
+ zfc_config[:ab_testing] || default_zfc_ab_testing_config
545
+ end
546
+
547
+ # Check if ZFC A/B testing is enabled
548
+ def zfc_ab_testing_enabled?
549
+ zfc_ab_testing_config[:enabled] == true
550
+ end
551
+
552
+ # Prompt optimization configuration methods
553
+
554
+ # Get prompt optimization configuration
555
+ def prompt_optimization_config
556
+ @config[:prompt_optimization] || default_prompt_optimization_config
557
+ end
558
+
559
+ # Check if prompt optimization is enabled
560
+ def prompt_optimization_enabled?
561
+ prompt_optimization_config[:enabled] == true
562
+ end
563
+
564
+ # Get maximum tokens for prompt
565
+ def prompt_max_tokens
566
+ prompt_optimization_config[:max_tokens] || 16000
567
+ end
568
+
569
+ # Get include threshold configuration
570
+ def prompt_include_thresholds
571
+ prompt_optimization_config[:include_threshold] || default_include_thresholds
572
+ end
573
+
574
+ # Get style guide include threshold
575
+ def prompt_style_guide_threshold
576
+ prompt_include_thresholds[:style_guide] || 0.75
577
+ end
578
+
579
+ # Get templates include threshold
580
+ def prompt_templates_threshold
581
+ prompt_include_thresholds[:templates] || 0.8
582
+ end
583
+
584
+ # Get source code include threshold
585
+ def prompt_source_threshold
586
+ prompt_include_thresholds[:source] || 0.7
587
+ end
588
+
589
+ # Check if dynamic adjustment is enabled
590
+ def prompt_dynamic_adjustment?
591
+ prompt_optimization_config[:dynamic_adjustment] != false
592
+ end
593
+
594
+ # Check if fragment logging is enabled
595
+ def prompt_log_fragments?
596
+ prompt_optimization_config[:log_selected_fragments] == true
597
+ end
598
+
599
+ # Devcontainer configuration methods
600
+
601
+ # Get devcontainer configuration
602
+ def devcontainer_config
603
+ @config[:devcontainer] || default_devcontainer_config
604
+ end
605
+
606
+ # Check if devcontainer features are enabled
607
+ def devcontainer_enabled?
608
+ devcontainer_config[:enabled] != false
609
+ end
610
+
611
+ # Check if full permissions should be granted in devcontainer
612
+ def full_permissions_in_devcontainer?
613
+ devcontainer_config[:full_permissions_when_in_devcontainer] == true
614
+ end
615
+
616
+ # Get forced detection value (nil for auto-detection)
617
+ def devcontainer_force_detection
618
+ devcontainer_config[:force_detection]
619
+ end
620
+
621
+ # Check if currently in devcontainer (with optional force override)
622
+ def in_devcontainer?
623
+ forced = devcontainer_force_detection
624
+ return forced unless forced.nil?
625
+
626
+ require_relative "../utils/devcontainer_detector"
627
+ Aidp::Utils::DevcontainerDetector.in_devcontainer?
628
+ end
629
+
630
+ # Get devcontainer permissions config
631
+ def devcontainer_permissions
632
+ devcontainer_config[:permissions] || {}
633
+ end
634
+
635
+ # Check if dangerous filesystem operations are allowed in devcontainer
636
+ def devcontainer_dangerous_ops_allowed?
637
+ devcontainer_permissions[:dangerous_filesystem_ops] == true
638
+ end
639
+
640
+ # Get list of providers that should skip permission checks in devcontainer
641
+ def devcontainer_skip_permission_checks
642
+ devcontainer_permissions[:skip_permission_checks] || []
643
+ end
644
+
645
+ # Check if a specific provider should skip permission checks in devcontainer
646
+ def devcontainer_skip_permissions_for?(provider_name)
647
+ devcontainer_skip_permission_checks.include?(provider_name.to_s)
648
+ end
649
+
650
+ # Get devcontainer settings
651
+ def devcontainer_settings
652
+ devcontainer_config[:settings] || {}
653
+ end
654
+
655
+ # Get timeout multiplier for devcontainer
656
+ def devcontainer_timeout_multiplier
657
+ devcontainer_settings[:timeout_multiplier] || 1.0
658
+ end
659
+
660
+ # Check if verbose logging is enabled in devcontainer
661
+ def devcontainer_verbose_logging?
662
+ devcontainer_settings[:verbose_logging] == true
663
+ end
664
+
665
+ # Get allowed domains for devcontainer firewall
666
+ def devcontainer_allowed_domains
667
+ devcontainer_settings[:allowed_domains] || []
668
+ end
669
+
670
+ # Check if provider should run with full permissions
671
+ # Combines devcontainer detection with configuration
672
+ def should_use_full_permissions?(provider_name)
673
+ return false unless devcontainer_enabled?
674
+ return false unless in_devcontainer?
675
+
676
+ # Check if full permissions are globally enabled for devcontainer
677
+ return true if full_permissions_in_devcontainer?
678
+
679
+ # Check if this specific provider should skip permissions
680
+ devcontainer_skip_permissions_for?(provider_name)
681
+ end
682
+
344
683
  private
345
684
 
346
685
  def validate_configuration!
@@ -557,6 +896,53 @@ module Aidp
557
896
  }
558
897
  end
559
898
 
899
+ def default_version_control_config
900
+ {
901
+ tool: "git",
902
+ behavior: "nothing",
903
+ conventional_commits: false
904
+ }
905
+ end
906
+
907
+ def default_coverage_config
908
+ {
909
+ enabled: false,
910
+ tool: nil,
911
+ run_command: nil,
912
+ report_paths: [],
913
+ fail_on_drop: false,
914
+ minimum_coverage: nil
915
+ }
916
+ end
917
+
918
+ def default_interactive_testing_config
919
+ {
920
+ enabled: false,
921
+ app_type: "web",
922
+ tools: {}
923
+ }
924
+ end
925
+
926
+ def default_thinking_config
927
+ {
928
+ default_tier: "mini", # Use mini tier by default for cost optimization
929
+ max_tier: "max",
930
+ allow_provider_switch: true,
931
+ auto_escalate: true,
932
+ escalation_threshold: 2,
933
+ escalation: default_escalation_config,
934
+ permissions_by_tier: {},
935
+ overrides: {}
936
+ }
937
+ end
938
+
939
+ def default_escalation_config
940
+ {
941
+ on_fail_attempts: 2,
942
+ on_complexity_threshold: {}
943
+ }
944
+ end
945
+
560
946
  def default_logging_config
561
947
  {
562
948
  log_level: :info,
@@ -613,6 +999,72 @@ module Aidp
613
999
  }
614
1000
  end
615
1001
 
1002
+ # Default ZFC configuration
1003
+ def default_zfc_config
1004
+ {
1005
+ enabled: false, # Experimental feature - disabled by default
1006
+ fallback_to_legacy: true,
1007
+ decisions: {},
1008
+ cost_limits: default_zfc_cost_limits,
1009
+ ab_testing: default_zfc_ab_testing_config
1010
+ }
1011
+ end
1012
+
1013
+ # Default ZFC cost limits
1014
+ def default_zfc_cost_limits
1015
+ {
1016
+ max_daily_cost: 5.00,
1017
+ max_cost_per_decision: 0.01,
1018
+ alert_threshold: 0.8
1019
+ }
1020
+ end
1021
+
1022
+ # Default ZFC A/B testing configuration
1023
+ def default_zfc_ab_testing_config
1024
+ {
1025
+ enabled: false,
1026
+ sample_rate: 0.1,
1027
+ log_comparisons: true
1028
+ }
1029
+ end
1030
+
1031
+ # Default prompt optimization configuration
1032
+ def default_prompt_optimization_config
1033
+ {
1034
+ enabled: false, # Experimental feature - disabled by default
1035
+ max_tokens: 16000,
1036
+ include_threshold: default_include_thresholds,
1037
+ dynamic_adjustment: true,
1038
+ log_selected_fragments: false
1039
+ }
1040
+ end
1041
+
1042
+ # Default include thresholds for prompt optimization
1043
+ def default_include_thresholds
1044
+ {
1045
+ style_guide: 0.75,
1046
+ templates: 0.8,
1047
+ source: 0.7
1048
+ }
1049
+ end
1050
+
1051
+ def default_devcontainer_config
1052
+ {
1053
+ enabled: true,
1054
+ full_permissions_when_in_devcontainer: false,
1055
+ force_detection: nil,
1056
+ permissions: {
1057
+ dangerous_filesystem_ops: false,
1058
+ skip_permission_checks: []
1059
+ },
1060
+ settings: {
1061
+ timeout_multiplier: 1.0,
1062
+ verbose_logging: false,
1063
+ allowed_domains: []
1064
+ }
1065
+ }
1066
+ end
1067
+
616
1068
  # Custom error class for configuration issues
617
1069
  class ConfigurationError < StandardError; end
618
1070
  end
@@ -22,10 +22,19 @@ module Aidp
22
22
  error: "error"
23
23
  }.freeze
24
24
 
25
- def initialize(project_dir, mode = :analyze, options = {})
25
+ # Simple sleeper abstraction for test control
26
+ class Sleeper
27
+ def sleep(duration)
28
+ Kernel.sleep(duration)
29
+ end
30
+ end
31
+
32
+ def initialize(project_dir, mode = :analyze, options = {}, prompt: TTY::Prompt.new, sleeper: Sleeper.new)
26
33
  @project_dir = project_dir
27
34
  @mode = mode.to_sym
28
35
  @options = options
36
+ @prompt = prompt
37
+ @sleeper = sleeper
29
38
  @state = STATES[:idle]
30
39
  @start_time = nil
31
40
  @current_step = nil
@@ -49,8 +58,14 @@ module Aidp
49
58
  # Initialize other components
50
59
  @configuration = Configuration.new(project_dir)
51
60
  @state_manager = StateManager.new(project_dir, @mode)
52
- @condition_detector = ConditionDetector.new
53
- @provider_manager = ProviderManager.new(@configuration, prompt: TTY::Prompt.new)
61
+ @provider_manager = ProviderManager.new(@configuration, prompt: @prompt)
62
+
63
+ # Use ZFC-enabled condition detector
64
+ # ZfcConditionDetector will create its own ProviderFactory if needed
65
+ # Falls back to legacy pattern matching when ZFC is disabled
66
+ require_relative "zfc_condition_detector"
67
+ @condition_detector = ZfcConditionDetector.new(@configuration)
68
+
54
69
  @error_handler = ErrorHandler.new(@provider_manager, @configuration)
55
70
  @completion_checker = CompletionChecker.new(@project_dir, @workflow_type)
56
71
  end
@@ -240,7 +255,7 @@ module Aidp
240
255
  # Remove job after a delay to show completion
241
256
  # UI delay to let user see completion status before removal
242
257
  Thread.new do
243
- sleep 2 # Acceptable for UI timing
258
+ @sleeper.sleep(2) # UI timing delay
244
259
  @tui.remove_job(step_job_id)
245
260
  end
246
261
 
@@ -349,9 +364,9 @@ module Aidp
349
364
  def get_mode_runner
350
365
  case @mode
351
366
  when :analyze
352
- Aidp::Analyze::Runner.new(@project_dir, self, prompt: TTY::Prompt.new)
367
+ Aidp::Analyze::Runner.new(@project_dir, self, prompt: @prompt)
353
368
  when :execute
354
- Aidp::Execute::Runner.new(@project_dir, self, prompt: TTY::Prompt.new)
369
+ Aidp::Execute::Runner.new(@project_dir, self, prompt: @prompt)
355
370
  else
356
371
  raise ArgumentError, "Unsupported mode: #{@mode}"
357
372
  end
@@ -376,7 +391,7 @@ module Aidp
376
391
  def handle_pause_condition
377
392
  case @state
378
393
  when STATES[:paused]
379
- sleep(1)
394
+ @sleeper.sleep(1)
380
395
  when STATES[:waiting_for_user]
381
396
  # User interface handles this
382
397
  nil
@@ -503,7 +518,7 @@ module Aidp
503
518
  while Time.now < reset_time && @state == STATES[:waiting_for_rate_limit]
504
519
  remaining = reset_time - Time.now
505
520
  @tui.show_message("⏳ Rate limit reset in #{remaining.to_i} seconds", :info)
506
- sleep(1)
521
+ @sleeper.sleep(1)
507
522
  end
508
523
  end
509
524
 
@@ -10,10 +10,19 @@ module Aidp
10
10
  class ErrorHandler
11
11
  include Aidp::DebugMixin
12
12
 
13
- def initialize(provider_manager, configuration, metrics_manager = nil)
13
+ # Simple wrapper to allow dependency injection of sleep behavior in tests
14
+ class Sleeper
15
+ def sleep(seconds)
16
+ Kernel.sleep(seconds)
17
+ end
18
+ end
19
+
20
+ # @param sleeper [#sleep] object responding to sleep(seconds); injectable for tests
21
+ def initialize(provider_manager, configuration, metrics_manager = nil, sleeper: nil)
14
22
  @provider_manager = provider_manager
15
23
  @configuration = configuration
16
24
  @metrics_manager = metrics_manager
25
+ @sleeper = sleeper || Sleeper.new
17
26
  @retry_strategies = {}
18
27
  @retry_counts = {}
19
28
  @error_history = []
@@ -112,7 +121,7 @@ module Aidp
112
121
  if should_retry?(error_info, strategy)
113
122
  delay = @backoff_calculator.calculate_delay(attempt, strategy[:backoff_strategy] || :exponential, 1, 10)
114
123
  debug_log("🔁 Retry attempt #{attempt} for #{current_provider}", level: :info, data: {delay: delay, error_type: error_info[:error_type]})
115
- sleep(delay) if delay > 0
124
+ @sleeper.sleep(delay) if delay > 0
116
125
  retry
117
126
  end
118
127
  end
@@ -191,9 +200,7 @@ module Aidp
191
200
  )
192
201
 
193
202
  # Wait for backoff delay
194
- if delay > 0
195
- sleep(delay)
196
- end
203
+ @sleeper.sleep(delay) if delay > 0
197
204
 
198
205
  # Execute the retry
199
206
  retry_result = execute_retry_attempt(error_info, strategy, context)
@@ -5,7 +5,6 @@ require_relative "../providers/base"
5
5
  require_relative "../providers/cursor"
6
6
  require_relative "../providers/anthropic"
7
7
  require_relative "../providers/gemini"
8
- require_relative "../providers/macos_ui"
9
8
  require_relative "../providers/opencode"
10
9
  require_relative "../providers/github_copilot"
11
10
  require_relative "../providers/codex"
@@ -19,7 +18,6 @@ module Aidp
19
18
  "anthropic" => Aidp::Providers::Anthropic,
20
19
  "claude" => Aidp::Providers::Anthropic,
21
20
  "gemini" => Aidp::Providers::Gemini,
22
- "macos" => Aidp::Providers::MacOSUI,
23
21
  "opencode" => Aidp::Providers::Opencode,
24
22
  "github_copilot" => Aidp::Providers::GithubCopilot,
25
23
  "codex" => Aidp::Providers::Codex
@@ -12,9 +12,10 @@ module Aidp
12
12
  include Aidp::MessageDisplay
13
13
  include Aidp::RescueLogging
14
14
 
15
- def initialize(configuration, prompt: TTY::Prompt.new)
15
+ def initialize(configuration, prompt: TTY::Prompt.new, binary_checker: Aidp::Util)
16
16
  @configuration = configuration
17
17
  @prompt = prompt
18
+ @binary_checker = binary_checker
18
19
  @current_provider = nil
19
20
  @current_model = nil
20
21
  @provider_history = []
@@ -1069,18 +1070,6 @@ module Aidp
1069
1070
  def provider_cli_available?(provider_name)
1070
1071
  normalized = normalize_provider_name(provider_name)
1071
1072
 
1072
- # Handle test environment overrides
1073
- if defined?(RSpec) || ENV["RSPEC_RUNNING"]
1074
- # Force claude to be missing for testing
1075
- if ENV["AIDP_FORCE_CLAUDE_MISSING"] == "1" && normalized == "claude"
1076
- return [false, "binary_missing"]
1077
- end
1078
- # Force claude to be available for testing
1079
- if ENV["AIDP_FORCE_CLAUDE_AVAILABLE"] == "1" && normalized == "claude"
1080
- return [true, "available"]
1081
- end
1082
- end
1083
-
1084
1073
  cache_key = "#{provider_name}:#{normalized}"
1085
1074
  cached = @binary_check_cache[cache_key]
1086
1075
  if cached && (Time.now - cached[:checked_at] < @binary_check_ttl)
@@ -1098,7 +1087,7 @@ module Aidp
1098
1087
  return [true, nil]
1099
1088
  end
1100
1089
  path = begin
1101
- Aidp::Util.which(binary)
1090
+ @binary_checker.which(binary)
1102
1091
  rescue => e
1103
1092
  log_rescue(e, component: "provider_manager", action: "locate_binary", fallback: nil, binary: binary)
1104
1093
  nil
@@ -1164,10 +1153,6 @@ module Aidp
1164
1153
  statuses = provider_health_status
1165
1154
  metrics = all_metrics
1166
1155
  configured = configured_providers
1167
- # Ensure fresh binary probe results in test mode so stubs of Aidp::Util.which take effect
1168
- if defined?(RSpec) || ENV["RSPEC_RUNNING"]
1169
- @binary_check_cache.clear
1170
- end
1171
1156
  rows_by_normalized = {}
1172
1157
  configured.each do |prov|
1173
1158
  # Temporarily hide macos provider until it's user-configurable
@@ -1384,7 +1369,7 @@ module Aidp
1384
1369
  @current_provider = provider_type
1385
1370
 
1386
1371
  # Execute the prompt with the provider
1387
- result = provider.send(prompt: prompt, session: nil)
1372
+ result = provider.send_message(prompt: prompt, session: nil)
1388
1373
 
1389
1374
  # Return structured result
1390
1375
  {
@@ -52,8 +52,14 @@ module Aidp
52
52
  # Initialize components
53
53
  @config_manager = ConfigManager.new(project_dir)
54
54
  @state_manager = StateManager.new(project_dir, @mode)
55
- @condition_detector = ConditionDetector.new
56
55
  @provider_manager = ProviderManager.new(@config_manager, prompt: @prompt)
56
+
57
+ # Use ZFC-enabled condition detector
58
+ # ZfcConditionDetector will create its own ProviderFactory if needed
59
+ # Falls back to legacy pattern matching when ZFC is disabled
60
+ require_relative "zfc_condition_detector"
61
+ @condition_detector = ZfcConditionDetector.new(@config_manager)
62
+
57
63
  @user_interface = SimpleUserInterface.new
58
64
  @error_handler = ErrorHandler.new(@provider_manager, @config_manager)
59
65
  @status_display = StatusDisplay.new
@@ -188,9 +194,9 @@ module Aidp
188
194
  def get_mode_runner
189
195
  case @mode
190
196
  when :analyze
191
- Aidp::Analyze::Runner.new(@project_dir, self, prompt: TTY::Prompt.new)
197
+ Aidp::Analyze::Runner.new(@project_dir, self, prompt: @prompt)
192
198
  when :execute
193
- Aidp::Execute::Runner.new(@project_dir, self, prompt: TTY::Prompt.new)
199
+ Aidp::Execute::Runner.new(@project_dir, self, prompt: @prompt)
194
200
  else
195
201
  raise ArgumentError, "Unsupported mode: #{@mode}"
196
202
  end