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

@@ -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"),
]