aidp 0.27.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -0
  3. data/lib/aidp/cli/models_command.rb +5 -6
  4. data/lib/aidp/cli.rb +10 -8
  5. data/lib/aidp/config.rb +54 -0
  6. data/lib/aidp/debug_mixin.rb +23 -1
  7. data/lib/aidp/execute/agent_signal_parser.rb +22 -0
  8. data/lib/aidp/execute/repl_macros.rb +2 -2
  9. data/lib/aidp/execute/steps.rb +94 -1
  10. data/lib/aidp/execute/work_loop_runner.rb +209 -17
  11. data/lib/aidp/execute/workflow_selector.rb +2 -25
  12. data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
  13. data/lib/aidp/harness/ai_decision_engine.rb +35 -2
  14. data/lib/aidp/harness/config_manager.rb +0 -5
  15. data/lib/aidp/harness/config_schema.rb +8 -0
  16. data/lib/aidp/harness/configuration.rb +27 -19
  17. data/lib/aidp/harness/enhanced_runner.rb +1 -4
  18. data/lib/aidp/harness/error_handler.rb +1 -72
  19. data/lib/aidp/harness/provider_factory.rb +11 -2
  20. data/lib/aidp/harness/state_manager.rb +0 -7
  21. data/lib/aidp/harness/thinking_depth_manager.rb +47 -68
  22. data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
  23. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
  24. data/lib/aidp/harness/ui/progress_display.rb +6 -2
  25. data/lib/aidp/harness/user_interface.rb +0 -58
  26. data/lib/aidp/init/runner.rb +7 -2
  27. data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
  28. data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
  29. data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
  30. data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
  31. data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
  32. data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
  33. data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
  34. data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
  35. data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
  36. data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
  37. data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
  38. data/lib/aidp/planning/parsers/document_parser.rb +141 -0
  39. data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
  40. data/lib/aidp/provider_manager.rb +8 -32
  41. data/lib/aidp/providers/aider.rb +264 -0
  42. data/lib/aidp/providers/anthropic.rb +74 -2
  43. data/lib/aidp/providers/base.rb +25 -1
  44. data/lib/aidp/providers/codex.rb +26 -3
  45. data/lib/aidp/providers/cursor.rb +16 -0
  46. data/lib/aidp/providers/gemini.rb +13 -0
  47. data/lib/aidp/providers/github_copilot.rb +17 -0
  48. data/lib/aidp/providers/kilocode.rb +11 -0
  49. data/lib/aidp/providers/opencode.rb +11 -0
  50. data/lib/aidp/setup/wizard.rb +249 -39
  51. data/lib/aidp/version.rb +1 -1
  52. data/lib/aidp/watch/build_processor.rb +211 -30
  53. data/lib/aidp/watch/change_request_processor.rb +128 -14
  54. data/lib/aidp/watch/ci_fix_processor.rb +103 -37
  55. data/lib/aidp/watch/ci_log_extractor.rb +258 -0
  56. data/lib/aidp/watch/github_state_extractor.rb +177 -0
  57. data/lib/aidp/watch/implementation_verifier.rb +284 -0
  58. data/lib/aidp/watch/plan_generator.rb +7 -43
  59. data/lib/aidp/watch/plan_processor.rb +7 -6
  60. data/lib/aidp/watch/repository_client.rb +245 -17
  61. data/lib/aidp/watch/review_processor.rb +98 -17
  62. data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
  63. data/lib/aidp/watch/runner.rb +181 -29
  64. data/lib/aidp/watch/state_store.rb +22 -1
  65. data/lib/aidp/workflows/definitions.rb +147 -0
  66. data/lib/aidp/workstream_cleanup.rb +245 -0
  67. data/lib/aidp/worktree.rb +19 -0
  68. data/templates/aidp.yml.example +57 -0
  69. data/templates/implementation/generate_tdd_specs.md +213 -0
  70. data/templates/implementation/iterative_implementation.md +122 -0
  71. data/templates/planning/agile/analyze_feedback.md +183 -0
  72. data/templates/planning/agile/generate_iteration_plan.md +179 -0
  73. data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
  74. data/templates/planning/agile/generate_marketing_report.md +162 -0
  75. data/templates/planning/agile/generate_mvp_scope.md +127 -0
  76. data/templates/planning/agile/generate_user_test_plan.md +143 -0
  77. data/templates/planning/agile/ingest_feedback.md +174 -0
  78. data/templates/planning/assemble_project_plan.md +113 -0
  79. data/templates/planning/assign_personas.md +108 -0
  80. data/templates/planning/create_tasks.md +52 -6
  81. data/templates/planning/generate_gantt.md +86 -0
  82. data/templates/planning/generate_wbs.md +85 -0
  83. data/templates/planning/initialize_planning_mode.md +70 -0
  84. data/templates/skills/README.md +2 -2
  85. data/templates/skills/marketing_strategist/SKILL.md +279 -0
  86. data/templates/skills/product_manager/SKILL.md +177 -0
  87. data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
  88. data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
  89. data/templates/skills/ux_researcher/SKILL.md +222 -0
  90. metadata +39 -1
@@ -166,6 +166,11 @@ module Aidp
166
166
  work_loop_config[:max_iterations]
167
167
  end
168
168
 
169
+ # Check if task completion is required for work loop completion
170
+ def task_completion_required?
171
+ work_loop_config.fetch(:task_completion_required, true)
172
+ end
173
+
169
174
  # Get test commands
170
175
  def test_commands
171
176
  normalize_commands(work_loop_config[:test_commands] || [])
@@ -432,34 +437,37 @@ module Aidp
432
437
  thinking_overrides[key] || thinking_overrides[key.to_sym]
433
438
  end
434
439
 
435
- # Get tiers configuration from user's config
436
- def thinking_tiers_config
437
- thinking_config[:tiers] || {}
440
+ # Get thinking tiers configuration for a specific provider
441
+ # @param provider_name [String] The provider name
442
+ # @return [Hash] The thinking tiers configuration for the provider
443
+ def provider_thinking_tiers(provider_name)
444
+ provider_cfg = provider_config(provider_name)
445
+ provider_cfg[:thinking_tiers] || provider_cfg["thinking_tiers"] || {}
438
446
  end
439
447
 
440
- # Get models configured for a specific tier
441
- # Returns array of {provider:, model:} hashes
442
- def models_for_tier(tier)
443
- tier_config = thinking_tiers_config[tier] || thinking_tiers_config[tier.to_sym]
448
+ # Get models configured for a specific tier and provider
449
+ # @param tier [String, Symbol] The tier name (mini, standard, thinking, pro, max)
450
+ # @param provider_name [String] The provider name (required)
451
+ # @return [Array<String>] Array of model names for the tier
452
+ def models_for_tier(tier, provider_name)
453
+ return [] unless provider_name
454
+
455
+ tier_config = provider_thinking_tiers(provider_name)[tier] ||
456
+ provider_thinking_tiers(provider_name)[tier.to_sym]
444
457
  return [] unless tier_config
445
458
 
446
459
  models = tier_config[:models] || tier_config["models"]
447
460
  return [] unless models
448
461
 
449
- # Normalize to array of hashes with symbol keys
450
- Array(models).map do |model_entry|
451
- if model_entry.is_a?(Hash)
452
- {
453
- provider: (model_entry[:provider] || model_entry["provider"]).to_s,
454
- model: (model_entry[:model] || model_entry["model"]).to_s
455
- }
456
- end
457
- end.compact
462
+ # Return simple array of model name strings
463
+ Array(models).map(&:to_s).compact
458
464
  end
459
465
 
460
- # Get all configured tiers
461
- def configured_tiers
462
- thinking_tiers_config.keys.map(&:to_s)
466
+ # Get all configured tiers for a provider
467
+ # @param provider_name [String] The provider name
468
+ # @return [Array<String>] Array of tier names
469
+ def configured_tiers(provider_name)
470
+ provider_thinking_tiers(provider_name).keys.map(&:to_s)
463
471
  end
464
472
 
465
473
  # Get fallback configuration
@@ -106,9 +106,6 @@ module Aidp
106
106
  @tui.show_message("šŸš€ Starting #{@mode.to_s.capitalize} Mode", :info)
107
107
 
108
108
  begin
109
- # Start TUI display loop
110
- @tui.start_display_loop
111
-
112
109
  # Load existing state if resuming
113
110
  # Temporarily disabled to test
114
111
  # load_state if @state_manager.has_state?
@@ -174,7 +171,7 @@ module Aidp
174
171
  ensure
175
172
  # Save state before exiting
176
173
  save_state
177
- @tui.stop_display_loop
174
+ @tui.restore_screen
178
175
  cleanup
179
176
  end
180
177
 
@@ -81,7 +81,7 @@ module Aidp
81
81
  error_type: error_info[:error_type],
82
82
  reason: "Retry not applicable or exhausted"
83
83
  })
84
- if [:authentication, :permission_denied].include?(error_info[:error_type].to_sym)
84
+ if error_info[:error_type].to_sym == :auth_expired
85
85
  # Mark provider unhealthy to avoid immediate re-selection
86
86
  begin
87
87
  if @provider_manager.respond_to?(:mark_provider_auth_failure)
@@ -399,43 +399,6 @@ module Aidp
399
399
  jitter: false
400
400
  },
401
401
 
402
- # Legacy aliases for backward compatibility
403
- network_error: {
404
- name: "network_error",
405
- enabled: true,
406
- max_retries: 3,
407
- backoff_strategy: :exponential,
408
- base_delay: 1.0,
409
- max_delay: 30.0,
410
- jitter: true
411
- },
412
- server_error: {
413
- name: "server_error",
414
- enabled: true,
415
- max_retries: 2,
416
- backoff_strategy: :linear,
417
- base_delay: 2.0,
418
- max_delay: 10.0,
419
- jitter: true
420
- },
421
- timeout: {
422
- name: "timeout",
423
- enabled: true,
424
- max_retries: 2,
425
- backoff_strategy: :exponential,
426
- base_delay: 1.0,
427
- max_delay: 15.0,
428
- jitter: true
429
- },
430
- rate_limit: {
431
- name: "rate_limit",
432
- enabled: false,
433
- max_retries: 0,
434
- backoff_strategy: :none,
435
- base_delay: 0.0,
436
- max_delay: 0.0,
437
- jitter: false
438
- },
439
402
  authentication: {
440
403
  name: "authentication",
441
404
  enabled: false,
@@ -723,40 +686,6 @@ module Aidp
723
686
  reason: "Permanent error, requires manual intervention",
724
687
  priority: :critical
725
688
  }
726
- # Legacy error type mappings for backward compatibility
727
- when :timeout
728
- {
729
- action: :switch_model,
730
- reason: "Timeout error, trying faster model",
731
- priority: :medium
732
- }
733
- when :network_error
734
- {
735
- action: :switch_provider,
736
- reason: "Network error, switching provider",
737
- priority: :medium
738
- }
739
- when :server_error
740
- {
741
- action: :switch_provider,
742
- reason: "Server error, switching provider",
743
- priority: :medium
744
- }
745
- when :authentication, :permission_denied
746
- # Try to switch to another provider. If no providers available, this will
747
- # be detected in attempt_recovery and we'll crash (crash-early principle)
748
- {
749
- action: :switch_provider,
750
- reason: "Authentication/permission issue – switching provider to continue",
751
- priority: :critical,
752
- crash_if_no_fallback: true
753
- }
754
- when :rate_limit
755
- {
756
- action: :switch_provider,
757
- reason: "Rate limit reached, switching provider",
758
- priority: :high
759
- }
760
689
  else
761
690
  {
762
691
  action: :switch_provider,
@@ -9,6 +9,7 @@ require_relative "../providers/opencode"
9
9
  require_relative "../providers/kilocode"
10
10
  require_relative "../providers/github_copilot"
11
11
  require_relative "../providers/codex"
12
+ require_relative "../providers/aider"
12
13
 
13
14
  module Aidp
14
15
  module Harness
@@ -22,7 +23,8 @@ module Aidp
22
23
  "opencode" => Aidp::Providers::Opencode,
23
24
  "kilocode" => Aidp::Providers::Kilocode,
24
25
  "github_copilot" => Aidp::Providers::GithubCopilot,
25
- "codex" => Aidp::Providers::Codex
26
+ "codex" => Aidp::Providers::Codex,
27
+ "aider" => Aidp::Providers::Aider
26
28
  }.freeze
27
29
 
28
30
  def initialize(config_manager = nil)
@@ -240,8 +242,15 @@ module Aidp
240
242
 
241
243
  def configure_provider(provider_instance, provider_config, options)
242
244
  # Set basic configuration
245
+ config_hash = provider_config.config(options).dup
246
+
247
+ # Add model to config if specified in options
248
+ if options[:model]
249
+ config_hash[:model] = options[:model]
250
+ end
251
+
243
252
  if provider_instance.respond_to?(:configure)
244
- provider_instance.configure(provider_config.config(options))
253
+ provider_instance.configure(config_hash)
245
254
  end
246
255
 
247
256
  # Set harness context if available
@@ -103,13 +103,6 @@ module Aidp
103
103
  save_state(updated_state)
104
104
  end
105
105
 
106
- # Get current step from state (legacy method - use progress tracker integration instead)
107
- def current_step_from_state
108
- state = load_state
109
- return nil unless state
110
- state[:current_step]
111
- end
112
-
113
106
  # Set current step
114
107
  def set_current_step(step_name)
115
108
  update_state(current_step: step_name, last_updated: Time.now)
@@ -153,40 +153,32 @@ module Aidp
153
153
  tier ||= current_tier
154
154
  validate_tier!(tier)
155
155
 
156
- # First, try to get models from user's configuration for this tier
157
- configured_models = configuration.models_for_tier(tier)
158
-
159
- if configured_models.any?
160
- # If provider specified, try to find model for that provider in config
161
- if provider
162
- matching_model = configured_models.find { |m| m[:provider] == provider }
163
- if matching_model
164
- Aidp.log_debug("thinking_depth_manager", "Selected model from user config",
165
- tier: tier,
166
- provider: provider,
167
- model: matching_model[:model])
168
- return [matching_model[:provider], matching_model[:model], {}]
169
- end
170
-
171
- # If provider doesn't support tier and switching allowed, try other providers in config
172
- unless configuration.allow_provider_switch_for_tier?
173
- Aidp.log_warn("thinking_depth_manager", "Provider lacks tier in config, switching disabled",
174
- tier: tier,
175
- provider: provider)
176
- return nil
177
- end
178
- end
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)
179
159
 
180
- # Try any configured model for this tier (prioritize first in list)
181
- first_model = configured_models.first
182
- if first_model
183
- Aidp.log_info("thinking_depth_manager", "Selected model from user config",
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",
184
164
  tier: tier,
185
- original_provider: provider,
186
- selected_provider: first_model[:provider],
187
- model: first_model[:model])
188
- return [first_model[:provider], first_model[:model], {}]
165
+ provider: provider,
166
+ model: model_name)
167
+ return [provider, model_name, {}]
189
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)
190
182
  end
191
183
 
192
184
  # Fall back to catalog-based selection if no models in user config
@@ -396,37 +388,22 @@ module Aidp
396
388
  fallback_tiers = generate_fallback_tier_order(requested_tier)
397
389
 
398
390
  fallback_tiers.each do |fallback_tier|
399
- # First, try user's configuration for this fallback tier
400
- configured_models = configuration.models_for_tier(fallback_tier)
401
-
402
- if configured_models.any?
403
- # Try specified provider first if given
404
- if provider
405
- matching_model = configured_models.find { |m| m[:provider] == provider }
406
- if matching_model
407
- Aidp.log_warn("thinking_depth_manager", "Falling back to different tier (from config)",
408
- requested_tier: requested_tier,
409
- fallback_tier: fallback_tier,
410
- provider: provider,
411
- model: matching_model[:model])
412
- return [matching_model[:provider], matching_model[:model], {}]
413
- end
414
- end
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)
415
394
 
416
- # Try any configured model for this tier
417
- first_model = configured_models.first
418
- if first_model
419
- Aidp.log_warn("thinking_depth_manager", "Falling back to different tier and provider (from config)",
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)",
420
398
  requested_tier: requested_tier,
421
399
  fallback_tier: fallback_tier,
422
- requested_provider: provider,
423
- fallback_provider: first_model[:provider],
424
- model: first_model[:model])
425
- return [first_model[:provider], first_model[:model], {}]
400
+ provider: provider,
401
+ model: model_name)
402
+ return [provider, model_name, {}]
426
403
  end
427
404
  end
428
405
 
429
- # Fall back to catalog if no models in config
406
+ # Fall back to catalog if no models in config for the provider
430
407
  # Try specified provider first if given
431
408
  if provider
432
409
  model_name, model_data = @registry.best_model_for_tier(fallback_tier, provider)
@@ -440,19 +417,21 @@ module Aidp
440
417
  end
441
418
  end
442
419
 
443
- # Try all available providers in catalog
444
- @registry.provider_names.each do |prov_name|
445
- next if prov_name == provider # Skip if already tried above
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
446
424
 
447
- model_name, model_data = @registry.best_model_for_tier(fallback_tier, prov_name)
448
- if model_name
449
- Aidp.log_warn("thinking_depth_manager", "Falling back to different tier and provider (from catalog)",
450
- requested_tier: requested_tier,
451
- fallback_tier: fallback_tier,
452
- requested_provider: provider,
453
- fallback_provider: prov_name,
454
- model: model_name)
455
- return [prov_name, model_name, model_data]
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
456
435
  end
457
436
  end
458
437
  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