aidp 0.39.8 → 0.39.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5df865d45987ecd333e79480c0329acc64ef04ffec6c8810a0e43fd94b5b9236
4
- data.tar.gz: '08b02ed42782161769e3ec9bc81e7d747b97d735cbcbb3031af01df6a94b3f5a'
3
+ metadata.gz: 48e9a4d5c62e99992a0b61d3547b20b2a7598c13dbc6ba8b23a4f8126cd74c35
4
+ data.tar.gz: 9427e78702251d548a938d5b93f466b9514686128162c4be5bb52eb8375013a0
5
5
  SHA512:
6
- metadata.gz: 118d2e50c63cd1bc536d3c0acd8dd01b626f174328eed7e0614e3ba13cb29315472cdc4caa0a6dda6f8672498b0bbae404e00cb11fcef40d36643b195c8b7a72
7
- data.tar.gz: a0667e973b6ed0a1eaf704a7ee5bb019870d4ad2163c00ba40c2b5cd3009add2dbf9c99172b4678e0588dfd7e2a821025a1bcbaf0311b99cf0282e33b4001600
6
+ metadata.gz: eb8f0d861f8b85f8df71034a2e6cbef875be34dcf28eacec009aaa65c8589527b904fa060598b43c9854e63c9b354ca0299bb70b96eb5da80e36c19b6ea0a033
7
+ data.tar.gz: a46a2734b9094b966c242a284b1673cd956df4281294557b813925d19f8204bdf470ad93d4a1c01e27a2846de2b6544258d062f0f36dacd29607827a7bc3049c
@@ -13,6 +13,8 @@ module Aidp
13
13
  # Manages background execution of work loops
14
14
  # Runs harness in daemon process and tracks job metadata
15
15
  class BackgroundRunner
16
+ StartError = Class.new(StandardError)
17
+
16
18
  include Aidp::MessageDisplay
17
19
  include Aidp::RescueLogging
18
20
  include Aidp::SafeDirectory
@@ -34,9 +36,11 @@ module Aidp
34
36
  # Start a background job
35
37
  # Returns job_id
36
38
  def start(mode, options = {})
39
+ ensure_jobs_directory
37
40
  job_id = generate_job_id
38
- log_file = File.join(@jobs_dir, job_id, "output.log")
39
- pid_file = File.join(@jobs_dir, job_id, "job.pid")
41
+ log_file = job_file_path(job_id, "output.log")
42
+ pid_file = job_file_path(job_id, "job.pid")
43
+ raise_start_error unless log_file && pid_file
40
44
 
41
45
  # Create job directory
42
46
  FileUtils.mkdir_p(File.dirname(log_file))
@@ -109,6 +113,9 @@ module Aidp
109
113
  metadata = load_job_metadata(job_id)
110
114
  return nil unless metadata
111
115
 
116
+ log_file = job_log_path(job_id)
117
+ return nil unless log_file
118
+
112
119
  # Check if process is still running
113
120
  pid = metadata[:pid]
114
121
  running = pid && process_running?(pid)
@@ -125,7 +132,7 @@ module Aidp
125
132
  started_at: metadata[:started_at],
126
133
  completed_at: metadata[:completed_at],
127
134
  checkpoint: checkpoint,
128
- log_file: File.join(@jobs_dir, job_id, "output.log")
135
+ log_file: log_file
129
136
  }
130
137
  end
131
138
 
@@ -169,12 +176,12 @@ module Aidp
169
176
 
170
177
  # Get job logs
171
178
  def job_logs(job_id, options = {})
172
- log_file = File.join(@jobs_dir, job_id, "output.log")
179
+ log_file = job_log_path(job_id)
180
+ return nil unless log_file
173
181
  return nil unless File.exist?(log_file)
174
182
 
175
183
  if options[:tail]
176
- lines = options[:lines] || 50
177
- `tail -n #{lines} #{log_file}`
184
+ tail_job_logs(log_file, options[:lines])
178
185
  else
179
186
  File.read(log_file)
180
187
  end
@@ -182,7 +189,8 @@ module Aidp
182
189
 
183
190
  # Follow job logs in real-time
184
191
  def follow_job_logs(job_id)
185
- log_file = File.join(@jobs_dir, job_id, "output.log")
192
+ log_file = job_log_path(job_id)
193
+ return unless log_file
186
194
  return unless File.exist?(log_file)
187
195
 
188
196
  # Use tail -f to follow logs
@@ -195,6 +203,10 @@ module Aidp
195
203
  @jobs_dir = safe_mkdir_p(@jobs_dir, component_name: "BackgroundRunner")
196
204
  end
197
205
 
206
+ def raise_start_error
207
+ raise StartError, "Unable to create or access background job directory: #{@jobs_dir}"
208
+ end
209
+
198
210
  def generate_job_id
199
211
  timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
200
212
  random = SecureRandom.hex(4)
@@ -202,7 +214,8 @@ module Aidp
202
214
  end
203
215
 
204
216
  def save_job_metadata(job_id, pid, mode, options)
205
- metadata_file = File.join(@jobs_dir, job_id, "metadata.yml")
217
+ metadata_file = job_metadata_path(job_id)
218
+ return unless metadata_file
206
219
 
207
220
  metadata = {
208
221
  job_id: job_id,
@@ -217,7 +230,8 @@ module Aidp
217
230
  end
218
231
 
219
232
  def load_job_metadata(job_id)
220
- metadata_file = File.join(@jobs_dir, job_id, "metadata.yml")
233
+ metadata_file = job_metadata_path(job_id)
234
+ return nil unless metadata_file
221
235
  return nil unless File.exist?(metadata_file)
222
236
 
223
237
  # Return raw metadata with times as ISO8601 strings to avoid unsafe class loading
@@ -231,7 +245,8 @@ module Aidp
231
245
  return unless metadata
232
246
 
233
247
  metadata.merge!(updates)
234
- metadata_file = File.join(@jobs_dir, job_id, "metadata.yml")
248
+ metadata_file = job_metadata_path(job_id)
249
+ return unless metadata_file
235
250
  File.write(metadata_file, metadata.to_yaml)
236
251
  end
237
252
 
@@ -298,6 +313,57 @@ module Aidp
298
313
  metadata[:status] || "completed"
299
314
  end
300
315
  end
316
+
317
+ def job_log_path(job_id)
318
+ job_file_path(job_id, "output.log")
319
+ end
320
+
321
+ def job_metadata_path(job_id)
322
+ job_file_path(job_id, "metadata.yml")
323
+ end
324
+
325
+ def job_file_path(job_id, file_name)
326
+ return unless Dir.exist?(@jobs_dir)
327
+
328
+ jobs_dir = File.realpath(@jobs_dir)
329
+ resolved_job_dir = resolved_job_dir_path(jobs_dir, job_id)
330
+ return unless resolved_job_dir
331
+ return unless resolved_job_dir.start_with?("#{jobs_dir}/")
332
+
333
+ resolved_job_file_path(resolved_job_dir, file_name)
334
+ end
335
+
336
+ def tail_job_logs(log_file, requested_lines)
337
+ lines = Integer(requested_lines || 50, exception: false)
338
+ lines = 50 unless lines&.positive?
339
+
340
+ IO.popen(["tail", "-n", lines.to_s, log_file], &:read)
341
+ end
342
+
343
+ def resolved_job_dir_path(jobs_dir, job_id)
344
+ job_dir = File.join(@jobs_dir, job_id.to_s)
345
+
346
+ if File.exist?(job_dir)
347
+ File.realpath(job_dir)
348
+ else
349
+ File.join(jobs_dir, job_id.to_s)
350
+ end
351
+ rescue Errno::ENOENT, Errno::EACCES
352
+ nil
353
+ end
354
+
355
+ def resolved_job_file_path(job_dir, file_name)
356
+ candidate = File.join(job_dir, file_name)
357
+ return candidate unless File.exist?(candidate) || File.symlink?(candidate)
358
+ return if File.symlink?(candidate)
359
+
360
+ resolved_file = File.realpath(candidate)
361
+ return unless resolved_file.start_with?("#{job_dir}/")
362
+
363
+ resolved_file
364
+ rescue Errno::ENOENT, Errno::EACCES
365
+ nil
366
+ end
301
367
  end
302
368
  end
303
369
  end
data/lib/aidp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aidp
4
- VERSION = "0.39.8"
4
+ VERSION = "0.39.9"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aidp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.39.8
4
+ version: 0.39.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan