package supervisor import ( "bytes" "context" "fmt" "os/exec" "strings" "time" ) // RunResult holds the outcome of running a task via an external command. type RunResult struct { TaskID string StartedAt time.Time FinishedAt time.Time Duration time.Duration ExitCode int Output string // stdout Error string // stderr } // CommandBuilder defines how to build the command for running a task. // This allows tests to substitute mock commands. type CommandBuilder func(ctx context.Context, task Task, projectPath string) *exec.Cmd // DefaultCommandBuilder creates the command to run Claude Code CLI. func DefaultCommandBuilder(ctx context.Context, task Task, projectPath string) *exec.Cmd { prompt := buildPrompt(task) model := strings.ToLower(task.Model) cmd := exec.CommandContext(ctx, "claude", "--print", "--model", model, "--message", prompt, ) cmd.Dir = projectPath return cmd } // RunTask executes a task using the given command builder, with timeout support. // If cmdBuilder is nil, DefaultCommandBuilder is used. func RunTask(task Task, projectPath string, timeout time.Duration, cmdBuilder CommandBuilder) RunResult { result := RunResult{ TaskID: task.ID, } if task.ID == "" { result.ExitCode = 1 result.Error = "empty task ID" return result } if cmdBuilder == nil { cmdBuilder = DefaultCommandBuilder } ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() cmd := cmdBuilder(ctx, task, projectPath) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr result.StartedAt = time.Now() err := cmd.Run() result.FinishedAt = time.Now() result.Duration = result.FinishedAt.Sub(result.StartedAt) result.Output = stdout.String() result.Error = stderr.String() if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { result.ExitCode = exitErr.ExitCode() } else { result.ExitCode = 1 if result.Error == "" { result.Error = err.Error() } } } return result } // buildPrompt creates the prompt string to send to Claude Code. func buildPrompt(task Task) string { return fmt.Sprintf(`Radi na KAOS projektu. Tvoj task: %s — %s Pročitaj CLAUDE.md u root-u projekta za pravila rada. %s Kad završiš: go build, go test, commituj sa "T%s: opis".`, task.ID, task.Title, task.Description, task.ID[1:]) }