aidp 0.25.0 → 0.27.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -6
  3. data/lib/aidp/analyze/error_handler.rb +11 -0
  4. data/lib/aidp/cli/checkpoint_command.rb +198 -0
  5. data/lib/aidp/cli/config_command.rb +71 -0
  6. data/lib/aidp/cli/enhanced_input.rb +2 -0
  7. data/lib/aidp/cli/first_run_wizard.rb +8 -7
  8. data/lib/aidp/cli/harness_command.rb +102 -0
  9. data/lib/aidp/cli/jobs_command.rb +3 -3
  10. data/lib/aidp/cli/mcp_dashboard.rb +4 -3
  11. data/lib/aidp/cli/models_command.rb +662 -0
  12. data/lib/aidp/cli/providers_command.rb +223 -0
  13. data/lib/aidp/cli.rb +35 -456
  14. data/lib/aidp/daemon/runner.rb +2 -2
  15. data/lib/aidp/debug_mixin.rb +2 -9
  16. data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
  17. data/lib/aidp/execute/checkpoint_display.rb +38 -37
  18. data/lib/aidp/execute/interactive_repl.rb +2 -1
  19. data/lib/aidp/execute/prompt_manager.rb +4 -4
  20. data/lib/aidp/execute/work_loop_runner.rb +253 -56
  21. data/lib/aidp/execute/workflow_selector.rb +2 -2
  22. data/lib/aidp/harness/config_loader.rb +20 -11
  23. data/lib/aidp/harness/config_manager.rb +5 -5
  24. data/lib/aidp/harness/config_schema.rb +30 -8
  25. data/lib/aidp/harness/configuration.rb +105 -4
  26. data/lib/aidp/harness/enhanced_runner.rb +24 -15
  27. data/lib/aidp/harness/error_handler.rb +26 -5
  28. data/lib/aidp/harness/filter_strategy.rb +45 -0
  29. data/lib/aidp/harness/generic_filter_strategy.rb +63 -0
  30. data/lib/aidp/harness/model_cache.rb +269 -0
  31. data/lib/aidp/harness/model_discovery_service.rb +259 -0
  32. data/lib/aidp/harness/model_registry.rb +201 -0
  33. data/lib/aidp/harness/output_filter.rb +136 -0
  34. data/lib/aidp/harness/provider_manager.rb +18 -3
  35. data/lib/aidp/harness/rspec_filter_strategy.rb +82 -0
  36. data/lib/aidp/harness/runner.rb +5 -0
  37. data/lib/aidp/harness/test_runner.rb +165 -27
  38. data/lib/aidp/harness/thinking_depth_manager.rb +223 -7
  39. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -1
  40. data/lib/aidp/logger.rb +35 -5
  41. data/lib/aidp/providers/adapter.rb +2 -4
  42. data/lib/aidp/providers/anthropic.rb +141 -128
  43. data/lib/aidp/providers/base.rb +98 -2
  44. data/lib/aidp/providers/capability_registry.rb +0 -1
  45. data/lib/aidp/providers/codex.rb +49 -67
  46. data/lib/aidp/providers/cursor.rb +71 -59
  47. data/lib/aidp/providers/gemini.rb +44 -60
  48. data/lib/aidp/providers/github_copilot.rb +2 -66
  49. data/lib/aidp/providers/kilocode.rb +24 -80
  50. data/lib/aidp/providers/opencode.rb +24 -80
  51. data/lib/aidp/safe_directory.rb +10 -3
  52. data/lib/aidp/setup/wizard.rb +345 -8
  53. data/lib/aidp/storage/csv_storage.rb +9 -3
  54. data/lib/aidp/storage/file_manager.rb +8 -2
  55. data/lib/aidp/storage/json_storage.rb +9 -3
  56. data/lib/aidp/version.rb +1 -1
  57. data/lib/aidp/watch/build_processor.rb +40 -1
  58. data/lib/aidp/watch/change_request_processor.rb +659 -0
  59. data/lib/aidp/watch/plan_generator.rb +93 -14
  60. data/lib/aidp/watch/plan_processor.rb +71 -8
  61. data/lib/aidp/watch/repository_client.rb +85 -20
  62. data/lib/aidp/watch/review_processor.rb +3 -3
  63. data/lib/aidp/watch/runner.rb +37 -0
  64. data/lib/aidp/watch/state_store.rb +46 -1
  65. data/lib/aidp/workflows/guided_agent.rb +3 -3
  66. data/lib/aidp/workstream_executor.rb +5 -2
  67. data/lib/aidp.rb +4 -0
  68. data/templates/aidp-development.yml.example +2 -2
  69. data/templates/aidp-production.yml.example +3 -3
  70. data/templates/aidp.yml.example +53 -0
  71. metadata +14 -1
@@ -8,6 +8,7 @@ require_relative "work_loop_unit_scheduler"
8
8
  require_relative "deterministic_unit"
9
9
  require_relative "agent_signal_parser"
10
10
  require_relative "../harness/test_runner"
11
+ require_relative "../errors"
11
12
 
12
13
  module Aidp
13
14
  module Execute
@@ -46,10 +47,11 @@ module Aidp
46
47
  @project_dir = project_dir
47
48
  @provider_manager = provider_manager
48
49
  @config = config
50
+ @prompt = options[:prompt] || TTY::Prompt.new
49
51
  @prompt_manager = PromptManager.new(project_dir, config: config)
50
52
  @test_runner = Aidp::Harness::TestRunner.new(project_dir, config)
51
53
  @checkpoint = Checkpoint.new(project_dir)
52
- @checkpoint_display = CheckpointDisplay.new
54
+ @checkpoint_display = CheckpointDisplay.new(prompt: @prompt)
53
55
  @guard_policy = GuardPolicy.new(project_dir, config.guards_config)
54
56
  @persistent_tasklist = PersistentTasklist.new(project_dir)
55
57
  @iteration_count = 0
@@ -59,6 +61,12 @@ module Aidp
59
61
  @state_history = []
60
62
  @deterministic_runner = DeterministicUnits::Runner.new(project_dir)
61
63
  @unit_scheduler = nil
64
+
65
+ # Initialize thinking depth manager for intelligent model selection
66
+ require_relative "../harness/thinking_depth_manager"
67
+ @thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(config)
68
+ @consecutive_failures = 0
69
+ @last_tier = nil
62
70
  end
63
71
 
64
72
  # Execute a step using fix-forward work loop pattern
@@ -154,6 +162,10 @@ module Aidp
154
162
  # Wrap agent call in exception handling for true fix-forward
155
163
  begin
156
164
  agent_result = apply_patch
165
+ rescue Aidp::Errors::ConfigurationError
166
+ # Configuration errors should crash immediately (crash-early principle)
167
+ # Re-raise without catching
168
+ raise
157
169
  rescue => e
158
170
  # Convert exception to error result for fix-forward handling
159
171
  Aidp.logger.error("work_loop", "Exception during agent call",
@@ -172,23 +184,70 @@ module Aidp
172
184
  next
173
185
  end
174
186
 
187
+ # Check for fatal configuration errors (crash early per LLM_STYLE_GUIDE)
188
+ if agent_result[:status] == "error" && agent_result[:message]&.include?("No model available")
189
+ tier = @thinking_depth_manager.current_tier
190
+ provider = @provider_manager.current_provider
191
+
192
+ error_msg = "No model configured for thinking tier '#{tier}'.\n\n" \
193
+ "Current provider: #{provider}\n" \
194
+ "Required tier: #{tier}\n\n" \
195
+ "To fix this, add a model to your aidp.yml:\n\n" \
196
+ "thinking_depth:\n" \
197
+ " tiers:\n" \
198
+ " #{tier}:\n" \
199
+ " models:\n" \
200
+ " - provider: #{provider}\n" \
201
+ " model: <model-name> # e.g., claude-3-5-sonnet-20241022\n\n" \
202
+ "Or run: aidp models list\n" \
203
+ "to see available models for your configured providers."
204
+
205
+ raise Aidp::Errors::ConfigurationError, error_msg
206
+ end
207
+
175
208
  # Process agent output for task filing signals
176
209
  process_task_filing(agent_result)
177
210
 
178
211
  transition_to(:test)
212
+ # Run all configured checks
179
213
  test_results = @test_runner.run_tests
180
214
  lint_results = @test_runner.run_linters
215
+ build_results = @test_runner.run_builds
216
+ doc_results = @test_runner.run_documentation
217
+
218
+ # Run formatters only if agent marked work complete (per issue #234)
219
+ formatter_results = if agent_marked_complete?(agent_result)
220
+ @test_runner.run_formatters
221
+ else
222
+ {success: true, output: "Formatters: Skipped (work not complete)", failures: [], required_failures: []}
223
+ end
224
+
225
+ all_results = {
226
+ tests: test_results,
227
+ lints: lint_results,
228
+ formatters: formatter_results,
229
+ builds: build_results,
230
+ docs: doc_results
231
+ }
232
+
233
+ record_periodic_checkpoint(all_results)
181
234
 
182
- record_periodic_checkpoint(test_results, lint_results)
235
+ # Track failures and escalate thinking tier if needed
236
+ track_failures_and_escalate(all_results)
183
237
 
184
- tests_pass = test_results[:success] && lint_results[:success]
238
+ # All required checks must pass for completion
239
+ all_checks_pass = test_results[:success] &&
240
+ lint_results[:success] &&
241
+ formatter_results[:success] &&
242
+ build_results[:success] &&
243
+ doc_results[:success]
185
244
 
186
- if tests_pass
245
+ if all_checks_pass
187
246
  transition_to(:pass)
188
247
 
189
248
  if agent_marked_complete?(agent_result)
190
249
  transition_to(:done)
191
- record_final_checkpoint(test_results, lint_results)
250
+ record_final_checkpoint(all_results)
192
251
  display_message("✅ Step #{@step_name} completed after #{@iteration_count} iterations", type: :success)
193
252
  display_state_summary
194
253
  archive_and_cleanup
@@ -201,18 +260,18 @@ module Aidp
201
260
  terminate: true
202
261
  )
203
262
  else
204
- display_message(" Tests passed but work not marked complete", type: :info)
263
+ display_message(" All checks passed but work not marked complete", type: :info)
205
264
  transition_to(:next_patch)
206
265
  end
207
266
  else
208
267
  transition_to(:fail)
209
- display_message(" Tests or linters failed", type: :warning)
268
+ display_message(" Required checks failed", type: :warning)
210
269
 
211
270
  transition_to(:diagnose)
212
- diagnostic = diagnose_failures(test_results, lint_results)
271
+ diagnostic = diagnose_failures(all_results)
213
272
 
214
273
  transition_to(:next_patch)
215
- prepare_next_iteration(test_results, lint_results, diagnostic)
274
+ prepare_next_iteration(all_results, diagnostic)
216
275
  end
217
276
  end
218
277
  end
@@ -222,14 +281,18 @@ module Aidp
222
281
 
223
282
  prompt = build_decider_prompt(context)
224
283
 
284
+ # Select model based on thinking depth tier
285
+ provider_name, model_name, _model_data = select_model_for_current_tier
286
+
225
287
  agent_result = @provider_manager.execute_with_provider(
226
- @provider_manager.current_provider,
288
+ provider_name,
227
289
  prompt,
228
290
  {
229
291
  step_name: @step_name,
230
292
  iteration: @iteration_count,
231
293
  project_dir: @project_dir,
232
- mode: :decide_whats_next
294
+ mode: :decide_whats_next,
295
+ model: model_name
233
296
  }
234
297
  )
235
298
 
@@ -250,14 +313,18 @@ module Aidp
250
313
 
251
314
  prompt = build_diagnose_prompt(context)
252
315
 
316
+ # Select model based on thinking depth tier
317
+ provider_name, model_name, _model_data = select_model_for_current_tier
318
+
253
319
  agent_result = @provider_manager.execute_with_provider(
254
- @provider_manager.current_provider,
320
+ provider_name,
255
321
  prompt,
256
322
  {
257
323
  step_name: @step_name,
258
324
  iteration: @iteration_count,
259
325
  project_dir: @project_dir,
260
- mode: :diagnose_failures
326
+ mode: :diagnose_failures,
327
+ model: model_name
261
328
  }
262
329
  )
263
330
 
@@ -421,27 +488,26 @@ module Aidp
421
488
  result[:status] == "completed" || prompt_marked_complete?
422
489
  end
423
490
 
424
- # Diagnose test/lint failures
491
+ # Diagnose all failures (tests, lints, formatters, builds, docs)
425
492
  # Returns diagnostic information to help agent understand what went wrong
426
- def diagnose_failures(test_results, lint_results)
493
+ def diagnose_failures(all_results)
427
494
  diagnostic = {
428
495
  iteration: @iteration_count,
429
496
  failures: []
430
497
  }
431
498
 
432
- unless test_results[:success]
433
- diagnostic[:failures] << {
434
- type: "tests",
435
- count: test_results[:failures]&.size || 0,
436
- commands: test_results[:failures]&.map { |f| f[:command] } || []
437
- }
438
- end
499
+ # Check each result type for failures
500
+ all_results.each do |category, results|
501
+ next if results[:success]
502
+
503
+ # Only include required failures in diagnostic
504
+ required_failures = results[:required_failures] || results[:failures] || []
505
+ next if required_failures.empty?
439
506
 
440
- unless lint_results[:success]
441
507
  diagnostic[:failures] << {
442
- type: "linters",
443
- count: lint_results[:failures]&.size || 0,
444
- commands: lint_results[:failures]&.map { |f| f[:command] } || []
508
+ type: category.to_s,
509
+ count: required_failures.size,
510
+ commands: required_failures.map { |f| f[:command] }
445
511
  }
446
512
  end
447
513
 
@@ -650,17 +716,36 @@ module Aidp
650
716
  # Prepend work loop instructions to every iteration
651
717
  full_prompt = build_work_loop_header(@step_name, @iteration_count) + "\n\n" + prompt_content
652
718
 
719
+ # Select model based on thinking depth tier
720
+ provider_name, model_name, _model_data = select_model_for_current_tier
721
+
722
+ if provider_name.nil? || model_name.nil?
723
+ Aidp.logger.error("work_loop", "Failed to select model for tier",
724
+ tier: @thinking_depth_manager.current_tier,
725
+ step: @step_name,
726
+ iteration: @iteration_count)
727
+ return {status: "error", message: "No model available for tier #{@thinking_depth_manager.current_tier}"}
728
+ end
729
+
730
+ # Log model selection
731
+ tier = @thinking_depth_manager.current_tier
732
+ if @last_tier != tier
733
+ display_message(" 💡 Using tier: #{tier} (#{provider_name}/#{model_name})", type: :info)
734
+ @last_tier = tier
735
+ end
736
+
653
737
  # CRITICAL: Change to project directory before calling provider
654
738
  # This ensures Claude CLI runs in the correct directory and can create files
655
739
  Dir.chdir(@project_dir) do
656
- # Send to provider via provider_manager
740
+ # Send to provider via provider_manager with selected model
657
741
  @provider_manager.execute_with_provider(
658
- @provider_manager.current_provider,
742
+ provider_name,
659
743
  full_prompt,
660
744
  {
661
745
  step_name: @step_name,
662
746
  iteration: @iteration_count,
663
- project_dir: @project_dir
747
+ project_dir: @project_dir,
748
+ model: model_name
664
749
  }
665
750
  )
666
751
  end
@@ -701,7 +786,7 @@ module Aidp
701
786
  prompt_content.match?(/^STATUS:\s*COMPLETE/i)
702
787
  end
703
788
 
704
- def prepare_next_iteration(test_results, lint_results, diagnostic = nil)
789
+ def prepare_next_iteration(all_results, diagnostic = nil)
705
790
  # Only append failures to PROMPT.md for agent to see
706
791
  # This follows fix-forward: never rollback, only add information for next patch
707
792
  failures = []
@@ -723,25 +808,30 @@ module Aidp
723
808
  failures << ""
724
809
  end
725
810
 
726
- unless test_results[:success]
727
- failures << "### Test Failures"
728
- failures << test_results[:output]
729
- failures << ""
730
- end
811
+ # Add failure output for each category that has failures
812
+ category_labels = {
813
+ tests: "Test",
814
+ lints: "Linter",
815
+ formatters: "Formatter",
816
+ builds: "Build",
817
+ docs: "Documentation"
818
+ }
731
819
 
732
- unless lint_results[:success]
733
- failures << "### Linter Failures"
734
- failures << lint_results[:output]
820
+ all_results.each do |category, results|
821
+ next if results[:success]
822
+
823
+ failures << "### #{category_labels[category]} Failures"
824
+ failures << results[:output]
735
825
  failures << ""
736
826
  end
737
827
 
738
- strategy = build_failure_strategy(test_results, lint_results)
828
+ strategy = build_failure_strategy(all_results)
739
829
  failures.concat(strategy) unless strategy.empty?
740
830
 
741
831
  failures << "**Fix-forward instructions**: Do not rollback changes. Build on what exists and fix the failures above."
742
832
  failures << ""
743
833
 
744
- return if test_results[:success] && lint_results[:success]
834
+ return if all_results.values.all? { |result| result[:success] }
745
835
 
746
836
  # Append failures to PROMPT.md and archive immediately (issue #224)
747
837
  current_prompt = @prompt_manager.read
@@ -832,20 +922,27 @@ module Aidp
832
922
  reminder.join("\n")
833
923
  end
834
924
 
835
- def build_failure_strategy(test_results, lint_results)
836
- return [] if test_results[:success] && lint_results[:success]
925
+ def build_failure_strategy(all_results)
926
+ return [] if all_results.values.all? { |result| result[:success] }
837
927
 
838
928
  lines = ["### Recovery Strategy", ""]
839
929
 
840
- unless test_results[:success]
841
- commands = format_command_list(test_results[:failures])
842
- lines << "- Re-run #{commands} locally to reproduce the failing specs listed above."
843
- lines << "- Triage the exact failures before moving on to new work."
844
- end
930
+ category_strategies = {
931
+ tests: "Re-run %s locally to reproduce the failing specs listed above. Triage the exact failures before moving on to new work.",
932
+ lints: "Execute %s and fix each reported offense.",
933
+ formatters: "Run %s to fix formatting issues.",
934
+ builds: "Run %s to diagnose and fix build errors.",
935
+ docs: "Review and update documentation using %s to meet requirements."
936
+ }
937
+
938
+ all_results.each do |category, results|
939
+ next if results[:success]
940
+
941
+ strategy_template = category_strategies[category]
942
+ next unless strategy_template
845
943
 
846
- unless lint_results[:success]
847
- commands = format_command_list(lint_results[:failures])
848
- lines << "- Execute #{commands} and fix each reported offense."
944
+ commands = format_command_list(results[:failures])
945
+ lines << "- #{strategy_template % commands}"
849
946
  end
850
947
 
851
948
  lines << ""
@@ -926,13 +1023,16 @@ module Aidp
926
1023
  end
927
1024
 
928
1025
  # Record checkpoint at regular intervals
929
- def record_periodic_checkpoint(test_results, lint_results)
1026
+ def record_periodic_checkpoint(all_results)
930
1027
  # Record every CHECKPOINT_INTERVAL iterations or on iteration 1
931
1028
  return unless @iteration_count == 1 || (@iteration_count % CHECKPOINT_INTERVAL == 0)
932
1029
 
933
1030
  metrics = {
934
- tests_passing: test_results[:success],
935
- linters_passing: lint_results[:success]
1031
+ tests_passing: all_results[:tests][:success],
1032
+ linters_passing: all_results[:lints][:success],
1033
+ formatters_passing: all_results[:formatters][:success],
1034
+ builds_passing: all_results[:builds][:success],
1035
+ docs_passing: all_results[:docs][:success]
936
1036
  }
937
1037
 
938
1038
  checkpoint_data = @checkpoint.record_checkpoint(@step_name, @iteration_count, metrics)
@@ -947,10 +1047,13 @@ module Aidp
947
1047
  end
948
1048
 
949
1049
  # Record final checkpoint when step completes
950
- def record_final_checkpoint(test_results, lint_results)
1050
+ def record_final_checkpoint(all_results)
951
1051
  metrics = {
952
- tests_passing: test_results[:success],
953
- linters_passing: lint_results[:success],
1052
+ tests_passing: all_results[:tests][:success],
1053
+ linters_passing: all_results[:lints][:success],
1054
+ formatters_passing: all_results[:formatters][:success],
1055
+ builds_passing: all_results[:builds][:success],
1056
+ docs_passing: all_results[:docs][:success],
954
1057
  completed: true
955
1058
  }
956
1059
 
@@ -1114,6 +1217,100 @@ module Aidp
1114
1217
  display_message(" ✓ Confirmed", type: :success)
1115
1218
  end
1116
1219
  end
1220
+
1221
+ # Select model based on current thinking depth tier
1222
+ # Returns [provider_name, model_name, model_data]
1223
+ def select_model_for_current_tier
1224
+ current_tier = @thinking_depth_manager.current_tier
1225
+ provider_name, model_name, model_data = @thinking_depth_manager.select_model_for_tier(
1226
+ current_tier,
1227
+ provider: @provider_manager.current_provider
1228
+ )
1229
+
1230
+ Aidp.logger.debug("work_loop", "Selected model for tier",
1231
+ tier: current_tier,
1232
+ provider: provider_name,
1233
+ model: model_name,
1234
+ step: @step_name,
1235
+ iteration: @iteration_count)
1236
+
1237
+ [provider_name, model_name, model_data]
1238
+ end
1239
+
1240
+ # Track test/lint/formatter/build/doc failures and escalate tier if needed
1241
+ def track_failures_and_escalate(all_results)
1242
+ all_pass = all_results.values.all? { |result| result[:success] }
1243
+
1244
+ if all_pass
1245
+ # Reset failure count on success
1246
+ @consecutive_failures = 0
1247
+ else
1248
+ # Increment failure count
1249
+ @consecutive_failures += 1
1250
+
1251
+ # Check if we should escalate based on consecutive failures
1252
+ if @thinking_depth_manager.should_escalate_on_failures?(@consecutive_failures)
1253
+ escalate_thinking_tier("consecutive_failures")
1254
+ end
1255
+ end
1256
+
1257
+ # Check complexity-based escalation
1258
+ changed_files = get_changed_files
1259
+ if @thinking_depth_manager.should_escalate_on_complexity?(
1260
+ files_changed: changed_files.size,
1261
+ modules_touched: estimate_modules_touched(changed_files)
1262
+ )
1263
+ escalate_thinking_tier("complexity_threshold")
1264
+ end
1265
+ end
1266
+
1267
+ # Escalate to next thinking tier
1268
+ def escalate_thinking_tier(reason)
1269
+ old_tier = @thinking_depth_manager.current_tier
1270
+ new_tier = @thinking_depth_manager.escalate_tier(reason: reason)
1271
+
1272
+ if new_tier
1273
+ display_message(" ⬆️ Escalated thinking tier: #{old_tier} → #{new_tier} (#{reason})", type: :warning)
1274
+ Aidp.logger.info("work_loop", "Escalated thinking tier",
1275
+ from: old_tier,
1276
+ to: new_tier,
1277
+ reason: reason,
1278
+ step: @step_name,
1279
+ iteration: @iteration_count,
1280
+ consecutive_failures: @consecutive_failures)
1281
+
1282
+ # Reset last tier to trigger display of new tier
1283
+ @last_tier = nil
1284
+ else
1285
+ Aidp.logger.debug("work_loop", "Cannot escalate tier further",
1286
+ current: old_tier,
1287
+ max: @thinking_depth_manager.max_tier,
1288
+ reason: reason)
1289
+ end
1290
+ end
1291
+
1292
+ # Estimate number of modules touched based on file paths
1293
+ def estimate_modules_touched(files)
1294
+ # Group files by their top-level directory or module
1295
+ modules = files.map do |file|
1296
+ parts = file.split("/")
1297
+ # Consider top 2 levels as module identifier
1298
+ parts.take(2).join("/")
1299
+ end.uniq
1300
+
1301
+ modules.size
1302
+ end
1303
+
1304
+ # Get thinking depth status for display
1305
+ def thinking_depth_status
1306
+ {
1307
+ current_tier: @thinking_depth_manager.current_tier,
1308
+ max_tier: @thinking_depth_manager.max_tier,
1309
+ can_escalate: @thinking_depth_manager.can_escalate?,
1310
+ consecutive_failures: @consecutive_failures,
1311
+ escalation_count: @thinking_depth_manager.escalation_count
1312
+ }
1313
+ end
1117
1314
  end
1118
1315
  end
1119
1316
  end
@@ -10,10 +10,10 @@ module Aidp
10
10
  class WorkflowSelector
11
11
  include Aidp::MessageDisplay
12
12
 
13
- def initialize(prompt: TTY::Prompt.new)
13
+ def initialize(prompt: TTY::Prompt.new, workflow_selector: nil)
14
14
  @user_input = {}
15
15
  @prompt = prompt
16
- @workflow_selector = Aidp::Workflows::Selector.new(prompt: @prompt)
16
+ @workflow_selector = workflow_selector || Aidp::Workflows::Selector.new(prompt: @prompt)
17
17
  end
18
18
 
19
19
  # Main entry point for interactive workflow selection
@@ -316,17 +316,19 @@ module Aidp
316
316
  def handle_validation_errors(errors)
317
317
  error_message = "Configuration validation failed:\n" + errors.join("\n")
318
318
 
319
- # Log error
320
- if defined?(Rails) && Rails.logger
321
- Rails.logger.error(error_message)
322
- else
323
- warn(error_message)
319
+ # Log error (suppress in test/CI environments)
320
+ unless suppress_config_warnings?
321
+ if defined?(Rails) && Rails.logger
322
+ Rails.logger.error(error_message)
323
+ else
324
+ warn(error_message)
325
+ end
324
326
  end
325
327
 
326
328
  # In development, try to fix common issues
327
329
  if ENV["AIDP_ENV"] == "development" || ENV["RACK_ENV"] == "development"
328
330
  if @validator.fix_common_issues
329
- warn("Attempted to fix configuration issues. Please review the updated configuration file.")
331
+ warn("Attempted to fix configuration issues. Please review the updated configuration file.") unless suppress_config_warnings?
330
332
  end
331
333
  end
332
334
  end
@@ -334,11 +336,13 @@ module Aidp
334
336
  def log_warnings(warnings)
335
337
  warning_message = "Configuration warnings:\n" + warnings.join("\n")
336
338
 
337
- # Log warnings
338
- if defined?(Rails) && Rails.logger
339
- Rails.logger.warn(warning_message)
340
- else
341
- warn(warning_message)
339
+ # Log warnings (suppress in test/CI environments)
340
+ unless suppress_config_warnings?
341
+ if defined?(Rails) && Rails.logger
342
+ Rails.logger.warn(warning_message)
343
+ else
344
+ warn(warning_message)
345
+ end
342
346
  end
343
347
  end
344
348
 
@@ -390,6 +394,11 @@ module Aidp
390
394
  false
391
395
  end
392
396
  end
397
+
398
+ # Suppress configuration warnings in test/CI environments
399
+ def suppress_config_warnings?
400
+ ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
401
+ end
393
402
  end
394
403
  end
395
404
  end
@@ -359,19 +359,19 @@ module Aidp
359
359
  def load_config_with_options(options)
360
360
  # Apply different loading strategies based on options
361
361
  if options[:mode]
362
- @loader.get_mode_config(options[:mode], options[:force_reload])
362
+ @loader.mode_config(options[:mode], options[:force_reload])
363
363
  elsif options[:environment]
364
- @loader.get_environment_config(options[:environment], options[:force_reload])
364
+ @loader.environment_config(options[:environment], options[:force_reload])
365
365
  elsif options[:step]
366
366
  @loader.get_step_config(options[:step], options[:force_reload])
367
367
  elsif options[:features]
368
- @loader.get_config_with_features(options[:features], options[:force_reload])
368
+ @loader.config_with_features(options[:features], options[:force_reload])
369
369
  elsif options[:user]
370
370
  @loader.get_user_config(options[:user], options[:force_reload])
371
371
  elsif options[:time_based]
372
- @loader.get_time_based_config(options[:force_reload])
372
+ @loader.time_based_config(options[:force_reload])
373
373
  elsif options[:overrides]
374
- @loader.get_config_with_overrides(options[:overrides])
374
+ @loader.config_with_overrides(options[:overrides])
375
375
  else
376
376
  @loader.load_config(options[:force_reload])
377
377
  end
@@ -375,6 +375,9 @@ module Aidp
375
375
  max_iterations: 50,
376
376
  test_commands: [],
377
377
  lint_commands: [],
378
+ formatter_commands: [],
379
+ build_commands: [],
380
+ documentation_commands: [],
378
381
  units: {},
379
382
  guards: {enabled: false},
380
383
  version_control: {tool: "git", behavior: "nothing", conventional_commits: false},
@@ -397,18 +400,37 @@ module Aidp
397
400
  test_commands: {
398
401
  type: :array,
399
402
  required: false,
400
- default: [],
401
- items: {
402
- type: :string
403
- }
403
+ default: []
404
+ # Items can be strings or {command: string, required: boolean}
405
+ # Validation handled in Configuration class for flexibility
404
406
  },
405
407
  lint_commands: {
406
408
  type: :array,
407
409
  required: false,
408
- default: [],
409
- items: {
410
- type: :string
411
- }
410
+ default: []
411
+ # Items can be strings or {command: string, required: boolean}
412
+ # Validation handled in Configuration class for flexibility
413
+ },
414
+ formatter_commands: {
415
+ type: :array,
416
+ required: false,
417
+ default: []
418
+ # Items can be strings or {command: string, required: boolean}
419
+ # Validation handled in Configuration class for flexibility
420
+ },
421
+ build_commands: {
422
+ type: :array,
423
+ required: false,
424
+ default: []
425
+ # Items can be strings or {command: string, required: boolean}
426
+ # Validation handled in Configuration class for flexibility
427
+ },
428
+ documentation_commands: {
429
+ type: :array,
430
+ required: false,
431
+ default: []
432
+ # Items can be strings or {command: string, required: boolean}
433
+ # Validation handled in Configuration class for flexibility
412
434
  },
413
435
  units: {
414
436
  type: :hash,