aidp 0.34.0 → 0.39.3

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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -14
  3. data/lib/aidp/agent_harness_adapter.rb +309 -0
  4. data/lib/aidp/cli/issue_importer.rb +20 -6
  5. data/lib/aidp/cli/jobs_command.rb +1 -1
  6. data/lib/aidp/cli/models_command.rb +9 -16
  7. data/lib/aidp/cli/prompts_command.rb +253 -0
  8. data/lib/aidp/cli/security_command.rb +4 -10
  9. data/lib/aidp/cli/storage_command.rb +277 -0
  10. data/lib/aidp/cli/temporal_command.rb +460 -0
  11. data/lib/aidp/cli.rb +24 -2
  12. data/lib/aidp/config.rb +15 -0
  13. data/lib/aidp/config_paths.rb +12 -2
  14. data/lib/aidp/database/migrations.rb +114 -0
  15. data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
  16. data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
  17. data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
  18. data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
  19. data/lib/aidp/database/repositories/job_repository.rb +278 -0
  20. data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
  21. data/lib/aidp/database/repositories/progress_repository.rb +210 -0
  22. data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
  23. data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
  24. data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
  25. data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
  26. data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
  27. data/lib/aidp/database/repositories/task_repository.rb +231 -0
  28. data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
  29. data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
  30. data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
  31. data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
  32. data/lib/aidp/database/repository.rb +166 -0
  33. data/lib/aidp/database/schema.rb +347 -0
  34. data/lib/aidp/database/storage_migrator.rb +693 -0
  35. data/lib/aidp/database.rb +139 -0
  36. data/lib/aidp/debug_mixin.rb +2 -2
  37. data/lib/aidp/execute/checkpoint.rb +26 -0
  38. data/lib/aidp/execute/checkpoint_display.rb +16 -0
  39. data/lib/aidp/execute/prompt_evaluator.rb +30 -57
  40. data/lib/aidp/execute/work_loop_runner.rb +294 -46
  41. data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
  42. data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
  43. data/lib/aidp/harness/ai_decision_engine.rb +78 -296
  44. data/lib/aidp/harness/config_schema.rb +59 -0
  45. data/lib/aidp/harness/configuration.rb +229 -10
  46. data/lib/aidp/harness/deprecation_cache.rb +53 -11
  47. data/lib/aidp/harness/enhanced_runner.rb +1 -1
  48. data/lib/aidp/harness/error_handler.rb +11 -5
  49. data/lib/aidp/harness/provider_config.rb +36 -0
  50. data/lib/aidp/harness/provider_factory.rb +26 -24
  51. data/lib/aidp/harness/provider_info.rb +9 -5
  52. data/lib/aidp/harness/provider_manager.rb +2 -2
  53. data/lib/aidp/harness/provider_metrics.rb +103 -1
  54. data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
  55. data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
  56. data/lib/aidp/harness/runner.rb +7 -2
  57. data/lib/aidp/harness/state_manager.rb +1 -1
  58. data/lib/aidp/harness/test_runner.rb +167 -11
  59. data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
  60. data/lib/aidp/harness/ui.rb +11 -0
  61. data/lib/aidp/harness/usage_limit.rb +179 -0
  62. data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
  63. data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
  64. data/lib/aidp/harness/usage_period.rb +234 -0
  65. data/lib/aidp/init/agent_instructions_generator.rb +317 -0
  66. data/lib/aidp/init/runner.rb +25 -4
  67. data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
  68. data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
  69. data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
  70. data/lib/aidp/interfaces/logger_interface.rb +150 -0
  71. data/lib/aidp/interfaces/ui_interface.rb +254 -0
  72. data/lib/aidp/logger.rb +25 -7
  73. data/lib/aidp/orchestration_adapter.rb +279 -0
  74. data/lib/aidp/pr_worktree_manager.rb +4 -15
  75. data/lib/aidp/prompts/feedback_collector.rb +198 -0
  76. data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
  77. data/lib/aidp/prompts/template_evolver.rb +305 -0
  78. data/lib/aidp/prompts/template_version_manager.rb +325 -0
  79. data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
  80. data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
  81. data/lib/aidp/setup/wizard.rb +616 -176
  82. data/lib/aidp/shell_executor.rb +58 -0
  83. data/lib/aidp/style_guide/selector.rb +56 -1
  84. data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
  85. data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
  86. data/lib/aidp/temporal/activities/base_activity.rb +78 -0
  87. data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
  88. data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
  89. data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
  90. data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
  91. data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
  92. data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
  93. data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
  94. data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
  95. data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
  96. data/lib/aidp/temporal/configuration.rb +169 -0
  97. data/lib/aidp/temporal/connection.rb +93 -0
  98. data/lib/aidp/temporal/worker.rb +119 -0
  99. data/lib/aidp/temporal/workflow_client.rb +171 -0
  100. data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
  101. data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
  102. data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
  103. data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
  104. data/lib/aidp/temporal.rb +120 -0
  105. data/lib/aidp/version.rb +1 -1
  106. data/lib/aidp/watch/base_processor.rb +35 -0
  107. data/lib/aidp/watch/build_processor.rb +132 -0
  108. data/lib/aidp/watch/github_state_extractor.rb +0 -8
  109. data/lib/aidp/watch/rebase_processor.rb +245 -0
  110. data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
  111. data/lib/aidp/watch/runner.rb +366 -285
  112. data/lib/aidp/watch/state_store.rb +80 -0
  113. data/lib/aidp/watch/work_item.rb +96 -0
  114. data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
  115. data/lib/aidp/watch/worktree_reconciler.rb +643 -0
  116. data/lib/aidp/workflows/guided_agent.rb +1 -1
  117. data/lib/aidp/worktree.rb +62 -0
  118. data/templates/aidp.yml.example +70 -0
  119. metadata +118 -16
  120. data/lib/aidp/analyze/json_file_storage.rb +0 -292
  121. data/lib/aidp/execute/future_work_backlog.rb +0 -411
  122. data/lib/aidp/providers/adapter.rb +0 -239
  123. data/lib/aidp/providers/aider.rb +0 -264
  124. data/lib/aidp/providers/anthropic.rb +0 -614
  125. data/lib/aidp/providers/base.rb +0 -617
  126. data/lib/aidp/providers/capability_registry.rb +0 -204
  127. data/lib/aidp/providers/codex.rb +0 -267
  128. data/lib/aidp/providers/cursor.rb +0 -304
  129. data/lib/aidp/providers/error_taxonomy.rb +0 -195
  130. data/lib/aidp/providers/gemini.rb +0 -110
  131. data/lib/aidp/providers/github_copilot.rb +0 -227
  132. data/lib/aidp/providers/kilocode.rb +0 -157
  133. data/lib/aidp/providers/opencode.rb +0 -139
@@ -71,10 +71,16 @@ module Aidp
71
71
  @unit_scheduler = nil
72
72
 
73
73
  # Initialize thinking depth manager for intelligent model selection
74
+ # Issue #375: Enable autonomous mode by default for work loops
74
75
  require_relative "../harness/thinking_depth_manager"
75
- @thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(config, root_dir: @project_dir)
76
+ @thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(
77
+ config,
78
+ root_dir: @project_dir,
79
+ autonomous_mode: true # Issue #375: Work loops use autonomous mode
80
+ )
76
81
  @consecutive_failures = 0
77
82
  @last_tier = nil
83
+ @last_model = nil # Issue #375: Track last used model
78
84
 
79
85
  # Initialize style guide selector for intelligent section selection
80
86
  @style_guide_selector = options[:style_guide_selector] || Aidp::StyleGuide::Selector.new(project_dir: project_dir)
@@ -281,26 +287,9 @@ module Aidp
281
287
  process_task_filing(agent_result)
282
288
 
283
289
  transition_to(:test)
284
- # Run all configured checks
285
- test_results = @test_runner.run_tests
286
- lint_results = @test_runner.run_linters
287
- build_results = @test_runner.run_builds
288
- doc_results = @test_runner.run_documentation
289
-
290
- # Run formatters only if agent marked work complete (per issue #234)
291
- formatter_results = if agent_marked_complete?(agent_result)
292
- @test_runner.run_formatters
293
- else
294
- {success: true, output: "Formatters: Skipped (work not complete)", failures: [], required_failures: []}
295
- end
296
290
 
297
- all_results = {
298
- tests: test_results,
299
- lints: lint_results,
300
- formatters: formatter_results,
301
- builds: build_results,
302
- docs: doc_results
303
- }
291
+ # Run all configured checks using phase-based execution
292
+ all_results = run_phase_based_commands(agent_result)
304
293
 
305
294
  record_periodic_checkpoint(all_results)
306
295
 
@@ -308,28 +297,21 @@ module Aidp
308
297
  track_failures_and_escalate(all_results)
309
298
 
310
299
  # All required checks must pass for completion
311
- all_checks_pass = test_results[:success] &&
312
- lint_results[:success] &&
313
- formatter_results[:success] &&
314
- build_results[:success] &&
315
- doc_results[:success]
300
+ all_checks_pass = all_results.values.all? { |r| r[:success] }
316
301
 
317
302
  # Check task completion status
318
303
  task_completion_result = check_task_completion
319
304
  agent_completed = agent_marked_complete?(agent_result)
320
305
 
321
306
  # FIX for issue #391: Comprehensive logging at completion decision point
307
+ results_summary = all_results.transform_values { |r| r[:success] }
322
308
  Aidp.log_debug("work_loop", "completion_decision_point",
323
309
  iteration: @iteration_count,
324
310
  all_checks_pass: all_checks_pass,
325
311
  agent_marked_complete: agent_completed,
326
312
  task_completion_complete: task_completion_result[:complete],
327
313
  task_completion_reason: task_completion_result[:reason],
328
- test_success: test_results[:success],
329
- lint_success: lint_results[:success],
330
- formatter_success: formatter_results[:success],
331
- build_success: build_results[:success],
332
- doc_success: doc_results[:success])
314
+ phase_results: results_summary)
333
315
 
334
316
  if all_checks_pass
335
317
  transition_to(:pass)
@@ -341,6 +323,16 @@ module Aidp
341
323
  iteration: @iteration_count,
342
324
  reason: task_completion_result[:reason])
343
325
 
326
+ # Run full_loop phase commands before final completion
327
+ full_loop_results = run_full_loop_commands
328
+ unless full_loop_results[:success]
329
+ # Full loop commands failed - continue iterating
330
+ display_message(" Full loop commands failed, continuing work loop", type: :warning)
331
+ all_results[:full_loop] = full_loop_results
332
+ transition_to(:fail)
333
+ next
334
+ end
335
+
344
336
  transition_to(:done)
345
337
  record_final_checkpoint(all_results)
346
338
  display_task_summary
@@ -510,8 +502,9 @@ module Aidp
510
502
 
511
503
  def append_evaluation_feedback_to_prompt(evaluation)
512
504
  feedback_section = build_evaluation_feedback_section(evaluation)
513
-
514
- @prompt_manager.append(feedback_section)
505
+ current_prompt = @prompt_manager.read || ""
506
+ updated_prompt = current_prompt + "\n\n---\n\n" + feedback_section
507
+ @prompt_manager.write(updated_prompt, step_name: @step_name)
515
508
 
516
509
  Aidp.log_debug("work_loop", "appended_evaluation_feedback",
517
510
  iteration: @iteration_count,
@@ -771,6 +764,81 @@ module Aidp
771
764
  result[:status] == "completed" || prompt_marked_complete?
772
765
  end
773
766
 
767
+ # Run commands using the new phase-based execution model
768
+ # This method supports both the new generic commands and legacy category-specific commands
769
+ #
770
+ # @param agent_result [Hash] Result from the agent (used to determine if on_completion should run)
771
+ # @return [Hash] Results keyed by phase/category
772
+ def run_phase_based_commands(agent_result)
773
+ all_results = {}
774
+
775
+ # Check if we're using the new generic commands or legacy category commands
776
+ if @config.respond_to?(:commands) && @config.commands.any?
777
+ # New phase-based execution
778
+ each_unit_results = @test_runner.run_commands_for_phase(:each_unit)
779
+ all_results[:each_unit] = each_unit_results
780
+
781
+ # Run on_completion commands only if agent marked work complete
782
+ if agent_marked_complete?(agent_result)
783
+ on_completion_results = @test_runner.run_commands_for_phase(:on_completion)
784
+ all_results[:on_completion] = on_completion_results
785
+ else
786
+ all_results[:on_completion] = {
787
+ success: true,
788
+ output: "On-completion commands: Skipped (work not complete)",
789
+ failures: [],
790
+ required_failures: []
791
+ }
792
+ end
793
+
794
+ Aidp.log_debug("work_loop", "ran_phase_based_commands",
795
+ each_unit_success: each_unit_results[:success],
796
+ on_completion_success: all_results[:on_completion][:success],
797
+ agent_completed: agent_marked_complete?(agent_result))
798
+ else
799
+ # Legacy category-based execution for backwards compatibility
800
+ all_results = run_legacy_category_commands(agent_result)
801
+ end
802
+
803
+ all_results
804
+ end
805
+
806
+ # Run full_loop phase commands (only at end of entire work loop)
807
+ def run_full_loop_commands
808
+ return {success: true, output: "", failures: [], required_failures: []} unless @config.respond_to?(:commands)
809
+
810
+ full_loop_results = @test_runner.run_commands_for_phase(:full_loop)
811
+
812
+ Aidp.log_debug("work_loop", "ran_full_loop_commands",
813
+ success: full_loop_results[:success],
814
+ command_count: full_loop_results[:results_by_command]&.size || 0)
815
+
816
+ full_loop_results
817
+ end
818
+
819
+ # Run commands using legacy category-based approach (backwards compatibility)
820
+ def run_legacy_category_commands(agent_result)
821
+ test_results = @test_runner.run_tests
822
+ lint_results = @test_runner.run_linters
823
+ build_results = @test_runner.run_builds
824
+ doc_results = @test_runner.run_documentation
825
+
826
+ # Run formatters only if agent marked work complete (per issue #234)
827
+ formatter_results = if agent_marked_complete?(agent_result)
828
+ @test_runner.run_formatters
829
+ else
830
+ {success: true, output: "Formatters: Skipped (work not complete)", failures: [], required_failures: []}
831
+ end
832
+
833
+ {
834
+ tests: test_results,
835
+ lints: lint_results,
836
+ formatters: formatter_results,
837
+ builds: build_results,
838
+ docs: doc_results
839
+ }
840
+ end
841
+
774
842
  # Diagnose all failures (tests, lints, formatters, builds, docs)
775
843
  # Returns diagnostic information to help agent understand what went wrong
776
844
  def diagnose_failures(all_results)
@@ -818,6 +886,8 @@ module Aidp
818
886
  user_input = format_user_input(context[:user_input])
819
887
  deterministic_outputs = Array(context[:deterministic_outputs])
820
888
  previous_summary = context[:previous_agent_summary]
889
+ task_description = format_task_description(user_input, previous_summary)
890
+ additional_context = format_additional_context(context[:additional_context])
821
891
 
822
892
  initial_prompt = build_initial_prompt_content(
823
893
  template: template_content,
@@ -826,7 +896,9 @@ module Aidp
826
896
  user_input: user_input,
827
897
  step_name: @step_name,
828
898
  deterministic_outputs: deterministic_outputs,
829
- previous_agent_summary: previous_summary
899
+ previous_agent_summary: previous_summary,
900
+ task_description: task_description,
901
+ additional_context: additional_context
830
902
  )
831
903
 
832
904
  @prompt_manager.write(initial_prompt, step_name: @step_name)
@@ -910,6 +982,42 @@ module Aidp
910
982
  parts.join("\n\n")
911
983
  end
912
984
 
985
+ def format_task_description(user_input, previous_agent_summary)
986
+ description = build_task_description(user_input, {previous_agent_summary: previous_agent_summary}).to_s.strip
987
+ description.empty? ? "_No task description provided._" : description
988
+ end
989
+
990
+ def format_additional_context(additional_context)
991
+ return "_No additional context._" if additional_context.nil?
992
+
993
+ formatted = case additional_context
994
+ when String
995
+ additional_context.strip
996
+ when Array
997
+ additional_context.map do |entry|
998
+ if entry.is_a?(Hash)
999
+ entry.map { |key, value| "#{key}: #{value}" }.join(", ")
1000
+ else
1001
+ entry.to_s
1002
+ end
1003
+ end.reject(&:empty?).map { |line| "- #{line}" }.join("\n")
1004
+ when Hash
1005
+ additional_context.map { |key, value| "- #{key}: #{value}" }.join("\n")
1006
+ else
1007
+ additional_context.to_s
1008
+ end
1009
+
1010
+ formatted.empty? ? "_No additional context._" : formatted
1011
+ end
1012
+
1013
+ def interpolate_task_template(template, task_description:, additional_context:)
1014
+ return "" if template.nil?
1015
+
1016
+ template
1017
+ .gsub("{{task_description}}", task_description.to_s)
1018
+ .gsub("{{additional_context}}", additional_context.to_s)
1019
+ end
1020
+
913
1021
  # Extract relevant tags from input and spec
914
1022
  def extract_tags(user_input, step_spec)
915
1023
  tags = []
@@ -930,16 +1038,16 @@ module Aidp
930
1038
  tags.uniq
931
1039
  end
932
1040
 
933
- def build_initial_prompt_content(template:, prd:, style_guide:, user_input:, step_name:, deterministic_outputs:, previous_agent_summary:)
1041
+ def build_initial_prompt_content(template:, prd:, style_guide:, user_input:, step_name:, deterministic_outputs:, previous_agent_summary:, task_description:, additional_context:)
934
1042
  parts = []
935
1043
 
936
1044
  parts << "# Work Loop: #{step_name}"
937
1045
  parts << ""
938
1046
  parts << "## Instructions"
939
1047
  parts << "You are working in a work loop. Your responsibilities:"
940
- parts << "1. Read this PROMPT.md file to understand what needs to be done"
1048
+ parts << "1. Read this file (`.aidp/PROMPT.md`) to understand what needs to be done"
941
1049
  parts << "2. Complete the work described below"
942
- parts << "3. **IMPORTANT**: Edit this PROMPT.md file yourself to:"
1050
+ parts << "3. **IMPORTANT**: Edit this `.aidp/PROMPT.md` file to:"
943
1051
  parts << " - Remove completed items"
944
1052
  parts << " - Update with current status"
945
1053
  parts << " - Keep it concise (remove unnecessary context)"
@@ -948,7 +1056,7 @@ module Aidp
948
1056
  parts << "5. If tests/linters fail, you'll see the errors in the next iteration"
949
1057
  parts << ""
950
1058
  parts << "## Completion Criteria"
951
- parts << "Mark this step COMPLETE by adding this line to PROMPT.md:"
1059
+ parts << "Mark this step COMPLETE by adding this line to `.aidp/PROMPT.md`:"
952
1060
  parts << "```"
953
1061
  parts << "STATUS: COMPLETE"
954
1062
  parts << "```"
@@ -988,7 +1096,11 @@ module Aidp
988
1096
  end
989
1097
 
990
1098
  parts << "## Task Template"
991
- parts << template
1099
+ parts << interpolate_task_template(
1100
+ template,
1101
+ task_description: task_description,
1102
+ additional_context: additional_context
1103
+ )
992
1104
  parts << ""
993
1105
 
994
1106
  parts.join("\n")
@@ -1261,7 +1373,7 @@ module Aidp
1261
1373
  parts << "### Step 3: Complete the Work Loop"
1262
1374
  parts << "Only after ALL tasks are done:"
1263
1375
  parts << "- Verify tests pass"
1264
- parts << "- Add STATUS: COMPLETE to PROMPT.md"
1376
+ parts << "- Add STATUS: COMPLETE to `.aidp/PROMPT.md`"
1265
1377
  parts << ""
1266
1378
  parts << "### Task Rules"
1267
1379
  parts << "- **At least ONE task must be filed** - completion blocked without tasks"
@@ -1285,7 +1397,7 @@ module Aidp
1285
1397
  end
1286
1398
 
1287
1399
  parts << "## Completion Criteria"
1288
- parts << "Mark this step COMPLETE by adding these lines to PROMPT.md:"
1400
+ parts << "Mark this step COMPLETE by adding these lines to `.aidp/PROMPT.md`:"
1289
1401
  parts << "```"
1290
1402
  parts << "STATUS: COMPLETE"
1291
1403
  if @config.task_completion_required?
@@ -2144,15 +2256,83 @@ module Aidp
2144
2256
  end
2145
2257
  end
2146
2258
 
2259
+ # Maximum escalation depth to prevent infinite recursion
2260
+ MAX_ESCALATION_DEPTH = 5
2261
+
2147
2262
  # Select model based on current thinking depth tier
2148
2263
  # Returns [provider_name, model_name, model_data]
2149
- def select_model_for_current_tier
2264
+ # Issue #375: Uses intelligent model selection in autonomous mode
2265
+ # @param escalation_depth [Integer] Current recursion depth (prevents infinite loops)
2266
+ def select_model_for_current_tier(escalation_depth: 0)
2150
2267
  current_tier = @thinking_depth_manager.current_tier
2268
+ provider = @provider_manager.current_provider
2269
+
2270
+ # Issue #375: In autonomous mode, use intelligent model selection
2271
+ # that considers previous attempts and prefers untested models
2272
+ if @thinking_depth_manager.autonomous_mode?
2273
+ model_name = @thinking_depth_manager.select_next_model(provider: provider)
2274
+ if model_name
2275
+ @last_model = model_name
2276
+ Aidp.logger.debug("work_loop", "Selected model intelligently",
2277
+ tier: current_tier,
2278
+ provider: provider,
2279
+ model: model_name,
2280
+ step: @step_name,
2281
+ iteration: @iteration_count)
2282
+ return [provider, model_name, {}]
2283
+ end
2284
+
2285
+ # No model from intelligent selection - check if we should escalate
2286
+ escalation_check = @thinking_depth_manager.should_escalate_tier?(provider: provider)
2287
+ if escalation_check[:should_escalate] && escalation_depth < MAX_ESCALATION_DEPTH
2288
+ # Attempt escalation to get access to higher-tier models
2289
+ new_tier = @thinking_depth_manager.escalate_tier_intelligent(provider: provider)
2290
+ if new_tier
2291
+ Aidp.logger.info("work_loop", "Escalated tier after exhausting models",
2292
+ from: current_tier,
2293
+ to: new_tier,
2294
+ reason: escalation_check[:reason])
2295
+ # Retry selection with new tier (increment depth to prevent infinite recursion)
2296
+ return select_model_for_current_tier(escalation_depth: escalation_depth + 1)
2297
+ else
2298
+ Aidp.logger.warn("work_loop", "Escalation recommended but not possible",
2299
+ tier: current_tier,
2300
+ reason: "at_max_tier_or_blocked")
2301
+ # Fall through to standard selection as last resort
2302
+ end
2303
+ elsif escalation_depth >= MAX_ESCALATION_DEPTH
2304
+ Aidp.logger.error("work_loop", "Max escalation depth reached - model selection exhausted",
2305
+ depth: escalation_depth,
2306
+ tier: current_tier,
2307
+ provider: provider)
2308
+ raise Aidp::Harness::NoModelAvailableError.new(
2309
+ tier: current_tier,
2310
+ provider: provider
2311
+ )
2312
+ end
2313
+ # Fall through to standard selection if escalation not recommended or not possible
2314
+ end
2315
+
2316
+ # Standard model selection (non-autonomous or fallback)
2151
2317
  provider_name, model_name, model_data = @thinking_depth_manager.select_model_for_tier(
2152
2318
  current_tier,
2153
- provider: @provider_manager.current_provider
2319
+ provider: provider
2154
2320
  )
2155
2321
 
2322
+ # Validate that we got a usable model
2323
+ if model_name.nil?
2324
+ Aidp.logger.error("work_loop", "No model available after standard selection",
2325
+ tier: current_tier,
2326
+ provider: provider_name)
2327
+ raise Aidp::Harness::NoModelAvailableError.new(
2328
+ tier: current_tier,
2329
+ provider: provider_name || provider
2330
+ )
2331
+ end
2332
+
2333
+ # Track the selected model for attempt recording
2334
+ @last_model = model_name
2335
+
2156
2336
  Aidp.logger.debug("work_loop", "Selected model for tier",
2157
2337
  tier: current_tier,
2158
2338
  provider: provider_name,
@@ -2164,8 +2344,20 @@ module Aidp
2164
2344
  end
2165
2345
 
2166
2346
  # Track test/lint/formatter/build/doc failures and escalate tier if needed
2347
+ # Issue #375: Uses intelligent escalation that tries all models in tier first
2167
2348
  def track_failures_and_escalate(all_results)
2168
2349
  all_pass = all_results.values.all? { |result| result[:success] }
2350
+ provider = @provider_manager.current_provider
2351
+ model = @last_model
2352
+
2353
+ # Record model attempt with success/failure
2354
+ if model
2355
+ @thinking_depth_manager.record_model_attempt(
2356
+ provider: provider,
2357
+ model: model,
2358
+ success: all_pass
2359
+ )
2360
+ end
2169
2361
 
2170
2362
  if all_pass
2171
2363
  # Reset failure count on success
@@ -2174,19 +2366,75 @@ module Aidp
2174
2366
  # Increment failure count
2175
2367
  @consecutive_failures += 1
2176
2368
 
2177
- # Check if we should escalate based on consecutive failures
2178
- if @thinking_depth_manager.should_escalate_on_failures?(@consecutive_failures)
2369
+ # Issue #375: Use intelligent escalation in autonomous mode
2370
+ if @thinking_depth_manager.autonomous_mode?
2371
+ intelligent_escalate_thinking_tier(provider)
2372
+ elsif @thinking_depth_manager.should_escalate_on_failures?(@consecutive_failures)
2373
+ # Legacy behavior for non-autonomous mode
2179
2374
  escalate_thinking_tier("consecutive_failures")
2180
2375
  end
2181
2376
  end
2182
2377
 
2183
- # Check complexity-based escalation
2378
+ # Check complexity-based escalation (applies to both modes)
2184
2379
  changed_files = get_changed_files
2185
2380
  if @thinking_depth_manager.should_escalate_on_complexity?(
2186
2381
  files_changed: changed_files.size,
2187
2382
  modules_touched: estimate_modules_touched(changed_files)
2188
2383
  )
2189
- escalate_thinking_tier("complexity_threshold")
2384
+ # In autonomous mode, only escalate if intelligent check allows
2385
+ if @thinking_depth_manager.autonomous_mode?
2386
+ intelligent_escalate_thinking_tier(provider, reason: "complexity_threshold")
2387
+ else
2388
+ escalate_thinking_tier("complexity_threshold")
2389
+ end
2390
+ end
2391
+ end
2392
+
2393
+ # Issue #375: Intelligent tier escalation that tries all models in current tier first
2394
+ def intelligent_escalate_thinking_tier(provider, reason: nil)
2395
+ escalation_check = @thinking_depth_manager.should_escalate_tier?(provider: provider)
2396
+
2397
+ unless escalation_check[:should_escalate]
2398
+ # Log why we're not escalating yet
2399
+ case escalation_check[:reason]
2400
+ when "untested_models_remain"
2401
+ display_message(" ℹ️ Not escalating tier: #{escalation_check[:untested_count]} untested models remain", type: :info)
2402
+ when "below_min_attempts"
2403
+ display_message(" ℹ️ Not escalating tier: #{escalation_check[:current]}/#{escalation_check[:required]} attempts made", type: :info)
2404
+ end
2405
+
2406
+ Aidp.log_debug("work_loop", "Intelligent escalation blocked",
2407
+ reason: escalation_check[:reason],
2408
+ details: escalation_check)
2409
+ return
2410
+ end
2411
+
2412
+ # Proceed with escalation
2413
+ old_tier = @thinking_depth_manager.current_tier
2414
+ new_tier = @thinking_depth_manager.escalate_tier_intelligent(
2415
+ provider: provider,
2416
+ reason: reason || escalation_check[:reason]
2417
+ )
2418
+
2419
+ if new_tier
2420
+ display_message(" ⬆️ Escalated thinking tier: #{old_tier} → #{new_tier} (#{reason || escalation_check[:reason]})", type: :warning)
2421
+ display_message(" Total attempts in #{old_tier}: #{escalation_check[:total_attempts]}", type: :info)
2422
+
2423
+ Aidp.logger.info("work_loop", "Intelligent tier escalation",
2424
+ from: old_tier,
2425
+ to: new_tier,
2426
+ reason: reason || escalation_check[:reason],
2427
+ step: @step_name,
2428
+ iteration: @iteration_count,
2429
+ model_attempts_summary: @thinking_depth_manager.model_attempts_summary)
2430
+
2431
+ # Reset last tier to trigger display of new tier
2432
+ @last_tier = nil
2433
+ else
2434
+ Aidp.logger.debug("work_loop", "Cannot escalate tier further",
2435
+ current: @thinking_depth_manager.current_tier,
2436
+ max: @thinking_depth_manager.max_tier,
2437
+ reason: reason)
2190
2438
  end
2191
2439
  end
2192
2440
 
@@ -2,15 +2,7 @@
2
2
 
3
3
  require "yaml"
4
4
  require "fileutils"
5
- require_relative "../providers/base"
6
- require_relative "../providers/anthropic"
7
- require_relative "../providers/cursor"
8
- require_relative "../providers/github_copilot"
9
- require_relative "../providers/gemini"
10
- require_relative "../providers/kilocode"
11
- require_relative "../providers/opencode"
12
- require_relative "../providers/codex"
13
- require_relative "../providers/aider"
5
+ require "agent_harness"
14
6
 
15
7
  module Aidp
16
8
  module Firewall
@@ -233,24 +225,16 @@ module Aidp
233
225
 
234
226
  private
235
227
 
236
- # Get list of all provider classes
228
+ # Get list of all provider classes from AgentHarness registry
237
229
  def provider_classes
238
- [
239
- Aidp::Providers::Anthropic,
240
- Aidp::Providers::Cursor,
241
- Aidp::Providers::GithubCopilot,
242
- Aidp::Providers::Gemini,
243
- Aidp::Providers::Kilocode,
244
- Aidp::Providers::Opencode,
245
- Aidp::Providers::Codex,
246
- Aidp::Providers::Aider
247
- ]
230
+ registry = AgentHarness::Providers::Registry.instance
231
+ registry.all.map { |name| registry.get(name) }
248
232
  end
249
233
 
250
- # Extract provider name from class name
234
+ # Extract provider name from provider class
251
235
  def extract_provider_name(provider_class)
252
- # Convert Aidp::Providers::GithubCopilot to "github_copilot"
253
- provider_class.name.split("::").last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
236
+ # AgentHarness providers have provider_name class method
237
+ provider_class.provider_name.to_s
254
238
  end
255
239
 
256
240
  # Default path to firewall config file