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")) }