Automatischer Push: 46 Datei(en) geändert
- .claude/agents/planer.md - .claude/skills/plan-executer-pruefung/SKILL.md - .claude/workflows/plankritik.js - .claude/workflows/planung.js - .claude/workflows/pruefung.js - .claude/workflows/verbrauch.js - .claude/workflows/welle-mit-pruefung.js - .vault-tools/plan-kopie/LIESMICH.md - .vault-tools/plan-kopie/plan_zeichen.py - .vault-tools/plan-kopie/rot_nachweis.py - .vault-tools/trading-plattform-pruefung/LIESMICH.md - .vault-tools/trading-plattform-pruefung/abendkontrolle.py - … und 34 weitere 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
c5da36cb41
commit
f86a54a6e7
29 files changed
+6067
-79
No files matched your search
@@ -18,6 +18,7 @@ Du denkst, du tippst nicht. Dein Ergebnis ist ein Plan, den ein Executer ohne R
|
||||
- **Erst lesen, dann planen.** Bestehende Struktur, Namensgebung, Testkonventionen und Abhängigkeiten des Zielprojekts lesen. Der Plan passt sich der vorhandenen Umgebung an, nicht umgekehrt.
|
||||
- **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).
|
||||
- **Keine Platzhalter.** Kein „analog zu Task 2", kein „hier noch ergänzen", kein Pseudocode. Was im Plan steht, ist tippfertig.
|
||||
- **Nachbesserung nach einer ausgeführten Welle schreibst du als neue Taskdateien, nicht als Neufassung.** Du hast kein Edit-Werkzeug und schreibst jede überarbeitete Datei komplett neu; am 2026-09-23 im Nexhorizons Hub waren dabei 10 von 17 Befunden der Nachprüfung durch das Umschreiben selbst entstanden. Deshalb: neue `task-NN-nachbesserung-<thema>.md` mit `Aus:`/`wird:`-Blöcken gegen den Baum, bestehende Taskdateien bleiben byte-gleich. Was ältere Tasks an den neuen Stand anpassen muss, schreibst du als Folgenliste `nachbesserung-<welle>-folgen.md` im selben `Aus:`/`wird:`-Format; eingesetzt wird sie vom Koordinator oder einem Executer. Für Korrekturen vor der ersten Ausführung gilt das nicht, außer der Auftrag verlangt es.
|
||||
- **Offene Entscheidungen benennen, nicht überspielen.** Wo der Auftrag mehrdeutig ist: Annahme treffen, im Abschnitt „Getroffene Annahmen" explizit nennen. Nur abbrechen, wenn jede Annahme den Plan unbrauchbar machen würde.
|
||||
|
||||
## Aufbau des Plans
|
||||
|
||||
@@ -29,8 +29,9 @@ wird.
|
||||
Für die Plankritik in Schritt 2, die Welle in Schritt 3 und die Prüfung in Schritt 4 gibt es
|
||||
gespeicherte Workflows mit eingebauten Rollenparametern: `plankritik`, `executer-welle` und
|
||||
`pruefung` unter `.claude/workflows/`, dazu `welle-mit-pruefung` für Schritt 3 und 4 in einem
|
||||
Aufruf. Aufruf und Argumente stehen jeweils in `whenToUse` im
|
||||
Kopf der Datei. Sie ersetzen deine eigene Gegenprüfung nicht.
|
||||
Aufruf und `planung` für die Erstplanung vieler Taskdateien. Aufruf und Argumente stehen jeweils
|
||||
in `whenToUse` im Kopf der Datei. Wie viele Opus-Agenten sie gleichzeitig starten, regelt
|
||||
`codeworkflow.md`, „Verbrauch und Opus-Plätze". Sie ersetzen deine eigene Gegenprüfung nicht.
|
||||
|
||||
Danach je nach Aufgabe zusätzlich:
|
||||
|
||||
@@ -46,7 +47,8 @@ Danach je nach Aufgabe zusätzlich:
|
||||
1. **Die Gegenprüfung in Schritt 4.** Ein Agentenbericht ist eine Behauptung, kein Beleg. Du
|
||||
wiederholst den Testlauf selbst, liest die zentralen Artefakte selbst und fährst einen
|
||||
eigenen Lauf mit echten Daten und eigenem Prüfcode. Der `pruefer`-Subagent (`opus`)
|
||||
verdoppelt das, er ersetzt es nicht.
|
||||
verdoppelt das, er ersetzt es nicht. Du fährst sie, während er läuft, nur lesend
|
||||
(`codeworkflow.md`, Schritt 4).
|
||||
2. **Den Commit.** Subagenten committen nie — paralleles `git commit` kollidiert am Index-Lock.
|
||||
|
||||
## Grenzen
|
||||
|
||||
+102
-23
@@ -1,10 +1,10 @@
|
||||
export const meta = {
|
||||
name: 'plankritik',
|
||||
description: 'Liest einen Planordner mit getrennten pruefer-Linsen, fasst doppelte Befunde zusammen und lässt jeden wichtigen oder blockierenden Befund von einem zweiten pruefer zu widerlegen versuchen; höchstens zwei Opus gleichzeitig',
|
||||
whenToUse: 'Schritt 2 der Standardschleife vor dem ersten Executer, Pflicht ab etwa drei Tasks (AI/Regelwerk/codeworkflow.md, "Plankritik vor dem ersten Executer"). Args: {projekt, planordner, spec?, kopie?, materialisieren?: {ohne: [relpfade], tasks?: [taskdateien]}, suite?, linsen?, verifizieren?: true, verifizieren_ab?: "wichtig"|"blockierend", effort?, werkzeuge?, zusatz?}. Ohne kopie baut materialisieren sie selbst (kopie_bauen.py plus plan_materialisiere.py); ohne() muss Geheimnisdateien und große Datenordner nennen.',
|
||||
description: 'Liest einen Planordner mit getrennten pruefer-Linsen, gleichzeitig nach Verbrauch bis vier, fasst doppelte Befunde zusammen und lässt jeden wichtigen oder blockierenden Befund von einem zweiten pruefer zu widerlegen versuchen, höchstens zwei gleichzeitig',
|
||||
whenToUse: 'Schritt 2 der Standardschleife vor dem ersten Executer, Pflicht ab etwa drei Tasks (AI/Regelwerk/codeworkflow.md, "Plankritik vor dem ersten Executer"). Args: {projekt, planordner, spec?, kopie?, materialisieren?: {ohne: [relpfade], tasks?: [taskdateien]}, suite?, linsen?, plaetze?: 1..4, verifizieren?: true, verifizieren_ab?: "wichtig"|"blockierend", effort?, werkzeuge?: Ordner der Planwerkzeuge (Pfad, keine Ausgabe; Vorgabe D:/Claude/.vault-tools/plan-kopie), zusatz?}. Ohne kopie baut materialisieren sie selbst (kopie_bauen.py plus plan_materialisiere.py); ohne() muss Geheimnisdateien und große Datenordner nennen. plaetze begrenzt die gleichzeitigen Linsen; verbrauch.js senkt sie nach dem 5-Stunden-Fenster und der Woche. Rückgabe mit status fertig oder gebremst; ein gebremster Lauf wird neu gestartet, nicht fortgesetzt.',
|
||||
phases: [
|
||||
{ title: 'Vorbereitung', detail: 'Hilfsagent: Kopie bauen, Plan anwenden, Planwerkzeuge fahren' },
|
||||
{ title: 'Plankritik', detail: 'je Linse ein pruefer, paarweise' },
|
||||
{ title: 'Plankritik', detail: 'je Linse ein pruefer, gleichzeitig nach Verbrauch' },
|
||||
{ title: 'Widerlegung', detail: 'je wichtiger Befund ein zweiter pruefer, höchstens zwei gleichzeitig' },
|
||||
],
|
||||
}
|
||||
@@ -13,8 +13,15 @@ export const meta = {
|
||||
// Frontmatter von .claude/agents/pruefer.md; args.effort überschreibt ihn nur bewusst.
|
||||
// Der Hilfsagent hat keinen agentType und bekommt deshalb seinen Effort mit (low: gemessen
|
||||
// gleich treu wie high bei mechanischer Arbeit, codeworkflow.md "Effort je Rolle").
|
||||
// Opus-Plätze: Vor den Linsen und vor der Widerlegung liest verbrauch.js den Planverbrauch ab.
|
||||
// Die Linsen laufen mit so vielen Plätzen, wie verbrauch.js erlaubt (höchstens so viele wie
|
||||
// Linsen, höchstens LINSEN_HOECHSTENS), die Widerlegung mit höchstens zwei. Gebremst heißt: Die
|
||||
// Phase startet nicht, und der Lauf kommt mit status gebremst zurück.
|
||||
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
|
||||
const OPUS_PARALLEL = 2
|
||||
const LINSEN_HOECHSTENS = 4
|
||||
const WIDERLEGUNG_HOECHSTENS = 2
|
||||
const OHNE_ABLESUNG = 2
|
||||
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, "'\\''") + "'"
|
||||
@@ -76,9 +83,11 @@ function pruefeArgs(a) {
|
||||
if (a.materialisieren && a.materialisieren.tasks !== undefined && !Array.isArray(a.materialisieren.tasks)) throw new Error('plankritik: materialisieren.tasks muss eine Liste von Taskdateien sein')
|
||||
const unbekannt = (a.linsen || []).filter(l => !LINSEN[l])
|
||||
if (unbekannt.length) throw new Error('plankritik: unbekannte Linse: ' + unbekannt.join(', ') + '; bekannt: ' + Object.keys(LINSEN).join(', '))
|
||||
if (a.linsen && new Set(a.linsen).size !== a.linsen.length) throw new Error('plankritik: eine Linse steht doppelt in linsen: ' + a.linsen.join(', '))
|
||||
if (a.effort !== undefined && !EFFORTS.includes(a.effort)) throw new Error('plankritik: effort "' + a.effort + '" ungültig')
|
||||
if (a.verifizieren_ab !== undefined && !['wichtig', 'blockierend'].includes(a.verifizieren_ab)) throw new Error('plankritik: verifizieren_ab muss wichtig oder blockierend sein')
|
||||
if (a.materialisieren && !(Array.isArray(a.materialisieren.ohne) && a.materialisieren.ohne.length > 0 && a.materialisieren.ohne.every(x => typeof x === 'string' && x))) throw new Error('plankritik: materialisieren.ohne muss eine nicht leere Liste sein — Ausschlüsse als Pfade vom Projektwurzelordner, mindestens .git (falls vorhanden) und Geheimnisdateien')
|
||||
if (a.plaetze !== undefined && !(Number.isInteger(a.plaetze) && a.plaetze >= 1 && a.plaetze <= LINSEN_HOECHSTENS)) throw new Error('plankritik: plaetze muss eine ganze Zahl von 1 bis ' + LINSEN_HOECHSTENS + ' sein')
|
||||
if (a.materialisieren && !(Array.isArray(a.materialisieren.ohne) && a.materialisieren.ohne.length > 0 && a.materialisieren.ohne.every(x => typeof x === 'string' && x))) throw new Error('plankritik: materialisieren.ohne muss eine nicht leere Liste sein, Ausschlüsse als Pfade vom Projektwurzelordner, mindestens .git (falls vorhanden) und Geheimnisdateien')
|
||||
}
|
||||
|
||||
// Höchstens n Aufgaben gleichzeitig, ohne Leerlauf zwischen Paaren.
|
||||
@@ -95,6 +104,26 @@ async function hoechstens(n, items, fn) {
|
||||
return ergebnisse
|
||||
}
|
||||
|
||||
// Eine Hilfsfunktion ablesen() steht auch in planung.js (gleiche Form) sowie in pruefung.js und
|
||||
// welle-mit-pruefung.js (dort nur messend, ohne Rückgabe). Die Regel (Fenster, Stufen, Rückfall)
|
||||
// steht nur in verbrauch.js; hier steht nur der Rückfall für den Fall, dass verbrauch.js selbst
|
||||
// nicht läuft, etwa weil dieser Workflow als Kind läuft.
|
||||
const ablesungen = []
|
||||
async function ablesen(anlass, wunsch) {
|
||||
let r = null
|
||||
try {
|
||||
r = await workflow({ scriptPath: VERBRAUCH }, { anlass: 'plankritik:' + anlass, wunsch })
|
||||
} catch (e) {
|
||||
r = { status: 'nicht_gelesen', grund: 'verbrauch.js nicht ausführbar: ' + (e && e.message) }
|
||||
}
|
||||
if (!r || typeof r.plaetze !== 'number') {
|
||||
r = { status: 'nicht_gelesen', quelle: 'keine', fuenf_stunden: null, woche: null, plaetze: Math.min(wunsch, OHNE_ABLESUNG), gebremst: false, grund: (r && r.grund) || 'verbrauch.js ohne verwertbares Ergebnis' }
|
||||
}
|
||||
ablesungen.push({ anlass, ...r })
|
||||
log(`Verbrauch ${anlass}: ${r.status}, ${r.plaetze} Plätze${r.gebremst ? ', gebremst' : ''} (${r.grund})`)
|
||||
return r
|
||||
}
|
||||
|
||||
function werkzeugAgent(label, phase, befehl, zweck) {
|
||||
return agent(`Du führst einen Werkzeugbefehl aus und gibst seine Ausgabe roh zurück. Zweck: ${zweck}
|
||||
|
||||
@@ -105,8 +134,14 @@ ${befehl}
|
||||
{ label, phase, schema: LAUF, model: 'sonnet', effort: 'low' })
|
||||
}
|
||||
|
||||
function testort(ctx) {
|
||||
return ctx.kopie
|
||||
? `Tests fährst du nur in der Kopie ${ctx.kopie}, nie im echten Projektbaum: Dort kann Betrieb laufen, und andere Prüfer arbeiten gleichzeitig. Mutanten legst du nur in einer eigenen Kopie dieser Kopie an, in einem neuen Ordner unter dem System-Temp, nie in ${ctx.kopie} selbst.`
|
||||
: 'Tests fährst du in dieser Plankritik nicht, auch nicht im echten Projektbaum: Dort kann Betrieb laufen, und andere Prüfer arbeiten gleichzeitig.'
|
||||
}
|
||||
|
||||
function auftrag(a, linse, ctx) {
|
||||
return `Lies den Plan im Ordner ${a.planordner} für das Projekt ${a.projekt}, bevor ein Executer startet. Du änderst keine Datei im Projekt und keine im Plan.
|
||||
return `Lies den Plan im Ordner ${a.planordner} für das Projekt ${a.projekt}, bevor ein Executer startet. Du änderst keine Datei im Projekt und keine im Plan. Eine Umsetzung gibt es noch nicht, also auch keinen Executer-Bericht, dessen Testlauf du wiederholen könntest. ${testort(ctx)}
|
||||
|
||||
${LINSEN[linse](a, ctx)}
|
||||
|
||||
@@ -143,7 +178,7 @@ function widerlegungsauftrag(a, g, ctx) {
|
||||
${texte}
|
||||
</befund>
|
||||
|
||||
Versuche ihn zu widerlegen. Lies die Stelle selbst und reproduziere den Befund mit einem eigenen Befehl${ctx.kopie ? `; der Plan ist auf die Kopie ${ctx.kopie} angewendet, dort darfst du Tests fahren` : ''}. Du änderst keine Datei im Projekt und keine im Plan.
|
||||
Versuche ihn zu widerlegen. Lies die Stelle selbst und reproduziere den Befund mit einem eigenen Befehl. Du änderst keine Datei im Projekt und keine im Plan. ${testort(ctx)}
|
||||
- "bestaetigt" nur mit eigener Reproduktion oder einem wörtlichen Beleg aus der Quelle.
|
||||
- "widerlegt" nur mit einem Gegenbeleg: ein Befehl mit Ausgabe oder ein Zitat, das die Aussage falsch macht.
|
||||
- Sonst "offen".
|
||||
@@ -171,6 +206,10 @@ function kopieAusLauf(r, wozu) {
|
||||
return m[1]
|
||||
}
|
||||
|
||||
function tempText() {
|
||||
return ctx.temp ? `${ctx.temp} bleibt liegen (Projektkopie ohne die Ausschlüsse); löschen, wenn nicht mehr gebraucht` : null
|
||||
}
|
||||
|
||||
phase('Vorbereitung')
|
||||
if (ctx.kopie && args.materialisieren) log('kopie ist gesetzt, materialisieren wird übergangen')
|
||||
if (!ctx.kopie && args.materialisieren) {
|
||||
@@ -186,14 +225,38 @@ 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')
|
||||
if (r && r.laeufe && r.laeufe[0]) ctx.werkzeuge = r.laeufe[0].ausgabe
|
||||
// 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')
|
||||
vorbereitung.push(r)
|
||||
}
|
||||
|
||||
phase('Plankritik')
|
||||
const gewaehlt = args.linsen && args.linsen.length ? args.linsen : Object.keys(LINSEN)
|
||||
const kritiken = await hoechstens(OPUS_PARALLEL, gewaehlt, linse => {
|
||||
const vorLinsen = await ablesen('vor Linsen', Math.min(args.plaetze || gewaehlt.length, gewaehlt.length, LINSEN_HOECHSTENS))
|
||||
if (vorLinsen.gebremst) {
|
||||
log('Plankritik gebremst vor den Linsen: ' + vorLinsen.grund)
|
||||
return {
|
||||
status: 'gebremst',
|
||||
gebremst_vor: 'Linsen',
|
||||
zaehlung: { roh: 0, zusammengefasst: 0, bestaetigt: 0, offen: 0, widerlegt: 0, nicht_geprueft: 0 },
|
||||
kopie: ctx.kopie,
|
||||
temp_ordner: tempText(),
|
||||
befunde: [],
|
||||
selbst_ausgefuehrt: [],
|
||||
nicht_pruefbar: [],
|
||||
vorbereitung,
|
||||
ohne_ergebnis: gewaehlt,
|
||||
verbrauch: ablesungen,
|
||||
plaetze: { linsen: 0, widerlegung: 0 },
|
||||
naechster_schritt: 'Nach der Zurücksetzung des Fensters neu starten, nicht mit resumeFromRunId fortsetzen: Die Ablesung käme sonst aus dem Zwischenspeicher.',
|
||||
}
|
||||
}
|
||||
const kritiken = await hoechstens(vorLinsen.plaetze, gewaehlt, linse => {
|
||||
const opts = { label: 'plankritik:' + linse, phase: 'Plankritik', schema: BEFUNDE, agentType: 'pruefer', model: 'opus' }
|
||||
if (args.effort) opts.effort = args.effort
|
||||
return agent(auftrag(args, linse, ctx), opts).then(r => ({ linse, ergebnis: r }))
|
||||
@@ -206,34 +269,50 @@ const roh = ergebnisse.flatMap(e => e.ergebnis.befunde.map(b => ({ linse: e.lins
|
||||
const gruppen = zusammenfassen(roh)
|
||||
const ab = RANG[args.verifizieren_ab || 'wichtig']
|
||||
const zuPruefen = args.verifizieren === false ? [] : gruppen.filter(g => RANG[g.schweregrad] <= ab)
|
||||
const nichtGeprueft = gruppen.length - zuPruefen.length
|
||||
log(`${roh.length} Befunde, zusammengefasst ${gruppen.length}; ${zuPruefen.length} gehen in die Widerlegung, ${nichtGeprueft} nicht (${args.verifizieren === false ? 'abgeschaltet' : 'unter der Schwelle ' + (args.verifizieren_ab || 'wichtig')})`)
|
||||
log(`${roh.length} Befunde, zusammengefasst ${gruppen.length}; ${zuPruefen.length} gehen in die Widerlegung, ${gruppen.length - zuPruefen.length} nicht (${args.verifizieren === false ? 'abgeschaltet' : 'unter der Schwelle ' + (args.verifizieren_ab || 'wichtig')})`)
|
||||
|
||||
phase('Widerlegung')
|
||||
const urteile = await hoechstens(OPUS_PARALLEL, zuPruefen, (g, i) => {
|
||||
const opts = { label: `widerlegung:${i + 1}:${dateiname(g.datei)}:${g.zeile}`, phase: 'Widerlegung', schema: URTEIL, agentType: 'pruefer', model: 'opus' }
|
||||
if (args.effort) opts.effort = args.effort
|
||||
return agent(widerlegungsauftrag(args, g, ctx), opts)
|
||||
})
|
||||
zuPruefen.forEach((g, i) => {
|
||||
if (!urteile[i]) log(`Widerlegung ${i + 1} (${g.datei}:${g.zeile}) ohne Ergebnis; Befund bleibt offen`)
|
||||
g.widerlegung = urteile[i] || { urteil: 'offen', schweregrad: g.schweregrad, bedingung: '', begruendung: 'Widerlegungsagent ohne Ergebnis', beleg: '' }
|
||||
})
|
||||
let gebremstVor = null
|
||||
let plaetzeWiderlegung = 0
|
||||
if (zuPruefen.length) {
|
||||
const vorWiderlegung = await ablesen('vor Widerlegung', Math.min(WIDERLEGUNG_HOECHSTENS, zuPruefen.length))
|
||||
if (vorWiderlegung.gebremst) {
|
||||
gebremstVor = 'Widerlegung'
|
||||
log('Widerlegung gebremst, die Befunde bleiben ungeprüft: ' + vorWiderlegung.grund)
|
||||
} else plaetzeWiderlegung = vorWiderlegung.plaetze
|
||||
}
|
||||
if (!gebremstVor) {
|
||||
const urteile = await hoechstens(plaetzeWiderlegung, zuPruefen, (g, i) => {
|
||||
const opts = { label: `widerlegung:${i + 1}:${dateiname(g.datei)}:${g.zeile}`, phase: 'Widerlegung', schema: URTEIL, agentType: 'pruefer', model: 'opus' }
|
||||
if (args.effort) opts.effort = args.effort
|
||||
return agent(widerlegungsauftrag(args, g, ctx), opts)
|
||||
})
|
||||
zuPruefen.forEach((g, i) => {
|
||||
if (!urteile[i]) log(`Widerlegung ${i + 1} (${g.datei}:${g.zeile}) ohne Ergebnis; Befund bleibt offen`)
|
||||
g.widerlegung = urteile[i] || { urteil: 'offen', schweregrad: g.schweregrad, bedingung: '', begruendung: 'Widerlegungsagent ohne Ergebnis', beleg: '' }
|
||||
})
|
||||
}
|
||||
await ablesen('Ende', 0)
|
||||
|
||||
const URTEILRANG = { bestaetigt: 0, offen: 1, undefined: 2, widerlegt: 3 }
|
||||
const befunde = gruppen
|
||||
.map(g => ({ urteil: g.widerlegung ? g.widerlegung.urteil : 'nicht geprüft', ...g }))
|
||||
.map(g => ({ urteil: g.widerlegung ? g.widerlegung.urteil : gebremstVor && zuPruefen.includes(g) ? 'nicht geprüft (gebremst)' : 'nicht geprüft', ...g }))
|
||||
.sort((x, y) => (URTEILRANG[x.widerlegung && x.widerlegung.urteil] - URTEILRANG[y.widerlegung && y.widerlegung.urteil]) || (RANG[x.schweregrad] - RANG[y.schweregrad]))
|
||||
const zaehlung = { roh: roh.length, zusammengefasst: gruppen.length, bestaetigt: 0, offen: 0, widerlegt: 0, nicht_geprueft: nichtGeprueft }
|
||||
const zaehlung = { roh: roh.length, zusammengefasst: gruppen.length, bestaetigt: 0, offen: 0, widerlegt: 0, nicht_geprueft: gruppen.filter(g => !g.widerlegung).length }
|
||||
befunde.forEach(b => { if (b.widerlegung) zaehlung[b.widerlegung.urteil]++ })
|
||||
|
||||
return {
|
||||
status: gebremstVor ? 'gebremst' : 'fertig',
|
||||
gebremst_vor: gebremstVor,
|
||||
zaehlung,
|
||||
kopie: ctx.kopie,
|
||||
temp_ordner: ctx.temp ? `${ctx.temp} bleibt liegen (Projektkopie ohne die Ausschlüsse); löschen, wenn nicht mehr gebraucht` : null,
|
||||
temp_ordner: tempText(),
|
||||
befunde,
|
||||
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,
|
||||
ohne_ergebnis: ohneErgebnis,
|
||||
verbrauch: ablesungen,
|
||||
plaetze: { linsen: vorLinsen.plaetze, widerlegung: plaetzeWiderlegung },
|
||||
naechster_schritt: gebremstVor ? 'Die Widerlegung nach der Zurücksetzung des Fensters nachholen: plankritik neu starten, nicht mit resumeFromRunId fortsetzen.' : 'Bestätigte und offene Befunde am Plan korrigieren, danach die Kopie neu bauen.',
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
export const meta = {
|
||||
name: 'planung',
|
||||
description: 'Erstplanung: je Task ein planer, gestartet, sobald die Taskdateien seiner Voraussetzungen geschrieben sind; Opus-Plätze nach Verbrauch, vorgegeben zwei',
|
||||
whenToUse: 'Schritt 2 der Standardschleife, wenn 00-uebersicht.md mit der Tasktabelle steht und viele Taskdateien zu schreiben sind (AI/Regelwerk/codeworkflow.md, "Verbrauch und Opus-Plätze"). Nicht für Nachbesserungen. Args: {projekt, planordner, gemeinsam, taskauftrag, tasks: [{nr, thema, datei, modell, voraussetzungen: [nr], welle?}], erledigt?: [nr], plaetze?: 1..4, nachlesen_alle?: 1..20, zusatz?}. plaetze ist der Wunsch, verbrauch.js senkt ihn; ohne plaetze gilt zwei, drei oder vier erst nach der Eichung der Stufen. taskauftrag darf {NR}, {THEMA}, {DATEI}, {MODELL}, {WELLE} und {VORAUSSETZUNGEN} enthalten. Rückgabe mit status fertig, unvollstaendig, gebremst oder abgebrochen; offene Tasks mit erledigt in einem neuen Lauf nachholen, nicht mit resumeFromRunId. Die Plankritik läuft danach als eigener Workflow.',
|
||||
phases: [{ title: 'Taskdateien', detail: 'planer je Task, Start nach den Voraussetzungen, Plätze nach Verbrauch' }],
|
||||
}
|
||||
|
||||
// Rollenparameter: model wird immer gesetzt (Stamm), der Effort kommt aus dem Frontmatter von
|
||||
// .claude/agents/planer.md. Ein Task startet erst, wenn die Planer aller seiner Voraussetzungen
|
||||
// ein Ergebnis geliefert haben; sonst erfindet er Namen, die ein Vorgänger anders schreibt
|
||||
// (codeworkflow.md, "Verbrauch und Opus-Plätze"). Die Zahl gleichzeitiger Planer liest
|
||||
// verbrauch.js am Anfang und nach je nachlesen_alle fertigen Planern ab. Gebremst heißt: Kein
|
||||
// Planer startet mehr, die laufenden enden, der Rest kommt als offen zurück. Nach drei Planern
|
||||
// ohne Ergebnis startet keiner mehr, meist ist dann das Kontingent erschöpft. Ein Ergebnis zählt
|
||||
// nur, wenn es die erwartete Taskdatei nennt; sonst gilt der Task als gescheitert.
|
||||
// VORGABE_PLAETZE ist Stufe 1 der Freigabe vom 2026-09-24 (Pool mit zwei Plätzen). Stufe 3 mit
|
||||
// drei bis vier Plätzen für die Erstplanung kommt erst nach der Eichung der Stufen in verbrauch.js.
|
||||
const MODELLE = ['haiku', 'sonnet', 'opus']
|
||||
const HOECHSTENS = 4
|
||||
const VORGABE_PLAETZE = 2
|
||||
const OHNE_ABLESUNG = 2
|
||||
const ABBRUCH_NACH = 3
|
||||
const NACHLESEN_ALLE = 4
|
||||
const VERBRAUCH = 'D:/Claude/.claude/workflows/verbrauch.js'
|
||||
|
||||
const TASK_SCHEMA = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
datei: { type: 'string' },
|
||||
zieldateien: { type: 'array', items: { type: 'string' } },
|
||||
testdateien: { type: 'array', items: { type: 'string' } },
|
||||
zeilen_code: { type: 'integer' },
|
||||
abweichungen_von_uebersicht: { type: 'array', items: { type: 'string' } },
|
||||
offene_punkte: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: ['datei', 'zieldateien', 'testdateien', 'abweichungen_von_uebersicht', 'offene_punkte'],
|
||||
}
|
||||
|
||||
function pruefeArgs(a) {
|
||||
if (!a || typeof a !== 'object') throw new Error('planung: args fehlen')
|
||||
const fehlt = ['projekt', 'planordner', 'gemeinsam', 'taskauftrag', 'tasks'].filter(k => !a[k])
|
||||
if (fehlt.length) throw new Error('planung: args fehlen: ' + fehlt.join(', '))
|
||||
if (!Array.isArray(a.tasks) || a.tasks.length === 0) throw new Error('planung: tasks muss eine nicht leere Liste sein')
|
||||
const nummern = new Set()
|
||||
const dateien = new Set()
|
||||
a.tasks.forEach((t, i) => {
|
||||
if (!t || typeof t !== 'object') throw new Error(`planung: tasks[${i}] ist kein Objekt`)
|
||||
if (!Number.isInteger(t.nr)) throw new Error(`planung: tasks[${i}]: nr muss eine ganze Zahl sein`)
|
||||
if (nummern.has(t.nr)) throw new Error(`planung: Task ${t.nr} steht doppelt in tasks`)
|
||||
nummern.add(t.nr)
|
||||
for (const k of ['thema', 'datei', 'modell']) if (typeof t[k] !== 'string' || !t[k]) throw new Error(`planung: Task ${t.nr}: ${k} fehlt`)
|
||||
if (!MODELLE.includes(t.modell)) throw new Error(`planung: Task ${t.nr}: modell "${t.modell}" nicht in ${MODELLE.join('/')}`)
|
||||
if (dateien.has(t.datei)) throw new Error(`planung: Datei ${t.datei} steht bei zwei Tasks`)
|
||||
dateien.add(t.datei)
|
||||
if (!Array.isArray(t.voraussetzungen) || !t.voraussetzungen.every(n => Number.isInteger(n))) throw new Error(`planung: Task ${t.nr}: voraussetzungen muss eine Liste ganzer Zahlen sein`)
|
||||
if (new Set(t.voraussetzungen).size !== t.voraussetzungen.length) throw new Error(`planung: Task ${t.nr}: eine Voraussetzung steht doppelt`)
|
||||
})
|
||||
a.tasks.forEach(t => t.voraussetzungen.forEach(n => {
|
||||
if (!nummern.has(n)) throw new Error(`planung: Task ${t.nr}: Voraussetzung ${n} gibt es nicht`)
|
||||
}))
|
||||
if (a.erledigt !== undefined && !(Array.isArray(a.erledigt) && a.erledigt.every(n => nummern.has(n)))) throw new Error('planung: erledigt muss eine Liste bekannter Tasknummern sein')
|
||||
if (a.plaetze !== undefined && !(Number.isInteger(a.plaetze) && a.plaetze >= 1 && a.plaetze <= HOECHSTENS)) throw new Error('planung: plaetze muss eine ganze Zahl von 1 bis ' + HOECHSTENS + ' sein')
|
||||
if (a.nachlesen_alle !== undefined && !(Number.isInteger(a.nachlesen_alle) && a.nachlesen_alle >= 1 && a.nachlesen_alle <= 20)) throw new Error('planung: nachlesen_alle muss eine ganze Zahl von 1 bis 20 sein')
|
||||
// Zyklen: Kahn-Verfahren über die Voraussetzungen; was übrig bleibt, hängt im Kreis.
|
||||
const offen = new Map(a.tasks.map(t => [t.nr, t.voraussetzungen.length]))
|
||||
const bereit = a.tasks.filter(t => t.voraussetzungen.length === 0).map(t => t.nr)
|
||||
let geschafft = 0
|
||||
while (bereit.length) {
|
||||
const nr = bereit.pop()
|
||||
geschafft++
|
||||
for (const t of a.tasks) if (t.voraussetzungen.includes(nr)) {
|
||||
offen.set(t.nr, offen.get(t.nr) - 1)
|
||||
if (offen.get(t.nr) === 0) bereit.push(t.nr)
|
||||
}
|
||||
}
|
||||
if (geschafft < a.tasks.length) throw new Error('planung: Zyklus in den Voraussetzungen bei Task ' + [...offen].filter(([, n]) => n > 0).map(([nr]) => nr).join(', '))
|
||||
}
|
||||
|
||||
// Eine Hilfsfunktion ablesen() steht auch in plankritik.js (gleiche Form) sowie in pruefung.js
|
||||
// und welle-mit-pruefung.js (dort nur messend, ohne Rückgabe). Die Regel (Fenster, Stufen,
|
||||
// Rückfall) steht nur in verbrauch.js.
|
||||
const ablesungen = []
|
||||
async function ablesen(anlass, wunsch) {
|
||||
let r = null
|
||||
try {
|
||||
r = await workflow({ scriptPath: VERBRAUCH }, { anlass: 'planung:' + anlass, wunsch })
|
||||
} catch (e) {
|
||||
r = { status: 'nicht_gelesen', grund: 'verbrauch.js nicht ausführbar: ' + (e && e.message) }
|
||||
}
|
||||
if (!r || typeof r.plaetze !== 'number') {
|
||||
r = { status: 'nicht_gelesen', quelle: 'keine', fuenf_stunden: null, woche: null, plaetze: Math.min(wunsch, OHNE_ABLESUNG), gebremst: false, grund: (r && r.grund) || 'verbrauch.js ohne verwertbares Ergebnis' }
|
||||
}
|
||||
ablesungen.push({ anlass, ...r })
|
||||
log(`Verbrauch ${anlass}: ${r.status}, ${r.plaetze} Plätze${r.gebremst ? ', gebremst' : ''} (${r.grund})`)
|
||||
return r
|
||||
}
|
||||
|
||||
function auftrag(a, t, jeNr) {
|
||||
const vor = t.voraussetzungen.map(n => jeNr.get(n).datei)
|
||||
const text = a.taskauftrag
|
||||
.split('{NR}').join(String(t.nr).padStart(2, '0'))
|
||||
.split('{THEMA}').join(t.thema)
|
||||
.split('{DATEI}').join(t.datei)
|
||||
.split('{WELLE}').join(t.welle === undefined ? '' : String(t.welle))
|
||||
.split('{MODELL}').join(t.modell)
|
||||
.split('{VORAUSSETZUNGEN}').join(vor.length ? vor.join(', ') : '(keine)')
|
||||
const lies = vor.length ? ` Die Taskdateien deiner Voraussetzungen liegen fertig im Planordner: ${vor.join(', ')}. Lies sie, bevor du einen Namen, eine Signatur oder einen Import daraus übernimmst.` : ''
|
||||
return `${a.gemeinsam}
|
||||
|
||||
${text}
|
||||
|
||||
Schreib genau eine Datei: ${a.planordner}/${t.datei}.${lies}
|
||||
${a.zusatz ? a.zusatz + '\n' : ''}Übertrage deinen Schlussbericht in die Felder des StructuredOutput-Werkzeugs.`
|
||||
}
|
||||
|
||||
pruefeArgs(args)
|
||||
phase('Taskdateien')
|
||||
const jeNr = new Map(args.tasks.map(t => [t.nr, t]))
|
||||
const status = new Map(args.tasks.map(t => [t.nr, (args.erledigt || []).includes(t.nr) ? 'erledigt' : 'offen']))
|
||||
const reihenfolge = [...args.tasks].sort((x, y) => ((x.welle || 0) - (y.welle || 0)) || (x.nr - y.nr))
|
||||
const wunsch = args.plaetze || VORGABE_PLAETZE
|
||||
const nachlesenAlle = args.nachlesen_alle || NACHLESEN_ALLE
|
||||
const geschrieben = []
|
||||
const gescheitert = []
|
||||
const plaetzeVerlauf = []
|
||||
const laufend = new Map()
|
||||
let gebremst = false
|
||||
let abgebrochen = false
|
||||
let plaetze = 0
|
||||
let fertigSeitAblesung = 0
|
||||
|
||||
const nochOffen = () => [...status.values()].some(s => s === 'offen')
|
||||
|
||||
function starte(t) {
|
||||
status.set(t.nr, 'laeuft')
|
||||
const opts = { label: `planer:task-${String(t.nr).padStart(2, '0')}`, phase: 'Taskdateien', schema: TASK_SCHEMA, agentType: 'planer', model: 'opus' }
|
||||
laufend.set(t.nr, agent(auftrag(args, t, jeNr), opts).then(r => ({ nr: t.nr, r }), e => ({ nr: t.nr, r: null, fehler: e && e.message })))
|
||||
}
|
||||
|
||||
if (nochOffen()) {
|
||||
const v = await ablesen('Anfang', wunsch)
|
||||
plaetzeVerlauf.push(v.plaetze)
|
||||
if (v.gebremst) gebremst = true
|
||||
else plaetze = v.plaetze
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// Tasks hinter einer gescheiterten oder blockierten Voraussetzung starten nie.
|
||||
let geaendert = true
|
||||
while (geaendert) {
|
||||
geaendert = false
|
||||
for (const t of args.tasks) {
|
||||
if (status.get(t.nr) === 'offen' && t.voraussetzungen.some(n => ['gescheitert', 'blockiert'].includes(status.get(n)))) {
|
||||
status.set(t.nr, 'blockiert')
|
||||
geaendert = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!gebremst && !abgebrochen) {
|
||||
for (const t of reihenfolge) {
|
||||
if (laufend.size >= plaetze) break
|
||||
if (status.get(t.nr) === 'offen' && t.voraussetzungen.every(n => ['fertig', 'erledigt'].includes(status.get(n)))) starte(t)
|
||||
}
|
||||
}
|
||||
if (laufend.size === 0) break
|
||||
const { nr, r: roh, fehler: grund } = await Promise.race(laufend.values())
|
||||
laufend.delete(nr)
|
||||
const passt = roh && typeof roh.datei === 'string' && ('/' + roh.datei.replace(/\\/g, '/')).endsWith('/' + jeNr.get(nr).datei)
|
||||
const r = passt ? roh : null
|
||||
const fehler = grund || (roh && !passt ? `Ergebnis nennt ${JSON.stringify(roh.datei)} statt ${jeNr.get(nr).datei}` : null)
|
||||
if (r) {
|
||||
status.set(nr, 'fertig')
|
||||
geschrieben.push({ nr, ...r })
|
||||
} else {
|
||||
status.set(nr, 'gescheitert')
|
||||
gescheitert.push(nr)
|
||||
log(`Task ${nr}: planer ohne Ergebnis${fehler ? ' (' + fehler + ')' : ''}`)
|
||||
if (gescheitert.length >= ABBRUCH_NACH && !abgebrochen) {
|
||||
abgebrochen = true
|
||||
log(`${gescheitert.length} Planer ohne Ergebnis, vermutlich Kontingent: kein weiterer Start`)
|
||||
}
|
||||
}
|
||||
fertigSeitAblesung++
|
||||
if (!gebremst && !abgebrochen && fertigSeitAblesung >= nachlesenAlle && nochOffen()) {
|
||||
fertigSeitAblesung = 0
|
||||
const v = await ablesen(`nach ${geschrieben.length + gescheitert.length} Tasks`, wunsch)
|
||||
plaetzeVerlauf.push(v.plaetze)
|
||||
if (v.gebremst) {
|
||||
gebremst = true
|
||||
log('Planung gebremst: kein weiterer Start, die laufenden Planer enden')
|
||||
} else plaetze = v.plaetze
|
||||
}
|
||||
}
|
||||
if (geschrieben.length + gescheitert.length > 0) await ablesen('Ende', 0)
|
||||
|
||||
const liste = s => args.tasks.filter(t => status.get(t.nr) === s).map(t => t.nr)
|
||||
const blockiert = liste('blockiert')
|
||||
const offen = liste('offen')
|
||||
const zustand = gebremst ? 'gebremst' : abgebrochen ? 'abgebrochen' : gescheitert.length || blockiert.length || offen.length ? 'unvollstaendig' : 'fertig'
|
||||
return {
|
||||
status: zustand,
|
||||
geschrieben,
|
||||
gescheitert,
|
||||
blockiert,
|
||||
offen,
|
||||
verbrauch: ablesungen,
|
||||
plaetze_verlauf: plaetzeVerlauf,
|
||||
naechster_schritt: zustand === 'fertig'
|
||||
? 'plankritik über den ganzen Planordner.'
|
||||
: 'Gescheiterte, blockierte und offene Tasks in einem neuen Lauf nachholen, mit erledigt: [' + args.tasks.filter(t => ['fertig', 'erledigt'].includes(status.get(t.nr))).map(t => t.nr).join(', ') + ']; nicht mit resumeFromRunId fortsetzen.',
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
export const meta = {
|
||||
name: 'pruefung',
|
||||
description: 'Lässt eine fertige Welle vom pruefer mit dem vollen Pflichtprogramm gegen Plan und Zielumgebung prüfen; kritische Änderungen mit xhigh',
|
||||
whenToUse: 'Schritt 4 der Standardschleife nach einer Welle, zusätzlich zur eigenen Gegenprüfung des Koordinators (AI/Regelwerk/codeworkflow.md). Args: {projekt, plan, tasks?, suite?, version?, kritisch?, abschluss?, schwerpunkt?, effort?}. kritisch für sicherheits- oder datenrelevante Änderungen; abschluss fragt, ob eine weitere Runde noch Substanz hat.',
|
||||
description: 'Lässt eine fertige Welle vom pruefer mit dem vollen Pflichtprogramm gegen Plan und Zielumgebung prüfen; kritische Änderungen mit xhigh; schreibt den Planverbrauch vor und nach der Prüfung mit',
|
||||
whenToUse: 'Schritt 4 der Standardschleife nach einer Welle, zusätzlich zur eigenen Gegenprüfung des Koordinators (AI/Regelwerk/codeworkflow.md). Args: {projekt, plan, tasks?, suite?, version?, kritisch?, abschluss?, schwerpunkt?, effort?, verbrauch?}. kritisch für sicherheits- oder datenrelevante Änderungen; abschluss fragt, ob eine weitere Runde noch Substanz hat; verbrauch: false schaltet die Ablesung ab, welle-mit-pruefung liest dann selbst.',
|
||||
phases: [{ title: 'Prüfung', detail: 'ein pruefer' }],
|
||||
}
|
||||
|
||||
// Rollenparameter: model wird immer gesetzt (Stamm). Der Effort kommt aus dem Frontmatter
|
||||
// von .claude/agents/pruefer.md (high). kritisch hebt auf xhigh: In der Messung vom
|
||||
// 2026-09-23 fuhr nur xhigh einen großen eigenen Datenlauf (codeworkflow.md, "Effort je Rolle").
|
||||
// Verbrauch: Vor und nach dem pruefer liest verbrauch.js den Planverbrauch ab, nur zum Messen
|
||||
// (wunsch 0); gebremst wird hier nicht. Als Kind von welle-mit-pruefung kann dieser Workflow
|
||||
// keinen weiteren aufrufen; dort liest der Elternworkflow und gibt verbrauch: false mit.
|
||||
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
|
||||
const VERBRAUCH = 'D:/Claude/.claude/workflows/verbrauch.js'
|
||||
|
||||
const BERICHT = {
|
||||
type: 'object',
|
||||
@@ -42,6 +46,7 @@ function pruefeArgs(a) {
|
||||
if (fehlt.length) throw new Error('pruefung: args fehlen: ' + fehlt.join(', '))
|
||||
if (a.effort !== undefined && !EFFORTS.includes(a.effort)) throw new Error('pruefung: effort "' + a.effort + '" ungültig')
|
||||
if (a.tasks !== undefined && !Array.isArray(a.tasks)) throw new Error('pruefung: tasks muss eine Liste sein')
|
||||
if (a.verbrauch !== undefined && typeof a.verbrauch !== 'boolean') throw new Error('pruefung: verbrauch muss true oder false sein')
|
||||
}
|
||||
|
||||
function auftrag(a) {
|
||||
@@ -52,15 +57,32 @@ ${a.version ? `Aussagen über fremde Software belegst du am tatsächlich eingese
|
||||
${a.abschluss ? 'Das ist die Abschlussprüfung dieser Runde. Sag im Feld weitere_runde, ob eine weitere Runde noch Substanz hat oder nur noch Text umschichtet, und warum.\n' : ''}Übertrage deinen Schlussbericht in die Felder des StructuredOutput-Werkzeugs.`
|
||||
}
|
||||
|
||||
// Eine messende Hilfsfunktion ablesen() steht auch in welle-mit-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) {
|
||||
if (args.verbrauch === false) return
|
||||
let r = null
|
||||
try {
|
||||
r = await workflow({ scriptPath: VERBRAUCH }, { anlass: '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)
|
||||
phase('Prüfung')
|
||||
const opts = { label: 'pruefung' + (args.kritisch ? ':kritisch' : ''), phase: 'Prüfung', schema: BERICHT, agentType: 'pruefer', model: 'opus' }
|
||||
if (args.effort) opts.effort = args.effort
|
||||
else if (args.kritisch) opts.effort = 'xhigh'
|
||||
await ablesen('vor Prüfung')
|
||||
const bericht = await agent(auftrag(args), opts)
|
||||
await ablesen('nach Prüfung')
|
||||
if (!bericht) throw new Error('pruefung: der pruefer hat keinen Bericht geliefert')
|
||||
|
||||
const RANG = { blockierend: 0, wichtig: 1, klein: 2 }
|
||||
const zaehlung = { blockierend: 0, wichtig: 0, klein: 0 }
|
||||
bericht.befunde.forEach(b => { zaehlung[b.schweregrad]++ })
|
||||
return { effort: opts.effort || '(Frontmatter)', zaehlung, ...bericht, befunde: [...bericht.befunde].sort((x, y) => RANG[x.schweregrad] - RANG[y.schweregrad]) }
|
||||
return { effort: opts.effort || '(Frontmatter)', zaehlung, ...bericht, befunde: [...bericht.befunde].sort((x, y) => RANG[x.schweregrad] - RANG[y.schweregrad]), verbrauch: ablesungen }
|
||||
@@ -0,0 +1,118 @@
|
||||
export const meta = {
|
||||
name: 'verbrauch',
|
||||
description: 'Liest den Planverbrauch (5-Stunden-Fenster, Woche) über get_usage und rechnet daraus, wie viele Opus-Agenten gleichzeitig laufen dürfen',
|
||||
whenToUse: 'Vor jeder Opus-Staffel eines Workflows, als Kind aufgerufen: workflow({ scriptPath: "D:/Claude/.claude/workflows/verbrauch.js" }, { anlass, wunsch }). Args: {anlass, wunsch: ganze Zahl ab 0, probe?}. wunsch 0 heißt nur messen. Rückgabe {status: ok|nicht_gelesen|unbekannte_fenster, quelle, fuenf_stunden, woche, woche_opus, plaetze, gebremst, grund, fenster}. Ohne lesbare Ablesung gilt die alte Obergrenze zwei. Regel: AI/Regelwerk/codeworkflow.md, "Verbrauch und Opus-Plätze".',
|
||||
phases: [{ title: 'Verbrauch', detail: 'ein Haiku-Agent liest get_usage ab, das Skript entscheidet' }],
|
||||
}
|
||||
|
||||
// Geltungsbereich (AI/Regelwerk/pruefwerkzeuge.md): Das Skript liest den Planverbrauch und
|
||||
// rechnet daraus, wie viele Opus-Agenten gleichzeitig laufen dürfen. Es ist eine Bremse, kein
|
||||
// Nachweis. Woher die Antworten kommen:
|
||||
// - Welche Fenster zählen und wie sie heißen: Quelle. get_usage in der Desktop-App lieferte am
|
||||
// 2026-09-24 "5-hour limit", "Weekly · all models" und "Weekly · Fable", je mit percentUsed
|
||||
// und resetsAt. Von 19 typisierten Limit-Abbrüchen im Session-Speicher (22.08. bis 20.09.)
|
||||
// waren 16 five_hour und 3 seven_day, keiner seven_day_opus. Ein Fenster "Weekly · Opus" zählt
|
||||
// trotzdem wie die Woche, falls es auftaucht, weil die Agenten, um die es geht, auf Opus laufen.
|
||||
// - Die Stufen: Projektvorgabe, vorläufig (Pascal, 2026-09-24). Sie stehen nur hier und werden
|
||||
// nach der Messreihe mit .vault-tools/workflow-pruefung/verbrauch_reihe.py nachgestellt.
|
||||
// - Ohne lesbare Ablesung: Projektvorgabe, die bisherige Stammregel von zwei Opus-Agenten.
|
||||
// Was das Skript nicht sieht: Agenten, die nach der Ablesung in anderen Sitzungen starten;
|
||||
// laufende Agenten, die nach der Ablesung weiter verbrauchen; Sitzungen ohne get_usage
|
||||
// (außerhalb der Desktop-App). Das Urteil rechnet das Skript, der Haiku-Agent liest nur ab.
|
||||
|
||||
const HARTE_OBERGRENZE = 4
|
||||
const OHNE_ABLESUNG = 2
|
||||
// Je Stufe [Schwelle in Prozent, Plätze unterhalb der Schwelle]; ab der letzten Schwelle 0.
|
||||
const STUFEN_FUENF_STUNDEN = [[50, 4], [70, 2], [85, 1]]
|
||||
const STUFEN_WOCHE = [[70, 4], [85, 2], [95, 1]]
|
||||
|
||||
const FUENF_STUNDEN = /^\s*5[- ]?hour/i
|
||||
const WOCHE_ALLE = /^\s*weekly\b.*\ball models\b/i
|
||||
const WOCHE_OPUS = /^\s*weekly\b.*\bopus\b/i
|
||||
|
||||
const ABLESUNG = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
verfuegbar: { type: 'boolean' },
|
||||
status: { type: 'string' },
|
||||
plan: { type: 'string' },
|
||||
fenster: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: { label: { type: 'string' }, percentUsed: { type: 'number' }, resetsAt: { type: 'string' } },
|
||||
required: ['label', 'percentUsed', 'resetsAt'],
|
||||
},
|
||||
},
|
||||
fehler: { type: 'string' },
|
||||
},
|
||||
required: ['verfuegbar', 'status', 'plan', 'fenster', 'fehler'],
|
||||
}
|
||||
|
||||
const AUFTRAG = `Lade mit dem Werkzeug ToolSearch das Werkzeug mcp__ccd_session_mgmt__get_usage (Abfrage "select:mcp__ccd_session_mgmt__get_usage") und rufe es einmal ohne Parameter auf.
|
||||
Übertrage aus dem Ergebnis plan.status nach status und plan.plan nach plan. Übertrage jeden Eintrag aus plan.windows mit label, percentUsed und resetsAt unverändert nach fenster, auch Einträge, die dir unwichtig erscheinen. Setze verfuegbar auf true und fehler auf "".
|
||||
Ist das Werkzeug nicht verfügbar oder scheitert der Aufruf, setze verfuegbar auf false, status auf "fehler", plan auf "", fenster auf [] und schreibe die Fehlermeldung wörtlich nach fehler.
|
||||
Tu sonst nichts. Übertrage das Ergebnis in die Felder des StructuredOutput-Werkzeugs.`
|
||||
|
||||
function pruefeArgs(a) {
|
||||
if (!a || typeof a !== 'object') throw new Error('verbrauch: args fehlen')
|
||||
if (typeof a.anlass !== 'string' || !a.anlass.trim()) throw new Error('verbrauch: anlass fehlt')
|
||||
if (!Number.isInteger(a.wunsch) || a.wunsch < 0) throw new Error('verbrauch: wunsch muss eine ganze Zahl ab 0 sein, ist ' + JSON.stringify(a.wunsch))
|
||||
if (a.probe !== undefined && (a.probe === null || typeof a.probe !== 'object' || Array.isArray(a.probe))) throw new Error('verbrauch: probe muss ein Objekt sein')
|
||||
}
|
||||
|
||||
function stufe(prozent, stufen) {
|
||||
for (const [schwelle, plaetze] of stufen) if (prozent < schwelle) return plaetze
|
||||
return 0
|
||||
}
|
||||
|
||||
// Ordnet die Fenster ein. Ein Fenster, das als 5 Stunden oder Woche erkannt wird, aber keine
|
||||
// Prozentzahl ab 0 trägt, ist ein Befund und wird nicht still übergangen. Über 100 % heißt
|
||||
// überschritten und bremst wie jeder Wert über der letzten Stufe.
|
||||
function einordnen(fenster) {
|
||||
if (!Array.isArray(fenster)) return { fehler: 'fenster ist keine Liste' }
|
||||
const e = { fuenf: null, woche: null, opus: null, andere: [] }
|
||||
for (const f of fenster) {
|
||||
if (!f || typeof f !== 'object' || typeof f.label !== 'string') return { fehler: 'Fenster ohne label: ' + JSON.stringify(f) }
|
||||
const art = FUENF_STUNDEN.test(f.label) ? 'fuenf' : WOCHE_ALLE.test(f.label) ? 'woche' : WOCHE_OPUS.test(f.label) ? 'opus' : null
|
||||
if (!art) { e.andere.push(f.label); continue }
|
||||
const p = f.percentUsed
|
||||
if (typeof p !== 'number' || !Number.isFinite(p) || p < 0) return { fehler: `${f.label}: percentUsed ${JSON.stringify(p)} ist keine Zahl ab 0` }
|
||||
const neu = { prozent: p, zuruecksetzung: typeof f.resetsAt === 'string' ? f.resetsAt : null }
|
||||
if (!e[art] || e[art].prozent < p) e[art] = neu
|
||||
}
|
||||
const fehlt = [!e.fuenf && '5-hour limit', !e.woche && 'Weekly all models'].filter(Boolean)
|
||||
if (fehlt.length) return { fehler: 'Fenster fehlt: ' + fehlt.join(', ') + (e.andere.length ? '; gelesen: ' + e.andere.join(', ') : '') }
|
||||
return e
|
||||
}
|
||||
|
||||
pruefeArgs(args)
|
||||
phase('Verbrauch')
|
||||
const wunsch = args.wunsch
|
||||
const deckel = wunsch > HARTE_OBERGRENZE ? `; wunsch ${wunsch} auf die harte Obergrenze ${HARTE_OBERGRENZE} gesenkt` : ''
|
||||
let quelle = 'probe'
|
||||
let ablesung = args.probe
|
||||
if (ablesung === undefined) {
|
||||
quelle = 'agent'
|
||||
ablesung = await agent(AUFTRAG, { label: 'verbrauch:' + args.anlass, phase: 'Verbrauch', schema: ABLESUNG, model: 'haiku' })
|
||||
}
|
||||
|
||||
function ohneAblesung(status, grund) {
|
||||
const plaetze = Math.min(wunsch, OHNE_ABLESUNG)
|
||||
log(`Verbrauch ${args.anlass}: ${status} (${grund}); ${plaetze} Plätze nach der alten Obergrenze`)
|
||||
return { status, quelle, fuenf_stunden: null, woche: null, woche_opus: null, plaetze, gebremst: false, grund: grund + deckel, fenster: ablesung && Array.isArray(ablesung.fenster) ? ablesung.fenster : null }
|
||||
}
|
||||
|
||||
if (!ablesung || typeof ablesung !== 'object') return ohneAblesung('nicht_gelesen', 'kein Ergebnis vom Ableseagenten')
|
||||
if (ablesung.verfuegbar !== true || ablesung.status !== 'ok') return ohneAblesung('nicht_gelesen', `get_usage: verfuegbar ${ablesung.verfuegbar}, status ${ablesung.status}${ablesung.fehler ? ', ' + ablesung.fehler : ''}`)
|
||||
const e = einordnen(ablesung.fenster)
|
||||
if (e.fehler) return ohneAblesung('unbekannte_fenster', e.fehler)
|
||||
|
||||
const wocheProzent = Math.max(e.woche.prozent, e.opus ? e.opus.prozent : 0)
|
||||
const grenzeFuenf = stufe(e.fuenf.prozent, STUFEN_FUENF_STUNDEN)
|
||||
const grenzeWoche = stufe(wocheProzent, STUFEN_WOCHE)
|
||||
const plaetze = Math.min(wunsch, grenzeFuenf, grenzeWoche, HARTE_OBERGRENZE)
|
||||
const gebremst = wunsch > 0 && plaetze === 0
|
||||
const grund = `5 Stunden ${e.fuenf.prozent} % (bis ${grenzeFuenf} Plätze), Woche ${wocheProzent} % (bis ${grenzeWoche} Plätze)${e.opus ? ', Opus-Woche ' + e.opus.prozent + ' %' : ''}${deckel}`
|
||||
log(`Verbrauch ${args.anlass}: ${grund}; ${plaetze} von ${wunsch} Plätzen${gebremst ? ', gebremst' : ''}`)
|
||||
return { status: 'ok', quelle, fuenf_stunden: e.fuenf, woche: e.woche, woche_opus: e.opus, plaetze, gebremst, grund, fenster: ablesung.fenster }
|
||||
@@ -1,7 +1,7 @@
|
||||
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',
|
||||
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?}. Ohne kopie wird sie vor der Welle gebaut; ohne nennt dann .git und Geheimnisdateien. Ersetzt die eigene Gegenprüfung des Koordinators nicht.',
|
||||
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' },
|
||||
@@ -14,8 +14,12 @@ export const meta = {
|
||||
// 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.
|
||||
// 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'
|
||||
@@ -42,6 +46,7 @@ function pruefeArgs(a) {
|
||||
})
|
||||
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')
|
||||
@@ -64,6 +69,20 @@ function kopfUndEnde(text, kopf, ende) {
|
||||
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)
|
||||
@@ -113,8 +132,8 @@ 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(', ')
|
||||
if (pruefungGrund) log('Prüfung übersprungen: ' + pruefungGrund)
|
||||
else {
|
||||
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 = [
|
||||
@@ -122,25 +141,55 @@ else {
|
||||
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', {
|
||||
projekt: args.projekt, plan: args.planordner, tasks: taskdateien, suite: args.suite,
|
||||
kritisch: args.kritisch, abschluss: args.abschluss, version: args.version, effort: args.effort, schwerpunkt,
|
||||
})
|
||||
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: temp ? `${temp} bleibt liegen (Projektkopie ohne die Ausschlüsse); löschen, wenn der Diff nicht mehr gebraucht wird` : null,
|
||||
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.',
|
||||
}
|
||||
Reference in new issue
Block a user