KAOS/code/internal/supervisor/reporter.go
djuka 028872be43 T05: Reporter — pisanje izveštaja
- WriteReport generiše markdown izveštaj u reports/
- formatDuration, truncateOutput za formatiranje
- Kreira folder ako ne postoji, skraćuje output na 50 linija
- 10 reporter testova — svi prolaze
- T03 premešten u done/

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

96 lines
2.6 KiB
Go

package supervisor
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// WriteReport generates a markdown report for a completed task and writes it
// to the reports directory. Returns the path to the generated report file.
func WriteReport(task Task, run RunResult, verify VerifyResult, reportsDir string) (string, error) {
if err := os.MkdirAll(reportsDir, 0755); err != nil {
return "", fmt.Errorf("create reports dir: %w", err)
}
filename := fmt.Sprintf("%s-report.md", task.ID)
path := filepath.Join(reportsDir, filename)
content := buildReport(task, run, verify)
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
return "", fmt.Errorf("write report: %w", err)
}
return path, nil
}
// buildReport generates the markdown content for a task report.
func buildReport(task Task, run RunResult, verify VerifyResult) string {
var b strings.Builder
// Header
fmt.Fprintf(&b, "# %s: %s\n\n", task.ID, task.Title)
// Metadata
fmt.Fprintf(&b, "**Datum:** %s\n", run.StartedAt.Format("2006-01-02 15:04"))
fmt.Fprintf(&b, "**Trajanje:** %s\n", formatDuration(run.Duration))
if verify.AllPassed {
b.WriteString("**Status:** ✅ Završen\n")
} else {
b.WriteString("**Status:** ❌ Neuspešan\n")
}
// Verification
b.WriteString("\n## Verifikacija\n")
for _, c := range []CheckResult{verify.Build, verify.Vet, verify.Test} {
icon := "✅"
if c.Status == "fail" {
icon = "❌"
}
line := fmt.Sprintf("- %s %s (%s)", icon, c.Name, c.Duration.Round(time.Millisecond))
if c.Name == "test" && c.TestCount > 0 {
line = fmt.Sprintf("- %s %s (%d testova, %s)", icon, c.Name, c.TestCount, c.Duration.Round(time.Millisecond))
}
b.WriteString(line + "\n")
}
// Agent output
b.WriteString("\n## Agent output\n")
b.WriteString(truncateOutput(run.Output, 50))
b.WriteString("\n")
return b.String()
}
// formatDuration formats a duration as "Xm Ys" or "Xs" for short durations.
func formatDuration(d time.Duration) string {
d = d.Round(time.Second)
minutes := int(d.Minutes())
seconds := int(d.Seconds()) % 60
if minutes > 0 {
return fmt.Sprintf("%dm %ds", minutes, seconds)
}
return fmt.Sprintf("%ds", seconds)
}
// truncateOutput keeps only the last maxLines lines of output.
func truncateOutput(output string, maxLines int) string {
if output == "" {
return "(nema outputa)\n"
}
lines := strings.Split(strings.TrimRight(output, "\n"), "\n")
if len(lines) <= maxLines {
return strings.Join(lines, "\n") + "\n"
}
truncated := lines[len(lines)-maxLines:]
return fmt.Sprintf("... (skraćeno, prikazano poslednjih %d od %d linija)\n%s\n",
maxLines, len(lines), strings.Join(truncated, "\n"))
}