Class: Soba::Services::WorkflowExecutor

Inherits:
Object
  • Object
show all
Defined in:
lib/soba/services/workflow_executor.rb

Instance Method Summary collapse

Constructor Details

#initialize(tmux_session_manager: nil, git_workspace_manager: nil) ⇒ WorkflowExecutor

Returns a new instance of WorkflowExecutor.



15
16
17
18
# File 'lib/soba/services/workflow_executor.rb', line 15

def initialize(tmux_session_manager: nil, git_workspace_manager: nil)
  @tmux_session_manager = tmux_session_manager
  @git_workspace_manager = git_workspace_manager || GitWorkspaceManager.new
end

Instance Method Details

#execute(phase:, issue_number:, use_tmux: true, setup_workspace: true, issue_title: nil, phase_name: nil) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/soba/services/workflow_executor.rb', line 20

def execute(phase:, issue_number:, use_tmux: true, setup_workspace: true, issue_title: nil, phase_name: nil)
  return nil unless phase.command

  # Slack通知を送信(設定が有効な場合)
  send_slack_notification(issue_number, issue_title, phase_name) if phase_name

  # フェーズ開始時にmainブランチを更新し、ワークスペースをセットアップ
  if setup_workspace
    # mainブランチを最新化
    begin
      @git_workspace_manager.update_main_branch
      puts "Successfully updated main branch"
    rescue GitWorkspaceManager::GitOperationError => e
      puts "Warning: Failed to update main branch: #{e.message}"
      puts "  Continuing without main branch update..."
      # mainブランチの更新に失敗しても続行(エラーハンドリング)
    end

    # ワークスペースをセットアップ
    begin
      @git_workspace_manager.setup_workspace(issue_number)
      puts "Successfully setup workspace for issue ##{issue_number}"
    rescue GitWorkspaceManager::GitOperationError => e
      puts "Warning: Failed to setup workspace: #{e.message}"
      puts "  Continuing without worktree setup..."
      # ワークスペースのセットアップに失敗しても続行(既存の動作を維持)
    end
  end

  if use_tmux
    execute_in_tmux(phase: phase, issue_number: issue_number)
  else
    execute_direct(phase: phase, issue_number: issue_number)
  end
end

#execute_direct(phase:, issue_number:) ⇒ Object



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
83
84
85
86
87
88
89
90
# File 'lib/soba/services/workflow_executor.rb', line 56

def execute_direct(phase:, issue_number:)
  return nil unless phase.command

  command_array = build_command(phase, issue_number)
  worktree_path = @git_workspace_manager.get_worktree_path(issue_number)

  result = if worktree_path
             # worktreeが存在する場合はその中で実行
             Dir.chdir(worktree_path) do
               Open3.popen3(*command_array) do |stdin, stdout, stderr, wait_thr|
                 stdin.close
                 [stdout.read, stderr.read, wait_thr.value]
               end
             end
           else
             # worktreeが存在しない場合は現在のディレクトリで実行
             Open3.popen3(*command_array) do |stdin, stdout, stderr, wait_thr|
               stdin.close
               [stdout.read, stderr.read, wait_thr.value]
             end
           end

  stdout, stderr, status = result

  {
    success: status.exitstatus == 0,
    output: stdout,
    error: stderr,
    exit_code: status.exitstatus,
  }
rescue Errno::ENOENT => e
  raise WorkflowExecutionError, "Failed to execute workflow command: #{e.message}"
rescue StandardError => e
  raise WorkflowExecutionError, "Failed to execute workflow command: #{e.message}"
end

#execute_in_tmux(phase:, issue_number:) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/soba/services/workflow_executor.rb', line 92

def execute_in_tmux(phase:, issue_number:)
  return nil unless phase.command

  command_string = build_command_string_with_worktree(phase, issue_number)
  puts "Executing in tmux for phase: #{phase.name || 'unknown'}, issue ##{issue_number}"

  begin
    # 新しいtmux管理方式: 1リポジトリ = 1セッション、1 Issue = 1 window
    session_result = @tmux_session_manager.find_or_create_repository_session
    return session_result unless session_result[:success]

    window_result = @tmux_session_manager.create_issue_window(
      session_name: session_result[:session_name],
      issue_number: issue_number
    )
    return window_result unless window_result[:success]

    # フェーズごとにpane分割(既存のwindowがある場合は新規pane作成)
    if window_result[:created]
      # 新規windowの場合は最初のpaneでコマンド実行
      puts "  Created new window: #{window_result[:window_name]}"
      apply_command_delay
      tmux_client = Soba::Infrastructure::TmuxClient.new
      tmux_client.send_keys("#{session_result[:session_name]}:#{window_result[:window_name]}", command_string)
      pane_id = nil
    else
      # 既存windowの場合は新規paneを作成(水平分割)
      phase_name = phase.name || 'unknown'
      puts "  Creating new pane for phase: #{phase_name} in window: #{window_result[:window_name]}"
      pane_result = @tmux_session_manager.create_phase_pane(
        session_name: session_result[:session_name],
        window_name: window_result[:window_name],
        phase: phase_name,
        vertical: false
      )
      return pane_result unless pane_result[:success]

      apply_command_delay
      pane_id = pane_result[:pane_id]
      puts "  Created pane: #{pane_id}"
      tmux_client = Soba::Infrastructure::TmuxClient.new
      tmux_client.send_keys(pane_id, command_string)
    end

    # 監視用コマンドを生成
    target = pane_id || "#{session_result[:session_name]}:#{window_result[:window_name]}"
    monitor_commands = [
      "tmux attach -t #{target}",
      "tmux capture-pane -t #{target} -p",
    ]

    {
      success: true,
      session_name: session_result[:session_name],
      window_name: window_result[:window_name],
      pane_id: pane_id,
      mode: 'tmux',
      tmux_info: {
        session: session_result[:session_name],
        window: window_result[:window_name],
        pane: pane_id,
        monitor_commands: monitor_commands,
      },
    }
  rescue Soba::Infrastructure::TmuxNotInstalled => e
    # tmuxがインストールされていない場合は通常実行にフォールバック
    puts "Warning: #{e.message}. Falling back to direct execution..."
    execute_direct(phase: phase, issue_number: issue_number)
  rescue StandardError => e
    # その他のtmuxエラーの場合も通常実行にフォールバック
    puts "Warning: Tmux execution failed: #{e.message}. Falling back to direct execution..."
    execute_direct(phase: phase, issue_number: issue_number)
  end
end