Module: Ocak::Planner

Included in:
PipelineExecutor
Defined in:
lib/ocak/planner.rb

Overview

Batch planning logic extracted from PipelineRunner.

Constant Summary collapse

STEP_PROMPTS =
{
  'implement' => 'Implement GitHub issue #%<issue>s',
  'review' => 'Review the changes for GitHub issue #%<issue>s. Run: git diff main',
  'verify' => 'Review the changes for GitHub issue #%<issue>s. Run: git diff main',
  'security' => 'Security review changes for GitHub issue #%<issue>s. Run: git diff main',
  'document' => 'Add documentation for changes in GitHub issue #%<issue>s',
  'audit' => 'Audit the changed files for issue #%<issue>s. Run: git diff main --name-only',
  'merge' => 'Create a PR, merge it, and close issue #%<issue>s',
  'create_pr' => 'Create a PR, merge it, and close issue #%<issue>s'
}.freeze

Instance Method Summary collapse

Instance Method Details

#build_step_prompt(role, issue_number, review_output) ⇒ Object



19
20
21
22
23
24
25
26
27
# File 'lib/ocak/planner.rb', line 19

def build_step_prompt(role, issue_number, review_output)
  if role == 'fix'
    "Fix these review findings for issue ##{issue_number}:\n\n#{review_output}"
  elsif STEP_PROMPTS.key?(role)
    format(STEP_PROMPTS[role], issue: issue_number)
  else
    "Run #{role} for GitHub issue ##{issue_number}"
  end
end

#parse_planner_output(output, issues, logger) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/ocak/planner.rb', line 46

def parse_planner_output(output, issues, logger)
  json_match = output.match(/\{[\s\S]*"batches"[\s\S]*\}/)
  if json_match
    parsed = JSON.parse(json_match[0])
    parsed['batches']
  else
    logger.warn('Could not parse planner output, falling back to sequential')
    sequential_batches(issues)
  end
rescue JSON::ParserError => e
  logger.warn("JSON parse error from planner: #{e.message}")
  sequential_batches(issues)
end

#plan_batches(issues, logger:, claude:) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/ocak/planner.rb', line 29

def plan_batches(issues, logger:, claude:)
  return sequential_batches(issues) if issues.size <= 1

  issue_json = JSON.generate(issues.map { |i| { number: i['number'], title: i['title'] } })
  result = claude.run_agent(
    'planner',
    "Analyze these issues and output parallelization batches as JSON:\n\n#{issue_json}"
  )

  unless result.success?
    logger.warn('Planner failed, falling back to sequential')
    return sequential_batches(issues)
  end

  parse_planner_output(result.output, issues, logger)
end

#sequential_batches(issues) ⇒ Object



60
61
62
63
64
65
66
# File 'lib/ocak/planner.rb', line 60

def sequential_batches(issues)
  issues.map.with_index do |i, idx|
    issue = i.dup
    issue['complexity'] ||= 'full'
    { 'batch' => idx + 1, 'issues' => [issue] }
  end
end