Class: Ocak::RunReport

Inherits:
Object
  • Object
show all
Defined in:
lib/ocak/run_report.rb

Constant Summary collapse

REPORTS_DIR =
'.ocak/reports'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(complexity: 'full') ⇒ RunReport

Returns a new instance of RunReport.



13
14
15
16
17
18
19
20
# File 'lib/ocak/run_report.rb', line 13

def initialize(complexity: 'full')
  @complexity = complexity
  @steps = []
  @started_at = Time.now.utc.iso8601
  @finished_at = nil
  @success = nil
  @failed_phase = nil
end

Instance Attribute Details

#complexityObject (readonly)

Returns the value of attribute complexity.



11
12
13
# File 'lib/ocak/run_report.rb', line 11

def complexity
  @complexity
end

#failed_phaseObject (readonly)

Returns the value of attribute failed_phase.



11
12
13
# File 'lib/ocak/run_report.rb', line 11

def failed_phase
  @failed_phase
end

#finished_atObject (readonly)

Returns the value of attribute finished_at.



11
12
13
# File 'lib/ocak/run_report.rb', line 11

def finished_at
  @finished_at
end

#started_atObject (readonly)

Returns the value of attribute started_at.



11
12
13
# File 'lib/ocak/run_report.rb', line 11

def started_at
  @started_at
end

#stepsObject (readonly)

Returns the value of attribute steps.



11
12
13
# File 'lib/ocak/run_report.rb', line 11

def steps
  @steps
end

#successObject (readonly)

Returns the value of attribute success.



11
12
13
# File 'lib/ocak/run_report.rb', line 11

def success
  @success
end

Class Method Details

.load_all(project_dir:) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
# File 'lib/ocak/run_report.rb', line 75

def self.load_all(project_dir:)
  dir = File.join(project_dir, REPORTS_DIR)
  return [] unless Dir.exist?(dir)

  Dir.glob(File.join(dir, 'issue-*.json')).filter_map do |path|
    JSON.parse(File.read(path), symbolize_names: true)
  rescue JSON::ParserError, SystemCallError => e
    warn("Skipping report #{File.basename(path)}: #{e.message}")
    nil
  end
end

Instance Method Details

#finish(success:, failed_phase: nil) ⇒ Object



37
38
39
40
41
# File 'lib/ocak/run_report.rb', line 37

def finish(success:, failed_phase: nil)
  @finished_at = Time.now.utc.iso8601
  @success = success
  @failed_phase = failed_phase
end

#record_step(index:, agent:, role:, status:, result: nil, skip_reason: nil) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/ocak/run_report.rb', line 22

def record_step(index:, agent:, role:, status:, result: nil, skip_reason: nil)
  entry = { index: index, agent: agent, role: role, status: status }

  if status == 'completed' && result
    entry[:duration_s] = (result.duration_ms.to_f / 1000).round
    entry[:cost_usd] = result.cost_usd.to_f
    entry[:num_turns] = result.num_turns.to_i
    entry[:files_edited] = result.files_edited || []
  elsif status == 'skipped' && skip_reason
    entry[:skip_reason] = skip_reason
  end

  @steps << entry
end

#save(issue_number, project_dir:) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/ocak/run_report.rb', line 43

def save(issue_number, project_dir:)
  return nil unless issue_number.to_s.match?(/\A\d+\z/)

  dir = File.join(project_dir, REPORTS_DIR)
  FileUtils.mkdir_p(dir)

  timestamp = Time.now.strftime('%Y%m%d%H%M%S')
  path = File.join(dir, "issue-#{issue_number}-#{timestamp}.json")
  File.write(path, JSON.pretty_generate(to_h(issue_number)))
  path
rescue SystemCallError => e
  warn("Failed to save report to #{path}: #{e.message}")
  nil
end

#to_h(issue_number) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/ocak/run_report.rb', line 58

def to_h(issue_number)
  total_duration = ((Time.parse(@finished_at) - Time.parse(@started_at)).round if @started_at && @finished_at)
  total_cost = @steps.sum { |s| s[:cost_usd].to_f }

  {
    issue_number: issue_number,
    complexity: @complexity,
    success: @success,
    started_at: @started_at,
    finished_at: @finished_at,
    total_duration_s: total_duration,
    total_cost_usd: total_cost.round(4),
    steps: @steps,
    failed_phase: @failed_phase
  }
end