aidp 0.33.0 → 0.34.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
  4. data/lib/aidp/cli/eval_command.rb +399 -0
  5. data/lib/aidp/cli/harness_command.rb +1 -1
  6. data/lib/aidp/cli/security_command.rb +416 -0
  7. data/lib/aidp/cli/tools_command.rb +6 -4
  8. data/lib/aidp/cli.rb +170 -3
  9. data/lib/aidp/concurrency/exec.rb +3 -0
  10. data/lib/aidp/config.rb +113 -0
  11. data/lib/aidp/config_paths.rb +20 -0
  12. data/lib/aidp/daemon/runner.rb +8 -4
  13. data/lib/aidp/errors.rb +134 -0
  14. data/lib/aidp/evaluations/context_capture.rb +205 -0
  15. data/lib/aidp/evaluations/evaluation_record.rb +114 -0
  16. data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
  17. data/lib/aidp/evaluations.rb +23 -0
  18. data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
  19. data/lib/aidp/execute/interactive_repl.rb +6 -2
  20. data/lib/aidp/execute/prompt_evaluator.rb +359 -0
  21. data/lib/aidp/execute/repl_macros.rb +100 -1
  22. data/lib/aidp/execute/work_loop_runner.rb +399 -47
  23. data/lib/aidp/execute/work_loop_state.rb +4 -1
  24. data/lib/aidp/execute/workflow_selector.rb +3 -0
  25. data/lib/aidp/harness/ai_decision_engine.rb +79 -0
  26. data/lib/aidp/harness/capability_registry.rb +2 -0
  27. data/lib/aidp/harness/condition_detector.rb +3 -0
  28. data/lib/aidp/harness/config_loader.rb +3 -0
  29. data/lib/aidp/harness/enhanced_runner.rb +14 -11
  30. data/lib/aidp/harness/error_handler.rb +3 -0
  31. data/lib/aidp/harness/provider_factory.rb +3 -0
  32. data/lib/aidp/harness/provider_manager.rb +6 -0
  33. data/lib/aidp/harness/runner.rb +5 -1
  34. data/lib/aidp/harness/state/persistence.rb +3 -0
  35. data/lib/aidp/harness/state_manager.rb +3 -0
  36. data/lib/aidp/harness/status_display.rb +28 -20
  37. data/lib/aidp/harness/thinking_depth_manager.rb +32 -32
  38. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
  39. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
  40. data/lib/aidp/harness/ui/error_handler.rb +3 -0
  41. data/lib/aidp/harness/ui/job_monitor.rb +4 -0
  42. data/lib/aidp/harness/ui/navigation/submenu.rb +2 -0
  43. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
  44. data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
  45. data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
  46. data/lib/aidp/harness/user_interface.rb +3 -0
  47. data/lib/aidp/loader.rb +2 -2
  48. data/lib/aidp/logger.rb +3 -0
  49. data/lib/aidp/message_display.rb +31 -0
  50. data/lib/aidp/pr_worktree_manager.rb +18 -6
  51. data/lib/aidp/provider_manager.rb +3 -0
  52. data/lib/aidp/providers/base.rb +2 -0
  53. data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
  54. data/lib/aidp/security/secrets_proxy.rb +328 -0
  55. data/lib/aidp/security/secrets_registry.rb +227 -0
  56. data/lib/aidp/security/trifecta_state.rb +220 -0
  57. data/lib/aidp/security/watch_mode_handler.rb +306 -0
  58. data/lib/aidp/security/work_loop_adapter.rb +277 -0
  59. data/lib/aidp/security.rb +56 -0
  60. data/lib/aidp/setup/wizard.rb +4 -2
  61. data/lib/aidp/version.rb +1 -1
  62. data/lib/aidp/watch/auto_merger.rb +274 -0
  63. data/lib/aidp/watch/auto_pr_processor.rb +125 -7
  64. data/lib/aidp/watch/build_processor.rb +16 -1
  65. data/lib/aidp/watch/change_request_processor.rb +680 -286
  66. data/lib/aidp/watch/ci_fix_processor.rb +262 -4
  67. data/lib/aidp/watch/feedback_collector.rb +191 -0
  68. data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
  69. data/lib/aidp/watch/implementation_verifier.rb +142 -1
  70. data/lib/aidp/watch/plan_generator.rb +70 -13
  71. data/lib/aidp/watch/plan_processor.rb +12 -5
  72. data/lib/aidp/watch/projects_processor.rb +286 -0
  73. data/lib/aidp/watch/repository_client.rb +861 -53
  74. data/lib/aidp/watch/review_processor.rb +33 -6
  75. data/lib/aidp/watch/runner.rb +51 -11
  76. data/lib/aidp/watch/state_store.rb +233 -0
  77. data/lib/aidp/watch/sub_issue_creator.rb +221 -0
  78. data/lib/aidp/workflows/guided_agent.rb +4 -0
  79. data/lib/aidp/workstream_executor.rb +3 -0
  80. data/lib/aidp/worktree.rb +61 -11
  81. data/lib/aidp/worktree_branch_manager.rb +347 -101
  82. data/templates/implementation/iterative_implementation.md +46 -3
  83. metadata +20 -1
@@ -187,6 +187,85 @@ module Aidp
187
187
  },
188
188
  default_tier: "mini",
189
189
  cache_ttl: nil
190
+ },
191
+
192
+ # FIX for issue #391: Prompt effectiveness evaluation for stuck work loops
193
+ prompt_evaluation: {
194
+ prompt_template: "{{prompt}}", # Custom prompt provided by caller
195
+ schema: {
196
+ type: "object",
197
+ properties: {
198
+ effective: {
199
+ type: "boolean",
200
+ description: "True if the prompt is likely to lead to completion"
201
+ },
202
+ issues: {
203
+ type: "array",
204
+ items: {type: "string"},
205
+ description: "Specific problems with the current prompt"
206
+ },
207
+ suggestions: {
208
+ type: "array",
209
+ items: {type: "string"},
210
+ description: "Actionable suggestions to improve effectiveness"
211
+ },
212
+ likely_blockers: {
213
+ type: "array",
214
+ items: {type: "string"},
215
+ description: "Potential blockers preventing progress"
216
+ },
217
+ confidence: {
218
+ type: "number",
219
+ minimum: 0.0,
220
+ maximum: 1.0,
221
+ description: "Confidence in this assessment"
222
+ }
223
+ },
224
+ required: ["effective", "issues", "suggestions", "confidence"]
225
+ },
226
+ default_tier: "mini",
227
+ cache_ttl: nil
228
+ },
229
+
230
+ # FIX for issue #391: Template improvement suggestions for AGD pattern
231
+ template_improvement: {
232
+ prompt_template: "{{prompt}}", # Custom prompt provided by caller
233
+ schema: {
234
+ type: "object",
235
+ properties: {
236
+ improved_sections: {
237
+ type: "array",
238
+ items: {
239
+ type: "object",
240
+ properties: {
241
+ section_name: {type: "string"},
242
+ original: {type: "string"},
243
+ improved: {type: "string"},
244
+ rationale: {type: "string"}
245
+ }
246
+ }
247
+ },
248
+ additional_sections: {
249
+ type: "array",
250
+ items: {
251
+ type: "object",
252
+ properties: {
253
+ section_name: {type: "string"},
254
+ content: {type: "string"},
255
+ rationale: {type: "string"}
256
+ }
257
+ }
258
+ },
259
+ completion_criteria_improvements: {
260
+ type: "array",
261
+ items: {type: "string"},
262
+ description: "Improvements to completion criteria definitions"
263
+ }
264
+ },
265
+ required: ["improved_sections", "completion_criteria_improvements"]
266
+ },
267
+ default_tier: "standard", # Use standard tier for thoughtful improvements
268
+ cache_ttl: nil
190
269
  }
191
270
  }.freeze
192
271
 
@@ -21,6 +21,8 @@ module Aidp
21
21
  }.freeze
22
22
 
23
23
  attr_reader :catalog_path
24
+ # Expose for testability
25
+ attr_reader :catalog_data
24
26
 
25
27
  def initialize(catalog_path: nil, root_dir: nil)
26
28
  @root_dir = root_dir || Dir.pwd
@@ -6,6 +6,9 @@ module Aidp
6
6
  module Harness
7
7
  # Detects run conditions (rate limits, user feedback, completion, errors)
8
8
  class ConditionDetector
9
+ # Expose patterns for testability
10
+ attr_reader :rate_limit_patterns, :user_feedback_patterns, :question_patterns, :reset_time_patterns
11
+
9
12
  def initialize
10
13
  # Enhanced rate limit patterns for different providers
11
14
  @rate_limit_patterns = {
@@ -9,6 +9,9 @@ module Aidp
9
9
  module Harness
10
10
  # Enhanced configuration loader for harness
11
11
  class ConfigLoader
12
+ # Expose for testability
13
+ attr_reader :validator
14
+
12
15
  def initialize(project_dir = Dir.pwd, validator: nil)
13
16
  @project_dir = project_dir
14
17
  @validator = validator || ConfigValidator.new(project_dir)
@@ -23,6 +23,15 @@ module Aidp
23
23
  error: "error"
24
24
  }.freeze
25
25
 
26
+ # Expose state for testability
27
+ attr_accessor :state, :current_step, :start_time
28
+ attr_writer :current_provider, :user_input, :execution_log
29
+ attr_accessor :mode, :project_dir, :selected_steps, :workflow_type
30
+ attr_reader :provider_manager, :sleeper
31
+ attr_writer :completion_checker, :workflow_controller, :error_handler
32
+ attr_accessor :condition_detector
33
+ attr_writer :state_manager, :configuration
34
+
26
35
  # Simple sleeper abstraction for test control
27
36
  class Sleeper
28
37
  def sleep(duration)
@@ -75,27 +84,21 @@ module Aidp
75
84
  @completion_checker = options[:completion_checker] || CompletionChecker.new(@project_dir, @workflow_type)
76
85
  end
77
86
 
78
- # Get current provider (delegate to provider manager)
87
+ # Get current provider (delegate to provider manager with fallback)
79
88
  def current_provider
80
89
  @current_provider || @provider_manager&.current_provider || "unknown"
81
90
  end
82
91
 
83
- # Get current step
84
- attr_reader :current_step
85
-
86
- # Get user input
92
+ # Get user input (with nil safety)
87
93
  def user_input
88
94
  @user_input || {}
89
95
  end
90
96
 
91
- # Get execution log
97
+ # Get execution log (with nil safety)
92
98
  def execution_log
93
99
  @execution_log || []
94
100
  end
95
101
 
96
- # Get provider manager
97
- attr_reader :provider_manager
98
-
99
102
  # Main execution method with enhanced TUI
100
103
  def run
101
104
  @state = STATES[:running]
@@ -354,7 +357,7 @@ module Aidp
354
357
  duration: @start_time ? Time.now - @start_time : 0,
355
358
  user_input_count: @user_input.size,
356
359
  execution_log_count: @execution_log.size,
357
- jobs_count: @tui.instance_variable_get(:@jobs).size
360
+ jobs_count: @tui.jobs.size
358
361
  }
359
362
  end
360
363
 
@@ -530,7 +533,7 @@ module Aidp
530
533
 
531
534
  def cleanup
532
535
  # Cleanup any remaining jobs
533
- @tui.instance_variable_get(:@jobs).keys.each do |job_id|
536
+ @tui.jobs.keys.each do |job_id|
534
537
  @tui.remove_job(job_id)
535
538
  end
536
539
  end
@@ -12,6 +12,9 @@ module Aidp
12
12
  class ErrorHandler
13
13
  include Aidp::DebugMixin
14
14
 
15
+ # Expose internal components for testability
16
+ attr_reader :retry_strategies, :backoff_calculator, :error_classifier, :recovery_planner
17
+
15
18
  # Simple wrapper to allow dependency injection of sleep behavior in tests
16
19
  class Sleeper
17
20
  def sleep(seconds)
@@ -15,6 +15,9 @@ module Aidp
15
15
  module Harness
16
16
  # Factory for creating configured provider instances
17
17
  class ProviderFactory
18
+ # Expose for testability
19
+ attr_reader :provider_instances, :provider_configs
20
+
18
21
  PROVIDER_CLASSES = {
19
22
  "cursor" => Aidp::Providers::Cursor,
20
23
  "anthropic" => Aidp::Providers::Anthropic,
@@ -13,6 +13,12 @@ module Aidp
13
13
  include Aidp::MessageDisplay
14
14
  include Aidp::RescueLogging
15
15
 
16
+ # Expose state for testability
17
+ attr_accessor :sticky_sessions, :load_balancing_enabled
18
+ attr_writer :current_model
19
+ attr_accessor :provider_health, :provider_metrics, :rate_limit_info
20
+ attr_reader :binary_check_cache, :binary_check_ttl
21
+
16
22
  def initialize(configuration, prompt: TTY::Prompt.new, binary_checker: Aidp::Util)
17
23
  @configuration = configuration
18
24
  @prompt = prompt
@@ -23,7 +23,11 @@ module Aidp
23
23
  }.freeze
24
24
 
25
25
  # Public accessors for testing and integration
26
- attr_reader :current_provider, :current_step, :user_input, :execution_log, :provider_manager, :clarification_questions
26
+ attr_reader :clarification_questions, :last_error
27
+ attr_accessor :current_provider, :current_step, :user_input, :execution_log, :provider_manager
28
+ attr_accessor :state, :mode, :project_dir, :configuration, :condition_detector
29
+ attr_accessor :state_manager, :user_interface, :error_handler, :status_display
30
+ attr_writer :completion_checker, :workflow_type, :non_interactive
27
31
 
28
32
  def initialize(project_dir, mode = :analyze, options = {})
29
33
  @project_dir = project_dir
@@ -11,6 +11,9 @@ module Aidp
11
11
  class Persistence
12
12
  include Aidp::SafeDirectory
13
13
 
14
+ # Expose for testability
15
+ attr_reader :state_file, :lock_file
16
+
14
17
  def initialize(project_dir, mode, skip_persistence: false)
15
18
  @project_dir = project_dir
16
19
  @mode = mode
@@ -14,6 +14,9 @@ module Aidp
14
14
  class StateManager
15
15
  include Aidp::SafeDirectory
16
16
 
17
+ # Expose for testability
18
+ attr_reader :project_dir, :mode
19
+
17
20
  def initialize(project_dir, mode, skip_persistence: false)
18
21
  @project_dir = project_dir
19
22
  @mode = mode
@@ -9,6 +9,17 @@ module Aidp
9
9
  class StatusDisplay
10
10
  include Aidp::MessageDisplay
11
11
 
12
+ # Expose state for testability
13
+ attr_accessor :current_step, :current_provider, :current_model
14
+ attr_accessor :start_time, :running
15
+ attr_accessor :performance_metrics, :error_summary
16
+ attr_accessor :provider_status, :token_usage, :circuit_breaker_status
17
+ attr_accessor :rate_limit_status, :recovery_status, :user_feedback_status
18
+ attr_accessor :work_completion_status, :display_config
19
+ attr_reader :status_formatter, :metrics_calculator, :alert_manager, :display_animator
20
+ # Internal status hash (separate from computed status_data method)
21
+ attr_accessor :internal_status_data
22
+
12
23
  def initialize(provider_manager = nil, metrics_manager = nil, circuit_breaker_manager = nil, error_logger = nil, prompt: TTY::Prompt.new)
13
24
  @provider_manager = provider_manager
14
25
  @metrics_manager = metrics_manager
@@ -27,6 +38,7 @@ module Aidp
27
38
  @update_interval = 2
28
39
  @last_update = Time.now
29
40
  @status_data = {}
41
+ @internal_status_data = @status_data # Alias for testability
30
42
  @performance_metrics = {}
31
43
  @error_summary = {}
32
44
  @provider_status = {}
@@ -167,16 +179,24 @@ module Aidp
167
179
  @error_summary[:last_updated] = Time.now
168
180
  end
169
181
 
170
- # Set display mode
171
- def display_mode(mode)
172
- @display_mode = mode
173
- @display_config[:mode] = mode
182
+ # Get or set display mode
183
+ def display_mode(mode = nil)
184
+ if mode.nil?
185
+ @display_mode
186
+ else
187
+ @display_mode = mode
188
+ @display_config[:mode] = mode
189
+ end
174
190
  end
175
191
 
176
- # Set update interval
177
- def update_interval(interval)
178
- @update_interval = interval
179
- @display_config[:update_interval] = interval
192
+ # Get or set update interval
193
+ def update_interval(interval = nil)
194
+ if interval.nil?
195
+ @update_interval
196
+ else
197
+ @update_interval = interval
198
+ @display_config[:update_interval] = interval
199
+ end
180
200
  end
181
201
 
182
202
  # Configure display settings
@@ -686,8 +706,6 @@ module Aidp
686
706
  }
687
707
  end
688
708
 
689
- attr_reader :provider_status
690
-
691
709
  def performance_status
692
710
  @performance_metrics
693
711
  end
@@ -696,20 +714,10 @@ module Aidp
696
714
  @error_summary
697
715
  end
698
716
 
699
- attr_reader :circuit_breaker_status
700
-
701
717
  def token_status
702
718
  @token_usage
703
719
  end
704
720
 
705
- attr_reader :rate_limit_status
706
-
707
- attr_reader :recovery_status
708
-
709
- attr_reader :user_feedback_status
710
-
711
- attr_reader :work_completion_status
712
-
713
721
  def alerts
714
722
  @alert_manager.active_alerts
715
723
  end
@@ -15,7 +15,7 @@ module Aidp
15
15
 
16
16
  def initialize(configuration, registry: nil, root_dir: nil)
17
17
  @configuration = configuration
18
- @registry = registry || CapabilityRegistry.new(root_dir: root_dir || configuration.instance_variable_get(:@project_dir))
18
+ @registry = registry || CapabilityRegistry.new(root_dir: root_dir || configuration.project_dir)
19
19
  @current_tier = nil
20
20
  @session_max_tier = nil
21
21
  @tier_history = []
@@ -243,50 +243,47 @@ module Aidp
243
243
  model: model_name)
244
244
  return [provider, model_name, model_data]
245
245
  end
246
-
247
- # If provider doesn't support tier and switching allowed, try others
248
- unless configuration.allow_provider_switch_for_tier?
249
- Aidp.log_warn("thinking_depth_manager", "Provider lacks tier in catalog, switching disabled",
250
- tier: tier,
251
- provider: provider)
252
- return nil
253
- end
254
- end
255
-
256
- # Try all providers in catalog
257
- if provider && !configuration.allow_provider_switch_for_tier?
258
- return nil
246
+ # Per issue #323: Don't return nil here - let fallback logic handle missing tiers
259
247
  end
260
248
 
261
- providers_to_try = provider ? [@registry.provider_names - [provider]].flatten : @registry.provider_names
249
+ # Try all providers in catalog if provider switching is allowed
250
+ if configuration.allow_provider_switch_for_tier?
251
+ providers_to_try = provider ? (@registry.provider_names - [provider]) : @registry.provider_names
262
252
 
263
- providers_to_try.each do |prov_name|
264
- model_name, model_data = @registry.best_model_for_tier(tier, prov_name)
265
- if model_name
266
- Aidp.log_info("thinking_depth_manager", "Selected model from catalog (alternate provider)",
267
- tier: tier,
268
- original_provider: provider,
269
- selected_provider: prov_name,
270
- model: model_name)
271
- return [prov_name, model_name, model_data]
253
+ providers_to_try.each do |prov_name|
254
+ model_name, model_data = @registry.best_model_for_tier(tier, prov_name)
255
+ if model_name
256
+ Aidp.log_info("thinking_depth_manager", "Selected model from catalog (alternate provider)",
257
+ tier: tier,
258
+ original_provider: provider,
259
+ selected_provider: prov_name,
260
+ model: model_name)
261
+ return [prov_name, model_name, model_data]
262
+ end
272
263
  end
273
264
  end
274
265
 
275
266
  # No model found for requested tier - try fallback to other tiers
276
- Aidp.log_warn("thinking_depth_manager", "No model found for requested tier, trying fallback",
267
+ # Per issue #323: fallback events log at debug level
268
+ Aidp.log_debug("thinking_depth_manager", "tier_not_found_trying_fallback",
277
269
  tier: tier,
278
270
  provider: provider)
279
271
 
280
272
  result = try_fallback_tiers(tier, provider)
281
273
 
282
- if provider_has_no_tiers && result.nil? && provider
283
- Aidp.log_info("thinking_depth_manager", "No configured tiers for provider, deferring to provider auto model selection",
274
+ # If no model found after fallback, defer to provider auto model selection
275
+ # This allows providers to select their own model when no explicit tier config exists
276
+ # Per issue #323: log at debug level, don't constrain model selection
277
+ if result.nil? && provider
278
+ Aidp.log_debug("thinking_depth_manager", "no_model_for_tier_deferring_to_provider",
284
279
  requested_tier: tier,
285
- provider: provider)
286
- return [provider, nil, {auto_model: true, reason: "provider_has_no_tiers"}]
280
+ provider: provider,
281
+ reason: provider_has_no_tiers ? "provider_has_no_tiers" : "tier_not_configured")
282
+ return [provider, nil, {auto_model: true, reason: provider_has_no_tiers ? "provider_has_no_tiers" : "tier_not_configured"}]
287
283
  end
288
284
 
289
285
  unless result
286
+ # This path should only be reached when no provider is specified
290
287
  # Enhanced error message with discovery hints
291
288
  display_enhanced_tier_error(tier, provider)
292
289
 
@@ -452,7 +449,8 @@ module Aidp
452
449
 
453
450
  if configured_models.any?
454
451
  model_name = configured_models.first
455
- Aidp.log_warn("thinking_depth_manager", "Falling back to different tier (from config)",
452
+ # Per issue #323: fallback events log at debug level
453
+ Aidp.log_debug("thinking_depth_manager", "tier_fallback_from_config",
456
454
  requested_tier: requested_tier,
457
455
  fallback_tier: fallback_tier,
458
456
  provider: provider,
@@ -466,7 +464,8 @@ module Aidp
466
464
  if provider
467
465
  model_name, model_data = @registry.best_model_for_tier(fallback_tier, provider)
468
466
  if model_name
469
- Aidp.log_warn("thinking_depth_manager", "Falling back to different tier (from catalog)",
467
+ # Per issue #323: fallback events log at debug level
468
+ Aidp.log_debug("thinking_depth_manager", "tier_fallback_from_catalog",
470
469
  requested_tier: requested_tier,
471
470
  fallback_tier: fallback_tier,
472
471
  provider: provider,
@@ -482,7 +481,8 @@ module Aidp
482
481
 
483
482
  model_name, model_data = @registry.best_model_for_tier(fallback_tier, prov_name)
484
483
  if model_name
485
- Aidp.log_warn("thinking_depth_manager", "Falling back to different tier and provider (from catalog)",
484
+ # Per issue #323: fallback events log at debug level
485
+ Aidp.log_debug("thinking_depth_manager", "tier_fallback_provider_switch",
486
486
  requested_tier: requested_tier,
487
487
  fallback_tier: fallback_tier,
488
488
  requested_provider: provider,
@@ -20,6 +20,10 @@ module Aidp
20
20
 
21
21
  class DisplayError < TUIError; end
22
22
 
23
+ # Expose state for testability
24
+ attr_accessor :headless, :current_mode, :workflow_active, :current_step
25
+ attr_reader :jobs
26
+
23
27
  def initialize(prompt: TTY::Prompt.new, tty: $stdin)
24
28
  @cursor = TTY::Cursor
25
29
  @screen = TTY::Screen
@@ -11,6 +11,10 @@ module Aidp
11
11
  class EnhancedWorkflowSelector
12
12
  class WorkflowError < StandardError; end
13
13
 
14
+ # Expose state for testability
15
+ attr_reader :tui, :project_dir, :user_input
16
+ attr_accessor :workflow_selector
17
+
14
18
  def initialize(tui = nil, project_dir: Dir.pwd)
15
19
  @tui = tui || EnhancedTUI.new
16
20
  @user_input = {}
@@ -18,6 +18,9 @@ module Aidp
18
18
 
19
19
  class InteractionError < UIError; end
20
20
 
21
+ # Expose for testability
22
+ attr_reader :logger
23
+
21
24
  def initialize(ui_components = {})
22
25
  @logger = ui_components[:logger] || default_logger
23
26
  @formatter = ui_components[:formatter] || ErrorFormatter.new
@@ -17,6 +17,10 @@ module Aidp
17
17
 
18
18
  class MonitorError < JobMonitorError; end
19
19
 
20
+ # Expose for testability
21
+ attr_accessor :monitor_thread
22
+ attr_reader :update_callbacks
23
+
20
24
  JOB_STATUSES = {
21
25
  pending: "Pending",
22
26
  running: "Running",
@@ -22,6 +22,8 @@ module Aidp
22
22
 
23
23
  attr_reader :title, :parent_menu
24
24
  attr_accessor :drill_down_enabled, :max_depth
25
+ # Expose current_level for testability
26
+ attr_accessor :current_level
25
27
 
26
28
  def add_submenu_item(item)
27
29
  validate_submenu_item(item)
@@ -17,6 +17,9 @@ module Aidp
17
17
 
18
18
  class SelectionError < WorkflowError; end
19
19
 
20
+ # Expose for testability
21
+ attr_reader :prompt
22
+
20
23
  WORKFLOW_MODES = {
21
24
  simple: {
22
25
  name: "Simple Mode",
@@ -132,6 +135,9 @@ module Aidp
132
135
 
133
136
  # Formats workflow selection display
134
137
  class WorkflowFormatter
138
+ # Expose for testability
139
+ attr_reader :pastel
140
+
135
141
  def initialize
136
142
  @pastel = Pastel.new
137
143
  end
@@ -11,6 +11,9 @@ module Aidp
11
11
  class SpinnerHelper
12
12
  class SpinnerError < StandardError; end
13
13
 
14
+ # Expose for testability
15
+ attr_accessor :active_spinners
16
+
14
17
  def initialize
15
18
  @pastel = Pastel.new
16
19
  @active_spinners = []
@@ -16,6 +16,9 @@ module Aidp
16
16
 
17
17
  class ControlError < WorkflowError; end
18
18
 
19
+ # Expose for testability
20
+ attr_accessor :pause_time, :control_thread
21
+
19
22
  WORKFLOW_STATES = {
20
23
  running: "Running",
21
24
  paused: "Paused",
@@ -9,6 +9,9 @@ module Aidp
9
9
  class UserInterface
10
10
  include Aidp::MessageDisplay
11
11
 
12
+ # Expose for testability
13
+ attr_reader :auto_confirm_defaults, :show_help_automatically, :verbose_mode, :file_selection_enabled
14
+
12
15
  def initialize(prompt: TTY::Prompt.new)
13
16
  @input_history = []
14
17
  @file_selection_enabled = false
data/lib/aidp/loader.rb CHANGED
@@ -21,10 +21,10 @@ module Aidp
21
21
  module Loader
22
22
  class << self
23
23
  # @return [Zeitwerk::Loader, nil] The configured loader instance
24
- attr_reader :loader
24
+ attr_accessor :loader
25
25
 
26
26
  # @return [Boolean] Whether reloading is enabled
27
- attr_reader :reloading_enabled
27
+ attr_accessor :reloading_enabled
28
28
 
29
29
  # Set up the Zeitwerk loader for AIDP
30
30
  #
data/lib/aidp/logger.rb CHANGED
@@ -307,6 +307,9 @@ module Aidp
307
307
 
308
308
  # Module-level logger accessor
309
309
  class << self
310
+ # Expose for testability
311
+ attr_writer :logger
312
+
310
313
  # Set up global logger instance
311
314
  def setup_logger(project_dir = Dir.pwd, config = {})
312
315
  @logger = Logger.new(project_dir, config)
@@ -8,6 +8,11 @@ module Aidp
8
8
  # include Aidp::MessageDisplay
9
9
  # display_message("Hello", type: :success)
10
10
  # Supports color types: :error, :success, :warning, :info, :highlight, :muted
11
+ #
12
+ # Quiet mode:
13
+ # When quiet mode is enabled (via CLI --quiet flag or setting quiet=true on instance),
14
+ # only :error, :warning, and :success messages are displayed. Info, highlight, and muted
15
+ # messages are suppressed to reduce output noise.
11
16
  module MessageDisplay
12
17
  COLOR_MAP = {
13
18
  error: :red,
@@ -19,12 +24,28 @@ module Aidp
19
24
  muted: :bright_black
20
25
  }.freeze
21
26
 
27
+ # Message types that are always shown even in quiet mode
28
+ CRITICAL_TYPES = %i[error warning warn success].freeze
29
+
22
30
  def self.included(base)
23
31
  base.extend(ClassMethods)
24
32
  end
25
33
 
34
+ # Check if quiet mode is enabled
35
+ # Priority: instance @quiet variable > CLI.last_options[:quiet]
36
+ def quiet_mode?
37
+ return @quiet if instance_variable_defined?(:@quiet) && !@quiet.nil?
38
+
39
+ Aidp::CLI.last_options&.dig(:quiet) || false
40
+ rescue
41
+ false
42
+ end
43
+
26
44
  # Instance helper for displaying a colored message via TTY::Prompt
27
45
  def display_message(message, type: :info)
46
+ # In quiet mode, suppress non-critical messages
47
+ return if quiet_mode? && !CRITICAL_TYPES.include?(type)
48
+
28
49
  # Ensure message is UTF-8 encoded to handle emoji and special characters
29
50
  message_str = message.to_s
30
51
  message_str = message_str.force_encoding("UTF-8") if message_str.encoding.name == "ASCII-8BIT"
@@ -43,8 +64,18 @@ module Aidp
43
64
  end
44
65
 
45
66
  module ClassMethods
67
+ # Check if quiet mode is enabled at class level
68
+ def quiet_mode?
69
+ Aidp::CLI.last_options&.dig(:quiet) || false
70
+ rescue
71
+ false
72
+ end
73
+
46
74
  # Class-level display helper (uses fresh prompt to respect $stdout changes)
47
75
  def display_message(message, type: :info)
76
+ # In quiet mode, suppress non-critical messages
77
+ return if quiet_mode? && !CRITICAL_TYPES.include?(type)
78
+
48
79
  # Ensure message is UTF-8 encoded to handle emoji and special characters
49
80
  message_str = message.to_s
50
81
  message_str = message_str.force_encoding("UTF-8") if message_str.encoding.name == "ASCII-8BIT"