Files
claude-regelwerk/.vault-tools/session-hooks/docs/plans/2026-09-13-sitzungs-hooks/00-uebersicht.md
T
Pascal MichelandClaude Opus 5 c064c729ed 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

type, title, status, summary, tags, created, updated
type title status summary tags created updated
plan Sitzungs-Hooks und Schließzaun im Linkcheck active Nach dem Plan gibt ein SessionStart-Hook den Stand des Sitzungsprotokolls als Kontext aus, ein PostToolUse-Hook startet den Regelwerk-Check nach Änderungen an der Anweisungsschicht, und der Linkcheck schließt Codeblöcke nach CommonMark.
plan
vault-tools
2026-09-13 2026-09-13

Sitzungs-Hooks und Schließzaun im Linkcheck

Ziel

Nach diesem Plan liegen zwei Hook-Skripte unter .vault-tools/session-hooks/, jedes mit Regressionstreiber. sitzungsstart.py zählt zu Sitzungsbeginn, was das Sitzungsprotokoll in CLAUDE.md verlangt, und gibt es als Kontext aus. regelwerk_nach_edit.py startet regelwerk_check.py, sobald eine Datei von Stamm, Regelwerk, Agenten oder Skills geändert wurde, und meldet Befunde, eine fehlende, abstürzende oder hängende Prüfung. Außerdem schließt strip_code() im Linkcheck einen Codeblock nur noch mit einem passenden Zaun. Damit verschwindet der Fehlalarm [[:space:]] in Code Projekte/active - Logging/docs/plans/2026-09-08-a-betriebsmodelle/task-09-gate.md. Wirksam in Claude Code werden die Hooks erst mit dem Eintrag in settings.json, und der ist nicht Teil dieses Plans.

Nicht im Umfang

  • Einträge in .claude/settings.json oder .claude/settings.local.json. Die macht der Koordinator nach der Prüfung.
  • Jede Änderung außerhalb von .vault-tools/session-hooks/ und .vault-tools/linkcheck/. Auch der Kommentar in regelwerk_check.py, der die Zahl der Linkcheck-Fälle nennt, bleibt stehen.
  • Die Öffnerregel des Linkchecks (Backtick im Info-String). Sie ist im Entwurf als Lücke benannt.
  • CHANGELOG.md: .vault-tools/ führt keins.

Architektur

Claude Code
  SessionStart ──stdin JSON──> sitzungsstart.py ──stdout JSON──> additionalContext
                                   │
                                   └─ importiert strip_code() aus linkcheck/linkcheck.py

  PostToolUse (Edit|Write|MultiEdit)
               ──stdin JSON──> regelwerk_nach_edit.py
                                   │  Pfad im Geltungsbereich?  nein -> still
                                   └─ startet regelwerk-check/regelwerk_check.py --vault <wurzel>
                                        ohne Befund -> still
                                        sonst       -> stdout JSON (additionalContext, systemMessage)

linkcheck/linkcheck.py  strip_code()  <── auch von regelwerk_check.py importiert

Beide Hooks enden immer mit Exit 0. Entscheidungen und Herkunft stehen in .vault-tools/session-hooks/ENTWURF.md (entsteht in Task 1), die Regel zum Schließzaun in .vault-tools/linkcheck/ENTWURF-codeblock-erkennung.md (Task 3).

Globale Vorgaben

Diese Vorgaben gelten für jeden Task. Die Taskdateien verweisen hierher und wiederholen sie nicht.

  1. Abschrift, keine Autorschaft. Code und Texte aus dem Plan werden Zeichen für Zeichen übernommen. Wer einen Widerspruch im Plan findet, meldet ihn und löst ihn nicht still auf. Kommentare und Docstrings werden nicht umformuliert.
  2. Test zuerst. Erst den Treiber oder die Fälle anlegen, den Fehlschlag im Wortlaut beobachten und melden, dann die Umsetzung. Weicht der Fehlschlag vom Plan ab, anhalten und melden.
  3. Nur die Zieldateien des eigenen Tasks. Keine weiteren Dateien, keine Hilfsskripte im Vault. Temporäres gehört ins Temp-Verzeichnis, das der Treiber selbst anlegt und löscht.
  4. Shell. PowerShell, Arbeitsverzeichnis D:\Claude. Vor jedem Python-Aufruf $env:PYTHONIOENCODING = "utf-8". Interpreter ist python, nie python3. Kein bash, kein Heredoc, kein python -c, kein python -.
  5. Dateien. UTF-8 ohne BOM, Zeilenende LF. Nach dem Anlegen oder Ändern einer Python-Datei python -m py_compile <datei>.
  6. Nur Standardbibliothek, Python 3.13.
  7. Kein Commit. Der Vault-Root ist kein Git-Repository, und Subagenten committen nie.
  8. Keine Geheimnisse lesen. .claude/settings.json, .claude/settings.local.json und Transkripte werden nicht geöffnet.
  9. Schlägt ein Fall fehl, wird nicht am Code gedreht, bis er grün ist. Gemeldet werden Fall, vollständige Ausgabe und die vermutete Ursache. Die Korrektur passiert im Plan.

Dateiübersicht

Datei Task Art
.vault-tools/session-hooks/sitzungsstart.py 1 neu
.vault-tools/session-hooks/faelle_sitzungsstart.py 1 neu
.vault-tools/session-hooks/ENTWURF.md 1 neu
.vault-tools/session-hooks/regelwerk_nach_edit.py 2 neu
.vault-tools/session-hooks/faelle_nach_edit.py 2 neu
.vault-tools/linkcheck/linkcheck.py 3 ändern
.vault-tools/linkcheck/faelle.py 3 ändern
.vault-tools/linkcheck/ENTWURF-codeblock-erkennung.md 3 ändern

Tasks

Nr Datei Zieldateien Modell parallel mit
1 task-01-sitzungsstart.md sitzungsstart.py, faelle_sitzungsstart.py, ENTWURF.md in session-hooks/ sonnet 2, 3
2 task-02-regelwerk-nach-edit.md regelwerk_nach_edit.py, faelle_nach_edit.py in session-hooks/ sonnet 1, 3
3 task-03-schliesszaun.md linkcheck.py, faelle.py, ENTWURF-codeblock-erkennung.md in linkcheck/ haiku 1, 2

Die Zieldateien sind disjunkt, alle drei Tasks können gleichzeitig laufen. Eine Abhängigkeit gibt es zur Laufzeit: sitzungsstart.py importiert strip_code() aus linkcheck.py, das Task 3 ändert. Die Fälle von Task 1 hängen nicht von der Schließzaun-Regel ab, ein Lauf während einer halb geschriebenen Datei kann aber scheitern. Deshalb wiederholt die Abnahme den Treiber von Task 1 nach dem Ende von Task 3. Wer Nebenläufigkeit ganz vermeiden will, startet Task 3 zuerst, er ist der kleinste.

Vor dem Start (Koordinator)

Den Linkcheck einmal vor Task 3 laufen lassen und die Ausgabe sichern, sonst fehlt der Vergleich:

Set-Location D:\Claude
$env:PYTHONIOENCODING = "utf-8"
python .vault-tools\linkcheck\linkcheck.py --vault . --alle > "$env:TEMP\linkcheck-vorher.txt"; "exit=$LASTEXITCODE"

Laut Sitzungsprotokoll AI/sessions/2026-09-13-18.md meldete er 3 tote Links bei 1006 Notizen, einer davon der Fehlalarm in task-09-gate.md.

Abnahme

  1. Die Verifikationsbefehle aller drei Taskdateien selbst wiederholen, nicht aus dem Bericht übernehmen.
  2. Den Treiber von Task 1 nach dem Ende von Task 3 noch einmal fahren.
  3. Jede neu angelegte Datei byteweise gegen ihren Codeblock im Plan vergleichen, bei Task 3 jede ersetzte Stelle gegen den Neutext. .vault-tools/plan-diff/plan_diff.py erwartet eine andere Form der Pfadzeile, als diese Taskdateien sie haben. Ob es hier trägt, ist nicht geprüft, im Zweifel von Hand vergleichen.
  4. Je Treiber mindestens einen Fall absichtlich kaputt machen und rot sehen. In sitzungsstart.py (?<!\S) aus _NEEDS_REVIEW entfernen: Der Fall „aehnliche Tags zaehlen nicht" muss fallen. In regelwerk_nach_edit.py das + "/" im Präfixvergleich streichen: Der Fall „Datei neben dem Vault mit gleichem Namensanfang" muss fallen. Der Fall „Nachbarordner mit gleichem Namensanfang" bleibt bei diesem Eingriff grün, weil der Rest des Pfads nicht passt; das ist kein blinder Fleck. In faelle.py von Task 3 genügt der Blick auf die drei Fälle, die vor der Umsetzung rot waren. Danach alles zurücksetzen.
  5. Lauf mit echten Daten wie unten.

Lauf mit echten Daten

Task 1. python .vault-tools\session-hooks\faelle_sitzungsstart.py --echt zweimal. Stand beim Planen, zum Vergleich:

  • Tagesnotiz Inbox/2026-09-13.md vorhanden.
  • Inbox ohne type: daily: 2026-09-13-vernetzung-vault-claude.md. Die übrigen 28 Dateien in Inbox/ tragen type: daily.
  • needs-review: Inbox/2026-09-13-vernetzung-vault-claude.md über den Frontmatter-Tag. Den Text #needs-review gab es beim Planen in 17 anderen Dateien. In den gelesenen Zeilen steht er in Backticks, auch in CLAUDE.md Zeile 118, oder in Zeilen, die zu Codebeispielen gehören; ob diese Zeilen in einem Fenced-Block liegen, wurde nicht geprüft. Nicht gelesen wurden zwei lange Zeilen: Code Projekte/active - Agentic Cockpit/docs/plans/2026-09-05-agentic-cockpit/task-04-vault-reader-teil-1.md Zeile 53 und Code Projekte/active - Agentic Cockpit/docs/code/vault_reader.md Zeile 26. Jeden Treffer des Hooks an seiner Zeile nachlesen. Steht das Tag dort ohne Backticks im Fließtext, stimmt der Treffer.
  • review ohne review-done: Zahl gegen Review-Queue.base in Obsidian halten. Eine Abweichung muss sich aus den Ausschlüssen (Import/, Codeblöcke, Inline-Code) erklären lassen, sonst ist sie ein Befund.
  • pending-review: pruefwerkzeuge.md.
  • Sitzungsdateien vom 2026-09-13: beim Planen 2026-09-13-16.md, 2026-09-13-16b.md, 2026-09-13-18.md, dazu alles, was seitdem entstanden ist.
  • Laufzeit beider Läufe unter 5 Sekunden.

Die Gegenprüfung für needs-review läuft mit eigener Suche, nicht mit dem Treiber: Grep über *.md nach needs-review, dann Ausschlüsse, Backticks und Codeblöcke von Hand abziehen.

Task 2. python .vault-tools\session-hooks\faelle_nach_edit.py --echt. Solange pruefwerkzeuge.md auf pending-review steht: systemMessage mit Exit 1, 1 Befundzeile(n) und im Kontext [klein] AI/Regelwerk/pruefwerkzeuge.md — status: pending-review — Freigabe steht aus. Danach einen Hook-Lauf mit einem Pfad außerhalb des Geltungsbereichs von Hand prüfen, er muss still bleiben:

Set-Location D:\Claude
$env:PYTHONIOENCODING = "utf-8"
'{"cwd": "D:\\Claude", "tool_name": "Edit", "tool_input": {"file_path": "D:\\Claude\\Inbox\\2026-09-13.md"}}' | python .vault-tools\session-hooks\regelwerk_nach_edit.py; "exit=$LASTEXITCODE"

Erwartet: nur exit=0, keine Zeile davor.

Task 3. Den Linkcheck nach Task 3 erneut fahren und gegen linkcheck-vorher.txt halten. Erwartet: Die Zeile mit task-09-gate.md -> [[:space:]] fehlt, alle anderen Befunde sind gleich. Dazu der Vergleich alte gegen neue Zaunerkennung über alle Notizen, weil der Linkcheck nur tote Links zeigt und ein jetzt verschluckter gültiger Link dort nicht auffällt. Das Skript gehört ins Scratchpad, nicht in den Vault:

"""Vergleicht alte und neue Zaunerkennung über den Vault: Welche Notizen liefern andere Wikilinks?

Nur für die Abnahme von Task 3, gehört nicht in den Vault.
"""
import re
import sys

VAULT = r"D:\Claude"
sys.path.insert(0, VAULT + r"\.vault-tools\linkcheck")
sys.path.insert(0, VAULT + r"\.vault-tools\obsidian-vault-navigator\scripts")
import vaultlib as V  # noqa: E402
from linkcheck import strip_code as neu  # noqa: E402

_FENCE_RE = re.compile(r"^\s{0,3}(`{3,}|~{3,})")
_INLINE_CODE_RE = re.compile(r"(`+)(?:(?!\1).)*?\1")


def alt(body):
    """strip_code() in der Fassung vor Task 3."""
    if not body:
        return ""
    zaun = None
    aus = []
    for zeile in body.split("\n"):
        m = _FENCE_RE.match(zeile)
        if zaun is None:
            if m:
                zaun = m.group(1)[0] * 3
                aus.append(" " * len(zeile))
                continue
            aus.append(_INLINE_CODE_RE.sub(lambda t: " " * len(t.group(0)), zeile))
        else:
            if m and m.group(1)[0] * 3 == zaun:
                zaun = None
            aus.append(" " * len(zeile))
    return "\n".join(aus)


def links(text):
    return {m.group(1).strip() for m in V._WIKILINK_RE.finditer(text)}


def main():
    sys.stdout.reconfigure(encoding="utf-8")
    geprueft = 0
    anders = 0
    for ap, rp in V.walk_vault(VAULT):
        if not rp.endswith(".md"):
            continue
        with open(ap, "r", encoding="utf-8", errors="replace") as fh:
            _, body = V.parse_frontmatter(fh.read())
        geprueft += 1
        a, n = links(alt(body)), links(neu(body))
        if a != n:
            anders += 1
            print(rp)
            print("  nur alt:", sorted(a - n))
            print("  nur neu:", sorted(n - a))
    print(f"{anders} von {geprueft} Notizen liefern andere Wikilinks.")


main()

Erwartet ist mindestens task-09-gate.md mit nur alt: [':space:']. Jede weitere Notiz mit nur alt: ist ein Link, den der Linkcheck nicht mehr sieht, und wird an der Stelle nachgelesen. Ist es ein echter Link hinter einem Block, den bisher eine Zeile mit Info-String versehentlich geschlossen hat, stimmt die neue Regel und die Notiz hat einen Zaunfehler. Ist es kein Zaunfehler, ist das ein Befund gegen Task 3.

Risiken

  • Rauschen nach jedem Edit. Solange pruefwerkzeuge.md auf pending-review steht, meldet regelwerk_nach_edit.py bei jeder Änderung an Stamm, Regelwerk, Agenten oder Skills denselben Befund. Das sieht man an der systemMessage. Ein Hook, der immer anschlägt, wird überlesen.
  • SessionStart feuert öfter als gedacht. Ohne Matcher läuft der Hook auch bei resume, clear, compact und fork und legt denselben Kontext erneut ab. Die Laufzeit fällt dann jedes Mal an.
  • Verschluckte Links. Ein Block, den bisher eine Zeile mit Info-String versehentlich geschlossen hat, läuft nach Task 3 weiter, notfalls bis Dateiende. Der Linkcheck prüft die Links dahinter dann nicht mehr. Sichtbar macht das nur der Vergleichslauf oben.
  • Timeout-Reihenfolge. regelwerk_nach_edit.py bricht den Check nach 20 Sekunden selbst ab. Setzt der settings.json-Eintrag ein timeout unter etwa 25 Sekunden, beendet Claude Code den Hook, bevor er melden kann, und die Meldung fehlt.
  • Falsche Wurzel. Beide Hooks nehmen cwd aus der Eingabe. Läuft eine Sitzung mit einem anderen cwd als D:\Claude und greift der Eintrag trotzdem, zeigt sitzungsstart.py das in der Kopfzeile. regelwerk_nach_edit.py bleibt dann bei Vault-Dateien still, und das sieht niemand. Ob Einträge aus D:\Claude\.claude\settings.json bei einem anderen Startordner überhaupt geladen werden, ist nicht geprüft.
  • Laufzeit. Die 5 Sekunden sind ein Ziel, gemessen wird am echten Vault. Bei kaltem Dateicache oder wachsendem Vault kann sitzungsstart.py länger brauchen. Der erste --echt-Lauf zeigt eher den kalten Wert, der zweite den warmen.
  • Mitternacht. Die Treiber rechnen das Datum einmal beim Start. Läuft ein Treiber über Mitternacht, schlagen datumsabhängige Fälle fehl, ohne dass am Hook etwas falsch ist.
  • Regressionslauf des Regelwerk-Checks. .vault-tools/regelwerk-check/regressionslauf.py bricht nach dem Code schon heute an der Grundlinie ab, weil die Kopie des Regelwerks pruefwerkzeuge.md mit pending-review enthält. Das ist aus dem Code gelesen, nicht gefahren, und hat mit Task 3 nichts zu tun. Ein Abbruch dort ist kein Beleg gegen Task 3.
  • Python auf dem PATH. Die Exec-Form startet python über den PATH. Ändert sich der PATH, scheitert der Hook mit einem Exit ungleich 0. Claude Code meldet das als nicht blockierenden Fehler, die Hook-Ausgabe fehlt dann.

Getroffene Annahmen

  • Inbox-Notizen werden auch in Unterordnern von Inbox/ gezählt, nur .md-Dateien, Punktordner ausgenommen.
  • Die Zählung review ohne review-done hat dieselben Ausschlüsse wie needs-review (Import/, _index/, Punktordner, node_modules).
  • Import/ und _index/ fallen nur direkt unter der Wurzel heraus, Punktordner und node_modules in jeder Tiefe.
  • Tags werden ohne Rücksicht auf Groß- und Kleinschreibung verglichen. Ein Inline-Tag steht am Zeilenanfang oder nach Leerraum. #needs-review/x und #needs-review-done zählen nicht als needs-review.
  • Kaputtes YAML wird tolerant gelesen: Ein Tag in einer nicht geschlossenen Liste zählt. Ein Frontmatter ohne Schlusszeile gilt nicht als Frontmatter.
  • regelwerk_nach_edit.py bestimmt die Wurzel wie sitzungsstart.py (cwd, sonst zwei Ebenen über dem Skript).
  • Ein relativer file_path gilt relativ zur Wurzel. Der Pfadvergleich ignoriert Groß- und Kleinschreibung im ganzen Pfad, nicht nur beim Laufwerk.
  • Still bleibt der Regelwerk-Hook nach dem Check nur bei Exit 0 mit der Schlusszeile „Keine der N gelaufenen Prüfungen hat angeschlagen." und ohne ACHTUNG-Zeile. Exit 0 mit ACHTUNG ist ein Befund.
  • Interner Timeout 20 Sekunden, Kontext höchstens 40 Zeilen und 4000 Zeichen.
  • tool_name wird nicht geprüft, das übernimmt der Matcher.
  • ENTWURF.md für beide Hooks gehört zu Task 1, damit die Zieldateien disjunkt bleiben.
  • Jeder Punkt des Sitzungsstart-Hooks läuft in einem eigenen try. Das geht über den Auftrag hinaus, dort war nur ein Fehler insgesamt verlangt.
  • Der Wortlaut der CommonMark-Spezifikation wurde nicht gezogen. Die Schließzaun-Regel ist an markdown-it-py 4.2.0 belegt, das lokal im Projekt Agentic Cockpit liegt.
  • In ENTWURF-codeblock-erkennung.md wird die gepflegte Zahl „vierzehn Angriffsfälle" durch „alle Angriffsfälle aus faelle.py" ersetzt.

Offene Punkte für den Koordinator

  • settings.json-Einträge: Matcher für SessionStart (alle Quellen oder nur startup und clear) und ein timeout von mindestens 30 Sekunden für den PostToolUse-Eintrag.
  • Das Rauschen durch pruefwerkzeuge.md auf pending-review: freigeben lassen oder für die Zeit bis zur Freigabe hinnehmen.
  • Den Abschnitt 4.5 der CommonMark-Spezifikation 0.31.2 gegen die Regel in ENTWURF-codeblock-erkennung.md halten und den Vermerk „Abgleich offen" danach streichen oder korrigieren.
  • regelwerk_check.py nennt im Kommentar 17 Linkcheck-Fälle, nach Task 3 sind es mehr. Die Datei liegt außerhalb dieses Plans.
  • regressionslauf.py des Regelwerk-Checks bricht wegen pending-review an der Grundlinie ab. Das ist unabhängig von diesem Plan, verdeckt aber jeden Regressionsnachweis dort.