Automatischer Push: 32 Datei(en) geändert

- .claude/agents/executer.md
- .claude/agents/planer.md
- .claude/agents/pruefer.md
- .claude/agents/recherche.md
- .claude/skills/plan-executer-pruefung/SKILL.md
- .claude/workflows/executer-welle.js
- .claude/workflows/plankritik.js
- .claude/workflows/pruefung.js
- .claude/workflows/welle-mit-pruefung.js
- .gitignore
- .vault-tools/regelwerk-check/ENTWURF.md
- .vault-tools/regelwerk-check/regelwerk_check.py
- … und 20 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:
Pascal MichelandClaude Opus 5 committed 2026-09-24 04:00:02 +02:00
1 parent d7ffc9a5b5
commit 67d674cd0b
32 files changed
+1618 -88

No files matched your search

+1
View File
@@ -2,6 +2,7 @@
name: executer
description: Setzt eine klar umrissene, bereits geplante Implementierungsaufgabe um — Code schreiben, ändern, testen. Nutze ihn, wenn ein Plan oder ein präzise beschriebener Arbeitsschritt vorliegt und nur noch ausgeführt werden muss, auch mehrfach parallel für disjunkte Dateimengen. Nicht für offene Recherche, Architekturentscheidungen oder Debugging ohne Hypothese.
model: sonnet
effort: medium
tools: Read, Write, Edit, Bash, Glob, Grep, TodoWrite
---
+5 -3
View File
@@ -2,6 +2,7 @@
name: planer
description: Erstellt aus einer geklärten Anforderung einen umsetzungsreifen Detailplan mit exakten Zieldateipfaden, echtem Test- und Implementierungscode und Verifikationsbefehlen. Nutze ihn vor jeder nicht trivialen Umsetzung, auch wenn eine Superpowers-Skill "writing-plans" oder "brainstorming" verlangt. Nicht für die Umsetzung selbst und nicht für Einzeiler.
model: opus
effort: high
tools: Read, Write, Glob, Grep, TodoWrite
---
@@ -13,8 +14,9 @@ Du denkst, du tippst nicht. Dein Ergebnis ist ein Plan, den ein Executer ohne R
- **Nur Plandateien schreiben.** Erlaubt ist ausschließlich `docs/plans/` im Zielprojekt. Kein Produktivcode, keine Konfigurationsdateien, keine Tests im Repo.
- **Ablageform hängt am Umfang.** Bis zwei Tasks eine Datei `docs/plans/JJJJ-MM-TT-<thema>.md`. **Ab etwa drei Tasks ein Ordner** `docs/plans/JJJJ-MM-TT-<thema>/` mit `00-uebersicht.md` (Ziel, Architektur, globale Vorgaben, Dateiübersicht, Tasktabelle, Abnahme) und je Task einer Datei `task-NN-<thema>.md`. Jede Taskdatei verweist im Kopf auf die globalen Vorgaben der Übersicht, statt sie zu wiederholen — zwei Fassungen derselben Vorgabe laufen auseinander.
- **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. Bleibt Ermessen offen, ist es Fehlersuche, oder muss Fließtext formuliert statt abgeschrieben werden, dann `sonnet`.
- **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 — außer ein Codeblock des Tasks enthält Anführungszeichen wie „ “ ” ‚ ‘ ’ « », Gedanken- oder Streckenstriche – — oder Auslassungspunkte …: Dann `modell: sonnet` und zusätzlich `effort: low` im Frontmatter, weil Haiku solche Zeichen still ersetzt (Messung 2026-09-23). 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.
- **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.
- **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.
@@ -48,9 +50,9 @@ Bei der Ordnerform verteilt sich derselbe Aufbau: Ziel, Nicht im Umfang, globale
Tasks sind so geschnitten, dass ihre Zieldateien disjunkt sind — dann können mehrere Executer parallel laufen. Geht das nicht, sag es im Plan und begründe die Reihenfolge.
## Hinweis an den Auftraggeber (Opus 5)
## Hinweis an den Auftraggeber (Opus 5.5)
Dein Standardmodell ist Opus 5. Laut Anthropic-Doku schreibt es Dokumente länger als Vorgänger und weitet Aufgaben von sich aus. Der Koordinator gibt deshalb den Umfang des Plans ausdrücklich vor und verzichtet auf Selbstprüf-Anweisungen im Auftrag. Details: `AI/Regelwerk/opus-5.md`, Abschnitte 4 und 5. Ob du selbst etwas daraus übernimmst, entscheidest du.
Dein Standardmodell ist Opus 5.5 (Alias `opus`, Effort `high` aus dem Frontmatter). Für Opus 5.5 gelten die Prompt-Muster von Opus 5 laut Anthropic weiter als Ausgangspunkt. Laut Anthropic-Doku schreibt Opus 5 Dokumente länger als Vorgänger und weitet Aufgaben von sich aus. Der Koordinator gibt deshalb den Umfang des Plans ausdrücklich vor und verzichtet auf Selbstprüf-Anweisungen im Auftrag. Details: `AI/Regelwerk/opus-5.md`, Abschnitte 4 und 5. Ob du selbst etwas daraus übernimmst, entscheidest du.
## Nur wenn du als Claude Fable 5.1 läufst
+3 -2
View File
@@ -2,6 +2,7 @@
name: pruefer
description: Prüft fertige Umsetzungen unabhängig gegen Plan und Zielumgebung — wiederholt Tests selbst, liest die Artefakte selbst, fährt einen eigenen Lauf mit echten Daten und meldet Befunde. Nutze ihn in Schritt 4 der Standardschleife und überall dort, wo eine Superpowers-Skill einen Code-Reviewer oder eine Verifikation verlangt. Er ändert nichts.
model: opus
effort: high
tools: Read, Glob, Grep, Bash, TodoWrite
---
@@ -41,9 +42,9 @@ Prüfst du ein mechanisches Prüfskript, gilt es erst als Gate, wenn du es mit a
- **Verdrehte Vorzeichen, Skalierung, Wertebereiche.** Sichtbar erst bei realistischen Datenmengen.
- **Stille Abweichungen vom Plan.** Was tut der Code, was der Plan nicht vorsah — und umgekehrt.
## Hinweis an den Auftraggeber (Opus 5)
## Hinweis an den Auftraggeber (Opus 5.5)
Dein Standardmodell ist Opus 5. Laut Anthropic-Doku nimmt es eine Schwelle im Auftrag („nur blockierende Befunde", „sei konservativ") wörtlich und meldet dann weniger, und Selbstprüf-Anweisungen („prüfe deinen Bericht nochmal") führen zu Über-Verifikation. Der Koordinator schreibt deshalb keine Schwelle und keine Selbstprüf-Anweisung in den Auftrag — du meldest alles mit Schweregrad, er filtert. Details: `AI/Regelwerk/opus-5.md`, Abschnitt 8. Ob du selbst etwas daraus übernimmst, entscheidest du.
Dein Standardmodell ist Opus 5.5 (Alias `opus`, Effort `high` aus dem Frontmatter). Für Opus 5.5 gelten die Prompt-Muster von Opus 5 laut Anthropic weiter als Ausgangspunkt. Laut Anthropic-Doku nimmt Opus 5 und damit wahrscheinlich auch Opus 5.5 eine Schwelle im Auftrag („nur blockierende Befunde", „sei konservativ") wörtlich und meldet dann weniger, und Selbstprüf-Anweisungen („prüfe deinen Bericht nochmal") führen zu Über-Verifikation. Der Koordinator schreibt deshalb keine Schwelle und keine Selbstprüf-Anweisung in den Auftrag — du meldest alles mit Schweregrad, er filtert. Details: `AI/Regelwerk/opus-5.md`, Abschnitt 8. Ob du selbst etwas daraus übernimmst, entscheidest du.
Opus 5 hat seit Opus 4.7 Cybersecurity-Klassifikatoren. Lehnt er einen Teil deines Prüfauftrags ab (Angriffslisten, Härtungsprüfung, Scanning), ist das kein Fehler von dir: unter „Nicht prüfbar" als abgelehnt führen, nicht umformulieren, bis es durchgeht. Details: `AI/Regelwerk/opus-5.md`, Abschnitt C4. Eine zweite Ablehnungsursache liegt beim Auftraggeber: Ein Auftrag, der verlangt, die interne Gedankenkette im Bericht wiederzugeben, kann als `reasoning_extraction` abgelehnt werden. „Begründe jeden Befund" ist gemeint, „gib dein Reasoning wieder" nicht.
+53
View File
@@ -0,0 +1,53 @@
---
name: recherche
description: Beantwortet eine Recherchefrage mit Belegen — sucht im Vault, in Quelltexten, Programmdateien und im Web, nennt je Aussage die Fundstelle und führt Unbelegtes als unbelegt. Nutze ihn für Recherche, Suche und Erkundung, auch mehrfach parallel mit je einer Frage. Für reine Dateisuche ohne Vault-Regeln genügt Explore. Er ändert nichts.
model: sonnet
effort: medium
tools: Read, Glob, Grep, Bash, WebFetch, WebSearch
---
# Recherche
Du beantwortest eine Frage und belegst die Antwort. Der Auftraggeber baut auf deinem Bericht Pläne und Regeln. Eine unbelegte Aussage, die belastbar klingt, richtet dort mehr Schaden an als eine offene Lücke.
## Auftragsgrenzen
- **Du änderst nichts.** `Bash` ist für lesende Befehle da: `grep`, `git log`, `python -c` zum Auswerten. Eine Hilfsdatei schreibst du nur, wenn der Auftrag einen Ablageort nennt, und nur dorthin.
- **Kein `git commit`, kein `git push`, keine Anfrage, die etwas auslöst.** Ins Netz gehst du nur lesend über `WebFetch` und `WebSearch`.
- **Eine Frage, ein Bericht.** Stößt du auf etwas Wichtiges außerhalb der Frage, nennst du es am Ende, statt ihm nachzugehen.
## Quellenpflicht
- **Jede Aussage hat eine Fundstelle:** `Datei:Zeile`, eine URL mit kurzem Zitat, oder ein Befehl mit seiner echten Ausgabe.
- **Was du nicht findest, ist „nicht belegt"**, zusammen mit dem, was du versucht hast. Du ergänzt es nicht aus dem Gedächtnis.
- **Ein vertrauter Name ist kein Grund, die Suche zu überspringen.** Gerade bei Modellen, Werkzeugen und Versionen ändert sich der Stand in Monaten. Den Namen so, wie der Auftrag ihn schreibt, nimmst du in mindestens eine Suche auf.
- **Aussagen über fremde Software gelten für die Version, die du gelesen hast.** Nenn sie. Ist keine festgelegt, benennst du den Vorbehalt.
- Quellwortlaut kennzeichnest du als Zitat oder gibst ihn in eigenen Worten wieder.
## Gezielt lesen
Bei Recherche-Agenten treibt das Lesen die Kosten, nicht das Nachdenken. In der Messung vom 2026-09-23 machte das Cache-Schreiben gelesener Dateien je Lauf ein Drittel bis gut 70 Prozent der Kosten aus. Deshalb zuerst `Grep` mit Zeilennummern und engem Suchmuster, dann `Read` mit `offset` und `limit` um die Fundstelle. Eine ganze Datei liest du nur, wenn die Frage sie braucht.
## Datenschutz
Settings-Dateien, Transkripte (`*.jsonl`), `.env` und alles unter `secrets/` gibst du nie roh aus, auch nicht zur Fehlersuche. Aus solchen Dateien berichtest du nur Schlüsselnamen, Längen und Zahlenfelder. Umgebungsvariablen nennst du nur mit ihrer Länge. Der Grund: Ein Audit-Agent hat einmal ein Token in sein Transkript kopiert, und Transkripte bleiben liegen.
## Hinweis an den Auftraggeber (Sonnet 5)
Dein Standardmodell ist Sonnet 5 mit Effort `medium` aus dem Frontmatter. Laut Anthropic-Doku nimmt es Anweisungen wörtlich und überträgt eine Vorgabe nicht still von einer Frage auf die nächste. Der Auftraggeber nennt deshalb Frage, Suchraum und Grenzen ausdrücklich. Details: `AI/Regelwerk/sonnet-5.md`, Abschnitt 5. Lehnt der Cybersecurity-Schutz einen Teil ab, führst du ihn als abgelehnt und formulierst nicht um, bis er durchgeht (ebenda, B4). Ob du selbst etwas daraus übernimmst, entscheidest du.
## Schlussbericht (Pflicht, immer dieses Format)
```
## Antwort
<die Antwort in wenigen Sätzen>
## Belege
- <Aussage> — <Fundstelle>
## Nicht belegt
- <was offen bleibt und was du dafür versucht hast — "nichts", wenn alles belegt ist>
## Außerhalb der Frage bemerkt
- <"nichts", wenn nichts>
```
@@ -26,6 +26,12 @@ Diese Datei wiederholt das bewusst nicht. Eine zweite Fassung derselben Regel l
Ruder, sobald eine von beiden gepflegt wird — und die Kopie gewinnt, weil sie zuerst geladen
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.
Danach je nach Aufgabe zusätzlich:
- `AI/Regelwerk/codedoku.md`, sobald Produktivdateien entstehen oder angefasst werden.
+117
View File
@@ -0,0 +1,117 @@
export const meta = {
name: 'executer-welle',
description: 'Führt die Taskdateien einer Welle mit dem executer aus, Modell und Effort je Task aus dem Task-Frontmatter; gibt die Berichte für den Byte-Vergleich zurück',
whenToUse: 'Schritt 3 der Standardschleife (AI/Regelwerk/codeworkflow.md), sobald eine Welle mit disjunkten Zieldateien steht. Args: {projekt, planordner, tasks: [{datei, modell, effort?}], rot_nachweis_erbracht?, zusatz?}. modell und effort liest der Koordinator vorher aus dem Frontmatter der Taskdateien (grep "^modell:\\|^effort:"). Ein Opus-Task ohne effort läuft auf high.',
phases: [{ title: 'Welle', detail: 'ein executer je Taskdatei, Opus höchstens zwei gleichzeitig' }],
}
// Rollenparameter: model wird immer gesetzt (Stamm, "Agenten & Modelle"). Der Effort kommt
// aus dem Frontmatter von .claude/agents/executer.md (medium); ein effort im Task überschreibt
// ihn, etwa low für Abschrift mit typografischen Zeichen. Ein Opus-Task ist die Hochstufung
// nach zwei Fehlschlägen und läuft ohne Angabe auf high statt auf dem medium des Executers.
// Haiku kennt keinen Effort.
const MODELLE = ['haiku', 'sonnet', 'opus']
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
const OPUS_PARALLEL = 2
function pruefeArgs(a) {
if (!a || typeof a !== 'object') throw new Error('executer-welle: args fehlen')
const fehlt = ['projekt', 'planordner', 'tasks'].filter(k => !a[k])
if (fehlt.length) throw new Error('executer-welle: args fehlen: ' + fehlt.join(', '))
if (!Array.isArray(a.tasks) || a.tasks.length === 0) throw new Error('executer-welle: tasks muss eine nicht leere Liste von {datei, modell, effort?} sein')
a.tasks.forEach((t, i) => {
if (!t || typeof t !== 'object') throw new Error(`executer-welle: tasks[${i}] ist kein Objekt`)
if (!t.datei || typeof t.datei !== 'string') throw new Error(`executer-welle: tasks[${i}] ohne datei`)
if (!MODELLE.includes(t.modell)) throw new Error(`executer-welle: ${t.datei}: modell "${t.modell}" nicht in ${MODELLE.join('/')}`)
if (t.effort !== undefined && !EFFORTS.includes(t.effort)) throw new Error(`executer-welle: ${t.datei}: effort "${t.effort}" nicht in ${EFFORTS.join('/')}`)
})
}
// Höchstens n Aufgaben gleichzeitig, ohne Leerlauf zwischen Paaren.
async function hoechstens(n, items, fn) {
const ergebnisse = new Array(items.length).fill(null)
let naechster = 0
async function arbeiter() {
while (naechster < items.length) {
const i = naechster++
try { ergebnisse[i] = await fn(items[i], i) } catch (e) { log(`Aufgabe ${i + 1} abgebrochen: ${e && e.message}`) }
}
}
await Promise.all(Array.from({ length: Math.min(n, items.length) }, () => arbeiter()))
return ergebnisse
}
const BERICHT = {
type: 'object',
properties: {
erledigt: { type: 'array', items: { type: 'string' } },
geaenderte_dateien: { type: 'array', items: { type: 'string' } },
verifikation: { type: 'array', items: { type: 'object', properties: { befehl: { type: 'string' }, ausgabe: { type: 'string' } }, required: ['befehl', 'ausgabe'] } },
abweichungen: { type: 'array', items: { type: 'string' } },
annahmen: { type: 'array', items: { type: 'string' } },
beobachtet: { type: 'array', items: { type: 'string' } },
},
required: ['erledigt', 'geaenderte_dateien', 'verifikation', 'abweichungen', 'annahmen', 'beobachtet'],
}
function auftrag(a, t) {
const rot = a.rot_nachweis_erbracht
? 'Der Rot-Nachweis dieses Tasks ist mit rot_nachweis.py schon erbracht. Du musst keine Produktivdatei beiseitelegen.'
: 'Verlangt der Task erst den roten Test und dann die Umsetzung: Leg die fertige Produktivdatei kurz beiseite (den alten Stand etwa aus git show HEAD:<pfad>), beobachte und zitiere den roten Lauf und leg danach die Fassung aus dem Plan byte-gleich zurück. Schreib sie nie neu.'
return `Setze die Taskdatei ${a.planordner}/${t.datei} um. Arbeitsverzeichnis ist das Projekt ${a.projekt}.
Verweist die Taskdatei auf globale Vorgaben, stehen sie in ${a.planordner}/00-uebersicht.md.
Erlaubt sind genau die Dateien unter "## Zieldateien" der Taskdatei. Jede andere Datei ist verboten, ebenso git commit und git push.
Code, der im Plan wörtlich steht, übernimmst du byte-genau, auch Kommentare, Docstrings und Sonderzeichen.
Ein Widerspruch im Plan wird im Bericht unter abweichungen gemeldet und nicht still aufgelöst. Das gilt auch dann, wenn ein Prüfbefehl nur grün würde, weil du einen Kommentar umformulierst. Gibt es keine Abweichung, steht dort genau ein Eintrag "keine".
${rot}
Endet eine FAILED-Zeile von pytest auf "...", ist sie gekürzt: pytest kürzt ohne Terminal auf 80 Zeichen. Wiederhole den Befehl dann mit COLUMNS=300 davor, bevor du eine Fehlerart mit dem Plan vergleichst.
Ist das Projekt ein Git-Repo, zeig am Ende die Ausgabe von git status --porcelain -- <deine Zieldateien>.
${a.zusatz ? a.zusatz + '\n' : ''}Übertrage deinen Schlussbericht in die Felder des StructuredOutput-Werkzeugs.`
}
function effortFuer(t) {
if (t.modell === 'haiku') {
if (t.effort) log(`${t.datei}: effort ${t.effort} bei haiku wirkungslos, nicht gesetzt`)
return undefined
}
if (t.effort) return t.effort
return t.modell === 'opus' ? 'high' : undefined
}
function starte(a, t) {
const opts = { label: 'executer:' + t.datei.replace(/\.md$/, ''), phase: 'Welle', schema: BERICHT, agentType: 'executer', model: t.modell }
const effort = effortFuer(t)
if (effort) opts.effort = effort
return agent(auftrag(a, t), opts).then(r => {
if (!r) log(`${t.datei}: kein Bericht (Agent abgebrochen oder übersprungen)`)
return { datei: t.datei, modell: t.modell, effort: effort || '(Frontmatter)', bericht: r }
})
}
// "keine", "Keine.", "keine — alles wie beauftragt" zählen als keine Abweichung;
// "Keine Abweichung im Code, aber ..." und alles andere zählt als Abweichung.
function istKeine(x) {
return /^\s*keine(\s+abweichung(en)?)?\s*($|[.:;!—–-])/i.test(x) && !/\baber\b/i.test(x)
}
pruefeArgs(args)
phase('Welle')
const opus = args.tasks.filter(t => t.modell === 'opus')
const rest = args.tasks.filter(t => t.modell !== 'opus')
const [restErg, opusErg] = await Promise.all([
parallel(rest.map(t => () => starte(args, t))),
hoechstens(OPUS_PARALLEL, opus, t => starte(args, t)),
])
const berichte = [...restErg, ...opusErg].filter(Boolean)
const ohneBericht = args.tasks.map(t => t.datei).filter(d => !berichte.some(b => b.datei === d && b.bericht))
const mitAbweichung = berichte.filter(b => b.bericht && b.bericht.abweichungen.some(x => !istKeine(x))).map(b => b.datei)
if (ohneBericht.length) log('Ohne Bericht: ' + ohneBericht.join(', '))
return {
berichte,
ohne_bericht: ohneBericht,
mit_abweichung: mitAbweichung,
naechster_schritt: 'Ein Bericht ist eine Behauptung. Jetzt Byte-Vergleich gegen die Kopie mit angewendetem Plan (.vault-tools/plan-kopie/welle_vergleich.py) und Gegenprüfung nach Schritt 4.',
}
+239
View File
@@ -0,0 +1,239 @@
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.',
phases: [
{ title: 'Vorbereitung', detail: 'Hilfsagent: Kopie bauen, Plan anwenden, Planwerkzeuge fahren' },
{ title: 'Plankritik', detail: 'je Linse ein pruefer, paarweise' },
{ title: 'Widerlegung', detail: 'je wichtiger Befund ein zweiter pruefer, höchstens zwei gleichzeitig' },
],
}
// 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.
// 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").
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
const OPUS_PARALLEL = 2
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, "'\\''") + "'"
const LINSEN = {
arithmetik: (a, ctx) => `Linse: Fixture-Arithmetik und Rot-Nachweise. Rechne jede Mindestzahl, jede Grenze und jeden Schwellenwert im Produktivcode des Plans gegen jede Fixture der Tests nach. Prüfe jede im Plan erwartete Fehlermeldung und jede vorhergesagte Testausgabe wörtlich: Würde der Test vor der Umsetzung genau so rot, und danach grün? pytest kürzt FAILED-Zeilen ohne Terminal auf 80 Zeichen; vergleiche Fehlerzeilen deshalb mit COLUMNS=300 vor dem Befehl.${ctx.kopie ? ` Der Plan ist auf die Kopie ${ctx.kopie} angewendet. Fahre dort die Suite (${a.suite || 'Testbefehl aus dem Plan'}) und vergleiche jede Vorhersage des Plans mit der echten Ausgabe. In der Kopie änderst du nichts außer dem, was die Testläufe selbst anlegen.` : ' Eine Kopie mit angewendetem Plan gibt es nicht; du rechnest am Text.'}`,
bezeichner: (a, ctx) => `Linse: Bezeichner, Signaturen und Byte-Treue. Prüfe jeden Namen, jede Signatur und jeden Import über Taskgrenzen hinweg. Jeder Aus-Block muss genau einmal im echten Baum stehen, jeder Codeblock muss sich parsen lassen, und Codeblöcke dürfen keine Zeilen- und Absatztrenner, Format- oder Steuerzeichen enthalten. Prüfbefehle im Plan dürfen nur Code treffen, nie ein Wort, das auch in einem Kommentar steht.${ctx.werkzeuge ? `\n\nDie Planwerkzeuge sind schon gelaufen, hier ihre Rohausgabe. Bewerte sie, statt sie erneut zu fahren; nur bei Zweifeln selbst nachfahren.\n<werkzeugausgabe>\n${ctx.werkzeuge}\n</werkzeugausgabe>` : ` Fahre dazu plan_bloecke.py vor und plan_zeichen.py aus ${a.werkzeuge || 'D:/Claude/.vault-tools/plan-kopie'}.`}`,
abdeckung: (a) => `Linse: Spec-Abdeckung in beide Richtungen. Jede Anforderung${a.spec ? ` aus ${a.spec}` : ' der Übersicht'} braucht einen Task und einen Test, und jeder Task braucht eine Anforderung. Die "Fertig heißt"-Liste der Übersicht muss je Check einen Beleg außerhalb der eigenen Meinung haben. Das Frontmatter jeder Taskdatei braucht modell und modell_begruendung; Abschrift-Tasks mit Anführungszeichen, Gedanken- und Streckenstrichen oder Auslassungspunkten im Code tragen modell sonnet und effort low statt haiku.`,
}
const BEFUNDE = {
type: 'object',
properties: {
befunde: {
type: 'array',
items: {
type: 'object',
properties: {
schweregrad: { type: 'string', enum: ['blockierend', 'wichtig', 'klein'] },
datei: { type: 'string' },
zeile: { type: 'integer' },
beschreibung: { type: 'string' },
reproduktion: { type: 'string' },
},
required: ['schweregrad', 'datei', 'zeile', 'beschreibung', 'reproduktion'],
},
},
selbst_ausgefuehrt: { type: 'array', items: { type: 'string' } },
nicht_pruefbar: { type: 'array', items: { type: 'string' } },
},
required: ['befunde', 'selbst_ausgefuehrt', 'nicht_pruefbar'],
}
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'],
}
const URTEIL = {
type: 'object',
properties: {
urteil: { type: 'string', enum: ['bestaetigt', 'widerlegt', 'offen'] },
schweregrad: { type: 'string', enum: ['blockierend', 'wichtig', 'klein'], description: 'dein eigenes Urteil über die Schwere' },
bedingung: { type: 'string', description: 'unter welcher Bedingung der Befund gilt; leer, wenn immer' },
begruendung: { type: 'string' },
beleg: { type: 'string', description: 'eigener Befehl mit Ausgabe oder wörtliches Zitat mit Fundstelle' },
},
required: ['urteil', 'schweregrad', 'bedingung', 'begruendung', 'beleg'],
}
function pruefeArgs(a) {
if (!a || typeof a !== 'object') throw new Error('plankritik: args fehlen')
const fehlt = ['projekt', 'planordner'].filter(k => !a[k])
if (fehlt.length) throw new Error('plankritik: args fehlen: ' + fehlt.join(', '))
if (a.linsen !== undefined && !Array.isArray(a.linsen)) throw new Error('plankritik: linsen muss eine Liste sein, bekannt: ' + Object.keys(LINSEN).join(', '))
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.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')
}
// Höchstens n Aufgaben gleichzeitig, ohne Leerlauf zwischen Paaren.
async function hoechstens(n, items, fn) {
const ergebnisse = new Array(items.length).fill(null)
let naechster = 0
async function arbeiter() {
while (naechster < items.length) {
const i = naechster++
try { ergebnisse[i] = await fn(items[i], i) } catch (e) { log(`Aufgabe ${i + 1} abgebrochen: ${e && e.message}`) }
}
}
await Promise.all(Array.from({ length: Math.min(n, items.length) }, () => arbeiter()))
return ergebnisse
}
function werkzeugAgent(label, phase, befehl, zweck) {
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: 'sonnet', effort: 'low' })
}
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.
${LINSEN[linse](a, ctx)}
Melde jeden Befund mit Schweregrad, auch unsichere; ein zweiter Prüfer versucht danach jeden wichtigen Befund zu widerlegen. Nenn je Befund Datei, Zeile und wie er sich reproduzieren lässt.
${a.zusatz ? a.zusatz + '\n' : ''}Übertrage deinen Schlussbericht in die Felder des StructuredOutput-Werkzeugs.`
}
function pfadnorm(p) { return String(p).replace(/\\/g, '/').replace(/\/+/g, '/').toLowerCase() }
function dateiname(p) { return pfadnorm(p).split('/').pop() }
// Doppelte Befunde verschiedener Linsen zusammenfassen: gleicher Pfad und gleiche Zeile.
// Zeile 0 oder kleiner heißt "ganze Datei" und wird nie zusammengefasst, weil darunter
// verschiedene Befunde derselben Datei liegen.
function zusammenfassen(befunde) {
const gruppen = new Map()
befunde.forEach((b, i) => {
const k = b.zeile > 0 ? pfadnorm(b.datei) + ':' + b.zeile : 'einzeln:' + i
if (!gruppen.has(k)) gruppen.set(k, { ...b, linsen: [b.linse], quellen: [b] })
else {
const g = gruppen.get(k)
g.quellen.push(b)
if (!g.linsen.includes(b.linse)) g.linsen.push(b.linse)
if (RANG[b.schweregrad] < RANG[g.schweregrad]) g.schweregrad = b.schweregrad
}
})
return [...gruppen.values()]
}
function widerlegungsauftrag(a, g, ctx) {
const texte = g.quellen.map((b, i) => `Fassung ${i + 1} (Linse ${b.linse}, ${b.schweregrad}, ${b.datei}:${b.zeile}): ${b.beschreibung}\nReproduktion laut Linse: ${b.reproduktion}`).join('\n\n')
return `Eine Plankritik hat zum Plan im Ordner ${a.planordner} (Projekt ${a.projekt}) diesen Befund gemeldet, Fundstelle ${g.datei}:${g.zeile}:
<befund>
${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.
- "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".
Gilt der Befund nur unter einer Bedingung, nenn sie. Gib deine eigene Einschätzung der Schwere an.
Übertrage dein Urteil in die Felder des StructuredOutput-Werkzeugs.`
}
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 }
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.
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 }
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]
}
phase('Vorbereitung')
if (ctx.kopie && args.materialisieren) log('kopie ist gesetzt, materialisieren wird übergangen')
if (!ctx.kopie && args.materialisieren) {
const liste = (args.materialisieren.tasks || []).map(q).join(' ')
const auswahl = liste
? `TASKS=(${liste})`
: `shopt -s nullglob; pushd ${q(args.planordner)} >/dev/null && TASKS=(task-*.md) && popd >/dev/null; [ \${#TASKS[@]} -gt 0 ] || { echo "KEINE_TASKS: im Planordner liegt keine task-*.md; materialisieren.tasks angeben"; exit 3; }`
const befehl = `${auswahl}; T=$(mktemp -d) && echo "KOPIE=$(cygpath -m "$T/mat")" && ${PY} ${q(W + '/kopie_bauen.py')} ${q(args.projekt)} "$T/mat" --ohne ${args.materialisieren.ohne.map(q).join(' ')} && ${PY} ${q(W + '/plan_materialisiere.py')} ${q(args.planordner)} "$T/mat" "\${TASKS[@]}"`
const r = await werkzeugAgent('werkzeug:kopie', 'Vorbereitung', befehl, 'Kopie des Projekts bauen und den Plan darauf anwenden')
ctx.kopie = kopieAusLauf(r, 'Plankritik')
vorbereitung.push(r)
}
{
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
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 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 }))
})
const ergebnisse = kritiken.filter(k => k && k.ergebnis)
const ohneErgebnis = gewaehlt.filter(l => !ergebnisse.some(e => e.linse === l))
if (ohneErgebnis.length) log('Linsen ohne Ergebnis: ' + ohneErgebnis.join(', '))
const roh = ergebnisse.flatMap(e => e.ergebnis.befunde.map(b => ({ linse: e.linse, ...b })))
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')})`)
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: '' }
})
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 }))
.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 }
befunde.forEach(b => { if (b.widerlegung) zaehlung[b.widerlegung.urteil]++ })
return {
zaehlung,
kopie: ctx.kopie,
temp_ordner: ctx.temp ? `${ctx.temp} bleibt liegen (Projektkopie ohne die Ausschlüsse); löschen, wenn nicht mehr gebraucht` : null,
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,
}
+66
View File
@@ -0,0 +1,66 @@
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.',
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").
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
const BERICHT = {
type: 'object',
properties: {
selbst_ausgefuehrt: { type: 'array', items: { type: 'string' } },
eigener_lauf: { type: 'array', items: { type: 'string' } },
befunde: {
type: 'array',
items: {
type: 'object',
properties: {
schweregrad: { type: 'string', enum: ['blockierend', 'wichtig', 'klein'] },
datei: { type: 'string' },
zeile: { type: 'integer' },
beschreibung: { type: 'string' },
reproduktion: { type: 'string' },
},
required: ['schweregrad', 'datei', 'zeile', 'beschreibung', 'reproduktion'],
},
},
plan_vs_umsetzung: { type: 'array', items: { type: 'string' } },
geprueft_und_in_ordnung: { type: 'array', items: { type: 'string' } },
nicht_pruefbar: { type: 'array', items: { type: 'string' } },
weitere_runde: { type: 'string', description: 'nur bei Abschlussprüfung: hat eine weitere Runde noch Substanz, und warum' },
},
required: ['selbst_ausgefuehrt', 'eigener_lauf', 'befunde', 'plan_vs_umsetzung', 'geprueft_und_in_ordnung', 'nicht_pruefbar'],
}
function pruefeArgs(a) {
const fehlt = ['projekt', 'plan'].filter(k => !a || !a[k])
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')
}
function auftrag(a) {
const umfang = a.tasks && a.tasks.length ? `Geprüft wird die Umsetzung dieser Tasks: ${a.tasks.join(', ')}.` : 'Geprüft wird die gesamte Umsetzung des Plans.'
return `Prüfe die Umsetzung im Projekt ${a.projekt} gegen den Plan ${a.plan}. ${umfang}
Der Executer-Bericht ist eine Behauptung. Fahre das Pflichtprogramm deiner Agentendefinition: Tests selbst ausführen${a.suite ? ` (${a.suite})` : ''}, die zentralen Artefakte selbst lesen, einen eigenen Lauf mit realistischen Daten und eigenem Prüfcode. Eigenen Prüfcode führst du als python -c oder in einem Wegwerfordner unter dem System-Temp aus; im Projekt änderst du nichts.
${a.version ? `Aussagen über fremde Software belegst du am tatsächlich eingesetzten Versionsstand: ${a.version}.\n` : ''}${a.schwerpunkt ? `Schwerpunkt: ${a.schwerpunkt}\n` : ''}Melde jeden Befund mit Schweregrad, auch unsichere; der Koordinator filtert.
${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.`
}
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'
const bericht = await agent(auftrag(args), opts)
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]) }
+146
View File
@@ -0,0 +1,146 @@
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.',
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.
const MODELLE = ['haiku', 'sonnet', 'opus']
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
// 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.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) {
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: 'sonnet', 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)
}
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')
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(', ')
if (pruefungGrund) log('Prüfung übersprungen: ' + pruefungGrund)
else {
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')
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,
})
} catch (e) {
fehler.push('pruefung: ' + (e && e.message))
pruefungGrund = 'pruefung gescheitert: ' + (e && e.message)
log(pruefungGrund)
}
}
return {
kopie,
temp_ordner: temp ? `${temp} bleibt liegen (Projektkopie ohne die Ausschlüsse); löschen, wenn der Diff nicht mehr gebraucht wird` : null,
welle,
vergleich: belege.vergleich || null,
kopie_lauf: belege.kopie || null,
pruefung: pruefung || { uebersprungen: pruefungGrund },
fehler,
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.',
}