Skill 12.0.0 bis 12.2.0 und Adapter 2.0.0 bis 2.2.0. Werkzeugfreigabe: Die Shell-Rechte des OpenCode-Adapters sind jetzt eine Denylist wie beim Claude-Adapter statt einer Allowlist. Ausloeser war der erste Qwen-Lauf, dessen einzige beide Werkzeugaufrufe an einer Pipeline scheiterten. Kontrolltest belegt beide Richtungen: Get-ChildItem | Format-Table laeuft durch, rm wird verweigert, die Datei bleibt bestehen. Lokaler Betrieb: Der Preflight entlaedt alle Modelle vor jedem Lauf und laedt den Kontext auf das Modellmaximum. Gemessen waren zuvor beide Modelle gleichzeitig geladen - 168 MiB frei von 16,3 GB. Qwen 27B passt auf dieser Karte nicht und wurde durch qwen3.5-9b ersetzt, in Q4_K_M wie Gemma. Messinstrument: analyse-anforderungen.py erkennt Feldnamen in Markdown- Fettschrift und Modulpraefixe in IDs. Der erste lokale Lauf mit Artefakten wurde sonst mit null Anforderungen gezaehlt statt mit neun. Regressionsprobe an vier Claude-Laeufen unveraendert. Befunde: Der Standard-Ausgabeblock, mit dem Claude sieben Artefakte erzeugt, liefert bei gemma-4-e4b null von sechs Laeufen ein Ergebnis - das Modell loest den Pfad relativ zum Arbeitsverzeichnis auf. Ein Lauf startete alle sieben vorgesehenen Rollen und lieferte neun formkonforme Anforderungen. Ein anderer erzeugte sieben richtig benannte Dateien ohne eine einzige formkonforme Anforderung. Und eine Shell-Umleitung schrieb an der Denylist vorbei in den eingefrorenen Snapshot - gefunden vom Vorher/Nachher-Vergleich, nicht von der Regel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
106 lines
3.6 KiB
Python
106 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Verdichtet die RawResult.json unterhalb eines Verzeichnisses zu einer Tabelle.
|
|
|
|
Gedacht fuer die Sichtung einer Matrix: Welche Zelle hat gemessen, welche ist
|
|
eine Fehlmessung, und woran lag es. Schreibt nichts - reine Auswertung.
|
|
|
|
python lauf-uebersicht.py "<Verzeichnis>" [--details]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
def schreibziele(lauf: Path) -> list[str]:
|
|
"""Wohin der Lauf zu schreiben versuchte, samt Ergebnis.
|
|
|
|
Der haeufigste Grund fuer ein leeres Ergebnisverzeichnis ist ein
|
|
Schreibversuch an den falschen Ort - der ist ohne diese Aufstellung nicht
|
|
zu sehen, weil ``written_files`` dann schlicht leer bleibt.
|
|
"""
|
|
session = lauf / "_meta" / "opencode-session.json"
|
|
if not session.is_file():
|
|
return []
|
|
try:
|
|
daten = json.loads(session.read_text(encoding="utf-8"))
|
|
except (json.JSONDecodeError, OSError):
|
|
return []
|
|
ziele = []
|
|
for nachricht in daten.get("messages", []):
|
|
for teil in nachricht.get("parts", []):
|
|
if teil.get("type") != "tool" or teil.get("tool") not in ("write", "edit"):
|
|
continue
|
|
zustand = teil.get("state", {})
|
|
ziele.append(
|
|
f"{teil.get('tool')} {zustand.get('status')} -> "
|
|
f"{zustand.get('input', {}).get('filePath')}"
|
|
)
|
|
return ziele
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Uebersicht ueber Versuchslaeufe")
|
|
parser.add_argument("verzeichnis")
|
|
parser.add_argument("--details", action="store_true", help="Schreibziele zeigen")
|
|
args = parser.parse_args()
|
|
|
|
wurzel = Path(args.verzeichnis)
|
|
zeilen = []
|
|
for datei in sorted(wurzel.rglob("RawResult.json")):
|
|
try:
|
|
d = json.loads(datei.read_text(encoding="utf-8"))
|
|
except (json.JSONDecodeError, OSError):
|
|
continue
|
|
lauf = datei.parent
|
|
zeilen.append(
|
|
{
|
|
"lauf": lauf.name,
|
|
"modus": d.get("mode", "?"),
|
|
"exit": d.get("exit_code"),
|
|
"status": d.get("subtype", "?"),
|
|
"min": round(d.get("duration_ms", 0) / 60000, 1),
|
|
"turns": d.get("num_turns", 0),
|
|
"tools": d.get("tool_call_count", 0),
|
|
"sub": d.get("subagent_stats", {}).get("spawned", 0),
|
|
"dateien": len(d.get("written_files") or []),
|
|
# Eine 0 aus einem laufenden Subagenten ist kein Messwert.
|
|
"tokens": (
|
|
d["usage"]["total_tokens"]
|
|
if d.get("usage_captured", True)
|
|
else None
|
|
),
|
|
"pfad": lauf,
|
|
}
|
|
)
|
|
|
|
if not zeilen:
|
|
print("Keine RawResult.json gefunden.")
|
|
return 1
|
|
|
|
kopf = f"{'Lauf':44} {'Modus':8} {'St':3} {'Min':>6} {'Turn':>5} {'Tool':>5} {'Sub':>4} {'Dat':>4} {'Tokens':>10}"
|
|
print(kopf)
|
|
print("-" * len(kopf))
|
|
for z in zeilen:
|
|
tok = "n. erf." if z["tokens"] is None else f"{z['tokens']:,}"
|
|
print(
|
|
f"{z['lauf'][:44]:44} {z['modus']:8} {str(z['exit']):3} {z['min']:6.1f} "
|
|
f"{z['turns']:5} {z['tools']:5} {z['sub']:4} {z['dateien']:4} {tok:>10}"
|
|
)
|
|
if args.details:
|
|
for ziel in schreibziele(z["pfad"]):
|
|
print(f" {ziel}")
|
|
|
|
gueltig = [z for z in zeilen if z["dateien"] > 0]
|
|
print(
|
|
f"\n{len(zeilen)} Laeufe, davon {len(gueltig)} mit Ergebnisdateien, "
|
|
f"{len(zeilen) - len(gueltig)} Fehlmessungen."
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|