ocak 0.3.0 → 0.5.0
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/README.md +27 -0
- data/lib/ocak/agent_generator.rb +4 -3
- data/lib/ocak/batch_processing.rb +102 -0
- data/lib/ocak/claude_runner.rb +12 -8
- data/lib/ocak/cli.rb +13 -0
- data/lib/ocak/command_runner.rb +39 -0
- data/lib/ocak/commands/hiz.rb +73 -140
- data/lib/ocak/commands/init.rb +12 -6
- data/lib/ocak/commands/issue/close.rb +37 -0
- data/lib/ocak/commands/issue/create.rb +59 -0
- data/lib/ocak/commands/issue/edit.rb +31 -0
- data/lib/ocak/commands/issue/list.rb +43 -0
- data/lib/ocak/commands/issue/view.rb +58 -0
- data/lib/ocak/commands/resume.rb +7 -6
- data/lib/ocak/commands/status.rb +20 -0
- data/lib/ocak/config.rb +28 -5
- data/lib/ocak/failure_reporting.rb +17 -0
- data/lib/ocak/git_utils.rb +25 -9
- data/lib/ocak/instance_builders.rb +50 -0
- data/lib/ocak/issue_backend.rb +31 -0
- data/lib/ocak/issue_fetcher.rb +40 -47
- data/lib/ocak/local_issue_fetcher.rb +165 -0
- data/lib/ocak/local_merge_manager.rb +104 -0
- data/lib/ocak/logger.rb +5 -2
- data/lib/ocak/merge_manager.rb +36 -31
- data/lib/ocak/merge_orchestration.rb +8 -2
- data/lib/ocak/parallel_execution.rb +36 -0
- data/lib/ocak/pipeline_executor.rb +51 -169
- data/lib/ocak/pipeline_runner.rb +20 -167
- data/lib/ocak/pipeline_state.rb +11 -2
- data/lib/ocak/planner.rb +2 -3
- data/lib/ocak/process_runner.rb +4 -2
- data/lib/ocak/project_key.rb +38 -0
- data/lib/ocak/reready_processor.rb +15 -13
- data/lib/ocak/run_report.rb +7 -2
- data/lib/ocak/shutdown_handling.rb +67 -0
- data/lib/ocak/state_management.rb +104 -0
- data/lib/ocak/step_comments.rb +12 -7
- data/lib/ocak/step_execution.rb +66 -0
- data/lib/ocak/stream_parser.rb +7 -2
- data/lib/ocak/templates/agents/auditor.md.erb +38 -9
- data/lib/ocak/templates/agents/implementer.md.erb +32 -8
- data/lib/ocak/templates/agents/merger.md.erb +12 -5
- data/lib/ocak/templates/agents/pipeline.md.erb +4 -0
- data/lib/ocak/templates/agents/reviewer.md.erb +2 -2
- data/lib/ocak/templates/agents/security_reviewer.md.erb +11 -0
- data/lib/ocak/templates/ocak.yml.erb +16 -0
- data/lib/ocak/verification.rb +43 -3
- data/lib/ocak/worktree_manager.rb +7 -3
- data/lib/ocak.rb +1 -1
- metadata +18 -15
|
@@ -12,6 +12,7 @@ stack:
|
|
|
12
12
|
<%- end -%>
|
|
13
13
|
<%- if lint_command -%>
|
|
14
14
|
lint_command: "<%= lint_command %>"
|
|
15
|
+
# lint_check_command: "<%= lint_command %>" # Explicit check-only lint command (no auto-fix flags)
|
|
15
16
|
<%- end -%>
|
|
16
17
|
<%- if setup_command -%>
|
|
17
18
|
setup_command: "<%= setup_command %>"
|
|
@@ -26,6 +27,12 @@ stack:
|
|
|
26
27
|
<%- end -%>
|
|
27
28
|
<%- end -%>
|
|
28
29
|
|
|
30
|
+
# Issue backend — 'github' (default) or 'local'
|
|
31
|
+
# Local mode stores issues as files in .ocak/issues/ (no gh CLI needed for issues).
|
|
32
|
+
# Auto-detected: if .ocak/issues/ contains .md files, local mode is used automatically.
|
|
33
|
+
# issues:
|
|
34
|
+
# backend: local
|
|
35
|
+
|
|
29
36
|
# Pipeline settings
|
|
30
37
|
pipeline:
|
|
31
38
|
max_parallel: 5
|
|
@@ -56,29 +63,38 @@ labels:
|
|
|
56
63
|
steps:
|
|
57
64
|
- agent: implementer
|
|
58
65
|
role: implement
|
|
66
|
+
model: sonnet
|
|
59
67
|
- agent: reviewer
|
|
60
68
|
role: review
|
|
69
|
+
model: opus
|
|
61
70
|
- agent: implementer
|
|
62
71
|
role: fix
|
|
63
72
|
condition: has_findings
|
|
73
|
+
model: sonnet
|
|
64
74
|
- agent: reviewer
|
|
65
75
|
role: verify
|
|
66
76
|
condition: had_fixes
|
|
77
|
+
model: opus
|
|
67
78
|
- agent: security_reviewer
|
|
68
79
|
role: security
|
|
80
|
+
model: sonnet
|
|
69
81
|
complexity: full # skipped for simple issues and --fast mode
|
|
70
82
|
- agent: implementer
|
|
71
83
|
role: fix
|
|
72
84
|
condition: has_findings
|
|
73
85
|
complexity: full # skipped for simple issues
|
|
86
|
+
model: sonnet
|
|
74
87
|
- agent: documenter
|
|
75
88
|
role: document
|
|
76
89
|
complexity: full # skipped for simple issues
|
|
90
|
+
model: sonnet
|
|
77
91
|
- agent: auditor
|
|
78
92
|
role: audit
|
|
79
93
|
complexity: full # skipped for simple issues
|
|
94
|
+
model: sonnet
|
|
80
95
|
- agent: merger
|
|
81
96
|
role: merge
|
|
97
|
+
model: sonnet
|
|
82
98
|
|
|
83
99
|
# Agent file paths — override to use custom agents
|
|
84
100
|
agents:
|
data/lib/ocak/verification.rb
CHANGED
|
@@ -6,11 +6,40 @@ require 'shellwords'
|
|
|
6
6
|
module Ocak
|
|
7
7
|
# Final verification checks (tests + lint) extracted from PipelineRunner.
|
|
8
8
|
module Verification
|
|
9
|
+
def run_verification_with_retry(logger:, claude:, chdir:, model: nil, &post_comment)
|
|
10
|
+
return nil unless @config.test_command || @config.lint_check_command
|
|
11
|
+
|
|
12
|
+
logger.info('--- Final verification ---')
|
|
13
|
+
post_comment&.call("\u{1F504} **Phase: final-verify** (verification)")
|
|
14
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
15
|
+
result = run_final_checks(logger, chdir: chdir)
|
|
16
|
+
|
|
17
|
+
unless result[:success]
|
|
18
|
+
logger.warn('Final checks failed, attempting fix...')
|
|
19
|
+
post_comment&.call("\u{26A0}\u{FE0F} **Final verification failed** \u2014 attempting auto-fix...")
|
|
20
|
+
fix_prompt = "Fix these test/lint failures:\n\n" \
|
|
21
|
+
"<verification_output>\n#{result[:output]}\n</verification_output>"
|
|
22
|
+
fix_opts = { chdir: chdir }
|
|
23
|
+
fix_opts[:model] = model if model
|
|
24
|
+
claude.run_agent('implementer', fix_prompt, **fix_opts)
|
|
25
|
+
result = run_final_checks(logger, chdir: chdir)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time).round
|
|
29
|
+
if result[:success]
|
|
30
|
+
post_comment&.call("\u{2705} **Phase: final-verify** completed \u2014 #{duration}s")
|
|
31
|
+
nil
|
|
32
|
+
else
|
|
33
|
+
post_comment&.call("\u{274C} **Phase: final-verify** failed \u2014 #{duration}s")
|
|
34
|
+
{ success: false, phase: 'final-verify', output: result[:output] }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
9
38
|
def run_final_checks(logger, chdir:)
|
|
10
39
|
failures = []
|
|
11
40
|
output_parts = []
|
|
12
41
|
|
|
13
|
-
check_tests(failures, output_parts, chdir: chdir)
|
|
42
|
+
check_tests(failures, output_parts, logger, chdir: chdir)
|
|
14
43
|
check_lint(failures, output_parts, logger, chdir: chdir)
|
|
15
44
|
|
|
16
45
|
if failures.empty?
|
|
@@ -23,7 +52,12 @@ module Ocak
|
|
|
23
52
|
end
|
|
24
53
|
|
|
25
54
|
def run_scoped_lint(logger, chdir:)
|
|
26
|
-
changed_stdout, = Open3.capture3('git', 'diff', '--name-only', 'main', chdir: chdir)
|
|
55
|
+
changed_stdout, stderr, status = Open3.capture3('git', 'diff', '--name-only', 'main', chdir: chdir)
|
|
56
|
+
unless status.success?
|
|
57
|
+
logger&.warn("Scoped lint skipped — git diff failed: #{stderr[0..200]}")
|
|
58
|
+
return nil
|
|
59
|
+
end
|
|
60
|
+
|
|
27
61
|
changed_files = changed_stdout.lines.map(&:strip).reject(&:empty?)
|
|
28
62
|
|
|
29
63
|
extensions = lint_extensions_for(@config.language)
|
|
@@ -42,6 +76,9 @@ module Ocak
|
|
|
42
76
|
return nil if status.success?
|
|
43
77
|
|
|
44
78
|
"=== #{@config.lint_check_command} (#{lintable.size} files) ===\n#{stdout}\n#{stderr}"
|
|
79
|
+
rescue ArgumentError => e
|
|
80
|
+
logger&.warn("Invalid shell command in config: #{@config.lint_check_command.inspect} (#{e.message})")
|
|
81
|
+
"=== #{@config.lint_check_command} ===\nArgumentError: #{e.message}"
|
|
45
82
|
end
|
|
46
83
|
|
|
47
84
|
def lint_extensions_for(language)
|
|
@@ -60,7 +97,7 @@ module Ocak
|
|
|
60
97
|
|
|
61
98
|
private
|
|
62
99
|
|
|
63
|
-
def check_tests(failures, output_parts, chdir:)
|
|
100
|
+
def check_tests(failures, output_parts, logger, chdir:)
|
|
64
101
|
return unless @config.test_command
|
|
65
102
|
|
|
66
103
|
stdout, stderr, status = Open3.capture3(*Shellwords.shellsplit(@config.test_command), chdir: chdir)
|
|
@@ -68,6 +105,9 @@ module Ocak
|
|
|
68
105
|
|
|
69
106
|
failures << @config.test_command
|
|
70
107
|
output_parts << "=== #{@config.test_command} ===\n#{stdout}\n#{stderr}"
|
|
108
|
+
rescue ArgumentError => e
|
|
109
|
+
logger&.warn("Invalid shell command in config: #{@config.test_command.inspect} (#{e.message})")
|
|
110
|
+
failures << @config.test_command
|
|
71
111
|
end
|
|
72
112
|
|
|
73
113
|
def check_lint(failures, output_parts, logger, chdir:)
|
|
@@ -7,14 +7,17 @@ require 'shellwords'
|
|
|
7
7
|
|
|
8
8
|
module Ocak
|
|
9
9
|
class WorktreeManager
|
|
10
|
-
def initialize(config:)
|
|
10
|
+
def initialize(config:, logger: nil)
|
|
11
11
|
@config = config
|
|
12
|
+
@logger = logger
|
|
12
13
|
@worktree_base = File.join(config.project_dir, config.worktree_dir)
|
|
13
14
|
@mutex = Mutex.new
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def create(issue_number, setup_command: nil)
|
|
17
18
|
@mutex.synchronize do
|
|
19
|
+
raise ArgumentError, "Invalid issue number: #{issue_number}" unless issue_number.to_s.match?(/\A\d+\z/)
|
|
20
|
+
|
|
18
21
|
FileUtils.mkdir_p(@worktree_base)
|
|
19
22
|
|
|
20
23
|
branch = "auto/issue-#{issue_number}-#{SecureRandom.hex(4)}"
|
|
@@ -56,7 +59,8 @@ module Ocak
|
|
|
56
59
|
begin
|
|
57
60
|
git('worktree', 'remove', '--force', wt[:path])
|
|
58
61
|
removed << wt[:path]
|
|
59
|
-
rescue StandardError
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
@logger&.warn("Failed to remove worktree #{wt[:path]}: #{e.message}")
|
|
60
64
|
next # skip failed removal so one bad worktree doesn't abort cleanup of others
|
|
61
65
|
end
|
|
62
66
|
end
|
|
@@ -64,7 +68,7 @@ module Ocak
|
|
|
64
68
|
removed
|
|
65
69
|
end
|
|
66
70
|
|
|
67
|
-
Worktree = Struct.new(:path, :branch, :issue_number)
|
|
71
|
+
Worktree = Struct.new(:path, :branch, :issue_number, keyword_init: true) # rubocop:disable Style/RedundantStructKeywordInit
|
|
68
72
|
|
|
69
73
|
class WorktreeError < StandardError; end
|
|
70
74
|
|
data/lib/ocak.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ocak
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Clay Harmon
|
|
@@ -23,20 +23,6 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.0'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: logger
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - "~>"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '1.6'
|
|
33
|
-
type: :runtime
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '1.6'
|
|
40
26
|
description: Ocak sets up and runs a multi-agent pipeline that autonomously implements
|
|
41
27
|
GitHub issues using Claude Code. Design issues, run the pipeline, ship code. Let
|
|
42
28
|
'em cook.
|
|
@@ -50,34 +36,51 @@ files:
|
|
|
50
36
|
- bin/ocak
|
|
51
37
|
- lib/ocak.rb
|
|
52
38
|
- lib/ocak/agent_generator.rb
|
|
39
|
+
- lib/ocak/batch_processing.rb
|
|
53
40
|
- lib/ocak/claude_runner.rb
|
|
54
41
|
- lib/ocak/cli.rb
|
|
42
|
+
- lib/ocak/command_runner.rb
|
|
55
43
|
- lib/ocak/commands/audit.rb
|
|
56
44
|
- lib/ocak/commands/clean.rb
|
|
57
45
|
- lib/ocak/commands/debt.rb
|
|
58
46
|
- lib/ocak/commands/design.rb
|
|
59
47
|
- lib/ocak/commands/hiz.rb
|
|
60
48
|
- lib/ocak/commands/init.rb
|
|
49
|
+
- lib/ocak/commands/issue/close.rb
|
|
50
|
+
- lib/ocak/commands/issue/create.rb
|
|
51
|
+
- lib/ocak/commands/issue/edit.rb
|
|
52
|
+
- lib/ocak/commands/issue/list.rb
|
|
53
|
+
- lib/ocak/commands/issue/view.rb
|
|
61
54
|
- lib/ocak/commands/resume.rb
|
|
62
55
|
- lib/ocak/commands/run.rb
|
|
63
56
|
- lib/ocak/commands/status.rb
|
|
64
57
|
- lib/ocak/config.rb
|
|
58
|
+
- lib/ocak/failure_reporting.rb
|
|
65
59
|
- lib/ocak/git_utils.rb
|
|
60
|
+
- lib/ocak/instance_builders.rb
|
|
61
|
+
- lib/ocak/issue_backend.rb
|
|
66
62
|
- lib/ocak/issue_fetcher.rb
|
|
63
|
+
- lib/ocak/local_issue_fetcher.rb
|
|
64
|
+
- lib/ocak/local_merge_manager.rb
|
|
67
65
|
- lib/ocak/logger.rb
|
|
68
66
|
- lib/ocak/merge_manager.rb
|
|
69
67
|
- lib/ocak/merge_orchestration.rb
|
|
70
68
|
- lib/ocak/monorepo_detector.rb
|
|
69
|
+
- lib/ocak/parallel_execution.rb
|
|
71
70
|
- lib/ocak/pipeline_executor.rb
|
|
72
71
|
- lib/ocak/pipeline_runner.rb
|
|
73
72
|
- lib/ocak/pipeline_state.rb
|
|
74
73
|
- lib/ocak/planner.rb
|
|
75
74
|
- lib/ocak/process_registry.rb
|
|
76
75
|
- lib/ocak/process_runner.rb
|
|
76
|
+
- lib/ocak/project_key.rb
|
|
77
77
|
- lib/ocak/reready_processor.rb
|
|
78
78
|
- lib/ocak/run_report.rb
|
|
79
|
+
- lib/ocak/shutdown_handling.rb
|
|
79
80
|
- lib/ocak/stack_detector.rb
|
|
81
|
+
- lib/ocak/state_management.rb
|
|
80
82
|
- lib/ocak/step_comments.rb
|
|
83
|
+
- lib/ocak/step_execution.rb
|
|
81
84
|
- lib/ocak/stream_parser.rb
|
|
82
85
|
- lib/ocak/templates/agents/auditor.md.erb
|
|
83
86
|
- lib/ocak/templates/agents/documenter.md.erb
|