The old path carried the reachableceo org prefix while the repo lives at
git.knownelement.com/ukrrs/MOPAC; the drift was cited as doc rot. go.mod
and every internal import updated; build/vet/test clean in the builder.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
// Package writeback persists turn results as REPORT files. The REPORT is
|
|
// both the human-facing record and the telemetry trail (model, tier, class,
|
|
// tokens, rounds) that makes routing decisions auditable.
|
|
package writeback
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"ukrrs.com/mopac/harness/internal/task"
|
|
)
|
|
|
|
// Report is everything that lands in a REPORT file.
|
|
type Report struct {
|
|
Time time.Time
|
|
Vertical string
|
|
Task task.Task
|
|
Class string
|
|
Tier string
|
|
Model string
|
|
PromptTokens int
|
|
CompletionTokens int
|
|
TotalTokens int
|
|
Rounds int
|
|
ToolCalls int
|
|
Denied int
|
|
Duration time.Duration
|
|
StopReason string
|
|
Content string
|
|
}
|
|
|
|
// Write renders r to <dir>/REPORT-<vertical>-<taskID>-<timestamp>.md and
|
|
// refreshes <dir>/REPORT-latest.md. Both writes are atomic (tmp + rename).
|
|
func Write(dir string, r Report) (string, error) {
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return "", fmt.Errorf("writeback: %w", err)
|
|
}
|
|
name := fmt.Sprintf("REPORT-%s-%s-%s.md",
|
|
sanitize(r.Vertical), sanitize(r.Task.ID), r.Time.Format("20060102-150405"))
|
|
body := r.Render()
|
|
path := filepath.Join(dir, name)
|
|
if err := atomicWrite(path, body); err != nil {
|
|
return "", fmt.Errorf("writeback: %w", err)
|
|
}
|
|
if err := atomicWrite(filepath.Join(dir, "REPORT-latest.md"), body); err != nil {
|
|
return "", fmt.Errorf("writeback: %w", err)
|
|
}
|
|
return path, nil
|
|
}
|
|
|
|
// Render formats the REPORT markdown.
|
|
func (r Report) Render() string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "# REPORT - %s - %s: %s\n\n", r.Vertical, r.Task.ID, r.Task.Subject)
|
|
fmt.Fprintf(&b, "- date: %s\n", r.Time.Format(time.RFC3339))
|
|
fmt.Fprintf(&b, "- task source: %s\n", r.Task.Source)
|
|
fmt.Fprintf(&b, "- model: %s (tier %s, class %s)\n", r.Model, r.Tier, r.Class)
|
|
fmt.Fprintf(&b, "- usage: prompt=%d completion=%d total=%d tokens\n",
|
|
r.PromptTokens, r.CompletionTokens, r.TotalTokens)
|
|
fmt.Fprintf(&b, "- turn: rounds=%d tool_calls=%d denied=%d stop=%s\n",
|
|
r.Rounds, r.ToolCalls, r.Denied, r.StopReason)
|
|
fmt.Fprintf(&b, "- duration: %s\n", r.Duration.Round(time.Millisecond))
|
|
fmt.Fprintf(&b, "\n## Result\n\n")
|
|
content := r.Content
|
|
if content == "" {
|
|
content = "(no content produced)"
|
|
}
|
|
b.WriteString(content)
|
|
b.WriteString("\n")
|
|
return b.String()
|
|
}
|
|
|
|
func atomicWrite(path, body string) error {
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, []byte(body), 0o644); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(tmp, path)
|
|
}
|
|
|
|
func sanitize(s string) string {
|
|
var b strings.Builder
|
|
for _, r := range s {
|
|
switch {
|
|
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '.', r == '_', r == '-':
|
|
b.WriteRune(r)
|
|
default:
|
|
b.WriteByte('-')
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|