Trend-Radar + Pruefstand: Box beobachtet Modell-/Engine-Trends und testet Kandidaten selbst
- trend-radar-feed.sh (Cron Sa 05:15): Live-Puls ALLER Rollen mit Vorwochen-Vergleich, Engine-A/B gegen neuen llama.cpp-Build (Fruehwarnung vor So-Auto-Update), mechanische Watch-Liste (MMQ-Issue, ROCm-Releases, Community-Grid), HF-Kandidaten-Suche mit Zitat-Gate -> max. 1 Pruefstand-Kandidat/Woche - pruefstand.sh + harness.py (Cron So 01:00, no-agent): volles Programm je Kandidat - 6 Coding-Aufgaben mit echten Unit-Tests, 4 Agenten-Aufgaben (Tool-Loop + Endzustand), 4 Recherche-Fragen mit Zitat-Ehrlichkeits-Gate, Deutsch-Richter, Vision-Testbild, Tempo kurz+lang; Baseline der Amtsinhaber als Vergleichsmassstab - Leitplanken (User-Entscheid 17.07.): Download-Deckel 35 GB (drueber -> Karte), 1 Kandidat/Woche, Cache mit Auto-Aufraeumen, RAM-/Platz-Wache, Bench-Port :5899, Radar/Pruefstand EMPFEHLEN nur - Rollen-Wechsel bleibt Commander-Klick - E2E bewiesen (Mock-LLM): coding 6/6, recherche 4/4 inkl. Zitat-Gate, Agent-Roundtrip, Richter-Parsing; Suiten-Selbsttest mit Referenzloesungen 6/6 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prüfstand (17.07.2026): das "volle Programm" für Modell-Kandidaten — echte Coding-,
|
||||
# Agenten-, Recherche-, Deutsch- und Vision-Prüfungen (harness.py) plus Tempo, verglichen
|
||||
# gegen die BASELINE der Amtsinhaber. Läuft als Cron (So 01:00, --no-agent: stdout wird
|
||||
# wörtlich zugestellt; LEERER stdout = stille Nacht).
|
||||
#
|
||||
# Modi:
|
||||
# (ohne Argument) Cron-Modus: Baseline fehlt → Baseline messen; sonst ältesten
|
||||
# Kandidaten aus der Queue prüfen; nichts zu tun → still.
|
||||
# --baseline Amtsinhaber über llama-swap (:8080) neu vermessen.
|
||||
# --kandidat <spec> einen Kandidaten-Spec (JSON) direkt prüfen.
|
||||
#
|
||||
# Leitplanken (User-Entscheid 17.07.): max 1 Kandidat je Lauf · Download-Deckel 35 GB
|
||||
# (größere Kandidaten → Karte, Download erst nach Klick) · eigener Cache mit
|
||||
# Auto-Aufräumen · Live-Betrieb bleibt unangetastet (eigener Server auf :5899) ·
|
||||
# der Prüfstand EMPFIEHLT NUR — Rollen-Wechsel entscheidet der Commander per Karte.
|
||||
# RAM-Wache vor dem Serverstart; Heredoc+Pipe-Falle vermieden (Temp-Dateien, Lehre 10.07.).
|
||||
set -uo pipefail
|
||||
|
||||
MC="$HOME/mission-control-v2"
|
||||
PSD="$MC/deploy/pruefstand"
|
||||
VAULT="$HOME/wissens-vault"
|
||||
STATE="$HOME/.hermes/state/pruefstand"
|
||||
CACHE="/srv/models/pruefstand-cache"
|
||||
HERMES="$HOME/.local/bin/hermes"
|
||||
BENCH_BIN="${BENCH_BIN:-/opt/llamacpp-vulkan/llama-server}"
|
||||
BENCH_PORT="${BENCH_PORT:-5899}"
|
||||
LIVE="http://127.0.0.1:8080/v1"
|
||||
JUDGE_MODEL="${PRUEFSTAND_JUDGE:-gpt-oss-120b}"
|
||||
DECKEL_GB="${PRUEFSTAND_DECKEL_GB:-35}"
|
||||
DEADLINE_MIN="${PRUEFSTAND_DEADLINE_MIN:-100}"
|
||||
|
||||
mkdir -p "$STATE/queue" "$STATE/done" "$STATE/ergebnisse"
|
||||
|
||||
# Suiten je Rolle (Amtsinhaber-Alias = Rollenname in llama-swap)
|
||||
suiten_fuer() {
|
||||
case "$1" in
|
||||
hermes) echo "speed,agent,recherche,deutsch" ;;
|
||||
coder) echo "speed,coding,agent" ;;
|
||||
heavy) echo "speed,coding,recherche,deutsch" ;;
|
||||
vision) echo "speed,vision" ;;
|
||||
scout) echo "speed,agent,vision" ;;
|
||||
*) echo "speed,recherche,deutsch" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
briefkasten() { # $1=Betreff $2=Text (stiller Chronik-Spiegel, source=pruefstand)
|
||||
curl -sf -m 5 -X POST "${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data "$(python3 - "$1" "$2" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"text": sys.argv[2], "subject": sys.argv[1],
|
||||
"source": "pruefstand", "priority": "silent"}))
|
||||
PY
|
||||
)" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
zusammenfassung_lesen() { # $1=ergebnis.json → kompakte Zeilen "suite: x/y · Note · tg"
|
||||
python3 - "$1" <<'PY'
|
||||
import json, sys
|
||||
try:
|
||||
z = json.load(open(sys.argv[1])).get("zusammenfassung", {})
|
||||
except Exception:
|
||||
print("(Ergebnis nicht lesbar)"); raise SystemExit
|
||||
for name, s in z.items():
|
||||
teile = [f"{s['bestanden']}/{s['gesamt']}"]
|
||||
if s.get("note_mittel"): teile.append(f"Note {s['note_mittel']}")
|
||||
if s.get("tg_mittel"): teile.append(f"tg {s['tg_mittel']} t/s")
|
||||
if s.get("pp_mittel"): teile.append(f"pp {s['pp_mittel']} t/s")
|
||||
print(f" {name}: " + " · ".join(teile))
|
||||
PY
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- Baseline
|
||||
baseline_lauf() {
|
||||
echo "## PRÜFSTAND: Basislinie der Amtsinhaber (erhoben am $(date '+%d.%m.%Y %H:%M'))"
|
||||
echo "(Echte Prüfungen: Coding mit Unit-Tests, Agenten-Aufgaben, Recherche mit Zitat-Gate, Deutsch-Richter, Tempo.)"
|
||||
local rollen="hermes coder heavy vision scout"
|
||||
for rolle in $rollen; do
|
||||
local out="$STATE/ergebnisse/baseline-${rolle}.json"
|
||||
echo
|
||||
echo "### Amtsinhaber \`$rolle\`"
|
||||
python3 "$PSD/harness.py" --endpoint "$LIVE" --model "$rolle" \
|
||||
--suites "$(suiten_fuer "$rolle")" --out "$out" \
|
||||
--judge-endpoint "$LIVE" --judge-model "$JUDGE_MODEL" \
|
||||
--deadline-min 30 2>&1 | tail -8
|
||||
done
|
||||
# embed: nur Funktions- und Latenz-Probe (Suiten ergeben hier keinen Sinn)
|
||||
echo
|
||||
echo "### Amtsinhaber \`embed\`"
|
||||
python3 - "$LIVE" <<'PY'
|
||||
import json, sys, time, urllib.request
|
||||
t0 = time.time()
|
||||
req = urllib.request.Request(sys.argv[1].rstrip("/") + "/embeddings",
|
||||
data=json.dumps({"model": "embed", "input": "Prüfstand-Probe"}).encode(),
|
||||
headers={"Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
d = json.load(r)
|
||||
dim = len(d["data"][0]["embedding"])
|
||||
print(f" ok · Dimension {dim} · {round((time.time()-t0)*1000)} ms")
|
||||
except Exception as e:
|
||||
print(f" FEHLER: {e}")
|
||||
PY
|
||||
# Sammel-Baseline schreiben (eine Datei, die Kandidaten-Läufe vergleichen)
|
||||
python3 - "$STATE" <<'PY'
|
||||
import glob, json, os, sys, time
|
||||
state = sys.argv[1]
|
||||
basis = {}
|
||||
for f in glob.glob(os.path.join(state, "ergebnisse", "baseline-*.json")):
|
||||
rolle = os.path.basename(f)[len("baseline-"):-len(".json")]
|
||||
try:
|
||||
basis[rolle] = json.load(open(f)).get("zusammenfassung", {})
|
||||
except Exception:
|
||||
pass
|
||||
json.dump({"erhoben": time.strftime("%Y-%m-%d %H:%M"), "rollen": basis},
|
||||
open(os.path.join(state, "baseline.json"), "w"), ensure_ascii=False, indent=1)
|
||||
print(f"\nBasislinie gespeichert ({len(basis)} Rollen).")
|
||||
PY
|
||||
briefkasten "Prüfstand" "Basislinie der Amtsinhaber erhoben — künftige Kandidaten werden dagegen verglichen."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- Kandidat
|
||||
kandidat_lauf() { # $1=spec.json
|
||||
local SPEC="$1"
|
||||
local KEY ROLLE HF_ID WARUM
|
||||
KEY="$(jq -r '.key' "$SPEC")"
|
||||
ROLLE="$(jq -r '.rolle' "$SPEC")"
|
||||
HF_ID="$(jq -r '.hf_id' "$SPEC")"
|
||||
WARUM="$(jq -r '.warum // ""' "$SPEC")"
|
||||
echo "## PRÜFSTAND: Kandidat \`$HF_ID\` für Rolle \`$ROLLE\` (am $(date '+%d.%m.%Y %H:%M'))"
|
||||
[ -n "$WARUM" ] && echo "Anlass: $WARUM"
|
||||
|
||||
if [ -f "$STATE/done/${KEY}.json" ]; then
|
||||
echo "Schon geprüft (State done/${KEY}) — nichts zu tun."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ---- GGUF im HF-Repo finden (bevorzugt ~Q4, ein Stück, Deckel prüfen)
|
||||
local TREE_JSON="$STATE/tmp-tree-${KEY}.json"
|
||||
curl -sf --max-time 60 \
|
||||
"https://huggingface.co/api/models/${HF_ID}/tree/main?recursive=true" \
|
||||
-o "$TREE_JSON" || { echo "HF-API nicht erreichbar — Abbruch, Spec bleibt in der Queue."; return 1; }
|
||||
local AUSWAHL
|
||||
AUSWAHL="$(python3 - "$TREE_JSON" "$DECKEL_GB" <<'PY'
|
||||
import json, re, sys
|
||||
baum = json.load(open(sys.argv[1]))
|
||||
deckel = float(sys.argv[2]) * 2**30
|
||||
dateien = [(e.get("path",""), (e.get("lfs") or {}).get("size") or e.get("size") or 0)
|
||||
for e in baum if isinstance(e, dict)]
|
||||
ggufs = [(p, g) for p, g in dateien if p.lower().endswith(".gguf")]
|
||||
mmproj = next((p for p, g in ggufs if "mmproj" in p.lower()), "")
|
||||
haupt = [(p, g) for p, g in ggufs if "mmproj" not in p.lower()]
|
||||
# Split-GGUFs (…-00001-of-…) klammern wir in v1 aus — sauberer Einzeldatei-Download
|
||||
haupt = [(p, g) for p, g in haupt if not re.search(r"-\d{5}-of-\d{5}", p)]
|
||||
prio = ["q4_k_m", "ud-q4_k_m", "q4_k_s", "iq4", "q5_k_m", "q4", "q8_0"]
|
||||
def rang(p):
|
||||
pl = p.lower()
|
||||
for i, q in enumerate(prio):
|
||||
if q in pl:
|
||||
return i
|
||||
return len(prio)
|
||||
haupt.sort(key=lambda t: (rang(t[0]), t[1]))
|
||||
if not haupt:
|
||||
print("KEINE"); raise SystemExit
|
||||
pfad, groesse = haupt[0]
|
||||
status = "OK" if groesse and groesse <= deckel else "ZU_GROSS"
|
||||
print(status); print(pfad); print(groesse or 0); print(mmproj)
|
||||
PY
|
||||
)"
|
||||
rm -f "$TREE_JSON"
|
||||
local STATUS PFAD GROESSE MMPROJ
|
||||
STATUS="$(printf '%s\n' "$AUSWAHL" | sed -n 1p)"
|
||||
PFAD="$(printf '%s\n' "$AUSWAHL" | sed -n 2p)"
|
||||
GROESSE="$(printf '%s\n' "$AUSWAHL" | sed -n 3p)"
|
||||
MMPROJ="$(printf '%s\n' "$AUSWAHL" | sed -n 4p)"
|
||||
local GB=$(( ${GROESSE:-0} / 1073741824 ))
|
||||
|
||||
if [ "$STATUS" = "KEINE" ]; then
|
||||
echo "Kein brauchbares Einzel-GGUF im Repo (nur Splits oder gar keins) — Karte statt Download."
|
||||
"$HERMES" kanban create "Prüfstand: ${HF_ID} braucht Handarbeit (kein Einzel-GGUF)" \
|
||||
--body "Kandidat für Rolle ${ROLLE}. Im HF-Repo liegt kein Einzeldatei-GGUF ≤ ${DECKEL_GB} GB (vermutlich Split-Dateien). Bei Interesse manuell laden und 'bash ~/mission-control-v2/deploy/pruefstand/pruefstand.sh --kandidat <spec>' fahren. Anlass: ${WARUM}" \
|
||||
--triage --created-by pruefstand --idempotency-key "pruefstand-${KEY}" >/dev/null 2>&1 || true
|
||||
mv "$SPEC" "$STATE/done/${KEY}.spec.json"
|
||||
echo "{\"status\":\"kein_gguf\"}" > "$STATE/done/${KEY}.json"
|
||||
return 0
|
||||
fi
|
||||
if [ "$STATUS" = "ZU_GROSS" ]; then
|
||||
echo "GGUF ${PFAD} ist ${GB} GB > Deckel ${DECKEL_GB} GB — Download wartet auf deinen Klick (Karte liegt bereit)."
|
||||
"$HERMES" kanban create "Prüfstand: ${HF_ID} (${GB} GB) — Download freigeben?" \
|
||||
--body "Kandidat für Rolle ${ROLLE}, Datei ${PFAD} (${GB} GB) liegt ÜBER dem Autonomie-Deckel von ${DECKEL_GB} GB (Entscheid 17.07.). Nach Freigabe: Spec wieder in ~/.hermes/state/pruefstand/queue/ legen und PRUEFSTAND_DECKEL_GB erhöhen oder manuell laden. Anlass: ${WARUM}" \
|
||||
--triage --created-by pruefstand --idempotency-key "pruefstand-${KEY}" >/dev/null 2>&1 || true
|
||||
mv "$SPEC" "$STATE/done/${KEY}.spec.json"
|
||||
echo "{\"status\":\"zu_gross\",\"gb\":${GB}}" > "$STATE/done/${KEY}.json"
|
||||
briefkasten "Prüfstand" "Kandidat ${HF_ID} (${GB} GB) wartet auf Download-Freigabe (Deckel ${DECKEL_GB} GB)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ---- Platz- und RAM-Wache
|
||||
local FREI_GB
|
||||
FREI_GB="$(df --output=avail -BG /srv/models | tail -1 | tr -dc '0-9')"
|
||||
if [ "${FREI_GB:-0}" -lt $(( GB + 20 )) ]; then
|
||||
echo "Zu wenig Platz auf /srv/models (${FREI_GB} GB frei, gebraucht ~$((GB+20))) — Lauf verschoben, Spec bleibt in der Queue."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# ---- Download in den Cache (mit Resume, Auto-Aufräumen am Ende)
|
||||
mkdir -p "$CACHE/$KEY"
|
||||
local ZIEL="$CACHE/$KEY/$(basename "$PFAD")"
|
||||
echo "Lade ${PFAD} (${GB} GB) …"
|
||||
curl -fL --retry 3 -C - --max-time 7200 -o "$ZIEL" \
|
||||
"https://huggingface.co/${HF_ID}/resolve/main/${PFAD}" \
|
||||
|| { echo "Download fehlgeschlagen — Spec bleibt in der Queue."; return 1; }
|
||||
local MM_ZIEL=""
|
||||
if [ -n "$MMPROJ" ] && { [ "$ROLLE" = "vision" ] || [ "$ROLLE" = "scout" ]; }; then
|
||||
MM_ZIEL="$CACHE/$KEY/$(basename "$MMPROJ")"
|
||||
curl -fL --retry 3 -C - --max-time 3600 -o "$MM_ZIEL" \
|
||||
"https://huggingface.co/${HF_ID}/resolve/main/${MMPROJ}" || MM_ZIEL=""
|
||||
fi
|
||||
|
||||
local MEM_FREI_GB
|
||||
MEM_FREI_GB="$(awk '/MemAvailable/{printf "%d", $2/1048576}' /proc/meminfo)"
|
||||
if [ "${MEM_FREI_GB:-0}" -lt $(( GB + 8 )) ]; then
|
||||
echo "Zu wenig freier RAM (${MEM_FREI_GB} GB) für ein ${GB}-GB-Modell neben dem Warm-Set — Lauf verschoben."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# ---- Kandidaten-Server auf dem Bench-Port (Live-Betrieb unangetastet)
|
||||
echo "Starte Kandidaten-Server (:${BENCH_PORT}) …"
|
||||
local MMFLAG=""
|
||||
[ -n "$MM_ZIEL" ] && MMFLAG="--mmproj $MM_ZIEL"
|
||||
$BENCH_BIN -m "$ZIEL" --host 127.0.0.1 --port "$BENCH_PORT" \
|
||||
-c 32768 -ngl 999 -fa on --no-mmap --jinja $MMFLAG \
|
||||
>/tmp/pruefstand-server.log 2>&1 &
|
||||
local SPID=$!
|
||||
trap 'kill $SPID 2>/dev/null; wait $SPID 2>/dev/null' RETURN
|
||||
local BEREIT=0
|
||||
for i in $(seq 1 240); do
|
||||
curl -s --max-time 2 "http://127.0.0.1:$BENCH_PORT/health" | grep -q ok && { BEREIT=1; break; }
|
||||
sleep 2
|
||||
kill -0 $SPID 2>/dev/null || break
|
||||
done
|
||||
if [ "$BEREIT" != "1" ]; then
|
||||
echo "LADE-CRASH — Kandidat startet nicht (Architektur zu neu für die Engine?):"
|
||||
tail -3 /tmp/pruefstand-server.log
|
||||
echo "{\"status\":\"lade_crash\"}" > "$STATE/done/${KEY}.json"
|
||||
mv "$SPEC" "$STATE/done/${KEY}.spec.json"
|
||||
rm -rf "${CACHE:?}/$KEY"
|
||||
briefkasten "Prüfstand" "Kandidat ${HF_ID} startet auf unserer Engine nicht (Lade-Crash) — aussortiert."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ---- Das volle Programm
|
||||
local OUT="$STATE/ergebnisse/kandidat-${KEY}.json"
|
||||
echo "Fahre Suiten: $(suiten_fuer "$ROLLE") (Zeitbudget ${DEADLINE_MIN} min)"
|
||||
python3 "$PSD/harness.py" --endpoint "http://127.0.0.1:$BENCH_PORT/v1" \
|
||||
--model "kandidat" --suites "$(suiten_fuer "$ROLLE")" --out "$OUT" \
|
||||
--judge-endpoint "$LIVE" --judge-model "$JUDGE_MODEL" \
|
||||
--deadline-min "$DEADLINE_MIN" 2>&1 | tail -8
|
||||
kill $SPID 2>/dev/null; wait $SPID 2>/dev/null; trap - RETURN
|
||||
|
||||
# ---- Steckbrief: Kandidat vs. Amtsinhaber
|
||||
local BERICHT="$STATE/ergebnisse/kandidat-${KEY}.md"
|
||||
python3 - "$OUT" "$STATE/baseline.json" "$ROLLE" "$HF_ID" "$WARUM" "$BERICHT" <<'PY'
|
||||
import json, sys, time
|
||||
kand = json.load(open(sys.argv[1]))
|
||||
try:
|
||||
basis = json.load(open(sys.argv[2]))["rollen"].get(sys.argv[3], {})
|
||||
except Exception:
|
||||
basis = {}
|
||||
rolle, hf_id, warum, ziel = sys.argv[3], sys.argv[4], sys.argv[5], sys.argv[6]
|
||||
kz = kand.get("zusammenfassung", {})
|
||||
zeilen = [f"# Prüfstand-Steckbrief: {hf_id}",
|
||||
f"Rolle: **{rolle}** · erhoben {time.strftime('%d.%m.%Y %H:%M')}",
|
||||
f"Anlass: {warum}" if warum else "", "",
|
||||
"| Suite | Kandidat | Amtsinhaber |", "|---|---|---|"]
|
||||
punkte_k = punkte_b = faelle = 0
|
||||
for name in sorted(set(kz) | set(basis)):
|
||||
def fmt(s):
|
||||
if not s: return "—"
|
||||
t = f"{s['bestanden']}/{s['gesamt']}"
|
||||
if s.get("note_mittel"): t += f" · Note {s['note_mittel']}"
|
||||
if s.get("tg_mittel"): t += f" · tg {s['tg_mittel']}"
|
||||
if s.get("pp_mittel"): t += f" · pp {s['pp_mittel']}"
|
||||
return t
|
||||
zeilen.append(f"| {name} | {fmt(kz.get(name))} | {fmt(basis.get(name))} |")
|
||||
if name in kz and name in basis and name != "speed":
|
||||
faelle += 1
|
||||
punkte_k += kz[name]["bestanden"] / max(1, kz[name]["gesamt"])
|
||||
punkte_b += basis[name]["bestanden"] / max(1, basis[name]["gesamt"])
|
||||
tg_k = (kz.get("speed") or {}).get("tg_mittel") or 0
|
||||
tg_b = (basis.get("speed") or {}).get("tg_mittel") or 0
|
||||
zeilen.append("")
|
||||
if faelle:
|
||||
qk, qb = punkte_k / faelle, punkte_b / faelle
|
||||
tempo = (f"Tempo {round(100*tg_k/tg_b-100):+d} % vs. Amtsinhaber" if tg_k and tg_b
|
||||
else "Tempo-Vergleich unvollständig")
|
||||
if qk >= qb and tg_k >= 0.8 * (tg_b or tg_k):
|
||||
urteil = "KANDIDAT SIEHT STARK AUS — Karte prüfen lohnt sich."
|
||||
elif qk >= qb:
|
||||
urteil = "Qualität hält mit, aber deutlich langsamer — vermutlich kein Aufsteiger."
|
||||
else:
|
||||
urteil = "Qualität unter Amtsinhaber — kein Aufsteiger."
|
||||
zeilen.append(f"**Einordnung:** Bestehensquote {qk:.0%} vs. {qb:.0%} · {tempo}. {urteil}")
|
||||
zeilen.append("")
|
||||
zeilen.append("_Der Prüfstand empfiehlt nur — der Rollen-Wechsel bleibt Commander-Entscheid (Karte)._")
|
||||
text = "\n".join(z for z in zeilen if z is not None)
|
||||
open(ziel, "w", encoding="utf-8").write(text + "\n")
|
||||
print(text)
|
||||
PY
|
||||
|
||||
# ---- Vault-Bericht + Karte + stille Chronik, dann Cache aufräumen
|
||||
mkdir -p "$VAULT/pruefstand"
|
||||
cp "$BERICHT" "$VAULT/pruefstand/${KEY}.md" 2>/dev/null || true
|
||||
( cd "$VAULT" 2>/dev/null && git add "pruefstand/${KEY}.md" >/dev/null 2>&1 \
|
||||
&& git commit -q -m "pruefstand: Steckbrief ${KEY}" >/dev/null 2>&1 ) || true
|
||||
"$HERMES" kanban create "Prüfstand-Ergebnis: ${HF_ID} für Rolle ${ROLLE}" \
|
||||
--body "$(cat "$BERICHT")" \
|
||||
--triage --created-by pruefstand --idempotency-key "pruefstand-${KEY}" >/dev/null 2>&1 || true
|
||||
briefkasten "Prüfstand" "Kandidat ${HF_ID} (Rolle ${ROLLE}) geprüft — Steckbrief im Auftrags-Kanban und Vault (pruefstand/${KEY}.md)."
|
||||
mv "$SPEC" "$STATE/done/${KEY}.spec.json" 2>/dev/null || true
|
||||
cp "$OUT" "$STATE/done/${KEY}.json" 2>/dev/null || echo "{\"status\":\"geprueft\"}" > "$STATE/done/${KEY}.json"
|
||||
rm -rf "${CACHE:?}/$KEY"
|
||||
echo
|
||||
echo "(Cache aufgeräumt; Roh-Ergebnisse: $STATE/ergebnisse/kandidat-${KEY}.*)"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- Einstieg
|
||||
case "${1:-}" in
|
||||
--baseline)
|
||||
baseline_lauf
|
||||
;;
|
||||
--kandidat)
|
||||
[ -n "${2:-}" ] || { echo "Nutzung: pruefstand.sh --kandidat <spec.json>"; exit 1; }
|
||||
kandidat_lauf "$2"
|
||||
;;
|
||||
*)
|
||||
if [ ! -f "$STATE/baseline.json" ]; then
|
||||
baseline_lauf
|
||||
exit 0
|
||||
fi
|
||||
NAECHSTER="$(ls -1tr "$STATE/queue/"*.json 2>/dev/null | head -1 || true)"
|
||||
if [ -n "$NAECHSTER" ]; then
|
||||
kandidat_lauf "$NAECHSTER"
|
||||
fi
|
||||
# keine Queue → LEERER stdout → --no-agent-Cron bleibt still (bewusst)
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user