export const meta = { name: 'welle-mit-pruefung', description: 'Schritt 3 und 4 in einem Aufruf: Kopie mit angewendetem Plan vor der Welle, executer-welle, Byte-Vergleich und Suite, danach pruefung mit den Rohbelegen; mit bis "vergleich" endet er vor der Prüfung', whenToUse: 'Nach der Plankritik, sobald eine Welle mit disjunkten Zieldateien steht (AI/Regelwerk/codeworkflow.md, Schritte 3 und 4). Args: {projekt, planordner, tasks: [{datei, modell, effort?}], kopie? oder ohne: [relpfade, nicht leer], prosa?: [relpfade], suite?, kritisch?, abschluss?, effort?, version?, schwerpunkt?, rot_nachweis_erbracht?, zusatz?, werkzeuge?, bis?: "vergleich"}. Ohne kopie wird sie vor der Welle gebaut; ohne nennt dann .git und Geheimnisdateien. Mit bis "vergleich" kommt pruefung_args zurück: pruefung damit selbst starten und währenddessen die eigene Gegenprüfung fahren. Ersetzt die eigene Gegenprüfung des Koordinators nicht.', phases: [ { title: 'Kopie', detail: 'Hilfsagent: Stand vor der Welle kopieren und den Plan der Welle anwenden' }, { title: 'Welle', detail: 'executer-welle' }, { title: 'Vergleich', detail: 'Hilfsagent: welle_vergleich.py und Suite im echten Baum' }, { title: 'Prüfung', detail: 'pruefung mit den Rohbelegen' }, ], } // Ruft die gespeicherten Workflows executer-welle und pruefung verschachtelt auf, damit ihre // Aufträge und Rollenparameter an genau einer Stelle stehen. Der Hilfsagent hat keinen // agentType und bekommt seinen Effort deshalb mit (low). Opus läuft nur in den Kindern, // und die laufen nacheinander; die Obergrenze von zwei hält executer-welle selbst. // Modell: werkzeug:kopie läuft auf haiku, weil nur das Skript die Ausgabe liest. werkzeug:vergleich // bleibt auf sonnet: seine Rohausgabe geht an den pruefer. // 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. 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' const LAUF = { type: 'object', properties: { laeufe: { type: 'array', items: { type: 'object', properties: { befehl: { type: 'string' }, exit: { type: 'integer' }, ausgabe: { type: 'string' } }, required: ['befehl', 'exit', 'ausgabe'] } }, }, required: ['laeufe'], } // Dieselben Regeln wie in executer-welle, aber VOR dem ersten Agenten: Ein ungültiger Task // soll keine Projektkopie im Temp hinterlassen. function pruefeArgs(a) { if (!a || typeof a !== 'object') throw new Error('welle-mit-pruefung: args fehlen') const fehlt = ['projekt', 'planordner', 'tasks'].filter(k => !a[k]) if (fehlt.length) throw new Error('welle-mit-pruefung: args fehlen: ' + fehlt.join(', ')) if (!Array.isArray(a.tasks) || a.tasks.length === 0) throw new Error('welle-mit-pruefung: tasks muss eine nicht leere Liste von {datei, modell, effort?} sein') a.tasks.forEach((t, i) => { if (!t || typeof t !== 'object' || !t.datei || typeof t.datei !== 'string') throw new Error(`welle-mit-pruefung: tasks[${i}] ohne datei`) if (!MODELLE.includes(t.modell)) throw new Error(`welle-mit-pruefung: ${t.datei}: modell "${t.modell}" nicht in ${MODELLE.join('/')}`) if (t.effort !== undefined && !EFFORTS.includes(t.effort)) throw new Error(`welle-mit-pruefung: ${t.datei}: effort "${t.effort}" nicht in ${EFFORTS.join('/')}`) }) if (a.effort !== undefined && !EFFORTS.includes(a.effort)) throw new Error('welle-mit-pruefung: effort "' + a.effort + '" ungültig') if (a.prosa !== undefined && !Array.isArray(a.prosa)) throw new Error('welle-mit-pruefung: prosa muss eine Liste sein') if (a.bis !== undefined && a.bis !== 'vergleich') throw new Error('welle-mit-pruefung: bis kennt nur "vergleich"') if (!a.kopie) { const ok = Array.isArray(a.ohne) && a.ohne.length > 0 && a.ohne.every(x => typeof x === 'string' && x) if (!ok) throw new Error('welle-mit-pruefung: kopie oder ohne angeben. ohne ist eine nicht leere Liste von Ausschlüssen für kopie_bauen.py, als Pfade vom Projektwurzelordner, mindestens .git (falls vorhanden) und Geheimnisdateien') } } function werkzeugAgent(label, phase, befehl, zweck, modell = 'sonnet') { return agent(`Du führst einen Werkzeugbefehl aus und gibst seine Ausgabe roh zurück. Zweck: ${zweck} Führe genau diesen Befehl einmal im Bash-Werkzeug (Git-Bash) aus, unverändert: ${befehl} Ändere keine Datei außer denen, die der Befehl selbst anlegt. Deute nichts und fasse nichts zusammen. Gib Befehl, Exit-Code und die vollständige Ausgabe zurück; bei mehr als 200 Zeilen die ersten 60 und die letzten 120, mit einer Zeile "[... N Zeilen gekürzt ...]" dazwischen.`, { label, phase, schema: LAUF, model: modell, effort: 'low' }) } // Anfang und Ende behalten: Das Ergebnis der Suite steht am Schluss. function kopfUndEnde(text, kopf, ende) { if (!text || text.length <= kopf + ende) return text || '' return text.slice(0, kopf) + '\n[... gekürzt ...]\n' + text.slice(-ende) } // Eine messende Hilfsfunktion ablesen() steht auch in pruefung.js; plankritik.js und planung.js // haben eine mit Wunsch und Rückgabe. Die Regel (Fenster, Stufen, Rückfall) steht nur in // verbrauch.js. const ablesungen = [] async function ablesen(anlass) { let r = null try { r = await workflow({ scriptPath: VERBRAUCH }, { anlass: 'welle-mit-pruefung:' + anlass, wunsch: 0 }) } catch (e) { r = { status: 'nicht_gelesen', grund: 'verbrauch.js nicht ausführbar: ' + (e && e.message) } } ablesungen.push({ anlass, ...(r || { status: 'nicht_gelesen', grund: 'verbrauch.js ohne Ergebnis' }) }) } pruefeArgs(args) const W = args.werkzeuge || 'D:/Claude/.vault-tools/plan-kopie' const taskdateien = args.tasks.map(t => t.datei) let kopie = args.kopie || null let temp = null const belege = {} const fehler = [] phase('Kopie') 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) 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') } phase('Welle') let welle = null try { welle = await workflow('executer-welle', { projekt: args.projekt, planordner: args.planordner, tasks: args.tasks, rot_nachweis_erbracht: args.rot_nachweis_erbracht, zusatz: args.zusatz, }) } catch (e) { fehler.push('executer-welle: ' + (e && e.message)) log('executer-welle gescheitert: ' + (e && e.message)) } phase('Vergleich') if (welle) { const teile = [] if (kopie) teile.push(`${PY} ${q(W + '/welle_vergleich.py')} --projekt ${q(args.projekt)} --mat ${q(kopie)}${args.prosa && args.prosa.length ? ' --prosa ' + args.prosa.map(q).join(' ') : ''} -- ${taskdateien.map(d => q(args.planordner + '/' + d)).join(' ')}; echo "EXIT welle_vergleich: $?"`) if (args.suite) teile.push(`if cd ${q(args.projekt)}; then export COLUMNS=300; ${args.suite}; echo "EXIT suite: $?"; else echo "EXIT suite: cd ins Projekt gescheitert"; fi`) if (teile.length) { belege.vergleich = await werkzeugAgent('werkzeug:vergleich', 'Vergleich', teile.join('; '), 'Nach der Welle: geänderte Dateien und Bytes gegen die Kopie, dann die Suite im echten Baum') if (!belege.vergleich) log('Vergleich: Hilfsagent ohne Ergebnis') } else log('Vergleich entfällt: weder Kopie noch suite') } else log('Vergleich entfällt: keine Welle') phase('Prüfung') let pruefung = null let pruefungGrund = null 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) : '' const schwerpunkt = [ args.schwerpunkt || '', welle.mit_abweichung.length ? `Diese Tasks melden Abweichungen vom Plan: ${welle.mit_abweichung.join(', ')}.` : '', vergleichstext ? `Rohausgabe von Byte-Vergleich und Suite nach der Welle (vom Hilfsagenten, nicht von dir gefahren):\n${vergleichstext}` : '', ].filter(Boolean).join('\n') pruefungArgs = { projekt: args.projekt, plan: args.planordner, tasks: taskdateien, suite: args.suite, kritisch: args.kritisch, abschluss: args.abschluss, version: args.version, effort: args.effort, schwerpunkt, } } const tempText = temp ? `${temp} bleibt liegen (Projektkopie ohne die Ausschlüsse); löschen, wenn der Diff nicht mehr gebraucht wird` : null if (args.bis === 'vergleich') { if (pruefungGrund) log('Prüfung wäre übersprungen worden: ' + pruefungGrund) return { status: 'bis_vergleich', kopie, temp_ordner: tempText, welle, vergleich: belege.vergleich || null, kopie_lauf: belege.kopie || null, pruefung_args: pruefungArgs, pruefung_grund: pruefungGrund, fehler, verbrauch: ablesungen, naechster_schritt: pruefungArgs ? 'pruefung mit pruefung_args als eigenen Workflow im Hintergrund starten. Währenddessen die eigene Gegenprüfung nach Schritt 4, nur lesend: nichts in Projektbaum oder Planordner schreiben, Rot- und Mutantentests nur auf einer Kopie, den pruefer nicht abbrechen. Plankorrektur und Commit erst nach seinem Bericht.' : 'Keine Prüfung möglich (' + pruefungGrund + '). Welle nachziehen, dann erneut.', } } if (pruefungGrund) log('Prüfung übersprungen: ' + pruefungGrund) else { await ablesen('vor Prüfung') try { pruefung = await workflow('pruefung', { ...pruefungArgs, verbrauch: false }) } catch (e) { fehler.push('pruefung: ' + (e && e.message)) pruefungGrund = 'pruefung gescheitert: ' + (e && e.message) log(pruefungGrund) } await ablesen('nach Prüfung') } return { status: pruefung ? 'fertig' : 'ohne_pruefung', kopie, temp_ordner: tempText, welle, vergleich: belege.vergleich || null, kopie_lauf: belege.kopie || null, pruefung: pruefung || { uebersprungen: pruefungGrund }, fehler, verbrauch: ablesungen, naechster_schritt: 'Eigene Gegenprüfung nach Schritt 4: Testlauf selbst wiederholen, zentrale Artefakte selbst lesen, eigener Lauf mit echten Daten; bei Abweichung im Byte-Vergleich diff auf die ganze Datei. Befunde am Plan korrigieren, nicht am Code.', }