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:
1 parent
b7cc7433a9
commit
c5da36cb41
18 files changed
+1055
-35
No files matched your search
@@ -27,6 +27,27 @@ Geschrieben am 2026-08-30 **vor** der ersten Zeile Code, wie es [[AI/Regelwerk/p
|
||||
|
||||
→ **Aufgelöst durch eine erzwungene Form.** Das Skript sucht das Literal `docs/plans/JJJJ-MM-TT` und klassifiziert das unmittelbar folgende Zeichen: `/` = Ordnerform, `.` = Dateiform. Regel: **Wer die Dateiform nennt, ohne die Ordnerform zu nennen, ist ein Befund** — denn `codeworkflow.md` schreibt beide vor und die Ordnerform ist die, die vergessen wird. Genau dieser Fall lag am 2026-08-30 in `planer.md` vor.
|
||||
|
||||
## Nachtrag 2026-09-23: Prüfung 5 prüft auch `effort:`
|
||||
|
||||
Geschrieben vor der Änderung am Skript. Anlass: Keine Agentendatei trug `effort:`, jeder Subagent erbte deshalb den Effort der Hauptsitzung, oft `xhigh` oder `max`. Seitdem steht der Effort je Rolle im Stamm und im Frontmatter (Messung und Begründung: [[AI/Regelwerk/codeworkflow|codeworkflow.md]], „Effort je Rolle"). Die Prüfung sorgt dafür, dass beide gleich bleiben.
|
||||
|
||||
| # | Frage | Herkunft | Auflösung |
|
||||
|---|---|---|---|
|
||||
| 5a | Welche Effort-Werte gibt es? | **Quelle** für die Stufen — Claude Code 2.1.280, Enum `["low","medium","high","xhigh","max"]` in der Programmdatei (nachgelesen 2026-09-23). Der Leser für Agentendateien nimmt darüber hinaus Großschreibung, `med` und ganze Zahlen an. **Projektvorgabe** ist die engere Form. | Erlaubt sind nur die fünf Wörter, klein geschrieben: eine Schreibweise, die jeder ohne Programmkenntnis liest. Die Meldung sagt deshalb „nicht in der Vorgabe", nicht „ungültig". Anführungszeichen um den Wert sind YAML und zählen nicht mit. |
|
||||
| 5b | Welcher Effort gilt je Agent? | **Projektvorgabe** — Rollentabelle im Stamm, Spalte mit der Überschrift „Effort", erster Backtick-Ausdruck der Zelle | Das `effort:` im Frontmatter muss ihm gleichen. Die Spalte wird über ihre Überschrift gefunden, nicht über die Position. |
|
||||
| 5c | Agentendatei ohne `effort:` oder mit leerem Wert | **Projektvorgabe** — Stamm: „Deshalb trägt jede Agentendatei `effort:`" | Befund: Der Agent erbt den Effort der Hauptsitzung. |
|
||||
| 5d | Rollentabelle ohne Effort-Spalte | **Projektvorgabe** | Befund am Stamm. Die Effort-Hälfte von Prüfung 5 schweigt dann nicht, sie meldet, dass sie nicht laufen kann. |
|
||||
| 5e | Tabellenzeile eines Agenten ohne Backtick-Wert in der Effort-Spalte | **Projektvorgabe** | Befund am Stamm, mit dem Namen des Agenten. |
|
||||
|
||||
Benannte Lücken, bewusst nicht geprüft:
|
||||
|
||||
- **`effort:` bei `model: haiku`.** Haiku 4.5 kennt keinen Effort; Claude Code sendet ihn dann nicht (Programmdatei). Die Rollentabelle führt keinen Agenten auf Haiku; die Abschrift-Stufe steht im Task-Frontmatter, nicht in einer Agentendatei.
|
||||
- **Doppelter Schlüssel `effort:`.** Der flache Frontmatter-Leser nimmt den letzten Wert, wie bei `model:`.
|
||||
- **Ob Claude Code den Wert anwendet.** Das zeigt nur das Feld `effort` im Transkript eines echten Laufs, und eine geänderte Agentendatei wirkt in der laufenden Sitzung nicht sofort. Belegt am 2026-09-23 in frischen Sitzungen über das Agent-Werkzeug und im Workflow.
|
||||
- **`effort` in gespeicherten Workflows unter `.claude/workflows/`.** Sie setzen den Effort mit `agentType` absichtlich nicht; eine Abweichung dort sieht diese Prüfung nicht.
|
||||
|
||||
Dazu zwei Korrekturen am Regressionslauf, die mit diesem Nachtrag gekommen sind: Die Grundlinie setzt `status: pending-review` in ihrer Kopie auf `reference`. Vorher brach der Lauf nach jeder Selbstpflege an der Grundlinie ab, weil Prüfung 4 dann anschlägt — gemessen am 2026-09-23. Und Prüfung 4 bekommt dafür erstmals einen eigenen Angriffsfall.
|
||||
|
||||
## Positivliste, nicht Negativliste
|
||||
|
||||
Alle sechs Prüfungen schränken **geschlossene** Mengen ein: die Menge der Wikilinks in einer Datei, die Menge der Pflichtfelder, die Menge der Agentendateien, die Menge der Regelwerk-Dateien. Es gibt keine Prüfung, die eine offene Menge verbotener Muster abzählt — und damit auch keine handgepflegte Negativliste, die gegen sie verlieren könnte.
|
||||
@@ -46,6 +67,16 @@ Je Fall eine Wegwerfkopie des Vaults, Rücksetzen dazwischen, Exit-Code und ausl
|
||||
| leer | Regelwerk-Datei auf 0 Byte gekürzt |
|
||||
| zusätzlich | neue Regelwerk-Datei, die niemand verlinkt |
|
||||
| Wertebereich | `model: sonnet` in `pruefer.md` (Tabelle sagt `opus`) |
|
||||
| Wertebereich | `effort: xhigh` in `pruefer.md` (Tabelle sagt `high`) |
|
||||
| Wertebereich | `effort: hoch` in `planer.md` — kein gültiger Wert |
|
||||
| fehlt, **Reihenfolge** | `effort:` fehlt in der ersten Agentendatei (`executer.md`) und, als eigener Fall, in der letzten (`recherche.md`) |
|
||||
| null | `effort:` ohne Wert in `planer.md` |
|
||||
| Umgebung | Effort-Spalte der Rollentabelle entfernt |
|
||||
| Verweis ins Leere | Effort-Zelle eines Agenten ohne Backtick-Wert |
|
||||
| Freigabe | eine Regelwerk-Datei auf `status: pending-review` |
|
||||
| Gegenfall (muss stumm bleiben) | `effort: "high"` in Anführungszeichen |
|
||||
| Gegenfall (muss stumm bleiben) | Zusatzspalte mit Backtick-Wert hinter der Effort-Spalte — fängt einen Rückfall auf die Spaltenposition |
|
||||
| Gegenfall (muss stumm bleiben) | Effort-Zelle mit zweitem Backtick-Wert — fängt „letzter statt erster Backtick-Ausdruck" |
|
||||
| Verweis ins Leere | Wikilink auf nicht existierende Datei |
|
||||
| Konvention | Dateiform der Planablage ohne Ordnerform |
|
||||
| **Reihenfolge** | toter Wikilink an **erster** und an **letzter** Position derselben Datei |
|
||||
@@ -57,7 +88,7 @@ Die letzten drei sind die wichtigsten: Sie prüfen, dass eine fehlende Vorausset
|
||||
|
||||
## Abbruchbedingung, vor der ersten Runde benannt
|
||||
|
||||
Schluss ist, wenn eine Runde nur noch Umformulierungen findet oder wenn alle verbleibenden Befunde dieselbe Antwort haben. Die Prüfung ist fertig, wenn alle dreizehn Fälle der Angriffsliste anschlagen und die Grundlinie grün ist — nicht, wenn sie „durchläuft".
|
||||
Schluss ist, wenn eine Runde nur noch Umformulierungen findet oder wenn alle verbleibenden Befunde dieselbe Antwort haben. Die Prüfung ist fertig, wenn alle Fälle der Angriffsliste anschlagen, alle Gegenfälle stumm bleiben und die Grundlinie grün ist — nicht, wenn sie „durchläuft". Die Zahl der Fälle zählt der Regressionslauf selbst; hier steht keine, weil sie bei jeder Erweiterung von Hand nachgezogen und dabei falsch würde.
|
||||
|
||||
## Was diese Prüfung ausdrücklich nicht ist
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ Sechs Prüfungen, jede über eine GESCHLOSSENE Menge:
|
||||
2 Frontmatter Trägt jede Regelwerk-Datei die sieben Pflichtfelder?
|
||||
3 Erreichbarkeit Ist jede Regelwerk-Datei vom Stamm aus verlinkt?
|
||||
4 Freigabe Steht eine Regelwerk-Datei auf status: pending-review?
|
||||
5 Modell Weicht model: einer Agentendatei von der Rollentabelle ab?
|
||||
5 Modell/Effort Weichen model: oder effort: einer Agentendatei von der Rollentabelle ab?
|
||||
6 Planablage Nennt eine Datei die Dateiform ohne die Ordnerform?
|
||||
|
||||
Die Rollentabelle und die Liste der Regelwerk-Verweise werden AUS CLAUDE.md
|
||||
@@ -36,8 +36,12 @@ WAS DIESE PRÜFUNG NICHT SIEHT — benannte Lücken
|
||||
eine Heuristik im Positivlisten-Gewand. Bewusst weggelassen.
|
||||
|
||||
* Modellaussagen im Fließtext. "(`executer`, Sonnet — bei reiner Abschrift
|
||||
Haiku)" ist für einen Menschen eindeutig und für ein Skript nicht. Geprüft
|
||||
wird ausschließlich das model:-Feld im Frontmatter.
|
||||
Haiku)" ist für einen Menschen eindeutig und für ein Skript nicht.
|
||||
Geprüft werden ausschließlich die Felder model: und effort: im
|
||||
Frontmatter.
|
||||
|
||||
* Ein YAML-Kommentar hinter dem Wert (effort: high # …) wird nicht
|
||||
abgeschnitten und ergibt einen Befund; für model: galt das schon vorher.
|
||||
|
||||
* INHALTLICHE Widersprüche. Zwei Dateien können dieselbe Regel in
|
||||
verschiedenen Worten gegensätzlich formulieren, und diese Prüfung meldet
|
||||
@@ -46,6 +50,23 @@ WAS DIESE PRÜFUNG NICHT SIEHT — benannte Lücken
|
||||
aufgefallen: die Planablage (Prüfung 6) ja, "fortsetzen statt frisch
|
||||
starten" nein.
|
||||
|
||||
* effort: bei model: haiku. Haiku 4.5 kennt keinen Effort; Claude Code
|
||||
sendet ihn dann nicht (Claude Code 2.1.280, Programmdatei, nachgelesen
|
||||
2026-09-23). Die Rollentabelle führt keinen Agenten auf Haiku; die
|
||||
Abschrift-Stufe steht im Task-Frontmatter, nicht in einer Agentendatei.
|
||||
|
||||
* Doppelter Schlüssel effort:. Der flache Frontmatter-Leser nimmt den
|
||||
letzten Wert, wie bei model:.
|
||||
|
||||
* Ob Claude Code den Wert anwendet. Das zeigt nur das Feld effort im
|
||||
Transkript eines echten Laufs, und eine geänderte Agentendatei wirkt
|
||||
in der laufenden Sitzung nicht sofort. Belegt am 2026-09-23 in frischen
|
||||
Sitzungen über das Agent-Werkzeug und im Workflow.
|
||||
|
||||
* effort in gespeicherten Workflows unter .claude/workflows/. Sie setzen
|
||||
den Effort mit agentType absichtlich nicht; eine Abweichung dort sieht
|
||||
diese Prüfung nicht.
|
||||
|
||||
===============================================================================
|
||||
DIESES SKRIPT IST EIN FANGNETZ, KEIN NACHWEIS.
|
||||
===============================================================================
|
||||
@@ -103,6 +124,11 @@ def wikilinks(text: str) -> list[str]:
|
||||
PLANPFAD = re.compile(r"docs/plans/JJJJ-MM-TT[^\s`]*")
|
||||
BACKTICK = re.compile(r"`([^`]+)`")
|
||||
|
||||
# Die Stufen stammen aus Claude Code 2.1.280 (Programmdatei, nachgelesen
|
||||
# 2026-09-23). Claude Code selbst nimmt zusätzlich Großschreibung, "med"
|
||||
# und ganze Zahlen an; die Vorgabe hier ist bewusst enger.
|
||||
EFFORT_WERTE = ("low", "medium", "high", "xhigh", "max")
|
||||
|
||||
|
||||
class Befund:
|
||||
def __init__(self, schwere: str, ort: str, text: str) -> None:
|
||||
@@ -163,7 +189,7 @@ def rollentabelle(text: str) -> dict[str, str]:
|
||||
"""Liest die Zuordnung Agent -> Modellalias aus der Tabelle im Stamm.
|
||||
|
||||
Eine Zeile sieht so aus:
|
||||
| Planen, Spezifizieren | `planer` | Opus 5 (`opus`) |
|
||||
| Planen, Spezifizieren | `planer` | Opus 5.5 (`opus`) | `high` (Frontmatter) |
|
||||
Genommen wird der erste Backtick-Ausdruck der Agenten- und der
|
||||
Modellspalte. Zeilen ohne Backticks in der Agentenspalte (etwa
|
||||
'Hauptsitzung') werden übersprungen.
|
||||
@@ -184,6 +210,52 @@ def rollentabelle(text: str) -> dict[str, str]:
|
||||
return zuordnung
|
||||
|
||||
|
||||
def entquotet(wert: str) -> str:
|
||||
"""Nimmt passende einfache oder doppelte Anführungszeichen ab: 'high' -> high."""
|
||||
if len(wert) >= 2 and wert[0] == wert[-1] and wert[0] in ("'", '"'):
|
||||
return wert[1:-1]
|
||||
return wert
|
||||
|
||||
|
||||
def effort_spalte(text: str) -> int | None:
|
||||
"""Findet den Index der Effort-Spalte in der Rollentabelle im Stamm.
|
||||
|
||||
Gesucht wird die Tabellenzeile, deren Zellen (getrimmt) sowohl 'Agent'
|
||||
als auch 'Effort' enthalten — das ist die Kopfzeile. Der Index der Zelle
|
||||
'Effort' ist die Effort-Spalte.
|
||||
"""
|
||||
for zeile in text.split("\n"):
|
||||
if not zeile.startswith("|"):
|
||||
continue
|
||||
spalten = [s.strip() for s in zeile.strip("|").split("|")]
|
||||
if "Agent" in spalten and "Effort" in spalten:
|
||||
return spalten.index("Effort")
|
||||
return None
|
||||
|
||||
|
||||
def rollentabelle_effort(text: str, spalte: int) -> dict[str, str]:
|
||||
"""Liest den Effort je Agent aus der Rollentabelle im Stamm.
|
||||
|
||||
Genommen wird der erste Backtick-Ausdruck der Effort-Spalte (`spalte`).
|
||||
Fehlt ein Backtick-Ausdruck, ist der Wert für diesen Agenten leer.
|
||||
"""
|
||||
zuordnung: dict[str, str] = {}
|
||||
for zeile in text.split("\n"):
|
||||
if not zeile.startswith("|"):
|
||||
continue
|
||||
spalten = [s.strip() for s in zeile.strip("|").split("|")]
|
||||
if len(spalten) <= spalte:
|
||||
continue
|
||||
agenten = BACKTICK.findall(spalten[1])
|
||||
if not agenten:
|
||||
continue
|
||||
effort_treffer = BACKTICK.findall(spalten[spalte])
|
||||
wert = effort_treffer[0].strip() if effort_treffer else ""
|
||||
for name in agenten:
|
||||
zuordnung[name.strip()] = wert
|
||||
return zuordnung
|
||||
|
||||
|
||||
def stamm_regelwerkverweise(text: str) -> set[str]:
|
||||
"""Alle im Stamm verlinkten Regelwerk-Dateien, als Dateiname ohne Endung."""
|
||||
namen: set[str] = set()
|
||||
@@ -252,7 +324,13 @@ def pruefe_freigabe(vault: Path, befunde: list[Befund]) -> None:
|
||||
)
|
||||
|
||||
|
||||
def pruefe_modelle(vault: Path, tabelle: dict[str, str], befunde: list[Befund]) -> None:
|
||||
def pruefe_modelle(
|
||||
vault: Path,
|
||||
tabelle: dict[str, str],
|
||||
effort_index: int | None,
|
||||
effort_tabelle: dict[str, str],
|
||||
befunde: list[Befund],
|
||||
) -> None:
|
||||
for datei in sorted((vault / AGENTEN).glob("*.md")):
|
||||
rel = datei.relative_to(vault).as_posix()
|
||||
fm = frontmatter(lies(datei))
|
||||
@@ -278,6 +356,47 @@ def pruefe_modelle(vault: Path, tabelle: dict[str, str], befunde: list[Befund])
|
||||
)
|
||||
)
|
||||
|
||||
# Effort: nur prüfbar, wenn die Effort-Spalte der Rollentabelle
|
||||
# gefunden wurde (siehe main() für den Befund am Stamm, falls nicht).
|
||||
if effort_index is None:
|
||||
continue
|
||||
soll = effort_tabelle.get(name, "")
|
||||
if not soll:
|
||||
befunde.append(
|
||||
Befund("wichtig", "CLAUDE.md", f"Rollentabelle nennt für '{name}' keinen Effort")
|
||||
)
|
||||
effort_roh = fm.get("effort")
|
||||
if not effort_roh:
|
||||
befunde.append(
|
||||
Befund(
|
||||
"wichtig",
|
||||
rel,
|
||||
"kein effort: im Frontmatter — der Agent erbt den Effort der Hauptsitzung",
|
||||
)
|
||||
)
|
||||
continue
|
||||
effort_wert = entquotet(effort_roh)
|
||||
if not effort_wert:
|
||||
befunde.append(
|
||||
Befund(
|
||||
"wichtig",
|
||||
rel,
|
||||
"kein effort: im Frontmatter — der Agent erbt den Effort der Hauptsitzung",
|
||||
)
|
||||
)
|
||||
elif effort_wert not in EFFORT_WERTE:
|
||||
befunde.append(
|
||||
Befund(
|
||||
"blockierend",
|
||||
rel,
|
||||
f"effort: {effort_wert} ist nicht in der Vorgabe (erlaubt: low, medium, high, xhigh, max)",
|
||||
)
|
||||
)
|
||||
elif soll and effort_wert != soll:
|
||||
befunde.append(
|
||||
Befund("blockierend", rel, f"effort: {effort_wert} — Rollentabelle im Stamm sagt {soll}")
|
||||
)
|
||||
|
||||
|
||||
def pruefe_planablage(vault: Path, dateien: list[Path], befunde: list[Befund]) -> None:
|
||||
for datei in dateien:
|
||||
@@ -339,6 +458,17 @@ def main() -> int:
|
||||
Befund("blockierend", "CLAUDE.md", "keine Regelwerk-Verweise gefunden — Prüfung 3 kann nicht laufen")
|
||||
)
|
||||
|
||||
effort_index = effort_spalte(stammtext) if tabelle else None
|
||||
if tabelle and effort_index is None:
|
||||
befunde.append(
|
||||
Befund(
|
||||
"blockierend",
|
||||
"CLAUDE.md",
|
||||
"Effort-Spalte der Rollentabelle nicht auffindbar — Prüfung 5 kann den Effort nicht prüfen",
|
||||
)
|
||||
)
|
||||
effort_tabelle = rollentabelle_effort(stammtext, effort_index) if effort_index is not None else {}
|
||||
|
||||
dateien = dateien_im_geltungsbereich(vault)
|
||||
|
||||
pruefe_wikilinks(vault, dateien, befunde); gelaufen += 1
|
||||
@@ -347,7 +477,7 @@ def main() -> int:
|
||||
pruefe_erreichbarkeit(vault, verlinkt, befunde); gelaufen += 1
|
||||
pruefe_freigabe(vault, befunde); gelaufen += 1
|
||||
if tabelle and (vault / AGENTEN).is_dir():
|
||||
pruefe_modelle(vault, tabelle, befunde); gelaufen += 1
|
||||
pruefe_modelle(vault, tabelle, effort_index, effort_tabelle, befunde); gelaufen += 1
|
||||
pruefe_planablage(vault, dateien, befunde); gelaufen += 1
|
||||
|
||||
except SystemExit:
|
||||
|
||||
@@ -6,7 +6,10 @@ Angriffsliste und Begründung: .vault-tools/regelwerk-check/ENTWURF.md
|
||||
|
||||
Der Treiber baut aus dem echten Vault einen Minimalvault, in dem die Prüfung
|
||||
sauber durchläuft — das ist die GRUNDLINIE. Läuft sie nicht grün, misst der
|
||||
Rest nichts und der Treiber bricht ab.
|
||||
Rest nichts und der Treiber bricht ab. Dazu normalisiert die Grundlinie jede
|
||||
Frontmatter-Zeile status: pending-review auf status: reference — ohne das
|
||||
bricht der Lauf nach jeder Selbstpflege an der Grundlinie ab, weil Prüfung 4
|
||||
dann anschlägt (gemessen am 2026-09-23).
|
||||
|
||||
Danach je Angriffsfall: frische Kopie der Grundlinie, genau eine Beschädigung,
|
||||
Prüfung fahren, Exit-Code und auslösende Meldung protokollieren. Ein Fall gilt
|
||||
@@ -64,6 +67,15 @@ def baue_grundlinie(ziel: Path) -> None:
|
||||
unter.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(skill, unter / "SKILL.md")
|
||||
|
||||
# jede Zeile status: pending-review auf status: reference normalisieren.
|
||||
# Ohne das bricht der Lauf nach jeder Selbstpflege an der Grundlinie ab,
|
||||
# weil Prüfung 4 (Freigabe) dann anschlägt — gemessen am 2026-09-23.
|
||||
for datei in (ziel / "AI" / "Regelwerk").glob("*.md"):
|
||||
t = datei.read_text(encoding="utf-8")
|
||||
neu = re.sub(r"^status: pending-review$", "status: reference", t, flags=re.M)
|
||||
if neu != t:
|
||||
datei.write_text(neu, encoding="utf-8")
|
||||
|
||||
# Jedes Linkziel außerhalb des Geltungsbereichs als leere Datei anlegen,
|
||||
# damit die Grundlinie nicht an fehlendem Umfeld scheitert.
|
||||
for datei in list(ziel.rglob("*.md")):
|
||||
@@ -158,6 +170,144 @@ def s_keine_rollentabelle(v: Path) -> None:
|
||||
f.write_text(re.sub(r"^\|.*$", "", t, flags=re.M), encoding="utf-8")
|
||||
|
||||
|
||||
def s_effort_wertebereich_xhigh(v: Path) -> None:
|
||||
f = v / ".claude" / "agents" / "pruefer.md"
|
||||
t = f.read_text(encoding="utf-8")
|
||||
muster = r"^effort: high$"
|
||||
assert len(re.findall(muster, t, flags=re.M)) == 1
|
||||
f.write_text(re.sub(muster, "effort: xhigh", t, count=1, flags=re.M), encoding="utf-8")
|
||||
|
||||
|
||||
def s_effort_wertebereich_ungueltig(v: Path) -> None:
|
||||
f = v / ".claude" / "agents" / "planer.md"
|
||||
t = f.read_text(encoding="utf-8")
|
||||
muster = r"^effort: high$"
|
||||
assert len(re.findall(muster, t, flags=re.M)) == 1
|
||||
f.write_text(re.sub(muster, "effort: hoch", t, count=1, flags=re.M), encoding="utf-8")
|
||||
|
||||
|
||||
def s_effort_fehlt_erste(v: Path) -> None:
|
||||
"""Reihenfolge-Fall: effort: fehlt in der ERSTEN Agentendatei (executer.md)."""
|
||||
f = v / ".claude" / "agents" / "executer.md"
|
||||
t = f.read_text(encoding="utf-8")
|
||||
muster = r"^effort:.*\n"
|
||||
assert len(re.findall(muster, t, flags=re.M)) == 1
|
||||
f.write_text(re.sub(muster, "", t, count=1, flags=re.M), encoding="utf-8")
|
||||
|
||||
|
||||
def s_effort_fehlt_letzte(v: Path) -> None:
|
||||
"""Reihenfolge-Fall: derselbe Schaden in der LETZTEN Agentendatei (recherche.md)."""
|
||||
f = v / ".claude" / "agents" / "recherche.md"
|
||||
t = f.read_text(encoding="utf-8")
|
||||
muster = r"^effort:.*\n"
|
||||
assert len(re.findall(muster, t, flags=re.M)) == 1
|
||||
f.write_text(re.sub(muster, "", t, count=1, flags=re.M), encoding="utf-8")
|
||||
|
||||
|
||||
def s_effort_null(v: Path) -> None:
|
||||
f = v / ".claude" / "agents" / "planer.md"
|
||||
t = f.read_text(encoding="utf-8")
|
||||
muster = r"^effort:.*$"
|
||||
assert len(re.findall(muster, t, flags=re.M)) == 1
|
||||
f.write_text(re.sub(muster, "effort:", t, count=1, flags=re.M), encoding="utf-8")
|
||||
|
||||
|
||||
def s_effort_spalte_fehlt(v: Path) -> None:
|
||||
"""Entfernt in der Rollentabelle bei der Kopfzeile und allen direkt
|
||||
folgenden Tabellenzeilen die letzte Zelle — die Effort-Spalte."""
|
||||
f = v / "CLAUDE.md"
|
||||
zeilen = f.read_text(encoding="utf-8").split("\n")
|
||||
kopfzeilen = [
|
||||
i for i, z in enumerate(zeilen)
|
||||
if z.startswith("|") and "Agent" in z and "Effort" in z
|
||||
]
|
||||
assert len(kopfzeilen) == 1
|
||||
start = kopfzeilen[0]
|
||||
ende = start
|
||||
while ende < len(zeilen) and zeilen[ende].startswith("|"):
|
||||
ende += 1
|
||||
for i in range(start, ende):
|
||||
teile = zeilen[i].split("|")
|
||||
kern = teile[1:-1]
|
||||
if len(kern) < 2:
|
||||
continue
|
||||
zeilen[i] = "|" + "|".join(kern[:-1]) + "|"
|
||||
f.write_text("\n".join(zeilen), encoding="utf-8")
|
||||
|
||||
|
||||
def s_effort_verweis_leer(v: Path) -> None:
|
||||
"""Entfernt in der Tabellenzeile mit `pruefer` die Backticks um 'high'."""
|
||||
f = v / "CLAUDE.md"
|
||||
zeilen = f.read_text(encoding="utf-8").split("\n")
|
||||
treffer = [i for i, z in enumerate(zeilen) if z.startswith("|") and "`pruefer`" in z]
|
||||
assert len(treffer) == 1
|
||||
i = treffer[0]
|
||||
assert zeilen[i].count("`high`") == 1
|
||||
zeilen[i] = zeilen[i].replace("`high`", "high", 1)
|
||||
f.write_text("\n".join(zeilen), encoding="utf-8")
|
||||
|
||||
|
||||
def s_effort_freigabe_pending(v: Path) -> None:
|
||||
f = v / "AI" / "Regelwerk" / "schreibstil.md"
|
||||
t = f.read_text(encoding="utf-8")
|
||||
muster = r"^status:.*$"
|
||||
assert len(re.findall(muster, t, flags=re.M)) == 1
|
||||
f.write_text(re.sub(muster, "status: pending-review", t, count=1, flags=re.M), encoding="utf-8")
|
||||
|
||||
|
||||
def g_effort_anfuehrung(v: Path) -> None:
|
||||
"""Kein Schaden: effort in passenden Anführungszeichen ist gültiges YAML.
|
||||
|
||||
Muss stumm bleiben — Regressionslauf-Fall zu Punkt 4 der Effort-Prüfung
|
||||
(Prüfwerkzeuge.md, Abschnitt 5: neuer Code bekommt seinen eigenen Fall).
|
||||
"""
|
||||
f = v / ".claude" / "agents" / "pruefer.md"
|
||||
t = f.read_text(encoding="utf-8")
|
||||
muster = r"^effort: high$"
|
||||
assert len(re.findall(muster, t, flags=re.M)) == 1
|
||||
f.write_text(re.sub(muster, 'effort: "high"', t, count=1, flags=re.M), encoding="utf-8")
|
||||
|
||||
|
||||
def g_zusatzspalte(v: Path) -> None:
|
||||
"""Kein Schaden: eine Spalte hinter Effort mit eigenem Backtick-Wert.
|
||||
|
||||
Muss stumm bleiben — die Effort-Spalte wird über ihre Überschrift
|
||||
gefunden, nicht über die letzte Spalte.
|
||||
"""
|
||||
f = v / "CLAUDE.md"
|
||||
zeilen = f.read_text(encoding="utf-8").split("\n")
|
||||
kopfzeilen = [
|
||||
i for i, z in enumerate(zeilen)
|
||||
if z.startswith("|") and "Agent" in z and "Effort" in z
|
||||
]
|
||||
assert len(kopfzeilen) == 1
|
||||
start = kopfzeilen[0]
|
||||
zeilen[start] = zeilen[start].rstrip() + " Notiz |"
|
||||
zeilen[start + 1] = zeilen[start + 1].rstrip() + " --- |"
|
||||
i = start + 2
|
||||
while i < len(zeilen) and zeilen[i].startswith("|"):
|
||||
zeilen[i] = zeilen[i].rstrip() + " `xhigh` |"
|
||||
i += 1
|
||||
f.write_text("\n".join(zeilen), encoding="utf-8")
|
||||
|
||||
|
||||
def g_zweiter_backtick(v: Path) -> None:
|
||||
"""Kein Schaden: ein zweiter Backtick-Ausdruck in der Effort-Zelle.
|
||||
|
||||
Muss stumm bleiben — genommen wird der erste Backtick-Ausdruck.
|
||||
"""
|
||||
f = v / "CLAUDE.md"
|
||||
zeilen = f.read_text(encoding="utf-8").split("\n")
|
||||
treffer = [i for i, z in enumerate(zeilen) if z.startswith("|") and "`pruefer`" in z]
|
||||
assert len(treffer) == 1
|
||||
i = treffer[0]
|
||||
assert zeilen[i].count("`high` (Frontmatter)") == 1
|
||||
zeilen[i] = zeilen[i].replace(
|
||||
"`high` (Frontmatter)", "`high` (Frontmatter, nicht `xhigh`)", 1
|
||||
)
|
||||
f.write_text("\n".join(zeilen), encoding="utf-8")
|
||||
|
||||
|
||||
def g_link_im_codeblock(v: Path) -> None:
|
||||
"""Kein Schaden, sondern eine Falle: Wikilink-artiges IN Code.
|
||||
|
||||
@@ -187,6 +337,9 @@ def g_link_im_codeblock(v: Path) -> None:
|
||||
# seinen eigenen Fall (pruefwerkzeuge.md, Abschnitt 5).
|
||||
GEGENFAELLE = [
|
||||
("codeblock | Wikilink-artiges in Code und Backticks", g_link_im_codeblock),
|
||||
("anfuehrung | effort in Anführungszeichen", g_effort_anfuehrung),
|
||||
("zusatzspalte | Spalte mit Backtick-Wert hinter Effort", g_zusatzspalte),
|
||||
("zweiter-backtick | Effort-Zelle mit zweitem Backtick-Wert", g_zweiter_backtick),
|
||||
]
|
||||
|
||||
|
||||
@@ -203,6 +356,14 @@ FAELLE = [
|
||||
("umgebung | CLAUDE.md fehlt", s_kein_stamm, "fehlt — nichts prüfbar"),
|
||||
("umgebung | AI/Regelwerk fehlt", s_kein_regelwerk, "Verzeichnis fehlt"),
|
||||
("umgebung | Rollentabelle unauffindbar", s_keine_rollentabelle, "Rollentabelle nicht auffindbar"),
|
||||
("wertebereich| effort: xhigh statt high", s_effort_wertebereich_xhigh, "effort: xhigh — Rollentabelle im Stamm sagt high"),
|
||||
("wertebereich| effort: hoch (ungültig)", s_effort_wertebereich_ungueltig, "ist nicht in der Vorgabe"),
|
||||
("fehlt | effort fehlt, ERSTE Agentendatei", s_effort_fehlt_erste, ".claude/agents/executer.md — kein effort: im Frontmatter"),
|
||||
("fehlt | effort fehlt, LETZTE Agentendatei", s_effort_fehlt_letzte, ".claude/agents/recherche.md — kein effort: im Frontmatter"),
|
||||
("null | effort ohne Wert", s_effort_null, ".claude/agents/planer.md — kein effort: im Frontmatter"),
|
||||
("umgebung | Effort-Spalte fehlt", s_effort_spalte_fehlt, "Effort-Spalte der Rollentabelle nicht auffindbar"),
|
||||
("verweis | Effort-Zelle ohne Wert", s_effort_verweis_leer, "Rollentabelle nennt für 'pruefer' keinen Effort"),
|
||||
("freigabe | status: pending-review", s_effort_freigabe_pending, "Freigabe steht aus"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user