Class: Aidp::AutoUpdate::FailureTracker

Inherits:
Object
  • Object
show all
Includes:
SafeDirectory
Defined in:
lib/aidp/auto_update/failure_tracker.rb

Overview

Service for tracking update failures to prevent restart loops

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from SafeDirectory

#safe_mkdir_p

Constructor Details

#initialize(project_dir: Dir.pwd, max_failures: 3) ⇒ FailureTracker

Returns a new instance of FailureTracker.



16
17
18
19
20
21
22
23
# File 'lib/aidp/auto_update/failure_tracker.rb', line 16

def initialize(project_dir: Dir.pwd, max_failures: 3)
  @project_dir = project_dir
  state_dir = File.join(project_dir, ".aidp")
  actual_dir = safe_mkdir_p(state_dir, component_name: "FailureTracker")
  @state_file = File.join(actual_dir, "auto_update_failures.json")
  @max_failures = max_failures
  @state = load_state
end

Instance Attribute Details

#max_failuresObject (readonly)

Returns the value of attribute max_failures.



14
15
16
# File 'lib/aidp/auto_update/failure_tracker.rb', line 14

def max_failures
  @max_failures
end

#state_fileObject (readonly)

Returns the value of attribute state_file.



14
15
16
# File 'lib/aidp/auto_update/failure_tracker.rb', line 14

def state_file
  @state_file
end

Instance Method Details

#failure_countInteger

Get current failure count

Returns:

  • (Integer)


82
83
84
# File 'lib/aidp/auto_update/failure_tracker.rb', line 82

def failure_count
  @state[:failures].size
end

#failure_timestampsArray<Time>

Get all failure timestamps

Returns:

  • (Array<Time>)


100
101
102
103
104
105
106
# File 'lib/aidp/auto_update/failure_tracker.rb', line 100

def failure_timestamps
  @state[:failures].map { |f| Time.parse(f[:timestamp]) }
rescue => e
  Aidp.log_error("failure_tracker", "timestamp_parsing_failed",
    error: e.message)
  []
end

#force_resetObject

Manually reset failures (for CLI command or recovery)



109
110
111
112
113
114
115
# File 'lib/aidp/auto_update/failure_tracker.rb', line 109

def force_reset
  Aidp.log_warn("failure_tracker", "manual_reset_triggered",
    previous_failures: @state[:failures].size)

  @state[:failures] = []
  save_state
end

#record_failureObject

Record a failure



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/aidp/auto_update/failure_tracker.rb', line 26

def record_failure
  @state[:failures] << {
    timestamp: Time.now.utc.iso8601,
    version: Aidp::VERSION
  }

  # Keep only recent failures (last hour)
  @state[:failures].select! { |f|
    Time.parse(f[:timestamp]) > Time.now - 3600
  }

  save_state

  Aidp.log_warn("failure_tracker", "failure_recorded",
    total_failures: @state[:failures].size,
    max_failures: @max_failures)
rescue => e
  Aidp.log_error("failure_tracker", "record_failure_failed",
    error: e.message)
end

#reset_on_successObject

Reset failure count after successful operation



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/aidp/auto_update/failure_tracker.rb', line 63

def reset_on_success
  previous_failures = @state[:failures].size

  @state[:failures] = []
  @state[:last_success] = Time.now.utc.iso8601
  @state[:last_success_version] = Aidp::VERSION

  save_state

  Aidp.log_info("failure_tracker", "reset_on_success",
    previous_failures: previous_failures,
    version: Aidp::VERSION)
rescue => e
  Aidp.log_error("failure_tracker", "reset_failed",
    error: e.message)
end

#statusHash

Get state summary for status display

Returns:

  • (Hash)


119
120
121
122
123
124
125
126
127
128
# File 'lib/aidp/auto_update/failure_tracker.rb', line 119

def status
  {
    failure_count: failure_count,
    max_failures: @max_failures,
    too_many_failures: too_many_failures?,
    last_success: @state[:last_success],
    last_success_version: @state[:last_success_version],
    recent_failures: @state[:failures]
  }
end

#time_since_last_successInteger?

Get time since last success

Returns:

  • (Integer, nil)

    Seconds since last success, or nil if never successful



88
89
90
91
92
93
94
95
96
# File 'lib/aidp/auto_update/failure_tracker.rb', line 88

def time_since_last_success
  return nil unless @state[:last_success]

  Time.now - Time.parse(@state[:last_success])
rescue => e
  Aidp.log_error("failure_tracker", "time_calculation_failed",
    error: e.message)
  nil
end

#too_many_failures?Boolean

Check if too many consecutive failures have occurred

Returns:

  • (Boolean)


49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/aidp/auto_update/failure_tracker.rb', line 49

def too_many_failures?
  failure_count = @state[:failures].size
  is_looping = failure_count >= @max_failures

  if is_looping
    Aidp.log_error("failure_tracker", "restart_loop_detected",
      failure_count: failure_count,
      max_failures: @max_failures)
  end

  is_looping
end