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
-
#progress_tracker ⇒ Object
readonly
Get the underlying progress tracker.
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.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/aidp/harness/state_manager.rb', line 17 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
#progress_tracker ⇒ Object (readonly)
Get the underlying progress tracker
199 200 201 |
# File 'lib/aidp/harness/state_manager.rb', line 199 def progress_tracker @progress_tracker end |
Instance Method Details
#add_user_input(key, value) ⇒ Object
Add user input
119 120 121 122 123 |
# File 'lib/aidp/harness/state_manager.rb', line 119 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
257 258 259 |
# File 'lib/aidp/harness/state_manager.rb', line 257 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)
172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/aidp/harness/state_manager.rb', line 172 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)
76 77 78 79 80 81 82 83 84 |
# File 'lib/aidp/harness/state_manager.rb', line 76 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)
455 456 457 458 459 460 461 |
# File 'lib/aidp/harness/state_manager.rb', line 455 def clear_workstream update_state( current_workstream: nil, workstream_path: nil, workstream_branch: nil ) end |
#completed_steps ⇒ Object
Get completed steps from progress tracker
202 203 204 |
# File 'lib/aidp/harness/state_manager.rb', line 202 def completed_steps @progress_tracker.completed_steps end |
#current_step ⇒ Object
Get current step from progress tracker
207 208 209 |
# File 'lib/aidp/harness/state_manager.rb', line 207 def current_step @progress_tracker.current_step end |
#current_workstream ⇒ Object
Get current workstream slug
424 425 426 427 |
# File 'lib/aidp/harness/state_manager.rb', line 424 def current_workstream state = load_state state[:current_workstream] end |
#current_workstream_path ⇒ Object
Get current workstream path (or project_dir if none)
430 431 432 433 434 435 436 437 |
# File 'lib/aidp/harness/state_manager.rb', line 430 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
187 188 189 190 191 192 193 194 |
# File 'lib/aidp/harness/state_manager.rb', line 187 def export_state { state_file: @state_file, has_state: has_state?, metadata: , state: load_state } end |
#get_performance_metrics ⇒ Object
473 474 475 476 477 478 479 |
# File 'lib/aidp/harness/state_manager.rb', line 473 def get_performance_metrics { efficiency: calculate_efficiency_metrics, reliability: calculate_reliability_metrics, performance: calculate_performance_metrics } end |
#get_token_usage_summary ⇒ Object
409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/aidp/harness/state_manager.rb', line 409 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
300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/aidp/harness/state_manager.rb', line 300 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
39 40 41 42 |
# File 'lib/aidp/harness/state_manager.rb', line 39 def has_state? return false if @skip_persistence File.exist?(@state_file) end |
#load_state ⇒ Object
Load existing state
45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/aidp/harness/state_manager.rb', line 45 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.}" {} end end |
#mark_step_completed(step_name) ⇒ Object
Mark step as completed
217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/aidp/harness/state_manager.rb', line 217 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
233 234 235 236 237 |
# File 'lib/aidp/harness/state_manager.rb', line 233 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
165 166 167 168 169 |
# File 'lib/aidp/harness/state_manager.rb', line 165 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
240 241 242 |
# File 'lib/aidp/harness/state_manager.rb', line 240 def next_step @progress_tracker.next_step end |
#progress_percentage ⇒ Object
Calculate progress percentage
288 289 290 291 |
# File 'lib/aidp/harness/state_manager.rb', line 288 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
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/aidp/harness/state_manager.rb', line 270 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
156 157 158 159 160 161 162 |
# File 'lib/aidp/harness/state_manager.rb', line 156 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
126 127 128 129 |
# File 'lib/aidp/harness/state_manager.rb', line 126 def provider_state state = load_state state[:provider_state] || {} end |
#rate_limit_info ⇒ Object
Get rate limit information
139 140 141 142 |
# File 'lib/aidp/harness/state_manager.rb', line 139 def rate_limit_info state = load_state state[:rate_limit_info] || {} end |
#record_error_event(step_name, error_type, provider_name = nil) ⇒ Object
357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/aidp/harness/state_manager.rb', line 357 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
315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/aidp/harness/state_manager.rb', line 315 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
329 330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/aidp/harness/state_manager.rb', line 329 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
372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/aidp/harness/state_manager.rb', line 372 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
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/aidp/harness/state_manager.rb', line 387 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
343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/aidp/harness/state_manager.rb', line 343 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
262 263 264 265 266 267 |
# File 'lib/aidp/harness/state_manager.rb', line 262 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
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/aidp/harness/state_manager.rb', line 58 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
294 295 296 297 |
# File 'lib/aidp/harness/state_manager.rb', line 294 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
107 108 109 |
# File 'lib/aidp/harness/state_manager.rb', line 107 def set_current_step(step_name) update_state(current_step: step_name, last_updated: Time.now) end |
#set_workstream(slug) ⇒ Object
Set current workstream
440 441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'lib/aidp/harness/state_manager.rb', line 440 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
87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/aidp/harness/state_manager.rb', line 87 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
212 213 214 |
# File 'lib/aidp/harness/state_manager.rb', line 212 def step_completed?(step_name) @progress_tracker.step_completed?(step_name) end |
#total_steps ⇒ Object
Get total steps count
245 246 247 248 249 250 251 252 253 254 |
# File 'lib/aidp/harness/state_manager.rb', line 245 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
132 133 134 135 136 |
# File 'lib/aidp/harness/state_manager.rb', line 132 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
145 146 147 148 149 150 151 152 153 |
# File 'lib/aidp/harness/state_manager.rb', line 145 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
100 101 102 103 104 |
# File 'lib/aidp/harness/state_manager.rb', line 100 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
112 113 114 115 116 |
# File 'lib/aidp/harness/state_manager.rb', line 112 def user_input state = load_state return {} unless state state[:user_input] || {} end |
#workstream_metadata ⇒ Object
Get workstream metadata
464 465 466 467 468 469 470 471 |
# File 'lib/aidp/harness/state_manager.rb', line 464 def state = load_state { slug: state[:current_workstream], path: state[:workstream_path], branch: state[:workstream_branch] } end |