Regelwerk: Pruefaufwand nach Risiko, Paketgroesse, Drehbuch; Plan fuer Pruefung 7 (Steuerzeichen in Workflows)

Punkte 1 und 2 aus dem Paket Arbeitsweise (Pascals Freigabe 2026-09-26)
in codeworkflow.md; Plan fuer Punkt 4 unter .vault-tools/regelwerk-check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5.5 committed 2026-09-26 20:51:29 +02:00
1 parent 59087fb235
commit 3082ab5b75
4 files changed
+591 -1

No files matched your search

@@ -0,0 +1,51 @@
---
type: plan
title: "Prüfung 7: unsichtbare Zeichen in gespeicherten Workflows"
status: active
summary: "regelwerk_check.py bekommt Prüfung 7, die in den gespeicherten Workflows jedes Zeichen der Kategorien Cc (außer LF) und Cf meldet; der Regressionslauf bekommt Angriffs- und Gegenfälle, der Hook nach dem Bearbeiten löst auch bei Workflows aus."
tags: [plan, vault-tools]
created: 2026-09-26
updated: 2026-09-26
---
# Prüfung 7: unsichtbare Zeichen in gespeicherten Workflows
## Ziel
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 Ursache war CRLF im Arbeitsbaum. Nach diesem Plan findet `regelwerk_check.py` so eine Datei, bevor jemand den Workflow startet. Der PostToolUse-Hook fährt die Prüfung auch nach einer Änderung unter `.claude/workflows/`. Freigegeben hat Pascal das am 2026-09-26 als Punkt 4 des Pakets „Arbeitsweise“.
## Entscheidungsliste
| Frage | Antwort | Herkunft |
|---|---|---|
| Welche Zeichen weist das Werkzeug ab? | Belegt ist nur CR. Die Zeichenmenge steht im Abbild von Claude Code 2.1.281 nicht eindeutig neben der Meldung. | Quelle (Vorfall), Rest unbelegt |
| Welche Zeichen meldet Prüfung 7? | Jedes Zeichen der Unicode-Kategorie Cc außer LF, und jedes der Kategorie Cf. Das ist eine Obermenge: CR, Tab, NUL, Bidi-Steuerzeichen und BOM fallen darunter. | Projektvorgabe (Koordinator) |
| Welche Dateien? | Jede `*.js` unter `.claude/workflows/`, auch in Unterordnern. | Projektvorgabe |
| Wie gelesen? | Als Bytes, dann UTF-8 streng. `read_text` übersetzt CRLF still und wäre blind. | Quelle (Erinnerung `gespeicherter-workflow-per-name-scheitert`) |
| Kein UTF-8? | Befund, kein Absturz. | Projektvorgabe (`pruefwerkzeuge.md`, Abschnitt 4) |
| Ordner fehlt oder ist leer? | Kein Befund: Es gibt nichts zu prüfen. | Projektvorgabe (`pruefwerkzeuge.md`, Abschnitt 6) |
| Schwere? | blockierend, denn der Workflow startet nicht. | Projektvorgabe |
## Globale Vorgaben
- Nur die Zieldateien der Taskdatei. `CLAUDE.md`, beide `ENTWURF.md` und alles unter `AI/` ändert der Koordinator selbst.
- Codeblöcke wörtlich mit `python D:/Claude/.vault-tools/plan-kopie/plan_materialisiere.py <planordner> D:/Claude <taskdatei>` anwenden. Nichts von Hand nachschreiben.
- Ein Widerspruch im Plan wird gemeldet, nicht still aufgelöst.
- Kein Commit, kein `git add`.
## Tasks
| Task | Inhalt | Modell |
|---|---|---|
| `task-01-pruefung-7.md` | `regelwerk_check.py` und `regressionslauf.py` | haiku |
| `task-02-hook.md` | `regelwerk_nach_edit.py` und `faelle_nach_edit.py` | haiku |
Die beiden Tasks haben disjunkte Dateien und laufen in einer Welle.
## Fertig heißt
1. `python .vault-tools/regelwerk-check/regressionslauf.py` endet mit Exit 0, alle Angriffsfälle angeschlagen, alle Gegenfälle stumm. Die neuen Fälle stehen einzeln in der Ausgabe.
2. `python .vault-tools/regelwerk-check/regelwerk_check.py --vault .` auf dem echten Vault: Exit 0 und die Schlusszeile nennt 7 gelaufene Prüfungen.
3. Eine Kopie von `plankritik.js` mit CRLF in einem Wegwerf-Vault ergibt einen Befund mit `U+000D`. Das ist der Vorfall in seiner exakten Form.
4. `python .vault-tools/session-hooks/faelle_nach_edit.py` endet mit Exit 0, und der Fall „Workflowdatei“ steht als angeschlagen in der Ausgabe.
5. Die geänderten Dateien sind byte-gleich mit der Kopie, auf die der Koordinator den Plan vorher angewendet hat.
@@ -0,0 +1,411 @@
---
type: plan-task
title: "Task 01 — Prüfung 7 in regelwerk_check.py und ihre Fälle im Regressionslauf"
status: draft
summary: "Neue Prüfung auf unsichtbare Zeichen in gespeicherten Workflows, dazu acht Angriffs- und zwei Gegenfälle."
tags: [plan-task, vault-tools]
created: 2026-09-26
updated: 2026-09-26
modell: haiku
modell_begruendung: "Alle Blöcke stehen wörtlich im Plan und werden per Skript angewendet; Steuerzeichen entstehen nur über Codepunkte, keine Escape-Folge muss von Hand geschrieben werden."
---
# Task 01 — Prüfung 7 in `regelwerk_check.py` und ihre Fälle
Globale Vorgaben: `00-uebersicht.md` im selben Ordner, Abschnitt „Globale Vorgaben“.
## Zieldateien
- `D:\Claude\.vault-tools\regelwerk-check\regelwerk_check.py` (geändert)
- `D:\Claude\.vault-tools\regelwerk-check\regressionslauf.py` (geändert)
## Schritt 1 — Blöcke anwenden
```
python D:/Claude/.vault-tools/plan-kopie/plan_materialisiere.py D:/Claude/.vault-tools/regelwerk-check/docs/plans/2026-09-26-workflow-steuerzeichen D:/Claude task-01-pruefung-7.md
```
Erwartet: `0 Befund(e).` und `'ersetzt': 13`.
### Geltungsbereich im Kopf von `.vault-tools/regelwerk-check/regelwerk_check.py`
Aus:
```
Geprüft werden genau vier Orte:
CLAUDE.md der Stamm
AI/Regelwerk/*.md die ausgelagerten Regeln
.claude/skills/*/SKILL.md die Skills
.claude/agents/*.md die Agentendefinitionen
Sechs Prüfungen, jede über eine GESCHLOSSENE Menge:
```
wird:
```
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
Sieben Prüfungen, jede über eine GESCHLOSSENE Menge:
```
### Prüfliste im Kopf von `.vault-tools/regelwerk-check/regelwerk_check.py`
Aus:
```
6 Planablage Nennt eine Datei die Dateiform ohne die Ordnerform?
```
wird:
```
6 Planablage Nennt eine Datei die Dateiform ohne die Ordnerform?
7 Steuerzeichen Trägt ein gespeicherter Workflow ein unsichtbares Zeichen?
```
### Benannte Lücken in `.vault-tools/regelwerk-check/regelwerk_check.py`
Aus:
```
* effort in gespeicherten Workflows unter .claude/workflows/. Sie setzen
den Effort mit agentType absichtlich nicht; eine Abweichung dort sieht
diese Prüfung nicht.
```
wird:
```
* effort in gespeicherten Workflows unter .claude/workflows/. Sie setzen
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.
```
### Achsenzahl in `.vault-tools/regelwerk-check/regelwerk_check.py`
Aus:
```
Ein grüner Lauf belegt, dass keine der sechs formalen Achsen verletzt ist.
```
wird:
```
Ein grüner Lauf belegt, dass keine der sieben formalen Achsen verletzt ist.
```
### Importe in `.vault-tools/regelwerk-check/regelwerk_check.py`
Aus:
```
import argparse
import re
import sys
from pathlib import Path
```
wird:
```
import argparse
import re
import sys
import unicodedata
from pathlib import Path
```
### Konstanten in `.vault-tools/regelwerk-check/regelwerk_check.py`
Aus:
```
SKILLS = ".claude/skills"
```
wird:
```
SKILLS = ".claude/skills"
WORKFLOWS = ".claude/workflows"
# Die Zahl der Prüfungen steht an dieser einen Stelle; main() liest sie hier.
PRUEFUNGEN = 7
```
### Neue Prüfung in `.vault-tools/regelwerk-check/regelwerk_check.py`
Aus:
```
# --------------------------------------------------------------------- Lauf
```
wird:
```
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
```
### Aufruf in `.vault-tools/regelwerk-check/regelwerk_check.py`
Aus:
```
pruefe_planablage(vault, dateien, befunde); gelaufen += 1
```
wird:
```
pruefe_planablage(vault, dateien, befunde); gelaufen += 1
pruefe_workflow_zeichen(vault, befunde); gelaufen += 1
```
### Schlusszeilen in `.vault-tools/regelwerk-check/regelwerk_check.py`
Aus:
```
print(f"{len(befunde)} Befund(e) aus {gelaufen} von 6 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.")
```
wird:
```
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 < PRUEFUNGEN and not abbruch:
print(f"ACHTUNG: nur {gelaufen} von {PRUEFUNGEN} Prüfungen sind gelaufen — der Rest hatte keine Voraussetzung.")
```
### Grundlinie in `.vault-tools/regelwerk-check/regressionslauf.py`
Aus:
```
for skill in (VAULT / ".claude" / "skills").glob("*/SKILL.md"):
unter = ziel / ".claude" / "skills" / skill.parent.name
unter.mkdir(parents=True, exist_ok=True)
shutil.copy2(skill, unter / "SKILL.md")
```
wird:
```
for skill in (VAULT / ".claude" / "skills").glob("*/SKILL.md"):
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)
```
### Beschädigungen für Prüfung 7 in `.vault-tools/regelwerk-check/regressionslauf.py`
Aus:
```
# Gegenfaelle: duerfen NICHT anschlagen. Ohne sie waere die Codeblock-
```
wird:
```
# 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-
```
### Gegenfälle in `.vault-tools/regelwerk-check/regressionslauf.py`
Aus:
```
("zweiter-backtick | Effort-Zelle mit zweitem Backtick-Wert", g_zweiter_backtick),
]
```
wird:
```
("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),
]
```
### Angriffsfälle in `.vault-tools/regelwerk-check/regressionslauf.py`
Aus:
```
("freigabe | status: pending-review", s_effort_freigabe_pending, "Freigabe steht aus"),
]
```
wird:
```
("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"),
]
```
## Schritt 2 — Verifikation
```
python D:/Claude/.vault-tools/regelwerk-check/regressionslauf.py
```
Erwartet: Exit 0, die Grundlinie grün, jede Zeile unter ANGRIFFSLISTE beginnt mit `ANGESCHLAGEN`, jede unter GEGENFÄLLE mit `STUMM`.
```
python D:/Claude/.vault-tools/regelwerk-check/regelwerk_check.py --vault D:/Claude
```
Erwartet: Exit 0 und `Keine der 7 gelaufenen Prüfungen hat angeschlagen.`
Melde beide Ausgaben vollständig. Läuft etwas anders: nichts anpassen, melden.
@@ -0,0 +1,118 @@
---
type: plan-task
title: "Task 02 — Der Hook nach dem Bearbeiten löst auch bei Workflows aus"
status: draft
summary: "regelwerk_nach_edit.py nimmt den Workflow-Ordner in den Geltungsbereich; der Treiber bekommt einen Fall, der anschlägt, und einen mit ähnlichem Ordnernamen, der still bleibt."
tags: [plan-task, vault-tools]
created: 2026-09-26
updated: 2026-09-26
modell: haiku
modell_begruendung: "Vier kurze Blöcke, wörtlich im Plan, per Skript angewendet."
---
# Task 02 — Der Hook löst auch bei Workflows aus
Globale Vorgaben: `00-uebersicht.md` im selben Ordner, Abschnitt „Globale Vorgaben“.
## Zieldateien
- `D:\Claude\.vault-tools\session-hooks\regelwerk_nach_edit.py` (geändert)
- `D:\Claude\.vault-tools\session-hooks\faelle_nach_edit.py` (geändert)
## Schritt 1 — Blöcke anwenden
```
python D:/Claude/.vault-tools/plan-kopie/plan_materialisiere.py D:/Claude/.vault-tools/regelwerk-check/docs/plans/2026-09-26-workflow-steuerzeichen D:/Claude task-02-hook.md
```
Erwartet: `0 Befund(e).` und `'ersetzt': 5`.
### Geltungsbereich im Kopf von `.vault-tools/session-hooks/regelwerk_nach_edit.py`
Aus:
```
CLAUDE.md, AI/Regelwerk/**, .claude/agents/** oder .claude/skills/** zeigt.
```
wird:
```
CLAUDE.md, AI/Regelwerk/**, .claude/agents/**, .claude/skills/** oder
.claude/workflows/** zeigt.
```
### Ordnerliste in `.vault-tools/session-hooks/regelwerk_nach_edit.py`
Aus:
```
GELTUNG_ORDNER = ("ai/regelwerk/", ".claude/agents/", ".claude/skills/")
```
wird:
```
GELTUNG_ORDNER = ("ai/regelwerk/", ".claude/agents/", ".claude/skills/", ".claude/workflows/")
```
### Wegwerf-Vault in `.vault-tools/session-hooks/faelle_nach_edit.py`
Aus:
```
schreibe(v, ".claude/skills/demo/SKILL.md", "---\nname: demo\n---\n")
```
wird:
```
schreibe(v, ".claude/skills/demo/SKILL.md", "---\nname: demo\n---\n")
schreibe(v, ".claude/workflows/demo.js", "export const meta = {}\n")
```
### Fall, der still bleibt, in `.vault-tools/session-hooks/faelle_nach_edit.py`
Aus:
```
still("zusaetzlich | settings.json unter .claude",
lambda v: edit(v, str(v / ".claude" / "settings.json"))),
```
wird:
```
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"))),
```
### Fall, der meldet, in `.vault-tools/session-hooks/faelle_nach_edit.py`
Aus:
```
meldet("pfad | Skilldatei",
lambda v: edit(v, str(v / ".claude" / "skills" / "demo" / "SKILL.md"))),
```
wird:
```
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"))),
```
## Schritt 2 — Verifikation
```
python D:/Claude/.vault-tools/session-hooks/faelle_nach_edit.py
```
Erwartet: Exit 0; die Fälle „Workflowdatei“ und „Ordnername mit gleichem Anfang wie workflows“ stehen in der Ausgabe als bestanden.
Melde die Ausgabe vollständig. Läuft etwas anders: nichts anpassen, melden.