Files
claude-regelwerk/.vault-tools/linkcheck/ENTWURF-waisen.md
T
Pascal MichelandClaude Opus 5 eb48f8822d Automatischer Push: 45 Datei(en) geändert
- .claude/agents/planer.md
- .claude/agents/quorum-stimme.md
- .claude/skills/bookstack-upload/SKILL.md
- .claude/skills/bookstack-upload/scripts/bookstack/archiv.py
- .claude/skills/bookstack-upload/scripts/bookstack/auswahl.py
- .claude/skills/bookstack-upload/scripts/bookstack/beschreibung.py
- .claude/skills/bookstack-upload/scripts/bookstack/bilder.py
- .claude/skills/bookstack-upload/scripts/bookstack/ergebnis.py
- .claude/skills/bookstack-upload/scripts/bookstack/hochladen.py
- .claude/skills/bookstack-upload/scripts/bookstack/konvertierung.py
- .claude/skills/bookstack-upload/scripts/bookstack/struktur.py
- .claude/skills/bookstack-upload/scripts/bookstack/verweise.py
- … und 33 weitere

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

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

11 KiB

Entwurf: Waisen-Zähler

Angelegt am 2026-09-29 als Entwurfsschritt nach AI/Regelwerk/pruefwerkzeuge, Abschnitt 1, vor der ersten Codezeile. Anforderung: Spec Waisen im Graphen vom 2026-09-29 (liegt in der Inbox, nicht veröffentlicht). Plan: docs/plans/2026-09-29-waisen/ in diesem Ordner.

Anlass

Am 2026-09-29 hatten laut Messung der Spec 852 von 1.996 Notizen keine Kante im Graphen, 664 davon Plandateien. Das Messskript lag nur im Scratchpad. waisen.py macht die Messung wiederholbar, und der Sitzungsstart zeigt die Zahl, damit neue Waisen auffallen.

Entscheidungsliste

# Frage Antwort Herkunft
1 Was ist ein Knoten? Jede Datei mit Endung .md, Groß- und Kleinschreibung der Endung egal. Anhänge sind keine Knoten. Projektvorgabe: Spec, Befund: Anhänge zeigt der Graph nicht
2 Punktordner und Punktdateien? Weder Knoten noch Quelle einer Kante. Projektvorgabe: Spec, Befund
3 userIgnoreFilters? Zur Laufzeit aus .obsidian/app.json. Jeder Eintrag wird getrimmt, ein leerer fällt weg. Ein Eintrag mit mehr als zwei Zeichen, der mit / beginnt und endet, ist ein regulärer Ausdruck, gesucht im relativen Pfad; jeder andere ist ein Präfix ab der Vault-Wurzel. Beide gelten ohne Rücksicht auf Groß- und Kleinschreibung. Ausgeblendete Dateien sind weder Knoten noch Quelle einer Kante. Quelle für die Filterregeln: Obsidian 1.13.7, updateUserIgnoreFilters und isUserIgnored, siehe Abschnitt Quelle der Obsidian-Regeln. Projektvorgabe für Knoten und Kanten: Spec
4 Dialekt der Ausdrücke? Python re statt JavaScript. Ein Ausdruck, den nur einer der beiden Dialekte versteht, wirkt verschieden. Für die Einträge im Vault am 2026-09-29 gleichwertig. Projektvorgabe, Lücke im Kopfkommentar
5 Ordner vorab überspringen? Ja, wenn ein Filter auf den Ordnerpfad mit Schrägstrich passt. Sonst läuft der Zähler durch jedes node_modules. Projektvorgabe, Lücke für Ausdrücke mit Endanker benannt
6 Was ist ein Wikilink? [[Ziel]], [[Ziel|Alias]], [[Ziel#Abschnitt]], [[Ziel^Block]], Einbettung mit !. Ein Backslash am Ende des Ziels fällt weg (escaptes Pipe in Tabellen). Quelle: _WIKILINK_RE, vaultlib.py Zeile 61; Backslash: linkcheck.py, wikilinks()
7 Wikilink im Frontmatter? Zählt, jede Fundstelle im Frontmatter-Text, egal ob in Anführungszeichen. Projektvorgabe: Spec. Ob Obsidian das so zählt: Prüfschritt P2
8 Was ist ein Markdown-Link? [Text](Ziel) im Text, auch als Bild, Ziel auch in spitzen Klammern, optional mit Titel. Anker und Abfrage fallen weg, das Ziel wird URL-dekodiert. Ziele mit Schema (https:, mailto:, obsidian:, Laufwerk) und reine Anker zählen nicht. Ein Ziel ohne .md wird mit .md gesucht. Der Schemafilter ist Doppelschutz: Unter Windows trägt kein Dateiname einen Doppelpunkt, die Auflösung findet für ein Ziel mit Schema ohnehin keine Notiz. Deshalb misst ihn kein Fall und kein Mutant. Projektvorgabe: Spec. URL-Dekodierung und fehlende Endung: Prüfschritte P1 und P3
9 Markdown-Link im Frontmatter? Zählt nicht. Projektvorgabe
10 Wie wird ein Ziel aufgelöst? Kandidaten sind die Notizen mit demselben Dateinamen samt Endung, klein geschrieben: erst mit dem Ziel wie geschrieben, wenn sein Name einen Punkt trägt, sonst mit angehängtem .md. Ein Ziel mit ./ oder ../ wird gegen den Ordner der Quelle gerechnet, ./../ wie ../, ein ../ über die Wurzel hinaus bleibt an der Wurzel. Dann gewinnt der Kandidat, dessen Pfad dem Ziel gleicht; ein / vorn fällt dafür weg. Ein Ziel mit / vorn endet danach ohne Treffer. Sonst zählt jeder Kandidat, dessen Pfad auf das Ziel endet, ohne Ordnergrenze. Backslashes bleiben stehen. Quelle: Obsidian 1.13.7, getLinkpathDest. Ob der Linkleser von Obsidian Backslashes vorher umwandelt: Lücke, benannt; Prüfweg ist der Vergleich vorher und nachher in Task 06 des Plans
11 Groß- und Kleinschreibung im Ziel? Egal. Quelle: getLinkpathDest vergleicht in Kleinschreibung
12 Mehrdeutiger Name? Erst die Kandidaten, deren Pfad mit dem Ordner der Quelle beginnt (der ganze Teilbaum, ohne Ordnergrenze), dann die übrigen; in beiden Gruppen der kürzere Pfad in Zeichen zuerst. Bei gleicher Länge entscheidet die Reihenfolge des Durchlaufs, alphabetisch je Ordner. waisen.py nimmt den ersten Kandidaten. Quelle: Obsidian 1.13.7, getLinkpathDest und die Vergleichsfunktion nach Pfadlänge. Reihenfolge bei gleicher Länge: Lücke, benannt. Dass der Graph den ersten Kandidaten nimmt: Projektvorgabe, der Auszug zeigt es nicht
13 Code? Zäune und Code-Spans blendet strip_code() aus. Quelle: ENTWURF-codeblock-erkennung.md
14 Kommentare mit %% oder als HTML? Nicht ausgeblendet. Lücke, benannt
15 Selbstreferenz, Link ins Leere, Link auf ausgeblendete Notiz? Keine Kante. Projektvorgabe
16 Was ist eine Waise? Ein Knoten ohne ausgehende und ohne eingehende Kante. Projektvorgabe: Spec
17 Ausnahmen? Positivliste waisen-ausnahmen.txt, ein Muster je Zeile relativ zur Wurzel (* ohne Schrägstrich, ** über Ordner, ? ein Zeichen), Groß- und Kleinschreibung egal. Ausgenommene Notizen zählen nicht als Waisen, bleiben aber Knoten und Quelle von Kanten. Ein Muster ohne Treffer wird genannt. Projektvorgabe: Spec, Positivliste beim Zähler, keine Kundennamen
18 Ausnahmedatei fehlt, unlesbar, Muster ungültig oder doppelt? Befund, Exit 2, keine Zahl. Projektvorgabe: pruefwerkzeuge.md Abschnitt 6
19 .obsidian fehlt? Befund, Exit 2: kein Obsidian-Vault. Fehlt nur app.json oder der Schlüssel, gilt kein Filter, wie in Obsidian ohne Einstellung. app.json kaputt, userIgnoreFilters null oder keine Liste, ein Eintrag kein Text: Befund, Exit 2. Ein leerer Eintrag fällt weg. Ein ungültiger Ausdruck fällt weg und steht als Zeile Filter übersprungen, ungültiger regulärer Ausdruck: in der Ausgabe, bei --zahl auf stderr, damit stdout nur die Zahl trägt. Projektvorgabe für die Abbrüche, strenger als Obsidian 1.13.7, das null als keinen Filter nimmt. Quelle für leere und ungültige Einträge: updateUserIgnoreFilters
20 Datei oder Ordner nicht lesbar? Befund, Exit 2, keine Zahl, weil eine Zahl ohne diese Dateien falsch wäre. Projektvorgabe: pruefwerkzeuge.md Abschnitte 4 und 6
21 Ausgabe und Exit? Kopf, Zahlen und die Zeile Waisen außerhalb der Ausnahmen: N; --liste dazu die Pfade; --zahl nur N. Exit 0 ohne, 1 mit Waisen, 2 bei Abbruch. Projektvorgabe
22 Laufzeit? Unter 10 s über den Vault, gemessen mit faelle_waisen.py --echt. Im Sitzungsstart bricht der Hook nach 15 s ab. Projektvorgabe: Hook-Budget 30 s in .claude/settings.json

Keine Zeile steht auf Annahme ohne Prüfweg. Wo eine Antwort nicht aus einer Quelle stammt, ist sie Projektvorgabe und im Plan als Prüfschritt P1 bis P3 in der App benannt.

Quelle der Obsidian-Regeln

Nachgebessert am 2026-09-30 nach der Prüfung der ersten Welle (Plan docs/plans/2026-09-29-waisen/, Task 06).

  • Filter und Linkauflösung folgen Obsidian 1.13.7: updateUserIgnoreFilters, isUserIgnored, getLinkpathDest und die Vergleichsfunktion nach Pfadlänge, mit der getLinkpathDest sortiert.
  • Gelesen am 2026-09-30 mit grep -a im Programmabbild obsidian-1.13.7.asar unter %APPDATA%\obsidian\. Nachgebaut, nicht abgeschrieben.
  • Gegenprobe des Prüfers am selben Tag: Sein Nachbau dieser Regeln, der zusätzlich Backslashes in / umwandelt, fand im echten Vault 0 Linkziele, die von waisen.py in der Fassung vom 2026-09-29 abweichen.
  • Nicht aus der Quelle belegt: ob der Linkleser von Obsidian Backslashes vor der Auflösung umwandelt, und in welcher Reihenfolge sein Namensverzeichnis gleich lange Pfade führt.
  • Eine neue Obsidian-Version macht diese Herkunft nicht falsch, aber ungeprüft. Prüfweg: dieselben Funktionen im neuen Abbild nachlesen.

Angriffsliste

Die einzelnen Fälle stehen an genau einer Stelle, in faelle_waisen.py. Hier steht, welche Klassen aus pruefwerkzeuge.md Abschnitt 3 abgedeckt sind.

Klasse Fälle
fehlt .obsidian, app.json, Schlüssel userIgnoreFilters, Ausnahmedatei, linkcheck.py, Vault-Ordner, Schlusszeile des Frontmatters
null userIgnoreFilters ist null
falscher Typ app.json als Liste, Filtereintrag als Zahl, app.json ohne JSON
leer leere Filterliste, leere und nur aus Leerzeichen bestehende Filtereinträge, leere Ausnahmedatei, nur Kommentare, leere Notiz, Vault ohne Notiz
doppelt doppeltes Muster, gleichnamige Notizen in zwei Ordnern: gleicher Ordner, relativ zur Quelle, kürzester Pfad, Teilbaum der Quelle vor kürzerem Pfad, Länge vor Tiefe, ./ und ./../ vor gleichnamiger Notiz an der Wurzel; wo es eine Wahl gibt, liegt die richtige alphabetisch hinten
zusätzlich Punktordner, Punktdatei, andere Endung, Endung in Großbuchstaben, per Dateifilter ausgeblendete Notiz als Knoten und als Quelle
Wertebereich Muster mit Backslash, führendem Schrägstrich, .., . als Pfadteil, leerem Pfadteil, Laufwerk; Stern über Ordnergrenze; Filter aus zwei Schrägstrichen, Filter mit Leerzeichen, Präfixfilter und Ausdruck in anderer Schreibung als der Pfad, ungültiger Ausdruck mit und ohne --zahl; Linkziel mit Backslash, mit / vorn ohne Treffer an der Wurzel, mit ../ über die Wurzel hinaus, Pfadende ohne Ordnergrenze
Verweis ins Leere Wikilink ins Leere, auf Anhang, auf ausgeblendete Notiz, Selbstreferenz, Anker allein
Reihenfolge Waise an erster und letzter Stelle, Muster in erster und letzter Zeile, Filter an erster und letzter Stelle, Link am Dateiende ohne Zeilenumbruch
Umgebung CRLF, BOM, ungültiges UTF-8 in Notiz und Ausnahmedatei, Umlaut und Emoji im Namen ohne PYTHONIOENCODING
Code Wikilink im Zaun, Markdown-Link im Code-Span, Link nach dem Zaun, Zaun ohne Ende
Absturz Notiz nicht lesbar, Ordner nicht lesbar, unerwarteter Fehler (alle im Prozess)

Nicht gemessen: der Schemafilter aus Entscheidung 8. Die Fälle Web-Adresse findet keine Notiz und Laufwerkspfad findet keine Notiz bestehen auch ohne ihn, weil die Auflösung für ein Ziel mit Doppelpunkt nie eine Notiz findet.

Absturzfestigkeit

Der Hauptablauf läuft in try. Ein erwarteter Befund (Abbruch) und ein unerwarteter Fehler enden beide mit Exit 2 und einer Zeile Waisen - ABGEBROCHEN:, ein unerwarteter Fehler ohne --zahl zusätzlich mit Traceback. Eine Teilzahl gibt es bewusst nicht: Der Zähler sammelt keine Einzelbefunde, die man trotz Abbruch zeigen könnte, und eine Zahl über einen Teil des Vaults wäre falsch.

Rotlauf

faelle_waisen.py --rotlauf baut jeden Mutanten aus MUTANTEN in eine Kopie von waisen.py und fährt die genannten Fälle dagegen. Jeder Mutant entfernt genau eine Entscheidung aus der Liste oben. Ein Mutant, dessen Zeile nicht genau einmal in waisen.py steht, ist ein Befund, kein stilles Überspringen.

Abbruchbedingung

Schluss ist, wenn der Treiber mit grüner Grundlinie ohne Fehlschlag durchläuft, der Rotlauf jeden Mutanten tötet, --echt unter 10 s bleibt, der Koordinator zehn Pfade aus --liste in Obsidian geöffnet und ohne Kante gefunden hat und eine Prüfrunde nur noch Umformulierungen findet.

Was dieses Skript nicht ist

Ein Fangnetz. Es zählt nach nachgebauten Regeln. Wie der Graph aussieht, zeigt nur Obsidian selbst.