Files
claude-werkzeuge/.vault-tools/linkcheck/ENTWURF-codeblock-erkennung.md
T
Pascal MichelandClaude Opus 5.5 fa1c26b407 Erste Fassung: Claude-Werkzeuge aus dem Vault
.vault-tools (ohne trading-plattform-pruefung und watermarks-remover),
.claude/agents, .claude/skills, .claude/workflows, .claude/settings.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 11:27:59 +02:00

131 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Entwurf: Codeblock-Erkennung bei der Link-Extraktion
Angelegt 2026-09-03. Entwurfsschritt nach [[AI/Regelwerk/pruefwerkzeuge|pruefwerkzeuge.md]], 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.