KAOS/code/internal/supervisor/runner.go
djuka 9d2c249ed1 T03: Runner — pokretanje Claude Code
- RunTask sa timeout-om, stdout/stderr capture, exit code
- buildPrompt generiše prompt za Claude CLI
- CommandBuilder interfejs za mock testiranje
- 7 runner testova — svi prolaze
- T04 premešten u done/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:46:07 +00:00

97 lines
2.3 KiB
Go

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:])
}