- .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]>
288 lines
35 KiB
Python
288 lines
35 KiB
Python
"""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())
|