- 5 komandi: run, status, next, verify, history - Config proširen sa KAOS_TASKS_DIR - Tabelarni status sa emoji ikonama - run: scan → find → move → run → verify → report → review - 8 CLI testova, 59 ukupno — svi prolaze - T05 premešten u done/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
243 lines
5.6 KiB
Go
243 lines
5.6 KiB
Go
// Package main is the entry point for the KAOS supervisor process.
|
|
// It parses CLI commands and delegates to supervisor functions.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"github.com/dal/kaos/internal/config"
|
|
"github.com/dal/kaos/internal/supervisor"
|
|
)
|
|
|
|
func main() {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Greška pri učitavanju konfiguracije: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(os.Args) < 2 {
|
|
printHelp()
|
|
os.Exit(1)
|
|
}
|
|
|
|
command := os.Args[1]
|
|
|
|
switch command {
|
|
case "run":
|
|
os.Exit(cmdRun(cfg))
|
|
case "status":
|
|
os.Exit(cmdStatus(cfg))
|
|
case "next":
|
|
os.Exit(cmdNext(cfg))
|
|
case "verify":
|
|
os.Exit(cmdVerify(cfg))
|
|
case "history":
|
|
os.Exit(cmdHistory(cfg))
|
|
case "help", "--help", "-h":
|
|
printHelp()
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "Nepoznata komanda: %s\n\n", command)
|
|
printHelp()
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func printHelp() {
|
|
fmt.Println("KAOS Supervisor")
|
|
fmt.Println()
|
|
fmt.Println("Komande:")
|
|
fmt.Println(" run [TASK_ID] Pokreni task (ili sledeći iz ready/)")
|
|
fmt.Println(" status Prikaži status svih taskova")
|
|
fmt.Println(" next Prikaži sledeći task za rad")
|
|
fmt.Println(" verify [PATH] Pokreni verifikaciju na projektu")
|
|
fmt.Println(" history Prikaži izvršene taskove iz reports/")
|
|
fmt.Println(" help Prikaži ovu pomoć")
|
|
}
|
|
|
|
func cmdRun(cfg *config.Config) int {
|
|
tasks, err := supervisor.ScanTasks(cfg.TasksDir)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Greška pri skeniranju taskova: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
var task *supervisor.Task
|
|
|
|
if len(os.Args) > 2 {
|
|
taskID := os.Args[2]
|
|
task = supervisor.FindTask(tasks, taskID)
|
|
if task == nil {
|
|
fmt.Fprintf(os.Stderr, "Task %s nije pronađen\n", taskID)
|
|
return 1
|
|
}
|
|
if task.Status != "ready" {
|
|
fmt.Fprintf(os.Stderr, "Task %s nije u ready/ (trenutno: %s)\n", taskID, task.Status)
|
|
return 1
|
|
}
|
|
} else {
|
|
task = supervisor.NextTask(tasks)
|
|
if task == nil {
|
|
fmt.Println("Nema taskova spremnih za rad")
|
|
return 0
|
|
}
|
|
}
|
|
|
|
fmt.Printf("▶ Pokrećem: %s — %s\n", task.ID, task.Title)
|
|
|
|
// Move to active
|
|
if err := supervisor.MoveTask(cfg.TasksDir, task.ID, "ready", "active"); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Greška pri premještanju u active/: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
// Run the task
|
|
result := supervisor.RunTask(*task, cfg.ProjectPath, cfg.Timeout, nil)
|
|
|
|
fmt.Printf("⏱ Trajanje: %s\n", result.Duration.Round(1000000000)) // round to seconds
|
|
fmt.Printf("📤 Exit code: %d\n", result.ExitCode)
|
|
|
|
// Verify
|
|
fmt.Println("\n🔍 Verifikacija...")
|
|
verify := supervisor.Verify(cfg.ProjectPath)
|
|
fmt.Print(supervisor.FormatResult(verify))
|
|
|
|
// Write report
|
|
reportsDir := filepath.Join(cfg.TasksDir, "reports")
|
|
reportPath, err := supervisor.WriteReport(*task, result, verify, reportsDir)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Greška pri pisanju izveštaja: %v\n", err)
|
|
} else {
|
|
fmt.Printf("\n📝 Izveštaj: %s\n", reportPath)
|
|
}
|
|
|
|
// Move to review
|
|
if err := supervisor.MoveTask(cfg.TasksDir, task.ID, "active", "review"); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Greška pri premještanju u review/: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
fmt.Printf("\n✅ %s premešten u review/\n", task.ID)
|
|
|
|
if !verify.AllPassed {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func cmdStatus(cfg *config.Config) int {
|
|
tasks, err := supervisor.ScanTasks(cfg.TasksDir)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Greška pri skeniranju taskova: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
if len(tasks) == 0 {
|
|
fmt.Println("Nema taskova")
|
|
return 0
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
fmt.Fprintln(w, "ID\tNaslov\tFolder\tZavisi od")
|
|
fmt.Fprintln(w, "──\t──────\t──────\t─────────")
|
|
|
|
for _, t := range tasks {
|
|
deps := "—"
|
|
if len(t.DependsOn) > 0 {
|
|
deps = strings.Join(t.DependsOn, ", ")
|
|
}
|
|
|
|
icon := statusIcon(t.Status)
|
|
fmt.Fprintf(w, "%s\t%s\t%s %s\t%s\n", t.ID, t.Title, icon, t.Status, deps)
|
|
}
|
|
w.Flush()
|
|
return 0
|
|
}
|
|
|
|
func cmdNext(cfg *config.Config) int {
|
|
tasks, err := supervisor.ScanTasks(cfg.TasksDir)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Greška pri skeniranju taskova: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
task := supervisor.NextTask(tasks)
|
|
if task == nil {
|
|
fmt.Println("Nema taskova spremnih za rad")
|
|
return 0
|
|
}
|
|
|
|
fmt.Printf("Sledeći: %s — %s\n", task.ID, task.Title)
|
|
fmt.Printf("Agent: %s | Model: %s\n", task.Agent, task.Model)
|
|
if len(task.DependsOn) > 0 {
|
|
fmt.Printf("Zavisi od: %s\n", strings.Join(task.DependsOn, ", "))
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func cmdVerify(cfg *config.Config) int {
|
|
path := cfg.ProjectPath
|
|
if len(os.Args) > 2 {
|
|
path = os.Args[2]
|
|
}
|
|
|
|
fmt.Printf("🔍 Verifikacija: %s\n\n", path)
|
|
result := supervisor.Verify(path)
|
|
fmt.Print(supervisor.FormatResult(result))
|
|
|
|
if !result.AllPassed {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func cmdHistory(cfg *config.Config) int {
|
|
reportsDir := filepath.Join(cfg.TasksDir, "reports")
|
|
|
|
entries, err := os.ReadDir(reportsDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
fmt.Println("Nema izveštaja")
|
|
return 0
|
|
}
|
|
fmt.Fprintf(os.Stderr, "Greška pri čitanju reports/: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
if len(entries) == 0 {
|
|
fmt.Println("Nema izveštaja")
|
|
return 0
|
|
}
|
|
|
|
fmt.Println("Izveštaji:")
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), "-report.md") {
|
|
continue
|
|
}
|
|
name := strings.TrimSuffix(entry.Name(), "-report.md")
|
|
fmt.Printf(" 📝 %s (%s)\n", name, filepath.Join(reportsDir, entry.Name()))
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func statusIcon(status string) string {
|
|
switch status {
|
|
case "done":
|
|
return "✅"
|
|
case "active":
|
|
return "🔄"
|
|
case "ready":
|
|
return "📋"
|
|
case "review":
|
|
return "👀"
|
|
case "backlog":
|
|
return "📦"
|
|
default:
|
|
return "❓"
|
|
}
|
|
}
|