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()
}
+78
View File
@@ -0,0 +1,78 @@
package writeback
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
"git.knownelement.com/reachableceo/MOPAC/harness/internal/task"
)
func TestWriteProducesReportAndLatest(t *testing.T) {
dir := t.TempDir()
r := Report{
Time: time.Date(2026, 8, 28, 19, 41, 2, 0, time.UTC),
Vertical: "demo-stack",
Task: task.Task{ID: "demo-1", Subject: "MVP demo", Prompt: "tell me about yourself", Class: "primary", Source: "demo"},
Class: "primary",
Tier: "mopac-primary",
Model: "glm-5.3",
PromptTokens: 12,
CompletionTokens: 340,
TotalTokens: 352,
Rounds: 1,
StopReason: "complete",
Duration: 2 * time.Second,
Content: "I am GLM, a large language model.",
}
path, err := Write(dir, r)
if err != nil {
t.Fatalf("Write: %v", err)
}
want := filepath.Join(dir, "REPORT-demo-stack-demo-1-20260828-194102.md")
if path != want {
t.Errorf("path = %s, want %s", path, want)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
s := string(body)
for _, want := range []string{
"model: glm-5.3 (tier mopac-primary, class primary)",
"usage: prompt=12 completion=340 total=352 tokens",
"rounds=1",
"## Result",
"I am GLM, a large language model.",
} {
if !strings.Contains(s, want) {
t.Errorf("REPORT missing %q", want)
}
}
latest, err := os.ReadFile(filepath.Join(dir, "REPORT-latest.md"))
if err != nil || len(latest) != len(body) {
t.Errorf("REPORT-latest.md missing or stale (err=%v)", err)
}
// No .tmp litter.
entries, _ := os.ReadDir(dir)
for _, e := range entries {
if strings.HasSuffix(e.Name(), ".tmp") {
t.Errorf("tmp file left behind: %s", e.Name())
}
}
}
func TestSanitize(t *testing.T) {
if got := sanitize("a/b c"); got != "a-b-c" {
t.Errorf("sanitize = %q", got)
}
}
func TestEmptyContentPlaceholder(t *testing.T) {
r := Report{Time: time.Now(), Vertical: "v", Task: task.Task{ID: "x"}, Content: ""}
if !strings.Contains(r.Render(), "(no content produced)") {
t.Errorf("empty content should render a placeholder")
}
}