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',
  'merge' => '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



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

def build_step_prompt(role, issue_number, review_output)
  if role == 'fix'
    "Fix these review findings for issue ##{issue_number}:\n\n<review_output>\n#{review_output}\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



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

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])
    batches = parsed['batches']
    return sequential_batches(issues) unless batches.is_a?(Array)

    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



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

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_data>\n#{issue_json}\n</issue_data>"
  )

  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



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

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