Files
claude-regelwerk/.vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-02-sitzungsstart.md
T
Pascal MichelandClaude Opus 5 e8472409e9 Automatischer Push: 25 Datei(en) geändert
- .claude/agents/executer.md
- .claude/agents/recherche.md
- .claude/skills/anleitung-straffen/SKILL.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/00-uebersicht.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-01-zaehler.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-02-sitzungsstart.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-03-regeltexte.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-04-bestandsskript.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-05-bestandslauf.md
- .vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/00-uebersicht.md
- .vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/task-01-quorum-werkzeug.md
- .vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/task-02-quorum-workflow.md
- … und 13 weitere

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-30 04:00:02 +02:00

15 KiB

type, title, status, summary, tags, created, updated, modell, modell_begruendung
type title status summary tags created updated modell modell_begruendung
plan-task Task 02 — Zeile „Waisen außerhalb der Ausnahmen“ im SessionStart-Hook draft sitzungsstart.py ruft waisen.py --zahl als eigenen Prozess mit 15 s Grenze und meldet die Zahl als Zeile; der Treiber bekommt eine Grundlinie mit .obsidian/app.json, neue Fälle und angepasste Erwartungen; ENTWURF.md bekommt einen Nachtrag.
plan-task
vault-tools
2026-09-29 2026-09-29 haiku Fünfzehn Aus-wird-Blöcke und ein angehängter Abschnitt stehen wörtlich im Plan und werden per Skript angewendet; keine typografischen Zeichen in den Codeblöcken.

Übersicht: 00-uebersicht.md

Task 02 — Zeile im SessionStart-Hook

Globale Vorgaben: 00-uebersicht.md im selben Ordner, Abschnitt „Globale Vorgaben“.

Voraussetzung: Task 01 ist umgesetzt, waisen.py und waisen-ausnahmen.txt liegen unter .vault-tools/linkcheck/.

Zieldateien

  • D:\Claude\.vault-tools\session-hooks\sitzungsstart.py (geändert)
  • D:\Claude\.vault-tools\session-hooks\faelle_sitzungsstart.py (geändert)
  • D:\Claude\.vault-tools\session-hooks\ENTWURF.md (geändert)

Verbote

  • .claude/settings.json bleibt, wie es ist. Der Timeout von 30 s wird nicht geändert.
  • regelwerk_nach_edit.py und faelle_nach_edit.py bleiben unverändert.
  • ZIEL_LAUFZEIT_S in faelle_sitzungsstart.py bleibt bei 5.0, auch wenn die Messung darüber liegt.
  • Nichts unter .vault-tools/linkcheck/ ändern.

Schritt 1 — Blöcke anwenden

python D:/Claude/.vault-tools/plan-kopie/plan_materialisiere.py D:/Claude/.vault-tools/linkcheck/docs/plans/2026-09-29-waisen D:/Claude task-02-sitzungsstart.md

Erwartet: eine Zeile mit 'ersetzt': 15 und 'angehaengt': 1 und die Schlusszeile 0 Befund(e).

Geltungsbereich im Kopf von .vault-tools/session-hooks/sitzungsstart.py

Aus:

  Die Punkte aus dem Abschnitt Sitzungsprotokoll in CLAUDE.md, gezählt und
  nicht bewertet: Datum laut Maschinenuhr, Tagesnotiz, Inbox-Notizen ohne
  type: daily, Notizen mit Tag needs-review, Notizen mit Tag review ohne
  review-done, Regelwerk-Dateien mit status: pending-review, heutige
  Sitzungsdateien. Die Vault-Wurzel ist ausschließlich der Ordner zwei Ebenen

wird:

  Die Punkte aus dem Abschnitt Sitzungsprotokoll in CLAUDE.md, gezählt und
  nicht bewertet: Datum laut Maschinenuhr, Tagesnotiz, Inbox-Notizen ohne
  type: daily, Notizen mit Tag needs-review, Notizen mit Tag review ohne
  review-done, Regelwerk-Dateien mit status: pending-review, heutige
  Sitzungsdateien, dazu die Zahl der Waisen außerhalb der Ausnahmen aus
  .vault-tools/linkcheck/waisen.py (eigener Prozess, nach 15 s abgebrochen;
  Regeln und Lücken stehen im Kopf von waisen.py). Die Vault-Wurzel ist
  ausschließlich der Ordner zwei Ebenen

Importe in .vault-tools/session-hooks/sitzungsstart.py

Aus:

import re
import sys

wird:

import re
import subprocess
import sys

Konstanten in .vault-tools/session-hooks/sitzungsstart.py

Aus:

SKRIPT = Path(__file__).resolve()
LINKCHECK = SKRIPT.parent.parent / "linkcheck"

wird:

SKRIPT = Path(__file__).resolve()
LINKCHECK = SKRIPT.parent.parent / "linkcheck"
WAISEN = LINKCHECK / "waisen.py"
# Das Budget des Hooks in .claude/settings.json sind 30 s für alles.
ZEITGRENZE_WAISEN_S = 15.0

Neuer Punkt in .vault-tools/session-hooks/sitzungsstart.py

Aus:

PUNKTE = [
    ("Datum", punkt_datum),
    ("Tagesnotiz", punkt_tagesnotiz),
    ("Inbox", punkt_inbox),
    ("needs-review/review", punkt_tags),
    ("Regelwerk", punkt_regelwerk),
    ("Sitzungen", punkt_sitzungen),
]

wird:

def punkt_waisen(wurzel: Path, heute: str) -> list[str]:
    if not WAISEN.is_file():
        raise RuntimeError(f"waisen.py fehlt unter {WAISEN}")
    umgebung = dict(os.environ)
    umgebung["PYTHONIOENCODING"] = "utf-8"
    try:
        lauf = subprocess.run(
            [sys.executable, str(WAISEN), "--vault", str(wurzel), "--zahl"],
            capture_output=True,
            stdin=subprocess.DEVNULL,
            timeout=ZEITGRENZE_WAISEN_S,
            env=umgebung,
        )
    except subprocess.TimeoutExpired:
        raise RuntimeError(
            f"waisen.py nach {ZEITGRENZE_WAISEN_S:g} s abgebrochen, nicht gezählt"
        ) from None
    ausgabe = lauf.stdout.decode("utf-8", errors="replace").strip()
    if lauf.returncode in (0, 1) and ausgabe.isdigit():
        return [f"- Waisen außerhalb der Ausnahmen: {ausgabe}"]
    erste = ausgabe or lauf.stderr.decode("utf-8", errors="replace").strip()
    raise RuntimeError(f"waisen.py Exit {lauf.returncode}: {erste.split(chr(10))[0].strip()}")


PUNKTE = [
    ("Datum", punkt_datum),
    ("Tagesnotiz", punkt_tagesnotiz),
    ("Inbox", punkt_inbox),
    ("needs-review/review", punkt_tags),
    ("Regelwerk", punkt_regelwerk),
    ("Sitzungen", punkt_sitzungen),
    ("Waisen", punkt_waisen),
]

Konstanten in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

SI = f"- Sitzungsdateien AI/sessions/{HEUTE}-*.md: "
FEHLERZEILE = "Sitzungsstart-Hook:"

wird:

SI = f"- Sitzungsdateien AI/sessions/{HEUTE}-*.md: "
WA = "- Waisen außerhalb der Ausnahmen: "
FEHLERZEILE = "Sitzungsstart-Hook:"
WAISEN = HIER.parent / "linkcheck" / "waisen.py"
AUSNAHMEN = HIER.parent / "linkcheck" / "waisen-ausnahmen.txt"

Grundlinie in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

    schreibe(v, "Notizen/sauber.md",
             "---\ntype: note\ntags: [thema]\n---\n\nKein Tag hier, nur das Wort review ohne Raute.\n")

wird:

    schreibe(v, "Notizen/sauber.md",
             "---\ntype: note\ntags: [thema]\n---\n\nKein Tag hier, nur das Wort review ohne Raute.\n")
    # waisen.py braucht einen Obsidian-Vault; ohne Filter hat die Grundlinie sechs Waisen.
    schreibe(v, ".obsidian/app.json", '{"userIgnoreFilters": []}')

Erwartete Zeilen der Grundlinie in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

        SI + f"1 ({HEUTE}-10.md)",
    ]

wird:

        SI + f"1 ({HEUTE}-10.md)",
        WA + "6",
    ]

Kopf von kopiere_hook in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

def kopiere_hook(v: Path, linkcheck_kopieren: bool = True) -> Path:

wird:

def kopiere_hook(v: Path, linkcheck_kopieren: bool = True, waisen_kopieren: bool = True) -> Path:

Rumpf von kopiere_hook in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

    if linkcheck_kopieren:
        lc_ziel = v / ".vault-tools" / "linkcheck"
        lc_ziel.mkdir(parents=True, exist_ok=True)
        shutil.copy2(LINKCHECK, lc_ziel / "linkcheck.py")
    return skript

wird:

    lc_ziel = v / ".vault-tools" / "linkcheck"
    lc_ziel.mkdir(parents=True, exist_ok=True)
    if linkcheck_kopieren:
        shutil.copy2(LINKCHECK, lc_ziel / "linkcheck.py")
    if waisen_kopieren:
        shutil.copy2(WAISEN, lc_ziel / "waisen.py")
        shutil.copy2(AUSNAHMEN, lc_ziel / "waisen-ausnahmen.txt")
    return skript

Feld im Fall in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

    stdin: Callable[[Path], bytes] | None = None
    linkcheck_kopieren: bool = True

wird:

    stdin: Callable[[Path], bytes] | None = None
    linkcheck_kopieren: bool = True
    waisen_kopieren: bool = True

Fall ohne jeden Ordner in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

    Fall("leer        | Vault ohne jeden Ordner",
         lambda v: [shutil.rmtree(p) for p in list(v.iterdir())],
         [TN + "fehlt", IN + "Ordner Inbox/ fehlt", NR + "0", RV + "0",
          PR + "Ordner AI/Regelwerk/ fehlt", SI + "Ordner AI/sessions/ fehlt"]),

wird:

    Fall("leer        | Vault ohne jeden Ordner",
         lambda v: [shutil.rmtree(p) for p in list(v.iterdir())],
         [TN + "fehlt", IN + "Ordner Inbox/ fehlt", NR + "0", RV + "0",
          PR + "Ordner AI/Regelwerk/ fehlt", SI + "Ordner AI/sessions/ fehlt"],
         verboten=[WA],
         praefixe=["Sitzungsstart-Hook: Waisen: RuntimeError: waisen.py Exit 2: Waisen - ABGEBROCHEN: kein Obsidian-Vault"]),

Fall ohne linkcheck.py und neue Fälle in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

         verboten=[NR, RV],
         praefixe=["Sitzungsstart-Hook: needs-review/review: RuntimeError: linkcheck.py nicht importierbar"],
         stdin=lambda v: b"{}",
         linkcheck_kopieren=False),
]

wird:

         verboten=[NR, RV, WA],
         praefixe=["Sitzungsstart-Hook: needs-review/review: RuntimeError: linkcheck.py nicht importierbar",
                   "Sitzungsstart-Hook: Waisen: RuntimeError: waisen.py Exit 2: Waisen - ABGEBROCHEN: linkcheck.py nicht importierbar"],
         stdin=lambda v: b"{}",
         linkcheck_kopieren=False),
    Fall("waisen      | verlinkte Notiz senkt die Zahl",
         lambda v: schreibe(v, "Notizen/verweis.md", "[[Notizen/sauber]]\n"),
         [WA + "5"]),
    Fall("waisen      | Waise an erster und an letzter Stelle",
         lambda v: (schreibe(v, "0-erste/a.md", "x\n"), schreibe(v, "zz-letzte/z.md", "x\n")),
         [WA + "8"]),
    Fall("waisen      | Muster aus der Ausnahmedatei greift",
         lambda v: schreibe(v, "Bookstack/archiv/seite.md", "x\n"),
         [WA + "6"]),
    Fall("waisen      | waisen.py fehlt", nichts,
         [DA, TN + "vorhanden", IN + "1 (offen.md)", NR + "0", RV + "0", PR + "0", SI + f"1 ({HEUTE}-10.md)"],
         verboten=[WA],
         praefixe=["Sitzungsstart-Hook: Waisen: RuntimeError: waisen.py fehlt"],
         waisen_kopieren=False),
    Fall("waisen      | kaputte app.json meldet Fehler statt Zahl",
         lambda v: schreibe(v, ".obsidian/app.json", "{kaputt"),
         [DA, TN + "vorhanden", NR + "0", SI + f"1 ({HEUTE}-10.md)"],
         verboten=[WA],
         praefixe=["Sitzungsstart-Hook: Waisen: RuntimeError: waisen.py Exit 2: Waisen - ABGEBROCHEN: .obsidian/app.json nicht lesbar"]),
    Fall("umgebung    | ohne PYTHONIOENCODING zaehlt Waisen mit Umlaut im Namen",
         lambda v: schreibe(v, "Notizen/Übersicht 🚀.md", "x\n"),
         [WA + "7"],
         umgebung=ohne_pythonioencoding),
]

Aufruf von kopiere_hook in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

    skript = kopiere_hook(v, fall.linkcheck_kopieren)

wird:

    skript = kopiere_hook(v, fall.linkcheck_kopieren, fall.waisen_kopieren)

Gesperrte Datei in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

    erwartet = grundzeilen(v) + ["Sitzungsstart-Hook: 1 Datei(en) nicht lesbar (erste: Notizen/gesperrt.md)"]

wird:

    # gesperrt.md ist für waisen.py lesbar und die siebte Waise.
    erwartet = [z for z in grundzeilen(v) if not z.startswith(WA)] + [WA + "7"]
    erwartet.append("Sitzungsstart-Hook: 1 Datei(en) nicht lesbar (erste: Notizen/gesperrt.md)")

Zeitgrenze im Prozess in .vault-tools/session-hooks/faelle_sitzungsstart.py

Aus:

IM_PROZESS = [
    ("absturz     | ein Punkt wirft", ip_punkt_wirft),
    ("absturz     | Hauptablauf wirft", ip_hauptablauf_wirft),
    ("lesen       | Datei nicht lesbar", ip_datei_gesperrt),
    ("lesen       | Ordner nicht lesbar", ip_ordner_gesperrt),
]

wird:

def ip_waisen_zeitgrenze(v: Path) -> tuple[bool, str]:
    """Die Zeitgrenze lässt sich von außen nicht billig überschreiten:
    im Prozess auf eine Millisekunde gesetzt, startet waisen.py nicht rechtzeitig."""
    skript = kopiere_hook(v)
    modul = lade_hook("hook_waisen_zeitgrenze", skript)
    modul.ZEITGRENZE_WAISEN_S = 0.001
    zeilen = im_prozess(modul, v)
    if zeilen is None:
        return False, "    keine gueltige Ausgabe"
    erwartet = [z for z in grundzeilen(v) if not z.startswith(WA)]
    erwartet.append("Sitzungsstart-Hook: Waisen: RuntimeError: waisen.py nach 0.001 s abgebrochen, nicht gezählt")
    probleme = bewerte(zeilen, erwartet, [WA], [])
    return not probleme, "\n".join("    " + p for p in probleme)


IM_PROZESS = [
    ("absturz     | ein Punkt wirft", ip_punkt_wirft),
    ("absturz     | Hauptablauf wirft", ip_hauptablauf_wirft),
    ("lesen       | Datei nicht lesbar", ip_datei_gesperrt),
    ("lesen       | Ordner nicht lesbar", ip_ordner_gesperrt),
    ("waisen      | Zeitgrenze ueberschritten", ip_waisen_zeitgrenze),
]

Nachtrag am Ende von .vault-tools/session-hooks/ENTWURF.md

Häng diesen Block ans Ende:


## Nachtrag 2026-09-29: Zahl der Waisen

**Anlass.** Die Spec Waisen im Graphen vom 2026-09-29 verlangt beim Sitzungsstart eine Zeile mit der Zahl der Waisen außerhalb der Ausnahmen, nur die Zahl, keine Liste. Plan: `.vault-tools/linkcheck/docs/plans/2026-09-29-waisen/`.

| # | Frage | Antwort | Herkunft |
|---|---|---|---|
| 16 | Woher kommt die Zahl? | `waisen.py --zahl` aus `.vault-tools/linkcheck/`, als eigener Prozess mit `sys.executable`, stdin geschlossen, `PYTHONIOENCODING=utf-8`. Die Regeln des Zählers stehen in `.vault-tools/linkcheck/ENTWURF-waisen.md`. | **Projektvorgabe**: Plan |
| 17 | Wie lange darf sie dauern? | 15 s, dann bricht der Hook den Zähler ab und meldet eine Fehlerzeile. Das Budget des Hooks in `.claude/settings.json` sind 30 s für alles. | **Projektvorgabe** |
| 18 | Was, wenn `waisen.py` fehlt, abbricht oder keine Zahl liefert? | Fehlerzeile `Sitzungsstart-Hook: Waisen:` mit der ersten Ausgabezeile des Zählers, keine Zahl. | **Projektvorgabe**: `pruefwerkzeuge.md` Abschnitt 6 |

**Treiber.** Die Grundlinie trägt jetzt `.obsidian/app.json` und hat sechs Waisen. `kopiere_hook()` kopiert `waisen.py` und `waisen-ausnahmen.txt` mit. Neue Fälle: ein Link senkt die Zahl, Waisen an erster und letzter Stelle, ein Muster aus der Ausnahmedatei greift, `waisen.py` fehlt, kaputte `app.json`, Umlaut und Emoji im Namen ohne `PYTHONIOENCODING`, und im Prozess die überschrittene Zeitgrenze. Zwei bestehende Fälle erwarten zusätzlich die Fehlerzeile des Zählers: Vault ohne jeden Ordner (dann fehlt `.obsidian`) und `linkcheck.py` fehlt. Dass der Hook die Zahl wirklich vom Zähler übernimmt, zeigen die verschiedenen erwarteten Zahlen 5, 6, 7 und 8 in den neuen Fällen.

**Laufzeit.** Frage 15 peilt unter 5 s an. Ob das mit dem Zähler hält, misst `faelle_sitzungsstart.py --echt`.

Schritt 2 — Angriffsliste

PYTHONIOENCODING=utf-8 python D:/Claude/.vault-tools/session-hooks/faelle_sitzungsstart.py; echo "exit=$?"

Erwartet: Grundlinie gruen, keine Zeile, die mit FEHLER beginnt, die Fälle mit waisen im Namen als BESTANDEN, Schlusszeile N von N Faellen bestanden. mit zweimal derselben Zahl, exit=0.

Schritt 3 — Messung M2 über den echten Vault

PYTHONIOENCODING=utf-8 python D:/Claude/.vault-tools/session-hooks/faelle_sitzungsstart.py --echt; echo "exit=$?"

Erwartet: die Kontextzeilen des echten Vaults, darunter eine Zeile - Waisen außerhalb der Ausnahmen: mit einer Zahl und keine Zeile Sitzungsstart-Hook: Waisen:. Steht dort LAUFZEIT UEBER ZIEL und exit=1: nichts ändern, die Laufzeit melden. Das ist Offener Punkt 4 der Übersicht, kein Fehler dieses Tasks.

Schritt 4 — Nachbarn unverändert grün

PYTHONIOENCODING=utf-8 python D:/Claude/.vault-tools/session-hooks/faelle_nach_edit.py; echo "exit=$?"

Erwartet: exit=0.

Bericht

Melde die vollständige Ausgabe von Schritt 1, 3 und 4, von Schritt 2 die Schlusszeile und jede Zeile, die nicht mit BESTANDEN beginnt. Läuft etwas anders als erwartet: nichts anpassen, melden.