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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +268 -0
  4. data/bin/ocak +7 -0
  5. data/lib/ocak/agent_generator.rb +171 -0
  6. data/lib/ocak/claude_runner.rb +169 -0
  7. data/lib/ocak/cli.rb +28 -0
  8. data/lib/ocak/commands/audit.rb +25 -0
  9. data/lib/ocak/commands/clean.rb +30 -0
  10. data/lib/ocak/commands/debt.rb +21 -0
  11. data/lib/ocak/commands/design.rb +34 -0
  12. data/lib/ocak/commands/init.rb +212 -0
  13. data/lib/ocak/commands/resume.rb +128 -0
  14. data/lib/ocak/commands/run.rb +60 -0
  15. data/lib/ocak/commands/status.rb +102 -0
  16. data/lib/ocak/config.rb +109 -0
  17. data/lib/ocak/issue_fetcher.rb +137 -0
  18. data/lib/ocak/logger.rb +192 -0
  19. data/lib/ocak/merge_manager.rb +158 -0
  20. data/lib/ocak/pipeline_runner.rb +389 -0
  21. data/lib/ocak/pipeline_state.rb +51 -0
  22. data/lib/ocak/planner.rb +68 -0
  23. data/lib/ocak/process_runner.rb +82 -0
  24. data/lib/ocak/stack_detector.rb +333 -0
  25. data/lib/ocak/stream_parser.rb +189 -0
  26. data/lib/ocak/templates/agents/auditor.md.erb +87 -0
  27. data/lib/ocak/templates/agents/documenter.md.erb +67 -0
  28. data/lib/ocak/templates/agents/implementer.md.erb +154 -0
  29. data/lib/ocak/templates/agents/merger.md.erb +97 -0
  30. data/lib/ocak/templates/agents/pipeline.md.erb +126 -0
  31. data/lib/ocak/templates/agents/planner.md.erb +86 -0
  32. data/lib/ocak/templates/agents/reviewer.md.erb +98 -0
  33. data/lib/ocak/templates/agents/security_reviewer.md.erb +112 -0
  34. data/lib/ocak/templates/gitignore_additions.txt +10 -0
  35. data/lib/ocak/templates/hooks/post_edit_lint.sh.erb +57 -0
  36. data/lib/ocak/templates/hooks/task_completed_test.sh.erb +34 -0
  37. data/lib/ocak/templates/ocak.yml.erb +99 -0
  38. data/lib/ocak/templates/skills/audit/SKILL.md.erb +132 -0
  39. data/lib/ocak/templates/skills/debt/SKILL.md.erb +128 -0
  40. data/lib/ocak/templates/skills/design/SKILL.md.erb +131 -0
  41. data/lib/ocak/templates/skills/scan_file/SKILL.md.erb +113 -0
  42. data/lib/ocak/verification.rb +83 -0
  43. data/lib/ocak/worktree_manager.rb +92 -0
  44. data/lib/ocak.rb +13 -0
  45. 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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ocak
4
+ VERSION = '0.1.0'
5
+
6
+ def self.root
7
+ File.expand_path('..', __dir__)
8
+ end
9
+
10
+ def self.templates_dir
11
+ File.join(File.dirname(__FILE__), 'ocak', 'templates')
12
+ end
13
+ end
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: []