regelwerk_check: Pruefung 7 meldet unsichtbare Zeichen in gespeicherten Workflows

Jedes Zeichen der Kategorie Cc ausser LF und jedes der Kategorie Cf in
.claude/workflows/**/*.js, gelesen als Bytes. Anlass: plankritik.js mit
CRLF scheiterte am Workflow-Werkzeug. Der Hook nach dem Bearbeiten loest
auch bei Workflows aus. Regressionslauf 28/28 angeschlagen, 6/6 stumm;
Hook-Treiber 58/58; Vorfall in exakter Form gemeldet (U+000D).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5.5 committed 2026-09-26 20:54:51 +02:00
1 parent 0ed7d58e41
commit 3b7128c9e1
7 files changed
+184 -13

No files matched your search

+22 -2
View File
@@ -48,11 +48,25 @@ Benannte Lücken, bewusst nicht geprüft:
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.
## Nachtrag 2026-09-26: Prüfung 7, unsichtbare Zeichen in gespeicherten Workflows
Geschrieben vor der Änderung am Skript. Anlass: Am 2026-09-25 und 26 lehnte das Workflow-Werkzeug `plankritik.js` ab („script contains control characters that would be hidden in the approval dialog“). Die Datei trug CRLF im Arbeitsbaum. Pascal hat die Prüfung am 2026-09-26 als Punkt 4 des Pakets „Arbeitsweise“ freigegeben. Plan: `docs/plans/2026-09-26-workflow-steuerzeichen/` in diesem Ordner.
| # | Frage | Herkunft | Auflösung |
|---|---|---|---|
| 7a | Welche Zeichen weist das Werkzeug ab? | **Quelle** nur für CR (der Vorfall). Die Zeichenmenge steht im Abbild von Claude Code 2.1.281 nicht eindeutig neben der Meldung (am Abbild gelesen 2026-09-26). | Der Rest ist unbelegt und wird nicht geraten. |
| 7b | Welche Zeichen meldet die Prüfung? | **Projektvorgabe** | Jedes Zeichen der Unicode-Kategorie Cc außer LF und jedes der Kategorie Cf. Das ist eine geschlossene Menge und eine Obermenge dessen, was belegt ist. Tab wird gemeldet, obwohl unbelegt ist, ob das Werkzeug ihn abweist. |
| 7c | Welche Dateien? | **Projektvorgabe** | Jede `*.js` unter `.claude/workflows/`, auch in Unterordnern. |
| 7d | Wie gelesen? | **Quelle** — Erinnerung `gespeicherter-workflow-per-name-scheitert` | Als Bytes. `read_text` übersetzt CRLF still und war beim Vorfall blind. |
| 7e | Kein UTF-8, Ordner fehlt oder ist leer | **Projektvorgabe** — `pruefwerkzeuge.md`, Abschnitte 4 und 6 | Kein UTF-8 ist ein Befund. Fehlt der Ordner oder ist er leer, gibt es nichts zu prüfen, und Schweigen ist richtig. |
Der Hook `session-hooks/regelwerk_nach_edit.py` löst seitdem auch bei Änderungen unter `.claude/workflows/` aus.
## 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.
Alle sieben 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, die Menge der Zeichen zweier Unicode-Kategorien. 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.
Die Begründung gilt **nur** für diese sechs Achsen. Sie gilt ausdrücklich nicht für die Frage, ob zwei Regeln inhaltlich dasselbe sagen. Das sieht die Prüfung nicht und kann es nicht sehen.
Die Begründung gilt **nur** für diese sieben Achsen. Sie gilt ausdrücklich nicht für die Frage, ob zwei Regeln inhaltlich dasselbe sagen. Das sieht die Prüfung nicht und kann es nicht sehen.
## Angriffsliste — einmal vollständig
@@ -83,6 +97,12 @@ Je Fall eine Wegwerfkopie des Vaults, Rücksetzen dazwischen, Exit-Code und ausl
| Umgebung | `--vault` zeigt auf ein Verzeichnis ohne CLAUDE.md |
| Umgebung | `AI/Regelwerk/` fehlt vollständig |
| Umgebung | Rollentabelle im Stamm nicht auffindbar |
| Steuerzeichen, **Reihenfolge** | CRLF in der ersten Workflowdatei und, als eigener Fall, ein CR in der letzten Zeile der letzten |
| Steuerzeichen | NUL, Bidi-Umkehr U+202E, BOM am Dateianfang, Tab, je ein Fall |
| Kodierung | Workflowdatei ist kein UTF-8 |
| zusätzlich | Workflowdatei mit CR in einem Unterordner |
| Gegenfall (muss stumm bleiben) | typografische Anführungszeichen, Striche, Auslassungspunkte, Umlaut und Euro im Workflow |
| Gegenfall (muss stumm bleiben) | Workflow-Ordner fehlt |
Die letzten drei sind die wichtigsten: Sie prüfen, dass eine fehlende Voraussetzung ein **Befund** ist und kein Grund zu schweigen (§6).
@@ -8,13 +8,14 @@ Entwurf und Angriffsliste: .vault-tools/regelwerk-check/ENTWURF.md
GELTUNGSBEREICH — was diese Prüfung sieht
===============================================================================
Geprüft werden genau vier Orte:
Geprüft werden genau fünf Orte:
CLAUDE.md der Stamm
AI/Regelwerk/*.md die ausgelagerten Regeln
.claude/skills/*/SKILL.md die Skills
.claude/agents/*.md die Agentendefinitionen
.claude/workflows/**/*.js die gespeicherten Workflows, nur für Prüfung 7
Sechs Prüfungen, jede über eine GESCHLOSSENE Menge:
Sieben Prüfungen, jede über eine GESCHLOSSENE Menge:
1 Wikilinks Zeigt jeder [[Verweis]] auf eine existierende Datei?
2 Frontmatter Trägt jede Regelwerk-Datei die sieben Pflichtfelder?
@@ -22,6 +23,7 @@ Sechs Prüfungen, jede über eine GESCHLOSSENE Menge:
4 Freigabe Steht eine Regelwerk-Datei auf status: pending-review?
5 Modell/Effort Weichen model: oder effort: einer Agentendatei von der Rollentabelle ab?
6 Planablage Nennt eine Datei die Dateiform ohne die Ordnerform?
7 Steuerzeichen Trägt ein gespeicherter Workflow ein unsichtbares Zeichen?
Die Rollentabelle und die Liste der Regelwerk-Verweise werden AUS CLAUDE.md
GELESEN, nicht hier nachgebaut. Damit kann die Prüfung nicht gegen eine
@@ -67,11 +69,26 @@ WAS DIESE PRÜFUNG NICHT SIEHT — benannte Lücken
den Effort mit agentType absichtlich nicht; eine Abweichung dort sieht
diese Prüfung nicht.
* Prüfung 7 meldet eine OBERMENGE dessen, was das Workflow-Werkzeug
abweist. Belegt ist nur CR: plankritik.js mit CRLF scheiterte am
2026-09-25 und 26 mit "script contains control characters that would be
hidden in the approval dialog", mit LF lief sie. Die genaue Zeichenmenge
des Werkzeugs steht im Programmabbild nicht eindeutig neben der Meldung
(Claude Code 2.1.281, am Abbild gelesen 2026-09-26). Projektvorgabe ist
deshalb: jedes Zeichen der Unicode-Kategorie Cc außer LF und jedes der
Kategorie Cf. Darunter fallen CR, Tab, NUL, die Bidi-Steuerzeichen und
die BOM. Ob das Werkzeug einen Tab abweist, ist unbelegt; gemeldet wird
er trotzdem, eingerückt wird mit Leerzeichen.
* Prüfung 7 liest nur .claude/workflows/. Die Kopie, die das Werkzeug je
Lauf im Sitzungsordner ablegt, und ein Skript, das per scriptPath von
anderswo kommt, sieht sie nicht.
===============================================================================
DIESES SKRIPT IST EIN FANGNETZ, KEIN NACHWEIS.
===============================================================================
Ein grüner Lauf belegt, dass keine der sechs formalen Achsen verletzt ist.
Ein grüner Lauf belegt, dass keine der sieben formalen Achsen verletzt ist.
Er belegt NICHT, dass die Anweisungsschicht widerspruchsfrei ist. Der
belastbare Nachweis ist das Lesen. Diese Prüfung nimmt nur die Formalien aus
dem Weg, damit beim Lesen Kontext für den Inhalt bleibt.
@@ -88,12 +105,16 @@ from __future__ import annotations
import argparse
import re
import sys
import unicodedata
from pathlib import Path
PFLICHTFELDER = ("type", "status", "title", "summary", "tags", "created", "updated")
REGELWERK = "AI/Regelwerk"
AGENTEN = ".claude/agents"
SKILLS = ".claude/skills"
WORKFLOWS = ".claude/workflows"
# Die Zahl der Prüfungen steht an dieser einen Stelle; main() liest sie hier.
PRUEFUNGEN = 7
WIKILINK = re.compile(r"\[\[([^\]]+)\]\]")
@@ -423,6 +444,49 @@ def pruefe_planablage(vault: Path, dateien: list[Path], befunde: list[Befund]) -
)
def pruefe_workflow_zeichen(vault: Path, befunde: list[Befund]) -> None:
"""Prüfung 7: unsichtbare Zeichen in den gespeicherten Workflows.
Gemeldet wird je Datei einmal, mit Anzahl und erster Fundstelle, jedes Zeichen der
Unicode-Kategorie Cc außer LF und jedes der Kategorie Cf. Gelesen wird als Bytes:
Path.read_text übersetzt CRLF still in LF und wäre genau für den Fall blind, der am
2026-09-25 plankritik.js lahmgelegt hat. Fehlt der Ordner, gibt es nichts zu prüfen,
und Schweigen ist richtig.
"""
ordner = vault / WORKFLOWS
if not ordner.is_dir():
return
for datei in sorted(ordner.rglob("*.js")):
if not datei.is_file():
continue
rel = datei.relative_to(vault).as_posix()
try:
text = datei.read_bytes().decode("utf-8")
except UnicodeDecodeError as fehler:
befunde.append(Befund("blockierend", rel, f"nicht als UTF-8 lesbar (ab Byte {fehler.start})"))
continue
funde = [
stelle
for stelle, zeichen in enumerate(text)
if (zeichen != "\n" and unicodedata.category(zeichen) == "Cc")
or unicodedata.category(zeichen) == "Cf"
]
if not funde:
continue
erstes = funde[0]
zeile = text.count("\n", 0, erstes) + 1
spalte = erstes - (text.rfind("\n", 0, erstes) + 1) + 1
befunde.append(
Befund(
"blockierend",
rel,
f"{len(funde)} unsichtbare(s) Zeichen, das erste in Zeile {zeile}, Spalte {spalte}: "
f"U+{ord(text[erstes]):04X} (Kategorie {unicodedata.category(text[erstes])}); "
"das Workflow-Werkzeug weist so eine Datei ab, also auf LF stellen und das Zeichen entfernen",
)
)
# --------------------------------------------------------------------- Lauf
def main() -> int:
@@ -479,6 +543,7 @@ def main() -> int:
if tabelle and (vault / AGENTEN).is_dir():
pruefe_modelle(vault, tabelle, effort_index, effort_tabelle, befunde); gelaufen += 1
pruefe_planablage(vault, dateien, befunde); gelaufen += 1
pruefe_workflow_zeichen(vault, befunde); gelaufen += 1
except SystemExit:
pass
@@ -495,11 +560,11 @@ def main() -> int:
print()
if befunde:
# Die Zahlen werden gerechnet, nicht geschrieben.
print(f"{len(befunde)} Befund(e) aus {gelaufen} von 6 Prüfungen.")
print(f"{len(befunde)} Befund(e) aus {gelaufen} von {PRUEFUNGEN} Prüfungen.")
else:
print(f"Keine der {gelaufen} gelaufenen Prüfungen hat angeschlagen.")
if gelaufen < 6 and not abbruch:
print(f"ACHTUNG: nur {gelaufen} von 6 Prüfungen sind gelaufen — der Rest hatte keine Voraussetzung.")
if gelaufen < PRUEFUNGEN and not abbruch:
print(f"ACHTUNG: nur {gelaufen} von {PRUEFUNGEN} Prüfungen sind gelaufen — der Rest hatte keine Voraussetzung.")
if abbruch:
return 2
@@ -66,6 +66,11 @@ def baue_grundlinie(ziel: Path) -> None:
unter = ziel / ".claude" / "skills" / skill.parent.name
unter.mkdir(parents=True, exist_ok=True)
shutil.copy2(skill, unter / "SKILL.md")
# Prüfung 7 liest die gespeicherten Workflows. copy2 kopiert Bytes, die Grundlinie
# trägt also dieselben Zeilenenden wie der Vault.
(ziel / ".claude" / "workflows").mkdir(parents=True)
for f in (VAULT / ".claude" / "workflows").glob("*.js"):
shutil.copy2(f, ziel / ".claude" / "workflows" / f.name)
# jede Zeile status: pending-review auf status: reference normalisieren.
# Ohne das bricht der Lauf nach jeder Selbstpflege an der Grundlinie ab,
@@ -332,6 +337,71 @@ def g_link_im_codeblock(v: Path) -> None:
f.write_text(t + "\n".join(zeilen), encoding="utf-8")
# Steuerzeichen entstehen nur ueber Codepunkte, nie als Escape-Folge im Quelltext: Die
# Schreibschicht hat solche Folgen schon still in echte Zeichen verwandelt
# (codeworkflow.md, "Escape-Folgen und unsichtbare Zeichen").
CR = bytes([13])
LF = bytes([10])
def _workflows(v: Path) -> list[Path]:
dateien = sorted((v / ".claude" / "workflows").glob("*.js"))
assert len(dateien) >= 2, "die Grundlinie braucht mindestens zwei gespeicherte Workflows"
return dateien
def _haenge_an(f: Path, zusatz: bytes) -> None:
f.write_bytes(f.read_bytes() + zusatz)
def s_workflow_crlf_erste_datei(v: Path) -> None:
f = _workflows(v)[0]
f.write_bytes(f.read_bytes().replace(LF, CR + LF))
def s_workflow_cr_letzte_datei_letzte_zeile(v: Path) -> None:
f = _workflows(v)[-1]
f.write_bytes(f.read_bytes().rstrip(LF) + CR + LF)
def s_workflow_nul(v: Path) -> None:
_haenge_an(_workflows(v)[0], b"// " + bytes([0]) + LF)
def s_workflow_bidi(v: Path) -> None:
_haenge_an(_workflows(v)[0], ("// " + chr(0x202E) + "x").encode("utf-8") + LF)
def s_workflow_bom(v: Path) -> None:
f = _workflows(v)[-1]
f.write_bytes(chr(0xFEFF).encode("utf-8") + f.read_bytes())
def s_workflow_tab(v: Path) -> None:
_haenge_an(_workflows(v)[0], b"//" + bytes([9]) + b"x" + LF)
def s_workflow_kein_utf8(v: Path) -> None:
_haenge_an(_workflows(v)[0], b"// " + bytes([0xFF]) + LF)
def s_workflow_unterordner(v: Path) -> None:
unter = v / ".claude" / "workflows" / "unter"
unter.mkdir()
(unter / "tief.js").write_bytes(b"a" + CR + LF)
def g_workflow_typografie(v: Path) -> None:
"""Kein Schaden: typografische Zeichen und Umlaute sind keine Steuerzeichen."""
zeichen = "".join(chr(c) for c in (0x201E, 0x201C, 0x2013, 0x2014, 0x2026, 0xE4, 0x20AC))
_haenge_an(_workflows(v)[0], ("// " + zeichen).encode("utf-8") + LF)
def g_workflow_ordner_fehlt(v: Path) -> None:
"""Kein Schaden: ohne Ordner gibt es nichts zu pruefen."""
shutil.rmtree(v / ".claude" / "workflows")
# Gegenfaelle: duerfen NICHT anschlagen. Ohne sie waere die Codeblock-
# Erkennung selbst ungeprueft — neuer Code in einer Fix-Runde bekommt
# seinen eigenen Fall (pruefwerkzeuge.md, Abschnitt 5).
@@ -340,6 +410,8 @@ GEGENFAELLE = [
("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),
("typografie | Anführung, Striche, Umlaut im Workflow", g_workflow_typografie),
("ordner-fehlt | kein Workflow-Ordner", g_workflow_ordner_fehlt),
]
@@ -364,6 +436,14 @@ FAELLE = [
("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"),
("zeichen | CRLF in der ERSTEN Workflowdatei", s_workflow_crlf_erste_datei, "U+000D"),
("zeichen | CR in der LETZTEN Zeile der LETZTEN Workflowdatei", s_workflow_cr_letzte_datei_letzte_zeile, "U+000D"),
("zeichen | NUL", s_workflow_nul, "U+0000"),
("zeichen | Bidi-Umkehr", s_workflow_bidi, "U+202E"),
("zeichen | BOM am Dateianfang", s_workflow_bom, "Zeile 1, Spalte 1: U+FEFF"),
("zeichen | Tab", s_workflow_tab, "U+0009"),
("kodierung | Workflow ist kein UTF-8", s_workflow_kein_utf8, "nicht als UTF-8 lesbar"),
("zusaetzlich| CR in einem Unterordner", s_workflow_unterordner, ".claude/workflows/unter/tief.js"),
]
+1 -1
View File
@@ -36,7 +36,7 @@ Eingabe auf stdin, Ausgabeform, Bedeutung der Exit-Codes und Matcher stammen aus
| # | Frage | Antwort | Herkunft |
|---|---|---|---|
| 1 | Wann läuft der Check? | Wenn `tool_input.file_path` innerhalb der Wurzel auf `CLAUDE.md`, `AI/Regelwerk/**`, `.claude/agents/**` oder `.claude/skills/**` zeigt | **Projektvorgabe** — `CLAUDE.md`, Abschnitt Selbstpflege: nach jeder Änderung an Stamm, Regelwerk, Skills oder Agenten |
| 1 | Wann läuft der Check? | Wenn `tool_input.file_path` innerhalb der Wurzel auf `CLAUDE.md`, `AI/Regelwerk/**`, `.claude/agents/**`, `.claude/skills/**` oder, seit 2026-09-26, `.claude/workflows/**` zeigt | **Projektvorgabe** — `CLAUDE.md`, Abschnitt Selbstpflege: nach jeder Änderung an Stamm, Regelwerk, Skills, Agenten oder gespeicherten Workflows |
| 2 | Wie wird der Pfad verglichen? | Backslash zu Schrägstrich, relativ zur Wurzel aufgelöst, `.` und `..` aufgelöst, alles klein geschrieben, Vergleich mit Trenner hinter der Wurzel | **Projektvorgabe** — Windows-Dateisystem ohne Rücksicht auf Groß- und Kleinschreibung; Symlinks bleiben unaufgelöst (Lücke) |
| 3 | Woher die Wurzel? | Wie bei `sitzungsstart.py`, Frage 4: ausschließlich der Ordner zwei Ebenen über dem Skript, `cwd` zählt nicht | **Projektvorgabe (Koordinator, Nachbesserung Runde 2)** — auf diesen Hook übertragen |
| 4 | Welche Eingaben bleiben still? | Leeres stdin, kein JSON-Objekt, `tool_input` oder `file_path` fehlt oder hat den falschen Typ, Pfad außerhalb des Geltungsbereichs | **Projektvorgabe** — Planauftrag |
@@ -105,6 +105,7 @@ def baue_vault(v: Path, fake: str | None) -> None:
schreibe(v, "AI/Regelwerk/unter/tief.md", "x\n")
schreibe(v, ".claude/agents/planer.md", "---\nname: planer\n---\n")
schreibe(v, ".claude/skills/demo/SKILL.md", "---\nname: demo\n---\n")
schreibe(v, ".claude/workflows/demo.js", "export const meta = {}\n")
schreibe(v, "Inbox/notiz.md", "x\n")
schreibe(v, "Code/CLAUDE.md", "x\n")
if fake is not None:
@@ -232,6 +233,8 @@ FAELLE = [
lambda v: edit(v, str(v / "AI" / "RegelwerkAlt" / "x.md"))),
still("zusaetzlich | settings.json unter .claude",
lambda v: edit(v, str(v / ".claude" / "settings.json"))),
still("verweis | Ordnername mit gleichem Anfang wie workflows",
lambda v: edit(v, str(v / ".claude" / "workflowsAlt" / "x.js"))),
still("wertebereich| der Regelwerk-Ordner selbst",
lambda v: edit(v, str(v / "AI" / "Regelwerk"))),
still("verweis | .. fuehrt aus dem Regelwerk hinaus",
@@ -256,6 +259,8 @@ FAELLE = [
lambda v: edit(v, str(v / ".claude" / "agents" / "planer.md"))),
meldet("pfad | Skilldatei",
lambda v: edit(v, str(v / ".claude" / "skills" / "demo" / "SKILL.md"))),
meldet("pfad | Workflowdatei",
lambda v: edit(v, str(v / ".claude" / "workflows" / "demo.js"))),
meldet("verweis | neue Datei, die es noch nicht gibt",
lambda v: edit(v, str(v / "AI" / "Regelwerk" / "neu.md"))),
meldet("pfad | relativer Pfad", lambda v: edit(v, "AI/Regelwerk/regel.md")),
@@ -6,7 +6,8 @@ Entwurf: ENTWURF.md im selben Ordner (Entscheidungsliste, Herkunft, Angriffsli
GELTUNGSBEREICH
Ausgelöst wird nur, wenn tool_input.file_path innerhalb der Vault-Wurzel auf
CLAUDE.md, AI/Regelwerk/**, .claude/agents/** oder .claude/skills/** zeigt.
CLAUDE.md, AI/Regelwerk/**, .claude/agents/**, .claude/skills/** oder
.claude/workflows/** zeigt.
Die Wurzel ist ausschließlich der Ordner zwei Ebenen über dieser Datei. Weder
die Umgebungsvariable CLAUDE_PROJECT_DIR noch das Feld cwd der Hook-Eingabe
werden gelesen: cwd folgt einem cd im Bash-Werkzeug und zeigt nach einem
@@ -61,7 +62,7 @@ MAX_ZEICHEN = 4000
# Kleinbuchstaben und Schrägstrich vorwärts, verglichen gegen normiere().
GELTUNG_DATEIEN = ("claude.md",)
GELTUNG_ORDNER = ("ai/regelwerk/", ".claude/agents/", ".claude/skills/")
GELTUNG_ORDNER = ("ai/regelwerk/", ".claude/agents/", ".claude/skills/", ".claude/workflows/")
# Schlusszeile von regelwerk_check.py ohne Befund (dort Zeile 370).
_GRUEN = re.compile(r"^Keine der \d+ gelaufenen Prüfungen hat angeschlagen\.$")
+2 -2
View File
@@ -115,13 +115,13 @@ Von mir direkt angeordnete Änderungen an diesen Dateien brauchen diese Markieru
**Eine Regel steht an genau einer Stelle.** Skills unter `.claude/skills/` und Agenten unter `.claude/agents/` verweisen auf das Regelwerk, statt es zu kopieren. Wer eine Regelwerk-Datei ändert, prüft, ob ein Skill oder ein Agent dieselbe Aussage noch einmal führt — sonst driftet die Kopie und gewinnt, weil sie zuerst geladen wird. **Ausnahme:** Eine Agentendefinition darf Regeln enthalten, die der Agent in seinem isolierten Kontext braucht (so bei `pruefer` und `planer`). Dann ist die Agentendatei der Ort dieser Regel, und das Regelwerk verweist auf sie, statt sie zu wiederholen.
Nach jeder Änderung an Stamm, Regelwerk, Skills oder Agenten läuft die Prüfung automatisch über den PostToolUse-Hook in `.claude/settings.json` und meldet Befunde in den Kontext. Ohne Hook, etwa bei Änderungen per Skript, von Hand:
Nach jeder Änderung an Stamm, Regelwerk, Skills, Agenten oder gespeicherten Workflows läuft die Prüfung automatisch über den PostToolUse-Hook in `.claude/settings.json` und meldet Befunde in den Kontext. Ohne Hook, etwa bei Änderungen per Skript, von Hand:
```bash
python .vault-tools/regelwerk-check/regelwerk_check.py --vault .
```
Sechs formale Prüfungen: tote Wikilinks, Frontmatter-Pflichtfelder, ob jede Regelwerk-Datei vom Stamm aus verlinkt ist, offene `pending-review`-Freigaben, `model:` und `effort:` gegen die Rollentabelle oben, und die Planablage-Konvention. **Das ist ein Fangnetz, kein Nachweis** — inhaltliche Widersprüche sieht es nicht. Geltungsbereich, benannte Lücken und die belegte Angriffsliste: `.vault-tools/regelwerk-check/ENTWURF.md`.
Sieben formale Prüfungen: tote Wikilinks, Frontmatter-Pflichtfelder, ob jede Regelwerk-Datei vom Stamm aus verlinkt ist, offene `pending-review`-Freigaben, `model:` und `effort:` gegen die Rollentabelle oben, die Planablage-Konvention und unsichtbare Steuerzeichen wie CR in den gespeicherten Workflows unter `.claude/workflows/`, an denen das Workflow-Werkzeug scheitert. **Das ist ein Fangnetz, kein Nachweis** — inhaltliche Widersprüche sieht es nicht. Geltungsbereich, benannte Lücken und die belegte Angriffsliste: `.vault-tools/regelwerk-check/ENTWURF.md`.
## Sitzungsprotokoll
Beginne jede Sitzung wie folgt. Die Zahlen zu den Schritten 1 bis 3 liefert der SessionStart-Hook schon in den Kontext; lesen musst du trotzdem selbst.