ocak 0.6.0 → 0.6.1
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 +4 -4
- data/lib/ocak/instance_builders.rb +1 -1
- data/lib/ocak/merge_manager.rb +9 -6
- data/lib/ocak/merge_orchestration.rb +12 -4
- data/lib/ocak/pipeline_executor.rb +1 -0
- data/lib/ocak/planner.rb +21 -8
- data/lib/ocak/step_execution.rb +14 -6
- data/lib/ocak/templates/agents/implementer.md.erb +1 -1
- data/lib/ocak/templates/agents/merger.md.erb +2 -3
- data/lib/ocak.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7363b1ed65b047ead5c507fdc210dea94400edca689dff88200b6cbb2a666a5d
|
|
4
|
+
data.tar.gz: 39d9e7021f666cccc84643c7d66c00db9080adaab63d9163069ca963a030963c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53330c5ca283948f69a63aecc4c4b14866dd6c6fc9f8c2b5f46a2fb9f73572b92c81ff43df6c2c2fdd05ecc35135d5cd236bab8cd156527477e7da55995d12c7
|
|
7
|
+
data.tar.gz: dd9f956429a2b37f1aa6dec984795bdd752d28f80ea282bbc2709377e5777f11cad181dc9c64b97505037a544e053510b070f744860b1287788da5d6ebe88693
|
|
@@ -22,7 +22,7 @@ module Ocak
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def build_merge_manager(logger:, issues:)
|
|
25
|
-
if issues.is_a?(LocalIssueFetcher) && !gh_available?
|
|
25
|
+
if issues.is_a?(LocalIssueFetcher) && !@config.multi_repo? && !gh_available?
|
|
26
26
|
LocalMergeManager.new(config: @config, logger: logger, issues: issues)
|
|
27
27
|
else
|
|
28
28
|
MergeManager.new(config: @config, claude: build_claude(logger), logger: logger,
|
data/lib/ocak/merge_manager.rb
CHANGED
|
@@ -73,12 +73,15 @@ module Ocak
|
|
|
73
73
|
private
|
|
74
74
|
|
|
75
75
|
def merger_prompt(issue_number, worktree)
|
|
76
|
-
if worktree.target_repo
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
base = if worktree.target_repo
|
|
77
|
+
"Create a PR and merge it for issue ##{issue_number}. Branch: #{worktree.branch}. " \
|
|
78
|
+
'Do NOT close any issues (the issue lives in a different repository).'
|
|
79
|
+
else
|
|
80
|
+
"Create a PR, merge it, and close issue ##{issue_number}. Branch: #{worktree.branch}"
|
|
81
|
+
end
|
|
82
|
+
issue_data = @issues.view(issue_number)
|
|
83
|
+
base += "\n\n<issue_data>\nTitle: #{issue_data['title']}\n\n#{issue_data['body']}\n</issue_data>" if issue_data
|
|
84
|
+
base
|
|
82
85
|
end
|
|
83
86
|
|
|
84
87
|
def log_and_nil(message)
|
|
@@ -38,6 +38,7 @@ module Ocak
|
|
|
38
38
|
else
|
|
39
39
|
"Create a PR, merge it, and close issue ##{issue_number}"
|
|
40
40
|
end
|
|
41
|
+
prompt += merger_issue_context(issue_number, issues)
|
|
41
42
|
claude.run_agent('merger', prompt, chdir: target_dir)
|
|
42
43
|
end
|
|
43
44
|
@state_machine.mark_completed(issue_number)
|
|
@@ -45,10 +46,10 @@ module Ocak
|
|
|
45
46
|
end
|
|
46
47
|
end
|
|
47
48
|
|
|
48
|
-
def handle_single_manual_review(issue_number, logger:, claude:, issues: nil, chdir: @config.project_dir)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
def handle_single_manual_review(issue_number, logger:, claude:, issues: nil, chdir: @config.project_dir)
|
|
50
|
+
prompt = "Create a PR for issue ##{issue_number} but do NOT merge it and do NOT close the issue"
|
|
51
|
+
prompt += merger_issue_context(issue_number, issues)
|
|
52
|
+
claude.run_agent('merger', prompt, chdir: chdir)
|
|
52
53
|
@state_machine.mark_for_review(issue_number)
|
|
53
54
|
logger.info("Issue ##{issue_number} PR created (manual review mode)")
|
|
54
55
|
end
|
|
@@ -96,6 +97,13 @@ module Ocak
|
|
|
96
97
|
logger.info("Posted audit comment on PR ##{pr_number}")
|
|
97
98
|
end
|
|
98
99
|
|
|
100
|
+
def merger_issue_context(issue_number, issues)
|
|
101
|
+
issue_data = issues&.view(issue_number)
|
|
102
|
+
return '' unless issue_data
|
|
103
|
+
|
|
104
|
+
"\n\n<issue_data>\nTitle: #{issue_data['title']}\n\n#{issue_data['body']}\n</issue_data>"
|
|
105
|
+
end
|
|
106
|
+
|
|
99
107
|
def pipeline_has_merge_step?
|
|
100
108
|
@config.steps.any? { |s| s[:role].to_s == 'merge' || s['role'].to_s == 'merge' }
|
|
101
109
|
end
|
|
@@ -44,6 +44,7 @@ module Ocak
|
|
|
44
44
|
|
|
45
45
|
report = RunReport.new(complexity: complexity)
|
|
46
46
|
state = build_initial_state(complexity, report)
|
|
47
|
+
state[:issue_data] = @issues&.view(issue_number)
|
|
47
48
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
48
49
|
post_pipeline_start_comment(issue_number, state) if post_start_comment
|
|
49
50
|
|
data/lib/ocak/planner.rb
CHANGED
|
@@ -14,14 +14,27 @@ module Ocak
|
|
|
14
14
|
'merge' => 'Create a PR, merge it, and close issue #%<issue>s'
|
|
15
15
|
}.freeze
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
ISSUE_CONTEXT_ROLES = %w[implement document merge].freeze
|
|
18
|
+
|
|
19
|
+
def build_step_prompt(role, issue_number, review_output, issue_data: nil)
|
|
20
|
+
prompt = if role == 'fix'
|
|
21
|
+
"Fix these review findings for issue ##{issue_number}:\n\n" \
|
|
22
|
+
"<review_output>\n#{review_output}\n</review_output>"
|
|
23
|
+
elsif STEP_PROMPTS.key?(role)
|
|
24
|
+
format(STEP_PROMPTS[role], issue: issue_number)
|
|
25
|
+
else
|
|
26
|
+
"Run #{role} for GitHub issue ##{issue_number}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
prompt += format_issue_context(issue_data) if issue_data && ISSUE_CONTEXT_ROLES.include?(role)
|
|
30
|
+
prompt
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def format_issue_context(issue_data)
|
|
34
|
+
parts = []
|
|
35
|
+
parts << "Title: #{issue_data['title']}" if issue_data['title']
|
|
36
|
+
parts << issue_data['body'] if issue_data['body']
|
|
37
|
+
"\n\n<issue_data>\n#{parts.join("\n\n")}\n</issue_data>"
|
|
25
38
|
end
|
|
26
39
|
|
|
27
40
|
def plan_batches(issues, logger:, claude:)
|
data/lib/ocak/step_execution.rb
CHANGED
|
@@ -18,7 +18,8 @@ module Ocak
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
result = execute_step(step, issue_number, state[:last_review_output], logger: logger, claude: claude,
|
|
21
|
-
chdir: chdir
|
|
21
|
+
chdir: chdir,
|
|
22
|
+
issue_data: state[:issue_data])
|
|
22
23
|
state[:report].record_step(index: idx, agent: agent, role: role, status: 'completed', result: result)
|
|
23
24
|
ctx = StateManagement::StepContext.new(issue_number, idx, role, result, state, logger, chdir)
|
|
24
25
|
record_step_result(ctx, mutex: mutex)
|
|
@@ -37,12 +38,12 @@ module Ocak
|
|
|
37
38
|
state[:steps_skipped] += 1
|
|
38
39
|
end
|
|
39
40
|
|
|
40
|
-
def execute_step(step, issue_number, review_output, logger:, claude:, chdir:)
|
|
41
|
+
def execute_step(step, issue_number, review_output, logger:, claude:, chdir:, issue_data: nil) # rubocop:disable Metrics/ParameterLists
|
|
41
42
|
agent = step[:agent].to_s
|
|
42
43
|
role = step[:role].to_s
|
|
43
44
|
logger.info("--- Phase: #{role} (#{agent}) ---")
|
|
44
45
|
post_step_comment(issue_number, "🔄 **Phase: #{role}** (#{agent})")
|
|
45
|
-
prompt = build_step_prompt(role, issue_number, review_output)
|
|
46
|
+
prompt = build_step_prompt(role, issue_number, review_output, issue_data: issue_data)
|
|
46
47
|
opts = { chdir: chdir }
|
|
47
48
|
opts[:model] = step[:model].to_s if step[:model]
|
|
48
49
|
claude.run_agent(agent.tr('_', '-'), prompt, **opts)
|
|
@@ -51,9 +52,8 @@ module Ocak
|
|
|
51
52
|
def skip_reason(step, state)
|
|
52
53
|
condition = step[:condition]
|
|
53
54
|
|
|
54
|
-
return
|
|
55
|
-
return 'audit
|
|
56
|
-
return 'manual review mode' if step[:role].to_s == 'merge' && @config.manual_review
|
|
55
|
+
return merge_skip_reason(state) if step[:role].to_s == 'merge'
|
|
56
|
+
return 'audit mode not enabled' if step[:role].to_s == 'audit' && !@config.audit_mode
|
|
57
57
|
return 'fast-track issue (simple complexity)' if step[:complexity] == 'full' && state[:complexity] == 'simple'
|
|
58
58
|
if condition == 'has_findings' && !state[:last_review_output]&.include?('🔴')
|
|
59
59
|
return 'no blocking findings from review'
|
|
@@ -62,5 +62,13 @@ module Ocak
|
|
|
62
62
|
|
|
63
63
|
nil
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
def merge_skip_reason(state)
|
|
67
|
+
return 'merge handled by MergeManager' if @skip_merge
|
|
68
|
+
return 'audit found blocking issues' if @config.audit_mode && state[:audit_blocked]
|
|
69
|
+
return 'manual review mode' if @config.manual_review
|
|
70
|
+
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
65
73
|
end
|
|
66
74
|
end
|
|
@@ -12,7 +12,7 @@ You implement GitHub issues. The issue is your single source of truth — everyt
|
|
|
12
12
|
## Before You Start
|
|
13
13
|
|
|
14
14
|
1. Check branch state: `git log main..HEAD --oneline` and `git diff main --stat` — work may already be partially done
|
|
15
|
-
2. Read the issue
|
|
15
|
+
2. Read the issue data provided in your task prompt (in `<issue_data>` tags) — this is your source of truth for the issue title and body
|
|
16
16
|
3. Read `CLAUDE.md` for project conventions
|
|
17
17
|
4. Read every file listed in the issue's "Relevant files" and "Patterns to Follow" sections
|
|
18
18
|
5. When writing new code, find the closest existing example first (neighboring file in the same directory/namespace) and mirror its structure exactly
|
|
@@ -39,10 +39,9 @@ If anything fails after conflict resolution, STOP and report the failures. Do no
|
|
|
39
39
|
|
|
40
40
|
### 2. Create Pull Request
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
# Get the issue title and body for context
|
|
44
|
-
gh issue view <number> --json title,body
|
|
42
|
+
Use the issue data provided in your task prompt (in `<issue_data>` tags) for the issue title and body context. Do NOT run `gh issue view` — the issue may live in a different repository.
|
|
45
43
|
|
|
44
|
+
```bash
|
|
46
45
|
# Get full diff for PR description
|
|
47
46
|
git diff main --stat
|
|
48
47
|
git log main..HEAD --oneline
|
data/lib/ocak.rb
CHANGED