38 Datei(en) geändert. Erzeugt von .vault-tools/auto-push/werkzeuge_spiegel.py.
198 lines
11 KiB
JavaScript
198 lines
11 KiB
JavaScript
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.',
|
|
}
|