aidp 0.26.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.
- checksums.yaml +4 -4
- data/lib/aidp/cli/checkpoint_command.rb +198 -0
- data/lib/aidp/cli/config_command.rb +71 -0
- data/lib/aidp/cli/enhanced_input.rb +2 -0
- data/lib/aidp/cli/first_run_wizard.rb +8 -7
- data/lib/aidp/cli/harness_command.rb +102 -0
- data/lib/aidp/cli/jobs_command.rb +3 -3
- data/lib/aidp/cli/mcp_dashboard.rb +4 -3
- data/lib/aidp/cli/models_command.rb +662 -0
- data/lib/aidp/cli/providers_command.rb +223 -0
- data/lib/aidp/cli.rb +35 -456
- data/lib/aidp/daemon/runner.rb +2 -2
- data/lib/aidp/debug_mixin.rb +2 -9
- data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
- data/lib/aidp/execute/checkpoint_display.rb +38 -37
- data/lib/aidp/execute/interactive_repl.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +4 -4
- data/lib/aidp/execute/work_loop_runner.rb +29 -2
- data/lib/aidp/execute/workflow_selector.rb +2 -2
- data/lib/aidp/harness/config_manager.rb +5 -5
- data/lib/aidp/harness/configuration.rb +32 -2
- data/lib/aidp/harness/enhanced_runner.rb +24 -15
- data/lib/aidp/harness/error_handler.rb +26 -5
- data/lib/aidp/harness/model_cache.rb +269 -0
- data/lib/aidp/harness/model_discovery_service.rb +259 -0
- data/lib/aidp/harness/model_registry.rb +201 -0
- data/lib/aidp/harness/runner.rb +5 -0
- data/lib/aidp/harness/thinking_depth_manager.rb +223 -7
- data/lib/aidp/message_display.rb +0 -46
- data/lib/aidp/providers/adapter.rb +2 -4
- data/lib/aidp/providers/anthropic.rb +141 -128
- data/lib/aidp/providers/base.rb +98 -2
- data/lib/aidp/providers/capability_registry.rb +0 -1
- data/lib/aidp/providers/codex.rb +49 -67
- data/lib/aidp/providers/cursor.rb +71 -59
- data/lib/aidp/providers/gemini.rb +44 -60
- data/lib/aidp/providers/github_copilot.rb +2 -66
- data/lib/aidp/providers/kilocode.rb +24 -80
- data/lib/aidp/providers/opencode.rb +24 -80
- data/lib/aidp/setup/wizard.rb +345 -8
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/plan_generator.rb +93 -14
- data/lib/aidp/watch/review_processor.rb +3 -3
- data/lib/aidp/workflows/guided_agent.rb +3 -3
- data/templates/aidp-development.yml.example +2 -2
- data/templates/aidp-production.yml.example +3 -3
- metadata +9 -1
|
@@ -8,17 +8,18 @@ module Aidp
|
|
|
8
8
|
class CheckpointDisplay
|
|
9
9
|
include Aidp::MessageDisplay
|
|
10
10
|
|
|
11
|
-
def initialize
|
|
11
|
+
def initialize(prompt: nil)
|
|
12
12
|
@pastel = Pastel.new
|
|
13
|
+
@prompt = prompt || TTY::Prompt.new
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
# Display a checkpoint during work loop iteration
|
|
16
17
|
def display_checkpoint(checkpoint_data, show_details: false)
|
|
17
18
|
return unless checkpoint_data
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
@prompt.say("")
|
|
21
|
+
@prompt.say(@pastel.bold("📊 Checkpoint - Iteration #{checkpoint_data[:iteration]}"))
|
|
22
|
+
@prompt.say(@pastel.dim("─" * 60))
|
|
22
23
|
|
|
23
24
|
display_metrics(checkpoint_data[:metrics])
|
|
24
25
|
display_status(checkpoint_data[:status])
|
|
@@ -27,52 +28,52 @@ module Aidp
|
|
|
27
28
|
display_trends(checkpoint_data[:trends]) if checkpoint_data[:trends]
|
|
28
29
|
end
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
@prompt.say(@pastel.dim("─" * 60))
|
|
32
|
+
@prompt.say("")
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
# Display progress summary with trends
|
|
35
36
|
def display_progress_summary(summary)
|
|
36
37
|
return unless summary
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
@prompt.say("")
|
|
40
|
+
@prompt.say(@pastel.bold("📈 Progress Summary"))
|
|
41
|
+
@prompt.say(@pastel.dim("=" * 60))
|
|
41
42
|
|
|
42
43
|
current = summary[:current]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
@prompt.say("Step: #{@pastel.cyan(current[:step_name])}")
|
|
45
|
+
@prompt.say("Iteration: #{current[:iteration]}")
|
|
46
|
+
@prompt.say("Status: #{format_status(current[:status])}")
|
|
47
|
+
@prompt.say("")
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
@prompt.say(@pastel.bold("Current Metrics:"))
|
|
49
50
|
display_metrics(current[:metrics])
|
|
50
51
|
|
|
51
52
|
if summary[:trends]
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
@prompt.say("")
|
|
54
|
+
@prompt.say(@pastel.bold("Trends:"))
|
|
54
55
|
display_trends(summary[:trends])
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
if summary[:quality_score]
|
|
58
|
-
|
|
59
|
+
@prompt.say("")
|
|
59
60
|
display_quality_score(summary[:quality_score])
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
@prompt.say(@pastel.dim("=" * 60))
|
|
64
|
+
@prompt.say("")
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
# Display checkpoint history as a table
|
|
67
68
|
def display_checkpoint_history(history, limit: 10)
|
|
68
69
|
return if history.empty?
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
@prompt.say("")
|
|
72
|
+
@prompt.say(@pastel.bold("📜 Checkpoint History (Last #{[limit, history.size].min})"))
|
|
73
|
+
@prompt.say(@pastel.dim("=" * 80))
|
|
73
74
|
|
|
74
75
|
# Table header
|
|
75
|
-
|
|
76
|
+
@prompt.say(format_table_row([
|
|
76
77
|
"Iteration",
|
|
77
78
|
"Time",
|
|
78
79
|
"LOC",
|
|
@@ -80,15 +81,15 @@ module Aidp
|
|
|
80
81
|
"Quality",
|
|
81
82
|
"PRD Progress",
|
|
82
83
|
"Status"
|
|
83
|
-
], header: true)
|
|
84
|
-
|
|
84
|
+
], header: true))
|
|
85
|
+
@prompt.say(@pastel.dim("-" * 80))
|
|
85
86
|
|
|
86
87
|
# Table rows
|
|
87
88
|
history.last(limit).each do |checkpoint|
|
|
88
89
|
metrics = checkpoint[:metrics]
|
|
89
90
|
timestamp = Time.parse(checkpoint[:timestamp]).strftime("%H:%M:%S")
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
@prompt.say(format_table_row([
|
|
92
93
|
checkpoint[:iteration].to_s,
|
|
93
94
|
timestamp,
|
|
94
95
|
metrics[:lines_of_code].to_s,
|
|
@@ -96,11 +97,11 @@ module Aidp
|
|
|
96
97
|
"#{metrics[:code_quality]}%",
|
|
97
98
|
"#{metrics[:prd_task_progress]}%",
|
|
98
99
|
format_status(checkpoint[:status])
|
|
99
|
-
])
|
|
100
|
+
]))
|
|
100
101
|
end
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
@prompt.say(@pastel.dim("=" * 80))
|
|
104
|
+
@prompt.say("")
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
# Display inline progress indicator (for work loop)
|
|
@@ -124,15 +125,15 @@ module Aidp
|
|
|
124
125
|
private
|
|
125
126
|
|
|
126
127
|
def display_metrics(metrics)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
@prompt.say(" Lines of Code: #{@pastel.yellow(metrics[:lines_of_code].to_s)}")
|
|
129
|
+
@prompt.say(" Test Coverage: #{format_percentage_with_color(metrics[:test_coverage])}")
|
|
130
|
+
@prompt.say(" Code Quality: #{format_percentage_with_color(metrics[:code_quality])}")
|
|
131
|
+
@prompt.say(" PRD Task Progress: #{format_percentage_with_color(metrics[:prd_task_progress])}")
|
|
132
|
+
@prompt.say(" File Count: #{metrics[:file_count]}")
|
|
132
133
|
end
|
|
133
134
|
|
|
134
135
|
def display_status(status)
|
|
135
|
-
|
|
136
|
+
@prompt.say(" Overall Status: #{format_status(status)}")
|
|
136
137
|
end
|
|
137
138
|
|
|
138
139
|
def format_status(status)
|
|
@@ -156,7 +157,7 @@ module Aidp
|
|
|
156
157
|
arrow = trend_arrow(trend_data[:direction])
|
|
157
158
|
change = format_change(trend_data[:change], trend_data[:change_percent])
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
@prompt.say(" #{metric_name}: #{arrow} #{change}")
|
|
160
161
|
end
|
|
161
162
|
end
|
|
162
163
|
|
|
@@ -185,7 +186,7 @@ module Aidp
|
|
|
185
186
|
:red
|
|
186
187
|
end
|
|
187
188
|
|
|
188
|
-
|
|
189
|
+
@prompt.say(" Quality Score: #{@pastel.send(color, "#{score.round(2)}%")}")
|
|
189
190
|
end
|
|
190
191
|
|
|
191
192
|
def format_percentage(value)
|
|
@@ -29,6 +29,7 @@ module Aidp
|
|
|
29
29
|
@config = config
|
|
30
30
|
@options = options
|
|
31
31
|
@prompt = options[:prompt] || TTY::Prompt.new
|
|
32
|
+
@async_runner_class = options[:async_runner_class] || AsyncWorkLoopRunner
|
|
32
33
|
@async_runner = nil
|
|
33
34
|
@repl_macros = ReplMacros.new
|
|
34
35
|
@output_display_thread = nil
|
|
@@ -38,7 +39,7 @@ module Aidp
|
|
|
38
39
|
|
|
39
40
|
# Start work loop and enter interactive REPL
|
|
40
41
|
def start_work_loop(step_name, step_spec, context = {})
|
|
41
|
-
@async_runner =
|
|
42
|
+
@async_runner = @async_runner_class.new(
|
|
42
43
|
@project_dir,
|
|
43
44
|
@provider_manager,
|
|
44
45
|
@config,
|
|
@@ -17,20 +17,20 @@ module Aidp
|
|
|
17
17
|
|
|
18
18
|
attr_reader :optimizer, :last_optimization_stats
|
|
19
19
|
|
|
20
|
-
def initialize(project_dir, config: nil)
|
|
20
|
+
def initialize(project_dir, config: nil, optimizer: nil)
|
|
21
21
|
@project_dir = project_dir
|
|
22
22
|
@aidp_dir = File.join(project_dir, ".aidp")
|
|
23
23
|
@prompt_path = File.join(@aidp_dir, PROMPT_FILENAME)
|
|
24
24
|
@archive_dir = File.join(project_dir, ARCHIVE_DIR)
|
|
25
25
|
@config = config
|
|
26
|
-
@optimizer =
|
|
26
|
+
@optimizer = optimizer
|
|
27
27
|
@last_optimization_stats = nil
|
|
28
28
|
|
|
29
29
|
# Ensure .aidp directory exists
|
|
30
30
|
FileUtils.mkdir_p(@aidp_dir)
|
|
31
31
|
|
|
32
|
-
# Initialize optimizer if enabled
|
|
33
|
-
if config&.respond_to?(:prompt_optimization_enabled?) && config.prompt_optimization_enabled?
|
|
32
|
+
# Initialize optimizer if enabled and not provided
|
|
33
|
+
if @optimizer.nil? && config&.respond_to?(:prompt_optimization_enabled?) && config.prompt_optimization_enabled?
|
|
34
34
|
@optimizer = Aidp::PromptOptimization::Optimizer.new(
|
|
35
35
|
project_dir: project_dir,
|
|
36
36
|
config: config.prompt_optimization_config
|
|
@@ -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
|
|
@@ -62,7 +64,7 @@ module Aidp
|
|
|
62
64
|
|
|
63
65
|
# Initialize thinking depth manager for intelligent model selection
|
|
64
66
|
require_relative "../harness/thinking_depth_manager"
|
|
65
|
-
@thinking_depth_manager = Aidp::Harness::ThinkingDepthManager.new(config)
|
|
67
|
+
@thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(config)
|
|
66
68
|
@consecutive_failures = 0
|
|
67
69
|
@last_tier = nil
|
|
68
70
|
end
|
|
@@ -160,6 +162,10 @@ module Aidp
|
|
|
160
162
|
# Wrap agent call in exception handling for true fix-forward
|
|
161
163
|
begin
|
|
162
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
|
|
163
169
|
rescue => e
|
|
164
170
|
# Convert exception to error result for fix-forward handling
|
|
165
171
|
Aidp.logger.error("work_loop", "Exception during agent call",
|
|
@@ -178,6 +184,27 @@ module Aidp
|
|
|
178
184
|
next
|
|
179
185
|
end
|
|
180
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
|
+
|
|
181
208
|
# Process agent output for task filing signals
|
|
182
209
|
process_task_filing(agent_result)
|
|
183
210
|
|
|
@@ -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
|
|
@@ -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.
|
|
362
|
+
@loader.mode_config(options[:mode], options[:force_reload])
|
|
363
363
|
elsif options[:environment]
|
|
364
|
-
@loader.
|
|
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.
|
|
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.
|
|
372
|
+
@loader.time_based_config(options[:force_reload])
|
|
373
373
|
elsif options[:overrides]
|
|
374
|
-
@loader.
|
|
374
|
+
@loader.config_with_overrides(options[:overrides])
|
|
375
375
|
else
|
|
376
376
|
@loader.load_config(options[:force_reload])
|
|
377
377
|
end
|
|
@@ -383,12 +383,12 @@ module Aidp
|
|
|
383
383
|
|
|
384
384
|
# Get default thinking tier
|
|
385
385
|
def default_tier
|
|
386
|
-
thinking_config[:default_tier] ||
|
|
386
|
+
thinking_config[:default_tier] || default_thinking_config[:default_tier]
|
|
387
387
|
end
|
|
388
388
|
|
|
389
389
|
# Get maximum thinking tier
|
|
390
390
|
def max_tier
|
|
391
|
-
thinking_config[:max_tier] ||
|
|
391
|
+
thinking_config[:max_tier] || default_thinking_config[:max_tier]
|
|
392
392
|
end
|
|
393
393
|
|
|
394
394
|
# Check if provider switching for tier is allowed
|
|
@@ -432,6 +432,36 @@ module Aidp
|
|
|
432
432
|
thinking_overrides[key] || thinking_overrides[key.to_sym]
|
|
433
433
|
end
|
|
434
434
|
|
|
435
|
+
# Get tiers configuration from user's config
|
|
436
|
+
def thinking_tiers_config
|
|
437
|
+
thinking_config[:tiers] || {}
|
|
438
|
+
end
|
|
439
|
+
|
|
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]
|
|
444
|
+
return [] unless tier_config
|
|
445
|
+
|
|
446
|
+
models = tier_config[:models] || tier_config["models"]
|
|
447
|
+
return [] unless models
|
|
448
|
+
|
|
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
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Get all configured tiers
|
|
461
|
+
def configured_tiers
|
|
462
|
+
thinking_tiers_config.keys.map(&:to_s)
|
|
463
|
+
end
|
|
464
|
+
|
|
435
465
|
# Get fallback configuration
|
|
436
466
|
def fallback_config
|
|
437
467
|
harness_config[:fallback] || default_fallback_config
|
|
@@ -6,6 +6,7 @@ require_relative "ui/job_monitor"
|
|
|
6
6
|
require_relative "ui/workflow_controller"
|
|
7
7
|
require_relative "ui/progress_display"
|
|
8
8
|
require_relative "ui/status_widget"
|
|
9
|
+
require_relative "../errors"
|
|
9
10
|
|
|
10
11
|
module Aidp
|
|
11
12
|
module Harness
|
|
@@ -47,27 +48,31 @@ module Aidp
|
|
|
47
48
|
@selected_steps = options[:selected_steps] || []
|
|
48
49
|
@workflow_type = options[:workflow_type] || :default
|
|
49
50
|
|
|
50
|
-
# Initialize enhanced TUI components
|
|
51
|
-
@tui = UI::EnhancedTUI.new
|
|
52
|
-
@workflow_selector = UI::EnhancedWorkflowSelector.new(@tui)
|
|
53
|
-
@job_monitor = UI::JobMonitor.new
|
|
54
|
-
@workflow_controller = UI::WorkflowController.new
|
|
55
|
-
@progress_display = UI::ProgressDisplay.new
|
|
56
|
-
@status_widget = UI::StatusWidget.new
|
|
51
|
+
# Initialize enhanced TUI components (with dependency injection)
|
|
52
|
+
@tui = options[:tui] || UI::EnhancedTUI.new
|
|
53
|
+
@workflow_selector = options[:workflow_selector] || UI::EnhancedWorkflowSelector.new(@tui)
|
|
54
|
+
@job_monitor = options[:job_monitor] || UI::JobMonitor.new
|
|
55
|
+
@workflow_controller = options[:workflow_controller] || UI::WorkflowController.new
|
|
56
|
+
@progress_display = options[:progress_display] || UI::ProgressDisplay.new
|
|
57
|
+
@status_widget = options[:status_widget] || UI::StatusWidget.new
|
|
57
58
|
|
|
58
|
-
# Initialize other components
|
|
59
|
-
@configuration = Configuration.new(project_dir)
|
|
60
|
-
@state_manager = StateManager.new(project_dir, @mode)
|
|
61
|
-
@provider_manager = ProviderManager.new(@configuration, prompt: @prompt)
|
|
59
|
+
# Initialize other components (with dependency injection)
|
|
60
|
+
@configuration = options[:configuration] || Configuration.new(project_dir)
|
|
61
|
+
@state_manager = options[:state_manager] || StateManager.new(project_dir, @mode)
|
|
62
|
+
@provider_manager = options[:provider_manager] || ProviderManager.new(@configuration, prompt: @prompt)
|
|
62
63
|
|
|
63
64
|
# Use ZFC-enabled condition detector
|
|
64
65
|
# ZfcConditionDetector will create its own ProviderFactory if needed
|
|
65
66
|
# Falls back to legacy pattern matching when ZFC is disabled
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
if options[:condition_detector]
|
|
68
|
+
@condition_detector = options[:condition_detector]
|
|
69
|
+
else
|
|
70
|
+
require_relative "zfc_condition_detector"
|
|
71
|
+
@condition_detector = ZfcConditionDetector.new(@configuration)
|
|
72
|
+
end
|
|
68
73
|
|
|
69
|
-
@error_handler = ErrorHandler.new(@provider_manager, @configuration)
|
|
70
|
-
@completion_checker = CompletionChecker.new(@project_dir, @workflow_type)
|
|
74
|
+
@error_handler = options[:error_handler] || ErrorHandler.new(@provider_manager, @configuration)
|
|
75
|
+
@completion_checker = options[:completion_checker] || CompletionChecker.new(@project_dir, @workflow_type)
|
|
71
76
|
end
|
|
72
77
|
|
|
73
78
|
# Get current provider (delegate to provider manager)
|
|
@@ -158,6 +163,10 @@ module Aidp
|
|
|
158
163
|
handle_completion_criteria_not_met(completion_status)
|
|
159
164
|
end
|
|
160
165
|
end
|
|
166
|
+
rescue Aidp::Errors::ConfigurationError
|
|
167
|
+
# Configuration errors should crash immediately (crash-early principle)
|
|
168
|
+
# Re-raise without catching
|
|
169
|
+
raise
|
|
161
170
|
rescue => e
|
|
162
171
|
@state = STATES[:error]
|
|
163
172
|
# Single error message - don't duplicate
|
|
@@ -4,6 +4,7 @@ require "net/http"
|
|
|
4
4
|
require_relative "../debug_mixin"
|
|
5
5
|
require_relative "../concurrency"
|
|
6
6
|
require_relative "../providers/error_taxonomy"
|
|
7
|
+
require_relative "../errors"
|
|
7
8
|
|
|
8
9
|
module Aidp
|
|
9
10
|
module Harness
|
|
@@ -107,6 +108,10 @@ module Aidp
|
|
|
107
108
|
begin
|
|
108
109
|
attempt += 1
|
|
109
110
|
return yield
|
|
111
|
+
rescue Aidp::Errors::ConfigurationError
|
|
112
|
+
# Configuration errors should crash immediately (crash-early principle)
|
|
113
|
+
# Re-raise without catching
|
|
114
|
+
raise
|
|
110
115
|
rescue => error
|
|
111
116
|
current_provider = current_provider_safely
|
|
112
117
|
|
|
@@ -483,7 +488,7 @@ module Aidp
|
|
|
483
488
|
}
|
|
484
489
|
end
|
|
485
490
|
|
|
486
|
-
def attempt_provider_switch(error_info,
|
|
491
|
+
def attempt_provider_switch(error_info, recovery_plan)
|
|
487
492
|
new_provider = @provider_manager.switch_provider_for_error(
|
|
488
493
|
error_info[:error_type],
|
|
489
494
|
error_info[:context]
|
|
@@ -497,6 +502,18 @@ module Aidp
|
|
|
497
502
|
reason: "Error recovery: #{error_info[:error_type]}"
|
|
498
503
|
}
|
|
499
504
|
else
|
|
505
|
+
# If this is an auth error and we have no fallback providers, crash
|
|
506
|
+
if recovery_plan[:crash_if_no_fallback]
|
|
507
|
+
error_msg = "All providers have failed authentication.\n\n" \
|
|
508
|
+
"Last provider: #{error_info[:provider]}\n" \
|
|
509
|
+
"Error: #{error_info[:error]&.message || error_info[:error]}\n\n" \
|
|
510
|
+
"Please check your API credentials for all configured providers.\n" \
|
|
511
|
+
"Run 'aidp config --interactive' to update credentials."
|
|
512
|
+
|
|
513
|
+
raise Aidp::Errors::ConfigurationError, error_msg
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# For non-auth errors, return failure result
|
|
500
517
|
{
|
|
501
518
|
success: false,
|
|
502
519
|
action: :provider_switch_failed,
|
|
@@ -680,12 +697,13 @@ module Aidp
|
|
|
680
697
|
priority: :high
|
|
681
698
|
}
|
|
682
699
|
when :auth_expired
|
|
683
|
-
#
|
|
684
|
-
#
|
|
700
|
+
# Try to switch to another provider. If no providers available, this will
|
|
701
|
+
# be detected in attempt_recovery and we'll crash (crash-early principle)
|
|
685
702
|
{
|
|
686
703
|
action: :switch_provider,
|
|
687
704
|
reason: "Authentication expired – switching provider to continue",
|
|
688
|
-
priority: :critical
|
|
705
|
+
priority: :critical,
|
|
706
|
+
crash_if_no_fallback: true
|
|
689
707
|
}
|
|
690
708
|
when :quota_exceeded
|
|
691
709
|
{
|
|
@@ -725,10 +743,13 @@ module Aidp
|
|
|
725
743
|
priority: :medium
|
|
726
744
|
}
|
|
727
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)
|
|
728
748
|
{
|
|
729
749
|
action: :switch_provider,
|
|
730
750
|
reason: "Authentication/permission issue – switching provider to continue",
|
|
731
|
-
priority: :critical
|
|
751
|
+
priority: :critical,
|
|
752
|
+
crash_if_no_fallback: true
|
|
732
753
|
}
|
|
733
754
|
when :rate_limit
|
|
734
755
|
{
|