aidp 0.5.0 → 0.8.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 +128 -151
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +471 -0
- data/lib/aidp/analysis/seams.rb +159 -0
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +480 -0
- data/lib/aidp/analysis/tree_sitter_scan.rb +686 -0
- data/lib/aidp/analyze/error_handler.rb +2 -78
- data/lib/aidp/analyze/json_file_storage.rb +292 -0
- data/lib/aidp/analyze/progress.rb +12 -0
- data/lib/aidp/analyze/progress_visualizer.rb +12 -17
- data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
- data/lib/aidp/analyze/runner.rb +256 -87
- data/lib/aidp/analyze/steps.rb +6 -0
- data/lib/aidp/cli/jobs_command.rb +103 -435
- data/lib/aidp/cli.rb +317 -191
- data/lib/aidp/config.rb +298 -10
- data/lib/aidp/debug_logger.rb +195 -0
- data/lib/aidp/debug_mixin.rb +187 -0
- data/lib/aidp/execute/progress.rb +9 -0
- data/lib/aidp/execute/runner.rb +221 -40
- data/lib/aidp/execute/steps.rb +17 -7
- data/lib/aidp/execute/workflow_selector.rb +211 -0
- data/lib/aidp/harness/completion_checker.rb +268 -0
- data/lib/aidp/harness/condition_detector.rb +1526 -0
- data/lib/aidp/harness/config_loader.rb +373 -0
- data/lib/aidp/harness/config_manager.rb +382 -0
- data/lib/aidp/harness/config_schema.rb +1006 -0
- data/lib/aidp/harness/config_validator.rb +355 -0
- data/lib/aidp/harness/configuration.rb +477 -0
- data/lib/aidp/harness/enhanced_runner.rb +494 -0
- data/lib/aidp/harness/error_handler.rb +616 -0
- data/lib/aidp/harness/provider_config.rb +423 -0
- data/lib/aidp/harness/provider_factory.rb +306 -0
- data/lib/aidp/harness/provider_manager.rb +1269 -0
- data/lib/aidp/harness/provider_type_checker.rb +88 -0
- data/lib/aidp/harness/runner.rb +411 -0
- data/lib/aidp/harness/state/errors.rb +28 -0
- data/lib/aidp/harness/state/metrics.rb +219 -0
- data/lib/aidp/harness/state/persistence.rb +128 -0
- data/lib/aidp/harness/state/provider_state.rb +132 -0
- data/lib/aidp/harness/state/ui_state.rb +68 -0
- data/lib/aidp/harness/state/workflow_state.rb +123 -0
- data/lib/aidp/harness/state_manager.rb +586 -0
- data/lib/aidp/harness/status_display.rb +888 -0
- data/lib/aidp/harness/ui/base.rb +16 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
- data/lib/aidp/harness/ui/error_handler.rb +132 -0
- data/lib/aidp/harness/ui/frame_manager.rb +361 -0
- data/lib/aidp/harness/ui/job_monitor.rb +500 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
- data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
- data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
- data/lib/aidp/harness/ui/progress_display.rb +280 -0
- data/lib/aidp/harness/ui/question_collector.rb +141 -0
- data/lib/aidp/harness/ui/spinner_group.rb +184 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
- data/lib/aidp/harness/ui/status_manager.rb +312 -0
- data/lib/aidp/harness/ui/status_widget.rb +280 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
- data/lib/aidp/harness/user_interface.rb +2381 -0
- data/lib/aidp/provider_manager.rb +131 -7
- data/lib/aidp/providers/anthropic.rb +28 -109
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -183
- data/lib/aidp/providers/gemini.rb +24 -109
- data/lib/aidp/providers/macos_ui.rb +99 -5
- data/lib/aidp/providers/opencode.rb +194 -0
- data/lib/aidp/storage/csv_storage.rb +172 -0
- data/lib/aidp/storage/file_manager.rb +214 -0
- data/lib/aidp/storage/json_storage.rb +140 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +56 -35
- data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
- data/templates/COMMON/AGENT_BASE.md +11 -0
- data/templates/EXECUTE/00_PRD.md +4 -4
- data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
- data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
- data/templates/EXECUTE/08_TASKS.md +4 -4
- data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
- data/templates/README.md +279 -0
- data/templates/aidp-development.yml.example +373 -0
- data/templates/aidp-minimal.yml.example +48 -0
- data/templates/aidp-production.yml.example +475 -0
- data/templates/aidp.yml.example +598 -0
- metadata +106 -64
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -445
- data/lib/aidp/analyze/data_retention_manager.rb +0 -426
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -425
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -543
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -504
- data/lib/aidp/analyze/memory_manager.rb +0 -365
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -460
- data/lib/aidp/analyze/performance_optimizer.rb +0 -694
- data/lib/aidp/analyze/repository_chunker.rb +0 -704
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -662
- data/lib/aidp/analyze/tool_configuration.rb +0 -456
- data/lib/aidp/analyze/tool_modernization.rb +0 -750
- data/lib/aidp/database/pg_adapter.rb +0 -148
- data/lib/aidp/database_config.rb +0 -69
- data/lib/aidp/database_connection.rb +0 -72
- data/lib/aidp/database_migration.rb +0 -158
- data/lib/aidp/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -47
- data/lib/aidp/jobs/provider_execution_job.rb +0 -96
- data/lib/aidp/project_detector.rb +0 -117
- data/lib/aidp/providers/agent_supervisor.rb +0 -348
- data/lib/aidp/providers/supervised_base.rb +0 -317
- data/lib/aidp/providers/supervised_cursor.rb +0 -22
- data/lib/aidp/sync.rb +0 -13
- data/lib/aidp/workspace.rb +0 -19
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module Harness
|
5
|
+
# Shared module for provider type checking functionality
|
6
|
+
# This eliminates duplication across ProviderConfig and ConfigManager
|
7
|
+
module ProviderTypeChecker
|
8
|
+
# Check if provider is usage-based (pay per token)
|
9
|
+
def usage_based_provider?(provider_name_or_options = {}, options = {})
|
10
|
+
# Handle both ConfigManager (provider_name, options) and ProviderConfig (options) signatures
|
11
|
+
if provider_name_or_options.is_a?(String) || provider_name_or_options.is_a?(Symbol)
|
12
|
+
provider_name = provider_name_or_options
|
13
|
+
get_provider_type(provider_name, options) == "usage_based"
|
14
|
+
else
|
15
|
+
# ProviderConfig signature: usage_based_provider?(options)
|
16
|
+
options = provider_name_or_options
|
17
|
+
get_type(options) == "usage_based"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Check if provider is subscription-based (unlimited within limits)
|
22
|
+
def subscription_provider?(provider_name_or_options = {}, options = {})
|
23
|
+
if provider_name_or_options.is_a?(String) || provider_name_or_options.is_a?(Symbol)
|
24
|
+
provider_name = provider_name_or_options
|
25
|
+
get_provider_type(provider_name, options) == "subscription"
|
26
|
+
else
|
27
|
+
options = provider_name_or_options
|
28
|
+
get_type(options) == "subscription"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check if provider is passthrough (inherits billing from underlying service)
|
33
|
+
def passthrough_provider?(provider_name_or_options = {}, options = {})
|
34
|
+
if provider_name_or_options.is_a?(String) || provider_name_or_options.is_a?(Symbol)
|
35
|
+
provider_name = provider_name_or_options
|
36
|
+
get_provider_type(provider_name, options) == "passthrough"
|
37
|
+
else
|
38
|
+
options = provider_name_or_options
|
39
|
+
get_type(options) == "passthrough"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get provider type with fallback to subscription (ConfigManager signature)
|
44
|
+
def get_provider_type(provider_name, options = {})
|
45
|
+
provider_config = get_provider_config(provider_name, options)
|
46
|
+
return "subscription" unless provider_config
|
47
|
+
|
48
|
+
provider_config[:type] || provider_config["type"] || "subscription"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Check if provider requires API key
|
52
|
+
def requires_api_key?(provider_name_or_options = {}, options = {})
|
53
|
+
usage_based_provider?(provider_name_or_options, options)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check if provider has underlying service (passthrough)
|
57
|
+
def has_underlying_service?(provider_name_or_options = {}, options = {})
|
58
|
+
return false unless passthrough_provider?(provider_name_or_options, options)
|
59
|
+
|
60
|
+
if provider_name_or_options.is_a?(String) || provider_name_or_options.is_a?(Symbol)
|
61
|
+
provider_name = provider_name_or_options
|
62
|
+
provider_config = get_provider_config(provider_name, options)
|
63
|
+
else
|
64
|
+
options = provider_name_or_options
|
65
|
+
provider_config = get_config(options)
|
66
|
+
end
|
67
|
+
|
68
|
+
underlying_service = provider_config[:underlying_service] || provider_config["underlying_service"]
|
69
|
+
!underlying_service.nil? && !underlying_service.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get underlying service name for passthrough providers
|
73
|
+
def get_underlying_service(provider_name_or_options = {}, options = {})
|
74
|
+
return nil unless passthrough_provider?(provider_name_or_options, options)
|
75
|
+
|
76
|
+
if provider_name_or_options.is_a?(String) || provider_name_or_options.is_a?(Symbol)
|
77
|
+
provider_name = provider_name_or_options
|
78
|
+
provider_config = get_provider_config(provider_name, options)
|
79
|
+
else
|
80
|
+
options = provider_name_or_options
|
81
|
+
provider_config = get_config(options)
|
82
|
+
end
|
83
|
+
|
84
|
+
provider_config[:underlying_service] || provider_config["underlying_service"]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,411 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "timeout"
|
4
|
+
require "json"
|
5
|
+
require_relative "configuration"
|
6
|
+
require_relative "state_manager"
|
7
|
+
require_relative "condition_detector"
|
8
|
+
require_relative "provider_manager"
|
9
|
+
require_relative "user_interface"
|
10
|
+
require_relative "error_handler"
|
11
|
+
require_relative "status_display"
|
12
|
+
require_relative "completion_checker"
|
13
|
+
|
14
|
+
module Aidp
|
15
|
+
module Harness
|
16
|
+
# Main harness runner that orchestrates the execution loop
|
17
|
+
class Runner
|
18
|
+
# Harness execution states
|
19
|
+
STATES = {
|
20
|
+
idle: "idle",
|
21
|
+
running: "running",
|
22
|
+
paused: "paused",
|
23
|
+
waiting_for_user: "waiting_for_user",
|
24
|
+
waiting_for_rate_limit: "waiting_for_rate_limit",
|
25
|
+
stopped: "stopped",
|
26
|
+
completed: "completed",
|
27
|
+
error: "error"
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
def initialize(project_dir, mode = :analyze, options = {})
|
31
|
+
@project_dir = project_dir
|
32
|
+
@mode = mode.to_sym
|
33
|
+
@options = options
|
34
|
+
@state = STATES[:idle]
|
35
|
+
@start_time = nil
|
36
|
+
@current_step = nil
|
37
|
+
@current_provider = nil
|
38
|
+
@user_input = options[:user_input] || {} # Include user input from workflow selection
|
39
|
+
@execution_log = []
|
40
|
+
|
41
|
+
# Store workflow configuration
|
42
|
+
@selected_steps = options[:selected_steps]
|
43
|
+
@workflow_type = options[:workflow_type]
|
44
|
+
|
45
|
+
# Initialize components
|
46
|
+
@configuration = Configuration.new(project_dir)
|
47
|
+
@state_manager = StateManager.new(project_dir, @mode)
|
48
|
+
@condition_detector = ConditionDetector.new
|
49
|
+
@provider_manager = ProviderManager.new(@configuration)
|
50
|
+
@user_interface = UserInterface.new
|
51
|
+
@error_handler = ErrorHandler.new(@provider_manager, @configuration)
|
52
|
+
@status_display = StatusDisplay.new
|
53
|
+
@completion_checker = CompletionChecker.new(@project_dir, @workflow_type)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Main execution method - runs the harness loop
|
57
|
+
def run
|
58
|
+
@state = STATES[:running]
|
59
|
+
@start_time = Time.now
|
60
|
+
|
61
|
+
log_execution("Harness started", {mode: @mode, project_dir: @project_dir})
|
62
|
+
|
63
|
+
begin
|
64
|
+
# Load existing state if resuming
|
65
|
+
load_state if @state_manager.has_state?
|
66
|
+
|
67
|
+
# Get the appropriate runner for the mode
|
68
|
+
runner = get_mode_runner
|
69
|
+
|
70
|
+
# Main execution loop
|
71
|
+
loop do
|
72
|
+
break if should_stop?
|
73
|
+
|
74
|
+
# Check for pause conditions
|
75
|
+
if should_pause?
|
76
|
+
handle_pause_condition
|
77
|
+
next
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get next step to execute
|
81
|
+
next_step = get_next_step(runner)
|
82
|
+
break unless next_step
|
83
|
+
|
84
|
+
# Execute the step
|
85
|
+
execute_step(runner, next_step)
|
86
|
+
|
87
|
+
# Update state
|
88
|
+
update_state
|
89
|
+
end
|
90
|
+
|
91
|
+
# Mark as completed if we finished all steps AND all completion criteria are met
|
92
|
+
if all_steps_completed?(runner)
|
93
|
+
completion_status = @completion_checker.completion_status
|
94
|
+
if completion_status[:all_complete]
|
95
|
+
@state = STATES[:completed]
|
96
|
+
log_execution("Harness completed successfully - all criteria met", completion_status)
|
97
|
+
else
|
98
|
+
log_execution("Steps completed but completion criteria not met", completion_status)
|
99
|
+
puts "\n⚠️ All steps completed but some completion criteria not met:"
|
100
|
+
puts completion_status[:summary]
|
101
|
+
|
102
|
+
# Ask user if they want to continue anyway
|
103
|
+
if @user_interface.get_confirmation("Continue anyway? This may indicate issues that should be addressed.", default: false)
|
104
|
+
@state = STATES[:completed]
|
105
|
+
log_execution("Harness completed with user override")
|
106
|
+
else
|
107
|
+
@state = STATES[:error]
|
108
|
+
log_execution("Harness stopped due to unmet completion criteria")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
rescue => e
|
113
|
+
@state = STATES[:error]
|
114
|
+
log_execution("Harness error: #{e.message}", {error: e.class.name})
|
115
|
+
handle_error(e)
|
116
|
+
ensure
|
117
|
+
# Save state before exiting
|
118
|
+
save_state
|
119
|
+
cleanup
|
120
|
+
end
|
121
|
+
|
122
|
+
{status: @state, message: get_completion_message}
|
123
|
+
end
|
124
|
+
|
125
|
+
# Pause the harness execution
|
126
|
+
def pause
|
127
|
+
return unless @state == STATES[:running]
|
128
|
+
|
129
|
+
@state = STATES[:paused]
|
130
|
+
log_execution("Harness paused by user")
|
131
|
+
@status_display.show_paused_status
|
132
|
+
end
|
133
|
+
|
134
|
+
# Resume the harness execution
|
135
|
+
def resume
|
136
|
+
return unless @state == STATES[:paused]
|
137
|
+
|
138
|
+
@state = STATES[:running]
|
139
|
+
log_execution("Harness resumed by user")
|
140
|
+
@status_display.show_resumed_status
|
141
|
+
end
|
142
|
+
|
143
|
+
# Stop the harness execution
|
144
|
+
def stop
|
145
|
+
@state = STATES[:stopped]
|
146
|
+
log_execution("Harness stopped by user")
|
147
|
+
@status_display.show_stopped_status
|
148
|
+
end
|
149
|
+
|
150
|
+
# Get current harness status
|
151
|
+
def status
|
152
|
+
{
|
153
|
+
state: @state,
|
154
|
+
mode: @mode,
|
155
|
+
current_step: @current_step,
|
156
|
+
current_provider: @current_provider,
|
157
|
+
start_time: @start_time,
|
158
|
+
duration: @start_time ? Time.now - @start_time : 0,
|
159
|
+
user_input_count: @user_input.size,
|
160
|
+
execution_log_count: @execution_log.size,
|
161
|
+
progress: @state_manager.progress_summary
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
# Get detailed status including all components
|
166
|
+
def detailed_status
|
167
|
+
{
|
168
|
+
harness: status,
|
169
|
+
configuration: {
|
170
|
+
default_provider: @configuration.default_provider,
|
171
|
+
fallback_providers: @configuration.fallback_providers,
|
172
|
+
max_retries: @configuration.max_retries
|
173
|
+
},
|
174
|
+
provider_manager: @provider_manager.status,
|
175
|
+
error_stats: @error_handler.error_stats
|
176
|
+
}
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def get_mode_runner
|
182
|
+
case @mode
|
183
|
+
when :analyze
|
184
|
+
Aidp::Analyze::Runner.new(@project_dir, self)
|
185
|
+
when :execute
|
186
|
+
Aidp::Execute::Runner.new(@project_dir, self)
|
187
|
+
else
|
188
|
+
raise ArgumentError, "Unsupported mode: #{@mode}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_next_step(runner)
|
193
|
+
# Use the mode runner's next_step method
|
194
|
+
runner.next_step
|
195
|
+
end
|
196
|
+
|
197
|
+
def execute_step(runner, step_name)
|
198
|
+
@current_step = step_name
|
199
|
+
log_execution("Executing step: #{step_name}")
|
200
|
+
|
201
|
+
# Mark step as in progress using the runner's method
|
202
|
+
runner.mark_step_in_progress(step_name)
|
203
|
+
|
204
|
+
# Update status display
|
205
|
+
@status_display.update_current_step(step_name)
|
206
|
+
|
207
|
+
# Get current provider
|
208
|
+
@current_provider = @provider_manager.current_provider
|
209
|
+
@status_display.update_current_provider(@current_provider)
|
210
|
+
|
211
|
+
# Execute the step with error handling
|
212
|
+
result = @error_handler.execute_with_retry do
|
213
|
+
# Merge harness options with user input
|
214
|
+
step_options = @options.merge(user_input: @user_input)
|
215
|
+
runner.run_step(step_name, step_options)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Check for conditions that require user interaction
|
219
|
+
if @condition_detector.needs_user_feedback?(result)
|
220
|
+
handle_user_feedback_request(result)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Check for rate limiting
|
224
|
+
if @condition_detector.is_rate_limited?(result)
|
225
|
+
handle_rate_limit(result)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Mark step as completed if successful using the runner's method
|
229
|
+
if result && result[:status] == "completed"
|
230
|
+
runner.mark_step_completed(step_name)
|
231
|
+
end
|
232
|
+
|
233
|
+
log_execution("Step completed: #{step_name}", {result: result})
|
234
|
+
result
|
235
|
+
end
|
236
|
+
|
237
|
+
def handle_user_feedback_request(result)
|
238
|
+
@state = STATES[:waiting_for_user]
|
239
|
+
log_execution("Waiting for user feedback")
|
240
|
+
|
241
|
+
# Extract questions from result
|
242
|
+
questions = @condition_detector.extract_questions(result)
|
243
|
+
|
244
|
+
# Collect user input
|
245
|
+
user_responses = @user_interface.collect_feedback(questions)
|
246
|
+
|
247
|
+
# Store user input in both local state and state manager
|
248
|
+
@user_input.merge!(user_responses)
|
249
|
+
user_responses.each do |key, value|
|
250
|
+
@state_manager.add_user_input(key, value)
|
251
|
+
end
|
252
|
+
|
253
|
+
@state = STATES[:running]
|
254
|
+
log_execution("User feedback collected", {responses: user_responses.keys})
|
255
|
+
end
|
256
|
+
|
257
|
+
def handle_rate_limit(_result)
|
258
|
+
@state = STATES[:waiting_for_rate_limit]
|
259
|
+
log_execution("Rate limit detected, switching provider")
|
260
|
+
|
261
|
+
# Mark current provider as rate limited
|
262
|
+
@provider_manager.mark_rate_limited(@current_provider)
|
263
|
+
|
264
|
+
# Switch to next provider
|
265
|
+
next_provider = @provider_manager.switch_provider
|
266
|
+
@current_provider = next_provider
|
267
|
+
|
268
|
+
if next_provider
|
269
|
+
@state = STATES[:running]
|
270
|
+
log_execution("Switched to provider: #{next_provider}")
|
271
|
+
else
|
272
|
+
# All providers rate limited, wait for reset
|
273
|
+
wait_for_rate_limit_reset
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def wait_for_rate_limit_reset
|
278
|
+
reset_time = @provider_manager.next_reset_time
|
279
|
+
if reset_time
|
280
|
+
@status_display.show_rate_limit_wait(reset_time)
|
281
|
+
sleep_until_reset(reset_time)
|
282
|
+
@state = STATES[:running]
|
283
|
+
else
|
284
|
+
@state = STATES[:error]
|
285
|
+
raise "All providers rate limited with no reset time available"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def sleep_until_reset(reset_time)
|
290
|
+
while Time.now < reset_time && @state == STATES[:waiting_for_rate_limit]
|
291
|
+
remaining = reset_time - Time.now
|
292
|
+
@status_display.update_rate_limit_countdown(remaining)
|
293
|
+
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
294
|
+
sleep(1)
|
295
|
+
else
|
296
|
+
Async::Task.current.sleep(1)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def should_stop?
|
302
|
+
@state == STATES[:stopped] ||
|
303
|
+
@state == STATES[:completed] ||
|
304
|
+
@state == STATES[:error]
|
305
|
+
end
|
306
|
+
|
307
|
+
def should_pause?
|
308
|
+
@state == STATES[:paused] ||
|
309
|
+
@state == STATES[:waiting_for_user] ||
|
310
|
+
@state == STATES[:waiting_for_rate_limit]
|
311
|
+
end
|
312
|
+
|
313
|
+
def handle_pause_condition
|
314
|
+
case @state
|
315
|
+
when STATES[:paused]
|
316
|
+
# Wait for user to resume
|
317
|
+
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
318
|
+
sleep(1)
|
319
|
+
else
|
320
|
+
Async::Task.current.sleep(1)
|
321
|
+
end
|
322
|
+
when STATES[:waiting_for_user]
|
323
|
+
# User interface handles this
|
324
|
+
nil
|
325
|
+
when STATES[:waiting_for_rate_limit]
|
326
|
+
# Rate limit handling
|
327
|
+
nil
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def all_steps_completed?(runner)
|
332
|
+
# Use the mode runner's all_steps_completed? method
|
333
|
+
runner.all_steps_completed?
|
334
|
+
end
|
335
|
+
|
336
|
+
def update_state
|
337
|
+
@state_manager.update_state({
|
338
|
+
state: @state,
|
339
|
+
current_step: @current_step,
|
340
|
+
current_provider: @current_provider,
|
341
|
+
user_input: @user_input,
|
342
|
+
last_updated: Time.now
|
343
|
+
})
|
344
|
+
end
|
345
|
+
|
346
|
+
def load_state
|
347
|
+
if @state_manager.has_state?
|
348
|
+
state_data = @state_manager.load_state
|
349
|
+
@current_step = state_data[:current_step]
|
350
|
+
@current_provider = state_data[:current_provider]
|
351
|
+
@user_input = @state_manager.user_input
|
352
|
+
log_execution("Loaded existing state", state_data)
|
353
|
+
else
|
354
|
+
log_execution("No existing state found, starting fresh")
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def save_state
|
359
|
+
# Save harness-specific state
|
360
|
+
@state_manager.save_state({
|
361
|
+
state: @state,
|
362
|
+
current_step: @current_step,
|
363
|
+
current_provider: @current_provider,
|
364
|
+
user_input: @user_input,
|
365
|
+
execution_log: @execution_log,
|
366
|
+
last_saved: Time.now
|
367
|
+
})
|
368
|
+
|
369
|
+
# Also save execution log entries to state manager
|
370
|
+
@execution_log.each do |entry|
|
371
|
+
@state_manager.add_execution_log(entry)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def handle_error(error)
|
376
|
+
@error_handler.handle_error(error, self)
|
377
|
+
end
|
378
|
+
|
379
|
+
def cleanup
|
380
|
+
@status_display.cleanup
|
381
|
+
log_execution("Harness cleanup completed")
|
382
|
+
end
|
383
|
+
|
384
|
+
def log_execution(message, data = {})
|
385
|
+
log_entry = {
|
386
|
+
timestamp: Time.now,
|
387
|
+
message: message,
|
388
|
+
state: @state,
|
389
|
+
data: data
|
390
|
+
}
|
391
|
+
@execution_log << log_entry
|
392
|
+
|
393
|
+
# Also log to standard logging if available
|
394
|
+
puts "[#{Time.now.strftime("%H:%M:%S")}] #{message}" if ENV["AIDP_DEBUG"] == "1"
|
395
|
+
end
|
396
|
+
|
397
|
+
def get_completion_message
|
398
|
+
case @state
|
399
|
+
when STATES[:completed]
|
400
|
+
"Harness completed successfully. All steps finished."
|
401
|
+
when STATES[:stopped]
|
402
|
+
"Harness stopped by user."
|
403
|
+
when STATES[:error]
|
404
|
+
"Harness encountered an error and stopped."
|
405
|
+
else
|
406
|
+
"Harness finished in state: #{@state}"
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module Harness
|
5
|
+
module State
|
6
|
+
# Base error class for state management
|
7
|
+
class StateError < StandardError; end
|
8
|
+
|
9
|
+
# Raised when state file operations fail
|
10
|
+
class PersistenceError < StateError; end
|
11
|
+
|
12
|
+
# Raised when state lock cannot be acquired
|
13
|
+
class LockTimeoutError < StateError; end
|
14
|
+
|
15
|
+
# Raised when state data is invalid or corrupted
|
16
|
+
class InvalidStateError < StateError; end
|
17
|
+
|
18
|
+
# Raised when provider state operations fail
|
19
|
+
class ProviderStateError < StateError; end
|
20
|
+
|
21
|
+
# Raised when workflow state operations fail
|
22
|
+
class WorkflowStateError < StateError; end
|
23
|
+
|
24
|
+
# Raised when metrics calculations fail
|
25
|
+
class MetricsError < StateError; end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|