aidp 0.9.6 → 0.11.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.
- checksums.yaml +4 -4
- data/README.md +194 -25
- data/lib/aidp/analyze/error_handler.rb +4 -2
- data/lib/aidp/{analysis → analyze}/kb_inspector.rb +93 -89
- data/lib/aidp/analyze/prioritizer.rb +3 -2
- data/lib/aidp/analyze/progress.rb +2 -1
- data/lib/aidp/analyze/ruby_maat_integration.rb +7 -3
- data/lib/aidp/analyze/runner.rb +73 -11
- data/lib/aidp/{analysis → analyze}/seams.rb +1 -1
- data/lib/aidp/analyze/steps.rb +10 -8
- data/lib/aidp/{analysis → analyze}/tree_sitter_grammar_loader.rb +11 -5
- data/lib/aidp/{analysis → analyze}/tree_sitter_scan.rb +21 -15
- data/lib/aidp/cli/checkpoint_command.rb +98 -0
- data/lib/aidp/cli/first_run_wizard.rb +83 -103
- data/lib/aidp/cli/jobs_command.rb +270 -36
- data/lib/aidp/cli/terminal_io.rb +3 -3
- data/lib/aidp/cli.rb +411 -69
- data/lib/aidp/config.rb +5 -8
- data/lib/aidp/debug_logger.rb +4 -4
- data/lib/aidp/debug_mixin.rb +11 -4
- data/lib/aidp/execute/checkpoint.rb +282 -0
- data/lib/aidp/execute/checkpoint_display.rb +221 -0
- data/lib/aidp/execute/progress.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +62 -0
- data/lib/aidp/execute/runner.rb +67 -20
- data/lib/aidp/execute/steps.rb +36 -27
- data/lib/aidp/execute/work_loop_runner.rb +308 -0
- data/lib/aidp/execute/workflow_selector.rb +50 -26
- data/lib/aidp/harness/condition_detector.rb +4 -4
- data/lib/aidp/harness/config_schema.rb +40 -0
- data/lib/aidp/harness/config_validator.rb +3 -6
- data/lib/aidp/harness/configuration.rb +35 -1
- data/lib/aidp/harness/enhanced_runner.rb +25 -4
- data/lib/aidp/harness/error_handler.rb +103 -28
- data/lib/aidp/harness/provider_factory.rb +6 -1
- data/lib/aidp/harness/provider_manager.rb +273 -19
- data/lib/aidp/harness/runner.rb +14 -6
- data/lib/aidp/harness/simple_user_interface.rb +6 -4
- data/lib/aidp/harness/status_display.rb +118 -106
- data/lib/aidp/harness/test_runner.rb +83 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +7 -5
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -4
- data/lib/aidp/harness/ui/error_handler.rb +7 -2
- data/lib/aidp/harness/ui/frame_manager.rb +61 -39
- data/lib/aidp/harness/ui/job_monitor.rb +2 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +27 -16
- data/lib/aidp/harness/ui/navigation/menu_item.rb +1 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +1 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +2 -0
- data/lib/aidp/harness/ui/progress_display.rb +26 -7
- data/lib/aidp/harness/ui/question_collector.rb +2 -0
- data/lib/aidp/harness/ui/spinner_group.rb +2 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +1 -1
- data/lib/aidp/harness/ui/status_manager.rb +4 -2
- data/lib/aidp/harness/ui/status_widget.rb +20 -9
- data/lib/aidp/harness/ui/workflow_controller.rb +27 -9
- data/lib/aidp/harness/user_interface.rb +338 -330
- data/lib/aidp/jobs/background_runner.rb +278 -0
- data/lib/aidp/message_display.rb +48 -0
- data/lib/aidp/provider_manager.rb +13 -7
- data/lib/aidp/providers/anthropic.rb +101 -18
- data/lib/aidp/providers/base.rb +51 -1
- data/lib/aidp/providers/codex.rb +248 -0
- data/lib/aidp/providers/cursor.rb +39 -48
- data/lib/aidp/providers/gemini.rb +26 -16
- data/lib/aidp/providers/github_copilot.rb +263 -0
- data/lib/aidp/providers/opencode.rb +38 -47
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/workflows/definitions.rb +357 -0
- data/lib/aidp/workflows/selector.rb +171 -0
- data/lib/aidp.rb +16 -4
- data/templates/planning/generate_llm_style_guide.md +119 -0
- metadata +43 -31
- data/lib/aidp/analyze/progress_visualizer.rb +0 -314
- /data/templates/{ANALYZE/02_ARCHITECTURE_ANALYSIS.md → analysis/analyze_architecture.md} +0 -0
- /data/templates/{ANALYZE/05_DOCUMENTATION_ANALYSIS.md → analysis/analyze_documentation.md} +0 -0
- /data/templates/{ANALYZE/04_FUNCTIONALITY_ANALYSIS.md → analysis/analyze_functionality.md} +0 -0
- /data/templates/{ANALYZE/01_REPOSITORY_ANALYSIS.md → analysis/analyze_repository.md} +0 -0
- /data/templates/{ANALYZE/06_STATIC_ANALYSIS.md → analysis/analyze_static_code.md} +0 -0
- /data/templates/{ANALYZE/03_TEST_ANALYSIS.md → analysis/analyze_tests.md} +0 -0
- /data/templates/{ANALYZE/07_REFACTORING_RECOMMENDATIONS.md → analysis/recommend_refactoring.md} +0 -0
- /data/templates/{ANALYZE/06a_tree_sitter_scan.md → analysis/scan_with_tree_sitter.md} +0 -0
- /data/templates/{EXECUTE/11_STATIC_ANALYSIS.md → implementation/configure_static_analysis.md} +0 -0
- /data/templates/{EXECUTE/14_DOCS_PORTAL.md → implementation/create_documentation_portal.md} +0 -0
- /data/templates/{EXECUTE/10_IMPLEMENTATION_AGENT.md → implementation/implement_features.md} +0 -0
- /data/templates/{EXECUTE/13_DELIVERY_ROLLOUT.md → implementation/plan_delivery.md} +0 -0
- /data/templates/{EXECUTE/15_POST_RELEASE.md → implementation/review_post_release.md} +0 -0
- /data/templates/{EXECUTE/09_SCAFFOLDING_DEVEX.md → implementation/setup_scaffolding.md} +0 -0
- /data/templates/{EXECUTE/02A_ARCH_GATE_QUESTIONS.md → planning/ask_architecture_questions.md} +0 -0
- /data/templates/{EXECUTE/00_PRD.md → planning/create_prd.md} +0 -0
- /data/templates/{EXECUTE/08_TASKS.md → planning/create_tasks.md} +0 -0
- /data/templates/{EXECUTE/04_DOMAIN_DECOMPOSITION.md → planning/decompose_domain.md} +0 -0
- /data/templates/{EXECUTE/01_NFRS.md → planning/define_nfrs.md} +0 -0
- /data/templates/{EXECUTE/05_CONTRACTS.md → planning/design_apis.md} +0 -0
- /data/templates/{EXECUTE/02_ARCHITECTURE.md → planning/design_architecture.md} +0 -0
- /data/templates/{EXECUTE/06_THREAT_MODEL.md → planning/design_data_model.md} +0 -0
- /data/templates/{EXECUTE/03_ADR_FACTORY.md → planning/generate_adrs.md} +0 -0
- /data/templates/{EXECUTE/12_OBSERVABILITY_SLOS.md → planning/plan_observability.md} +0 -0
- /data/templates/{EXECUTE/07_TEST_PLAN.md → planning/plan_testing.md} +0 -0
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "tty-prompt"
|
|
3
4
|
require_relative "provider_factory"
|
|
4
5
|
|
|
5
6
|
module Aidp
|
|
6
7
|
module Harness
|
|
7
8
|
# Manages provider switching and fallback logic
|
|
8
9
|
class ProviderManager
|
|
9
|
-
|
|
10
|
+
include Aidp::MessageDisplay
|
|
11
|
+
|
|
12
|
+
def initialize(configuration, prompt: TTY::Prompt.new)
|
|
10
13
|
@configuration = configuration
|
|
14
|
+
@prompt = prompt
|
|
11
15
|
@current_provider = nil
|
|
12
16
|
@current_model = nil
|
|
13
17
|
@provider_history = []
|
|
@@ -29,6 +33,9 @@ module Aidp
|
|
|
29
33
|
@model_fallback_chains = {}
|
|
30
34
|
@model_switching_enabled = true
|
|
31
35
|
@model_weights = {}
|
|
36
|
+
@unavailable_cache = {}
|
|
37
|
+
@binary_check_cache = {}
|
|
38
|
+
@binary_check_ttl = 300 # seconds
|
|
32
39
|
initialize_fallback_chains
|
|
33
40
|
initialize_provider_health
|
|
34
41
|
initialize_model_configs
|
|
@@ -560,9 +567,39 @@ module Aidp
|
|
|
560
567
|
|
|
561
568
|
# Check if provider is available (not rate limited, healthy, circuit breaker closed)
|
|
562
569
|
def is_provider_available?(provider_name)
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
570
|
+
cli_ok, _reason = provider_cli_available?(provider_name)
|
|
571
|
+
return false unless cli_ok
|
|
572
|
+
return false if is_rate_limited?(provider_name)
|
|
573
|
+
return false unless is_provider_healthy?(provider_name)
|
|
574
|
+
return false if is_provider_circuit_breaker_open?(provider_name)
|
|
575
|
+
true
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Mark provider unhealthy (auth or generic) and optionally open circuit breaker
|
|
579
|
+
def mark_provider_unhealthy(provider_name, reason: "manual", open_circuit: true)
|
|
580
|
+
return unless @provider_health[provider_name]
|
|
581
|
+
health = @provider_health[provider_name]
|
|
582
|
+
health[:status] = (reason == "auth") ? "unhealthy_auth" : "unhealthy"
|
|
583
|
+
health[:last_updated] = Time.now
|
|
584
|
+
health[:unhealthy_reason] = reason
|
|
585
|
+
if open_circuit
|
|
586
|
+
health[:circuit_breaker_open] = true
|
|
587
|
+
health[:circuit_breaker_opened_at] = Time.now
|
|
588
|
+
log_circuit_breaker_event(provider_name, "opened")
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def mark_provider_auth_failure(provider_name)
|
|
593
|
+
mark_provider_unhealthy(provider_name, reason: "auth", open_circuit: true)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# Mark provider unhealthy specifically due to failure exhaustion (non-auth)
|
|
597
|
+
def mark_provider_failure_exhausted(provider_name)
|
|
598
|
+
return unless @provider_health[provider_name]
|
|
599
|
+
health = @provider_health[provider_name]
|
|
600
|
+
# Don't override more critical states (auth or circuit already open)
|
|
601
|
+
return if health[:unhealthy_reason] == "auth"
|
|
602
|
+
mark_provider_unhealthy(provider_name, reason: "fail_exhausted", open_circuit: true)
|
|
566
603
|
end
|
|
567
604
|
|
|
568
605
|
# Check if model is rate limited
|
|
@@ -948,6 +985,198 @@ module Aidp
|
|
|
948
985
|
@provider_metrics.dup
|
|
949
986
|
end
|
|
950
987
|
|
|
988
|
+
# Determine whether a provider CLI/binary appears installed
|
|
989
|
+
def provider_installed?(provider_name)
|
|
990
|
+
return @unavailable_cache[provider_name] unless @unavailable_cache[provider_name].nil?
|
|
991
|
+
installed = true
|
|
992
|
+
begin
|
|
993
|
+
case provider_name
|
|
994
|
+
when "anthropic", "claude"
|
|
995
|
+
# Prefer direct binary probe instead of Anthropic.available? (which uses which internally)
|
|
996
|
+
path = begin
|
|
997
|
+
Aidp::Util.which("claude")
|
|
998
|
+
rescue
|
|
999
|
+
nil
|
|
1000
|
+
end
|
|
1001
|
+
installed = !path.nil?
|
|
1002
|
+
when "cursor"
|
|
1003
|
+
require_relative "../providers/cursor"
|
|
1004
|
+
installed = Aidp::Providers::Cursor.available?
|
|
1005
|
+
end
|
|
1006
|
+
rescue LoadError
|
|
1007
|
+
installed = false
|
|
1008
|
+
end
|
|
1009
|
+
@unavailable_cache[provider_name] = installed
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
# Attempt to run a provider's CLI with --version (or no-op) to verify executable health
|
|
1013
|
+
def provider_cli_available?(provider_name)
|
|
1014
|
+
normalized = normalize_provider_name(provider_name)
|
|
1015
|
+
|
|
1016
|
+
# Handle test environment overrides
|
|
1017
|
+
if defined?(RSpec) || ENV["RSPEC_RUNNING"]
|
|
1018
|
+
# Force claude to be missing for testing
|
|
1019
|
+
if ENV["AIDP_FORCE_CLAUDE_MISSING"] == "1" && normalized == "claude"
|
|
1020
|
+
return [false, "binary_missing"]
|
|
1021
|
+
end
|
|
1022
|
+
# Force claude to be available for testing
|
|
1023
|
+
if ENV["AIDP_FORCE_CLAUDE_AVAILABLE"] == "1" && normalized == "claude"
|
|
1024
|
+
return [true, "available"]
|
|
1025
|
+
end
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
cache_key = "#{provider_name}:#{normalized}"
|
|
1029
|
+
cached = @binary_check_cache[cache_key]
|
|
1030
|
+
if cached && (Time.now - cached[:checked_at] < @binary_check_ttl)
|
|
1031
|
+
return [cached[:ok], cached[:reason]]
|
|
1032
|
+
end
|
|
1033
|
+
# Map normalized provider -> binary
|
|
1034
|
+
binary = case normalized
|
|
1035
|
+
when "claude" then "claude"
|
|
1036
|
+
when "cursor" then "cursor"
|
|
1037
|
+
when "gemini" then "gemini"
|
|
1038
|
+
when "macos" then nil # passthrough; no direct binary expected
|
|
1039
|
+
end
|
|
1040
|
+
unless binary
|
|
1041
|
+
@binary_check_cache[cache_key] = {ok: true, reason: nil, checked_at: Time.now}
|
|
1042
|
+
return [true, nil]
|
|
1043
|
+
end
|
|
1044
|
+
path = begin
|
|
1045
|
+
Aidp::Util.which(binary)
|
|
1046
|
+
rescue
|
|
1047
|
+
nil
|
|
1048
|
+
end
|
|
1049
|
+
unless path
|
|
1050
|
+
@binary_check_cache[cache_key] = {ok: false, reason: "binary_missing", checked_at: Time.now}
|
|
1051
|
+
return [false, "binary_missing"]
|
|
1052
|
+
end
|
|
1053
|
+
# Light command execution to ensure it responds quickly
|
|
1054
|
+
ok = true
|
|
1055
|
+
reason = nil
|
|
1056
|
+
begin
|
|
1057
|
+
# Use IO.popen to avoid shell injection and impose a short timeout
|
|
1058
|
+
r, w = IO.pipe
|
|
1059
|
+
pid = Process.spawn(binary, "--version", out: w, err: w)
|
|
1060
|
+
w.close
|
|
1061
|
+
deadline = Time.now + 3
|
|
1062
|
+
status = nil
|
|
1063
|
+
while Time.now < deadline
|
|
1064
|
+
pid_done, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
1065
|
+
break if pid_done
|
|
1066
|
+
sleep 0.05
|
|
1067
|
+
end
|
|
1068
|
+
unless status
|
|
1069
|
+
# Timeout -> kill
|
|
1070
|
+
begin
|
|
1071
|
+
Process.kill("TERM", pid)
|
|
1072
|
+
rescue
|
|
1073
|
+
nil
|
|
1074
|
+
end
|
|
1075
|
+
sleep 0.1
|
|
1076
|
+
begin
|
|
1077
|
+
Process.kill("KILL", pid)
|
|
1078
|
+
rescue
|
|
1079
|
+
nil
|
|
1080
|
+
end
|
|
1081
|
+
ok = false
|
|
1082
|
+
reason = "binary_timeout"
|
|
1083
|
+
end
|
|
1084
|
+
output = r.read.to_s
|
|
1085
|
+
r.close
|
|
1086
|
+
if ok && output.strip.empty?
|
|
1087
|
+
# Some CLIs require just calling without args; treat empty as still OK
|
|
1088
|
+
ok = true
|
|
1089
|
+
end
|
|
1090
|
+
rescue => e
|
|
1091
|
+
ok = false
|
|
1092
|
+
reason = e.class.name.downcase.include?("enoent") ? "binary_missing" : "binary_error"
|
|
1093
|
+
end
|
|
1094
|
+
@binary_check_cache[cache_key] = {ok: ok, reason: reason, checked_at: Time.now}
|
|
1095
|
+
[ok, reason]
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
# Summarize health and metrics for dashboard/CLI display
|
|
1099
|
+
def health_dashboard
|
|
1100
|
+
now = Time.now
|
|
1101
|
+
statuses = get_provider_health_status
|
|
1102
|
+
metrics = all_metrics
|
|
1103
|
+
configured = @configuration.configured_providers
|
|
1104
|
+
# Ensure fresh binary probe results in test mode so stubs of Aidp::Util.which take effect
|
|
1105
|
+
if defined?(RSpec) || ENV["RSPEC_RUNNING"]
|
|
1106
|
+
@binary_check_cache.clear
|
|
1107
|
+
end
|
|
1108
|
+
rows_by_normalized = {}
|
|
1109
|
+
configured.each do |prov|
|
|
1110
|
+
# Temporarily hide macos provider until it's user-configurable
|
|
1111
|
+
next if prov == "macos"
|
|
1112
|
+
normalized = normalize_provider_name(prov)
|
|
1113
|
+
cli_ok_prefetch, cli_reason_prefetch = provider_cli_available?(prov)
|
|
1114
|
+
h = statuses[prov] || {}
|
|
1115
|
+
m = metrics[prov] || {}
|
|
1116
|
+
rl = @rate_limit_info[prov]
|
|
1117
|
+
reset_in = (rl && rl[:reset_time]) ? [(rl[:reset_time] - now).to_i, 0].max : nil
|
|
1118
|
+
cb_remaining = if h[:circuit_breaker_open] && h[:circuit_breaker_opened_at]
|
|
1119
|
+
elapsed = now - h[:circuit_breaker_opened_at]
|
|
1120
|
+
rem = @circuit_breaker_timeout - elapsed
|
|
1121
|
+
rem.positive? ? rem.to_i : 0
|
|
1122
|
+
end
|
|
1123
|
+
row = {
|
|
1124
|
+
provider: normalized,
|
|
1125
|
+
installed: provider_installed?(prov),
|
|
1126
|
+
status: h[:status] || (provider_installed?(prov) ? "unknown" : "uninstalled"),
|
|
1127
|
+
unhealthy_reason: h[:unhealthy_reason],
|
|
1128
|
+
available: false, # will set true below only if all checks pass
|
|
1129
|
+
circuit_breaker: h[:circuit_breaker_open] ? "open" : "closed",
|
|
1130
|
+
circuit_breaker_remaining: cb_remaining,
|
|
1131
|
+
rate_limited: !!rl,
|
|
1132
|
+
rate_limit_reset_in: reset_in,
|
|
1133
|
+
total_requests: m[:total_requests] || 0,
|
|
1134
|
+
failed_requests: m[:failed_requests] || 0,
|
|
1135
|
+
success_requests: m[:successful_requests] || 0,
|
|
1136
|
+
total_tokens: m[:total_tokens] || 0,
|
|
1137
|
+
last_used: m[:last_used]
|
|
1138
|
+
}
|
|
1139
|
+
# Incorporate CLI check outcome into reason/availability if failing
|
|
1140
|
+
unless cli_ok_prefetch
|
|
1141
|
+
row[:available] = false
|
|
1142
|
+
row[:unhealthy_reason] ||= cli_reason_prefetch
|
|
1143
|
+
row[:status] = "unhealthy" if row[:status] == "healthy" || row[:status] == "healthy_auth"
|
|
1144
|
+
end
|
|
1145
|
+
if cli_ok_prefetch && is_provider_available?(prov)
|
|
1146
|
+
row[:available] = true
|
|
1147
|
+
end
|
|
1148
|
+
if (existing = rows_by_normalized[normalized])
|
|
1149
|
+
# Merge metrics: sum counts/tokens, keep most severe status, earliest unhealthy reason if any
|
|
1150
|
+
existing[:total_requests] += row[:total_requests]
|
|
1151
|
+
existing[:failed_requests] += row[:failed_requests]
|
|
1152
|
+
existing[:success_requests] += row[:success_requests]
|
|
1153
|
+
existing[:total_tokens] += row[:total_tokens]
|
|
1154
|
+
# If either unavailable then mark unavailable
|
|
1155
|
+
existing[:available] &&= row[:available]
|
|
1156
|
+
# Prefer an unhealthy or circuit breaker status over healthy
|
|
1157
|
+
existing[:status] = merge_status_priority(existing[:status], row[:status])
|
|
1158
|
+
existing[:unhealthy_reason] ||= row[:unhealthy_reason]
|
|
1159
|
+
# Circuit breaker open takes precedence
|
|
1160
|
+
if row[:circuit_breaker] == "open"
|
|
1161
|
+
existing[:circuit_breaker] = "open"
|
|
1162
|
+
existing[:circuit_breaker_remaining] = [existing[:circuit_breaker_remaining].to_i, row[:circuit_breaker_remaining].to_i].max
|
|
1163
|
+
end
|
|
1164
|
+
# Rate limited if any underlying
|
|
1165
|
+
if row[:rate_limited]
|
|
1166
|
+
existing[:rate_limited] = true
|
|
1167
|
+
existing[:rate_limit_reset_in] = [existing[:rate_limit_reset_in].to_i, row[:rate_limit_reset_in].to_i].max
|
|
1168
|
+
end
|
|
1169
|
+
# Keep most recent last_used
|
|
1170
|
+
if row[:last_used] && (!existing[:last_used] || row[:last_used] > existing[:last_used])
|
|
1171
|
+
existing[:last_used] = row[:last_used]
|
|
1172
|
+
end
|
|
1173
|
+
else
|
|
1174
|
+
rows_by_normalized[normalized] = row
|
|
1175
|
+
end
|
|
1176
|
+
end
|
|
1177
|
+
rows_by_normalized.values
|
|
1178
|
+
end
|
|
1179
|
+
|
|
951
1180
|
# Get provider history
|
|
952
1181
|
def provider_history
|
|
953
1182
|
@provider_history.dup
|
|
@@ -1004,7 +1233,9 @@ module Aidp
|
|
|
1004
1233
|
circuit_breaker_open: health[:circuit_breaker_open],
|
|
1005
1234
|
last_updated: health[:last_updated],
|
|
1006
1235
|
last_used: health[:last_used],
|
|
1007
|
-
last_rate_limited: health[:last_rate_limited]
|
|
1236
|
+
last_rate_limited: health[:last_rate_limited],
|
|
1237
|
+
circuit_breaker_opened_at: health[:circuit_breaker_opened_at],
|
|
1238
|
+
unhealthy_reason: health[:unhealthy_reason]
|
|
1008
1239
|
}
|
|
1009
1240
|
end
|
|
1010
1241
|
end
|
|
@@ -1199,20 +1430,43 @@ module Aidp
|
|
|
1199
1430
|
[delay, 60].min # Cap at 60 seconds
|
|
1200
1431
|
end
|
|
1201
1432
|
|
|
1433
|
+
private
|
|
1434
|
+
|
|
1435
|
+
# Normalize provider naming for display (hide legacy 'anthropic')
|
|
1436
|
+
def normalize_provider_name(name)
|
|
1437
|
+
return "claude" if name == "anthropic"
|
|
1438
|
+
name
|
|
1439
|
+
end
|
|
1440
|
+
|
|
1441
|
+
# Status priority for merging duplicate normalized providers
|
|
1442
|
+
def merge_status_priority(a, b)
|
|
1443
|
+
order = {
|
|
1444
|
+
"circuit_breaker_open" => 5,
|
|
1445
|
+
"unhealthy_auth" => 4,
|
|
1446
|
+
"unhealthy" => 3,
|
|
1447
|
+
"unknown" => 2,
|
|
1448
|
+
"healthy" => 1,
|
|
1449
|
+
nil => 0
|
|
1450
|
+
}
|
|
1451
|
+
((order[a] || 0) >= (order[b] || 0)) ? a : b
|
|
1452
|
+
end
|
|
1453
|
+
|
|
1454
|
+
public
|
|
1455
|
+
|
|
1202
1456
|
# Log provider switch
|
|
1203
1457
|
def log_provider_switch(from_provider, to_provider, reason, context)
|
|
1204
|
-
|
|
1458
|
+
display_message("🔄 Provider switch: #{from_provider} → #{to_provider} (#{reason})", type: :info)
|
|
1205
1459
|
if context.any?
|
|
1206
|
-
|
|
1460
|
+
display_message(" Context: #{context.inspect}", type: :muted)
|
|
1207
1461
|
end
|
|
1208
1462
|
end
|
|
1209
1463
|
|
|
1210
1464
|
# Log no providers available
|
|
1211
1465
|
def log_no_providers_available(reason, context)
|
|
1212
|
-
|
|
1213
|
-
|
|
1466
|
+
display_message("❌ No providers available for switching (#{reason})", type: :error)
|
|
1467
|
+
display_message(" All providers are rate limited, unhealthy, or circuit breaker open", type: :warning)
|
|
1214
1468
|
if context.any?
|
|
1215
|
-
|
|
1469
|
+
display_message(" Context: #{context.inspect}", type: :muted)
|
|
1216
1470
|
end
|
|
1217
1471
|
end
|
|
1218
1472
|
|
|
@@ -1220,26 +1474,26 @@ module Aidp
|
|
|
1220
1474
|
def log_circuit_breaker_event(provider_name, event)
|
|
1221
1475
|
case event
|
|
1222
1476
|
when "opened"
|
|
1223
|
-
|
|
1477
|
+
display_message("🔴 Circuit breaker opened for provider: #{provider_name}", type: :error)
|
|
1224
1478
|
when "reset"
|
|
1225
|
-
|
|
1479
|
+
display_message("🟢 Circuit breaker reset for provider: #{provider_name}", type: :success)
|
|
1226
1480
|
end
|
|
1227
1481
|
end
|
|
1228
1482
|
|
|
1229
1483
|
# Log model switch
|
|
1230
1484
|
def log_model_switch(from_model, to_model, reason, context)
|
|
1231
|
-
|
|
1485
|
+
display_message("🔄 Model switch: #{from_model} → #{to_model} (#{reason})", type: :info)
|
|
1232
1486
|
if context.any?
|
|
1233
|
-
|
|
1487
|
+
display_message(" Context: #{context.inspect}", type: :muted)
|
|
1234
1488
|
end
|
|
1235
1489
|
end
|
|
1236
1490
|
|
|
1237
1491
|
# Log no models available
|
|
1238
1492
|
def log_no_models_available(provider_name, reason, context)
|
|
1239
|
-
|
|
1240
|
-
|
|
1493
|
+
display_message("❌ No models available for provider #{provider_name} (#{reason})", type: :error)
|
|
1494
|
+
display_message(" All models are rate limited, unhealthy, or circuit breaker open", type: :warning)
|
|
1241
1495
|
if context.any?
|
|
1242
|
-
|
|
1496
|
+
display_message(" Context: #{context.inspect}", type: :muted)
|
|
1243
1497
|
end
|
|
1244
1498
|
end
|
|
1245
1499
|
|
|
@@ -1247,9 +1501,9 @@ module Aidp
|
|
|
1247
1501
|
def log_model_circuit_breaker_event(provider_name, model_name, event)
|
|
1248
1502
|
case event
|
|
1249
1503
|
when "opened"
|
|
1250
|
-
|
|
1504
|
+
display_message("🔴 Circuit breaker opened for model: #{provider_name}:#{model_name}", type: :error)
|
|
1251
1505
|
when "reset"
|
|
1252
|
-
|
|
1506
|
+
display_message("🟢 Circuit breaker reset for model: #{provider_name}:#{model_name}", type: :success)
|
|
1253
1507
|
end
|
|
1254
1508
|
end
|
|
1255
1509
|
|
data/lib/aidp/harness/runner.rb
CHANGED
|
@@ -15,6 +15,8 @@ module Aidp
|
|
|
15
15
|
module Harness
|
|
16
16
|
# Main harness runner that orchestrates the execution loop
|
|
17
17
|
class Runner
|
|
18
|
+
include Aidp::MessageDisplay
|
|
19
|
+
|
|
18
20
|
# Harness execution states
|
|
19
21
|
STATES = {
|
|
20
22
|
idle: "idle",
|
|
@@ -27,6 +29,9 @@ module Aidp
|
|
|
27
29
|
error: "error"
|
|
28
30
|
}.freeze
|
|
29
31
|
|
|
32
|
+
# Public accessors for testing and integration
|
|
33
|
+
attr_reader :current_provider, :current_step, :user_input, :execution_log, :provider_manager
|
|
34
|
+
|
|
30
35
|
def initialize(project_dir, mode = :analyze, options = {})
|
|
31
36
|
@project_dir = project_dir
|
|
32
37
|
@mode = mode.to_sym
|
|
@@ -35,8 +40,9 @@ module Aidp
|
|
|
35
40
|
@start_time = nil
|
|
36
41
|
@current_step = nil
|
|
37
42
|
@current_provider = nil
|
|
38
|
-
@user_input = options[:user_input] || {}
|
|
43
|
+
@user_input = options[:user_input] || {} # Include user input from workflow selection
|
|
39
44
|
@execution_log = []
|
|
45
|
+
@prompt = options[:prompt] || TTY::Prompt.new
|
|
40
46
|
|
|
41
47
|
# Store workflow configuration
|
|
42
48
|
@selected_steps = options[:selected_steps]
|
|
@@ -46,7 +52,7 @@ module Aidp
|
|
|
46
52
|
@configuration = Configuration.new(project_dir)
|
|
47
53
|
@state_manager = StateManager.new(project_dir, @mode)
|
|
48
54
|
@condition_detector = ConditionDetector.new
|
|
49
|
-
@provider_manager = ProviderManager.new(@configuration)
|
|
55
|
+
@provider_manager = ProviderManager.new(@configuration, prompt: @prompt)
|
|
50
56
|
@user_interface = SimpleUserInterface.new
|
|
51
57
|
@error_handler = ErrorHandler.new(@provider_manager, @configuration)
|
|
52
58
|
@status_display = StatusDisplay.new
|
|
@@ -96,8 +102,8 @@ module Aidp
|
|
|
96
102
|
log_execution("Harness completed successfully - all criteria met", completion_status)
|
|
97
103
|
else
|
|
98
104
|
log_execution("Steps completed but completion criteria not met", completion_status)
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
display_message("\n⚠️ All steps completed but some completion criteria not met:", type: :warning)
|
|
106
|
+
display_message(completion_status[:summary], type: :info)
|
|
101
107
|
|
|
102
108
|
# Ask user if they want to continue anyway
|
|
103
109
|
if @user_interface.get_confirmation("Continue anyway? This may indicate issues that should be addressed.", default: false)
|
|
@@ -181,9 +187,9 @@ module Aidp
|
|
|
181
187
|
def get_mode_runner
|
|
182
188
|
case @mode
|
|
183
189
|
when :analyze
|
|
184
|
-
Aidp::Analyze::Runner.new(@project_dir, self)
|
|
190
|
+
Aidp::Analyze::Runner.new(@project_dir, self, prompt: TTY::Prompt.new)
|
|
185
191
|
when :execute
|
|
186
|
-
Aidp::Execute::Runner.new(@project_dir, self)
|
|
192
|
+
Aidp::Execute::Runner.new(@project_dir, self, prompt: TTY::Prompt.new)
|
|
187
193
|
else
|
|
188
194
|
raise ArgumentError, "Unsupported mode: #{@mode}"
|
|
189
195
|
end
|
|
@@ -406,6 +412,8 @@ module Aidp
|
|
|
406
412
|
"Harness finished in state: #{@state}"
|
|
407
413
|
end
|
|
408
414
|
end
|
|
415
|
+
|
|
416
|
+
private
|
|
409
417
|
end
|
|
410
418
|
end
|
|
411
419
|
end
|
|
@@ -7,6 +7,8 @@ module Aidp
|
|
|
7
7
|
# Simple, focused user interface for collecting feedback
|
|
8
8
|
# Replaces the bloated UserInterface with minimal, clean code
|
|
9
9
|
class SimpleUserInterface
|
|
10
|
+
include Aidp::MessageDisplay
|
|
11
|
+
|
|
10
12
|
def initialize(prompt: TTY::Prompt.new)
|
|
11
13
|
@prompt = prompt
|
|
12
14
|
end
|
|
@@ -27,9 +29,9 @@ module Aidp
|
|
|
27
29
|
private
|
|
28
30
|
|
|
29
31
|
def show_context(context)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
display_message("\n🤖 Agent needs feedback", type: :info)
|
|
33
|
+
display_message("Context: #{context[:description]}", type: :info) if context[:description]
|
|
34
|
+
display_message("", type: :info)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def ask_question(question_data)
|
|
@@ -39,7 +41,7 @@ module Aidp
|
|
|
39
41
|
required = question_data[:required] != false
|
|
40
42
|
options = question_data[:options]
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
display_message("\n#{question}", type: :info)
|
|
43
45
|
|
|
44
46
|
case type
|
|
45
47
|
when "text"
|