Files
claude-regelwerk/.vault-tools/linkcheck/ENTWURF-codeblock-erkennung.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

11 KiB
Raw Blame History

Entwurf: Codeblock-Erkennung bei der Link-Extraktion

Angelegt 2026-09-03. Entwurfsschritt nach AI/Regelwerk/pruefwerkzeuge, Abschnitt 1 — geschrieben vor der ersten Codezeile.

Anlass

vault_doctor.py meldete 92 tote Wikilinks. Die Stichprobe zeigt fast ausschließlich Fehlalarme:

  • [[ $EUID -eq 0 ]] — Bash-Test aus einem Codeblock
  • [[:space:]] — POSIX-Zeichenklasse aus einem Regex
  • [[bild.png]], [[Ziel]], [[Notiz]] — Platzhalter in der Dokumentation des Bookstack-Upload-Skills, das Wikilinks verarbeitet
  • [[1, 2]], [[float]], [[A]] — Typangaben und Indizes aus Codebeispielen

Ursache: _WIKILINK_RE (Zeile 61 in vaultlib.py) läuft über den gesamten Text. Das Wort fence kommt in der Datei kein einziges Mal vor.

Ein Prüfwerkzeug, dessen Befunde zu vier Fünfteln Rauschen sind, wird nicht mehr gelesen. Das ist der eigentliche Schaden, nicht die 92.

Entscheidungsliste

# Frage Antwort Herkunft
1 Was ist ein Wikilink? [[Ziel]], [[Ziel|Alias]], [[Ziel#Überschrift]], [[Ziel^Block]] Quelle — _WIKILINK_RE, vaultlib.py Zeile 61
2 Wo darf keiner erkannt werden? In eingezäunten Codeblöcken (```, ~~~) und in Code-Spans (Backticks) Quelle — CommonMark: Inhalt von Code wird nicht als Markup interpretiert; Obsidian rendert dort keine Links
3 Zählt das YAML-Frontmatter mit? Nein, ist bereits abgetrennt Quelle — vaultlib.py Zeile 117: body = text[m.end():]
4 Eingerückte Codeblöcke (vier Leerzeichen)? Nicht behandelt. Der Vault verwendet durchgängig eingezäunte Blöcke; eine Vier-Leerzeichen-Regel würde eingerückte Listenpunkte mit legitimen Links verschlucken Projektvorgabe, als Lücke im Kopfkommentar benannt
5 Ungeschlossener Zaun am Dateiende? Gilt bis Dateiende als Code Projektvorgabe — die konservative Wahl: lieber einen echten Link übersehen als einen Fehlalarm erzeugen
6 Auch Überschriften betroffen? Ja, gleicher Fehler — extract_headings liest # Kommentar aus Bash-Blöcken als Überschrift. In dieser Runde nicht geändert, weil es den Index-Inhalt verschiebt und einen eigenen Nachweis braucht Befund, benannt statt stillschweigend miterledigt
7 Welche Zeile schließt einen Zaun? Nur eine Zeile aus demselben Zaunzeichen, mindestens so lang wie der Öffner, bis zu drei LEERZEICHEN eingerückt (kein Tab), danach nur Leerzeichen oder Tab. Eine Zeile mit Info-String (drei Backticks und bash) im Block ist Inhalt Quelle — Fundstellen im Abschnitt „Nachbesserung 2026-09-13" und „Nachbesserung Runde 2"

Keine Zeile bleibt auf „Annahme" stehen.

Angriffsliste

Je Fall ein eigener Durchlauf, Grundlinie zuerst.

# Fall Erwartung
1 leerer Text keine Links, kein Absturz
2 nur Fließtext mit einem Link Link bleibt
3 Link vor und nach einem Zaun beide bleiben — Reihenfolge: erste und letzte Position
4 ```bash mit Link darin Link weg
5 ~~~ als Zaunzeichen Link weg
6 Zaun ohne Abschluss, Link danach Link weg
7 Inline-Code `[[x]]` Link weg
8 doppelte Backticks Link weg
9 einzelner Backtick ohne Partner Link bleibt — darf nicht den Rest verschlucken
10 vier Backticks als Zaun Link weg
11 eingerückter Zaun in einer Liste Link weg
12 Link unmittelbar an der Zaunzeile bleibt
13 echter Bestandsfall [[ $EUID -eq 0 ]] in einem Block kein Link
14 echter Bestandsfall [[:space:]] in Inline-Code kein Link
15 escaptes Pipe in einer Tabellenzelle [[Ziel|Alias]] Ziel ohne Backslash
16 escaptes Pipe ohne Alias Ziel ohne Backslash
17 Dreier-Zaun mit Info-String innerhalb eines Vierer-Zauns, darin [[:space:]] kein Link aus dem Block, Link danach bleibt
18 Dreier-Schließzaun in einem Vierer-Zaun schließt nicht
19 Vierer-Zaun schließt Vierer-Zaun schließt, Link danach bleibt
20 Zeile mit drei Backticks und bash in einem Dreier-Block schließt nicht
21 Schließzaun mit Leerzeichen und Tab dahinter schließt — neuer Ausdruck, eigener Fall
22 Schließzaun mit CR am Zeilenende schließt — neuer Ausdruck, eigener Fall
23 anderes Zaunzeichen (Backtick-Öffner, Tilde-Schließzeile) schließt nicht — eigener Fall für „gleiches Zaunzeichen"
24 Schließzaun mit vier Leerzeichen davor schließt nicht — Einrückungsgrenze
25 Schließzaun mit Tab davor schließt nicht — Tab zählt nicht als Einrückung, siehe „Nachbesserung Runde 2"

Nachbesserungen aus der ersten Messung

Der Lauf über den Vault brachte nach dem Codeblock-Filter noch 18 Befunde — wieder Fehlalarme, zwei neuer Klassen. Beide bekamen nach Abschnitt 5 des Regelwerks ihren eigenen Fall:

  • Escaptes Pipe. In Obsidian-Tabellenzellen muss das Pipe eines Alias als \| geschrieben werden. _WIKILINK_RE schneidet am Pipe, lässt den Backslash aber stehen — das Ziel hieß dann Pfad\ und fand nichts. Fälle 15 und 16.
  • Bildeinbettungen. ![[Pasted image 123.png]] ist ein gültiger Link auf eine Nicht-Markdown-Datei. Die Nachschlagemenge von vault_doctor.py enthält nur .md. Behoben durch eine zweite Menge aus allen übrigen Dateien.

Ergebnis: 92 gemeldete, 0 echte tote Links.

Nachbesserung 2026-09-13: Schließzaun nach CommonMark

Anlass. Der Linkcheck meldete am 2026-09-13 einen toten Wikilink [[:space:]] in Code Projekte/active - Logging/docs/plans/2026-09-08-a-betriebsmodelle/task-09-gate.md. Die Datei öffnet in Zeile 356 einen Block mit vier Backticks und zeigt darin ab Zeile 406 ein eingerücktes Beispiel mit drei Backticks und bash. Die alte Regel schloss den äußeren Block schon an Zeile 406, weil jede Zaunzeile aus demselben Zeichen genügte. Zeile 407 mit der POSIX-Zeichenklasse lag damit im Fließtext.

Regel. Ein Block schließt nur mit einer Zeile, die

  • aus demselben Zaunzeichen besteht wie der Öffner (Backtick oder Tilde),
  • mindestens so viele Zeichen hat wie der Öffner,
  • nach den Zaunzeichen nur Leerzeichen oder Tab trägt, also keinen Info-String.

Die Einrückung von bis zu drei Zeichen übernimmt der Schließzaun vom Öffner (\s{0,3}). Ein CR am Zeilenende ist zugelassen, damit eine Eingabe mit CRLF nicht an jedem Schließzaun vorbeiläuft.

Herkunft.

Aussage Fundstelle
dasselbe Zeichen markdown-it-py 4.2.0, markdown_it/rules_block/fence.py Zeile 106
mindestens so lang ebenda Zeilen 42–43, closing_len >= opening_len
danach nur Leerraum ebenda Zeilen 119–123; Leerraum heißt Leerzeichen oder Tab laut markdown_it/common/utils.py Zeilen 167–169

Gelesen am 2026-09-13 unter Code Projekte/active - Agentic Cockpit/.venv/Lib/site-packages/markdown_it/. Die Bibliothek nennt ihre Regel selbst CommonMark-konform (fence.py Zeile 145). Referenz der Regel ist die CommonMark-Spezifikation 0.31.2, Abschnitt 4.5 Fenced code blocks, https://spec.commonmark.org/0.31.2/#fenced-code-blocks. Den Wortlaut der Spezifikation hat beim Planen niemand gezogen, der Planlauf hatte keinen Netzzugriff. Belegt ist die Regel an der Implementierung. Der Abgleich mit dem Spezifikationstext ist offen.

Benannte Lücke. Ein Öffner mit Backtick im Info-String ist nach markdown-it-py kein Zaun (fence.py Zeilen 79–80). Hier gilt er weiter als Zaun. Diese Runde ändert nur den Schließzaun. Die Öffnerregel verschiebt, welche Zeilen überhaupt als Code gelten, und braucht einen eigenen Lauf über den Vault.

Was schiefgehen kann. Hat bisher eine Zeile mit Info-String einen Block versehentlich geschlossen, läuft der Block jetzt weiter, notfalls bis zum Dateiende. Wikilinks dahinter prüft der Linkcheck dann nicht mehr. Deshalb gehört zur Abnahme ein Vergleichslauf alt gegen neu über den ganzen Vault.

Nachbesserung Runde 2: gleiches Zaunzeichen und Einrückung ohne Tab

Anlass. Die erste Prüfrunde nach der Nachbesserung 2026-09-13 fand zwei Lücken in der Angriffsliste, keine im Verhalten der zwei damals geänderten Bedingungen — aber beide Bedingungen hatten bis dahin keinen eigenen Fall:

  • Die Bedingung „gleiches Zaunzeichen" (s.group(1)[0] == zaun[0]) hatte keinen Fall, der einen Backtick-Öffner gegen eine Tilde-Schließzeile prüft. Fall 23.
  • Die Einrückung vor dem Schließzaun ließ \s{0,3} zu, also auch einen Tab. Ein Tab zählt bei markdown-it-py als vier Spalten (state_block.py Zeilen 79–80 für die Tab-Rechnung, 257–261 für den Vergleich >= 4) und macht die Zeile zu eingerücktem Code — sie schließt dort nicht. Fall 25 belegt das: Gegen die alte Fassung mit \s{0,3} schlägt er fehl (rot), gegen [ ]{0,3} besteht er.

Korrektur. _SCHLUSS_RE in linkcheck.py verlangt jetzt ^[ ]{0,3} statt ^\s{0,3} vor dem Zaunzeichen — nur Leerzeichen, kein Tab. Die Bedingung „gleiches Zaunzeichen" selbst war schon vorher im Code (s.group(1)[0] == zaun[0]), nur ungeprüft; Fall 23 sichert sie jetzt ab, Fall 24 die Grenze von drei Einrückungszeichen.

Herkunft. Wie in der Nachbesserung 2026-09-13, markdown_it/rules_block/fence.py und zusätzlich markdown_it/rules_block/state_block.py Zeilen 257–261 für die Tab-als-vier-Spalten-Regel, gelesen unter Code Projekte/active - Agentic Cockpit/.venv/Lib/site-packages/markdown_it/ am 2026-09-13.

Nachbesserung nach Prüfrunde 2: Schließregel wieder symmetrisch

Anlass. Die zweite Prüfrunde hat gezeigt, dass ^[ ]{0,3} vor dem Schließzaun eine Rückentwicklung war. In einem Listenpunkt, der mit Tab eingerückt ist, öffnet linkcheck.py den Block an der Tab-Zeile (die Öffnerregel erlaubt \s{0,3}), schließt ihn aber nie mehr, und alles bis zum Dateiende gilt als Code. markdown-it-py zieht dort die Einrückung des Listeninhalts ab und schließt den Block. Belegt mit markdown-it-py 4.2.0 (MarkdownIt('commonmark')) gegen beide Fassungen.

Korrektur. _SCHLUSS_RE verlangt wieder ^\s{0,3}, symmetrisch zum Öffner. Fall 25 erwartet jetzt, dass ein Tab-Zaun schließt; Fall 26 prüft den Listenfall.

Benannte Lücke. Auf oberster Ebene zählt markdown-it-py den Tab als vier Spalten und liest die Zeile als eingerückten Code; dort schließt ein Tab-Zaun nicht, linkcheck.py schließt ihn trotzdem. Listen-Einrückung bildet das Werkzeug nicht nach, deshalb bleibt es bei der einfachen, symmetrischen Regel. Im Vault gab es am 2026-09-13 keine Datei, die das betrifft (Messung des Prüfers).

Die Regelzeile oben („Die Einrückung von bis zu drei Zeichen übernimmt der Schließzaun vom Öffner") gilt damit wieder uneingeschränkt.

Absturzfestigkeit

extract_links wird von build_index.py über den ganzen Vault gerufen. Ein Absturz legt den Index still. Die Hilfsfunktion arbeitet deshalb rein zeilenweise über Zeichenketten, ohne Indexarithmetik über Zeilengrenzen, und ersetzt Codeinhalt durch Leerzeichen statt ihn zu löschen — Länge und Zeilenzahl bleiben erhalten, nichts kann über eine entfernte Stelle hinweg zusammenwachsen.

Abbruchbedingung

Schluss ist, wenn ein Lauf über den Vault die Fehlalarme aus der Stichprobe nicht mehr meldet und alle Angriffsfälle aus faelle.py grün sind. Was danach an toten Links übrig bleibt, ist eine Aufräumliste — kein Prüfauftrag mehr.

Was dieses Skript nicht ist

Ein Fangnetz gegen offensichtliche Fehler. Es belegt nicht, dass alle Wikilinks im Vault stimmen — nur, dass die gemeldeten nicht aus Codeblöcken stammen.