Files
Pascal MichelandClaude Opus 5 22761eb186 Automatischer Push: 11 Datei(en) geändert
- .claude/agents/planer.md
- .claude/workflows/plankritik.js
- .claude/workflows/quorum.js
- .claude/workflows/welle-mit-pruefung.js
- .vault-tools/plan-kopie/LIESMICH.md
- .vault-tools/plan-kopie/plan_bloecke.py
- .vault-tools/plan-kopie/plan_materialisiere.py
- .vault-tools/plan-kopie/plan_zaun_treiber.py
- .vault-tools/workflow-pruefung/LIESMICH.md
- .vault-tools/workflow-pruefung/faelle_workflows.mjs
- .vault-tools/workflow-pruefung/mutanten_workflows.py

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-03 03:00:01 +01:00

288 lines
35 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Mutantenlauf gegen die Workflows unter .claude/workflows/: je Mutant genau eine Ersetzung, dann faelle_workflows.mjs.
Jede Ersetzung muss genau einmal vorkommen, sonst bricht der Lauf mit Exit 2 ab: Ein Muster, das
nicht mehr passt, ist ein veralteter Mutant und kein ueberlebter. Ein ueberlebter Mutant ist ein
Befund, ausser er steht in WIRKUNGSLOS mit Begruendung.
Aufruf: python mutanten_workflows.py [<ordner mit den workflows>]
Exit 0, wenn jeder wirksame Mutant gefangen wird, 1 bei einem Ueberlebenden, 2 bei einem veralteten Muster.
"""
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
HIER = Path(__file__).resolve().parent
FAELLE = HIER / "faelle_workflows.mjs"
# Mutanten, die bei den heutigen Stufen kein Verhalten aendern, mit Grund.
WIRKUNGSLOS = {
"harte Obergrenze fehlt": "keine Stufe gibt mehr als vier Plaetze; wer die Stufen anhebt, braucht einen Fall",
}
MUTANTEN = [
("verbrauch", "Stufe 50 -> 60", "[[50, 4], [70, 2], [85, 1]]", "[[60, 4], [70, 2], [85, 1]]"),
("verbrauch", "Wochenstufe 85 -> 80", "[[70, 4], [85, 2], [95, 1]]", "[[70, 4], [80, 2], [95, 1]]"),
("verbrauch", "ueber 100 wieder Fehler", "|| p < 0) return", "|| p < 0 || p > 100) return"),
("verbrauch", "doppeltes Fenster: erstes gewinnt", "if (!e[art] || e[art].prozent < p) e[art] = neu", "if (!e[art]) e[art] = neu"),
("verbrauch", "Opus-Woche ignoriert", "Math.max(e.woche.prozent, e.opus ? e.opus.prozent : 0)", "e.woche.prozent"),
("verbrauch", "Opus-Woche ersetzt die Woche", "Math.max(e.woche.prozent, e.opus ? e.opus.prozent : 0)", "(e.opus ? e.opus.prozent : e.woche.prozent)"),
("verbrauch", "harte Obergrenze fehlt", "Math.min(wunsch, grenzeFuenf, grenzeWoche, HARTE_OBERGRENZE)", "Math.min(wunsch, grenzeFuenf, grenzeWoche)"),
("verbrauch", "gebremst auch bei wunsch 0", "const gebremst = wunsch > 0 && plaetze === 0", "const gebremst = plaetze === 0"),
("verbrauch", "Status ok nicht verlangt", " || ablesung.status !== 'ok'", ""),
("verbrauch", "Rueckfall 3 statt 2", "const OHNE_ABLESUNG = 2", "const OHNE_ABLESUNG = 3"),
("verbrauch", "wunsch-Pruefung fehlt", "if (!Number.isInteger(a.wunsch) || a.wunsch < 0) throw", "if (false) throw"),
("verbrauch", "fremdes Fenster mit Zahl geprueft", "if (!art) { e.andere.push(f.label); continue }\n const p = f.percentUsed", "const p = f.percentUsed\n if (!art) { if (typeof p !== 'number') return { fehler: 'x' }; e.andere.push(f.label); continue }"),
("plankritik", "Bremse vor Linsen fehlt", "if (vorLinsen.gebremst) {", "if (false) {"),
("plankritik", "Bremse vor Widerlegung fehlt", "if (vorWiderlegung.gebremst) {", "if (false) {"),
("plankritik", "Widerlegung bis vier", "const WIDERLEGUNG_HOECHSTENS = 2", "const WIDERLEGUNG_HOECHSTENS = 4"),
("plankritik", "Widerlegung ignoriert die Ablesung", "} else plaetzeWiderlegung = vorWiderlegung.plaetze", "} else plaetzeWiderlegung = WIDERLEGUNG_HOECHSTENS"),
("plankritik", "Linsen fest zwei", "await hoechstens(vorLinsen.plaetze, gewaehlt,", "await hoechstens(2, gewaehlt,"),
("plankritik", "Rueckfall ohne Ablesung 3", "const OHNE_ABLESUNG = 2", "const OHNE_ABLESUNG = 3"),
("plankritik", "Testort fehlt in der Widerlegung", "Du änderst keine Datei im Projekt und keine im Plan. ${testort(ctx)}\n- ", "Du änderst keine Datei im Projekt und keine im Plan.\n- "),
("plankritik", "Testort fehlt bei den Linsen", "Executer-Bericht, dessen Testlauf du wiederholen könntest. ${testort(ctx)}", "Executer-Bericht, dessen Testlauf du wiederholen könntest. ${linse === 'arithmetik' ? testort(ctx) : ''}"),
("plankritik", "werkzeug:kopie wieder auf sonnet", "den Plan darauf anwenden', '', 'haiku')", "den Plan darauf anwenden', '', 'sonnet')"),
("plankritik", "Planwerkzeuge und Mutantenlauf auf haiku", "zusatz, modell = 'sonnet')", "zusatz, modell = 'haiku')"),
("plankritik", "keine Ablesung am Ende", "await ablesen('Ende', 0)\n", "\n"),
("plankritik", "gebremste Befunde ohne Hinweis", "'nicht geprüft (gebremst)'", "'nicht geprüft'"),
("plankritik", "gebremst auch unter der Schwelle", "gebremstVor && zuPruefen.includes(g) ?", "gebremstVor ?"),
("plankritik", "naechster_schritt uebergeht die Bremse", "naechster_schritt: gebremstVor ? ", "naechster_schritt: false ? "),
("plankritik", "zaehlung null bei Bremse vor Linsen", "zaehlung: { roh: 0, zusammengefasst: 0, bestaetigt: 0, offen: 0, widerlegt: 0, nicht_geprueft: 0 },", "zaehlung: null,"),
("pruefung", "verbrauch false wird uebergangen", "if (args.verbrauch === false) return\n", "\n"),
("pruefung", "Ablesung nach der Pruefung fehlt", "await ablesen('nach Prüfung')\n", "\n"),
("welle-mit-pruefung", "Kind liest selbst", "{ ...pruefungArgs, verbrauch: false }", "{ ...pruefungArgs }"),
("welle-mit-pruefung", "bis vergleich wirkt nicht", "if (args.bis === 'vergleich') {", "if (false) {"),
("welle-mit-pruefung", "werkzeug:kopie wieder auf sonnet", "Taskdateien der Welle darauf anwenden', 'haiku')", "Taskdateien der Welle darauf anwenden', 'sonnet')"),
("welle-mit-pruefung", "Vergleich auf haiku", "zweck, modell = 'sonnet')", "zweck, modell = 'haiku')"),
("welle-mit-pruefung", "Ablesung vor der Pruefung fehlt", " await ablesen('vor Prüfung')\n", "\n"),
("planung", "Voraussetzung laufend genuegt", "['fertig', 'erledigt'].includes(status.get(n)))) starte(t)", "['fertig', 'erledigt', 'laeuft'].includes(status.get(n)))) starte(t)"),
("planung", "keine Blockade", "status.set(t.nr, 'blockiert')\n geaendert = true", "geaendert = false"),
("planung", "kein Abbruch nach drei", "gescheitert.length >= ABBRUCH_NACH && !abgebrochen", "false"),
("planung", "Bremse beim Nachlesen fehlt", "if (v.gebremst) {\n gebremst = true\n log('Planung gebremst", "if (false) {\n gebremst = true\n log('Planung gebremst"),
("planung", "Nachlesen uebernimmt die Plaetze nicht", "log('Planung gebremst: kein weiterer Start, die laufenden Planer enden')\n } else plaetze = v.plaetze", "log('Planung gebremst: kein weiterer Start, die laufenden Planer enden')\n }"),
("planung", "Bremse am Anfang nicht gemeldet", " if (v.gebremst) gebremst = true\n else plaetze = v.plaetze", " plaetze = v.plaetze"),
("planung", "Plaetze nicht begrenzt", "if (laufend.size >= plaetze) break", "if (laufend.size >= 99) break"),
("planung", "Reihenfolge unsortiert", "const reihenfolge = [...args.tasks].sort((x, y) => ((x.welle || 0) - (y.welle || 0)) || (x.nr - y.nr))", "const reihenfolge = [...args.tasks]"),
("planung", "Nachlesen ohne offene Tasks", "fertigSeitAblesung >= nachlesenAlle && nochOffen()", "fertigSeitAblesung >= nachlesenAlle"),
("planung", "fremde Datei zaehlt als fertig", "const r = passt ? roh : null", "const r = roh"),
("planung", "Vorgabe vier statt zwei", "const VORGABE_PLAETZE = 2", "const VORGABE_PLAETZE = 4"),
("planung", "Zyklenpruefung fehlt", "if (geschafft < a.tasks.length) throw", "if (false) throw"),
("planung", "erledigt ignoriert", "(args.erledigt || []).includes(t.nr) ? 'erledigt' : 'offen'", "'offen'"),
("planung", "Rueckfall ohne Ablesung 3", "const OHNE_ABLESUNG = 2", "const OHNE_ABLESUNG = 3"),
("planung", "Datei ohne Pfadgrenze", "('/' + roh.datei.replace(/\\\\/g, '/')).endsWith('/' + jeNr.get(nr).datei)", "roh.datei.replace(/\\\\/g, '/').endsWith(jeNr.get(nr).datei)"),
("planung", "Nachlesezaehler nicht zurueckgesetzt", " fertigSeitAblesung = 0\n const v = await ablesen(", " const v = await ablesen("),
("planung", "nachlesen_alle ignoriert", "const nachlesenAlle = args.nachlesen_alle || NACHLESEN_ALLE", "const nachlesenAlle = NACHLESEN_ALLE"),
("planung", "effort nicht durchgereicht", " if (args.effort) opts.effort = args.effort\n", ""),
("planung", "effort ungeprueft", " if (a.effort !== undefined && !EFFORTS.includes(a.effort)) throw", " if (false) throw"),
("planung", "Rueckgabe ohne effort", " effort: args.effort || '(Frontmatter)',\n", ""),
("planung", "Lesehinweis fehlt", " Lies sie, bevor du einen Namen, eine Signatur oder einen Import daraus übernimmst.", ""),
("plankritik", "doppelte Linse erlaubt", " if (a.linsen && new Set(a.linsen).size !== a.linsen.length) throw", " if (false) throw"),
("plankritik", "Mutantenlauf-Rohausgabe geht nicht an arithmetik", "${mutantenText(ctx.mutantenLauf)}`,", "`,"),
("plankritik", "MSYS_NO_PATHCONV fehlt im Mutantenlauf-Befehl", "`MSYS_NO_PATHCONV=1 wsl -d Debian", "`wsl -d Debian"),
("plankritik", "Zeitgrenze des Mutantenlaufs verschwiegen", "if (/ZEITGRENZE/.test(m.rohausgabe)) hinweis =", "if (false) hinweis ="),
("plankritik", "Hilfsagent ohne Zeitvorgabe", ", 'Setze im Bash-Werkzeug den Parameter timeout auf 600000; der Befehl darf bis zu zehn Minuten laufen.')", ")"),
("welle-mit-pruefung", "status fertig ohne Pruefung", "status: pruefung ? 'fertig' : 'ohne_pruefung',", "status: 'fertig',"),
("welle-mit-pruefung", "Ablesung ohne try", " try {\n r = await workflow({ scriptPath: VERBRAUCH }, { anlass: 'welle-mit-pruefung:' + anlass, wunsch: 0 })\n } catch (e) {\n r = { status: 'nicht_gelesen', grund: 'verbrauch.js nicht ausführbar: ' + (e && e.message) }\n }", " r = await workflow({ scriptPath: VERBRAUCH }, { anlass: 'welle-mit-pruefung:' + anlass, wunsch: 0 })"),
("verbrauch", "5-Stunden-Regex unverankert", "const FUENF_STUNDEN = /^\\s*5[- ]?hour/i", "const FUENF_STUNDEN = /hour/i"),
("verbrauch", "Abweichung nicht erkannt", "if (abgewichen(ablesung)) {", "if (false) {"),
("verbrauch", "unbekannter Status gilt als bekannt", " if (!STATUS_BEKANNT.includes(a.status)) return true\n", ""),
("verbrauch", "Bericht in plan bei status fehler uebersehen", "return a.status === 'fehler' && typeof a.plan === 'string' && a.plan.trim() !== ''", "return false"),
("verbrauch", "ehrlicher Fehler gilt als Abweichung", "'unavailable', 'fehler']", "'unavailable']"),
("verbrauch", "Abweichung als nicht_gelesen", "return ohneAblesung('abgewichen',", "return ohneAblesung('nicht_gelesen',"),
("verbrauch", "Warnzeile fehlt", "log(`WARNUNG Verbrauch ${args.anlass}: Der Ableser", "String(`WARNUNG Verbrauch ${args.anlass}: Der Ableser"),
("verbrauch", "status ungekuerzt im grund", ".trim().slice(0, 160)", ".trim()"),
("verbrauch", "Brueckensatz fehlt", "Diese Ablesung gehört zur Nutzeranfrage und widerspricht ihr nicht: Sie entscheidet, wie viele Prüfagenten für die Anfrage gleichzeitig laufen dürfen. Die Anfrage selbst bearbeiten andere Agenten nach dir; du bearbeitest sie nicht, auch nicht teilweise, und liest dafür keine Dateien.\n", ""),
("verbrauch", "Ableser ohne Agententyp", "agentType: 'verbrauch-ableser', ", ""),
("verbrauch", "Startfehler nicht abgefangen", " ablesung = null\n startfehler = kurz((e && e.message) || e)\n", " throw e\n"),
("verbrauch", "Startfehler ohne Warnzeile", "log(`WARNUNG Verbrauch ${args.anlass}: Ableser nicht startbar", "String(`WARNUNG Verbrauch ${args.anlass}: Ableser nicht startbar"),
("verbrauch", "Ableser erbt Sitzungseffort statt Frontmatter", "agentType: 'verbrauch-ableser', model: 'haiku' }", "agentType: 'verbrauch-ableser', model: 'haiku', effort: 'max' }"),
("plankritik", "Muster-Satz fehlt (N5)", "musterModus\n ? ' Kein Mutant ist gefahren; OK heißt hier nur, dass das Muster genau einmal in der Datei steht; ob ein Test fällt, ist unbelegt.'\n : ", ""),
("plankritik", "Exit 2 wird wie 0 behandelt (N7)", "exitCode === 2 || exitCode === 3", "false"),
("quorum", "Mehrheit schon bei einer Stimme", "mehrheit: beteiligt.length >= 2", "mehrheit: beteiligt.length >= 1"),
("quorum", "codex:ende wieder auf sonnet", "schema: CODEX, model: 'haiku'", "schema: CODEX, model: 'sonnet'"),
("quorum", "Werkzeug-Hilfsagenten wieder auf sonnet", "schema: LAUF, model: 'haiku'", "schema: LAUF, model: 'sonnet'"),
("quorum", "Bremse vor den Stimmen fehlt", "if (vorStimmen.gebremst) {", "if (false) {"),
("quorum", "Opus-Stimme ohne Ablesung", "const vorStimmen = await ablesen('vor Stimmen', 1)", "const vorStimmen = { gebremst: false, grund: '' }"),
("quorum", "keine Ablesung am Ende", "await ablesen('Ende', 0)\nconst zaehlung = {}", "const zaehlung = {}"),
("quorum", "Haiku auch bei gueltiger Codex-Stimme", "if (!codex.abgegeben) {\n hinweis(", "if (true) {\n hinweis("),
("quorum", "usage limit nicht geprueft", " if (e.usage_limit_treffer !== 0) return '\"usage limit\" im Log'\n", ""),
("quorum", "Lesebeleg nicht geprueft", " if (!String(e.lesebeleg).includes('Lesemarke: ' + p.lesemarke)) return 'Lesebeleg fehlt oder zitiert eine andere Lesemarke'\n", ""),
("quorum", "Status vor dem Lauf nicht geprueft", "if (!/EXIT status: 0(?!\\d)/.test(start)) {", "if (false) {"),
("quorum", "codex false wird uebergangen", "if (a.codex === false) {", "if (false) {"),
("quorum", "Codex-Runden fest drei", "const runden = a.codex_runden || CODEX_RUNDEN", "const runden = CODEX_RUNDEN"),
("quorum", "kein Abbruch nach Zeitueberschreitung", "await werkzeugAgent('codex:abbruch', 'Stimmen',", "await Promise.resolve('codex:abbruch', 'Stimmen',"),
("quorum", "Warten ohne Zeitvorgabe", "'auf das Ende des Codex-Auftrags warten', LANGE)", "'auf das Ende des Codex-Auftrags warten')"),
("quorum", "Codex darf schreiben", "task --background --json", "task --background --write --json"),
("quorum", "leerer Unterschied startet Stimmen", "if (paket.leer) {", "if (false) {"),
("quorum", "unbelegter Befund zaehlt", "belegt: String(b.beleg).trim() !== ''", "belegt: true"),
("quorum", "Zusammenfuehren sieht unbelegte Befunde", "zusammenfuehrenAuftrag(belegt)", "zusammenfuehrenAuftrag(alle)"),
("quorum", "Dateien nicht getrennt", "if (datei[i] === datei[j] && bereich[i] && bereich[j]", "if (bereich[i] && bereich[j]"),
("quorum", "Schwere der Gruppe ist die niedrigste", ".sort((x, y) => RANG[x] - RANG[y])[0]", ".sort((x, y) => RANG[y] - RANG[x])[0]"),
("quorum", "Hinweis wird nachgeprueft", "gruppen.filter(g => !g.mehrheit && g.schwere !== 'hinweis')", "gruppen.filter(g => !g.mehrheit)"),
("quorum", "Mehrheitsgruppe wird nachgeprueft", "gruppen.filter(g => !g.mehrheit && g.schwere !== 'hinweis')", "gruppen.filter(g => g.schwere !== 'hinweis')"),
("quorum", "ungepruefter blockierender Befund sperrt nicht", "(gilt(g) || g.status === 'nicht_geprueft')", "gilt(g)"),
("quorum", "Urteil schon mit einer Stimme", "if (abgegeben.length < 2) {", "if (abgegeben.length < 1) {"),
("quorum", "Bericht ohne Urteil", "return ergebnis('unvollstaendig', 'kein_urteil', grund, null,", "return ergebnis('unvollstaendig', 'kein_urteil', grund, 'x',"),
("quorum", "jobId aus der ganzen Ausgabe samt Status", " const job = taskIds.size === 1 ? taskJobs[0] : null\n", ' const job = /"jobId": "([^"]+)"/.exec(start)\n'),
("quorum", "Erkennung vertauschter laeufe entfernt", " const job = taskIds.size === 1 ? taskJobs[0] : null\n", " const job = /\"jobId\": \"([^\"]+)\"/.exec(start.slice(start.lastIndexOf('EXIT status:')))\n"),
("quorum", "alte jobId als Ersatz zugelassen", " const job = taskIds.size === 1 ? taskJobs[0] : null\n", ' const job = taskJobs[0] || /"jobId": "([^"]+)"/.exec(start)\n'),
("quorum", "Hinweis auf vertauschte laeufe fehlt", " hinweis('Codex-Start: Ausgabe nicht in Befehlsreihenfolge; jobId aus der task-Ausgabe (' + job[1] + ')')\n", ""),
("quorum", "erster Kandidat gewinnt", " const job = taskIds.size === 1 ? taskJobs[0] : null\n", " const job = taskJobs[0] || null\n"),
("quorum", "Warnung vor ungesehenem Auftrag fehlt", " ? 'jobId nicht sicher bestimmbar; Codex-Auftrag läuft womöglich ungesehen weiter'\n", " ? 'Codex-Auftrag nicht gestartet'\n"),
("quorum", "Vertauschung nur an EXIT-Reihenfolge erkannt", " if (!(statusEnde < job.index && job.index < taskEnde)) {\n", " if (taskEnde < statusEnde) {\n"),
("quorum", "Warnung bei fehlendem Status fehlt", " z.grund = 'Status vor dem Lauf nicht gelesen' + (taskOK ? '; Codex-Auftrag läuft womöglich ungesehen weiter' : '')\n", " z.grund = 'Status vor dem Lauf nicht gelesen'\n"),
("quorum", "Grenze vor EXIT task nicht geprüft", " if (!(statusEnde < job.index && job.index < taskEnde)) {\n", " if (!(statusEnde < job.index)) {\n"),
("quorum", "eingerückte Zeile mit queued als Task-Herkunft zugelassen", r' const laufJobs = [...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/g)]' + "\n", r' const laufJobs = [...laufText.matchAll(/(?:\{\s*|^[ \t]+)"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/gm)]' + "\n"),
("quorum", "beliebige verschachtelte jobId zugelassen", r' const laufJobs = [...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/g)]' + "\n", r' const laufJobs = [...laufText.matchAll(/"jobId"\s*:\s*"([^"\\\r\n]+)"/g)]' + "\n"),
("quorum", "Objektöffnung vor jobId nicht verlangt", r' const laufJobs = [...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/g)]' + "\n", r' const laufJobs = [...laufText.matchAll(/"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/g)]' + "\n"),
("quorum", "Komma nach jobId nicht verlangt", r' const laufJobs = [...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/g)]' + "\n", r' const laufJobs = [...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,?\s*"status"\s*:\s*"queued"/g)]' + "\n"),
("quorum", "queued nicht verlangt", r' const laufJobs = [...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/g)]' + "\n", r' const laufJobs = [...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"[^"]+"/g)]' + "\n"),
("quorum", "Treffer statt verschiedener IDs gezählt", " const job = taskIds.size === 1 ? taskJobs[0] : null\n", " const job = taskJobs.length === 1 ? taskJobs[0] : null\n"),
("quorum", "Rohzeile wieder als Merkmal zugelassen", r' const laufJobs = [...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/g)]' + "\n", r' const laufJobs = [...laufText.matchAll(/^ "jobId": "([^"\\\r\n]+)",?\r?$/gm), ...laufText.matchAll(/\{\s*"jobId"\s*:\s*"([^"\\\r\n]+)"\s*,\s*"status"\s*:\s*"queued"/g)]' + "\n"),
("quorum", "Merkmal über Laufgrenzen zugelassen", " for (const laufText of laufTexte(startAntwort)) {\n", " for (const laufText of [start]) {\n"),
("quorum", "Task-EXIT-Suche nicht am Zeilenanfang verankert", r' const taskEnde = start.search(/^EXIT task: 0(?!\d)/m)' + "\n", r' const taskEnde = start.search(/EXIT task: 0(?!\d)/m)' + "\n"),
("quorum", "Letzte statt erster Fundstelle gewählt", " const job = taskIds.size === 1 ? taskJobs[0] : null\n", " const job = taskIds.size === 1 ? taskJobs[taskJobs.length - 1] : null\n"),
("quorum", "Fundstellen nicht auf den verbundenen Text umgerechnet", " treffer.index += startIndex\n", ""),
("quorum", "Trennzeichen beim Umrechnen nicht mitgezählt", " startIndex += laufText.length + 1\n", " startIndex += laufText.length\n"),
("quorum", "Task-Exit nicht verlangt bei gefundener jobId", " if (!taskOK || !job) {\n", " if (!job) {\n"),
("quorum", "Status-EXIT-Suche nicht am Zeilenanfang verankert", r" const statusEnde = start.search(/^EXIT status: 0(?!\d)/m)" + "\n", r" const statusEnde = start.search(/EXIT status: 0(?!\d)/m)" + "\n"),
("quorum", "Warnung auch ohne gelaufenen task", " z.grund = 'Status vor dem Lauf nicht gelesen' + (taskOK ? '; Codex-Auftrag läuft womöglich ungesehen weiter' : '')\n", " z.grund = 'Status vor dem Lauf nicht gelesen' + '; Codex-Auftrag läuft womöglich ungesehen weiter'\n"),
("quorum", "Teilgruppen ignoriert, Gruppe bleibt ganz", "teile.forEach(t => gruppen.push(t))", "gruppen.push(mitglieder)"),
("quorum", "Gruppe bei Aufteilung ganz aufgeloest", "teile.forEach(t => gruppen.push(t))", "(teile.length > 1 ? mitglieder.map(b => [b]) : teile).forEach(t => gruppen.push(t))"),
("quorum", "Aufteilung ohne Hinweis", "if (teile.length > 1) meldungen.push(", "if (false) meldungen.push("),
("quorum", "Toleranz null", "const TOLERANZ = 3", "const TOLERANZ = 0"),
("quorum", "Toleranz zwei", "const TOLERANZ = 3", "const TOLERANZ = 2"),
("quorum", "Toleranz vier", "const TOLERANZ = 3", "const TOLERANZ = 4"),
("quorum", "Toleranz nur nach unten", "y[0] <= x[1] + TOLERANZ", "y[0] <= x[1]"),
("quorum", "keine transitive Huelle", "if (datei[i] === datei[j] && bereich[i]", "if (i === 0 && datei[i] === datei[j] && bereich[i]"),
("quorum", "Stelle ohne Zeilennummer bleibt nie bei ihrer Teilgruppe", "if (stuecke.size === 1) verbinde(", "if (false) verbinde("),
("quorum", "Stelle ohne Zeilennummer schliesst sich der ersten Teilgruppe an", "if (stuecke.size === 1) verbinde(", "if (stuecke.size >= 1) verbinde("),
("quorum", "Stellen ohne Zeilennummer getrennt", "else if (stuecke.size === 0 && gleich.length) verbinde(", "else if (false) verbinde("),
("quorum", "a.py:42 nicht gelesen", " || /\\.[a-z0-9]+:(\\d+)(?:\\s*(?:-|–)\\s*(\\d+))?/i.exec(s)\n", ""),
("quorum", "Zeilennummer nur am Anfang gelesen", "/(?:^|[^\\p{L}\\d])(?:zeilen?", "/^\\s*(?:zeilen?"),
("quorum", "L12 nicht gelesen", "|z\\.|lines?|l)\\s*(\\d+)", "|z\\.|lines?)\\s*(\\d+)"),
("quorum", "Gedankenstrich im Bereich nicht gelesen", "(?:-|–|bis|to)\\s*l?", "(?:-|bis|to)\\s*l?"),
("quorum", "Zeilenbereich nur erste Zeile", "const bis = m[2] === undefined ? von : Number(m[2])", "const bis = von"),
("quorum", "Stelle ohne Zeilen zaehlt als Zeile 0", "if (!m) return null", "if (!m) return [0, 0]"),
("quorum", "Befundzeilen der Gruppe fehlen", "for (const b of g.mitglieder) z.push(", "for (const b of []) z.push("),
("quorum", "Stelle fehlt in der Befundzeile", ", ${b.schwere}, Stelle ${klartext(b.stelle)}): ${klartext(b.aussage)}", ", ${b.schwere}): ${klartext(b.aussage)}"),
("quorum", "Nachpruefungszeile fehlt", "if (g.nachpruefung) z.push(''", "if (false) z.push(''"),
("quorum", "Nachpruefung ohne Fundstelle", "${g.nachpruefung.urteil}, ${klartext(g.nachpruefung.fundstelle)}.", "${g.nachpruefung.urteil}."),
("quorum", "Unbelegt-Liste leer", "for (const b of x.unbelegt)", "for (const b of [])"),
("quorum", "Nicht pruefbar leer", "(x.stimmen[n].nicht_pruefbar || [])", "([])"),
("quorum", "Hinweise des Laufs fehlen im Bericht", "const alleHinweise = [...p.hinweise, ...laufHinweise]", "const alleHinweise = []"),
("quorum", "Lesemarke der Claude-Stimmen nicht geprueft", "lesemarke_ok: String(e.lesemarke).includes(p.lesemarke)", "lesemarke_ok: true"),
("quorum", "Lesemarke exakt verglichen", "lesemarke_ok: String(e.lesemarke).includes(p.lesemarke)", "lesemarke_ok: String(e.lesemarke) === p.lesemarke"),
("quorum", "Stelle fehlt in der Gruppenueberschrift", ", Stelle ${klartext(erste.stelle)}", ""),
("quorum", "Stand statt PAKET im Frontmatter", "'stand: PAKET'", "'stand: ' + p.stand"),
("quorum", "ablegen_befehl fehlt", "ablegen_befehl: md ? ablegenBefehl(paket) : null", "ablegen_befehl: null"),
("quorum", "ablegen_befehl auch ohne Bericht", "ablegen_befehl: md ? ablegenBefehl(paket) : null", "ablegen_befehl: ablegenBefehl(paket)"),
("quorum", "anzahl_dateien nicht geprueft", "if (!Number.isInteger(p.anzahl_dateien) || p.anzahl_dateien < 0) return", "if (false) return"),
("quorum", "paket_summe nicht verlangt", "'hinweise', 'paket_summe']", "'hinweise']"),
("quorum", "paket_summe nicht geprueft", "if (!/^[0-9a-f]{64}$/.test(p.paket_summe)) return", "if (false) return"),
("quorum", "ablegen_befehl ohne summe", " --summe ${q(p.paket_summe)}", ""),
("quorum", "Exit des Pakets nicht geprueft", "if (!/EXIT paket: 0(?!\\d)/.test(aus) || !m) {", "if (!m) {"),
("quorum", "Vorgabe sechs Warterunden", "const CODEX_RUNDEN = 3", "const CODEX_RUNDEN = 6"),
("quorum", "codex_runden 6 abgelehnt", "a.codex_runden <= 6", "a.codex_runden < 6"),
("quorum", "Suite ohne Vergleich des Arbeitsbaums", '" ] || echo "BEFUND: Die Suite hat', '" ] && echo "BEFUND: Die Suite hat'),
("quorum", "Arbeitsbaum-Vergleich frischt den Index auf", "const baum = `GIT_OPTIONAL_LOCKS=0 git -C", "const baum = `git -C"),
("quorum", "ordner ausserhalb des Vaults erlaubt", "if (vaultPfad(a.ordner) === null) throw", "if (false) throw"),
("quorum", "Kundendokument im Code-Projekt ohne README-Link", "if (teile[0].toLowerCase() === 'code projekte' && teile.length >= 2) return", "if (false) return"),
("quorum", "README-Tiefe um eins daneben", "'../'.repeat(teile.length - 1)", "'../'.repeat(teile.length)"),
("quorum", ".md bleibt im Wikilink", "u.replace(/\\.md$/i, '')", "u"),
("quorum", "Backslash im Ordner bleibt", "String(ordner).replace(/\\\\/g, '/')", "String(ordner)"),
("quorum", "Stimmen kennen einander", "Die anderen Stimmen kennst du nicht.", "Die anderen Stimmen sind Opus, Sonnet und Codex."),
("quorum", "Zusammenfuehren darf Dateien lesen", "Lies keine Dateien und fahre keine Befehle; du arbeitest nur mit dieser Liste.", "Lies die Dateien, wenn du sie brauchst."),
("quorum", "Klartext: Klammerfolge bleibt", r".replace(/\[{2,}/g, folge => folge.replace(/\[/g, '\\['))", ""),
("quorum", "Klartext: nur die erste Klammer entschaerft", r"folge => folge.replace(/\[/g, '\\['))", r"folge => '\\' + folge)"),
("quorum", "Klartext: Einbettung ausgenommen", r".replace(/\[{2,}/g,", r".replace(/(?<!!)\[{2,}/g,"),
("quorum", "Klartext: Tag bleibt", r".replace(/(^|[^\p{L}\p{N}_\\])#(?=[^\s#])/gu, '$1\\#')", ""),
("quorum", "Klartext: Tag nur nach Leerraum", r"(^|[^\p{L}\p{N}_\\])#", r"(^|\s)#"),
("quorum", "Klartext: Gruppenueberschrift, Stelle roh", ", Stelle ${klartext(erste.stelle)}", ", Stelle ${erste.stelle}"),
("quorum", "Klartext: Befundzeile, Stelle roh", ", ${b.schwere}, Stelle ${klartext(b.stelle)})", ", ${b.schwere}, Stelle ${b.stelle})"),
("quorum", "Klartext: Befundzeile, Aussage roh", "}): ${klartext(b.aussage)}", "}): ${b.aussage}"),
("quorum", "Klartext: Beleg roh", " Beleg: ${klartext(b.beleg)}", " Beleg: ${b.beleg}"),
("quorum", "Klartext: Nachpruefung, Fundstelle roh", "${klartext(g.nachpruefung.fundstelle)}", "${g.nachpruefung.fundstelle}"),
("quorum", "Klartext: Nachpruefung, Begruendung roh", "${klartext(g.nachpruefung.begruendung)}", "${g.nachpruefung.begruendung}"),
("quorum", "Klartext: Unbelegt, Stelle roh", "Stelle ${klartext(b.stelle)}: ", "Stelle ${b.stelle}: "),
("quorum", "Klartext: Unbelegt, Aussage roh", ")}: ${klartext(b.aussage)}", ")}: ${b.aussage}"),
("quorum", "Klartext: Nicht pruefbar roh", "- ${NAMEN[n]}: ${klartext(t)}", "- ${NAMEN[n]}: ${t}"),
("quorum", "Klartext: Hinweise roh", "'- ' + klartext(h)", "'- ' + h"),
("quorum", "Klartext: Stimmentabelle, Grund roh", "'nein: ' + klartext(s.grund)", "'nein: ' + s.grund"),
("quorum", "Klartext: ganzer Bericht, Hub-Zeile entschaerft", r"return z.join('\n')", r"return klartext(z.join('\n'))"),
# Exit 2 der Planwerkzeuge als Befund (2026-11-02)
("welle-mit-pruefung", "Planbefund haelt nicht", " if (planBefunde.length) {", " if (false) {"),
("welle-mit-pruefung", "Befundzeile ohne Zeilennummer", r"const BEFUNDZEILE = /^BEFUND [^\s:]+:\d+:/", r"const BEFUNDZEILE = /^BEFUND /"),
("welle-mit-pruefung", "CR bleibt in der Befundzeile", r".map(z => z.replace(/\r$/, ''))", ""),
("welle-mit-pruefung", "Halt nur bei Exit 2", " const planBefunde = befundZeilen(aus)", " const planBefunde = exitsAller(r).includes(2) ? befundZeilen(aus) : []"),
("welle-mit-pruefung", "Befund nur im ersten Lauf gesucht", " const planBefunde = befundZeilen(aus)", " const planBefunde = befundZeilen(r && r.laeufe && r.laeufe[0] ? String(r.laeufe[0].ausgabe || '') : '')"),
("welle-mit-pruefung", "Halt ohne Temp-Ordner", " temp_ordner: tempHinweis(temp),\n welle: null,", " temp_ordner: null,\n welle: null,"),
("plankritik", "Exit 2 mit Befund gilt nicht als gelaufen", "(exit === 2 && befunde.length > 0) || ", ""),
("plankritik", "Exit 2 ohne Befund gilt als gelaufen", "(exit === 2 && befunde.length > 0)", "exit === 2"),
("plankritik", "Exit 3 mit Marke gilt nicht als gelaufen", " || (name === 'plan_bloecke' && exit === 3 && /^Nichts geprueft:/m.test(abschnitt))", ""),
("plankritik", "Exit 3 ohne Marke gilt als gelaufen", "exit === 3 && /^Nichts geprueft:/m.test(abschnitt)", "exit === 3"),
("plankritik", "Befunde dem falschen Werkzeug zugeordnet", "werkzeugStand(aus, 'plan_zeichen', grenze >= 0 ? aus.slice(0, grenze) : aus)", "werkzeugStand(aus, 'plan_zeichen', aus)"),
("plankritik", "Hilfsagent-Meldung fuer gescheiterte Werkzeuge", " else log('Planwerkzeuge: ' + [zeichen, bloecke]", " else log('Planwerkzeuge: Hilfsagent ohne Ergebnis; ' + [zeichen, bloecke]"),
("plankritik", "Ausgabe trotz gescheitertem Werkzeug an die Linse", " if (zeichen.gelaufen && bloecke.gelaufen) ctx.werkzeuge = aus", " if (zeichen.gelaufen || bloecke.gelaufen) ctx.werkzeuge = aus"),
("plankritik", "materialisieren: Planbefund als Werkzeugfehler", " if (befunde.length) {\n befunde.forEach(", " if (false) {\n befunde.forEach("),
("plankritik", "materialisieren: Planbefund nicht erfasst", " befunde.forEach(befund => ctx.befunde.push({ werkzeug: 'plan_materialisiere', befund }))\n", ""),
("plankritik", "materialisieren: Temp-Ordner nicht genannt", " if (m) ctx.temp = m[1].replace(/\\/mat$/, '')\n log(", " log("),
("plankritik", "Befundzeile ohne Zeilennummer", r"const BEFUNDZEILE = /^BEFUND [^\s:]+:\d+:/", r"const BEFUNDZEILE = /^BEFUND /"),
("plankritik", "CR bleibt in der Befundzeile", r".map(z => z.replace(/\r$/, ''))", ""),
("plankritik", "gebremst ohne werkzeug_befunde", " werkzeug_befunde: ctx.befunde,\n ohne_ergebnis: gewaehlt,", " ohne_ergebnis: gewaehlt,"),
("plankritik", "fertig ohne werkzeug_befunde", " werkzeug_befunde: ctx.befunde,\n ohne_ergebnis: ohneErgebnis,", " ohne_ergebnis: ohneErgebnis,"),
("plankritik", "Planwerkzeuge nur im ersten Lauf gelesen", " const aus = ausgabeAller(r)\n const grenze", " const aus = String(lauf.ausgabe || '')\n const grenze"),
("plankritik", "materialisieren: Befund nur im ersten Lauf", " const befunde = befundZeilen(aus)", " const befunde = befundZeilen(r.laeufe[0].ausgabe)"),
# Alle laeufe an fuenf weiteren Stellen (2026-11-02, Folgeauftrag): je Stelle wieder nur laeufe[0], fuer die Kopie auch das Exit nur eines Laufs
("welle-mit-pruefung", "Kopie: KOPIE-Zeile nur im ersten Lauf", r" const m = /^KOPIE=(.+?)\s*$/m.exec(aus)", r" const m = /^KOPIE=(.+?)\s*$/m.exec(r && r.laeufe && r.laeufe[0] ? String(r.laeufe[0].ausgabe || '') : '')"),
("welle-mit-pruefung", "Kopie: angewendete Bloecke nur im ersten Lauf", " const angewendet = /'(neu|ersetzt|angehaengt)': [1-9]/.test(aus)", " const angewendet = /'(neu|ersetzt|angehaengt)': [1-9]/.test(r && r.laeufe && r.laeufe[0] ? String(r.laeufe[0].ausgabe || '') : '')"),
("welle-mit-pruefung", "Kopie: nur das Exit des ersten Laufs", "const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0)", "const alleExitNull = r => exitsAller(r)[0] === 0"),
("welle-mit-pruefung", "Kopie: nur das Exit des letzten Laufs", "const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0)", "const alleExitNull = r => exitsAller(r).slice(-1)[0] === 0"),
("welle-mit-pruefung", "Vergleich nur aus dem ersten Lauf", "kopfUndEnde(ausgabeAller(belege.vergleich), 1500, 2500)", "kopfUndEnde(belege.vergleich && belege.vergleich.laeufe[0] ? belege.vergleich.laeufe[0].ausgabe : '', 1500, 2500)"),
("plankritik", "materialisieren: KOPIE-Zeile nur im ersten Lauf", r" const m = /^KOPIE=(.+?)\s*$/m.exec(aus)", r" const m = /^KOPIE=(.+?)\s*$/m.exec(r.laeufe[0].ausgabe)"),
("plankritik", "materialisieren: angewendete Bloecke nur im ersten Lauf", " const angewendet = /'(neu|ersetzt|angehaengt)': [1-9]/.test(aus)", " const angewendet = /'(neu|ersetzt|angehaengt)': [1-9]/.test(r.laeufe[0].ausgabe)"),
("plankritik", "materialisieren: nur das Exit des ersten Laufs", "const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0)", "const alleExitNull = r => exitsAller(r)[0] === 0"),
("plankritik", "materialisieren: nur das Exit des letzten Laufs", "const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0)", "const alleExitNull = r => exitsAller(r).slice(-1)[0] === 0"),
("plankritik", "Mutantenlauf nur aus dem ersten Lauf", " const aus = ausgabeAller(r)\n const exitMatch", " const aus = r && r.laeufe && r.laeufe[0] ? String(r.laeufe[0].ausgabe || '') : ''\n const exitMatch"),
("quorum", "Hilfsagenten-Ausgabe nur aus dem ersten Lauf", r" return laufTexte(r).join('\n')", r" return laufTexte(r).slice(0, 1).join('\n')"),
("plankritik", "KEINE_MUTANTEN_TOML unverankert", r"if (/^KEINE_MUTANTEN_TOML\s*$/m.test(aus))", "if (/KEINE_MUTANTEN_TOML/.test(aus))"),
("welle-mit-pruefung", "Kopie: Lauf ohne exit zaehlt als 0", "const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0)", "const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0 || x == null)"),
("plankritik", "materialisieren: Lauf ohne exit zaehlt als 0", "const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0)", "const alleExitNull = r => exitsAller(r).length > 0 && exitsAller(r).every(x => x === 0 || x == null)"),
("welle-mit-pruefung", "Vergleich umgekehrt verbunden", "kopfUndEnde(ausgabeAller(belege.vergleich), 1500, 2500)", "kopfUndEnde(ausgabeAller({ laeufe: [...((belege.vergleich && belege.vergleich.laeufe) || [])].reverse() }), 1500, 2500)"),
("plankritik", "materialisieren: ohne-Ergebnis-Wache fehlt", " if (!exits.length) { log(`${wozu}: Hilfsagent ohne Ergebnis, keine Kopie`); return null }\n", ""),
]
def main() -> int:
ordner = Path(sys.argv[1]) if len(sys.argv) > 1 else HIER.parent.parent / ".claude" / "workflows"
ueberlebt, veraltet = [], []
for datei, name, alt, neu in MUTANTEN:
with tempfile.TemporaryDirectory() as tmp:
ziel = Path(tmp) / "wf"
shutil.copytree(ordner, ziel)
pfad = ziel / (datei + ".js")
text = pfad.read_text(encoding="utf-8")
if text.count(alt) != 1:
veraltet.append(name)
print(f"VERALTET {datei}: {name} (Muster {text.count(alt)}-mal)")
continue
pfad.write_text(text.replace(alt, neu), encoding="utf-8", newline="\n")
lauf = subprocess.run(["node", str(FAELLE), str(ziel)], capture_output=True, text=True, encoding="utf-8", errors="replace")
rot = [z for z in lauf.stdout.splitlines() if z.startswith("FEHLER")]
if lauf.returncode == 0:
grund = WIRKUNGSLOS.get(name)
if grund:
print(f"WIRKUNGSLOS {datei}: {name} ({grund})")
else:
ueberlebt.append(name)
print(f"UEBERLEBT {datei}: {name}")
else:
print(f"GEFANGEN {datei}: {name} ({len(rot)} rot, z. B. {rot[0][7:90] if rot else 'Absturz'})")
print(f"{len(MUTANTEN)} Mutanten, {len(ueberlebt)} ueberlebt, {len(veraltet)} veraltet")
return 2 if veraltet else 1 if ueberlebt else 0
if __name__ == "__main__":
sys.stdout.reconfigure(errors="replace")
sys.exit(main())