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

@@ -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