gpt skill ergünzt
This commit is contained in:
@@ -1,15 +1,15 @@
|
|||||||
---
|
---
|
||||||
name: run-experiment
|
name: run-experiment
|
||||||
description: Führt einen Versuchs-Prompt aus einer Prompt-Datei als messbaren Headless-Lauf aus (claude -p) und schreibt ein Messprotokoll mit Start-/Endzeit, Modell, Tokenverbrauch und weiteren Metriken. Verwenden bei "/run-experiment <Pfad-zur-Prompt-Datei>" oder wenn der User einen Versuch/ein Experiment ausführen und tracken will.
|
description: Führt einen Versuchs-Prompt aus einer Prompt-Datei als messbaren Headless-Lauf mit Claude Code oder Codex CLI aus und schreibt ein Messprotokoll mit Start-/Endzeit, Modell, Tokenverbrauch und weiteren Metriken. Verwenden bei "/run-experiment <Pfad-zur-Prompt-Datei>" oder wenn der User einen Versuch/ein Experiment ausführen und tracken will.
|
||||||
argument-hint: <Pfad zur Prompt-Datei> <Root-Verzeichnis>
|
argument-hint: <Pfad zur Prompt-Datei> <Root-Verzeichnis>
|
||||||
version: 4.5.0
|
version: 5.0.0
|
||||||
---
|
---
|
||||||
|
|
||||||
# RunExperiment – Versuchslauf mit Messprotokoll
|
# RunExperiment – Versuchslauf mit Messprotokoll
|
||||||
|
|
||||||
Du führst einen wissenschaftlichen Versuchslauf für die Masterarbeit aus. Der Prompt aus der
|
Du führst einen wissenschaftlichen Versuchslauf für die Masterarbeit aus. Der Prompt aus der
|
||||||
übergebenen Datei wird als **separater Headless-Lauf** von Claude Code ausgeführt, damit
|
übergebenen Datei wird als **separater Headless-Lauf** über den zum Modell passenden
|
||||||
Tokenverbrauch und Modell exakt und maschinenlesbar erfasst werden. Anschließend
|
Werkzeugadapter ausgeführt, damit Tokenverbrauch und Modell maschinenlesbar erfasst werden. Anschließend
|
||||||
schreibst du ein Messprotokoll neben die Prompt-Datei.
|
schreibst du ein Messprotokoll neben die Prompt-Datei.
|
||||||
|
|
||||||
## Parameter
|
## Parameter
|
||||||
@@ -39,7 +39,8 @@ Laufverzeichnis neben der Prompt-Datei**, niemals im Root-Verzeichnis:
|
|||||||
<Iteration>\<ModellID>\<Agentenmodus>\<Effort>\
|
<Iteration>\<ModellID>\<Agentenmodus>\<Effort>\
|
||||||
<NN>_Lauf_<yyyy-MM-dd_HHmmss>_v<skillversion>-<id4>\
|
<NN>_Lauf_<yyyy-MM-dd_HHmmss>_v<skillversion>-<id4>\
|
||||||
Protokoll.md
|
Protokoll.md
|
||||||
RawResult.json
|
RawResult.json (adapterübergreifend normalisierte Messdaten)
|
||||||
|
RawEvents.jsonl (nur Codex: unveränderter JSONL-Ereignisstrom)
|
||||||
Stderr.log
|
Stderr.log
|
||||||
_meta\ (Laufsteuerung: combined_prompt.md, before.txt, after.txt, Zeitstempel,
|
_meta\ (Laufsteuerung: combined_prompt.md, before.txt, after.txt, Zeitstempel,
|
||||||
subagenten.md/.json, anforderungen.md/.json)
|
subagenten.md/.json, anforderungen.md/.json)
|
||||||
@@ -99,7 +100,8 @@ Der Skill ist zweigeteilt:
|
|||||||
- **`## Prozess`** beschreibt werkzeugneutral, **was** je Lauf zu tun und zu erheben ist. Dieser
|
- **`## Prozess`** beschreibt werkzeugneutral, **was** je Lauf zu tun und zu erheben ist. Dieser
|
||||||
Teil gilt unabhängig davon, mit welchem LLM oder welcher CLI gearbeitet wird.
|
Teil gilt unabhängig davon, mit welchem LLM oder welcher CLI gearbeitet wird.
|
||||||
- **`## Werkzeugadapter`** beschreibt **wie** das mit einem konkreten Werkzeug umgesetzt wird.
|
- **`## Werkzeugadapter`** beschreibt **wie** das mit einem konkreten Werkzeug umgesetzt wird.
|
||||||
Derzeit ist nur der Adapter für Claude Code ausgearbeitet.
|
Ausgearbeitet sind Adapter für Claude Code und Codex CLI. Konkrete Flags und Rohfelder sind
|
||||||
|
ausschließlich dem gewählten Adapterabschnitt zu entnehmen.
|
||||||
|
|
||||||
**Arbeitsteilung mit der Prompt-Datei:** Der Prompt enthält ausschließlich die *Analyseanweisung*
|
**Arbeitsteilung mit der Prompt-Datei:** Der Prompt enthält ausschließlich die *Analyseanweisung*
|
||||||
und ist damit von jedem LLM verwendbar. Alles Werkzeug- und Ablaufbezogene – verfügbare
|
und ist damit von jedem LLM verwendbar. Alles Werkzeug- und Ablaufbezogene – verfügbare
|
||||||
@@ -120,7 +122,11 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
|
|||||||
Prompt-Versionen bleiben unverändert liegen – sie sind der Beleg dafür, unter welcher Fassung
|
Prompt-Versionen bleiben unverändert liegen – sie sind der Beleg dafür, unter welcher Fassung
|
||||||
frühere Läufe entstanden sind, und dürfen nicht nachträglich angepasst werden.
|
frühere Läufe entstanden sind, und dürfen nicht nachträglich angepasst werden.
|
||||||
2. Aus dem Metadaten-Block der Prompt-Datei (falls vorhanden) Versuch/Prompt-Version übernehmen.
|
2. Aus dem Metadaten-Block der Prompt-Datei (falls vorhanden) Versuch/Prompt-Version übernehmen.
|
||||||
3. **CLI-Pfad auflösen.** Unter Windows liegt `claude` in der Regel **nicht im PATH**. Erst
|
3. **Werkzeugadapter bestimmen und CLI-Pfad auflösen.** Die Modell-ID entscheidet eindeutig:
|
||||||
|
`claude-*` verwendet Claude Code, OpenAI-IDs wie `gpt-*` oder `o*` verwenden Codex CLI.
|
||||||
|
Keine Modell-ID an eine CLI übergeben, die sie nicht unterstützt.
|
||||||
|
|
||||||
|
Unter Windows liegt `claude` in der Regel **nicht im PATH**. Erst
|
||||||
`Get-Command claude -ErrorAction SilentlyContinue` versuchen; schlägt das fehl, auf die
|
`Get-Command claude -ErrorAction SilentlyContinue` versuchen; schlägt das fehl, auf die
|
||||||
VSCode-Extension zurückfallen und die höchste Versionsnummer wählen:
|
VSCode-Extension zurückfallen und die höchste Versionsnummer wählen:
|
||||||
```powershell
|
```powershell
|
||||||
@@ -130,13 +136,17 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
|
|||||||
Sort-Object FullName | Select-Object -Last 1 -ExpandProperty FullName
|
Sort-Object FullName | Select-Object -Last 1 -ExpandProperty FullName
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Findet sich keine ausführbare Datei: abbrechen und den User informieren. Den aufgelösten
|
Für OpenAI-Modelle entsprechend `Get-Command codex -ErrorAction SilentlyContinue` verwenden;
|
||||||
Pfad fürs Protokoll festhalten.
|
als Fallback die höchste VS-Code-Extension unter
|
||||||
|
`$env:USERPROFILE\.vscode\extensions\openai.chatgpt-*-win32-x64\bin\windows-x86_64\codex.exe`
|
||||||
|
wählen. Findet sich die benötigte ausführbare Datei nicht: abbrechen und den User informieren.
|
||||||
|
Adapter und aufgelösten Pfad fürs Protokoll festhalten.
|
||||||
4. **Modell beim User erfragen.** Hat der User das Modell nicht bereits im Aufruf genannt,
|
4. **Modell beim User erfragen.** Hat der User das Modell nicht bereits im Aufruf genannt,
|
||||||
**immer** per `AskUserQuestion` nachfragen – auch dann, wenn frühere Läufe derselben
|
**immer** per `AskUserQuestion` nachfragen – auch dann, wenn frühere Läufe derselben
|
||||||
Versuchsreihe ein bestimmtes Modell verwendet haben. Es gibt bewusst keinen Default.
|
Versuchsreihe ein bestimmtes Modell verwendet haben. Es gibt bewusst keinen Default.
|
||||||
|
|
||||||
Als Optionen die vollen Modell-IDs anbieten, nicht die Kurzformen:
|
Als Optionen die vollen Modell-IDs anbieten, nicht die Kurzformen. Nur aktuell dokumentierte
|
||||||
|
und von der installierten CLI angebotene IDs aufnehmen:
|
||||||
|
|
||||||
| Modell-ID | Einordnung |
|
| Modell-ID | Einordnung |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -145,6 +155,12 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
|
|||||||
| `claude-fable-5` | schnell und günstig |
|
| `claude-fable-5` | schnell und günstig |
|
||||||
| `claude-haiku-4-5-20251001` | günstigstes Modell |
|
| `claude-haiku-4-5-20251001` | günstigstes Modell |
|
||||||
|
|
||||||
|
| OpenAI-Modell-ID | Einordnung |
|
||||||
|
|---|---|
|
||||||
|
| `gpt-5.6-sol` | stärkstes Modell der GPT-5.6-Familie, 1.050.000 Kontexttokens |
|
||||||
|
| `gpt-5.6-terra` | ausgewogenes Verhältnis aus Leistung und Verbrauch |
|
||||||
|
| `gpt-5.6-luna` | schnelle und günstige GPT-5.6-Variante |
|
||||||
|
|
||||||
In der Frage den letzten verwendeten Stand nennen, damit der User bewusst wechseln oder
|
In der Frage den letzten verwendeten Stand nennen, damit der User bewusst wechseln oder
|
||||||
bewusst wiederholen kann (z. B. „Lauf 3 lief mit `claude-sonnet-5`, 11.516.200 Tokens, 23:40").
|
bewusst wiederholen kann (z. B. „Lauf 3 lief mit `claude-sonnet-5`, 11.516.200 Tokens, 23:40").
|
||||||
|
|
||||||
@@ -153,6 +169,11 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
|
|||||||
Versuchsbedingung nicht reproduzierbar. Für ein 1-Mio.-Token-Fenster die Variante mit
|
Versuchsbedingung nicht reproduzierbar. Für ein 1-Mio.-Token-Fenster die Variante mit
|
||||||
`[1m]`-Suffix wählen (z. B. `claude-opus-5[1m]`).
|
`[1m]`-Suffix wählen (z. B. `claude-opus-5[1m]`).
|
||||||
|
|
||||||
|
Für OpenAI ebenfalls keine Familienaliase wie `gpt-5.6` verwenden. Die gewünschte Variante
|
||||||
|
vollständig binden, beispielsweise `gpt-5.6-sol`. Vor einem Lauf die Modell-ID gegen die
|
||||||
|
aktuelle offizielle OpenAI-Modelldokumentation und, soweit verfügbar, den lokalen
|
||||||
|
`codex debug models`-Katalog prüfen.
|
||||||
|
|
||||||
Weicht das gewählte Modell vom letzten Lauf ab, im Protokoll unter „Anmerkungen" als
|
Weicht das gewählte Modell vom letzten Lauf ab, im Protokoll unter „Anmerkungen" als
|
||||||
geänderte Versuchsbedingung vermerken und in der Änderungshistorie des Skills ergänzen.
|
geänderte Versuchsbedingung vermerken und in der Änderungshistorie des Skills ergänzen.
|
||||||
5. **Agentenmodus beim User erfragen.** Wie beim Modell: kein Default, immer nachfragen, wenn
|
5. **Agentenmodus beim User erfragen.** Wie beim Modell: kein Default, immer nachfragen, wenn
|
||||||
@@ -194,23 +215,29 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
|
|||||||
der Prompt per SHA-256 gehasht und im Protokoll geführt. So bleibt der eingefrorene Snapshot
|
der Prompt per SHA-256 gehasht und im Protokoll geführt. So bleibt der eingefrorene Snapshot
|
||||||
unangetastet und die Agentenkonfiguration ist eine dokumentierte, versionierte
|
unangetastet und die Agentenkonfiguration ist eine dokumentierte, versionierte
|
||||||
Versuchsbedingung. Format siehe `claude --help` zu `--agents`.
|
Versuchsbedingung. Format siehe `claude --help` zu `--agents`.
|
||||||
|
|
||||||
|
**Codex-Adapter in Version 5.0.0:** ausschließlich `solo` ist freigegeben und wird doppelt
|
||||||
|
mit `--disable multi_agent` sowie `-c agents.enabled=false` erzwungen. Bei `builtin` oder
|
||||||
|
`custom` abbrechen und mitteilen, dass dieser Adaptermodus noch nicht verifiziert ist. Eine
|
||||||
|
stillschweigende Annäherung an Claude-Agentendefinitionen wäre keine reproduzierbare Bedingung.
|
||||||
6. **Effort beim User erfragen.** Kein Default, immer nachfragen, wenn der User die Stufe nicht
|
6. **Effort beim User erfragen.** Kein Default, immer nachfragen, wenn der User die Stufe nicht
|
||||||
bereits im Aufruf genannt hat. Stufen: `low`, `medium`, `high`, `xhigh`, `max`.
|
bereits im Aufruf genannt hat. Stufen: `low`, `medium`, `high`, `xhigh`, `max`.
|
||||||
Übergabe per `--effort <stufe>`.
|
Übergabe bei Claude per `--effort <stufe>`, bei Codex per
|
||||||
|
`-c model_reasoning_effort=\"<stufe>\"`.
|
||||||
|
|
||||||
In der Frage den bisherigen Stand nennen. **Alle Läufe bis einschließlich Lauf B liefen auf
|
In der Frage den bisherigen Stand nennen. **Alle Läufe bis einschließlich Lauf B liefen auf
|
||||||
`high`** – geerbt aus der Sitzungseinstellung, nicht bewusst gesetzt; der Wert wurde
|
`high`** – geerbt aus der Sitzungseinstellung, nicht bewusst gesetzt; der Wert wurde
|
||||||
nachträglich aus den Transkripten belegt. Wer die Stufe wechselt, ändert die
|
nachträglich aus den Transkripten belegt. Wer die Stufe wechselt, ändert die
|
||||||
Versuchsbedingung und macht den Lauf mit den bisherigen unvergleichbar.
|
Versuchsbedingung und macht den Lauf mit den bisherigen unvergleichbar.
|
||||||
|
|
||||||
**Wichtig – der Effort ist aus `RawResult.json` nicht rekonstruierbar.** Das Ergebnisobjekt
|
**Claude-spezifisch:** Der Effort ist aus dessen `RawResult.json` nicht rekonstruierbar. Das Ergebnisobjekt
|
||||||
enthält kein `effort`-Feld. Nur das Session-Transkript hält ihn je Nachricht fest
|
enthält kein `effort`-Feld. Nur das Session-Transkript hält ihn je Nachricht fest
|
||||||
(`"effort": "high"`). Fehlt er im Protokoll, ist die Bedingung nachträglich allein über das
|
(`"effort": "high"`). Fehlt er im Protokoll, ist die Bedingung nachträglich allein über das
|
||||||
Transkript belegbar – und das nur, solange die Session persistiert ist.
|
Transkript belegbar – und das nur, solange die Session persistiert ist.
|
||||||
7. Messgrößen VOR dem Lauf erfassen (PowerShell):
|
7. Messgrößen VOR dem Lauf erfassen (PowerShell):
|
||||||
- Startzeit: `Get-Date -Format o`
|
- Startzeit: `Get-Date -Format o`
|
||||||
- SHA-256 der Prompt-Datei: `(Get-FileHash <datei> -Algorithm SHA256).Hash`
|
- SHA-256 der Prompt-Datei: `(Get-FileHash <datei> -Algorithm SHA256).Hash`
|
||||||
- Claude-Code-Version: `& $claude --version`
|
- Adapter und CLI-Version: `& $cli --version`
|
||||||
- Git-Zustand des Root-Verzeichnisses: `git -C <root> rev-parse HEAD` und
|
- Git-Zustand des Root-Verzeichnisses: `git -C <root> rev-parse HEAD` und
|
||||||
`git -C <repo> status --porcelain -- <pfad-des-roots>` (dirty ja/nein).
|
`git -C <repo> status --porcelain -- <pfad-des-roots>` (dirty ja/nein).
|
||||||
**Immer pfadskopiert prüfen.** Liegt die Codebasis als Unterverzeichnis im Arbeitsrepo
|
**Immer pfadskopiert prüfen.** Liegt die Codebasis als Unterverzeichnis im Arbeitsrepo
|
||||||
@@ -228,7 +255,7 @@ per stdin übergebenen Text angehängt (siehe Schritt „Prompt zusammenstellen"
|
|||||||
Sort-Object { [int]($_.Name -replace '\D','') }
|
Sort-Object { [int]($_.Name -replace '\D','') }
|
||||||
$iteration = if ($iterationen) { $iterationen[-1].Name } else { 'Iteration 1' }
|
$iteration = if ($iterationen) { $iterationen[-1].Name } else { 'Iteration 1' }
|
||||||
$zelle = Join-Path (Join-Path (Join-Path $iteration $modell) $modus) $effort
|
$zelle = Join-Path (Join-Path (Join-Path $iteration $modell) $modus) $effort
|
||||||
$skillVer = 'v4.5.0' # entspricht version: im Frontmatter dieses Skills
|
$skillVer = 'v5.0.0' # entspricht version: im Frontmatter dieses Skills
|
||||||
do {
|
do {
|
||||||
$id4 = '{0:x4}' -f (Get-Random -Maximum 65536)
|
$id4 = '{0:x4}' -f (Get-Random -Maximum 65536)
|
||||||
$lauf = Join-Path "<promptverzeichnis>\$zelle" "<NN>_Lauf_$(Get-Date -Format 'yyyy-MM-dd_HHmmss')_${skillVer}-$id4"
|
$lauf = Join-Path "<promptverzeichnis>\$zelle" "<NN>_Lauf_$(Get-Date -Format 'yyyy-MM-dd_HHmmss')_${skillVer}-$id4"
|
||||||
@@ -320,9 +347,26 @@ Schreibe ALLE zu erzeugenden Ergebnisdateien in das Verzeichnis
|
|||||||
Verändere keine Dateien im Arbeitsverzeichnis (der analysierten Codebasis).
|
Verändere keine Dateien im Arbeitsverzeichnis (der analysierten Codebasis).
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Codex-Abweichung bei Block 2.** Der Codex-Adapter läuft mit einem technisch erzwungenen
|
||||||
|
`read-only`-Sandboxmodus. Er kann deshalb auch das externe Laufverzeichnis nicht direkt als
|
||||||
|
Agent beschreiben. Block 2 wird dort durch folgende strukturierte Rückgabeanweisung ersetzt;
|
||||||
|
die CLI erzwingt `codex-output-schema.json`, anschließend materialisiert
|
||||||
|
`normalise-codex-result.py` die Dateien:
|
||||||
|
|
||||||
|
```
|
||||||
|
### Ergebnisausgabe (überschreibt anderslautende Pfadangaben oben)
|
||||||
|
Verändere keine Dateien. Gib alle geforderten Ergebnisdateien im vorgegebenen JSON-Schema zurück.
|
||||||
|
Jeder Eintrag in `files` enthält unter `path` einen relativen Pfad innerhalb von `Ergebnisse`
|
||||||
|
und unter `content` den vollständigen Dateiinhalt. `summary` enthält nur eine kurze Laufzusammenfassung.
|
||||||
|
```
|
||||||
|
|
||||||
### 3. Ausführung (Headless-Lauf)
|
### 3. Ausführung (Headless-Lauf)
|
||||||
|
|
||||||
Dann den kombinierten Prompt per stdin an `claude -p` übergeben. `--add-dir` gibt dem
|
Den Adapter anhand der Modell-ID wählen. Der folgende Aufruf ist **ausschließlich der
|
||||||
|
Claude-Code-Adapter**. Der Codex-Aufruf steht vollständig unter `Adapter: Codex CLI` und darf
|
||||||
|
nicht mit Claude-Flags vermischt werden.
|
||||||
|
|
||||||
|
Den kombinierten Prompt per stdin an `claude -p` übergeben. `--add-dir` gibt dem
|
||||||
Headless-Lauf Schreibrecht auf das Laufverzeichnis außerhalb seines Arbeitsverzeichnisses.
|
Headless-Lauf Schreibrecht auf das Laufverzeichnis außerhalb seines Arbeitsverzeichnisses.
|
||||||
Versuchsläufe können lange dauern – **immer als Background-Task starten** (`run_in_background`),
|
Versuchsläufe können lange dauern – **immer als Background-Task starten** (`run_in_background`),
|
||||||
nicht mit Foreground-Timeout arbeiten:
|
nicht mit Foreground-Timeout arbeiten:
|
||||||
@@ -426,8 +470,9 @@ Regeln:
|
|||||||
|
|
||||||
### 4. Ergebnis auswerten
|
### 4. Ergebnis auswerten
|
||||||
|
|
||||||
`RawResult.json` im Laufverzeichnis lesen und defensiv parsen (Feldnamen können je nach Claude-Code-Version
|
`RawResult.json` im Laufverzeichnis lesen und defensiv parsen. Beim Claude-Adapter ist dies die
|
||||||
leicht abweichen). Relevante Felder:
|
unveränderte CLI-Antwort; beim Codex-Adapter erzeugt `normalise-codex-result.py` diese Datei aus
|
||||||
|
`RawEvents.jsonl` und `_meta\final_response.json`. Relevante Claude-Felder:
|
||||||
|
|
||||||
| Feld | Bedeutung |
|
| Feld | Bedeutung |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -541,12 +586,12 @@ Vorgabe delegiert – gegenüber den vorformulierten Agenten aus `--agents`.
|
|||||||
**Anforderungen auswerten** – der inhaltliche Ertrag des Laufs, nicht nur sein Aufwand:
|
**Anforderungen auswerten** – der inhaltliche Ertrag des Laufs, nicht nur sein Aufwand:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
python "<skillverzeichnis>nalyse-anforderungen.py" "<laufverzeichnis>"
|
python "<skillverzeichnis>\analyse-anforderungen.py" "<laufverzeichnis>"
|
||||||
```
|
```
|
||||||
|
|
||||||
Das Skript parst das im Prompt vorgegebene Blockformat (`ID:`, `Typ:`, `Belege:`, `Status:` …)
|
Das Skript parst das im Prompt vorgegebene Blockformat (`ID:`, `Typ:`, `Belege:`, `Status:` …)
|
||||||
aus `Ergebnisse\StRS.md`, `SyRS.md` und `SwRS.md` und schreibt `_metanforderungen.md`
|
aus `Ergebnisse\StRS.md`, `SyRS.md` und `SwRS.md` und schreibt `_meta\anforderungen.md`
|
||||||
(fertiger Protokollabschnitt) sowie `_metanforderungen.json` (maschinenlesbar). Erhoben werden:
|
(fertiger Protokollabschnitt) sowie `_meta\anforderungen.json` (maschinenlesbar). Erhoben werden:
|
||||||
|
|
||||||
- **Verteilung** über die drei Ebenen
|
- **Verteilung** über die drei Ebenen
|
||||||
- **Anforderungstypen** (funktional, Sicherheit, Daten, Schnittstelle …)
|
- **Anforderungstypen** (funktional, Sicherheit, Daten, Schnittstelle …)
|
||||||
@@ -588,7 +633,7 @@ Der erzeugte Abschnitt wird **unverändert** als `## Gefundene Anforderungen` in
|
|||||||
Erzeugte Dateien: Inhalt von `<laufverzeichnis>\Ergebnisse\` auflisten. Zusätzlich prüfen,
|
Erzeugte Dateien: Inhalt von `<laufverzeichnis>\Ergebnisse\` auflisten. Zusätzlich prüfen,
|
||||||
ob das Root unverändert blieb: `git -C <repo> status --porcelain -- <pfad-des-roots>` – mit
|
ob das Root unverändert blieb: `git -C <repo> status --porcelain -- <pfad-des-roots>` – mit
|
||||||
**demselben Pfadfilter wie in Abschnitt 1** – gegen `before.txt`
|
**demselben Pfadfilter wie in Abschnitt 1** – gegen `before.txt`
|
||||||
vergleichen (Nachher-Stand nach `$lauf\_metafter.txt`) – Abweichungen als Auffälligkeit
|
vergleichen (Nachher-Stand nach `$lauf\_meta\after.txt`) – Abweichungen als Auffälligkeit
|
||||||
ins Protokoll. Beim Vergleich Zeilenenden
|
ins Protokoll. Beim Vergleich Zeilenenden
|
||||||
normalisieren (`before.txt` entsteht je nach Werkzeug mit CRLF, der Nachher-Stand mit LF),
|
normalisieren (`before.txt` entsteht je nach Werkzeug mit CRLF, der Nachher-Stand mit LF),
|
||||||
sonst meldet ein naiver `diff` alle Zeilen als geändert.
|
sonst meldet ein naiver `diff` alle Zeilen als geändert.
|
||||||
@@ -616,11 +661,14 @@ Vorlage:
|
|||||||
|
|
||||||
## Werkzeugkonfiguration
|
## Werkzeugkonfiguration
|
||||||
- **Skill-Version:** <version aus dem Frontmatter dieses Skills>
|
- **Skill-Version:** <version aus dem Frontmatter dieses Skills>
|
||||||
- **Claude-Code-Version:** <version>
|
- **Werkzeugadapter:** <Claude Code | Codex CLI>
|
||||||
- **CLI-Pfad:** <aufgelöster Pfad zur claude.exe>
|
- **CLI-Version:** <version>
|
||||||
|
- **CLI-Pfad:** <aufgelöster Pfad zur ausführbaren Datei>
|
||||||
- **Modell (angefordert):** <Wert von `--model`>
|
- **Modell (angefordert):** <Wert von `--model`>
|
||||||
- **Modelle (tatsächlich eingesetzt):** <alle Schlüssel aus `modelUsage` mit Tokenanteil>
|
- **Modelle (tatsächlich eingesetzt):** <alle Schlüssel aus `modelUsage` mit Tokenanteil |
|
||||||
- **Kontrolle Modell:** <bestanden | **verletzt**: nicht angefordertes Modell <ID> mit <N> Tokens>
|
nicht erfasst: Codex-JSONL nennt das tatsächlich bediente Modell nicht>
|
||||||
|
- **Kontrolle Modell:** <bestanden | **verletzt**: nicht angefordertes Modell <ID> mit <N> Tokens |
|
||||||
|
nicht prüfbar: Codex-JSONL enthält keine tatsächliche Modell-ID>
|
||||||
- **Effort:** <low | medium | high | xhigh | max> (per `--effort` gesetzt; Gegenprobe im
|
- **Effort:** <low | medium | high | xhigh | max> (per `--effort` gesetzt; Gegenprobe im
|
||||||
Transkript-Feld `effort`)
|
Transkript-Feld `effort`)
|
||||||
- **Laufverzeichnis-ID:** `v<skillversion>-<id4>` aus dem Verzeichnisnamen
|
- **Laufverzeichnis-ID:** `v<skillversion>-<id4>` aus dem Verzeichnisnamen
|
||||||
@@ -632,9 +680,9 @@ Vorlage:
|
|||||||
- **Sampling-Parameter:** <Temperatur u. a. | nicht steuerbar>
|
- **Sampling-Parameter:** <Temperatur u. a. | nicht steuerbar>
|
||||||
- **Nur bei lokalem Modellbetrieb:** Inferenz-Runtime samt Version, Quantisierungsstufe des
|
- **Nur bei lokalem Modellbetrieb:** Inferenz-Runtime samt Version, Quantisierungsstufe des
|
||||||
Modell-Builds
|
Modell-Builds
|
||||||
- **Permission-Mode:** <acceptEdits | ...>
|
- **Permission-/Sandbox-Modus:** <acceptEdits | read-only / approval never | ...>
|
||||||
- **Toolfreigabe:** `--allowedTools <wörtlich>` / `--disallowedTools <wörtlich>`
|
- **Toolfreigabe:** <adapterabhängige Flags wörtlich>
|
||||||
- **Isolationsmechanismus:** <--safe-mode, --strict-mcp-config, ggf. --setting-sources>
|
- **Isolationsmechanismus:** <adapterabhängige Flags wörtlich>
|
||||||
- **MCP-Server / Agentendateien:** <keine – aus dem Snapshot entfernt, zusätzlich --safe-mode | Liste>
|
- **MCP-Server / Agentendateien:** <keine – aus dem Snapshot entfernt, zusätzlich --safe-mode | Liste>
|
||||||
- **Subagenten:** <Anzahl und Typ aus `subagent_stats`, z. B. 8 × Explore, 0 fehlgeschlagen>
|
- **Subagenten:** <Anzahl und Typ aus `subagent_stats`, z. B. 8 × Explore, 0 fehlgeschlagen>
|
||||||
- **Verschachtelung:** `spawned` = <N>, davon `spawned_by_subagents` = <M>, `max_depth` = <D>.
|
- **Verschachtelung:** `spawned` = <N>, davon `spawned_by_subagents` = <M>, `max_depth` = <D>.
|
||||||
@@ -676,9 +724,13 @@ Das ist die **berichtete Aufwandsgröße** der Versuchsreihe. `total_cost_usd` b
|
|||||||
`RawResult.json` erhalten, wird aber nicht ins Protokoll übernommen: Token sind modell- und
|
`RawResult.json` erhalten, wird aber nicht ins Protokoll übernommen: Token sind modell- und
|
||||||
preisunabhängig und bleiben damit über Preisänderungen und Modellwechsel hinweg vergleichbar.
|
preisunabhängig und bleiben damit über Preisänderungen und Modellwechsel hinweg vergleichbar.
|
||||||
|
|
||||||
|
**Codex-Semantik:** `cached_input_tokens` ist eine Teilmenge von `input_tokens` und wird nicht
|
||||||
|
erneut addiert. Dort gilt `Tokens gesamt = input_tokens + output_tokens`; Reasoning-Tokens sind
|
||||||
|
eine Teilmenge der Output-Tokens. Cache-Write-Tokens werden von `codex exec --json` nicht geliefert.
|
||||||
|
|
||||||
## Gefundene Anforderungen
|
## Gefundene Anforderungen
|
||||||
|
|
||||||
<unverändert aus `_metanforderungen.md` übernehmen – Verteilung, Typen, Belegqualität,
|
<unverändert aus `_meta\anforderungen.md` übernehmen – Verteilung, Typen, Belegqualität,
|
||||||
Status, Regelkonformität>
|
Status, Regelkonformität>
|
||||||
|
|
||||||
## Ergebnis
|
## Ergebnis
|
||||||
@@ -709,8 +761,9 @@ Werkzeug umgesetzt wird und welche Messgrößen dieses Werkzeug liefert.
|
|||||||
|
|
||||||
### Adapter: Claude Code
|
### Adapter: Claude Code
|
||||||
|
|
||||||
Der einzige derzeit ausgearbeitete Adapter. Alle konkreten Aufrufe, Flags und Feldnamen im
|
Alle mit `claude -p`, `--safe-mode`, `--allowedTools`, `modelUsage` und `subagent_stats`
|
||||||
Prozessteil beziehen sich auf ihn; er wurde gegen **CLI 2.1.245** entwickelt und verifiziert.
|
bezeichneten Aufrufe und Felder im Prozessteil gehören zu diesem Adapter. Er wurde gegen
|
||||||
|
**CLI 2.1.245** entwickelt und verifiziert.
|
||||||
|
|
||||||
**Gelieferte Messgrößen** – vollständig aus `RawResult.json`:
|
**Gelieferte Messgrößen** – vollständig aus `RawResult.json`:
|
||||||
|
|
||||||
@@ -729,10 +782,114 @@ Prozessteil beziehen sich auf ihn; er wurde gegen **CLI 2.1.245** entwickelt und
|
|||||||
werden nicht ausgewiesen; der Effort steht **nicht** in `RawResult.json` und ist nur über das
|
werden nicht ausgewiesen; der Effort steht **nicht** in `RawResult.json` und ist nur über das
|
||||||
Session-Transkript belegbar (siehe Schritt 6 der Vorbereitung).
|
Session-Transkript belegbar (siehe Schritt 6 der Vorbereitung).
|
||||||
|
|
||||||
|
### Adapter: Codex CLI / OpenAI-Modelle
|
||||||
|
|
||||||
|
Dieser Adapter wurde für den reproduzierbaren Headless-Betrieb mit `codex exec` und exakten
|
||||||
|
OpenAI-Modell-IDs entworfen. Referenzstand bei Einführung: **Codex CLI 0.149.0-alpha.4.3**.
|
||||||
|
Vor jedem Lauf `codex --version` protokollieren; bei geändertem JSONL-Schema den Normalisierer
|
||||||
|
zuerst mit einem kleinen, ausdrücklich freigegebenen Smoke-Test prüfen.
|
||||||
|
|
||||||
|
**Unterstützter Agentenmodus:** In Version 5.0.0 nur `solo`. `builtin` und `custom` müssen
|
||||||
|
abbrechen. `solo` wird mit zwei unabhängigen Einstellungen erzwungen:
|
||||||
|
`--disable multi_agent` und `-c agents.enabled=false`.
|
||||||
|
|
||||||
|
**Isolation und Ausgabe.** Codex arbeitet im Root mit `--sandbox read-only`. Anders als beim
|
||||||
|
Claude-Adapter erhält der Agent deshalb kein beschreibbares Zusatzverzeichnis. Er liefert ein
|
||||||
|
Schemaobjekt mit vollständigen Dateiinhalten; `--output-last-message` schreibt dieses durch die
|
||||||
|
CLI nach `_meta\final_response.json`. Erst nach Ende des Agenten materialisiert das lokale,
|
||||||
|
deterministische Skript die validierten relativen Pfade unter `Ergebnisse\`. Absolute Pfade,
|
||||||
|
`..`, Laufwerkspräfixe und case-insensitive Duplikate werden abgelehnt.
|
||||||
|
|
||||||
|
Vor dem Start `$codex`, `$skillDir`, `$lauf`, `$root`, `$modell` und `$effort` auf absolute
|
||||||
|
Pfade beziehungsweise die bestätigten Versuchsbedingungen setzen. Den Prompt mit dem
|
||||||
|
Codex-Ausgabeblock aus Abschnitt 2 nach `_meta\combined_prompt.md` schreiben. Der eigentliche
|
||||||
|
Aufruf lautet:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$codexArgs = @(
|
||||||
|
'--model', $modell,
|
||||||
|
'-c', "model_reasoning_effort=`"$effort`"",
|
||||||
|
'-c', 'service_tier="default"',
|
||||||
|
'--sandbox', 'read-only',
|
||||||
|
'--ask-for-approval', 'never',
|
||||||
|
'--disable', 'multi_agent',
|
||||||
|
'-c', 'agents.enabled=false',
|
||||||
|
'--disable', 'plugins',
|
||||||
|
'--disable', 'apps',
|
||||||
|
'--disable', 'hooks',
|
||||||
|
'--disable', 'skill_search',
|
||||||
|
'--cd', $root,
|
||||||
|
'exec',
|
||||||
|
'--ignore-user-config',
|
||||||
|
'--ignore-rules',
|
||||||
|
'--strict-config',
|
||||||
|
'--json',
|
||||||
|
'--output-schema', (Join-Path $skillDir 'codex-output-schema.json'),
|
||||||
|
'--output-last-message', (Join-Path $lauf '_meta\final_response.json'),
|
||||||
|
'-'
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-Content -Path "$lauf\_meta\startzeit.txt" -Value (Get-Date -Format o)
|
||||||
|
Get-Content "$lauf\_meta\combined_prompt.md" -Raw |
|
||||||
|
& $codex @codexArgs 2> "$lauf\Stderr.log" |
|
||||||
|
Set-Content "$lauf\RawEvents.jsonl" -Encoding utf8
|
||||||
|
$codexExit = $LASTEXITCODE
|
||||||
|
Set-Content -Path "$lauf\_meta\exitcode.txt" -Value $codexExit
|
||||||
|
Set-Content -Path "$lauf\_meta\endzeit.txt" -Value (Get-Date -Format o)
|
||||||
|
|
||||||
|
python (Join-Path $skillDir 'normalise-codex-result.py') $lauf `
|
||||||
|
--model $modell --effort $effort
|
||||||
|
```
|
||||||
|
|
||||||
|
Auch dieser Aufruf ist als Background-Task zu starten. Das Erscheinen von `RawEvents.jsonl`
|
||||||
|
allein bedeutet noch nicht, dass der Lauf fertig ist; Ende ist der abgeschlossene Prozess plus
|
||||||
|
erzeugte `RawResult.json`. Schlägt die Normalisierung fehl, Rohdateien unverändert lassen und
|
||||||
|
den Lauf als Fehler protokollieren.
|
||||||
|
|
||||||
|
**Warum diese Flags:**
|
||||||
|
|
||||||
|
| Flag / Einstellung | Zweck |
|
||||||
|
|---|---|
|
||||||
|
| `--model <id>` | vollständige, vom User bestätigte OpenAI-Modell-ID |
|
||||||
|
| `model_reasoning_effort` | expliziter Denkaufwand |
|
||||||
|
| `service_tier="default"` | verhindert eine geerbte Fast-/Priority-Bedingung |
|
||||||
|
| `--sandbox read-only` | technische Schreibsperre für den Codebasis-Snapshot |
|
||||||
|
| `--ask-for-approval never` | keine interaktiven Unterbrechungen im Headless-Lauf |
|
||||||
|
| `--ignore-user-config`, `--ignore-rules` | keine User-Konfiguration und keine Exec-Regeln |
|
||||||
|
| `--disable plugins/apps/hooks/skill_search` | keine externen oder benutzerspezifischen Erweiterungen |
|
||||||
|
| `--disable multi_agent`, `agents.enabled=false` | keine Subagenten im Modus `solo` |
|
||||||
|
| `--json` | vollständiger maschinenlesbarer Ereignisstrom |
|
||||||
|
| `--output-schema`, `--output-last-message` | validierbare Ergebnisdateien ohne Schreibrecht des Agenten |
|
||||||
|
|
||||||
|
`--ignore-user-config` lässt die gespeicherte Authentifizierung weiterhin nutzbar; niemals
|
||||||
|
`auth.json` kopieren oder in Laufartefakten ablegen. Live-Websuche ist nicht freigegeben, weil
|
||||||
|
`--search` fehlt. Der bereinigte Snapshot bleibt zusätzlich Pflicht, da projektlokale
|
||||||
|
Anweisungsdateien eine eigene Versuchsbedingung wären.
|
||||||
|
|
||||||
|
**Gelieferte Messgrößen** aus `RawEvents.jsonl`, normalisiert nach `RawResult.json`:
|
||||||
|
|
||||||
|
| Messgröße | Quelle |
|
||||||
|
|---|---|
|
||||||
|
| Abbruchstatus | Prozess-Exitcode, `turn.failed`, `error`, fehlerhafte JSONL-Zeilen |
|
||||||
|
| Wanduhrdauer | `_meta\startzeit.txt` bis `_meta\endzeit.txt` |
|
||||||
|
| Tokens gesamt | `turn.completed.usage.input_tokens + output_tokens` |
|
||||||
|
| Cache-Read-Tokens | `turn.completed.usage.cached_input_tokens`, Teilmenge der Input-Tokens |
|
||||||
|
| Reasoning-Tokens | `turn.completed.usage.reasoning_output_tokens`, Teilmenge der Output-Tokens |
|
||||||
|
| Agent-Turns | Anzahl `turn.completed` |
|
||||||
|
| Sitzungskennung | `thread.started.thread_id` |
|
||||||
|
| Werkzeugaktivität | Anzahl abgeschlossener `item.*` je Item-Typ |
|
||||||
|
| Erzeugte Artefakte | validierte Einträge aus `_meta\final_response.json` |
|
||||||
|
|
||||||
|
**Nicht erfasst und niemals schätzen:** reine API-Dauer, Cache-Write-Tokens, einzelne
|
||||||
|
Permission-Denials und die tatsächlich serverseitig bediente Modell-ID. `--model` dokumentiert
|
||||||
|
die angeforderte ID; da `codex exec --json` sie im Ereignisstrom nicht wiederholt, lautet die
|
||||||
|
Modellkontrolle im Protokoll `nicht prüfbar`. Temperatur und weitere Sampling-Parameter sind in
|
||||||
|
diesem CLI-Ablauf nicht steuerbar; Effort und Service-Tier werden dagegen explizit festgelegt.
|
||||||
|
|
||||||
### Weitere Adapter (für Versuch 4 nachzurüsten)
|
### Weitere Adapter (für Versuch 4 nachzurüsten)
|
||||||
|
|
||||||
Kapitel 4 der Arbeit sieht einen LLM-Querschnitt über Codex CLI, Qwen Code CLI über LM Studio
|
Kapitel 4 der Arbeit sieht zusätzlich Qwen Code CLI über LM Studio und DeepSeek über die
|
||||||
und DeepSeek über die Cloud-API vor. Diese Adapter sind noch nicht ausgearbeitet. Damit ein
|
Cloud-API vor. Diese Adapter sind noch nicht ausgearbeitet. Damit ein
|
||||||
Lauf als Messpunkt taugt, muss ein Adapter mindestens liefern:
|
Lauf als Messpunkt taugt, muss ein Adapter mindestens liefern:
|
||||||
|
|
||||||
| Pflichtangabe | Zweck |
|
| Pflichtangabe | Zweck |
|
||||||
@@ -789,6 +946,7 @@ der Historie unten – im selben Arbeitsschritt.
|
|||||||
|
|
||||||
| Version | Änderung | Grund | Verwendet in |
|
| Version | Änderung | Grund | Verwendet in |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|
| **5.0.0** | **Neuer Codex-CLI-Adapter für OpenAI-Modelle.** Exakte OpenAI-Modell-IDs, expliziter Reasoning-Effort und Service-Tier; technisch read-only ausgeführtes `codex exec`; schemaerzwungene Dateirückgabe; JSONL-Rohdaten und deterministische Normalisierung nach `RawResult.json`. Codex ist zunächst nur im Modus `solo` freigegeben. Zusätzlich fünf beschädigte Backslashes vor `analyse-anforderungen.py`, `anforderungen.*` und `after.txt` repariert. | Der bisherige Skill konnte nur Claude Code ausführen. Ein Codex-Lauf braucht andere Isolations-, Ausgabe- und Messmechanismen. MAJOR, weil Werkzeug, Rohdatenformat und Ergebnisübergabe neue Versuchsbedingungen bilden; der nächste Lauf eröffnet deshalb eine neue Iteration. | ab dem ersten Codex-Lauf |
|
||||||
| **1.0.0** | Ausgangsfassung: `--permission-mode acceptEdits`, kein Shell-Zugriff; Isolation durch Löschen der KI-Konfigurationsdateien im Root vor dem Lauf und `git restore` danach | Erstaufsetzung des Versuchsaufbaus | Lauf 1 (`01_Lauf_2026-08-25_1228`) |
|
| **1.0.0** | Ausgangsfassung: `--permission-mode acceptEdits`, kein Shell-Zugriff; Isolation durch Löschen der KI-Konfigurationsdateien im Root vor dem Lauf und `git restore` danach | Erstaufsetzung des Versuchsaufbaus | Lauf 1 (`01_Lauf_2026-08-25_1228`) |
|
||||||
| **2.0.0** | `--allowedTools "Bash" "PowerShell"` + 33er-Denylist für schreibende und bauende Kommandos. Isolation über **eingefrorenen Codebasis-Snapshot ohne KI-Konfigurationen** (Commit `79c1142`, Parent `89ccfd6`, GitHub-Remote entkoppelt), zusätzlich `--safe-mode` und `--strict-mcp-config`. Der destruktive Lösch-/Restore-Schritt pro Lauf entfällt. `permission_denials` und `subagent_stats` werden reguläre Messgrößen; Verbrauchstabelle trennt Hauptagent und Gesamtlauf. CLI-Pfad-Auflösung als eigener Schritt. | Lauf 1 erzeugte 36 Permission-Denials (32 × Bash, 4 × PowerShell); Shell-gestützte Verzeichnisinventuren fehlten dem Agenten. `--safe-mode` allein genügt nicht: Es unterdrückt das Vorladen von `CLAUDE.md`/`AGENTS.md`, verhindert aber nicht, dass der Agent sie mit Shell-Zugriff selbst liest – im Smoke-Test nachgewiesen. | Lauf 2 (`01_Lauf_2026-08-25_1349`, API-Abbruch) |
|
| **2.0.0** | `--allowedTools "Bash" "PowerShell"` + 33er-Denylist für schreibende und bauende Kommandos. Isolation über **eingefrorenen Codebasis-Snapshot ohne KI-Konfigurationen** (Commit `79c1142`, Parent `89ccfd6`, GitHub-Remote entkoppelt), zusätzlich `--safe-mode` und `--strict-mcp-config`. Der destruktive Lösch-/Restore-Schritt pro Lauf entfällt. `permission_denials` und `subagent_stats` werden reguläre Messgrößen; Verbrauchstabelle trennt Hauptagent und Gesamtlauf. CLI-Pfad-Auflösung als eigener Schritt. | Lauf 1 erzeugte 36 Permission-Denials (32 × Bash, 4 × PowerShell); Shell-gestützte Verzeichnisinventuren fehlten dem Agenten. `--safe-mode` allein genügt nicht: Es unterdrückt das Vorladen von `CLAUDE.md`/`AGENTS.md`, verhindert aber nicht, dass der Agent sie mit Shell-Zugriff selbst liest – im Smoke-Test nachgewiesen. | Lauf 2 (`01_Lauf_2026-08-25_1349`, API-Abbruch) |
|
||||||
| **2.0.1** | `before.txt` wird leerwertsicher geschrieben (`Set-Content -Value (… \| Out-String)` statt Pipeline) | Bei sauberem Root schreibt `Set-Content` aus leerer Pipeline die Datei nicht und lässt alten Inhalt stehen; der Vorher/Nachher-Vergleich meldete dadurch eine Abweichung, die es nicht gab | Lauf 3 (`01_Lauf_2026-08-25_1429`) |
|
| **2.0.1** | `before.txt` wird leerwertsicher geschrieben (`Set-Content -Value (… \| Out-String)` statt Pipeline) | Bei sauberem Root schreibt `Set-Content` aus leerer Pipeline die Datei nicht und lässt alten Inhalt stehen; der Vorher/Nachher-Vergleich meldete dadurch eine Abweichung, die es nicht gab | Lauf 3 (`01_Lauf_2026-08-25_1429`) |
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"files": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"path": { "type": "string", "minLength": 1 },
|
||||||
|
"content": { "type": "string" }
|
||||||
|
},
|
||||||
|
"required": ["path", "content"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": { "type": "string" }
|
||||||
|
},
|
||||||
|
"required": ["files", "summary"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Materialize Codex result files and normalize its JSONL measurements."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from collections import Counter
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path, PurePosixPath
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("run_directory", type=Path)
|
||||||
|
parser.add_argument("--model", required=True)
|
||||||
|
parser.add_argument("--effort", required=True)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def read_jsonl(path: Path) -> tuple[list[dict[str, Any]], list[str]]:
|
||||||
|
events: list[dict[str, Any]] = []
|
||||||
|
malformed: list[str] = []
|
||||||
|
for number, line in enumerate(path.read_text(encoding="utf-8-sig").splitlines(), 1):
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
value = json.loads(line)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
malformed.append(f"line {number}: {exc}")
|
||||||
|
continue
|
||||||
|
if isinstance(value, dict):
|
||||||
|
events.append(value)
|
||||||
|
else:
|
||||||
|
malformed.append(f"line {number}: JSON value is not an object")
|
||||||
|
return events, malformed
|
||||||
|
|
||||||
|
|
||||||
|
def safe_result_path(results_dir: Path, raw_path: str) -> Path:
|
||||||
|
relative = PurePosixPath(raw_path.replace("\\", "/"))
|
||||||
|
if relative.is_absolute() or not relative.parts or ".." in relative.parts:
|
||||||
|
raise ValueError(f"unsafe result path: {raw_path!r}")
|
||||||
|
if any(part in ("", ".") or ":" in part for part in relative.parts):
|
||||||
|
raise ValueError(f"invalid result path: {raw_path!r}")
|
||||||
|
target = results_dir.joinpath(*relative.parts).resolve()
|
||||||
|
root = results_dir.resolve()
|
||||||
|
if root != target and root not in target.parents:
|
||||||
|
raise ValueError(f"result path escapes Ergebnisse: {raw_path!r}")
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
def parse_iso(path: Path) -> datetime | None:
|
||||||
|
if not path.exists():
|
||||||
|
return None
|
||||||
|
value = path.read_text(encoding="utf-8-sig").strip()
|
||||||
|
try:
|
||||||
|
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
run_dir = args.run_directory.resolve()
|
||||||
|
meta_dir = run_dir / "_meta"
|
||||||
|
results_dir = run_dir / "Ergebnisse"
|
||||||
|
events, malformed = read_jsonl(run_dir / "RawEvents.jsonl")
|
||||||
|
envelope = json.loads((meta_dir / "final_response.json").read_text(encoding="utf-8-sig"))
|
||||||
|
if not isinstance(envelope, dict) or not isinstance(envelope.get("files"), list):
|
||||||
|
raise ValueError("final_response.json does not match the Codex output envelope")
|
||||||
|
|
||||||
|
results_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
seen: set[str] = set()
|
||||||
|
materialized: list[str] = []
|
||||||
|
for entry in envelope["files"]:
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
raise ValueError("file entry is not an object")
|
||||||
|
raw_path = entry.get("path")
|
||||||
|
content = entry.get("content")
|
||||||
|
if not isinstance(raw_path, str) or not isinstance(content, str):
|
||||||
|
raise ValueError("file entry requires string path and content")
|
||||||
|
target = safe_result_path(results_dir, raw_path)
|
||||||
|
key = str(target).casefold()
|
||||||
|
if key in seen:
|
||||||
|
raise ValueError(f"duplicate result path: {raw_path!r}")
|
||||||
|
seen.add(key)
|
||||||
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
target.write_text(content, encoding="utf-8", newline="\n")
|
||||||
|
materialized.append(str(target.relative_to(results_dir)).replace("\\", "/"))
|
||||||
|
|
||||||
|
usage = Counter()
|
||||||
|
item_types = Counter()
|
||||||
|
errors: list[Any] = list(malformed)
|
||||||
|
thread_id = None
|
||||||
|
turns = 0
|
||||||
|
for event in events:
|
||||||
|
event_type = event.get("type")
|
||||||
|
if event_type == "thread.started":
|
||||||
|
thread_id = event.get("thread_id")
|
||||||
|
elif event_type == "turn.completed":
|
||||||
|
turns += 1
|
||||||
|
turn_usage = event.get("usage") or {}
|
||||||
|
for field in ("input_tokens", "cached_input_tokens", "output_tokens", "reasoning_output_tokens"):
|
||||||
|
value = turn_usage.get(field, 0)
|
||||||
|
if isinstance(value, int):
|
||||||
|
usage[field] += value
|
||||||
|
elif event_type in ("turn.failed", "error"):
|
||||||
|
errors.append(event)
|
||||||
|
if event_type == "item.completed":
|
||||||
|
item = event.get("item") or {}
|
||||||
|
if isinstance(item.get("type"), str):
|
||||||
|
item_types[item["type"]] += 1
|
||||||
|
|
||||||
|
start = parse_iso(meta_dir / "startzeit.txt")
|
||||||
|
end = parse_iso(meta_dir / "endzeit.txt")
|
||||||
|
duration_ms = round((end - start).total_seconds() * 1000) if start and end else None
|
||||||
|
|
||||||
|
exit_code = None
|
||||||
|
exit_code_path = meta_dir / "exitcode.txt"
|
||||||
|
if exit_code_path.exists():
|
||||||
|
try:
|
||||||
|
exit_code = int(exit_code_path.read_text(encoding="utf-8-sig").strip())
|
||||||
|
except ValueError:
|
||||||
|
errors.append("invalid exitcode.txt")
|
||||||
|
if exit_code not in (None, 0):
|
||||||
|
errors.append({"exit_code": exit_code})
|
||||||
|
|
||||||
|
normalized = {
|
||||||
|
"adapter": "codex-cli",
|
||||||
|
"is_error": bool(errors),
|
||||||
|
"subtype": "success" if not errors else "error",
|
||||||
|
"session_id": thread_id,
|
||||||
|
"duration_ms": duration_ms,
|
||||||
|
"duration_api_ms": None,
|
||||||
|
"num_turns": turns,
|
||||||
|
"requested_model": args.model,
|
||||||
|
"actual_models": None,
|
||||||
|
"model_control": "not_verifiable_from_codex_exec_jsonl",
|
||||||
|
"effort": args.effort,
|
||||||
|
"usage": {
|
||||||
|
"input_tokens": usage["input_tokens"],
|
||||||
|
"cached_input_tokens": usage["cached_input_tokens"],
|
||||||
|
"output_tokens": usage["output_tokens"],
|
||||||
|
"reasoning_output_tokens": usage["reasoning_output_tokens"],
|
||||||
|
"total_tokens": usage["input_tokens"] + usage["output_tokens"],
|
||||||
|
"semantics": "cached_input_tokens is a subset of input_tokens and is not added again",
|
||||||
|
},
|
||||||
|
"item_counts": dict(sorted(item_types.items())),
|
||||||
|
"permission_denials": None,
|
||||||
|
"subagent_stats": {"spawned": 0, "source": "multi-agent disabled by configuration"},
|
||||||
|
"materialized_files": materialized,
|
||||||
|
"result": envelope.get("summary", ""),
|
||||||
|
"errors": errors,
|
||||||
|
}
|
||||||
|
(run_dir / "RawResult.json").write_text(
|
||||||
|
json.dumps(normalized, ensure_ascii=False, indent=2) + "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
newline="\n",
|
||||||
|
)
|
||||||
|
return 1 if errors else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user