ocak 0.3.0 → 0.4.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.
@@ -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?
@@ -42,6 +71,9 @@ module Ocak
42
71
  return nil if status.success?
43
72
 
44
73
  "=== #{@config.lint_check_command} (#{lintable.size} files) ===\n#{stdout}\n#{stderr}"
74
+ rescue ArgumentError => e
75
+ logger&.warn("Invalid shell command in config: #{@config.lint_check_command.inspect} (#{e.message})")
76
+ "=== #{@config.lint_check_command} ===\nArgumentError: #{e.message}"
45
77
  end
46
78
 
47
79
  def lint_extensions_for(language)
@@ -60,7 +92,7 @@ module Ocak
60
92
 
61
93
  private
62
94
 
63
- def check_tests(failures, output_parts, chdir:)
95
+ def check_tests(failures, output_parts, logger, chdir:)
64
96
  return unless @config.test_command
65
97
 
66
98
  stdout, stderr, status = Open3.capture3(*Shellwords.shellsplit(@config.test_command), chdir: chdir)
@@ -68,6 +100,9 @@ module Ocak
68
100
 
69
101
  failures << @config.test_command
70
102
  output_parts << "=== #{@config.test_command} ===\n#{stdout}\n#{stderr}"
103
+ rescue ArgumentError => e
104
+ logger&.warn("Invalid shell command in config: #{@config.test_command.inspect} (#{e.message})")
105
+ failures << @config.test_command
71
106
  end
72
107
 
73
108
  def check_lint(failures, output_parts, logger, chdir:)
@@ -7,8 +7,9 @@ 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
@@ -56,7 +57,8 @@ module Ocak
56
57
  begin
57
58
  git('worktree', 'remove', '--force', wt[:path])
58
59
  removed << wt[:path]
59
- rescue StandardError
60
+ rescue StandardError => e
61
+ @logger&.warn("Failed to remove worktree #{wt[:path]}: #{e.message}")
60
62
  next # skip failed removal so one bad worktree doesn't abort cleanup of others
61
63
  end
62
64
  end
@@ -64,7 +66,7 @@ module Ocak
64
66
  removed
65
67
  end
66
68
 
67
- Worktree = Struct.new(:path, :branch, :issue_number)
69
+ Worktree = Struct.new(:path, :branch, :issue_number, keyword_init: true) # rubocop:disable Style/RedundantStructKeywordInit
68
70
 
69
71
  class WorktreeError < StandardError; end
70
72
 
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.4.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.4.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.
@@ -62,6 +48,7 @@ files:
62
48
  - lib/ocak/commands/run.rb
63
49
  - lib/ocak/commands/status.rb
64
50
  - lib/ocak/config.rb
51
+ - lib/ocak/failure_reporting.rb
65
52
  - lib/ocak/git_utils.rb
66
53
  - lib/ocak/issue_fetcher.rb
67
54
  - lib/ocak/logger.rb