aidp 0.14.1 → 0.15.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.
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "deterministic_unit"
4
+ require_relative "../logger"
5
+
6
+ module Aidp
7
+ module Execute
8
+ class WorkLoopUnitScheduler
9
+ Unit = Struct.new(:type, :name, :definition, keyword_init: true) do
10
+ def agentic?
11
+ type == :agentic
12
+ end
13
+
14
+ def deterministic?
15
+ type == :deterministic
16
+ end
17
+ end
18
+
19
+ attr_reader :last_agentic_summary
20
+
21
+ def initialize(units_config, clock: Time)
22
+ @clock = clock
23
+ @deterministic_definitions = build_deterministic_definitions(units_config[:deterministic])
24
+ @defaults = default_options.merge(units_config[:defaults] || {})
25
+ @pending_units = []
26
+ @deterministic_history = []
27
+ @deterministic_state = Hash.new { |h, key| h[key] = default_deterministic_state }
28
+ @agentic_runs = []
29
+ @last_agentic_summary = nil
30
+ @consecutive_deciders = 0
31
+ @completed = false
32
+ @started = false
33
+ end
34
+
35
+ def next_unit
36
+ return nil if @completed
37
+
38
+ unless @started
39
+ @started = true
40
+ queue_requested_unit(@defaults[:initial_unit] || :agentic)
41
+ end
42
+
43
+ unit = @pending_units.shift
44
+ return unit if unit
45
+
46
+ queue_requested_unit(@defaults[:on_no_next_step] || :agentic)
47
+
48
+ @pending_units.shift
49
+ end
50
+
51
+ def record_agentic_result(result, requested_next: nil, summary: nil, completed: false)
52
+ @last_agentic_summary = summarize(summary)
53
+ @agentic_runs << {timestamp: @clock.now, result: result}
54
+
55
+ queue_requested_unit(requested_next) if requested_next
56
+
57
+ mark_completed if completed && !requested_next
58
+ end
59
+
60
+ def record_deterministic_result(definition, result)
61
+ state = @deterministic_state[definition.name]
62
+ state[:last_run_at] = result.finished_at
63
+
64
+ state[:current_backoff] = if result.success? || result.status == :event
65
+ definition.min_interval_seconds
66
+ else
67
+ [
68
+ state[:current_backoff] * definition.backoff_multiplier,
69
+ definition.max_backoff_seconds
70
+ ].min
71
+ end
72
+
73
+ @deterministic_history << {
74
+ name: definition.name,
75
+ status: result.status,
76
+ output_path: result.output_path,
77
+ finished_at: result.finished_at,
78
+ data: result.data
79
+ }
80
+
81
+ requested = definition.next_for(result.status)
82
+ queue_requested_unit(requested) if requested
83
+ end
84
+
85
+ def deterministic_context(limit: 5)
86
+ @deterministic_history.last(limit)
87
+ end
88
+
89
+ def completed?
90
+ @completed
91
+ end
92
+
93
+ private
94
+
95
+ def summarize(summary)
96
+ return nil unless summary
97
+ content = summary.to_s.strip
98
+ return nil if content.empty?
99
+ (content.length > 500) ? "#{content[0...500]}…" : content
100
+ end
101
+
102
+ def mark_completed
103
+ @completed = true
104
+ @pending_units.clear
105
+ end
106
+
107
+ def queue_requested_unit(identifier)
108
+ return if identifier.nil?
109
+
110
+ case identifier.to_sym
111
+ when :agentic
112
+ enqueue_agentic(:primary)
113
+ when :decide_whats_next
114
+ enqueue_agentic(:decide_whats_next)
115
+ else
116
+ enqueue_deterministic(identifier.to_s)
117
+ end
118
+ rescue NoMethodError
119
+ enqueue_agentic(:primary)
120
+ end
121
+
122
+ def enqueue_agentic(name)
123
+ if name == :decide_whats_next
124
+ if @consecutive_deciders >= @defaults[:max_consecutive_deciders]
125
+ enqueue_agentic(:primary)
126
+ return
127
+ end
128
+ @consecutive_deciders += 1
129
+ else
130
+ @consecutive_deciders = 0
131
+ end
132
+
133
+ @pending_units << Unit.new(type: :agentic, name: name)
134
+ end
135
+
136
+ def enqueue_deterministic(name)
137
+ definition = @deterministic_definitions[name]
138
+ unless definition
139
+ enqueue_agentic((@defaults[:fallback_agentic] || :agentic).to_sym)
140
+ return
141
+ end
142
+ return unless definition.enabled
143
+
144
+ state = @deterministic_state[definition.name]
145
+ state[:current_backoff] ||= definition.min_interval_seconds
146
+
147
+ if cooldown_remaining(definition).positive?
148
+ enqueue_agentic((@defaults[:fallback_agentic] || :agentic).to_sym)
149
+ return
150
+ end
151
+
152
+ @pending_units << Unit.new(type: :deterministic, name: definition.name, definition: definition)
153
+ end
154
+
155
+ def cooldown_remaining(definition)
156
+ state = @deterministic_state[definition.name]
157
+ state[:current_backoff] ||= definition.min_interval_seconds
158
+
159
+ return 0 unless state[:last_run_at]
160
+
161
+ next_allowed_at = state[:last_run_at] + state[:current_backoff]
162
+ remaining = next_allowed_at - @clock.now
163
+ remaining.positive? ? remaining : 0
164
+ end
165
+
166
+ def build_deterministic_definitions(config_list)
167
+ Array(config_list).each_with_object({}) do |config, mapping|
168
+ definition = DeterministicUnits::Definition.new(config.transform_keys(&:to_sym))
169
+ mapping[definition.name] = definition
170
+ rescue KeyError, ArgumentError => e
171
+ Aidp.logger.warn("work_loop", "Skipping invalid deterministic unit configuration",
172
+ name: config[:name], error: e.message)
173
+ end
174
+ end
175
+
176
+ def default_deterministic_state
177
+ {last_run_at: nil, current_backoff: nil}
178
+ end
179
+
180
+ def default_options
181
+ {
182
+ initial_unit: :agentic,
183
+ on_no_next_step: :agentic,
184
+ fallback_agentic: :decide_whats_next,
185
+ max_consecutive_deciders: 1
186
+ }
187
+ end
188
+ end
189
+ end
190
+ end
@@ -374,7 +374,8 @@ module Aidp
374
374
  enabled: true,
375
375
  max_iterations: 50,
376
376
  test_commands: [],
377
- lint_commands: []
377
+ lint_commands: [],
378
+ units: {}
378
379
  },
379
380
  properties: {
380
381
  enabled: {
@@ -405,6 +406,95 @@ module Aidp
405
406
  type: :string
406
407
  }
407
408
  },
409
+ units: {
410
+ type: :hash,
411
+ required: false,
412
+ default: {},
413
+ properties: {
414
+ deterministic: {
415
+ type: :array,
416
+ required: false,
417
+ default: [],
418
+ items: {
419
+ type: :hash,
420
+ properties: {
421
+ name: {
422
+ type: :string,
423
+ required: true
424
+ },
425
+ command: {
426
+ type: :string,
427
+ required: false
428
+ },
429
+ type: {
430
+ type: :string,
431
+ required: false,
432
+ enum: ["command", "wait"]
433
+ },
434
+ output_file: {
435
+ type: :string,
436
+ required: false
437
+ },
438
+ enabled: {
439
+ type: :boolean,
440
+ required: false,
441
+ default: true
442
+ },
443
+ min_interval_seconds: {
444
+ type: :integer,
445
+ required: false,
446
+ min: 1
447
+ },
448
+ max_backoff_seconds: {
449
+ type: :integer,
450
+ required: false,
451
+ min: 1
452
+ },
453
+ backoff_multiplier: {
454
+ type: :number,
455
+ required: false,
456
+ min: 1.0
457
+ },
458
+ metadata: {
459
+ type: :hash,
460
+ required: false,
461
+ default: {}
462
+ },
463
+ next: {
464
+ type: :hash,
465
+ required: false,
466
+ default: {}
467
+ }
468
+ }
469
+ }
470
+ },
471
+ defaults: {
472
+ type: :hash,
473
+ required: false,
474
+ default: {},
475
+ properties: {
476
+ initial_unit: {
477
+ type: :string,
478
+ required: false
479
+ },
480
+ on_no_next_step: {
481
+ type: :string,
482
+ required: false
483
+ },
484
+ fallback_agentic: {
485
+ type: :string,
486
+ required: false
487
+ },
488
+ max_consecutive_deciders: {
489
+ type: :integer,
490
+ required: false,
491
+ min: 1,
492
+ max: 10
493
+ }
494
+ }
495
+ }
496
+ }
497
+ },
408
498
  guards: {
409
499
  type: :hash,
410
500
  required: false,
@@ -150,6 +150,10 @@ module Aidp
150
150
  harness_config[:work_loop] || default_work_loop_config
151
151
  end
152
152
 
153
+ def work_loop_units_config
154
+ work_loop_config[:units] || default_units_config
155
+ end
156
+
153
157
  # Check if work loops are enabled
154
158
  def work_loop_enabled?
155
159
  work_loop_config[:enabled]
@@ -483,7 +487,62 @@ module Aidp
483
487
  max_iterations: 50,
484
488
  test_commands: [],
485
489
  lint_commands: [],
486
- guards: default_guards_config
490
+ guards: default_guards_config,
491
+ units: default_units_config
492
+ }
493
+ end
494
+
495
+ def default_units_config
496
+ {
497
+ deterministic: [
498
+ {
499
+ name: "run_full_tests",
500
+ command: "bundle exec rake spec",
501
+ output_file: ".aidp/out/run_full_tests.yml",
502
+ enabled: false,
503
+ min_interval_seconds: 300,
504
+ max_backoff_seconds: 1800,
505
+ next: {
506
+ success: :agentic,
507
+ failure: :decide_whats_next,
508
+ else: :decide_whats_next
509
+ }
510
+ },
511
+ {
512
+ name: "run_lint",
513
+ command: "bundle exec standardrb",
514
+ output_file: ".aidp/out/run_lint.yml",
515
+ enabled: false,
516
+ min_interval_seconds: 300,
517
+ max_backoff_seconds: 1800,
518
+ next: {
519
+ success: :agentic,
520
+ failure: :decide_whats_next,
521
+ else: :decide_whats_next
522
+ }
523
+ },
524
+ {
525
+ name: "wait_for_github",
526
+ type: :wait,
527
+ output_file: ".aidp/out/wait_for_github.yml",
528
+ metadata: {
529
+ interval_seconds: 60,
530
+ backoff_seconds: 60
531
+ },
532
+ min_interval_seconds: 60,
533
+ max_backoff_seconds: 900,
534
+ next: {
535
+ event: :agentic,
536
+ else: :wait_for_github
537
+ }
538
+ }
539
+ ],
540
+ defaults: {
541
+ initial_unit: :agentic,
542
+ on_no_next_step: :wait_for_github,
543
+ fallback_agentic: :decide_whats_next,
544
+ max_consecutive_deciders: 1
545
+ }
487
546
  }
488
547
  end
489
548
 
@@ -81,6 +81,8 @@ module Aidp
81
81
  @state = STATES[:running]
82
82
  @start_time = Time.now
83
83
 
84
+ Aidp.logger.info("harness_runner", "Starting harness execution", mode: @mode, workflow_type: @workflow_type, steps_count: @selected_steps.size)
85
+
84
86
  @tui.show_message("🚀 Starting #{@mode.to_s.capitalize} Mode", :info)
85
87
 
86
88
  begin
@@ -3,11 +3,14 @@
3
3
  require "json"
4
4
  require "yaml"
5
5
  require "fileutils"
6
+ require_relative "../rescue_logging"
6
7
 
7
8
  module Aidp
8
9
  module Harness
9
10
  # Stores detailed information about AI providers gathered from their CLI tools
10
11
  class ProviderInfo
12
+ include Aidp::RescueLogging
13
+
11
14
  attr_reader :provider_name, :info_file_path
12
15
 
13
16
  def initialize(provider_name, root_dir = nil)
@@ -56,6 +59,7 @@ module Aidp
56
59
 
57
60
  YAML.safe_load_file(@info_file_path, permitted_classes: [Time, Symbol])
58
61
  rescue => e
62
+ log_rescue(e, component: "provider_info", action: "load_yaml", fallback: nil, provider: @provider_name, path: @info_file_path)
59
63
  warn "Failed to load provider info for #{@provider_name}: #{e.message}"
60
64
  nil
61
65
  end
@@ -133,7 +137,8 @@ module Aidp
133
137
 
134
138
  last_checked = Time.parse(info[:last_checked].to_s)
135
139
  (Time.now - last_checked) > max_age
136
- rescue
140
+ rescue => e
141
+ log_rescue(e, component: "provider_info", action: "parse_last_checked_time", fallback: true, provider: @provider_name, timestamp: info[:last_checked])
137
142
  true
138
143
  end
139
144
 
@@ -147,6 +152,7 @@ module Aidp
147
152
 
148
153
  provider_instance.fetch_mcp_servers
149
154
  rescue => e
155
+ log_rescue(e, component: "provider_info", action: "fetch_mcp_servers", fallback: [], provider: @provider_name)
150
156
  warn "Failed to fetch MCP servers for #{@provider_name}: #{e.message}" if ENV["AIDP_DEBUG"]
151
157
  []
152
158
  end
@@ -157,7 +163,8 @@ module Aidp
157
163
  # Try to find the binary
158
164
  path = begin
159
165
  Aidp::Util.which(binary_name)
160
- rescue
166
+ rescue => e
167
+ log_rescue(e, component: "provider_info", action: "locate_provider_binary", fallback: nil, provider: @provider_name, binary: binary_name)
161
168
  nil
162
169
  end
163
170
  return nil unless path
@@ -183,7 +190,8 @@ module Aidp
183
190
  Process.kill("TERM", pid)
184
191
  sleep 0.1
185
192
  Process.kill("KILL", pid)
186
- rescue
193
+ rescue => e
194
+ log_rescue(e, component: "provider_info", action: "kill_timeout_provider_command", fallback: nil, provider: @provider_name, binary: binary_name, pid: pid)
187
195
  nil
188
196
  end
189
197
  return nil
@@ -192,7 +200,8 @@ module Aidp
192
200
  output = r.read
193
201
  r.close
194
202
  output
195
- rescue
203
+ rescue => e
204
+ log_rescue(e, component: "provider_info", action: "execute_provider_command", fallback: nil, provider: @provider_name, binary: binary_name, args: args)
196
205
  nil
197
206
  end
198
207
  end
@@ -355,6 +364,7 @@ module Aidp
355
364
  # Create provider instance
356
365
  @provider_instance = provider_class.new
357
366
  rescue => e
367
+ log_rescue(e, component: "provider_info", action: "create_provider_instance", fallback: nil, provider: @provider_name)
358
368
  warn "Failed to create provider instance for #{@provider_name}: #{e.message}" if ENV["AIDP_DEBUG"]
359
369
  nil
360
370
  end
@@ -2,12 +2,14 @@
2
2
 
3
3
  require "tty-prompt"
4
4
  require_relative "provider_factory"
5
+ require_relative "../rescue_logging"
5
6
 
6
7
  module Aidp
7
8
  module Harness
8
9
  # Manages provider switching and fallback logic
9
10
  class ProviderManager
10
11
  include Aidp::MessageDisplay
12
+ include Aidp::RescueLogging
11
13
 
12
14
  def initialize(configuration, prompt: TTY::Prompt.new)
13
15
  @configuration = configuration
@@ -69,6 +71,8 @@ module Aidp
69
71
 
70
72
  # Switch to next available provider with sophisticated fallback logic
71
73
  def switch_provider(reason = "manual_switch", context = {})
74
+ Aidp.logger.info("provider_manager", "Attempting provider switch", reason: reason, current: current_provider, **context)
75
+
72
76
  # Get fallback chain for current provider
73
77
  provider_fallback_chain = fallback_chain(current_provider)
74
78
 
@@ -79,7 +83,10 @@ module Aidp
79
83
  success = set_current_provider(next_provider, reason, context)
80
84
  if success
81
85
  log_provider_switch(current_provider, next_provider, reason, context)
86
+ Aidp.logger.info("provider_manager", "Provider switched successfully", from: current_provider, to: next_provider, reason: reason)
82
87
  return next_provider
88
+ else
89
+ Aidp.logger.warn("provider_manager", "Failed to switch to provider", provider: next_provider, reason: reason)
83
90
  end
84
91
  end
85
92
 
@@ -107,14 +114,21 @@ module Aidp
107
114
 
108
115
  # No providers available
109
116
  log_no_providers_available(reason, context)
117
+ Aidp.logger.error("provider_manager", "No providers available for fallback", reason: reason, **context)
110
118
  nil
111
119
  end
112
120
 
113
121
  # Switch provider for specific error type
114
122
  def switch_provider_for_error(error_type, error_details = {})
123
+ Aidp.logger.warn("provider_manager", "Error triggered provider switch", error_type: error_type, **error_details)
124
+
115
125
  case error_type
116
126
  when "rate_limit"
117
127
  switch_provider("rate_limit", error_details)
128
+ when "resource_exhausted", "quota_exceeded"
129
+ # Treat capacity/resource exhaustion like rate limit for fallback purposes
130
+ Aidp.logger.warn("provider_manager", "Resource/quota exhaustion detected", classified_from: error_type)
131
+ switch_provider("rate_limit", error_details.merge(classified_from: error_type))
118
132
  when "authentication"
119
133
  switch_provider("authentication_error", error_details)
120
134
  when "network"
@@ -269,9 +283,27 @@ module Aidp
269
283
 
270
284
  # Set current provider with enhanced validation
271
285
  def set_current_provider(provider_name, reason = "manual_switch", context = {})
272
- return false unless @configuration.provider_configured?(provider_name)
273
- return false unless is_provider_healthy?(provider_name)
274
- return false if is_provider_circuit_breaker_open?(provider_name)
286
+ # Use provider_config for ConfigManager, provider_configured? for legacy Configuration
287
+ config_exists = if @configuration.respond_to?(:provider_config)
288
+ @configuration.provider_config(provider_name)
289
+ else
290
+ @configuration.provider_configured?(provider_name)
291
+ end
292
+
293
+ unless config_exists
294
+ Aidp.logger.warn("provider_manager", "Provider not configured", provider: provider_name)
295
+ return false
296
+ end
297
+
298
+ unless is_provider_healthy?(provider_name)
299
+ Aidp.logger.warn("provider_manager", "Provider not healthy", provider: provider_name)
300
+ return false
301
+ end
302
+
303
+ if is_provider_circuit_breaker_open?(provider_name)
304
+ Aidp.logger.warn("provider_manager", "Provider circuit breaker open", provider: provider_name)
305
+ return false
306
+ end
275
307
 
276
308
  # Update provider health
277
309
  update_provider_health(provider_name, "switched_to")
@@ -292,6 +324,7 @@ module Aidp
292
324
  @current_model = default_model(provider_name)
293
325
 
294
326
  @current_provider = provider_name
327
+ Aidp.logger.info("provider_manager", "Provider activated", provider: provider_name, reason: reason)
295
328
  true
296
329
  end
297
330
 
@@ -468,11 +501,24 @@ module Aidp
468
501
  # Build default fallback chain
469
502
  def build_default_fallback_chain(provider_name)
470
503
  all_providers = configured_providers
471
- fallback_chain = all_providers.dup
472
- fallback_chain.delete(provider_name)
473
- fallback_chain.unshift(provider_name) # Put current provider first
474
- @fallback_chains[provider_name] = fallback_chain
475
- fallback_chain
504
+
505
+ # Harness-defined explicit ordering has priority
506
+ harness_fallbacks = if @configuration.respond_to?(:fallback_providers)
507
+ Array(@configuration.fallback_providers).map(&:to_s)
508
+ else
509
+ []
510
+ end
511
+
512
+ # Construct ordered chain:
513
+ # 1. current provider first
514
+ # 2. harness fallback providers (excluding current and de-duplicated)
515
+ # 3. any remaining configured providers not already listed
516
+ ordered = [provider_name]
517
+ ordered += harness_fallbacks.reject { |p| p == provider_name || ordered.include?(p) }
518
+ ordered += all_providers.reject { |p| ordered.include?(p) }
519
+
520
+ @fallback_chains[provider_name] = ordered
521
+ ordered
476
522
  end
477
523
 
478
524
  # Find next healthy provider in fallback chain
@@ -1008,7 +1054,8 @@ module Aidp
1008
1054
  require_relative "../providers/cursor"
1009
1055
  installed = Aidp::Providers::Cursor.available?
1010
1056
  end
1011
- rescue LoadError
1057
+ rescue LoadError => e
1058
+ log_rescue(e, component: "provider_manager", action: "check_provider_availability", fallback: false, provider: provider_name)
1012
1059
  installed = false
1013
1060
  end
1014
1061
  @unavailable_cache[provider_name] = installed
@@ -1048,7 +1095,8 @@ module Aidp
1048
1095
  end
1049
1096
  path = begin
1050
1097
  Aidp::Util.which(binary)
1051
- rescue
1098
+ rescue => e
1099
+ log_rescue(e, component: "provider_manager", action: "locate_binary", fallback: nil, binary: binary)
1052
1100
  nil
1053
1101
  end
1054
1102
  unless path
@@ -1074,13 +1122,15 @@ module Aidp
1074
1122
  # Timeout -> kill
1075
1123
  begin
1076
1124
  Process.kill("TERM", pid)
1077
- rescue
1125
+ rescue => e
1126
+ log_rescue(e, component: "provider_manager", action: "kill_timeout_process_term", fallback: nil, binary: binary, pid: pid)
1078
1127
  nil
1079
1128
  end
1080
1129
  sleep 0.1
1081
1130
  begin
1082
1131
  Process.kill("KILL", pid)
1083
- rescue
1132
+ rescue => e
1133
+ log_rescue(e, component: "provider_manager", action: "kill_timeout_process_kill", fallback: nil, binary: binary, pid: pid)
1084
1134
  nil
1085
1135
  end
1086
1136
  ok = false
@@ -1093,6 +1143,7 @@ module Aidp
1093
1143
  ok = true
1094
1144
  end
1095
1145
  rescue => e
1146
+ log_rescue(e, component: "provider_manager", action: "verify_binary_health", fallback: "binary_error", binary: binary)
1096
1147
  ok = false
1097
1148
  reason = e.class.name.downcase.include?("enoent") ? "binary_missing" : "binary_error"
1098
1149
  end
@@ -1340,6 +1391,7 @@ module Aidp
1340
1391
  }
1341
1392
  }
1342
1393
  rescue => e
1394
+ log_rescue(e, component: "provider_manager", action: "execute_with_provider", fallback: "error_result", provider: provider_type, prompt_length: prompt.length)
1343
1395
  # Return error result
1344
1396
  {
1345
1397
  status: "error",
@@ -3,6 +3,7 @@
3
3
  require "securerandom"
4
4
  require "yaml"
5
5
  require "fileutils"
6
+ require_relative "../rescue_logging"
6
7
 
7
8
  module Aidp
8
9
  module Jobs
@@ -10,6 +11,7 @@ module Aidp
10
11
  # Runs harness in daemon process and tracks job metadata
11
12
  class BackgroundRunner
12
13
  include Aidp::MessageDisplay
14
+ include Aidp::RescueLogging
13
15
 
14
16
  attr_reader :project_dir, :jobs_dir
15
17
 
@@ -55,6 +57,7 @@ module Aidp
55
57
  puts "[#{Time.now}] Job completed with status: #{result[:status]}"
56
58
  mark_job_completed(job_id, result)
57
59
  rescue => e
60
+ log_rescue(e, component: "background_runner", action: "execute_job", fallback: "mark_failed", job_id: job_id, mode: mode)
58
61
  puts "[#{Time.now}] Job failed with error: #{e.message}"
59
62
  puts e.backtrace.join("\n")
60
63
  mark_job_failed(job_id, e)
@@ -136,11 +139,13 @@ module Aidp
136
139
 
137
140
  mark_job_stopped(job_id)
138
141
  {success: true, message: "Job stopped successfully"}
139
- rescue Errno::ESRCH
142
+ rescue Errno::ESRCH => e
143
+ log_rescue(e, component: "background_runner", action: "stop_job", fallback: "mark_stopped", job_id: job_id, pid: pid, level: :info)
140
144
  # Process already dead
141
145
  mark_job_stopped(job_id)
142
146
  {success: true, message: "Job was already stopped"}
143
147
  rescue => e
148
+ log_rescue(e, component: "background_runner", action: "stop_job", fallback: "error_result", job_id: job_id, pid: pid)
144
149
  {success: false, message: "Failed to stop job: #{e.message}"}
145
150
  end
146
151
  end
@@ -244,7 +249,8 @@ module Aidp
244
249
 
245
250
  Process.kill(0, pid)
246
251
  true
247
- rescue Errno::ESRCH, Errno::EPERM
252
+ rescue Errno::ESRCH, Errno::EPERM => e
253
+ log_rescue(e, component: "background_runner", action: "check_process_running", fallback: false, pid: pid, level: :debug)
248
254
  false
249
255
  end
250
256
 
@@ -252,7 +258,8 @@ module Aidp
252
258
  # Try to load checkpoint from project directory
253
259
  checkpoint = Aidp::Execute::Checkpoint.new(@project_dir)
254
260
  checkpoint.latest_checkpoint
255
- rescue
261
+ rescue => e
262
+ log_rescue(e, component: "background_runner", action: "get_job_checkpoint", fallback: nil, job_id: job_id)
256
263
  nil
257
264
  end
258
265