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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -0
  3. data/lib/ocak/agent_generator.rb +4 -3
  4. data/lib/ocak/batch_processing.rb +102 -0
  5. data/lib/ocak/claude_runner.rb +12 -8
  6. data/lib/ocak/cli.rb +13 -0
  7. data/lib/ocak/command_runner.rb +39 -0
  8. data/lib/ocak/commands/hiz.rb +73 -140
  9. data/lib/ocak/commands/init.rb +12 -6
  10. data/lib/ocak/commands/issue/close.rb +37 -0
  11. data/lib/ocak/commands/issue/create.rb +59 -0
  12. data/lib/ocak/commands/issue/edit.rb +31 -0
  13. data/lib/ocak/commands/issue/list.rb +43 -0
  14. data/lib/ocak/commands/issue/view.rb +58 -0
  15. data/lib/ocak/commands/resume.rb +7 -6
  16. data/lib/ocak/commands/status.rb +20 -0
  17. data/lib/ocak/config.rb +28 -5
  18. data/lib/ocak/failure_reporting.rb +17 -0
  19. data/lib/ocak/git_utils.rb +25 -9
  20. data/lib/ocak/instance_builders.rb +50 -0
  21. data/lib/ocak/issue_backend.rb +31 -0
  22. data/lib/ocak/issue_fetcher.rb +40 -47
  23. data/lib/ocak/local_issue_fetcher.rb +165 -0
  24. data/lib/ocak/local_merge_manager.rb +104 -0
  25. data/lib/ocak/logger.rb +5 -2
  26. data/lib/ocak/merge_manager.rb +36 -31
  27. data/lib/ocak/merge_orchestration.rb +8 -2
  28. data/lib/ocak/parallel_execution.rb +36 -0
  29. data/lib/ocak/pipeline_executor.rb +51 -169
  30. data/lib/ocak/pipeline_runner.rb +20 -167
  31. data/lib/ocak/pipeline_state.rb +11 -2
  32. data/lib/ocak/planner.rb +2 -3
  33. data/lib/ocak/process_runner.rb +4 -2
  34. data/lib/ocak/project_key.rb +38 -0
  35. data/lib/ocak/reready_processor.rb +15 -13
  36. data/lib/ocak/run_report.rb +7 -2
  37. data/lib/ocak/shutdown_handling.rb +67 -0
  38. data/lib/ocak/state_management.rb +104 -0
  39. data/lib/ocak/step_comments.rb +12 -7
  40. data/lib/ocak/step_execution.rb +66 -0
  41. data/lib/ocak/stream_parser.rb +7 -2
  42. data/lib/ocak/templates/agents/auditor.md.erb +38 -9
  43. data/lib/ocak/templates/agents/implementer.md.erb +32 -8
  44. data/lib/ocak/templates/agents/merger.md.erb +12 -5
  45. data/lib/ocak/templates/agents/pipeline.md.erb +4 -0
  46. data/lib/ocak/templates/agents/reviewer.md.erb +2 -2
  47. data/lib/ocak/templates/agents/security_reviewer.md.erb +11 -0
  48. data/lib/ocak/templates/ocak.yml.erb +16 -0
  49. data/lib/ocak/verification.rb +43 -3
  50. data/lib/ocak/worktree_manager.rb +7 -3
  51. data/lib/ocak.rb +1 -1
  52. 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:
@@ -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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ocak
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.0'
5
5
 
6
6
  def self.root
7
7
  File.expand_path('..', __dir__)
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.3.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