ocak 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +268 -0
- data/bin/ocak +7 -0
- data/lib/ocak/agent_generator.rb +171 -0
- data/lib/ocak/claude_runner.rb +169 -0
- data/lib/ocak/cli.rb +28 -0
- data/lib/ocak/commands/audit.rb +25 -0
- data/lib/ocak/commands/clean.rb +30 -0
- data/lib/ocak/commands/debt.rb +21 -0
- data/lib/ocak/commands/design.rb +34 -0
- data/lib/ocak/commands/init.rb +212 -0
- data/lib/ocak/commands/resume.rb +128 -0
- data/lib/ocak/commands/run.rb +60 -0
- data/lib/ocak/commands/status.rb +102 -0
- data/lib/ocak/config.rb +109 -0
- data/lib/ocak/issue_fetcher.rb +137 -0
- data/lib/ocak/logger.rb +192 -0
- data/lib/ocak/merge_manager.rb +158 -0
- data/lib/ocak/pipeline_runner.rb +389 -0
- data/lib/ocak/pipeline_state.rb +51 -0
- data/lib/ocak/planner.rb +68 -0
- data/lib/ocak/process_runner.rb +82 -0
- data/lib/ocak/stack_detector.rb +333 -0
- data/lib/ocak/stream_parser.rb +189 -0
- data/lib/ocak/templates/agents/auditor.md.erb +87 -0
- data/lib/ocak/templates/agents/documenter.md.erb +67 -0
- data/lib/ocak/templates/agents/implementer.md.erb +154 -0
- data/lib/ocak/templates/agents/merger.md.erb +97 -0
- data/lib/ocak/templates/agents/pipeline.md.erb +126 -0
- data/lib/ocak/templates/agents/planner.md.erb +86 -0
- data/lib/ocak/templates/agents/reviewer.md.erb +98 -0
- data/lib/ocak/templates/agents/security_reviewer.md.erb +112 -0
- data/lib/ocak/templates/gitignore_additions.txt +10 -0
- data/lib/ocak/templates/hooks/post_edit_lint.sh.erb +57 -0
- data/lib/ocak/templates/hooks/task_completed_test.sh.erb +34 -0
- data/lib/ocak/templates/ocak.yml.erb +99 -0
- data/lib/ocak/templates/skills/audit/SKILL.md.erb +132 -0
- data/lib/ocak/templates/skills/debt/SKILL.md.erb +128 -0
- data/lib/ocak/templates/skills/design/SKILL.md.erb +131 -0
- data/lib/ocak/templates/skills/scan_file/SKILL.md.erb +113 -0
- data/lib/ocak/verification.rb +83 -0
- data/lib/ocak/worktree_manager.rb +92 -0
- data/lib/ocak.rb +13 -0
- metadata +115 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'shellwords'
|
|
5
|
+
|
|
6
|
+
module Ocak
|
|
7
|
+
# Final verification checks (tests + lint) extracted from PipelineRunner.
|
|
8
|
+
module Verification
|
|
9
|
+
def run_final_checks(logger, chdir:)
|
|
10
|
+
failures = []
|
|
11
|
+
output_parts = []
|
|
12
|
+
|
|
13
|
+
check_tests(failures, output_parts, chdir: chdir)
|
|
14
|
+
check_lint(failures, output_parts, logger, chdir: chdir)
|
|
15
|
+
|
|
16
|
+
if failures.empty?
|
|
17
|
+
logger.info('All checks passed')
|
|
18
|
+
{ success: true }
|
|
19
|
+
else
|
|
20
|
+
logger.warn("Checks failed: #{failures.join(', ')}")
|
|
21
|
+
{ success: false, failures: failures, output: output_parts.join("\n\n") }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run_scoped_lint(logger, chdir:)
|
|
26
|
+
changed_stdout, = Open3.capture3('git', 'diff', '--name-only', 'main', chdir: chdir)
|
|
27
|
+
changed_files = changed_stdout.lines.map(&:strip).reject(&:empty?)
|
|
28
|
+
|
|
29
|
+
extensions = lint_extensions_for(@config.language)
|
|
30
|
+
lintable = changed_files.select { |f| extensions.any? { |ext| f.end_with?(ext) } }
|
|
31
|
+
|
|
32
|
+
if lintable.empty?
|
|
33
|
+
logger.info('No changed files to lint')
|
|
34
|
+
return nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
cmd_parts = Shellwords.shellsplit(@config.lint_check_command)
|
|
38
|
+
cmd_parts << '--force-exclusion' if @config.lint_check_command.include?('rubocop')
|
|
39
|
+
cmd_parts.concat(lintable)
|
|
40
|
+
|
|
41
|
+
stdout, stderr, status = Open3.capture3(*cmd_parts, chdir: chdir)
|
|
42
|
+
return nil if status.success?
|
|
43
|
+
|
|
44
|
+
"=== #{@config.lint_check_command} (#{lintable.size} files) ===\n#{stdout}\n#{stderr}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def lint_extensions_for(language)
|
|
48
|
+
case language
|
|
49
|
+
when 'ruby' then %w[.rb .rake .gemspec]
|
|
50
|
+
when 'typescript' then %w[.ts .tsx]
|
|
51
|
+
when 'javascript' then %w[.js .jsx]
|
|
52
|
+
when 'python' then %w[.py]
|
|
53
|
+
when 'rust' then %w[.rs]
|
|
54
|
+
when 'go' then %w[.go]
|
|
55
|
+
when 'elixir' then %w[.ex .exs]
|
|
56
|
+
when 'java' then %w[.java]
|
|
57
|
+
else %w[.rb .ts .tsx .js .jsx .py .rs .go]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def check_tests(failures, output_parts, chdir:)
|
|
64
|
+
return unless @config.test_command
|
|
65
|
+
|
|
66
|
+
stdout, stderr, status = Open3.capture3(*Shellwords.shellsplit(@config.test_command), chdir: chdir)
|
|
67
|
+
return if status.success?
|
|
68
|
+
|
|
69
|
+
failures << @config.test_command
|
|
70
|
+
output_parts << "=== #{@config.test_command} ===\n#{stdout}\n#{stderr}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def check_lint(failures, output_parts, logger, chdir:)
|
|
74
|
+
return unless @config.lint_check_command
|
|
75
|
+
|
|
76
|
+
lint_output = run_scoped_lint(logger, chdir: chdir)
|
|
77
|
+
return unless lint_output
|
|
78
|
+
|
|
79
|
+
failures << @config.lint_check_command
|
|
80
|
+
output_parts << lint_output
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require 'shellwords'
|
|
7
|
+
|
|
8
|
+
module Ocak
|
|
9
|
+
class WorktreeManager
|
|
10
|
+
def initialize(config:)
|
|
11
|
+
@config = config
|
|
12
|
+
@worktree_base = File.join(config.project_dir, config.worktree_dir)
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create(issue_number, setup_command: nil)
|
|
17
|
+
@mutex.synchronize do
|
|
18
|
+
FileUtils.mkdir_p(@worktree_base)
|
|
19
|
+
|
|
20
|
+
branch = "auto/issue-#{issue_number}-#{SecureRandom.hex(4)}"
|
|
21
|
+
path = File.join(@worktree_base, "issue-#{issue_number}")
|
|
22
|
+
|
|
23
|
+
_, stderr, status = git('worktree', 'add', '-b', branch, path, 'main')
|
|
24
|
+
raise WorktreeError, "Failed to create worktree: #{stderr}" unless status.success?
|
|
25
|
+
|
|
26
|
+
if setup_command
|
|
27
|
+
_, stderr, status = Open3.capture3(*Shellwords.shellsplit(setup_command), chdir: path)
|
|
28
|
+
raise WorktreeError, "Setup command failed: #{stderr}" unless status.success?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Worktree.new(path: path, branch: branch, issue_number: issue_number)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def remove(worktree)
|
|
36
|
+
git('worktree', 'remove', '--force', worktree.path)
|
|
37
|
+
git('worktree', 'prune')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def list
|
|
41
|
+
stdout, _, status = git('worktree', 'list', '--porcelain')
|
|
42
|
+
return [] unless status.success?
|
|
43
|
+
|
|
44
|
+
parse_worktree_list(stdout)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def prune
|
|
48
|
+
git('worktree', 'prune')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def clean_stale
|
|
52
|
+
removed = []
|
|
53
|
+
list.each do |wt|
|
|
54
|
+
next unless wt[:path]&.include?(@worktree_base)
|
|
55
|
+
|
|
56
|
+
git('worktree', 'remove', '--force', wt[:path])
|
|
57
|
+
removed << wt[:path]
|
|
58
|
+
end
|
|
59
|
+
prune
|
|
60
|
+
removed
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Worktree = Struct.new(:path, :branch, :issue_number)
|
|
64
|
+
|
|
65
|
+
class WorktreeError < StandardError; end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def git(*)
|
|
70
|
+
Open3.capture3('git', *, chdir: @config.project_dir)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse_worktree_list(output)
|
|
74
|
+
worktrees = []
|
|
75
|
+
current = {}
|
|
76
|
+
|
|
77
|
+
output.each_line do |line|
|
|
78
|
+
line = line.strip
|
|
79
|
+
if line.empty?
|
|
80
|
+
worktrees << current unless current.empty?
|
|
81
|
+
current = {}
|
|
82
|
+
elsif line.start_with?('worktree ')
|
|
83
|
+
current[:path] = line.sub('worktree ', '')
|
|
84
|
+
elsif line.start_with?('branch ')
|
|
85
|
+
current[:branch] = line.sub('branch ', '').sub('refs/heads/', '')
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
worktrees << current unless current.empty?
|
|
89
|
+
worktrees
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/ocak.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ocak
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Clay Harmon
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: dry-cli
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
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
|
+
description: Ocak sets up and runs a multi-agent pipeline that autonomously implements
|
|
41
|
+
GitHub issues using Claude Code. Design issues, run the pipeline, ship code. Let
|
|
42
|
+
'em cook.
|
|
43
|
+
executables:
|
|
44
|
+
- ocak
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- LICENSE.txt
|
|
49
|
+
- README.md
|
|
50
|
+
- bin/ocak
|
|
51
|
+
- lib/ocak.rb
|
|
52
|
+
- lib/ocak/agent_generator.rb
|
|
53
|
+
- lib/ocak/claude_runner.rb
|
|
54
|
+
- lib/ocak/cli.rb
|
|
55
|
+
- lib/ocak/commands/audit.rb
|
|
56
|
+
- lib/ocak/commands/clean.rb
|
|
57
|
+
- lib/ocak/commands/debt.rb
|
|
58
|
+
- lib/ocak/commands/design.rb
|
|
59
|
+
- lib/ocak/commands/init.rb
|
|
60
|
+
- lib/ocak/commands/resume.rb
|
|
61
|
+
- lib/ocak/commands/run.rb
|
|
62
|
+
- lib/ocak/commands/status.rb
|
|
63
|
+
- lib/ocak/config.rb
|
|
64
|
+
- lib/ocak/issue_fetcher.rb
|
|
65
|
+
- lib/ocak/logger.rb
|
|
66
|
+
- lib/ocak/merge_manager.rb
|
|
67
|
+
- lib/ocak/pipeline_runner.rb
|
|
68
|
+
- lib/ocak/pipeline_state.rb
|
|
69
|
+
- lib/ocak/planner.rb
|
|
70
|
+
- lib/ocak/process_runner.rb
|
|
71
|
+
- lib/ocak/stack_detector.rb
|
|
72
|
+
- lib/ocak/stream_parser.rb
|
|
73
|
+
- lib/ocak/templates/agents/auditor.md.erb
|
|
74
|
+
- lib/ocak/templates/agents/documenter.md.erb
|
|
75
|
+
- lib/ocak/templates/agents/implementer.md.erb
|
|
76
|
+
- lib/ocak/templates/agents/merger.md.erb
|
|
77
|
+
- lib/ocak/templates/agents/pipeline.md.erb
|
|
78
|
+
- lib/ocak/templates/agents/planner.md.erb
|
|
79
|
+
- lib/ocak/templates/agents/reviewer.md.erb
|
|
80
|
+
- lib/ocak/templates/agents/security_reviewer.md.erb
|
|
81
|
+
- lib/ocak/templates/gitignore_additions.txt
|
|
82
|
+
- lib/ocak/templates/hooks/post_edit_lint.sh.erb
|
|
83
|
+
- lib/ocak/templates/hooks/task_completed_test.sh.erb
|
|
84
|
+
- lib/ocak/templates/ocak.yml.erb
|
|
85
|
+
- lib/ocak/templates/skills/audit/SKILL.md.erb
|
|
86
|
+
- lib/ocak/templates/skills/debt/SKILL.md.erb
|
|
87
|
+
- lib/ocak/templates/skills/design/SKILL.md.erb
|
|
88
|
+
- lib/ocak/templates/skills/scan_file/SKILL.md.erb
|
|
89
|
+
- lib/ocak/verification.rb
|
|
90
|
+
- lib/ocak/worktree_manager.rb
|
|
91
|
+
homepage: https://github.com/clayharmon/ocak
|
|
92
|
+
licenses:
|
|
93
|
+
- MIT
|
|
94
|
+
metadata:
|
|
95
|
+
homepage_uri: https://github.com/clayharmon/ocak
|
|
96
|
+
source_code_uri: https://github.com/clayharmon/ocak
|
|
97
|
+
rubygems_mfa_required: 'true'
|
|
98
|
+
rdoc_options: []
|
|
99
|
+
require_paths:
|
|
100
|
+
- lib
|
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '3.4'
|
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
requirements: []
|
|
112
|
+
rubygems_version: 3.6.9
|
|
113
|
+
specification_version: 4
|
|
114
|
+
summary: Autonomous GitHub issue processing pipeline using Claude Code
|
|
115
|
+
test_files: []
|