Class: Aidp::Harness::StateManager
- Inherits:
-
Object
- Object
- Aidp::Harness::StateManager
- Includes:
- SafeDirectory
- Defined in:
- lib/aidp/harness/state_manager.rb
Overview
Manages harness-specific state and persistence, extending existing progress tracking
Instance Attribute Summary collapse
-
#mode ⇒ Object
readonly
Expose for testability.
-
#progress_tracker ⇒ Object
readonly
Get the underlying progress tracker.
-
#project_dir ⇒ Object
readonly
Expose for testability.
Instance Method Summary collapse
-
#add_user_input(key, value) ⇒ Object
Add user input.
-
#all_steps_completed? ⇒ Boolean
Check if all steps are completed.
-
#cleanup_old_state(days_old = 7) ⇒ Object
Clean up old state (older than specified days).
-
#clear_state ⇒ Object
Clear state (for fresh start).
-
#clear_workstream ⇒ Object
Clear current workstream (switch back to main project).
-
#completed_steps ⇒ Object
Get completed steps from progress tracker.
-
#current_step ⇒ Object
Get current step from progress tracker.
-
#current_workstream ⇒ Object
Get current workstream slug.
-
#current_workstream_path ⇒ Object
Get current workstream path (or project_dir if none).
-
#export_state ⇒ Object
Export state for debugging.
- #get_performance_metrics ⇒ Object
- #get_token_usage_summary ⇒ Object
-
#harness_metrics ⇒ Object
Get harness-specific metrics.
-
#has_state? ⇒ Boolean
Check if state exists.
-
#initialize(project_dir, mode, skip_persistence: false) ⇒ StateManager
constructor
A new instance of StateManager.
-
#load_state ⇒ Object
Load existing state.
-
#mark_step_completed(step_name) ⇒ Object
Mark step as completed.
-
#mark_step_in_progress(step_name) ⇒ Object
Mark step as in progress.
-
#next_provider_reset_time ⇒ Object
Get next available provider reset time.
-
#next_step ⇒ Object
Get next step to execute.
-
#progress_percentage ⇒ Object
Calculate progress percentage.
-
#progress_summary ⇒ Object
Get progress summary.
-
#provider_rate_limited?(provider_name) ⇒ Boolean
Check if provider is rate limited.
-
#provider_state ⇒ Object
Get provider state.
-
#rate_limit_info ⇒ Object
Get rate limit information.
- #record_error_event(step_name, error_type, provider_name = nil) ⇒ Object
-
#record_provider_switch(from_provider, to_provider) ⇒ Object
Record harness events.
- #record_rate_limit_event(provider_name, reset_time) ⇒ Object
- #record_retry_attempt(step_name, provider_name, attempt_number) ⇒ Object
- #record_token_usage(provider_name, model_name, input_tokens, output_tokens, cost = nil) ⇒ Object
- #record_user_feedback_request(step_name, questions_count) ⇒ Object
-
#reset_all ⇒ Object
Reset both progress and harness state.
-
#save_state(state_data) ⇒ Object
Save current state.
-
#session_duration ⇒ Object
Calculate session duration.
-
#set_current_step(step_name) ⇒ Object
Set current step.
-
#set_workstream(slug) ⇒ Object
Set current workstream.
-
#state_metadata ⇒ Object
Get state metadata.
-
#step_completed?(step_name) ⇒ Boolean
Check if step is completed.
-
#total_steps ⇒ Object
Get total steps count.
-
#update_provider_state(provider_name, provider_data) ⇒ Object
Update provider state.
-
#update_rate_limit_info(provider_name, reset_time, error_count = 0) ⇒ Object
Update rate limit information.
-
#update_state(updates) ⇒ Object
Update specific state fields.
-
#user_input ⇒ Object
Get user input from state.
-
#workstream_metadata ⇒ Object
Get workstream metadata.
Methods included from SafeDirectory
Constructor Details
#initialize(project_dir, mode, skip_persistence: false) ⇒ StateManager
Returns a new instance of StateManager.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/aidp/harness/state_manager.rb', line 20 def initialize(project_dir, mode, skip_persistence: false) @project_dir = project_dir @mode = mode @state_dir = File.join(project_dir, ".aidp", "harness") @state_file = File.join(@state_dir, "#{mode}_state.json") @lock_file = File.join(@state_dir, "#{mode}_state.lock") @skip_persistence = skip_persistence @memory_state = {} if @skip_persistence # In-memory state for tests case mode when :analyze @progress_tracker = Aidp::Analyze::Progress.new(project_dir, skip_persistence: @skip_persistence) when :execute @progress_tracker = Aidp::Execute::Progress.new(project_dir, skip_persistence: @skip_persistence) else raise ArgumentError, "Unsupported mode: #{mode}" end ensure_state_directory end |
Instance Attribute Details
#mode ⇒ Object (readonly)
Expose for testability
18 19 20 |
# File 'lib/aidp/harness/state_manager.rb', line 18 def mode @mode end |
#progress_tracker ⇒ Object (readonly)
Get the underlying progress tracker
202 203 204 |
# File 'lib/aidp/harness/state_manager.rb', line 202 def progress_tracker @progress_tracker end |
#project_dir ⇒ Object (readonly)
Expose for testability
18 19 20 |
# File 'lib/aidp/harness/state_manager.rb', line 18 def project_dir @project_dir end |
Instance Method Details
#add_user_input(key, value) ⇒ Object
Add user input
122 123 124 125 126 |
# File 'lib/aidp/harness/state_manager.rb', line 122 def add_user_input(key, value) current_input = user_input current_input[key] = value update_state(user_input: current_input, last_updated: Time.now) end |
#all_steps_completed? ⇒ Boolean
Check if all steps are completed
260 261 262 |
# File 'lib/aidp/harness/state_manager.rb', line 260 def all_steps_completed? completed_steps.size == total_steps end |
#cleanup_old_state(days_old = 7) ⇒ Object
Clean up old state (older than specified days)
175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/aidp/harness/state_manager.rb', line 175 def cleanup_old_state(days_old = 7) return unless has_state? state = load_state saved_at = Time.parse(state[:saved_at]) if state[:saved_at] if saved_at && (Time.now - saved_at) > (days_old * 24 * 60 * 60) clear_state true else false end end |
#clear_state ⇒ Object
Clear state (for fresh start)
79 80 81 82 83 84 85 86 87 |
# File 'lib/aidp/harness/state_manager.rb', line 79 def clear_state if @skip_persistence @memory_state = {} return end with_lock do File.delete(@state_file) if File.exist?(@state_file) end end |
#clear_workstream ⇒ Object
Clear current workstream (switch back to main project)
458 459 460 461 462 463 464 |
# File 'lib/aidp/harness/state_manager.rb', line 458 def clear_workstream update_state( current_workstream: nil, workstream_path: nil, workstream_branch: nil ) end |
#completed_steps ⇒ Object
Get completed steps from progress tracker
205 206 207 |
# File 'lib/aidp/harness/state_manager.rb', line 205 def completed_steps @progress_tracker.completed_steps end |
#current_step ⇒ Object
Get current step from progress tracker
210 211 212 |
# File 'lib/aidp/harness/state_manager.rb', line 210 def current_step @progress_tracker.current_step end |
#current_workstream ⇒ Object
Get current workstream slug
427 428 429 430 |
# File 'lib/aidp/harness/state_manager.rb', line 427 def current_workstream state = load_state state[:current_workstream] end |
#current_workstream_path ⇒ Object
Get current workstream path (or project_dir if none)
433 434 435 436 437 438 439 440 |
# File 'lib/aidp/harness/state_manager.rb', line 433 def current_workstream_path slug = current_workstream return @project_dir unless slug require_relative "../worktree" ws = Aidp::Worktree.info(slug: slug, project_dir: @project_dir) ws ? ws[:path] : @project_dir end |
#export_state ⇒ Object
Export state for debugging
190 191 192 193 194 195 196 197 |
# File 'lib/aidp/harness/state_manager.rb', line 190 def export_state { state_file: @state_file, has_state: has_state?, metadata: , state: load_state } end |
#get_performance_metrics ⇒ Object
476 477 478 479 480 481 482 |
# File 'lib/aidp/harness/state_manager.rb', line 476 def get_performance_metrics { efficiency: calculate_efficiency_metrics, reliability: calculate_reliability_metrics, performance: calculate_performance_metrics } end |
#get_token_usage_summary ⇒ Object
412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/aidp/harness/state_manager.rb', line 412 def get_token_usage_summary state = load_state token_usage = state[:token_usage] || {} { total_tokens: token_usage.values.sum { |usage| usage[:total_tokens] }, total_cost: token_usage.values.sum { |usage| usage[:cost] }, total_requests: token_usage.values.sum { |usage| usage[:requests] }, by_provider_model: token_usage } end |
#harness_metrics ⇒ Object
Get harness-specific metrics
303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/aidp/harness/state_manager.rb', line 303 def harness_metrics state = load_state { provider_switches: state[:provider_switches] || 0, rate_limit_events: state[:rate_limit_events] || 0, user_feedback_requests: state[:user_feedback_requests] || 0, error_events: state[:error_events] || 0, retry_attempts: state[:retry_attempts] || 0, current_provider: state[:current_provider], harness_state: state[:state], last_activity: state[:last_updated] } end |
#has_state? ⇒ Boolean
Check if state exists
42 43 44 45 |
# File 'lib/aidp/harness/state_manager.rb', line 42 def has_state? return false if @skip_persistence File.exist?(@state_file) end |
#load_state ⇒ Object
Load existing state
48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/aidp/harness/state_manager.rb', line 48 def load_state return @memory_state if @skip_persistence return {} unless has_state? with_lock do content = File.read(@state_file) JSON.parse(content, symbolize_names: true) rescue JSON::ParserError => e warn "Failed to parse state file: #{e.message}" {} end end |
#mark_step_completed(step_name) ⇒ Object
Mark step as completed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/aidp/harness/state_manager.rb', line 220 def mark_step_completed(step_name) @progress_tracker.mark_step_completed(step_name) # Also update harness state update_state(current_step: nil, last_step_completed: step_name) # Increment iteration counter for current workstream if present ws_slug = current_workstream if ws_slug # File layout: this file is in lib/aidp/harness/state_manager.rb # workstream_state.rb lives at lib/aidp/workstream_state.rb # Correct relative path from here is ../workstream_state require_relative "../workstream_state" Aidp::WorkstreamState.increment_iteration(slug: ws_slug, project_dir: @project_dir) end end |
#mark_step_in_progress(step_name) ⇒ Object
Mark step as in progress
236 237 238 239 240 |
# File 'lib/aidp/harness/state_manager.rb', line 236 def mark_step_in_progress(step_name) @progress_tracker.mark_step_in_progress(step_name) # Also update harness state update_state(current_step: step_name) end |
#next_provider_reset_time ⇒ Object
Get next available provider reset time
168 169 170 171 172 |
# File 'lib/aidp/harness/state_manager.rb', line 168 def next_provider_reset_time rate_limit_info.map do |_provider, info| Time.parse(info[:reset_time]) if info[:reset_time] end.compact.min end |
#next_step ⇒ Object
Get next step to execute
243 244 245 |
# File 'lib/aidp/harness/state_manager.rb', line 243 def next_step @progress_tracker.next_step end |
#progress_percentage ⇒ Object
Calculate progress percentage
291 292 293 294 |
# File 'lib/aidp/harness/state_manager.rb', line 291 def progress_percentage return 100.0 if all_steps_completed? (completed_steps.size.to_f / total_steps * 100).round(2) end |
#progress_summary ⇒ Object
Get progress summary
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/aidp/harness/state_manager.rb', line 273 def progress_summary { mode: @mode, completed_steps: completed_steps.size, total_steps: total_steps, current_step: current_step, next_step: next_step, all_completed: all_steps_completed?, started_at: @progress_tracker.started_at, harness_state: has_state? ? load_state : {}, progress_percentage: progress_percentage, session_duration: session_duration, harness_metrics: harness_metrics, workstream: } end |
#provider_rate_limited?(provider_name) ⇒ Boolean
Check if provider is rate limited
159 160 161 162 163 164 165 |
# File 'lib/aidp/harness/state_manager.rb', line 159 def provider_rate_limited?(provider_name) info = rate_limit_info[provider_name] return false unless info reset_time = Time.parse(info[:reset_time]) if info[:reset_time] reset_time && Time.now < reset_time end |
#provider_state ⇒ Object
Get provider state
129 130 131 132 |
# File 'lib/aidp/harness/state_manager.rb', line 129 def provider_state state = load_state state[:provider_state] || {} end |
#rate_limit_info ⇒ Object
Get rate limit information
142 143 144 145 |
# File 'lib/aidp/harness/state_manager.rb', line 142 def rate_limit_info state = load_state state[:rate_limit_info] || {} end |
#record_error_event(step_name, error_type, provider_name = nil) ⇒ Object
360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/aidp/harness/state_manager.rb', line 360 def record_error_event(step_name, error_type, provider_name = nil) current_state = load_state error_events = (current_state[:error_events] || 0) + 1 update_state( error_events: error_events, last_error: { step: step_name, error_type: error_type, provider: provider_name, timestamp: Time.now } ) end |
#record_provider_switch(from_provider, to_provider) ⇒ Object
Record harness events
318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/aidp/harness/state_manager.rb', line 318 def record_provider_switch(from_provider, to_provider) current_state = load_state provider_switches = (current_state[:provider_switches] || 0) + 1 update_state( provider_switches: provider_switches, last_provider_switch: { from: from_provider, to: to_provider, timestamp: Time.now } ) end |
#record_rate_limit_event(provider_name, reset_time) ⇒ Object
332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/aidp/harness/state_manager.rb', line 332 def record_rate_limit_event(provider_name, reset_time) current_state = load_state rate_limit_events = (current_state[:rate_limit_events] || 0) + 1 update_state( rate_limit_events: rate_limit_events, last_rate_limit: { provider: provider_name, reset_time: reset_time, timestamp: Time.now } ) end |
#record_retry_attempt(step_name, provider_name, attempt_number) ⇒ Object
375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/aidp/harness/state_manager.rb', line 375 def record_retry_attempt(step_name, provider_name, attempt_number) current_state = load_state retry_attempts = (current_state[:retry_attempts] || 0) + 1 update_state( retry_attempts: retry_attempts, last_retry: { step: step_name, provider: provider_name, attempt: attempt_number, timestamp: Time.now } ) end |
#record_token_usage(provider_name, model_name, input_tokens, output_tokens, cost = nil) ⇒ Object
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 |
# File 'lib/aidp/harness/state_manager.rb', line 390 def record_token_usage(provider_name, model_name, input_tokens, output_tokens, cost = nil) current_state = load_state token_usage = current_state[:token_usage] || {} key = "#{provider_name}:#{model_name}" token_usage[key] ||= { input_tokens: 0, output_tokens: 0, total_tokens: 0, cost: 0.0, requests: 0 } token_usage[key][:input_tokens] += input_tokens token_usage[key][:output_tokens] += output_tokens token_usage[key][:total_tokens] += (input_tokens + output_tokens) token_usage[key][:cost] += cost if cost token_usage[key][:requests] += 1 update_state(token_usage: token_usage) end |
#record_user_feedback_request(step_name, questions_count) ⇒ Object
346 347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/aidp/harness/state_manager.rb', line 346 def record_user_feedback_request(step_name, questions_count) current_state = load_state user_feedback_requests = (current_state[:user_feedback_requests] || 0) + 1 update_state( user_feedback_requests: user_feedback_requests, last_user_feedback: { step: step_name, questions_count: questions_count, timestamp: Time.now } ) end |
#reset_all ⇒ Object
Reset both progress and harness state
265 266 267 268 269 270 |
# File 'lib/aidp/harness/state_manager.rb', line 265 def reset_all @progress_tracker.reset clear_state # Also clear test workstream variables # Test-only instance vars removed; rely on persistence skip flag for isolation end |
#save_state(state_data) ⇒ Object
Save current state
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/aidp/harness/state_manager.rb', line 61 def save_state(state_data) if @skip_persistence @memory_state = state_data return end with_lock do = state_data.merge( mode: @mode, project_dir: @project_dir, saved_at: Time.now.iso8601 ) temp_file = "#{@state_file}.tmp" File.write(temp_file, JSON.pretty_generate()) File.rename(temp_file, @state_file) end end |
#session_duration ⇒ Object
Calculate session duration
297 298 299 300 |
# File 'lib/aidp/harness/state_manager.rb', line 297 def session_duration return 0 unless @progress_tracker.started_at Time.now - @progress_tracker.started_at end |
#set_current_step(step_name) ⇒ Object
Set current step
110 111 112 |
# File 'lib/aidp/harness/state_manager.rb', line 110 def set_current_step(step_name) update_state(current_step: step_name, last_updated: Time.now) end |
#set_workstream(slug) ⇒ Object
Set current workstream
443 444 445 446 447 448 449 450 451 452 453 454 455 |
# File 'lib/aidp/harness/state_manager.rb', line 443 def set_workstream(slug) require_relative "../worktree" # Verify workstream exists ws = Aidp::Worktree.info(slug: slug, project_dir: @project_dir) return false unless ws update_state( current_workstream: slug, workstream_path: ws[:path], workstream_branch: ws[:branch] ) true end |
#state_metadata ⇒ Object
Get state metadata
90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/aidp/harness/state_manager.rb', line 90 def return {} unless has_state? state = load_state { mode: state[:mode], saved_at: state[:saved_at], current_step: state[:current_step], state: state[:state], last_updated: state[:last_updated] } end |
#step_completed?(step_name) ⇒ Boolean
Check if step is completed
215 216 217 |
# File 'lib/aidp/harness/state_manager.rb', line 215 def step_completed?(step_name) @progress_tracker.step_completed?(step_name) end |
#total_steps ⇒ Object
Get total steps count
248 249 250 251 252 253 254 255 256 257 |
# File 'lib/aidp/harness/state_manager.rb', line 248 def total_steps case @mode when :analyze Aidp::Analyze::Steps::SPEC.keys.size when :execute Aidp::Execute::Steps::SPEC.keys.size else 0 end end |
#update_provider_state(provider_name, provider_data) ⇒ Object
Update provider state
135 136 137 138 139 |
# File 'lib/aidp/harness/state_manager.rb', line 135 def update_provider_state(provider_name, provider_data) current_provider_state = provider_state current_provider_state[provider_name] = provider_data update_state(provider_state: current_provider_state, last_updated: Time.now) end |
#update_rate_limit_info(provider_name, reset_time, error_count = 0) ⇒ Object
Update rate limit information
148 149 150 151 152 153 154 155 156 |
# File 'lib/aidp/harness/state_manager.rb', line 148 def update_rate_limit_info(provider_name, reset_time, error_count = 0) current_info = rate_limit_info current_info[provider_name] = { reset_time: reset_time&.iso8601, error_count: error_count, last_updated: Time.now.iso8601 } update_state(rate_limit_info: current_info, last_updated: Time.now) end |
#update_state(updates) ⇒ Object
Update specific state fields
103 104 105 106 107 |
# File 'lib/aidp/harness/state_manager.rb', line 103 def update_state(updates) current_state = load_state || {} updated_state = current_state.merge(updates) save_state(updated_state) end |
#user_input ⇒ Object
Get user input from state
115 116 117 118 119 |
# File 'lib/aidp/harness/state_manager.rb', line 115 def user_input state = load_state return {} unless state state[:user_input] || {} end |
#workstream_metadata ⇒ Object
Get workstream metadata
467 468 469 470 471 472 473 474 |
# File 'lib/aidp/harness/state_manager.rb', line 467 def state = load_state { slug: state[:current_workstream], path: state[:workstream_path], branch: state[:workstream_branch] } end |