Automatischer Push: 11 Datei(en) geändert
- .claude/agents/planer.md - .claude/workflows/plankritik.js - .claude/workflows/quorum.js - .claude/workflows/welle-mit-pruefung.js - .vault-tools/plan-kopie/LIESMICH.md - .vault-tools/plan-kopie/plan_bloecke.py - .vault-tools/plan-kopie/plan_materialisiere.py - .vault-tools/plan-kopie/plan_zaun_treiber.py - .vault-tools/workflow-pruefung/LIESMICH.md - .vault-tools/workflow-pruefung/faelle_workflows.mjs - .vault-tools/workflow-pruefung/mutanten_workflows.py Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
7f484f9be5
commit
22761eb186
11 files changed
+1520
-65
No files matched your search
@@ -18,6 +18,7 @@ Du denkst, du tippst nicht. Dein Ergebnis ist ein Plan, den ein Executer ohne R
|
||||
- **Jede Taskdatei trägt im Frontmatter `modell:` und `modell_begruendung:`.** `title`, `summary` und `modell_begruendung` stehen in Anführungszeichen — ein `: ` im Text macht den Block sonst zu ungültigem YAML (Blöcke und Regel: `AI/Regelwerk/frontmatter.md`). Das Kriterium ist eine einzige Frage: Braucht der Executer für diesen Task Ermessen, oder steht alles wörtlich im Plan? Steht der Code wörtlich da, sind die Zieldateien namentlich genannt und die Verbote ausdrücklich, dann ist die Rolle Abschrift und `haiku` genügt, auch wenn ein Codeblock typografische Zeichen wie „ “ – — … enthält (Haiku 5.5, Pascals Entscheidung vom 2026-10-09; die Ausnahme für Haiku 4.5 steht in `AI/Regelwerk/codeworkflow.md`, Schritt 3). Bleibt Ermessen offen, ist es Fehlersuche, oder muss Fließtext formuliert statt abgeschrieben werden, dann `sonnet`.
|
||||
- **Erst lesen, dann planen.** Bestehende Struktur, Namensgebung, Testkonventionen und Abhängigkeiten des Zielprojekts lesen. Der Plan passt sich der vorhandenen Umgebung an, nicht umgekehrt. Nennt der Auftrag eine Leseliste mit Dateien und Zeilenbereichen, liest du sie zuerst. Sie ist nie abschließend: Jede Datei, deren Pfad, Bezeichner oder Inhalt du in den Plan schreibst, hast du selbst gelesen. Grund: Planer ohne eigenes Lesen schrieben Pfade aus dem Gedächtnis (Pascal, 2026-10-30).
|
||||
- **Erwartete pytest-Fehlerzeilen nur mit breiter Ausgabe.** Ohne Terminal kürzt pytest (gemessen an 9.1.1) jede `FAILED`-Zeile der Kurzzusammenfassung auf 80 Zeichen, die Fehlerart ist dann abgeschnitten („Failed: D..."). Nennt der Plan eine erwartete Fehlerzeile wörtlich, steht im Befehl `COLUMNS=300` davor (Git-Bash) bzw. `$env:COLUMNS=300;` (PowerShell).
|
||||
- **Ein Sammelfehler hat in der Kurzzusammenfassung keine Ursache.** Scheitert der Import einer Testdatei, schreibt pytest 9.1.1 dort nur `ERROR tests/test_x.py`, ohne ` - ModuleNotFoundError: …`; die Ursache steht nur im Block `ERRORS` als `E ModuleNotFoundError: No module named '…'`. Ein Rot-Nachweis über ein fehlendes Modul nennt deshalb beide Zeilen getrennt. Zweimal falsch vorhergesagt (2026-10-20 BookStack Kundendoku, 2026-11-02 Testfeld Proxmox), beide Male hätte ein wörtlich arbeitender Executer abgebrochen.
|
||||
- **Rot vor der Umsetzung bei neuen Modulen** (Pascal, 2026-10-26). Testet ein Task ein Modul, eine Datei oder ein Skript, das es vor dem Task nicht gibt, darf der Test vor der Umsetzung am Import (`ModuleNotFoundError`), an der fehlenden Datei oder an einem Gerüst-Stub (`NichtBereit`) scheitern. Die erwartete Fehlerzeile nennt genau diese erste Zeile. Dass die Zusicherungen greifen, belegen dann die Mutanten: Jeder neue Verhaltenszweig, auch jeder Fehler- und Sperrzweig, bekommt einen Mutanten mit `rot_bei`. Bei einem bestehenden Modul bleibt Rot an einer Zusicherung Pflicht.
|
||||
- **Doku-Tasks verankern nie auf Plandaten** (Pascal, 2026-10-26). `Aus:`-Blöcke in `CHANGELOG.md`, `README.md`, Anleitung und Codedoku verankern an einem Titel oder einer stabilen Abschnittsüberschrift (`# Änderungen`, `## Stand`), nie an einem Versionskopf, einer `updated:`-Zeile oder Text eines anderen Pakets. Ein neuer Versionskopf trägt „Datum bei Freigabe“. Kein Planblock ändert `updated:`; das zieht der Koordinator am Umsetzungstag nach. Beleg: UI-876 und KN-883 brachen in der Nacht zum 2026-10-26 dreimal, sobald ein Vorgängerpaket korrigiert wurde.
|
||||
- **Mutanten stehen in einer Datei, nicht als Prosa.** Verlangt der Auftrag Mutanten, schreibst du sie als `mutanten.toml` in den Planordner, Format 1 nach `D:\Claude\.vault-tools\plan-kopie\mutanten_lauf_ENTWURF.md`, Abschnitt 2; lies den Abschnitt, bevor du die Datei schreibst. Die Plankritik fährt die Datei mit `mutanten_lauf.py` auf ihrer Kopie. In „Fertig heißt“ steht danach nur der Aufruf mit der Erwartung „Exit 0, jeder Mutant `OK`“, keine Zählung. Drei Fallen, alle belegt:
|
||||
|
||||
@@ -26,6 +26,9 @@ export const meta = {
|
||||
// und der Woche.
|
||||
// - Rückgabe mit status fertig oder gebremst; ein gebremster Lauf wird neu gestartet, nicht
|
||||
// fortgesetzt.
|
||||
// - werkzeug_befunde (seit 2026-11-02) listet die Befundzeilen der Planwerkzeuge und die von
|
||||
// plan_materialisiere.py aus materialisieren, je mit dem Werkzeug. Die Plankritik hält dabei
|
||||
// nicht an; die Linsen laufen wie sonst.
|
||||
//
|
||||
// Rollenparameter: model wird immer gesetzt (Stamm). Der Effort der pruefer kommt aus dem
|
||||
// Frontmatter von .claude/agents/pruefer.md; args.effort überschreibt ihn nur bewusst.
|
||||
@@ -45,6 +48,36 @@ const VERBRAUCH = 'D:/Claude/.claude/workflows/verbrauch.js'
|
||||
const RANG = { blockierend: 0, wichtig: 1, klein: 2 }
|
||||
// Einfache Anführungszeichen: In Bash bleiben darin $, Backtick und Backslash wörtlich.
|
||||
const q = s => "'" + String(s).replace(/'/g, "'\\''") + "'"
|
||||
// Befundzeile der Planwerkzeuge: "BEFUND <datei>:<zeile>:", bei plan_zeichen "<datei>:<zeile>:<spalte>:".
|
||||
// kopie_bauen.py schreibt "BEFUND Projekt ..." ohne Zeilennummer; die Form trennt beide.
|
||||
const BEFUNDZEILE = /^BEFUND [^\s:]+:\d+:/
|
||||
const befundZeilen = text => String(text || '').split('\n').map(z => z.replace(/\r$/, '')).filter(z => BEFUNDZEILE.test(z))
|
||||
// Ein Hilfsagent darf seine Ausgabe auf mehrere laeufe verteilen: Im Echtlauf wf_9ab7fd8e-ad1
|
||||
// (2026-11-02) gab werkzeug:planwerkzeuge je Werkzeug einen Lauf zurück, und laeufe[0] allein
|
||||
// enthielt plan_bloecke nicht. Jede Ausgabe eines Hilfsagenten wird deshalb über alle laeufe gelesen,
|
||||
// in ihrer Reihenfolge und mit Zeilenumbruch verbunden: Befund- und EXIT-Zeilen, KOPIE-Zeile und
|
||||
// angewendete Blöcke, Mutantenlauf.
|
||||
const ausgabeAller = r => (r && Array.isArray(r.laeufe) ? r.laeufe : []).map(l => String((l && l.ausgabe) || '')).join('\n')
|
||||
// Exit über alle laeufe, für die Kopie aus materialisieren: Ihr Befehl ist eine Kette mit &&, sie
|
||||
// endet nur mit 0, wenn jedes Glied mit 0 endet. Verwendbar ist die Kopie deshalb nur, wenn jeder Lauf
|
||||
// Exit 0 hat; ein Lauf ohne Exit zählt nicht als 0 und steht im Log als undefined. Planwerkzeuge und
|
||||
// Mutantenlauf lesen stattdessen die EXIT-Zeilen, die ihr Befehl selbst schreibt.
|
||||
const exitsAller = r => (r && Array.isArray(r.laeufe) ? r.laeufe : []).map(l => l && l.exit)
|
||||
const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0)
|
||||
|
||||
// Stand eines Planwerkzeugs aus seiner EXIT-Zeile und dem Teil der Ausgabe, der zu ihm gehört.
|
||||
// Gelaufen ist es mit Exit 0, mit Exit 2 und mindestens einer Befundzeile, plan_bloecke auch mit
|
||||
// Exit 3 und "Nichts geprueft:" (kein Aus-/wird-Block). Exit 2 ohne Befundzeile ist ein
|
||||
// gescheitertes Werkzeug: python endet auch bei "can't open file" mit 2 (2026-09-24 ging so ein
|
||||
// Errno 2 als Rohausgabe an die Linse).
|
||||
function werkzeugStand(aus, name, abschnitt) {
|
||||
const m = new RegExp('EXIT ' + name + ': (\\d+)').exec(aus)
|
||||
const befunde = befundZeilen(abschnitt)
|
||||
if (!m) return { name, gelaufen: false, grund: 'ohne EXIT-Zeile', befunde }
|
||||
const exit = Number(m[1])
|
||||
const gelaufen = exit === 0 || (exit === 2 && befunde.length > 0) || (name === 'plan_bloecke' && exit === 3 && /^Nichts geprueft:/m.test(abschnitt))
|
||||
return { name, gelaufen, grund: 'Exit ' + exit + (exit === 2 && !befunde.length ? ' ohne Befundzeile' : ''), befunde }
|
||||
}
|
||||
|
||||
// Text für die Linse arithmetik, wenn der Mutantenlauf (werkzeug:mutanten) etwas geliefert hat.
|
||||
// Seit der Nachbesserung 2026-09-28 (N5, N7): modus: muster in der Rohausgabe heißt, dass kein
|
||||
@@ -231,19 +264,29 @@ Gilt der Befund nur unter einer Bedingung, nenn sie. Gib deine eigene Einschätz
|
||||
pruefeArgs(args)
|
||||
const W = args.werkzeuge || 'D:/Claude/.vault-tools/plan-kopie'
|
||||
const PY = 'PYTHONIOENCODING=utf-8 python -X utf8'
|
||||
const ctx = { kopie: args.kopie || null, werkzeuge: null, temp: null, mutantenLauf: null, mutantenStatus: null }
|
||||
const ctx = { kopie: args.kopie || null, werkzeuge: null, temp: null, mutantenLauf: null, mutantenStatus: null, befunde: [] }
|
||||
const MUTANTEN_MODUS = args.mutanten || 'voll'
|
||||
const vorbereitung = []
|
||||
|
||||
// Eine Kopie gilt nur als verwendbar, wenn der Lauf sauber endet, den Pfad bis zum
|
||||
// Zeilenende nennt (Pfade mit Leerzeichen) und das Planwerkzeug mindestens einen Block
|
||||
// angewendet hat. Sonst rechnet Linse 1 am Text, und das steht im Log.
|
||||
// Eine Kopie gilt nur als verwendbar, wenn jeder Lauf sauber endet (alleExitNull), die Ausgabe
|
||||
// den Pfad bis zum Zeilenende nennt (Pfade mit Leerzeichen) und das Planwerkzeug mindestens einen
|
||||
// Block angewendet hat. Sonst rechnet Linse 1 am Text, und das steht im Log. Meldet
|
||||
// plan_materialisiere.py einen Befund, ist das kein gescheitertes Werkzeug: Die Zeilen gehen
|
||||
// nach werkzeug_befunde, und der Temp-Ordner wird genannt.
|
||||
function kopieAusLauf(r, wozu) {
|
||||
const lauf = r && r.laeufe && r.laeufe[0]
|
||||
if (!lauf) { log(`${wozu}: Hilfsagent ohne Ergebnis, keine Kopie`); return null }
|
||||
const m = /^KOPIE=(.+?)\s*$/m.exec(lauf.ausgabe)
|
||||
const angewendet = /'(neu|ersetzt|angehaengt)': [1-9]/.test(lauf.ausgabe)
|
||||
if (lauf.exit !== 0 || !m) { log(`${wozu}: Kopie nicht verwendbar (Exit ${lauf.exit}${m ? '' : ', kein KOPIE-Pfad'})`); return null }
|
||||
const exits = exitsAller(r)
|
||||
if (!exits.length) { log(`${wozu}: Hilfsagent ohne Ergebnis, keine Kopie`); return null }
|
||||
const aus = ausgabeAller(r)
|
||||
const m = /^KOPIE=(.+?)\s*$/m.exec(aus)
|
||||
const angewendet = /'(neu|ersetzt|angehaengt)': [1-9]/.test(aus)
|
||||
const befunde = befundZeilen(aus)
|
||||
if (befunde.length) {
|
||||
befunde.forEach(befund => ctx.befunde.push({ werkzeug: 'plan_materialisiere', befund }))
|
||||
if (m) ctx.temp = m[1].replace(/\/mat$/, '')
|
||||
log(`${wozu}: plan_materialisiere.py meldet ${befunde.length} Befund(e) (Exit ${exits.map(String).join('/')}), keine Kopie; die Zeilen stehen unter werkzeug_befunde`)
|
||||
return null
|
||||
}
|
||||
if (!alleExitNull(r) || !m) { log(`${wozu}: Kopie nicht verwendbar (Exit ${exits.map(String).join('/')}${m ? '' : ', kein KOPIE-Pfad'})`); return null }
|
||||
ctx.temp = m[1].replace(/\/mat$/, '')
|
||||
if (!angewendet) { log(`${wozu}: das Planwerkzeug hat keinen Block angewendet; die Kopie ist der unveränderte Stand und wird nicht verwendet`); return null }
|
||||
return m[1]
|
||||
@@ -268,13 +311,22 @@ if (!ctx.kopie && args.materialisieren) {
|
||||
{
|
||||
const befehl = `${PY} ${q(W + '/plan_zeichen.py')} ${q(args.planordner)}; echo "EXIT plan_zeichen: $?"; ls ${q(args.planordner)}/task-*.md >/dev/null 2>&1 && { ${PY} ${q(W + '/plan_bloecke.py')} vor ${q(args.planordner)}/task-*.md --projekt ${q(args.projekt)}; echo "EXIT plan_bloecke: $?"; } || echo "plan_bloecke: keine task-*.md im Planordner, nicht gefahren"`
|
||||
const r = await werkzeugAgent('werkzeug:planwerkzeuge', 'Vorbereitung', befehl, 'Codeblöcke auf unsichtbare Zeichen und Aus-Blöcke gegen den Baum prüfen')
|
||||
// Nur wenn beide Werkzeuge mit 0 endeten, gilt die Ausgabe als gelaufen (2026-09-24: ein Protokollsatz statt
|
||||
// Pfad in args.werkzeuge endete mit Errno 2 und ging trotzdem als Rohausgabe an die Linse). Der Exit des
|
||||
// ganzen Befehls hilft dabei nicht, er ist der des letzten echo; gelesen werden die EXIT-Zeilen.
|
||||
const aus = r && r.laeufe && r.laeufe[0] ? String(r.laeufe[0].ausgabe || '') : ''
|
||||
const gelaufen = /EXIT plan_zeichen: 0(?!\d)/.test(aus) && (/EXIT plan_bloecke: 0(?!\d)/.test(aus) || /plan_bloecke: keine task-/.test(aus))
|
||||
if (gelaufen) ctx.werkzeuge = aus
|
||||
else log('Planwerkzeuge: Hilfsagent ohne Ergebnis; Linse bezeichner fährt sie selbst')
|
||||
// Die Ausgabe geht an die Linse, wenn beide Werkzeuge gelaufen sind, auch mit Befund (werkzeugStand oben).
|
||||
// Der Exit des ganzen Befehls hilft dabei nicht, er ist der des letzten echo; gelesen werden die
|
||||
// EXIT-Zeilen. Was vor "EXIT plan_zeichen:" steht, gehört zu plan_zeichen, der Rest zu plan_bloecke.
|
||||
const lauf = r && r.laeufe && r.laeufe[0]
|
||||
if (!lauf) log('Planwerkzeuge: Hilfsagent ohne Ergebnis; Linse bezeichner fährt sie selbst')
|
||||
else {
|
||||
const aus = ausgabeAller(r)
|
||||
const grenze = aus.indexOf('EXIT plan_zeichen:')
|
||||
const zeichen = werkzeugStand(aus, 'plan_zeichen', grenze >= 0 ? aus.slice(0, grenze) : aus)
|
||||
const bloecke = /plan_bloecke: keine task-/.test(aus)
|
||||
? { name: 'plan_bloecke', gelaufen: true, befunde: [] }
|
||||
: werkzeugStand(aus, 'plan_bloecke', grenze >= 0 ? aus.slice(grenze) : '')
|
||||
for (const w of [zeichen, bloecke]) w.befunde.forEach(befund => ctx.befunde.push({ werkzeug: w.name, befund }))
|
||||
if (zeichen.gelaufen && bloecke.gelaufen) ctx.werkzeuge = aus
|
||||
else log('Planwerkzeuge: ' + [zeichen, bloecke].filter(w => !w.gelaufen).map(w => w.name + ' ' + w.grund).join(', ') + '; Linse bezeichner fährt sie selbst')
|
||||
}
|
||||
vorbereitung.push(r)
|
||||
}
|
||||
// Mutantenlauf (seit 2026-09-27). mutanten_lauf.py ändert Dateien in der Wurzel, die es bekommt,
|
||||
@@ -298,9 +350,13 @@ else if (!ctx.kopie) ctx.mutantenStatus = 'ohne_kopie'
|
||||
if (ctx.kopie && MUTANTEN_MODUS !== 'aus') {
|
||||
const befehl = `MSYS_NO_PATHCONV=1 wsl -d Debian -- bash /mnt/d/Claude/.vault-tools/plan-kopie/mutanten_lauf_kopie.sh "$(cygpath -m -l ${q(ctx.kopie)})" "$(cygpath -m -l ${q(args.planordner)})" ${MUTANTEN_MODUS === 'muster' ? 'muster' : 'voll'}`
|
||||
const r = await werkzeugAgent('werkzeug:mutanten', 'Vorbereitung', befehl, 'Mutanten aus mutanten.toml auf einer Kopie der Kopie fahren', 'Setze im Bash-Werkzeug den Parameter timeout auf 600000; der Befehl darf bis zu zehn Minuten laufen.')
|
||||
const aus = r && r.laeufe && r.laeufe[0] ? String(r.laeufe[0].ausgabe || '') : ''
|
||||
// Alle laeufe, etwa Anfang und Ende einer gekürzten Ausgabe: Die EXIT-Zeile steht am Schluss
|
||||
// (mutanten_lauf_kopie.sh schreibt höchstens eine); bei mehreren gilt wie bisher die erste.
|
||||
// KEINE_MUTANTEN_TOML zählt nur als ganze Zeile, wie das Skript sie schreibt: Ein zusätzlicher Lauf,
|
||||
// der etwa den Skripttext zeigt, machte sonst aus einem gefahrenen Lauf keine_toml.
|
||||
const aus = ausgabeAller(r)
|
||||
const exitMatch = /EXIT mutanten_lauf: (\d+)/.exec(aus)
|
||||
if (/KEINE_MUTANTEN_TOML/.test(aus)) { ctx.mutantenLauf = { keineToml: true, rohausgabe: aus }; ctx.mutantenStatus = 'keine_toml' }
|
||||
if (/^KEINE_MUTANTEN_TOML\s*$/m.test(aus)) { ctx.mutantenLauf = { keineToml: true, rohausgabe: aus }; ctx.mutantenStatus = 'keine_toml' }
|
||||
else if (exitMatch) { ctx.mutantenLauf = { keineToml: false, rohausgabe: aus }; ctx.mutantenStatus = 'exit_' + exitMatch[1] }
|
||||
else { log('Mutantenlauf: Hilfsagent ohne verwertbares Ergebnis (kein KEINE_MUTANTEN_TOML, kein EXIT mutanten_lauf); Linsen laufen ohne Mutantenlauf weiter'); ctx.mutantenStatus = 'ohne_ergebnis' }
|
||||
vorbereitung.push(r)
|
||||
@@ -324,6 +380,7 @@ if (vorLinsen.gebremst) {
|
||||
selbst_ausgefuehrt: [],
|
||||
nicht_pruefbar: [],
|
||||
vorbereitung,
|
||||
werkzeug_befunde: ctx.befunde,
|
||||
ohne_ergebnis: gewaehlt,
|
||||
verbrauch: ablesungen,
|
||||
plaetze: { linsen: 0, widerlegung: 0 },
|
||||
@@ -388,6 +445,7 @@ return {
|
||||
selbst_ausgefuehrt: ergebnisse.map(e => ({ linse: e.linse, befehle: e.ergebnis.selbst_ausgefuehrt })),
|
||||
nicht_pruefbar: ergebnisse.flatMap(e => e.ergebnis.nicht_pruefbar.map(x => e.linse + ': ' + x)),
|
||||
vorbereitung,
|
||||
werkzeug_befunde: ctx.befunde,
|
||||
ohne_ergebnis: ohneErgebnis,
|
||||
verbrauch: ablesungen,
|
||||
plaetze: { linsen: vorLinsen.plaetze, widerlegung: plaetzeWiderlegung },
|
||||
|
||||
@@ -165,8 +165,16 @@ ${zusatz ? '\n' + zusatz + '\n' : ''}
|
||||
{ label, phase, schema: LAUF, model: 'haiku', effort: 'low' })
|
||||
}
|
||||
|
||||
// Ausgabe aller laeufe in ihrer Reihenfolge, mit Zeilenumbruch verbunden (seit 2026-11-02, wie
|
||||
// ausgabeAller in plankritik.js): Ein Hilfsagent darf die Ausgabe eines Befehls auf mehrere laeufe
|
||||
// verteilen (Echtlauf wf_9ab7fd8e-ad1). Mit laeufe[0] allein fehlte dann etwa "EXIT paket: 0" oder
|
||||
// "EXIT task: 0", und ein gestarteter Codex-Auftrag lief ungesehen weiter. Bewertet werden nur die
|
||||
// EXIT-Zeilen, die jeder Befehl selbst schreibt, nie das Feld exit.
|
||||
function ausgabe(r) {
|
||||
return r && r.laeufe && r.laeufe[0] ? String(r.laeufe[0].ausgabe || '') : ''
|
||||
return laufTexte(r).join('\n')
|
||||
}
|
||||
function laufTexte(r) {
|
||||
return (r && Array.isArray(r.laeufe) ? r.laeufe : []).map(l => String((l && l.ausgabe) || ''))
|
||||
}
|
||||
|
||||
function wurzel(a) {
|
||||
@@ -291,20 +299,45 @@ async function codexStimme(a, p) {
|
||||
return z
|
||||
}
|
||||
const P = q(wurzel(a))
|
||||
const start = ausgabe(await werkzeugAgent('codex:start', 'Stimmen',
|
||||
const startAntwort = await werkzeugAgent('codex:start', 'Stimmen',
|
||||
`node ${COMPANION} status --json --cwd ${P}; S=$?; echo "EXIT status: $S"; if [ "$S" -eq 0 ]; then node ${COMPANION} task --background --json --cwd ${P} --prompt-file ${q(p.ordner + '/codex-auftrag.md')}; echo "EXIT task: $?"; fi`,
|
||||
'den Codex-Status vor dem Lauf lesen und die Prüfung nur lesend im Hintergrund starten'))
|
||||
'den Codex-Status vor dem Lauf lesen und die Prüfung nur lesend im Hintergrund starten')
|
||||
const start = ausgabe(startAntwort)
|
||||
// Task beginnt mit jobId, Komma, status: queued (codex-companion.mjs 701-706; JSON: 91-100).
|
||||
// Nur innerhalb eines Laufs: Im Status folgt auf { nie jobId (job-control.mjs 161-181, 213-239;
|
||||
// lib/state.mjs 129-147; codex-companion.mjs 567-578); request hat kein status (604-613).
|
||||
// Einrückung allein belegt keine Herkunft; gleiche ID zählt einmal, die erste Fundstelle gilt.
|
||||
// echo setzt EXIT an den Zeilenanfang; Vorsätze zählen nicht für die Reihenfolgeprüfung.
|
||||
// Freie Umstellung innerhalb eines Laufs oder erfundene/geänderte Inhalte sind nicht abgesichert;
|
||||
// codexUngueltig() weist dann eine fremde Stimme über den Lesebeleg zurück.
|
||||
const taskOK = /EXIT task: 0(?!\d)/.test(start)
|
||||
if (!/EXIT status: 0(?!\d)/.test(start)) {
|
||||
z.grund = 'Status vor dem Lauf nicht gelesen'
|
||||
z.grund = 'Status vor dem Lauf nicht gelesen' + (taskOK ? '; Codex-Auftrag läuft womöglich ungesehen weiter' : '')
|
||||
return z
|
||||
}
|
||||
// Nur hinter "EXIT status:" suchen: status --json listet frühere Jobs derselben Sitzung samt
|
||||
// request.jobId (job-control.mjs 161-181); die erste Fundstelle wäre dann ein alter Job.
|
||||
const job = /"jobId": "([^"]+)"/.exec(start.slice(start.lastIndexOf('EXIT status:')))
|
||||
if (!/EXIT task: 0(?!\d)/.test(start) || !job) {
|
||||
z.grund = 'Codex-Auftrag nicht gestartet'
|
||||
const taskJobs = []
|
||||
let startIndex = 0
|
||||
for (const laufText of laufTexte(startAntwort)) {
|
||||
const laufJobs = [...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/g)]
|
||||
for (const treffer of laufJobs) {
|
||||
treffer.index += startIndex
|
||||
taskJobs.push(treffer)
|
||||
}
|
||||
startIndex += laufText.length + 1
|
||||
}
|
||||
const taskIds = new Set(taskJobs.map(treffer => treffer[1]))
|
||||
const job = taskIds.size === 1 ? taskJobs[0] : null
|
||||
if (!taskOK || !job) {
|
||||
z.grund = taskOK
|
||||
? 'jobId nicht sicher bestimmbar; Codex-Auftrag läuft womöglich ungesehen weiter'
|
||||
: 'Codex-Auftrag nicht gestartet'
|
||||
return z
|
||||
}
|
||||
const statusEnde = start.search(/^EXIT status: 0(?!\d)/m)
|
||||
const taskEnde = start.search(/^EXIT task: 0(?!\d)/m)
|
||||
if (!(statusEnde < job.index && job.index < taskEnde)) {
|
||||
hinweis('Codex-Start: Ausgabe nicht in Befehlsreihenfolge; jobId aus der task-Ausgabe (' + job[1] + ')')
|
||||
}
|
||||
z.job = job[1]
|
||||
const runden = a.codex_runden || CODEX_RUNDEN
|
||||
let fertig = false
|
||||
|
||||
@@ -19,12 +19,35 @@ export const meta = {
|
||||
// Verbrauch: Vor und nach der Prüfung liest dieser Workflow den Planverbrauch über verbrauch.js
|
||||
// ab (nur messen); das Kind pruefung bekommt verbrauch: false, weil ein Kind keinen weiteren
|
||||
// Workflow aufrufen kann.
|
||||
// Planbefund (seit 2026-11-02): Meldet plan_materialisiere.py in der Phase Kopie einen Befund, also
|
||||
// eine Zeile "BEFUND <taskdatei>:<zeile>:" (Exit 2, nach einem Absturz auch 1), startet keine Welle.
|
||||
// Der Lauf endet vor executer-welle mit status plan_befund und den Zeilen in plan_befunde, ohne
|
||||
// Vergleich, Prüfung und Ablesung. Vorher lief die Welle trotzdem, nur ohne Byte-Vergleich. Exit 2
|
||||
// ohne eine solche Zeile ist ein gescheitertes Werkzeug (python endet auch bei "can't open file"
|
||||
// mit 2) und nimmt den alten Weg, ebenso die BEFUND-Zeilen von kopie_bauen.py: Sie tragen keine
|
||||
// Zeilennummer.
|
||||
const MODELLE = ['haiku', 'sonnet', 'opus']
|
||||
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
|
||||
const VERBRAUCH = 'D:/Claude/.claude/workflows/verbrauch.js'
|
||||
// Einfache Anführungszeichen: In Bash bleiben darin $, Backtick und Backslash wörtlich.
|
||||
const q = s => "'" + String(s).replace(/'/g, "'\\''") + "'"
|
||||
const PY = 'PYTHONIOENCODING=utf-8 python -X utf8'
|
||||
// Befundzeile der Planwerkzeuge: "BEFUND <datei>:<zeile>:", bei plan_zeichen "<datei>:<zeile>:<spalte>:".
|
||||
const BEFUNDZEILE = /^BEFUND [^\s:]+:\d+:/
|
||||
const befundZeilen = text => String(text || '').split('\n').map(z => z.replace(/\r$/, '')).filter(z => BEFUNDZEILE.test(z))
|
||||
// Ein Hilfsagent darf seine Ausgabe auf mehrere laeufe verteilen (Echtlauf wf_9ab7fd8e-ad1 am
|
||||
// 2026-11-02: je Werkzeug ein Lauf). Jede Ausgabe eines Hilfsagenten wird deshalb über alle laeufe
|
||||
// gelesen, in ihrer Reihenfolge und mit Zeilenumbruch verbunden: Befundzeilen, KOPIE-Zeile und
|
||||
// angewendete Blöcke der Kopie, die Rohausgabe des Vergleichs. Sonst stünde ein Befund in laeufe[1],
|
||||
// und die Welle liefe trotzdem.
|
||||
const ausgabeAller = r => (r && Array.isArray(r.laeufe) ? r.laeufe : []).map(l => String((l && l.ausgabe) || '')).join('\n')
|
||||
// Exit über alle laeufe: Der Befehl der Kopie ist eine Kette mit &&, sie endet nur mit 0, wenn jedes
|
||||
// Glied mit 0 endet. Verwendbar ist die Kopie deshalb nur, wenn jeder Lauf Exit 0 hat; ein Lauf ohne
|
||||
// Exit zählt nicht als 0 und steht im Log als undefined. Mit laeufe[0] allein galt ein erster Lauf
|
||||
// mit 0 vor einem Absturz im zweiten als fertige Kopie.
|
||||
const exitsAller = r => (r && Array.isArray(r.laeufe) ? r.laeufe : []).map(l => l && l.exit)
|
||||
const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0)
|
||||
const tempHinweis = t => t ? `${t} bleibt liegen (Projektkopie ohne die Ausschlüsse); löschen, wenn der Diff nicht mehr gebraucht wird` : null
|
||||
|
||||
const LAUF = {
|
||||
type: 'object',
|
||||
@@ -98,12 +121,32 @@ if (!kopie) {
|
||||
const befehl = `T=$(mktemp -d) && echo "KOPIE=$(cygpath -m "$T/mat")" && ${PY} ${q(W + '/kopie_bauen.py')} ${q(args.projekt)} "$T/mat" --ohne ${args.ohne.map(q).join(' ')} && ${PY} ${q(W + '/plan_materialisiere.py')} ${q(args.planordner)} "$T/mat" ${taskdateien.map(q).join(' ')}`
|
||||
const r = await werkzeugAgent('werkzeug:kopie', 'Kopie', befehl, 'Stand vor der Welle kopieren und die Taskdateien der Welle darauf anwenden', 'haiku')
|
||||
belege.kopie = r
|
||||
const lauf = r && r.laeufe && r.laeufe[0]
|
||||
const m = lauf && /^KOPIE=(.+?)\s*$/m.exec(lauf.ausgabe)
|
||||
const aus = ausgabeAller(r)
|
||||
const m = /^KOPIE=(.+?)\s*$/m.exec(aus)
|
||||
if (m) temp = m[1].replace(/\/mat$/, '')
|
||||
const angewendet = lauf && /'(neu|ersetzt|angehaengt)': [1-9]/.test(lauf.ausgabe)
|
||||
if (lauf && lauf.exit === 0 && m && angewendet) kopie = m[1]
|
||||
else log('Kopie nicht verwendbar (' + (!lauf ? 'Hilfsagent ohne Ergebnis' : lauf.exit !== 0 ? 'Exit ' + lauf.exit : !m ? 'kein KOPIE-Pfad' : 'kein Block angewendet') + '); der Byte-Vergleich entfällt, die Welle läuft trotzdem')
|
||||
const planBefunde = befundZeilen(aus)
|
||||
if (planBefunde.length) {
|
||||
const grund = `plan_materialisiere.py meldet ${planBefunde.length} Befund(e) (Exit ${exitsAller(r).map(String).join('/')}); die Welle startet nicht`
|
||||
log('Kopie: ' + grund)
|
||||
return {
|
||||
status: 'plan_befund',
|
||||
plan_befunde: planBefunde,
|
||||
kopie: null,
|
||||
temp_ordner: tempHinweis(temp),
|
||||
welle: null,
|
||||
vergleich: null,
|
||||
kopie_lauf: r,
|
||||
pruefung: { uebersprungen: grund },
|
||||
pruefung_args: null,
|
||||
pruefung_grund: grund,
|
||||
fehler,
|
||||
verbrauch: ablesungen,
|
||||
naechster_schritt: 'Den Plan an den Zeilen in plan_befunde korrigieren und welle-mit-pruefung neu starten. Kein Executer ist gelaufen.' + (temp ? ' Die Kopie unter temp_ordner löschen.' : ''),
|
||||
}
|
||||
}
|
||||
const angewendet = /'(neu|ersetzt|angehaengt)': [1-9]/.test(aus)
|
||||
if (alleExitNull(r) && m && angewendet) kopie = m[1]
|
||||
else log('Kopie nicht verwendbar (' + (!exitsAller(r).length ? 'Hilfsagent ohne Ergebnis' : !alleExitNull(r) ? 'Exit ' + exitsAller(r).map(String).join('/') :!m ? 'kein KOPIE-Pfad' : 'kein Block angewendet') + '); der Byte-Vergleich entfällt, die Welle läuft trotzdem')
|
||||
}
|
||||
|
||||
phase('Welle')
|
||||
@@ -136,8 +179,9 @@ if (!welle) pruefungGrund = 'keine Welle'
|
||||
else if (welle.ohne_bericht && welle.ohne_bericht.length) pruefungGrund = 'Welle unvollständig, ohne Bericht: ' + welle.ohne_bericht.join(', ')
|
||||
let pruefungArgs = null
|
||||
if (!pruefungGrund) {
|
||||
const vl = belege.vergleich && belege.vergleich.laeufe && belege.vergleich.laeufe[0]
|
||||
const vergleichstext = vl ? kopfUndEnde(vl.ausgabe, 1500, 2500) : ''
|
||||
// Anfang und Ende des verbundenen Texts wie bei einem Lauf. Liefert der Hilfsagent die Suite vor
|
||||
// einem langen Vergleich, fällt ihr Ergebnis heraus (benannte Lücke; der pruefer fährt die Suite selbst).
|
||||
const vergleichstext = kopfUndEnde(ausgabeAller(belege.vergleich), 1500, 2500)
|
||||
const schwerpunkt = [
|
||||
args.schwerpunkt || '',
|
||||
welle.mit_abweichung.length ? `Diese Tasks melden Abweichungen vom Plan: ${welle.mit_abweichung.join(', ')}.` : '',
|
||||
@@ -149,7 +193,7 @@ if (!pruefungGrund) {
|
||||
}
|
||||
}
|
||||
|
||||
const tempText = temp ? `${temp} bleibt liegen (Projektkopie ohne die Ausschlüsse); löschen, wenn der Diff nicht mehr gebraucht wird` : null
|
||||
const tempText = tempHinweis(temp)
|
||||
|
||||
if (args.bis === 'vergleich') {
|
||||
if (pruefungGrund) log('Prüfung wäre übersprungen worden: ' + pruefungGrund)
|
||||
|
||||
Reference in new issue
Block a user