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,32 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "tty-prompt"
|
|
4
|
+
require_relative "../workflows/definitions"
|
|
5
|
+
require_relative "../workflows/selector"
|
|
4
6
|
|
|
5
7
|
module Aidp
|
|
6
8
|
module Execute
|
|
7
9
|
# Handles interactive workflow selection and project setup
|
|
8
10
|
class WorkflowSelector
|
|
11
|
+
include Aidp::MessageDisplay
|
|
12
|
+
|
|
9
13
|
def initialize(prompt: TTY::Prompt.new)
|
|
10
14
|
@user_input = {}
|
|
11
15
|
@prompt = prompt
|
|
16
|
+
@workflow_selector = Aidp::Workflows::Selector.new(prompt: @prompt)
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
# Main entry point for interactive workflow selection
|
|
15
|
-
def select_workflow(harness_mode: false)
|
|
20
|
+
def select_workflow(harness_mode: false, use_new_selector: true, mode: nil)
|
|
16
21
|
if harness_mode
|
|
17
22
|
# In harness mode, use default values to avoid blocking
|
|
18
23
|
select_workflow_with_defaults
|
|
24
|
+
elsif use_new_selector
|
|
25
|
+
# Use new unified workflow selector (default as of Issue #79)
|
|
26
|
+
select_workflow_with_new_selector(mode)
|
|
19
27
|
else
|
|
20
|
-
#
|
|
28
|
+
# Legacy interactive mode for backward compatibility
|
|
21
29
|
select_workflow_interactive
|
|
22
30
|
end
|
|
23
31
|
end
|
|
24
32
|
|
|
25
33
|
private
|
|
26
34
|
|
|
35
|
+
def select_workflow_with_new_selector(mode = nil)
|
|
36
|
+
# Step 1: Collect project information (still useful for all workflows)
|
|
37
|
+
collect_project_info
|
|
38
|
+
|
|
39
|
+
# Step 2: Use new workflow selector, defaulting to execute mode if not specified
|
|
40
|
+
workflow_mode = mode || :execute
|
|
41
|
+
result = @workflow_selector.select_workflow(workflow_mode)
|
|
42
|
+
|
|
43
|
+
{
|
|
44
|
+
workflow_type: result[:workflow_key],
|
|
45
|
+
steps: result[:steps],
|
|
46
|
+
user_input: @user_input,
|
|
47
|
+
workflow: result[:workflow]
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
27
51
|
def select_workflow_interactive
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
display_message("\n🚀 Welcome to AI Dev Pipeline!", type: :highlight)
|
|
53
|
+
display_message("Let's set up your development workflow.\n\n")
|
|
30
54
|
|
|
31
55
|
# Step 1: Collect project information
|
|
32
56
|
collect_project_info
|
|
@@ -45,7 +69,7 @@ module Aidp
|
|
|
45
69
|
end
|
|
46
70
|
|
|
47
71
|
def select_workflow_with_defaults
|
|
48
|
-
|
|
72
|
+
display_message("\n🚀 Starting harness with default workflow configuration...", type: :highlight)
|
|
49
73
|
|
|
50
74
|
# Use default project information
|
|
51
75
|
@user_input = {
|
|
@@ -71,7 +95,7 @@ module Aidp
|
|
|
71
95
|
private
|
|
72
96
|
|
|
73
97
|
def collect_project_info
|
|
74
|
-
|
|
98
|
+
display_message("📋 First, tell us about your project:\n", type: :highlight)
|
|
75
99
|
|
|
76
100
|
@user_input[:project_description] = prompt_required(
|
|
77
101
|
"What do you want to build? (Be specific about features and goals)"
|
|
@@ -91,17 +115,17 @@ module Aidp
|
|
|
91
115
|
end
|
|
92
116
|
|
|
93
117
|
def choose_workflow_type
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
118
|
+
display_message("\n🎯 Choose your development approach:\n", type: :highlight)
|
|
119
|
+
display_message("1. 🔬 Exploration/Experiment - Quick prototype or proof of concept")
|
|
120
|
+
display_message(" • Fast iteration, minimal documentation", type: :muted)
|
|
121
|
+
display_message(" • Focus on core functionality and validation", type: :muted)
|
|
122
|
+
display_message(" • Steps: PRD → Tasks → Implementation", type: :muted)
|
|
123
|
+
display_message("")
|
|
124
|
+
display_message("2. 🏗️ Full Development - Production-ready feature or system")
|
|
125
|
+
display_message(" • Comprehensive planning and documentation", type: :muted)
|
|
126
|
+
display_message(" • You can customize which steps to include", type: :muted)
|
|
127
|
+
display_message(" • Full enterprise workflow available", type: :muted)
|
|
128
|
+
display_message("")
|
|
105
129
|
|
|
106
130
|
choice = prompt_choice("Which approach fits your project?", ["1", "2", "exploration", "full"])
|
|
107
131
|
|
|
@@ -111,7 +135,7 @@ module Aidp
|
|
|
111
135
|
when "2", "full"
|
|
112
136
|
:full
|
|
113
137
|
else
|
|
114
|
-
|
|
138
|
+
display_message("Invalid choice. Defaulting to exploration workflow.", type: :warning)
|
|
115
139
|
:exploration
|
|
116
140
|
end
|
|
117
141
|
end
|
|
@@ -129,16 +153,16 @@ module Aidp
|
|
|
129
153
|
|
|
130
154
|
def exploration_workflow_steps
|
|
131
155
|
[
|
|
132
|
-
"00_PRD",
|
|
156
|
+
"00_PRD", # Generate PRD from user input (no manual gate)
|
|
133
157
|
"10_TESTING_STRATEGY", # Ensure we have tests
|
|
134
|
-
"11_STATIC_ANALYSIS",
|
|
135
|
-
"16_IMPLEMENTATION"
|
|
158
|
+
"11_STATIC_ANALYSIS", # Code quality
|
|
159
|
+
"16_IMPLEMENTATION" # Special step for actual development work
|
|
136
160
|
]
|
|
137
161
|
end
|
|
138
162
|
|
|
139
163
|
def full_workflow_steps
|
|
140
|
-
|
|
141
|
-
|
|
164
|
+
display_message("\n🛠️ Customize your full development workflow:\n", type: :highlight)
|
|
165
|
+
display_message("Select the steps you want to include (enter numbers separated by commas):\n")
|
|
142
166
|
|
|
143
167
|
available_steps = {
|
|
144
168
|
"1" => "00_PRD - Product Requirements Document",
|
|
@@ -155,8 +179,8 @@ module Aidp
|
|
|
155
179
|
"12" => "13_DELIVERY_ROLLOUT - Deployment Planning"
|
|
156
180
|
}
|
|
157
181
|
|
|
158
|
-
available_steps.each { |num, desc|
|
|
159
|
-
|
|
182
|
+
available_steps.each { |num, desc| display_message(" #{num}. #{desc}") }
|
|
183
|
+
display_message("")
|
|
160
184
|
|
|
161
185
|
selected = prompt_required("Enter step numbers (e.g., 1,3,5,9,10): ")
|
|
162
186
|
selected_numbers = selected.split(",").map(&:strip).map(&:to_i)
|
|
@@ -193,7 +217,7 @@ module Aidp
|
|
|
193
217
|
input = @prompt.ask("#{question}:")
|
|
194
218
|
|
|
195
219
|
if input.nil? || input.strip.empty?
|
|
196
|
-
|
|
220
|
+
display_message("❌ This field is required. Please provide an answer.", type: :error)
|
|
197
221
|
next
|
|
198
222
|
end
|
|
199
223
|
|
|
@@ -1310,13 +1310,13 @@ module Aidp
|
|
|
1310
1310
|
# Get timeout duration for operation type
|
|
1311
1311
|
def get_timeout_duration(operation_type, configuration = nil)
|
|
1312
1312
|
default_timeouts = {
|
|
1313
|
-
analyze: 300,
|
|
1314
|
-
execute: 600,
|
|
1313
|
+
analyze: 300, # 5 minutes
|
|
1314
|
+
execute: 600, # 10 minutes
|
|
1315
1315
|
provider_call: 120, # 2 minutes
|
|
1316
1316
|
file_operation: 30, # 30 seconds
|
|
1317
1317
|
network_request: 60, # 1 minute
|
|
1318
|
-
user_input: 300,
|
|
1319
|
-
default: 120
|
|
1318
|
+
user_input: 300, # 5 minutes
|
|
1319
|
+
default: 120 # 2 minutes
|
|
1320
1320
|
}
|
|
1321
1321
|
|
|
1322
1322
|
# Get timeout from configuration if available
|
|
@@ -366,6 +366,46 @@ module Aidp
|
|
|
366
366
|
enum: ["provider_model", "provider", "model", "none"]
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
|
+
},
|
|
370
|
+
work_loop: {
|
|
371
|
+
type: :hash,
|
|
372
|
+
required: false,
|
|
373
|
+
default: {
|
|
374
|
+
enabled: true,
|
|
375
|
+
max_iterations: 50,
|
|
376
|
+
test_commands: [],
|
|
377
|
+
lint_commands: []
|
|
378
|
+
},
|
|
379
|
+
properties: {
|
|
380
|
+
enabled: {
|
|
381
|
+
type: :boolean,
|
|
382
|
+
required: false,
|
|
383
|
+
default: true
|
|
384
|
+
},
|
|
385
|
+
max_iterations: {
|
|
386
|
+
type: :integer,
|
|
387
|
+
required: false,
|
|
388
|
+
default: 50,
|
|
389
|
+
min: 1,
|
|
390
|
+
max: 200
|
|
391
|
+
},
|
|
392
|
+
test_commands: {
|
|
393
|
+
type: :array,
|
|
394
|
+
required: false,
|
|
395
|
+
default: [],
|
|
396
|
+
items: {
|
|
397
|
+
type: :string
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
lint_commands: {
|
|
401
|
+
type: :array,
|
|
402
|
+
required: false,
|
|
403
|
+
default: [],
|
|
404
|
+
items: {
|
|
405
|
+
type: :string
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
369
409
|
}
|
|
370
410
|
}
|
|
371
411
|
},
|
|
@@ -76,8 +76,9 @@ module Aidp
|
|
|
76
76
|
return false if config_exists?
|
|
77
77
|
|
|
78
78
|
example_config = ConfigSchema.generate_example
|
|
79
|
-
config_path = File.join(@project_dir, "aidp.yml")
|
|
79
|
+
config_path = File.join(@project_dir, ".aidp", "aidp.yml")
|
|
80
80
|
|
|
81
|
+
FileUtils.mkdir_p(File.dirname(config_path))
|
|
81
82
|
File.write(config_path, YAML.dump(example_config))
|
|
82
83
|
true
|
|
83
84
|
end
|
|
@@ -243,14 +244,10 @@ module Aidp
|
|
|
243
244
|
private
|
|
244
245
|
|
|
245
246
|
def find_config_file
|
|
246
|
-
|
|
247
|
-
config_file = File.join(@project_dir, "aidp.yml")
|
|
248
|
-
legacy_config_file = File.join(@project_dir, ".aidp.yml")
|
|
247
|
+
config_file = File.join(@project_dir, ".aidp", "aidp.yml")
|
|
249
248
|
|
|
250
249
|
if File.exist?(config_file)
|
|
251
250
|
config_file
|
|
252
|
-
elsif File.exist?(legacy_config_file)
|
|
253
|
-
legacy_config_file
|
|
254
251
|
end
|
|
255
252
|
end
|
|
256
253
|
|
|
@@ -144,6 +144,31 @@ module Aidp
|
|
|
144
144
|
harness_config[:session] || get_default_session_config
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
+
# Get work loop configuration
|
|
148
|
+
def work_loop_config
|
|
149
|
+
harness_config[:work_loop] || get_default_work_loop_config
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Check if work loops are enabled
|
|
153
|
+
def work_loop_enabled?
|
|
154
|
+
work_loop_config[:enabled]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Get maximum iterations for work loops
|
|
158
|
+
def work_loop_max_iterations
|
|
159
|
+
work_loop_config[:max_iterations]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Get test commands
|
|
163
|
+
def test_commands
|
|
164
|
+
work_loop_config[:test_commands] || []
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Get lint commands
|
|
168
|
+
def lint_commands
|
|
169
|
+
work_loop_config[:lint_commands] || []
|
|
170
|
+
end
|
|
171
|
+
|
|
147
172
|
# Get provider priority
|
|
148
173
|
def provider_priority(provider_name)
|
|
149
174
|
provider_config(provider_name)[:priority] || 0
|
|
@@ -186,7 +211,7 @@ module Aidp
|
|
|
186
211
|
|
|
187
212
|
# Get configuration path
|
|
188
213
|
def config_path
|
|
189
|
-
File.join(@project_dir, "aidp.yml")
|
|
214
|
+
File.join(@project_dir, ".aidp", "aidp.yml")
|
|
190
215
|
end
|
|
191
216
|
|
|
192
217
|
# Get logging configuration
|
|
@@ -416,6 +441,15 @@ module Aidp
|
|
|
416
441
|
}
|
|
417
442
|
end
|
|
418
443
|
|
|
444
|
+
def get_default_work_loop_config
|
|
445
|
+
{
|
|
446
|
+
enabled: true,
|
|
447
|
+
max_iterations: 50,
|
|
448
|
+
test_commands: [],
|
|
449
|
+
lint_commands: []
|
|
450
|
+
}
|
|
451
|
+
end
|
|
452
|
+
|
|
419
453
|
def get_default_logging_config
|
|
420
454
|
{
|
|
421
455
|
log_level: :info,
|
|
@@ -31,7 +31,7 @@ module Aidp
|
|
|
31
31
|
@current_step = nil
|
|
32
32
|
@current_provider = nil
|
|
33
33
|
@user_input = options[:user_input] || {}
|
|
34
|
-
@user_input = {} if @user_input.nil?
|
|
34
|
+
@user_input = {} if @user_input.nil? # Ensure it's never nil
|
|
35
35
|
@execution_log = []
|
|
36
36
|
|
|
37
37
|
# Store workflow configuration
|
|
@@ -50,11 +50,32 @@ module Aidp
|
|
|
50
50
|
@configuration = Configuration.new(project_dir)
|
|
51
51
|
@state_manager = StateManager.new(project_dir, @mode)
|
|
52
52
|
@condition_detector = ConditionDetector.new
|
|
53
|
-
@provider_manager = ProviderManager.new(@configuration)
|
|
53
|
+
@provider_manager = ProviderManager.new(@configuration, prompt: TTY::Prompt.new)
|
|
54
54
|
@error_handler = ErrorHandler.new(@provider_manager, @configuration)
|
|
55
55
|
@completion_checker = CompletionChecker.new(@project_dir, @workflow_type)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# Get current provider (delegate to provider manager)
|
|
59
|
+
def current_provider
|
|
60
|
+
@current_provider || @provider_manager&.current_provider || "unknown"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get current step
|
|
64
|
+
attr_reader :current_step
|
|
65
|
+
|
|
66
|
+
# Get user input
|
|
67
|
+
def user_input
|
|
68
|
+
@user_input || {}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Get execution log
|
|
72
|
+
def execution_log
|
|
73
|
+
@execution_log || []
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get provider manager
|
|
77
|
+
attr_reader :provider_manager
|
|
78
|
+
|
|
58
79
|
# Main execution method with enhanced TUI
|
|
59
80
|
def run
|
|
60
81
|
@state = STATES[:running]
|
|
@@ -312,9 +333,9 @@ module Aidp
|
|
|
312
333
|
def get_mode_runner
|
|
313
334
|
case @mode
|
|
314
335
|
when :analyze
|
|
315
|
-
Aidp::Analyze::Runner.new(@project_dir, self)
|
|
336
|
+
Aidp::Analyze::Runner.new(@project_dir, self, prompt: TTY::Prompt.new)
|
|
316
337
|
when :execute
|
|
317
|
-
Aidp::Execute::Runner.new(@project_dir, self)
|
|
338
|
+
Aidp::Execute::Runner.new(@project_dir, self, prompt: TTY::Prompt.new)
|
|
318
339
|
else
|
|
319
340
|
raise ArgumentError, "Unsupported mode: #{@mode}"
|
|
320
341
|
end
|
|
@@ -69,6 +69,17 @@ module Aidp
|
|
|
69
69
|
error_type: error_info[:error_type],
|
|
70
70
|
reason: "Retry not applicable or exhausted"
|
|
71
71
|
})
|
|
72
|
+
if [:authentication, :permission_denied].include?(error_info[:error_type].to_sym)
|
|
73
|
+
# Mark provider unhealthy to avoid immediate re-selection
|
|
74
|
+
begin
|
|
75
|
+
if @provider_manager.respond_to?(:mark_provider_auth_failure)
|
|
76
|
+
@provider_manager.mark_provider_auth_failure(error_info[:provider])
|
|
77
|
+
debug_log("🔐 Marked provider #{error_info[:provider]} unhealthy due to auth error", level: :warn)
|
|
78
|
+
end
|
|
79
|
+
rescue => e
|
|
80
|
+
debug_log("⚠️ Failed to mark provider unhealthy after auth error", level: :warn, data: {error: e.message})
|
|
81
|
+
end
|
|
82
|
+
end
|
|
72
83
|
attempt_recovery(error_info, context)
|
|
73
84
|
|
|
74
85
|
end
|
|
@@ -76,35 +87,75 @@ module Aidp
|
|
|
76
87
|
|
|
77
88
|
# Execute a block with retry logic
|
|
78
89
|
def execute_with_retry(&block)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
attempt
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
providers_tried = []
|
|
91
|
+
|
|
92
|
+
loop do
|
|
93
|
+
max_attempts = @configuration.max_retries + 1
|
|
94
|
+
attempt = 0
|
|
95
|
+
|
|
96
|
+
begin
|
|
97
|
+
attempt += 1
|
|
98
|
+
return yield
|
|
99
|
+
rescue => error
|
|
100
|
+
current_provider = get_current_provider_safely
|
|
101
|
+
|
|
102
|
+
if attempt < max_attempts
|
|
103
|
+
error_info = {
|
|
104
|
+
error: error,
|
|
105
|
+
provider: current_provider,
|
|
106
|
+
model: get_current_model_safely,
|
|
107
|
+
error_type: @error_classifier.classify_error(error)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
strategy = get_retry_strategy(error_info[:error_type])
|
|
111
|
+
if should_retry?(error_info, strategy)
|
|
112
|
+
delay = @backoff_calculator.calculate_delay(attempt, strategy[:backoff_strategy] || :exponential, 1, 10)
|
|
113
|
+
debug_log("🔁 Retry attempt #{attempt} for #{current_provider}", level: :info, data: {delay: delay, error_type: error_info[:error_type]})
|
|
114
|
+
sleep(delay) if delay > 0
|
|
115
|
+
retry
|
|
116
|
+
end
|
|
117
|
+
end
|
|
93
118
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
119
|
+
# Provider exhausted – attempt recovery (may switch provider)
|
|
120
|
+
debug_log("🚫 Exhausted retries for provider, attempting recovery", level: :warn, data: {provider: current_provider, attempt: attempt, max_attempts: max_attempts})
|
|
121
|
+
handle_error(error, {
|
|
122
|
+
provider: current_provider,
|
|
123
|
+
model: get_current_model_safely,
|
|
124
|
+
exhausted_retries: true
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
new_provider = get_current_provider_safely
|
|
128
|
+
if new_provider != current_provider && !providers_tried.include?(new_provider)
|
|
129
|
+
providers_tried << current_provider
|
|
130
|
+
# Reset retry counts for the new provider
|
|
131
|
+
begin
|
|
132
|
+
reset_retry_counts(new_provider)
|
|
133
|
+
rescue => e
|
|
134
|
+
debug_log("⚠️ Failed to reset retry counts for new provider", level: :warn, data: {error: e.message})
|
|
135
|
+
end
|
|
136
|
+
debug_log("🔀 Switched provider after failure – re-executing block", level: :info, data: {from: current_provider, to: new_provider})
|
|
137
|
+
# Start retry loop fresh for new provider
|
|
138
|
+
next
|
|
100
139
|
end
|
|
101
|
-
end
|
|
102
140
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
141
|
+
# No new provider (or already tried) – return structured failure
|
|
142
|
+
debug_log("❌ No fallback provider available or all tried", level: :error, data: {providers_tried: providers_tried})
|
|
143
|
+
begin
|
|
144
|
+
if @provider_manager.respond_to?(:mark_provider_failure_exhausted)
|
|
145
|
+
@provider_manager.mark_provider_failure_exhausted(current_provider)
|
|
146
|
+
debug_log("🛑 Marked provider #{current_provider} unhealthy due to exhausted retries", level: :warn)
|
|
147
|
+
end
|
|
148
|
+
rescue => e
|
|
149
|
+
debug_log("⚠️ Failed to mark provider failure-exhausted", level: :warn, data: {error: e.message})
|
|
150
|
+
end
|
|
151
|
+
return {
|
|
152
|
+
status: "failed",
|
|
153
|
+
error: error,
|
|
154
|
+
message: error.message,
|
|
155
|
+
provider: current_provider,
|
|
156
|
+
providers_tried: providers_tried.dup
|
|
157
|
+
}
|
|
158
|
+
end
|
|
108
159
|
end
|
|
109
160
|
end
|
|
110
161
|
|
|
@@ -579,9 +630,12 @@ module Aidp
|
|
|
579
630
|
priority: :high
|
|
580
631
|
}
|
|
581
632
|
when :authentication, :permission_denied
|
|
633
|
+
# Previously we escalated immediately. Instead, attempt a provider switch
|
|
634
|
+
# so workflows can continue with alternate providers (e.g., Gemini, Cursor)
|
|
635
|
+
# while the user resolves credentials for the failing provider.
|
|
582
636
|
{
|
|
583
|
-
action: :
|
|
584
|
-
reason: "Authentication
|
|
637
|
+
action: :switch_provider,
|
|
638
|
+
reason: "Authentication/permission issue – switching provider to continue",
|
|
585
639
|
priority: :critical
|
|
586
640
|
}
|
|
587
641
|
when :timeout
|
|
@@ -611,6 +665,27 @@ module Aidp
|
|
|
611
665
|
end
|
|
612
666
|
end
|
|
613
667
|
end
|
|
668
|
+
|
|
669
|
+
# Safe access to provider manager methods that may not exist
|
|
670
|
+
def get_current_provider_safely
|
|
671
|
+
return "unknown" unless @provider_manager
|
|
672
|
+
return "unknown" unless @provider_manager.respond_to?(:current_provider)
|
|
673
|
+
|
|
674
|
+
@provider_manager.current_provider || "unknown"
|
|
675
|
+
rescue => e
|
|
676
|
+
debug_log("⚠️ Failed to get current provider", level: :warn, data: {error: e.message})
|
|
677
|
+
"unknown"
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def get_current_model_safely
|
|
681
|
+
return "unknown" unless @provider_manager
|
|
682
|
+
return "unknown" unless @provider_manager.respond_to?(:current_model)
|
|
683
|
+
|
|
684
|
+
@provider_manager.current_model || "unknown"
|
|
685
|
+
rescue => e
|
|
686
|
+
debug_log("⚠️ Failed to get current model", level: :warn, data: {error: e.message})
|
|
687
|
+
"unknown"
|
|
688
|
+
end
|
|
614
689
|
end
|
|
615
690
|
end
|
|
616
691
|
end
|
|
@@ -7,6 +7,8 @@ require_relative "../providers/anthropic"
|
|
|
7
7
|
require_relative "../providers/gemini"
|
|
8
8
|
require_relative "../providers/macos_ui"
|
|
9
9
|
require_relative "../providers/opencode"
|
|
10
|
+
require_relative "../providers/github_copilot"
|
|
11
|
+
require_relative "../providers/codex"
|
|
10
12
|
|
|
11
13
|
module Aidp
|
|
12
14
|
module Harness
|
|
@@ -15,9 +17,12 @@ module Aidp
|
|
|
15
17
|
PROVIDER_CLASSES = {
|
|
16
18
|
"cursor" => Aidp::Providers::Cursor,
|
|
17
19
|
"anthropic" => Aidp::Providers::Anthropic,
|
|
20
|
+
"claude" => Aidp::Providers::Anthropic,
|
|
18
21
|
"gemini" => Aidp::Providers::Gemini,
|
|
19
22
|
"macos" => Aidp::Providers::MacOSUI,
|
|
20
|
-
"opencode" => Aidp::Providers::Opencode
|
|
23
|
+
"opencode" => Aidp::Providers::Opencode,
|
|
24
|
+
"github_copilot" => Aidp::Providers::GithubCopilot,
|
|
25
|
+
"codex" => Aidp::Providers::Codex
|
|
21
26
|
}.freeze
|
|
22
27
|
|
|
23
28
|
def initialize(config_manager = nil)
|