aidp 0.26.0 → 0.28.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -0
  3. data/lib/aidp/cli/checkpoint_command.rb +198 -0
  4. data/lib/aidp/cli/config_command.rb +71 -0
  5. data/lib/aidp/cli/enhanced_input.rb +2 -0
  6. data/lib/aidp/cli/first_run_wizard.rb +8 -7
  7. data/lib/aidp/cli/harness_command.rb +102 -0
  8. data/lib/aidp/cli/jobs_command.rb +3 -3
  9. data/lib/aidp/cli/mcp_dashboard.rb +4 -3
  10. data/lib/aidp/cli/models_command.rb +661 -0
  11. data/lib/aidp/cli/providers_command.rb +223 -0
  12. data/lib/aidp/cli.rb +45 -464
  13. data/lib/aidp/config.rb +54 -0
  14. data/lib/aidp/daemon/runner.rb +2 -2
  15. data/lib/aidp/debug_mixin.rb +25 -10
  16. data/lib/aidp/execute/agent_signal_parser.rb +22 -0
  17. data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
  18. data/lib/aidp/execute/checkpoint_display.rb +38 -37
  19. data/lib/aidp/execute/interactive_repl.rb +2 -1
  20. data/lib/aidp/execute/prompt_manager.rb +4 -4
  21. data/lib/aidp/execute/repl_macros.rb +2 -2
  22. data/lib/aidp/execute/steps.rb +94 -1
  23. data/lib/aidp/execute/work_loop_runner.rb +238 -19
  24. data/lib/aidp/execute/workflow_selector.rb +4 -27
  25. data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
  26. data/lib/aidp/harness/ai_decision_engine.rb +35 -2
  27. data/lib/aidp/harness/config_manager.rb +5 -10
  28. data/lib/aidp/harness/config_schema.rb +8 -0
  29. data/lib/aidp/harness/configuration.rb +40 -2
  30. data/lib/aidp/harness/enhanced_runner.rb +25 -19
  31. data/lib/aidp/harness/error_handler.rb +23 -73
  32. data/lib/aidp/harness/model_cache.rb +269 -0
  33. data/lib/aidp/harness/model_discovery_service.rb +259 -0
  34. data/lib/aidp/harness/model_registry.rb +201 -0
  35. data/lib/aidp/harness/provider_factory.rb +11 -2
  36. data/lib/aidp/harness/runner.rb +5 -0
  37. data/lib/aidp/harness/state_manager.rb +0 -7
  38. data/lib/aidp/harness/thinking_depth_manager.rb +202 -7
  39. data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
  40. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
  41. data/lib/aidp/harness/ui/progress_display.rb +6 -2
  42. data/lib/aidp/harness/user_interface.rb +0 -58
  43. data/lib/aidp/init/runner.rb +7 -2
  44. data/lib/aidp/message_display.rb +0 -46
  45. data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
  46. data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
  47. data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
  48. data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
  49. data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
  50. data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
  51. data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
  52. data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
  53. data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
  54. data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
  55. data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
  56. data/lib/aidp/planning/parsers/document_parser.rb +141 -0
  57. data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
  58. data/lib/aidp/provider_manager.rb +8 -32
  59. data/lib/aidp/providers/adapter.rb +2 -4
  60. data/lib/aidp/providers/aider.rb +264 -0
  61. data/lib/aidp/providers/anthropic.rb +206 -121
  62. data/lib/aidp/providers/base.rb +123 -3
  63. data/lib/aidp/providers/capability_registry.rb +0 -1
  64. data/lib/aidp/providers/codex.rb +75 -70
  65. data/lib/aidp/providers/cursor.rb +87 -59
  66. data/lib/aidp/providers/gemini.rb +57 -60
  67. data/lib/aidp/providers/github_copilot.rb +19 -66
  68. data/lib/aidp/providers/kilocode.rb +35 -80
  69. data/lib/aidp/providers/opencode.rb +35 -80
  70. data/lib/aidp/setup/wizard.rb +555 -8
  71. data/lib/aidp/version.rb +1 -1
  72. data/lib/aidp/watch/build_processor.rb +211 -30
  73. data/lib/aidp/watch/change_request_processor.rb +128 -14
  74. data/lib/aidp/watch/ci_fix_processor.rb +103 -37
  75. data/lib/aidp/watch/ci_log_extractor.rb +258 -0
  76. data/lib/aidp/watch/github_state_extractor.rb +177 -0
  77. data/lib/aidp/watch/implementation_verifier.rb +284 -0
  78. data/lib/aidp/watch/plan_generator.rb +95 -52
  79. data/lib/aidp/watch/plan_processor.rb +7 -6
  80. data/lib/aidp/watch/repository_client.rb +245 -17
  81. data/lib/aidp/watch/review_processor.rb +100 -19
  82. data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
  83. data/lib/aidp/watch/runner.rb +181 -29
  84. data/lib/aidp/watch/state_store.rb +22 -1
  85. data/lib/aidp/workflows/definitions.rb +147 -0
  86. data/lib/aidp/workflows/guided_agent.rb +3 -3
  87. data/lib/aidp/workstream_cleanup.rb +245 -0
  88. data/lib/aidp/worktree.rb +19 -0
  89. data/templates/aidp-development.yml.example +2 -2
  90. data/templates/aidp-production.yml.example +3 -3
  91. data/templates/aidp.yml.example +57 -0
  92. data/templates/implementation/generate_tdd_specs.md +213 -0
  93. data/templates/implementation/iterative_implementation.md +122 -0
  94. data/templates/planning/agile/analyze_feedback.md +183 -0
  95. data/templates/planning/agile/generate_iteration_plan.md +179 -0
  96. data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
  97. data/templates/planning/agile/generate_marketing_report.md +162 -0
  98. data/templates/planning/agile/generate_mvp_scope.md +127 -0
  99. data/templates/planning/agile/generate_user_test_plan.md +143 -0
  100. data/templates/planning/agile/ingest_feedback.md +174 -0
  101. data/templates/planning/assemble_project_plan.md +113 -0
  102. data/templates/planning/assign_personas.md +108 -0
  103. data/templates/planning/create_tasks.md +52 -6
  104. data/templates/planning/generate_gantt.md +86 -0
  105. data/templates/planning/generate_wbs.md +85 -0
  106. data/templates/planning/initialize_planning_mode.md +70 -0
  107. data/templates/skills/README.md +2 -2
  108. data/templates/skills/marketing_strategist/SKILL.md +279 -0
  109. data/templates/skills/product_manager/SKILL.md +177 -0
  110. data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
  111. data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
  112. data/templates/skills/ux_researcher/SKILL.md +222 -0
  113. metadata +47 -1
@@ -2,12 +2,15 @@
2
2
 
3
3
  require_relative "capability_registry"
4
4
  require_relative "configuration"
5
+ require_relative "../message_display"
5
6
 
6
7
  module Aidp
7
8
  module Harness
8
9
  # Manages thinking depth tier selection and escalation
9
10
  # Integrates with CapabilityRegistry and Configuration to select appropriate models
10
11
  class ThinkingDepthManager
12
+ include Aidp::MessageDisplay
13
+
11
14
  attr_reader :configuration, :registry
12
15
 
13
16
  def initialize(configuration, registry: nil, root_dir: nil)
@@ -150,11 +153,40 @@ module Aidp
150
153
  tier ||= current_tier
151
154
  validate_tier!(tier)
152
155
 
153
- # If provider specified, try to find model for that provider
156
+ # First, try to get models from user's configuration for this tier and provider
157
+ if provider
158
+ configured_models = configuration.models_for_tier(tier, provider)
159
+
160
+ if configured_models.any?
161
+ # Use first configured model for this provider and tier
162
+ model_name = configured_models.first
163
+ Aidp.log_debug("thinking_depth_manager", "Selected model from user config",
164
+ tier: tier,
165
+ provider: provider,
166
+ model: model_name)
167
+ return [provider, model_name, {}]
168
+ end
169
+
170
+ # Provider specified but has no models for this tier in config
171
+ # Try catalog for the specified provider before switching providers
172
+ Aidp.log_debug("thinking_depth_manager", "Provider has no configured models for tier, trying catalog",
173
+ tier: tier,
174
+ provider: provider)
175
+
176
+ # Continue to catalog-based selection below (will try specified provider first)
177
+ else
178
+ # No provider specified - this should not happen in normal flow
179
+ # Log warning and fall through to catalog-based selection
180
+ Aidp.log_warn("thinking_depth_manager", "select_model_for_tier called without provider",
181
+ tier: tier)
182
+ end
183
+
184
+ # Fall back to catalog-based selection if no models in user config
185
+ # If provider specified, try to find model for that provider in catalog
154
186
  if provider
155
187
  model_name, model_data = @registry.best_model_for_tier(tier, provider)
156
188
  if model_name
157
- Aidp.log_debug("thinking_depth_manager", "Selected model",
189
+ Aidp.log_debug("thinking_depth_manager", "Selected model from catalog",
158
190
  tier: tier,
159
191
  provider: provider,
160
192
  model: model_name)
@@ -163,20 +195,24 @@ module Aidp
163
195
 
164
196
  # If provider doesn't support tier and switching allowed, try others
165
197
  unless configuration.allow_provider_switch_for_tier?
166
- Aidp.log_warn("thinking_depth_manager", "Provider lacks tier, switching disabled",
198
+ Aidp.log_warn("thinking_depth_manager", "Provider lacks tier in catalog, switching disabled",
167
199
  tier: tier,
168
200
  provider: provider)
169
201
  return nil
170
202
  end
171
203
  end
172
204
 
173
- # Try all providers
205
+ # Try all providers in catalog
206
+ if provider && !configuration.allow_provider_switch_for_tier?
207
+ return nil
208
+ end
209
+
174
210
  providers_to_try = provider ? [@registry.provider_names - [provider]].flatten : @registry.provider_names
175
211
 
176
212
  providers_to_try.each do |prov_name|
177
213
  model_name, model_data = @registry.best_model_for_tier(tier, prov_name)
178
214
  if model_name
179
- Aidp.log_info("thinking_depth_manager", "Selected model from alternate provider",
215
+ Aidp.log_info("thinking_depth_manager", "Selected model from catalog (alternate provider)",
180
216
  tier: tier,
181
217
  original_provider: provider,
182
218
  selected_provider: prov_name,
@@ -185,10 +221,23 @@ module Aidp
185
221
  end
186
222
  end
187
223
 
188
- Aidp.log_error("thinking_depth_manager", "No model found for tier",
224
+ # No model found for requested tier - try fallback to other tiers
225
+ Aidp.log_warn("thinking_depth_manager", "No model found for requested tier, trying fallback",
189
226
  tier: tier,
190
227
  provider: provider)
191
- nil
228
+
229
+ result = try_fallback_tiers(tier, provider)
230
+
231
+ unless result
232
+ # Enhanced error message with discovery hints
233
+ display_enhanced_tier_error(tier, provider)
234
+
235
+ Aidp.log_error("thinking_depth_manager", "No model found for tier or fallback tiers",
236
+ tier: tier,
237
+ provider: provider)
238
+ end
239
+
240
+ result
192
241
  end
193
242
 
194
243
  # Get tier for a specific model
@@ -330,6 +379,152 @@ module Aidp
330
379
  # Keep history bounded
331
380
  @tier_history.shift if @tier_history.size > 100
332
381
  end
382
+
383
+ # Try to find a model in fallback tiers when requested tier has no models
384
+ # Tries lower tiers first (cheaper), then higher tiers
385
+ # Returns [provider_name, model_name, model_data] or nil
386
+ def try_fallback_tiers(requested_tier, provider)
387
+ # Generate fallback order: try lower tiers first, then higher
388
+ fallback_tiers = generate_fallback_tier_order(requested_tier)
389
+
390
+ fallback_tiers.each do |fallback_tier|
391
+ # First, try user's configuration for this fallback tier and provider
392
+ if provider
393
+ configured_models = configuration.models_for_tier(fallback_tier, provider)
394
+
395
+ if configured_models.any?
396
+ model_name = configured_models.first
397
+ Aidp.log_warn("thinking_depth_manager", "Falling back to different tier (from config)",
398
+ requested_tier: requested_tier,
399
+ fallback_tier: fallback_tier,
400
+ provider: provider,
401
+ model: model_name)
402
+ return [provider, model_name, {}]
403
+ end
404
+ end
405
+
406
+ # Fall back to catalog if no models in config for the provider
407
+ # Try specified provider first if given
408
+ if provider
409
+ model_name, model_data = @registry.best_model_for_tier(fallback_tier, provider)
410
+ if model_name
411
+ Aidp.log_warn("thinking_depth_manager", "Falling back to different tier (from catalog)",
412
+ requested_tier: requested_tier,
413
+ fallback_tier: fallback_tier,
414
+ provider: provider,
415
+ model: model_name)
416
+ return [provider, model_name, model_data]
417
+ end
418
+ end
419
+
420
+ # Try all available providers in catalog if switching allowed
421
+ if configuration.allow_provider_switch_for_tier?
422
+ @registry.provider_names.each do |prov_name|
423
+ next if prov_name == provider # Skip if already tried above
424
+
425
+ model_name, model_data = @registry.best_model_for_tier(fallback_tier, prov_name)
426
+ if model_name
427
+ Aidp.log_warn("thinking_depth_manager", "Falling back to different tier and provider (from catalog)",
428
+ requested_tier: requested_tier,
429
+ fallback_tier: fallback_tier,
430
+ requested_provider: provider,
431
+ fallback_provider: prov_name,
432
+ model: model_name)
433
+ return [prov_name, model_name, model_data]
434
+ end
435
+ end
436
+ end
437
+ end
438
+
439
+ nil
440
+ end
441
+
442
+ # Generate fallback tier order: lower tiers first (cheaper), then higher
443
+ # For example, if tier is "standard", try: mini, thinking, pro, max
444
+ def generate_fallback_tier_order(tier)
445
+ current_priority = @registry.tier_priority(tier) || 1
446
+ all_tiers = CapabilityRegistry::VALID_TIERS
447
+
448
+ # Split into lower and higher tiers
449
+ lower_tiers = all_tiers.select { |t| (@registry.tier_priority(t) || 0) < current_priority }.reverse
450
+ higher_tiers = all_tiers.select { |t| (@registry.tier_priority(t) || 0) > current_priority }
451
+
452
+ # Try lower tiers first (cost optimization), then higher tiers
453
+ lower_tiers + higher_tiers
454
+ end
455
+
456
+ # Display enhanced error message with discovery hints
457
+ def display_enhanced_tier_error(tier, provider)
458
+ return unless defined?(Aidp::MessageDisplay)
459
+
460
+ # Check if there are discovered models in cache
461
+ discovered_models = check_discovered_models(tier, provider)
462
+
463
+ if discovered_models&.any?
464
+ display_tier_error_with_suggestions(tier, provider, discovered_models)
465
+ else
466
+ display_tier_error_with_discovery_hint(tier, provider)
467
+ end
468
+ end
469
+
470
+ # Check cache for discovered models for this tier
471
+ def check_discovered_models(tier, provider)
472
+ require_relative "model_cache"
473
+ require_relative "model_registry"
474
+
475
+ cache = Aidp::Harness::ModelCache.new
476
+ registry = Aidp::Harness::ModelRegistry.new
477
+
478
+ # Get all cached models for the provider
479
+ cached_models = cache.get_cached_models(provider)
480
+ return nil unless cached_models&.any?
481
+
482
+ # Filter to models for the requested tier
483
+ tier_models = cached_models.select do |model|
484
+ family = model[:family] || model["family"]
485
+ model_info = registry.get_model_info(family)
486
+ model_info && model_info["tier"] == tier.to_s
487
+ end
488
+
489
+ tier_models.any? ? tier_models : nil
490
+ rescue => e
491
+ Aidp.log_debug("thinking_depth_manager", "failed to check cached models",
492
+ error: e.message)
493
+ nil
494
+ end
495
+
496
+ # Display error with model suggestions from cache
497
+ def display_tier_error_with_suggestions(tier, provider, models)
498
+ display_message("\nāŒ No model configured for '#{tier}' tier", type: :error)
499
+ display_message(" Provider: #{provider}", type: :info) if provider
500
+
501
+ display_message("\nšŸ’” Discovered models for this tier:", type: :highlight)
502
+ models.first(3).each do |model|
503
+ model_name = model[:name] || model["name"]
504
+ display_message(" - #{model_name}", type: :info)
505
+ end
506
+
507
+ display_message("\n Add to aidp.yml:", type: :highlight)
508
+ display_message(" providers:", type: :info)
509
+ display_message(" #{provider}:", type: :info)
510
+ display_message(" thinking:", type: :info)
511
+ display_message(" tiers:", type: :info)
512
+ display_message(" #{tier}:", type: :info)
513
+ display_message(" models:", type: :info)
514
+ first_model = models.first[:name] || models.first["name"]
515
+ display_message(" - model: #{first_model}\n", type: :info)
516
+ end
517
+
518
+ # Display error with discovery hint
519
+ def display_tier_error_with_discovery_hint(tier, provider)
520
+ display_message("\nāŒ No model configured for '#{tier}' tier", type: :error)
521
+ display_message(" Provider: #{provider}", type: :info) if provider
522
+
523
+ display_message("\nšŸ’” Suggested actions:", type: :highlight)
524
+ display_message(" 1. Run 'aidp models discover' to find available models", type: :info)
525
+ display_message(" 2. Run 'aidp models list --tier=#{tier}' to see models for this tier", type: :info)
526
+ display_message(" 3. Run 'aidp models validate' to check your configuration\n", type: :info)
527
+ end
333
528
  end
334
529
  end
335
530
  end
@@ -43,16 +43,6 @@ module Aidp
43
43
  setup_signal_handlers
44
44
  end
45
45
 
46
- # Simple display initialization - no background threads
47
- def start_display_loop
48
- # Display loop is now just a no-op for compatibility
49
- end
50
-
51
- def stop_display_loop
52
- # Simple cleanup - no background threads to stop
53
- restore_screen
54
- end
55
-
56
46
  # Input methods using TTY::Prompt only - no background threads
57
47
  def get_user_input(prompt = "šŸ’¬ You: ")
58
48
  @prompt.ask(prompt)
@@ -258,6 +248,12 @@ module Aidp
258
248
  @prompt.say("šŸ“ #{message}", color: :cyan)
259
249
  end
260
250
 
251
+ def restore_screen
252
+ @cursor.show
253
+ @cursor.clear_screen
254
+ @cursor.move_to(1, 1)
255
+ end
256
+
261
257
  private
262
258
 
263
259
  def extract_questions_for_step(step_name)
@@ -284,20 +280,14 @@ module Aidp
284
280
  []
285
281
  end
286
282
 
287
- def restore_screen
288
- @cursor.show
289
- @cursor.clear_screen
290
- @cursor.move_to(1, 1)
291
- end
292
-
293
283
  def setup_signal_handlers
294
284
  Signal.trap("INT") do
295
- stop_display_loop
285
+ restore_screen
296
286
  exit(1)
297
287
  end
298
288
 
299
289
  Signal.trap("TERM") do
300
- stop_display_loop
290
+ restore_screen
301
291
  exit(0)
302
292
  end
303
293
  end
@@ -86,24 +86,6 @@ module Aidp
86
86
  }
87
87
  end
88
88
 
89
- # Legacy method - kept for backward compatibility if needed
90
- def select_execute_workflow_interactive
91
- # Step 1: Collect project information
92
- collect_project_info_interactive
93
-
94
- # Step 2: Choose workflow type
95
- workflow_type = choose_workflow_type_interactive
96
-
97
- # Step 3: Generate workflow steps
98
- steps = generate_workflow_steps_interactive(workflow_type)
99
-
100
- {
101
- workflow_type: workflow_type,
102
- steps: steps,
103
- user_input: @user_input
104
- }
105
- end
106
-
107
89
  def select_analyze_workflow_defaults
108
90
  @tui.show_message("šŸš€ Starting analyze mode with default configuration...", :info)
109
91
 
@@ -22,14 +22,18 @@ module Aidp
22
22
 
23
23
  def initialize(ui_components = {})
24
24
  super()
25
+ @output = ui_components[:output]
25
26
  @progress = ui_components[:progress] || TTY::ProgressBar
26
- @pastel = Pastel.new
27
+ @pastel = if @output&.respond_to?(:tty?) && @output.tty?
28
+ Pastel.new(enabled: true)
29
+ else
30
+ Pastel.new(enabled: false)
31
+ end
27
32
  @formatter = ui_components[:formatter] || ProgressFormatter.new
28
33
  @display_history = []
29
34
  @auto_refresh_enabled = false
30
35
  @refresh_interval = 1.0
31
36
  @refresh_thread = nil
32
- @output = ui_components[:output]
33
37
  @prompt = ui_components[:prompt] || TTY::Prompt.new
34
38
  @spinner_class = begin
35
39
  ui_components[:spinner] || TTY::Spinner
@@ -425,48 +425,6 @@ module Aidp
425
425
  display_message("\nšŸš€ Continuing execution...", type: :success)
426
426
  end
427
427
 
428
- # Display question information (legacy method for compatibility)
429
- def display_question_info(question_type, expected_input, options, default_value, required)
430
- info_parts = []
431
-
432
- # Question type
433
- type_emojis = {
434
- "text" => "šŸ“",
435
- "choice" => "šŸ”˜",
436
- "confirmation" => "āœ…",
437
- "file" => "šŸ“",
438
- "number" => "šŸ”¢",
439
- "email" => "šŸ“§",
440
- "url" => "šŸ”—"
441
- }
442
- type_emoji = type_emojis[question_type] || "ā“"
443
- info_parts << "#{type_emoji} #{question_type.capitalize}"
444
-
445
- # Expected input type
446
- if expected_input != "text"
447
- info_parts << "Expected: #{expected_input}"
448
- end
449
-
450
- # Options
451
- if options && !options.empty?
452
- info_parts << "Options: #{options.join(", ")}"
453
- end
454
-
455
- # Default value
456
- if default_value
457
- info_parts << "Default: #{default_value}"
458
- end
459
-
460
- # Required status
461
- info_parts << if required
462
- "Required: Yes"
463
- else
464
- "Required: No"
465
- end
466
-
467
- display_message(" #{info_parts.join(" | ")}", type: :info)
468
- end
469
-
470
428
  # Get response for a specific question with enhanced validation
471
429
  def question_response(question_data, _question_number)
472
430
  question_type = question_data[:type] || "text"
@@ -1576,12 +1534,6 @@ module Aidp
1576
1534
  end
1577
1535
  end
1578
1536
 
1579
- # Find files matching search term (legacy method for compatibility)
1580
- def find_files(search_term)
1581
- search_options = parse_file_search_options(search_term)
1582
- find_files_advanced(search_options)
1583
- end
1584
-
1585
1537
  # Display advanced file selection menu
1586
1538
  def display_advanced_file_menu(files, search_options)
1587
1539
  display_message("\nšŸ“ Available files:", type: :info)
@@ -1659,11 +1611,6 @@ module Aidp
1659
1611
  end
1660
1612
  end
1661
1613
 
1662
- # Display file selection menu (legacy method for compatibility)
1663
- def display_file_menu(files)
1664
- display_advanced_file_menu(files, {term: "", extensions: [], directories: []})
1665
- end
1666
-
1667
1614
  # Get advanced file selection from user
1668
1615
  def advanced_file_selection(max_files, _search_options)
1669
1616
  loop do
@@ -1763,11 +1710,6 @@ module Aidp
1763
1710
  @prompt.keypress("Press any key to continue...")
1764
1711
  end
1765
1712
 
1766
- # Get file selection from user (legacy method for compatibility)
1767
- def file_selection(max_files)
1768
- advanced_file_selection(max_files, {term: "", extensions: [], directories: []})
1769
- end
1770
-
1771
1713
  # Get confirmation from user
1772
1714
  def confirmation(message, default: true)
1773
1715
  default_text = default ? "Y/n" : "y/N"
@@ -337,8 +337,13 @@ module Aidp
337
337
  next unless tool_command
338
338
 
339
339
  # Check if command exists
340
- unless system("which #{tool_command} > /dev/null 2>&1")
341
- warnings << " āš ļø #{format_tool(tool_data[:tool])} detected but command '#{tool_command}' not found in PATH"
340
+ begin
341
+ unless system("which #{tool_command} > /dev/null 2>&1")
342
+ warnings << " āš ļø #{format_tool(tool_data[:tool])} detected but command '#{tool_command}' not found in PATH"
343
+ end
344
+ rescue IOError
345
+ # Streams may be closed in test environment - skip validation
346
+ Aidp.log_debug("init_runner", "skipping_tool_validation", tool: tool_command, reason: "closed_stream")
342
347
  end
343
348
  end
344
349
 
@@ -25,7 +25,6 @@ module Aidp
25
25
 
26
26
  # Instance helper for displaying a colored message via TTY::Prompt
27
27
  def display_message(message, type: :info)
28
- return if suppress_display_message?(message)
29
28
  # Ensure message is UTF-8 encoded to handle emoji and special characters
30
29
  message_str = message.to_s
31
30
  message_str = message_str.force_encoding("UTF-8") if message_str.encoding.name == "ASCII-8BIT"
@@ -43,32 +42,9 @@ module Aidp
43
42
  end
44
43
  end
45
44
 
46
- # Check if specific display message should be suppressed in test/CI environments
47
- def suppress_display_message?(message)
48
- return false unless in_test_environment?
49
-
50
- message_str = message.to_s
51
- # Only suppress specific automated status messages, not CLI output
52
- message_str.include?("šŸ”„ Provider switch:") ||
53
- message_str.include?("šŸ”„ Model switch:") ||
54
- message_str.include?("šŸ”“ Circuit breaker opened") ||
55
- message_str.include?("🟢 Circuit breaker reset") ||
56
- message_str.include?("āŒ No providers available") ||
57
- message_str.include?("āŒ No models available") ||
58
- message_str.include?("šŸ“Š Execution Summary") ||
59
- message_str.include?("ā–¶ļø [") || # Workstream execution messages
60
- message_str.include?("āœ… [") || # Workstream success messages
61
- message_str.include?("āŒ [") # Workstream failure messages
62
- end
63
-
64
- def in_test_environment?
65
- ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
66
- end
67
-
68
45
  module ClassMethods
69
46
  # Class-level display helper (uses fresh prompt to respect $stdout changes)
70
47
  def display_message(message, type: :info)
71
- return if suppress_display_message?(message)
72
48
  # Ensure message is UTF-8 encoded to handle emoji and special characters
73
49
  message_str = message.to_s
74
50
  message_str = message_str.force_encoding("UTF-8") if message_str.encoding.name == "ASCII-8BIT"
@@ -78,28 +54,6 @@ module Aidp
78
54
 
79
55
  private
80
56
 
81
- # Check if specific display message should be suppressed in test/CI environments
82
- def suppress_display_message?(message)
83
- return false unless in_test_environment?
84
-
85
- message_str = message.to_s
86
- # Only suppress specific automated status messages, not CLI output
87
- message_str.include?("šŸ”„ Provider switch:") ||
88
- message_str.include?("šŸ”„ Model switch:") ||
89
- message_str.include?("šŸ”“ Circuit breaker opened") ||
90
- message_str.include?("🟢 Circuit breaker reset") ||
91
- message_str.include?("āŒ No providers available") ||
92
- message_str.include?("āŒ No models available") ||
93
- message_str.include?("šŸ“Š Execution Summary") ||
94
- message_str.include?("ā–¶ļø [") || # Workstream execution messages
95
- message_str.include?("āœ… [") || # Workstream success messages
96
- message_str.include?("āŒ [") # Workstream failure messages
97
- end
98
-
99
- def in_test_environment?
100
- ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
101
- end
102
-
103
57
  # Don't memoize - create fresh prompt each time to respect $stdout redirection in tests
104
58
  def class_message_display_prompt
105
59
  TTY::Prompt.new