Behoben in 8.0.0-beta.30, Commit c261b8f. Verifiziert durch pytest (2129 passed, 3 skipped), docs verify und instructions verify lokal vor dem Publish, lint über den Demo-Korpus (keine Broken- und keine Wrapped-Findings) sowie CI auf dem publizierten Commit: Läufe 515 und 516, beide success.
Schlussprüfung (2026-10-03): kein docs/-Text, Contract oder Human-Doc durch die Änderung veraltet - keine docs/-Seite behandelt das Link-Parsing; die rename/rm-Records in tools/CONTRACT.md ("repoints every reference") gelten jetzt auch für umbrochene Links; EVALS.md bezieht harte Fehler aus HARD_ERROR_KEYS, die neue Kategorie ist darüber automatisch enthalten; migrate verify vergleicht Wikilink-Zahlen über count_wikilinks, das jetzt normalisiert. Keine Installationsanweisung berührt.
Befund (bis 8.0.0-beta.29)
Stand in einem [[Titel]]-Wikilink ein literaler Zeilenumbruch (plus Einrückung) zwischen [[ und ]] - typisch, wenn ein Agent Fließtext auf eine feste Spaltenbreite umbricht, ohne den Link als atomares Token zu behandeln -, las der Stack als Linkziel den Text mit dem Umbruch. Gegen den Tree geprüft (8.0.0-beta.29):
extract_wikilinks('siehe [[Foo Bar Target\n Pattern]] ...') -> {'Foo Bar Target\n Pattern'}
page_ops.LINK_RE -> 'Foo Bar Target\n Pattern'
body_references(b, 'Foo Bar Target Pattern') -> 0
retarget_body(b, 'Foo Bar Target Pattern', 'New') -> unverändert
Ursache: WIKILINK_RE = r"\[\[([^\]|#]+)" (tools/chemenu/kb_scan.py) und LINK_RE (tools/chemenu/commands/page_ops.py) schließen in der negierten Zeichenklasse keinen Zeilenumbruch aus; .strip() entfernte nur Whitespace an den Rändern. Der so gewonnene Titel traf nie eine Seite.
Folgen - drei, nicht nur die ursprünglich gemeldete:
lint meldete den Link unter "Broken Wikilinks" als fehlende Seite, mit dem Umbruch im ausgegebenen Titel - für eine Session nicht von einer echten toten Referenz zu unterscheiden.
rm zählte den umbrochenen Link nicht als Inbound-Referenz (inbound_pages → body_references) - die "still referenced"-Sperre griff nicht, die Seite wurde ohne --yes gelöscht.
rename repointete ihn nicht (retarget_body) - nach dem Rename war der Link wirklich tot.
Dazu falsche Orphan-/Most-Linked-Werte, weil die Kante im Link-Graph fehlte.
Entscheidungen (umgesetzt)
D1 - Normalisieren, nicht ausschließen. Ein Linkziel wird gelesen, indem jeder Whitespace-Lauf, der einen Zeilenumbruch enthält, zu genau einem Leerzeichen zusammenfällt (danach .strip()). Verworfen: \n/\r aus der Zeichenklasse nehmen, sodass ein umbrochener Link gar nicht matcht - der Link verschwände dann still aus Graph und Report, die Zielseite könnte als Orphan auftauchen, und rm/rename sähen ihn weiterhin nicht. Kein Sonderfall für Leerzeilen im Link: [[A\n\nB]] normalisiert ebenfalls zu A B und wird ebenso von D2 gemeldet - nichts davon ist still.
D2 - Eigene Lint-Kategorie "Wrapped Wikilinks", hart. Jeder Body-Wikilink, dessen Rohziel einen Zeilenumbruch enthält, wird unter dem Key wrapped_wikilinks gemeldet ([[Seite]] wraps a wikilink across lines - write it on one: [[Normalisierter Titel]]) - unabhängig davon, ob der normalisierte Titel existiert. "Broken Wikilinks" prüft den normalisierten Titel: ein umbrochener Link auf eine existierende Seite steht nur unter Wrapped, einer auf eine fehlende Seite unter beiden (zwei echte, getrennte Defekte).
Hart (HARD_ERROR_KEYS), weil (a) kb/CONTRACT.md § Titles are identifiers verlangt, dass ein Wikilink den Titel exakt trifft, und ein Renderer einen Link mit Zeilenumbruch nicht verlässlich als Link darstellt; (b) es für bestehende Instanzen nichts verschärft - genau diese Links waren vorher schon harte "Broken Wikilinks", die Lint-Ampel einer Instanz bleibt gleich, nur die Kategorie ist präziser. Kein Migration-Gating.
D3 - Eine Normalisierung, ein Ort.kb_scan.normalize_link_target() (neben WIKILINK_RE) wird von allen Body-Wikilink-Lesern benutzt: extract_wikilinks, count_wikilinks, die Index-Links in lint_core.run_lint, page_ops.retarget_body, page_ops.body_references. retarget_body schreibt den repointeten Link einzeilig - ein Rename repariert den Umbruch also nebenbei.
Bewusst außerhalb:
provenance.CITE_DEF_RE (Footnotes-Definitionen): wird von cite add erzeugt, nie von Hand umbrochen; Lint hat dafür eigene harte Checks.
strip_link_bullets (page_ops.py) / xref.py: matchen ganze See-also-Zeilen mit escaptem Titel; ein über zwei Zeilen umbrochener Bullet bleibt bei rm/xref remove stehen - er wird aber von D2 gemeldet und ist bei rm als Inbound sichtbar.
Ein Auto-Fix (lint --fix o.ä.): gibt es für keine Lint-Kategorie; die Korrektur ist ein Einzeiler beim Bearbeiten der Seite.
Ein über eine Zeile laufender Inline-Code-Span: markdown_code.strip_code_spans maskiert Spans bewusst nur innerhalb einer Zeile (Schutz davor, dass ein fehlender Backtick einen ganzen Absatz still maskiert). Ein umbrochener Link in so einem Span wird als Prosa gelesen und gemeldet; das bleibt so.
Geänderte Dateien
tools/chemenu/kb_scan.py - normalize_link_target(); extract_wikilinks/count_wikilinks benutzen sie; wrapped_wikilinks() liefert die normalisierten Ziele der umbrochenen Links eines Bodys (Code maskiert wie bisher).
tools/chemenu/lint_core.py - wrapped_wikilinks in run_lint, Report-Sektion "Wrapped Wikilinks" direkt nach "Broken Wikilinks", Key in HARD_ERROR_KEYS mit Begründungskommentar; Index-Links über dieselbe Funktion.
tools/chemenu/commands/lint.py - der lint-Command-Record nennt die Kategorie und hat eine eigene Note dazu; tools/CONTRACT.md per docs contract --apply regeneriert.
kb/CONTRACT.md § Titles are identifiers - Absatz: ein Wikilink ist ein Token und wird nie über Zeilen umbrochen; lint meldet ihn als Wrapped Wikilinks.
instructions/wiki-lint/SKILL.md - Aufzählung der mechanischen Findings ergänzt.
--minor, --impact low: neue Lint-Kategorie (neuer Report-Key, neue Sektion) plus Bugfix. Drop-in-Test: vorwärts reines Kopieren, kein Eingriff, keine Migration - die betroffenen Links waren schon vorher harte Fehler; rückwärts liest die alte Version nichts, was die neue geschrieben hat. Kein Grenzübertritt. Der Kandidat stand bereits auf 8.0.0-beta.N; der Bump ergab 8.0.0-beta.30.
Akzeptanzkriterien
extract_wikilinks/count_wikilinks liefern für [[Foo Bar\n Target]] und [[Foo Bar\r\nTarget|alias]] den Titel Foo Bar Target.
Gegen eine existierende Seite meldet lint einen umbrochenen Link nicht unter broken_links, aber unter wrapped_wikilinks; has_hard_errors (die Grundlage von lint --fail-on-error) ist wahr.
Ein umbrochener Link auf eine nicht existierende Seite steht unter broken_links mit dem normalisierten, einzeiligen Titel und zusätzlich unter wrapped_wikilinks.
Die Zielseite eines umbrochenen Links ist kein Orphan.
rm auf eine Seite, die nur über einen umbrochenen Link referenziert wird, verweigert ohne --yes und nennt die referenzierende Seite.
rename repointet einen umbrochenen Link; danach steht er einzeilig als [[Neu]] (Alias/Anker erhalten) im Body.
Ein umbrochener [[...]] innerhalb eines Fences erzeugt kein Finding. (Ursprünglich "Code-Spans/Fences" - eingeschränkt, siehe "Bewusst außerhalb".)
kb/CONTRACT.md § Titles are identifiers nennt die Regel; wiki-lint-Skill nennt die Kategorie.
pytest (2129 passed, 3 skipped), docs verify, instructions verify clean - lokal vor dem Publish. Demo-Korpus: keine Broken- und keine Wrapped-Findings.
CI grün auf dem publizierten Commit c261b8f (Läufe 515, 516).
Herkunft
Gemeldet aus einer privaten Instanz (Stack 6.1.0, 2026-09-17) nach einem wiki-ingest-Lauf, in dem zwei frisch angelegte Seiten denselben Wikilink identisch hart umbrochen hatten; dort von Hand auf eine Zeile gezogen. Der ursprüngliche Verdacht zur Ursache wurde am 2026-10-03 gegen den Tree bestätigt; die rm-/rename-Folgen wurden dabei zusätzlich gefunden.
## Ergebnis
Behoben in **8.0.0-beta.30**, Commit `c261b8f`. Verifiziert durch `pytest` (2129 passed, 3 skipped), `docs verify` und `instructions verify` lokal vor dem Publish, `lint` über den Demo-Korpus (keine Broken- und keine Wrapped-Findings) sowie CI auf dem publizierten Commit: Läufe [515](https://gitea.nehmer.net/torben/chemenu/actions/runs/515) und [516](https://gitea.nehmer.net/torben/chemenu/actions/runs/516), beide `success`.
Schlussprüfung (2026-10-03): kein `docs/`-Text, Contract oder Human-Doc durch die Änderung veraltet - keine `docs/`-Seite behandelt das Link-Parsing; die `rename`/`rm`-Records in `tools/CONTRACT.md` ("repoints every reference") gelten jetzt auch für umbrochene Links; EVALS.md bezieht harte Fehler aus `HARD_ERROR_KEYS`, die neue Kategorie ist darüber automatisch enthalten; `migrate verify` vergleicht Wikilink-Zahlen über `count_wikilinks`, das jetzt normalisiert. Keine Installationsanweisung berührt.
## Befund (bis 8.0.0-beta.29)
Stand in einem `[[Titel]]`-Wikilink ein literaler Zeilenumbruch (plus Einrückung) zwischen `[[` und `]]` - typisch, wenn ein Agent Fließtext auf eine feste Spaltenbreite umbricht, ohne den Link als atomares Token zu behandeln -, las der Stack als Linkziel den Text **mit** dem Umbruch. Gegen den Tree geprüft (8.0.0-beta.29):
```
extract_wikilinks('siehe [[Foo Bar Target\n Pattern]] ...') -> {'Foo Bar Target\n Pattern'}
page_ops.LINK_RE -> 'Foo Bar Target\n Pattern'
body_references(b, 'Foo Bar Target Pattern') -> 0
retarget_body(b, 'Foo Bar Target Pattern', 'New') -> unverändert
```
Ursache: `WIKILINK_RE = r"\[\[([^\]|#]+)"` (`tools/chemenu/kb_scan.py`) und `LINK_RE` (`tools/chemenu/commands/page_ops.py`) schließen in der negierten Zeichenklasse keinen Zeilenumbruch aus; `.strip()` entfernte nur Whitespace an den Rändern. Der so gewonnene Titel traf nie eine Seite.
Folgen - drei, nicht nur die ursprünglich gemeldete:
1. **`lint`** meldete den Link unter "Broken Wikilinks" als fehlende Seite, mit dem Umbruch im ausgegebenen Titel - für eine Session nicht von einer echten toten Referenz zu unterscheiden.
2. **`rm`** zählte den umbrochenen Link nicht als Inbound-Referenz (`inbound_pages` → `body_references`) - die "still referenced"-Sperre griff nicht, die Seite wurde ohne `--yes` gelöscht.
3. **`rename`** repointete ihn nicht (`retarget_body`) - nach dem Rename war der Link wirklich tot.
Dazu falsche Orphan-/Most-Linked-Werte, weil die Kante im Link-Graph fehlte.
## Entscheidungen (umgesetzt)
**D1 - Normalisieren, nicht ausschließen.** Ein Linkziel wird gelesen, indem jeder Whitespace-Lauf, der einen Zeilenumbruch enthält, zu genau einem Leerzeichen zusammenfällt (danach `.strip()`). Verworfen: `\n`/`\r` aus der Zeichenklasse nehmen, sodass ein umbrochener Link gar nicht matcht - der Link verschwände dann *still* aus Graph und Report, die Zielseite könnte als Orphan auftauchen, und `rm`/`rename` sähen ihn weiterhin nicht. Kein Sonderfall für Leerzeilen im Link: `[[A\n\nB]]` normalisiert ebenfalls zu `A B` und wird ebenso von D2 gemeldet - nichts davon ist still.
**D2 - Eigene Lint-Kategorie "Wrapped Wikilinks", hart.** Jeder Body-Wikilink, dessen Rohziel einen Zeilenumbruch enthält, wird unter dem Key `wrapped_wikilinks` gemeldet (`[[Seite]] wraps a wikilink across lines - write it on one: [[Normalisierter Titel]]`) - unabhängig davon, ob der normalisierte Titel existiert. "Broken Wikilinks" prüft den **normalisierten** Titel: ein umbrochener Link auf eine existierende Seite steht nur unter Wrapped, einer auf eine fehlende Seite unter beiden (zwei echte, getrennte Defekte).
Hart (`HARD_ERROR_KEYS`), weil (a) `kb/CONTRACT.md` § Titles are identifiers verlangt, dass ein Wikilink den Titel exakt trifft, und ein Renderer einen Link mit Zeilenumbruch nicht verlässlich als Link darstellt; (b) es für bestehende Instanzen nichts verschärft - genau diese Links waren vorher schon harte "Broken Wikilinks", die Lint-Ampel einer Instanz bleibt gleich, nur die Kategorie ist präziser. Kein Migration-Gating.
**D3 - Eine Normalisierung, ein Ort.** `kb_scan.normalize_link_target()` (neben `WIKILINK_RE`) wird von allen Body-Wikilink-Lesern benutzt: `extract_wikilinks`, `count_wikilinks`, die Index-Links in `lint_core.run_lint`, `page_ops.retarget_body`, `page_ops.body_references`. `retarget_body` schreibt den repointeten Link einzeilig - ein Rename repariert den Umbruch also nebenbei.
**Bewusst außerhalb:**
- `provenance.CITE_DEF_RE` (Footnotes-Definitionen): wird von `cite add` erzeugt, nie von Hand umbrochen; Lint hat dafür eigene harte Checks.
- `provenance.LEGACY_CITE_RE`: Pre-Migration-Marker, ohnehin harter Fehler.
- `strip_link_bullets` (`page_ops.py`) / `xref.py`: matchen ganze See-also-Zeilen mit escaptem Titel; ein über zwei Zeilen umbrochener Bullet bleibt bei `rm`/`xref remove` stehen - er wird aber von D2 gemeldet und ist bei `rm` als Inbound sichtbar.
- Ein Auto-Fix (`lint --fix` o.ä.): gibt es für keine Lint-Kategorie; die Korrektur ist ein Einzeiler beim Bearbeiten der Seite.
- Ein über eine Zeile laufender Inline-Code-Span: `markdown_code.strip_code_spans` maskiert Spans bewusst nur innerhalb einer Zeile (Schutz davor, dass ein fehlender Backtick einen ganzen Absatz still maskiert). Ein umbrochener Link in so einem Span wird als Prosa gelesen und gemeldet; das bleibt so.
## Geänderte Dateien
- `tools/chemenu/kb_scan.py` - `normalize_link_target()`; `extract_wikilinks`/`count_wikilinks` benutzen sie; `wrapped_wikilinks()` liefert die normalisierten Ziele der umbrochenen Links eines Bodys (Code maskiert wie bisher).
- `tools/chemenu/lint_core.py` - `wrapped_wikilinks` in `run_lint`, Report-Sektion "Wrapped Wikilinks" direkt nach "Broken Wikilinks", Key in `HARD_ERROR_KEYS` mit Begründungskommentar; Index-Links über dieselbe Funktion.
- `tools/chemenu/commands/page_ops.py` - `retarget_body`, `body_references` vergleichen normalisiert.
- `tools/chemenu/commands/lint.py` - der `lint`-Command-Record nennt die Kategorie und hat eine eigene Note dazu; `tools/CONTRACT.md` per `docs contract --apply` regeneriert.
- `kb/CONTRACT.md` § Titles are identifiers - Absatz: ein Wikilink ist ein Token und wird nie über Zeilen umbrochen; `lint` meldet ihn als *Wrapped Wikilinks*.
- `instructions/wiki-lint/SKILL.md` - Aufzählung der mechanischen Findings ergänzt.
- Tests: `tools/chemenu/tests/test_kb_scan.py`, `test_lint.py`, `test_page_ops.py`.
- `CHANGES.md` - Changeset im Kandidaten-Eintrag.
## Versionsteil
**`--minor`**, `--impact low`: neue Lint-Kategorie (neuer Report-Key, neue Sektion) plus Bugfix. Drop-in-Test: vorwärts reines Kopieren, kein Eingriff, keine Migration - die betroffenen Links waren schon vorher harte Fehler; rückwärts liest die alte Version nichts, was die neue geschrieben hat. Kein Grenzübertritt. Der Kandidat stand bereits auf 8.0.0-beta.N; der Bump ergab 8.0.0-beta.30.
## Akzeptanzkriterien
- [x] `extract_wikilinks`/`count_wikilinks` liefern für `[[Foo Bar\n Target]]` und `[[Foo Bar\r\nTarget|alias]]` den Titel `Foo Bar Target`.
- [x] Gegen eine existierende Seite meldet `lint` einen umbrochenen Link **nicht** unter `broken_links`, aber unter `wrapped_wikilinks`; `has_hard_errors` (die Grundlage von `lint --fail-on-error`) ist wahr.
- [x] Ein umbrochener Link auf eine **nicht** existierende Seite steht unter `broken_links` mit dem normalisierten, einzeiligen Titel und zusätzlich unter `wrapped_wikilinks`.
- [x] Die Zielseite eines umbrochenen Links ist kein Orphan.
- [x] `rm` auf eine Seite, die nur über einen umbrochenen Link referenziert wird, verweigert ohne `--yes` und nennt die referenzierende Seite.
- [x] `rename` repointet einen umbrochenen Link; danach steht er einzeilig als `[[Neu]]` (Alias/Anker erhalten) im Body.
- [x] Ein umbrochener `[[...]]` innerhalb eines Fences erzeugt kein Finding. (Ursprünglich "Code-Spans/Fences" - eingeschränkt, siehe "Bewusst außerhalb".)
- [x] `kb/CONTRACT.md` § Titles are identifiers nennt die Regel; `wiki-lint`-Skill nennt die Kategorie.
- [x] `pytest` (2129 passed, 3 skipped), `docs verify`, `instructions verify` clean - lokal vor dem Publish. Demo-Korpus: keine Broken- und keine Wrapped-Findings.
- [x] CI grün auf dem publizierten Commit `c261b8f` (Läufe 515, 516).
## Herkunft
Gemeldet aus einer privaten Instanz (Stack 6.1.0, 2026-09-17) nach einem `wiki-ingest`-Lauf, in dem zwei frisch angelegte Seiten denselben Wikilink identisch hart umbrochen hatten; dort von Hand auf eine Zeile gezogen. Der ursprüngliche Verdacht zur Ursache wurde am 2026-10-03 gegen den Tree bestätigt; die `rm`-/`rename`-Folgen wurden dabei zusätzlich gefunden.
torben
changed title from wikitool lint: ueber Zeilenumbruch gesplitteter Wikilink wird als fehlende Seite gemeldet statt als Syntaxfehler erkannt to Über Zeilenumbruch gesplitteter Wikilink: lint meldet fehlende Seite, rename/rm übersehen die Referenz2026-10-03 09:08:15 +00:00
Changelog: Verdacht gegen den Tree (8.0.0-beta.29) bestätigt, status/unconfirmed entfernt. Neu gefunden: auch rm (Inbound-Sperre) und rename (Repoint) übersehen umbrochene Links (page_ops.LINK_RE). Body neu als Spec: D1 normalisieren statt ausschließen, D2 eigene harte Lint-Kategorie wrapped_wikilinks, D3 eine Normalisierungsfunktion in kb_scan.py für alle Body-Leser; Bewusst-außerhalb-Liste, Dateien, Versionsteil --minor, Akzeptanzkriterien als prüfbare Eigenschaften. Labels: kind/defect → kind/build, prio/planned, size/M. Titel präzisiert.
**Changelog:** Verdacht gegen den Tree (8.0.0-beta.29) bestätigt, `status/unconfirmed` entfernt. Neu gefunden: auch `rm` (Inbound-Sperre) und `rename` (Repoint) übersehen umbrochene Links (`page_ops.LINK_RE`). Body neu als Spec: D1 normalisieren statt ausschließen, D2 eigene harte Lint-Kategorie `wrapped_wikilinks`, D3 eine Normalisierungsfunktion in `kb_scan.py` für alle Body-Leser; Bewusst-außerhalb-Liste, Dateien, Versionsteil `--minor`, Akzeptanzkriterien als prüfbare Eigenschaften. Labels: `kind/defect` → `kind/build`, `prio/planned`, `size/M`. Titel präzisiert.
Changelog: Gebaut und publiziert als 8.0.0-beta.30 (c261b8f), CI grün (Läufe 515, 516). Alle Akzeptanzkriterien abgehakt. Abweichung: das Code-Kriterium gilt nur für Fences - ein über die Zeile laufender Inline-Code-Span wird bewusst nicht maskiert (markdown_code.py), steht jetzt unter "Bewusst außerhalb". Zusätzlich angefasst: lint-Command-Record (Aufzählung + eigene Note), CHANGES.md. Neuer Abschnitt "Stand".
**Changelog:** Gebaut und publiziert als 8.0.0-beta.30 (`c261b8f`), CI grün (Läufe 515, 516). Alle Akzeptanzkriterien abgehakt. Abweichung: das Code-Kriterium gilt nur für Fences - ein über die Zeile laufender Inline-Code-Span wird bewusst nicht maskiert (`markdown_code.py`), steht jetzt unter "Bewusst außerhalb". Zusätzlich angefasst: `lint`-Command-Record (Aufzählung + eigene Note), `CHANGES.md`. Neuer Abschnitt "Stand".
Changelog: Body in Endfassung: "Stand" → "Ergebnis" mit allen Verifikationen und der Schlussprüfung (kein Dokument veraltet, keine Installationsanweisung berührt); Befund in Vergangenheitsform. Geschlossen.
**Changelog:** Body in Endfassung: "Stand" → "Ergebnis" mit allen Verifikationen und der Schlussprüfung (kein Dokument veraltet, keine Installationsanweisung berührt); Befund in Vergangenheitsform. Geschlossen.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Ergebnis
Behoben in 8.0.0-beta.30, Commit
c261b8f. Verifiziert durchpytest(2129 passed, 3 skipped),docs verifyundinstructions verifylokal vor dem Publish,lintüber den Demo-Korpus (keine Broken- und keine Wrapped-Findings) sowie CI auf dem publizierten Commit: Läufe 515 und 516, beidesuccess.Schlussprüfung (2026-10-03): kein
docs/-Text, Contract oder Human-Doc durch die Änderung veraltet - keinedocs/-Seite behandelt das Link-Parsing; dierename/rm-Records intools/CONTRACT.md("repoints every reference") gelten jetzt auch für umbrochene Links; EVALS.md bezieht harte Fehler ausHARD_ERROR_KEYS, die neue Kategorie ist darüber automatisch enthalten;migrate verifyvergleicht Wikilink-Zahlen übercount_wikilinks, das jetzt normalisiert. Keine Installationsanweisung berührt.Befund (bis 8.0.0-beta.29)
Stand in einem
[[Titel]]-Wikilink ein literaler Zeilenumbruch (plus Einrückung) zwischen[[und]]- typisch, wenn ein Agent Fließtext auf eine feste Spaltenbreite umbricht, ohne den Link als atomares Token zu behandeln -, las der Stack als Linkziel den Text mit dem Umbruch. Gegen den Tree geprüft (8.0.0-beta.29):Ursache:
WIKILINK_RE = r"\[\[([^\]|#]+)"(tools/chemenu/kb_scan.py) undLINK_RE(tools/chemenu/commands/page_ops.py) schließen in der negierten Zeichenklasse keinen Zeilenumbruch aus;.strip()entfernte nur Whitespace an den Rändern. Der so gewonnene Titel traf nie eine Seite.Folgen - drei, nicht nur die ursprünglich gemeldete:
lintmeldete den Link unter "Broken Wikilinks" als fehlende Seite, mit dem Umbruch im ausgegebenen Titel - für eine Session nicht von einer echten toten Referenz zu unterscheiden.rmzählte den umbrochenen Link nicht als Inbound-Referenz (inbound_pages→body_references) - die "still referenced"-Sperre griff nicht, die Seite wurde ohne--yesgelöscht.renamerepointete ihn nicht (retarget_body) - nach dem Rename war der Link wirklich tot.Dazu falsche Orphan-/Most-Linked-Werte, weil die Kante im Link-Graph fehlte.
Entscheidungen (umgesetzt)
D1 - Normalisieren, nicht ausschließen. Ein Linkziel wird gelesen, indem jeder Whitespace-Lauf, der einen Zeilenumbruch enthält, zu genau einem Leerzeichen zusammenfällt (danach
.strip()). Verworfen:\n/\raus der Zeichenklasse nehmen, sodass ein umbrochener Link gar nicht matcht - der Link verschwände dann still aus Graph und Report, die Zielseite könnte als Orphan auftauchen, undrm/renamesähen ihn weiterhin nicht. Kein Sonderfall für Leerzeilen im Link:[[A\n\nB]]normalisiert ebenfalls zuA Bund wird ebenso von D2 gemeldet - nichts davon ist still.D2 - Eigene Lint-Kategorie "Wrapped Wikilinks", hart. Jeder Body-Wikilink, dessen Rohziel einen Zeilenumbruch enthält, wird unter dem Key
wrapped_wikilinksgemeldet ([[Seite]] wraps a wikilink across lines - write it on one: [[Normalisierter Titel]]) - unabhängig davon, ob der normalisierte Titel existiert. "Broken Wikilinks" prüft den normalisierten Titel: ein umbrochener Link auf eine existierende Seite steht nur unter Wrapped, einer auf eine fehlende Seite unter beiden (zwei echte, getrennte Defekte).Hart (
HARD_ERROR_KEYS), weil (a)kb/CONTRACT.md§ Titles are identifiers verlangt, dass ein Wikilink den Titel exakt trifft, und ein Renderer einen Link mit Zeilenumbruch nicht verlässlich als Link darstellt; (b) es für bestehende Instanzen nichts verschärft - genau diese Links waren vorher schon harte "Broken Wikilinks", die Lint-Ampel einer Instanz bleibt gleich, nur die Kategorie ist präziser. Kein Migration-Gating.D3 - Eine Normalisierung, ein Ort.
kb_scan.normalize_link_target()(nebenWIKILINK_RE) wird von allen Body-Wikilink-Lesern benutzt:extract_wikilinks,count_wikilinks, die Index-Links inlint_core.run_lint,page_ops.retarget_body,page_ops.body_references.retarget_bodyschreibt den repointeten Link einzeilig - ein Rename repariert den Umbruch also nebenbei.Bewusst außerhalb:
provenance.CITE_DEF_RE(Footnotes-Definitionen): wird voncite adderzeugt, nie von Hand umbrochen; Lint hat dafür eigene harte Checks.provenance.LEGACY_CITE_RE: Pre-Migration-Marker, ohnehin harter Fehler.strip_link_bullets(page_ops.py) /xref.py: matchen ganze See-also-Zeilen mit escaptem Titel; ein über zwei Zeilen umbrochener Bullet bleibt beirm/xref removestehen - er wird aber von D2 gemeldet und ist beirmals Inbound sichtbar.lint --fixo.ä.): gibt es für keine Lint-Kategorie; die Korrektur ist ein Einzeiler beim Bearbeiten der Seite.markdown_code.strip_code_spansmaskiert Spans bewusst nur innerhalb einer Zeile (Schutz davor, dass ein fehlender Backtick einen ganzen Absatz still maskiert). Ein umbrochener Link in so einem Span wird als Prosa gelesen und gemeldet; das bleibt so.Geänderte Dateien
tools/chemenu/kb_scan.py-normalize_link_target();extract_wikilinks/count_wikilinksbenutzen sie;wrapped_wikilinks()liefert die normalisierten Ziele der umbrochenen Links eines Bodys (Code maskiert wie bisher).tools/chemenu/lint_core.py-wrapped_wikilinksinrun_lint, Report-Sektion "Wrapped Wikilinks" direkt nach "Broken Wikilinks", Key inHARD_ERROR_KEYSmit Begründungskommentar; Index-Links über dieselbe Funktion.tools/chemenu/commands/page_ops.py-retarget_body,body_referencesvergleichen normalisiert.tools/chemenu/commands/lint.py- derlint-Command-Record nennt die Kategorie und hat eine eigene Note dazu;tools/CONTRACT.mdperdocs contract --applyregeneriert.kb/CONTRACT.md§ Titles are identifiers - Absatz: ein Wikilink ist ein Token und wird nie über Zeilen umbrochen;lintmeldet ihn als Wrapped Wikilinks.instructions/wiki-lint/SKILL.md- Aufzählung der mechanischen Findings ergänzt.tools/chemenu/tests/test_kb_scan.py,test_lint.py,test_page_ops.py.CHANGES.md- Changeset im Kandidaten-Eintrag.Versionsteil
--minor,--impact low: neue Lint-Kategorie (neuer Report-Key, neue Sektion) plus Bugfix. Drop-in-Test: vorwärts reines Kopieren, kein Eingriff, keine Migration - die betroffenen Links waren schon vorher harte Fehler; rückwärts liest die alte Version nichts, was die neue geschrieben hat. Kein Grenzübertritt. Der Kandidat stand bereits auf 8.0.0-beta.N; der Bump ergab 8.0.0-beta.30.Akzeptanzkriterien
extract_wikilinks/count_wikilinksliefern für[[Foo Bar\n Target]]und[[Foo Bar\r\nTarget|alias]]den TitelFoo Bar Target.linteinen umbrochenen Link nicht unterbroken_links, aber unterwrapped_wikilinks;has_hard_errors(die Grundlage vonlint --fail-on-error) ist wahr.broken_linksmit dem normalisierten, einzeiligen Titel und zusätzlich unterwrapped_wikilinks.rmauf eine Seite, die nur über einen umbrochenen Link referenziert wird, verweigert ohne--yesund nennt die referenzierende Seite.renamerepointet einen umbrochenen Link; danach steht er einzeilig als[[Neu]](Alias/Anker erhalten) im Body.[[...]]innerhalb eines Fences erzeugt kein Finding. (Ursprünglich "Code-Spans/Fences" - eingeschränkt, siehe "Bewusst außerhalb".)kb/CONTRACT.md§ Titles are identifiers nennt die Regel;wiki-lint-Skill nennt die Kategorie.pytest(2129 passed, 3 skipped),docs verify,instructions verifyclean - lokal vor dem Publish. Demo-Korpus: keine Broken- und keine Wrapped-Findings.c261b8f(Läufe 515, 516).Herkunft
Gemeldet aus einer privaten Instanz (Stack 6.1.0, 2026-09-17) nach einem
wiki-ingest-Lauf, in dem zwei frisch angelegte Seiten denselben Wikilink identisch hart umbrochen hatten; dort von Hand auf eine Zeile gezogen. Der ursprüngliche Verdacht zur Ursache wurde am 2026-10-03 gegen den Tree bestätigt; dierm-/rename-Folgen wurden dabei zusätzlich gefunden.wikitool lint: ueber Zeilenumbruch gesplitteter Wikilink wird als fehlende Seite gemeldet statt als Syntaxfehler erkanntto Über Zeilenumbruch gesplitteter Wikilink: lint meldet fehlende Seite, rename/rm übersehen die ReferenzChangelog: Verdacht gegen den Tree (8.0.0-beta.29) bestätigt,
status/unconfirmedentfernt. Neu gefunden: auchrm(Inbound-Sperre) undrename(Repoint) übersehen umbrochene Links (page_ops.LINK_RE). Body neu als Spec: D1 normalisieren statt ausschließen, D2 eigene harte Lint-Kategoriewrapped_wikilinks, D3 eine Normalisierungsfunktion inkb_scan.pyfür alle Body-Leser; Bewusst-außerhalb-Liste, Dateien, Versionsteil--minor, Akzeptanzkriterien als prüfbare Eigenschaften. Labels:kind/defect→kind/build,prio/planned,size/M. Titel präzisiert.Changelog: Gebaut und publiziert als 8.0.0-beta.30 (
c261b8f), CI grün (Läufe 515, 516). Alle Akzeptanzkriterien abgehakt. Abweichung: das Code-Kriterium gilt nur für Fences - ein über die Zeile laufender Inline-Code-Span wird bewusst nicht maskiert (markdown_code.py), steht jetzt unter "Bewusst außerhalb". Zusätzlich angefasst:lint-Command-Record (Aufzählung + eigene Note),CHANGES.md. Neuer Abschnitt "Stand".Changelog: Body in Endfassung: "Stand" → "Ergebnis" mit allen Verifikationen und der Schlussprüfung (kein Dokument veraltet, keine Installationsanweisung berührt); Befund in Vergangenheitsform. Geschlossen.