Class: Aidp::Harness::Runner

Inherits:
Object
  • Object
show all
Includes:
MessageDisplay
Defined in:
lib/aidp/harness/runner.rb

Overview

Main harness runner that orchestrates the execution loop

Constant Summary collapse

STATES =

Harness execution states

{
  idle: "idle",
  running: "running",
  paused: "paused",
  waiting_for_user: "waiting_for_user",
  waiting_for_rate_limit: "waiting_for_rate_limit",
  stopped: "stopped",
  completed: "completed",
  error: "error",
  needs_clarification: "needs_clarification"
}.freeze

Constants included from MessageDisplay

MessageDisplay::COLOR_MAP

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MessageDisplay

#display_message, included, #message_display_prompt

Constructor Details

#initialize(project_dir, mode = :analyze, options = {}) ⇒ Runner

Returns a new instance of Runner.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/aidp/harness/runner.rb', line 38

def initialize(project_dir, mode = :analyze, options = {})
  @project_dir = project_dir
  @mode = mode.to_sym
  @options = options
  @state = STATES[:idle]
  @start_time = nil
  @current_step = nil
  @current_provider = nil
  @user_input = options[:user_input] || {} # Include user input from workflow selection
  @execution_log = []
  @last_error = nil
  @prompt = options[:prompt] || TTY::Prompt.new

  # Store workflow configuration
  @selected_steps = options[:selected_steps]
  @workflow_type = options[:workflow_type]
  @non_interactive = options[:non_interactive] || (@workflow_type == :watch_mode)

  # Initialize components
  @configuration = Configuration.new(project_dir)
  @state_manager = StateManager.new(project_dir, @mode)
  @provider_manager = ProviderManager.new(@configuration, prompt: @prompt)

  # Use ZFC-enabled condition detector
  # ZfcConditionDetector will create its own ProviderFactory if needed
  # Falls back to legacy pattern matching when ZFC is disabled
  require_relative "zfc_condition_detector"
  @condition_detector = ZfcConditionDetector.new(@configuration)

  @user_interface = SimpleUserInterface.new
  @error_handler = ErrorHandler.new(@provider_manager, @configuration)
  @status_display = StatusDisplay.new
  @completion_checker = CompletionChecker.new(@project_dir, @workflow_type)
  @failure_reason = nil
  @failure_metadata = nil
end

Instance Attribute Details

#clarification_questionsObject (readonly)

Public accessors for testing and integration



36
37
38
# File 'lib/aidp/harness/runner.rb', line 36

def clarification_questions
  @clarification_questions
end

#current_providerObject (readonly)

Public accessors for testing and integration



36
37
38
# File 'lib/aidp/harness/runner.rb', line 36

def current_provider
  @current_provider
end

#current_stepObject (readonly)

Public accessors for testing and integration



36
37
38
# File 'lib/aidp/harness/runner.rb', line 36

def current_step
  @current_step
end

#execution_logObject (readonly)

Public accessors for testing and integration



36
37
38
# File 'lib/aidp/harness/runner.rb', line 36

def execution_log
  @execution_log
end

#provider_managerObject (readonly)

Public accessors for testing and integration



36
37
38
# File 'lib/aidp/harness/runner.rb', line 36

def provider_manager
  @provider_manager
end

#user_inputObject (readonly)

Public accessors for testing and integration



36
37
38
# File 'lib/aidp/harness/runner.rb', line 36

def user_input
  @user_input
end

Instance Method Details

#detailed_statusObject

Get detailed status including all components



219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/aidp/harness/runner.rb', line 219

def detailed_status
  {
    harness: status,
    configuration: {
      default_provider: @configuration.default_provider,
      fallback_providers: @configuration.fallback_providers,
      max_retries: @configuration.harness_config[:max_retries]
    },
    provider_manager: @provider_manager.status,
    error_stats: @error_handler.error_stats
  }
end

#pauseObject

Pause the harness execution



179
180
181
182
183
184
185
# File 'lib/aidp/harness/runner.rb', line 179

def pause
  return unless @state == STATES[:running]

  @state = STATES[:paused]
  log_execution("Harness paused by user")
  @status_display.show_paused_status
end

#resumeObject

Resume the harness execution



188
189
190
191
192
193
194
# File 'lib/aidp/harness/runner.rb', line 188

def resume
  return unless @state == STATES[:paused]

  @state = STATES[:running]
  log_execution("Harness resumed by user")
  @status_display.show_resumed_status
end

#runObject

Main execution method - runs the harness loop



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/aidp/harness/runner.rb', line 76

def run
  @state = STATES[:running]
  @start_time = Time.now

  log_execution("Harness started", {mode: @mode, project_dir: @project_dir})

  begin
    # Load existing state if resuming
    load_state if @state_manager.has_state?

    # Get the appropriate runner for the mode
    runner = get_mode_runner

    # Main execution loop
    loop do
      break if should_stop?

      # Check for pause conditions
      if should_pause?
        handle_pause_condition
        next
      end

      # Get next step to execute
      next_step = get_next_step(runner)
      break unless next_step

      # Execute the step
      execute_step(runner, next_step)

      # Update state
      update_state
    end

    # Mark as completed if we finished all steps AND all completion criteria are met
    if all_steps_completed?(runner)
      completion_status = @completion_checker.completion_status
      if completion_status[:all_complete]
        @state = STATES[:completed]
        log_execution("Harness completed successfully - all criteria met", completion_status)
      else
        log_execution("Steps completed but completion criteria not met", completion_status)
        display_message("\n⚠️  All steps completed but some completion criteria not met:", type: :warning)
        display_message(completion_status[:summary], type: :info)

        # Ask user if they want to continue anyway
        if confirmation_prompt_allowed?
          if @user_interface.get_confirmation("Continue anyway? This may indicate issues that should be addressed.", default: false)
            @state = STATES[:completed]
            log_execution("Harness completed with user override")
          else
            mark_completion_failure(completion_status)
            @state = STATES[:error]
            log_execution("Harness stopped due to unmet completion criteria")
          end
        else
          display_message("⚠️  Non-interactive mode: cannot override failed completion criteria. Stopping run.", type: :warning)
          mark_completion_failure(completion_status)
          @state = STATES[:error]
          log_execution("Harness stopped due to unmet completion criteria in non-interactive mode")
        end
      end
    end
  rescue Aidp::Errors::ConfigurationError
    # Configuration errors should crash immediately (crash-early principle)
    # Re-raise without catching
    raise
  rescue => e
    @state = STATES[:error]
    @last_error = e
    log_execution("Harness error: #{e.message}", {error: e.class.name, backtrace: e.backtrace&.first(5)})
    handle_error(e)
  ensure
    # Save state before exiting - protect against exceptions during cleanup
    begin
      save_state
    rescue => e
      # Don't let state save failures kill the whole run or prevent cleanup
      Aidp.logger.error("harness", "Failed to save state during cleanup: #{e.message}", error: e.class.name)
      @last_error ||= e # Only set if no previous error
    end

    begin
      cleanup
    rescue => e
      # Don't let cleanup failures propagate
      Aidp.logger.error("harness", "Failed during cleanup: #{e.message}", error: e.class.name)
    end
  end

  result = {status: @state, message: get_completion_message}
  result[:reason] = @failure_reason if @failure_reason
  result[:failure_metadata] = @failure_metadata if @failure_metadata
  result[:clarification_questions] = @clarification_questions if @clarification_questions
  if @last_error
    result[:error] = @last_error.message
    result[:error_class] = @last_error.class.name
    result[:backtrace] = @last_error.backtrace&.first(10)
  end
  result
end

#statusObject

Get current harness status



204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/aidp/harness/runner.rb', line 204

def status
  {
    state: @state,
    mode: @mode,
    current_step: @current_step,
    current_provider: @current_provider,
    start_time: @start_time,
    duration: @start_time ? Time.now - @start_time : 0,
    user_input_count: @user_input.size,
    execution_log_count: @execution_log.size,
    progress: @state_manager.progress_summary
  }
end

#stopObject

Stop the harness execution



197
198
199
200
201
# File 'lib/aidp/harness/runner.rb', line 197

def stop
  @state = STATES[:stopped]
  log_execution("Harness stopped by user")
  @status_display.show_stopped_status
end