Module: Ocak::StateManagement
- Included in:
- PipelineExecutor
- Defined in:
- lib/ocak/state_management.rb
Overview
State accumulation and reporting logic extracted from PipelineExecutor. Includers must provide @config, @logger instance variables and pipeline_state, current_branch methods.
Defined Under Namespace
Classes: StepContext
Instance Method Summary collapse
- #accumulate_state(ctx) ⇒ Object
- #check_cost_budget(state, logger) ⇒ Object
- #check_step_failure(ctx) ⇒ Object
- #log_cost_summary(total_cost, logger) ⇒ Object
- #record_step_result(ctx, mutex: nil) ⇒ Object
- #save_report(report, issue_number, success:, failed_phase: nil) ⇒ Object
- #save_step_progress(ctx) ⇒ Object
- #sync(mutex) ⇒ Object
- #update_pipeline_state(role, result, state) ⇒ Object
- #write_step_output(issue_number, idx, agent, output) ⇒ Object
Instance Method Details
#accumulate_state(ctx) ⇒ Object
20 21 22 23 24 25 26 |
# File 'lib/ocak/state_management.rb', line 20 def accumulate_state(ctx) update_pipeline_state(ctx.role, ctx.result, ctx.state) ctx.state[:completed_steps] << ctx.idx ctx.state[:steps_run] += 1 ctx.state[:total_cost] += ctx.result.cost_usd.to_f ctx.state[:step_results][ctx.role] = ctx.result end |
#check_cost_budget(state, logger) ⇒ Object
63 64 65 66 67 68 69 70 |
# File 'lib/ocak/state_management.rb', line 63 def check_cost_budget(state, logger) return nil unless @config.cost_budget && state[:total_cost] > @config.cost_budget cost = format('%.2f', state[:total_cost]) budget = format('%.2f', @config.cost_budget) logger.error("Cost budget exceeded ($#{cost}/$#{budget})") { success: false, phase: 'budget', output: "Cost budget exceeded: $#{cost}" } end |
#check_step_failure(ctx) ⇒ Object
56 57 58 59 60 61 |
# File 'lib/ocak/state_management.rb', line 56 def check_step_failure(ctx) return nil if ctx.result.success? || !%w[implement merge].include?(ctx.role) ctx.logger.error("#{ctx.role} failed") { success: false, phase: ctx.role, output: ctx.result.output } end |
#log_cost_summary(total_cost, logger) ⇒ Object
88 89 90 91 92 93 94 |
# File 'lib/ocak/state_management.rb', line 88 def log_cost_summary(total_cost, logger) return if total_cost.zero? budget = @config.cost_budget budget_str = budget ? " / $#{format('%.2f', budget)} budget" : '' logger.info("Pipeline cost: $#{format('%.4f', total_cost)}#{budget_str}") end |
#record_step_result(ctx, mutex: nil) ⇒ Object
11 12 13 14 15 16 17 18 |
# File 'lib/ocak/state_management.rb', line 11 def record_step_result(ctx, mutex: nil) sync(mutex) { accumulate_state(ctx) } save_step_progress(ctx) write_step_output(ctx.issue_number, ctx.idx, ctx.role, ctx.result.output) post_step_completion_comment(ctx.issue_number, ctx.role, ctx.result) check_step_failure(ctx) || check_cost_budget(ctx.state, ctx.logger) end |
#save_report(report, issue_number, success:, failed_phase: nil) ⇒ Object
96 97 98 99 100 101 102 |
# File 'lib/ocak/state_management.rb', line 96 def save_report(report, issue_number, success:, failed_phase: nil) report.finish(success: success, failed_phase: failed_phase) report.save(issue_number, project_dir: @config.project_dir) rescue StandardError => e @logger&.debug("Report save failed: #{e.}") nil end |
#save_step_progress(ctx) ⇒ Object
36 37 38 39 40 41 |
# File 'lib/ocak/state_management.rb', line 36 def save_step_progress(ctx) pipeline_state.save(ctx.issue_number, completed_steps: ctx.state[:completed_steps], worktree_path: ctx.chdir, branch: current_branch(ctx.chdir, logger: ctx.logger)) end |
#sync(mutex) ⇒ Object
28 29 30 31 32 33 34 |
# File 'lib/ocak/state_management.rb', line 28 def sync(mutex, &) if mutex mutex.synchronize(&) else yield end end |
#update_pipeline_state(role, result, state) ⇒ Object
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/ocak/state_management.rb', line 72 def update_pipeline_state(role, result, state) case role when 'review', 'verify', 'security', 'audit' state[:last_review_output] = result.output if role == 'audit' state[:audit_output] = result.output state[:audit_blocked] = !result.success? || result.output.to_s.match?(/BLOCK|🔴/) end when 'fix' state[:had_fixes] = true state[:last_review_output] = nil when 'implement' state[:last_review_output] = nil end end |
#write_step_output(issue_number, idx, agent, output) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/ocak/state_management.rb', line 43 def write_step_output(issue_number, idx, agent, output) return if output.to_s.empty? return unless issue_number.to_s.match?(/\A\d+\z/) safe_agent = agent.to_s.gsub(/[^a-zA-Z0-9_-]/, '') dir = File.join(@config.project_dir, '.ocak', 'logs', "issue-#{issue_number}") FileUtils.mkdir_p(dir) File.write(File.join(dir, "step-#{idx}-#{safe_agent}.md"), output) rescue StandardError => e @logger&.debug("Step output write failed: #{e.}") nil end |