Module: Ocak::Verification

Included in:
PipelineExecutor
Defined in:
lib/ocak/verification.rb

Overview

Final verification checks (tests + lint) extracted from PipelineRunner.

Instance Method Summary collapse

Instance Method Details

#lint_extensions_for(language) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ocak/verification.rb', line 84

def lint_extensions_for(language)
  case language
  when 'ruby'                    then %w[.rb .rake .gemspec]
  when 'typescript'              then %w[.ts .tsx]
  when 'javascript'              then %w[.js .jsx]
  when 'python'                  then %w[.py]
  when 'rust'                    then %w[.rs]
  when 'go'                      then %w[.go]
  when 'elixir'                  then %w[.ex .exs]
  when 'java'                    then %w[.java]
  else                                %w[.rb .ts .tsx .js .jsx .py .rs .go]
  end
end

#run_final_checks(logger, chdir:) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/ocak/verification.rb', line 38

def run_final_checks(logger, chdir:)
  failures = []
  output_parts = []

  check_tests(failures, output_parts, logger, chdir: chdir)
  check_lint(failures, output_parts, logger, chdir: chdir)

  if failures.empty?
    logger.info('All checks passed')
    { success: true }
  else
    logger.warn("Checks failed: #{failures.join(', ')}")
    { success: false, failures: failures, output: output_parts.join("\n\n") }
  end
end

#run_scoped_lint(logger, chdir:) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/ocak/verification.rb', line 54

def run_scoped_lint(logger, chdir:)
  changed_stdout, stderr, status = Open3.capture3('git', 'diff', '--name-only', 'main', chdir: chdir)
  unless status.success?
    logger&.warn("Scoped lint skipped — git diff failed: #{stderr[0..200]}")
    return nil
  end

  changed_files = changed_stdout.lines.map(&:strip).reject(&:empty?)

  extensions = lint_extensions_for(@config.language)
  lintable = changed_files.select { |f| extensions.any? { |ext| f.end_with?(ext) } }

  if lintable.empty?
    logger.info('No changed files to lint')
    return nil
  end

  cmd_parts = Shellwords.shellsplit(@config.lint_check_command)
  cmd_parts << '--force-exclusion' if @config.lint_check_command.include?('rubocop')
  cmd_parts.concat(lintable)

  stdout, stderr, status = Open3.capture3(*cmd_parts, chdir: chdir)
  return nil if status.success?

  "=== #{@config.lint_check_command} (#{lintable.size} files) ===\n#{stdout}\n#{stderr}"
rescue ArgumentError => e
  logger&.warn("Invalid shell command in config: #{@config.lint_check_command.inspect} (#{e.message})")
  "=== #{@config.lint_check_command} ===\nArgumentError: #{e.message}"
end

#run_verification_with_retry(logger:, claude:, chdir:, model: nil, &post_comment) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/ocak/verification.rb', line 9

def run_verification_with_retry(logger:, claude:, chdir:, model: nil, &post_comment)
  return nil unless @config.test_command || @config.lint_check_command

  logger.info('--- Final verification ---')
  post_comment&.call("\u{1F504} **Phase: final-verify** (verification)")
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  result = run_final_checks(logger, chdir: chdir)

  unless result[:success]
    logger.warn('Final checks failed, attempting fix...')
    post_comment&.call("\u{26A0}\u{FE0F} **Final verification failed** \u2014 attempting auto-fix...")
    fix_prompt = "Fix these test/lint failures:\n\n" \
                 "<verification_output>\n#{result[:output]}\n</verification_output>"
    fix_opts = { chdir: chdir }
    fix_opts[:model] = model if model
    claude.run_agent('implementer', fix_prompt, **fix_opts)
    result = run_final_checks(logger, chdir: chdir)
  end

  duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time).round
  if result[:success]
    post_comment&.call("\u{2705} **Phase: final-verify** completed \u2014 #{duration}s")
    nil
  else
    post_comment&.call("\u{274C} **Phase: final-verify** failed \u2014 #{duration}s")
    { success: false, phase: 'final-verify', output: result[:output] }
  end
end