harness: LiteLLM client, Redmine intake, REPORT writeback, gated bash tool

OpenAI-compatible chat client for the LiteLLM proxy (base_url normalization,
Bearer auth, retry/backoff on 429/5xx/transport, usage accounting) - stdlib
http only. Intake lists issues in scope from /issues.json with the task
class read from a configurable custom field. Writeback lands each turn as
REPORT-<vertical>-<task>-<ts>.md plus REPORT-latest.md (atomic rename) with
model/tier/token telemetry. The bash tool ports maki's permission semantics
without tree-sitter: segment-by-segment compound-command checks, deny beats
allow, word-boundary "cmd *" matching, $()/backtick/subshell denied, output
truncation, per-command timeout with process-group cleanup.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
2026-08-28 19:20:57 -05:00
parent 591d345371
commit aefa73aa90
12 changed files with 1230 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
// 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"
"git.knownelement.com/reachableceo/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()
}