Files
claude-regelwerk/.vault-tools/session-hooks/ENTWURF.md
T
Pascal MichelandClaude Opus 5 2c9a4475fc Regelwerk, Agenten, Skills und Werkzeuge veröffentlichen
Erster Stand des öffentlichen Ausschnitts aus dem Vault. Die .gitignore
arbeitet als Positivliste: ausgeschlossen ist alles, eingeschlossen nur,
was ausdrücklich wieder aufgenommen wird. Notizen, Projekte, Finanzen,
das Sitzungsgedächtnis und settings.local.json bleiben damit außen vor.

Der Kundenname in AI/Regelwerk/seo.md ist durch eine neutrale
Bezeichnung ersetzt; die Regeln selbst sind unverändert.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-17 22:38:15 +02:00

17 KiB
Raw Blame History

Entwurf: Sitzungs-Hooks

Angelegt am 2026-09-13 als Entwurfsschritt nach AI/Regelwerk/pruefwerkzeuge, Abschnitt 1. Er gilt für zwei Skripte: sitzungsstart.py (SessionStart) und regelwerk_nach_edit.py (PostToolUse).

Anlass

Das Sitzungsprotokoll in CLAUDE.md verlangt zu Beginn jeder Sitzung dieselben Handgriffe: Tagesnotiz suchen, Inbox durchsehen, needs-review suchen. Nach jeder Änderung an Stamm, Regelwerk, Skills oder Agenten soll außerdem regelwerk_check.py laufen. Beides ist Zählen und Starten, kein Urteil.

Für den Sitzungsbeginn ist belegt, dass es schiefgeht: Das Datum kam zweimal aus der falschen Quelle, eine Sitzungsdatei hat eine andere überschrieben, und die Suche nach needs-review traf Erwähnungen in Backticks. Für den Regelwerk-Check liegt kein solcher Fall vor. Der Hook soll ihn starten, ohne dass jemand daran denken muss.

Woher die Hook-Schnittstelle kommt

Eingabe auf stdin, Ausgabeform, Bedeutung der Exit-Codes und Matcher stammen aus der Hook-Doku (code.claude.com/docs/en/hooks). Der Koordinator hat sie am 2026-09-13 gegen Claude Code 2.1.263 gemessen und im Planauftrag festgehalten. Beim Planen wurde die Doku nicht noch einmal gezogen. Diese Herkunft steht deshalb unten als Quelle (Koordinator) und nicht als eigene Quelle.

Entscheidungsliste sitzungsstart.py

# Frage Antwort Herkunft
1 Welche Punkte meldet der Hook? Datum, Tagesnotiz, Inbox ohne type: daily, needs-review, review ohne review-done, pending-review im Regelwerk, heutige Sitzungsdateien Projektvorgabe — CLAUDE.md, Abschnitt Sitzungsprotokoll, und Planauftrag
2 Welches Datum? Maschinenuhr, im Text ausdrücklich so beschriftet Projektvorgabe — das Sitzungsdatum aus dem Kontext hat Vorrang (Harness-Gedächtnis datum-aus-dem-sitzungskontext)
3 Welche Ausgabeform? Exit 0, genau ein JSON-Objekt mit hookSpecificOutput.hookEventName und additionalContext Quelle (Koordinator) — Hook-Doku, SessionStart
4 Woher kommt die Vault-Wurzel? Ausschließlich der Ordner zwei Ebenen über dem Skript. Das Feld cwd der Hook-Eingabe wird nicht mehr gelesen Projektvorgabe (Koordinator, Nachbesserung Runde 2) — Prüferbefund 1: cwd folgt einem cd im Bash-Werkzeug und zeigt in einer laufenden Sitzung nicht zuverlässig auf die Vault-Wurzel (Claude Code 2.1.263). Der Hook ist ohnehin nur für diesen einen Vault eingetragen, siehe „Nachbesserung Runde 2" unten
5 Was ist eine offene Inbox-Notiz? Jede .md unter Inbox/, auch in Unterordnern ohne Punkt, deren Frontmatter nicht type: daily trägt. Ohne Frontmatter zählt sie als offen Projektvorgabe — CLAUDE.md Sitzungsprotokoll Punkt 2; Unterordner als Annahme im Plan benannt
6 Was ist ein Inline-Tag? #name am Zeilenanfang oder nach Leerraum, danach kein Buchstabe, keine Ziffer, kein _, - oder /. Groß- und Kleinschreibung egal Projektvorgabe — die Obsidian-Doku zur Tag-Syntax wurde nicht gezogen; Grenze und Schreibweise sind unsere Festlegung
7 Zählt ein Tag in Code? Nein, weder im Fenced-Codeblock noch im Inline-Code. Ausgeblendet über strip_code() aus .vault-tools/linkcheck/linkcheck.py Projektvorgabe — AI/Regelwerk/frontmatter, Abschnitt Tags: ein nur erwähnter Tag steht in Backticks. Die Codeblock-Regel selbst ist in .vault-tools/linkcheck/ENTWURF-codeblock-erkennung.md belegt
8 Was ist ein Frontmatter-Tag? Ein Eintrag unter tags: als Inline-Liste, Block-Liste oder Skalar, ohne Anführungszeichen, Klammern und führende Raute verglichen Projektvorgabe — frontmatter.md: Tags im Frontmatter ohne #, als Liste
9 Kaputtes YAML? Tolerant: Ein Tag in einer nicht geschlossenen Liste zählt. Ein Frontmatter ohne Schlusszeile ist keins Projektvorgabe — lieber eine Markierung zu viel melden als eine übersehen
10 Welche Ordner fallen aus der Tag-Suche? Import/ und _index/ direkt unter der Wurzel, Punktordner und node_modules in jeder Tiefe Projektvorgabe — Planauftrag; dieselben Ausschlüsse wie im Vernetzungs-Audit vom 2026-09-13
11 Wann zählt review? Wenn die Notiz review trägt (Frontmatter oder Inline) und nirgends review-done. Gleiche Ausschlüsse wie Frage 10 Projektvorgabe — CLAUDE.md Sitzungsprotokoll Punkt 3; die gleichen Ausschlüsse sind als Annahme im Plan benannt
12 Wie viele Namen? Höchstens fünf je Punkt, alphabetisch, danach +N weitere Projektvorgabe — Planauftrag
13 Was passiert bei einem Fehler? Jeder Punkt läuft für sich in try. Ein Fehler wird als Zeile Sitzungsstart-Hook: … gemeldet, die übrigen Punkte laufen weiter. Ein Fehler außerhalb der Punkte behält die gesammelten Zeilen Projektvorgabe — pruefwerkzeuge.md Abschnitt 4
14 Fehlt linkcheck.py? Der Tag-Punkt meldet sich als Fehler und zählt nicht Projektvorgabe — pruefwerkzeuge.md Abschnitt 6: eine fehlende Voraussetzung ist ein Befund
15 Laufzeit? Unter 5 Sekunden über den ganzen Vault angepeilt, gemessen mit faelle_sitzungsstart.py --echt Projektvorgabe — Planauftrag

Entscheidungsliste regelwerk_nach_edit.py

# 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
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
5 Wann gilt der Check als ohne Befund? Exit 0 und die Schlusszeile Keine der N gelaufenen Prüfungen hat angeschlagen. und keine Zeile ACHTUNG Quelle — regelwerk_check.py Zeilen 366–376
6 Was ist ein Befund? Alles andere: Exit ungleich 0, fehlende Schlusszeile, ACHTUNG, fehlendes Skript, Timeout, Startfehler, eigener Absturz Projektvorgabe — pruefwerkzeuge.md Abschnitt 6
7 Welche Ausgabe? Exit 0, ein JSON-Objekt mit hookSpecificOutput (PostToolUse, additionalContext) und einzeiliger systemMessage; ohne Befund keine Ausgabe Quelle (Koordinator, korrigiert nach Prüfrunde 2) — Hilfetext von claude.exe 2.1.263: bei PostToolUse zeigt Exit 2 stderr sofort dem Modell. Ob JSON bei einem Exit ungleich 0 gelesen wird, sagt der Hilfetext nicht; der Prüfer fand im Binary Hinweise, dass es auch dann übernommen wird. Exit 0 plus JSON ist die in Doku und laufenden Plugins belegte Form, deshalb bleibt es dabei
8 Wie lange darf der Check laufen? 20 Sekunden, dann Abbruch und Meldung Projektvorgabe — unter dem timeout von 30 Sekunden, den der settings.json-Eintrag setzen soll
9 Wie viel Text geht in den Kontext? Höchstens 40 Zeilen und 4000 Zeichen, danach ein Kürzungshinweis Projektvorgabe — eine Längengrenze der Doku ist nicht belegt (Lücke)
10 Prüft der Hook tool_name? Nein, das macht der Matcher Edit|Write|MultiEdit Projektvorgabe
11 Mit welchem Interpreter? sys.executable, stdin geschlossen, PYTHONIOENCODING=utf-8 Projektvorgabe — AI/Regelwerk/windows-shell

Keine Zeile steht auf „Annahme". Wo eine Antwort auf einer Annahme beruhte, ist sie zur Projektvorgabe erhoben und im Plan unter „Getroffene Annahmen" benannt.

Angriffslisten

Die einzelnen Fälle stehen an genau einer Stelle, in den Treibern faelle_sitzungsstart.py und faelle_nach_edit.py. Hier steht nur, welche Klassen aus pruefwerkzeuge.md Abschnitt 3 abgedeckt sind, damit eine Lücke auffällt.

Klasse sitzungsstart.py regelwerk_nach_edit.py
fehlt Tagesnotiz, cwd tool_input, file_path, cwd, regelwerk_check.py
null cwd tool_input, file_path, cwd
falscher Typ stdin als Liste, cwd als Zahl oder Datei, tags als Skalar stdin als Liste oder Zahl, tool_input als Liste, file_path als Zahl oder Liste
leer leere Datei, leere Notiz, leeres stdin, leeres cwd, Vault ohne Ordner leeres stdin, leerer file_path
doppelt zwei Sitzungsdateien von heute —
zusätzlich sieben Inbox-Notizen (Namensgrenze), Inbox-Unterordner, seit Runde 2 auch ein Punktordner innerhalb der Inbox (eigener Fall neben dem Punktordner-Ausschluss unter der Vault-Wurzel) Pfad außerhalb, Unterordner-CLAUDE.md, settings.json, Write und MultiEdit
Wertebereich Tagesnotiz von gestern Nullzeichen, Regelwerk-Ordner selbst, sehr lange Ausgabe
Verweis ins Leere cwd existiert nicht, cwd zeigt auf einen anderen Ordner (Ergebnis bleibt der Skript-Vault, Runde 2) cwd existiert nicht, cwd zeigt auf einen anderen Ordner (Runde 2), neue Datei, .. hinaus und hinein, Präfix-Fallen
Reihenfolge Tag an erster und letzter Stelle, review an erster und letzter Stelle — (eine Eingabe hat keine Listenposition)
Grenze #needs-review gegen #needs-reviewer/#needs-review/x/abc#needs-review, Inline-Großschreibung zählt; seit Runde 2 auch #review gegen #reviewer/#review/x/abc#review, #review-done gegen #review-done-x, und ein großgeschriebener Tag im Frontmatter (tags: [Needs-Review]) —
Umgebung Inbox, Regelwerk, sessions, linkcheck.py fehlen; CRLF, BOM, ungültiges UTF-8, kaputtes YAML; seit Runde 2 auch ohne PYTHONIOENCODING mit Nicht-ASCII-Inhalt (Notizinhalt mit Umlauten und Emoji) Check fehlt, hängt, stürzt ab, Exit 2, Exit 0 mit ACHTUNG, Exit 0 ohne Schlusszeile; Backslash, Laufwerksschreibweise, ungültiges UTF-8; seit Runde 2 auch ohne PYTHONIOENCODING mit ungültigem UTF-8 und mit echtem Nicht-ASCII-Pfad
Code Tag im Fenced-, Tilde- und Inline-Code —
Absturz im Skript ein Punkt wirft, Hauptablauf wirft, Datei nicht lesbar, seit Runde 2 auch ein Ordner nicht lesbar (onerror von os.walk) Hook selbst wirft

Beide Treiber prüfen zuerst eine Grundlinie. Beim Regelwerk-Hook besteht sie aus zwei Läufen: Ein grüner Check bleibt still und ist nachweislich gelaufen, ein Befund wird gemeldet. Ohne den zweiten Lauf bestünde ein Hook, der immer schweigt, alle stillen Fälle.

Nachbesserung Runde 2

Anlass. Die erste Prüfrunde gegen beide Hooks fand zwei wichtige und sechs kleine Befunde; der CRLF-Befund zu ENTWURF-codeblock-erkennung.md wurde bewusst nicht behoben. „Runde 2" meint in Kommentaren und Nachträgen die Nachbesserung nach dieser ersten Prüfrunde. Die Fixe stehen hier zusammengefasst, Details in den jeweiligen Kopfkommentaren und in den Angriffslisten oben.

Befund 1 (wichtig): Vault-Wurzel aus cwd. Beide Hooks lasen die Vault-Wurzel zuerst aus cwd der Hook-Eingabe. cwd folgt aber einem cd im Bash-Werkzeug (z. B. in einen Projektordner mit eigenem Git-Repo) und zeigt in einer laufenden Sitzung nicht mehr auf den Vault — sitzungsstart.py hätte dann bei resume/clear/compact einen falschen Stand gemeldet, regelwerk_nach_edit.py wäre bei Änderungen am Regelwerk still geblieben, ohne dass das sichtbar gewesen wäre. Korrektur (Koordinatorentscheidung): Beide Hooks bestimmen ihre Wurzel jetzt ausschließlich aus dem eigenen Skriptpfad (Path(__file__).resolve().parents[2]), das Feld cwd wird nicht mehr gelesen — auch nicht über die zwischenzeitlich erwogene Umgebungsvariable CLAUDE_PROJECT_DIR. Die Hooks sind ohnehin nur für den einen Vault eingetragen, in dem sie liegen. Neuer Fall in beiden Treibern: cwd zeigt auf einen anderen, echten Ordner — das Ergebnis muss trotzdem der Vault sein, in dem der (kopierte) Hook selbst liegt. Weil die Wurzel jetzt am Ort des Skripts hängt, kopieren beide Regressionstreiber den Hook seither für jeden Fall zuerst in den Wegwerf-Vault (kopiere_hook() bzw. der entsprechende Block in lauf_fall()), statt das Original mit einem cwd-Feld auf den Wegwerf-Vault zu verweisen. Dieselbe Kopie war auch für ip_hook_wirft() in faelle_nach_edit.py nötig: ohne sie fand main() keinen Geltungsbereich für den Testpfad, der monkeygepatchte Absturz wurde nie ausgelöst, und der Fall wäre unbemerkt grün geblieben, weil stdout leer war statt eines JSON-Objekts.

Befund 2 (wichtig): Umgebung/Ausgabekodierung fehlte in der Angriffsliste. Claude Code setzt PYTHONIOENCODING beim Hook-Aufruf nicht; nur die globale Planvorgabe setzte sie bisher vor jedem Treiberlauf selbst. Ohne die Variable ist sys.stdout.encoding auf dieser Maschine cp1252. Solange ensure_ascii=True gilt (beide Hooks), bleibt die Ausgabe reines ASCII und das Problem ist unsichtbar — genau deshalb bekam die Klasse eigene Fälle: beide Treiber fahren jetzt mindestens einen Fall mit einer Umgebung ohne PYTHONIOENCODING und mit echtem Nicht-ASCII-Inhalt (Notiztext mit Umlauten und Emoji bzw. ein Dateipfad mit Umlauten und Emoji) und prüfen, dass stdout als UTF-8 dekodierbares, gültiges JSON bleibt. Mutationsbeleg (auf Scratchpad-Kopien, nicht im Vault): ensure_ascii=False gesetzt, derselbe Fall wird in beiden Treibern rot, aber aus unterschiedlichen Gründen (so von der zweiten Prüfrunde nachgemessen): sitzungsstart.py gibt nur Dateinamen aus, das Ü im Namen landet als cp1252-Byte in stdout; regelwerk_nach_edit.py wirft beim Emoji im Pfad einen UnicodeEncodeError, den main() fängt und durch eine vollständige Ersatzmeldung ersetzt. Dass der Umlaut-Fall auch ohne Emoji anschlägt, sichert erst die strenge Dekodierung aus der Nachbesserung nach Prüfrunde 2.

Befunde 3 und 4 (klein): fehlende Grenzfälle. Ergänzt: die Grenzen von #review/#review-done (#reviewer, #review/x, abc#review, #review-done-x), ein großgeschriebener Tag im Frontmatter (tags: [Needs-Review], nicht nur inline), ein Punktordner innerhalb der Inbox (getrennt vom Punktordner-Ausschluss unter der Vault-Wurzel), und der onerror-Pfad von os.walk in durchlaufe() (im Prozess, weil von außen nicht ohne echte Rechteänderung auslösbar).

Befund 5 und 6 (klein, Linkcheck): „gleiches Zaunzeichen" ohne Fall, Tab vor dem Schließzaun. In linkcheck.py/faelle.py behoben, siehe .vault-tools/linkcheck/ENTWURF-codeblock-erkennung.md, Abschnitt „Nachbesserung Runde 2".

Befund 7 (klein): Exit-2-Aussage korrigiert. Frage 7 der Entscheidungsliste zu regelwerk_nach_edit.py behauptete, PostToolUse ignoriere Exit 2. Nach dem Hilfetext von claude.exe 2.1.263 zeigt Exit 2 bei PostToolUse stattdessen stderr sofort dem Modell. Die Umsetzung war davon nicht betroffen (Exit ist immer 0), nur die Begründung in Frage 7 und im Kopfkommentar von regelwerk_nach_edit.py war falsch und ist jetzt korrigiert.

Nachbesserung nach Prüfrunde 2

Die zweite Prüfrunde fand keinen wichtigen Befund an den Hooks mehr, nur eine Aufräumliste. Der Koordinator hat sie ohne weitere Executer-Runde selbst abgearbeitet:

  • Strenge Dekodierung in beiden Treibern. fahre() dekodiert stdout jetzt ohne errors="replace". Ungültiges UTF-8 ergibt eine Meldung, die kein JSON ist, und der Fall wird rot.
  • Zwei Fälle ergänzt in faelle_sitzungsstart.py: abc#review-done zählt nicht als erledigt, eine Punktdatei außerhalb von Punktordnern (Notizen/.versteckt.md) zählt nicht.
  • Linkcheck: Die Schließregel ist wieder symmetrisch zur Öffnerregel (\s{0,3}). Die strengere Fassung ließ einen in einer Liste mit Tab eingerückten Block bis zum Dateiende offen. Details: .vault-tools/linkcheck/ENTWURF-codeblock-erkennung.md.
  • Begründungen korrigiert: Mutationsbeleg zu Befund 2 und Frage 7 (Exit-Codes), siehe oben.

Absturzfestigkeit

Beide Skripte enden in jedem Fall mit Exit 0, weil Claude Code stdout sonst nicht als JSON liest. Jede Ebene der Eingabe wird auf ihren Typ geprüft, bevor darauf zugegriffen wird. Ein unerwarteter Fehler in der Arbeit selbst wird gemeldet. Die letzte Schranke im __main__-Block greift nur noch, wenn das Schreiben der Ausgabe selbst scheitert, etwa an einem geschlossenen stdout. Dann gibt es nichts mehr zu melden.

Abbruchbedingung

Schluss ist, wenn beide Treiber mit grüner Grundlinie und ohne Fehlschlag durchlaufen, der Lauf mit --echt über den echten Vault Ergebnisse liefert, die der Koordinator mit eigener Suche bestätigt hat, und eine Prüfrunde nur noch Umformulierungen findet.

Was diese Skripte nicht sind

Ein Fangnetz. sitzungsstart.py nimmt das Zählen ab, nicht das Lesen der Inbox. regelwerk_nach_edit.py reicht weiter, was regelwerk_check.py sieht, und das sind formale Widersprüche, keine inhaltlichen. Der belastbare Nachweis, dass die Hooks in Claude Code wirken, ist eine echte Sitzung nach dem Eintrag in settings.json, nicht dieser Entwurf.