--- type: plan-task title: "Task 06 — Nachbesserung: Bericht entschärft Obsidian-Syntax aus Stimmen und Nachprüfung" status: draft summary: "quorum.js schickt jeden Freitext aus Stimmen, Zusammenführen und Nachprüfung durch eine neue Funktion klartext(), damit zitierte Wikilinks, Einbettungen und Tags im Bericht lesbarer Klartext bleiben; Hub-Zeile und Frontmatter bleiben unverändert." tags: [plan-task, vault-tools, codeworkflow] created: 2026-10-05 updated: 2026-10-05 modell: sonnet effort: low modell_begruendung: "Abschrift per plan_materialisiere.py mit genannten Zieldateien und Verboten, ohne Fließtext und ohne typografische Zeichen in Codeblöcken; sonnet mit effort low statt haiku, weil der Mutantenlauf länger dauert als ein Aufruf des Bash-Werkzeugs und im Hintergrund gestartet und abgefragt werden muss." --- Übersicht: [00-uebersicht.md](00-uebersicht.md) # Task 06 — Nachbesserung: Bericht entschärft Obsidian-Syntax aus Stimmen und Nachprüfung Die globalen Vorgaben stehen in `00-uebersicht.md` im selben Ordner, Abschnitt „Globale Vorgaben“. Arbeitsverzeichnis ist `D:/Claude`. **Voraussetzung:** Task 01 bis 05 sind angewendet und committet. Dieser Task ist ein Delta gegen den heutigen Baum; die Taskdateien 01 bis 05 bleiben unverändert. ## Anlass - `quorum.js` übernimmt Freitext aus Stimmen, Zusammenführen und Nachprüfung ungefiltert in `bericht_md`. - Zitiert eine Stimme Obsidian-Syntax, wird sie im Vault lebendig. Beleg: Der erste echte Bericht enthält zwei tote Wikilinks, einen unter „Nicht prüfbar“, einen in einer Befundaussage; der Linkcheck meldet beide, im Graphen entstehen Geisterknoten. - Ein zitierter Tag wie `#needs-review` würde den Bericht als Review-Notiz zählen lassen, eine Einbettung wie `![[c]]` eine fremde Datei einbetten. - Der erste echte Bericht selbst wird hier nicht angefasst (offener Punkt unten). ## Entscheidung: Backslash vor Klammer und Raute Eine neue Funktion `klartext()` setzt einen Backslash vor jede öffnende eckige Klammer in einer Folge von zwei oder mehr und vor jede Raute, die einen Tag beginnen könnte. Die Leseansicht zeigt den Text wie zitiert. | Zitat der Stimme | im Quelltext des Berichts | Regel | |---|---|---| | `[[a/b]]` | `\[\[a/b]]` | Klammerfolge | | `![[c]]` | `!\[\[c]]` | Klammerfolge, das `!` bleibt | | `#needs-review` | `\#needs-review` | Raute am Wortanfang vor einem Zeichen, das kein Leerraum und keine Raute ist | | `[[#x]]` | `\[\[\#x]]` | beides; vor der Raute steht eine Klammer, kein Buchstabe | | `x = 1 # Kommentar` | unverändert | Raute vor Leerzeichen | | `C#`, `seite.md#abschnitt` | unverändert | am Wortende bzw. Buchstabe davor | Warum diese Form: - Backslash ist das Markdown-Escape für `[` und `#` (CommonMark erlaubt es für jedes ASCII-Satzzeichen). Es wirkt je Zeichen, braucht keine Grenzen eines Links und ändert bei zweiter Anwendung nichts mehr. - Backticks um das Zitat scheiden aus: Freitext trägt selbst Backticks, dann bricht die Code-Spanne, und Anfang und Ende jedes Links müssten erkannt werden. - Gelesen im Quelltext der Vault-Werkzeuge: `linkcheck.py` und `waisen.py` sehen in `\[\[x]]` keinen Link (Befund des Koordinators). `sitzungsstart.py` zählt `#needs-review` nur nach Leerraum oder am Zeilenanfang (Regex `(? ({ datei: 'src/a.py', stelle: Q_ZITATE, schwere, aussage: Q_ZITATE, beleg: Q_ZITATE }) // Was Obsidian als Wikilink, Einbettung oder Tag lesen könnte. Der Tag zählt hier schon hinter // jedem Zeichen außer Buchstabe, Ziffer, Unterstrich und Backslash, nicht erst nach Leerraum. function qObsidian(text) { const funde = [] if (/(^|[^!])\[\[/.test(text)) funde.push('Wikilink') if (text.includes('![[')) funde.push('Einbettung') if (/(^|[^\p{L}\p{N}_\\])#[^\s#]/u.test(text)) funde.push('Tag') return funde } const qSauber = (soll, text) => { const f = qObsidian(text); soll(f.length === 0, 'Obsidian-Syntax im Bericht: ' + f.join(', ')) } fall('quorum', 'Klartext im Bericht: Stelle, Aussage und Beleg einer Gruppe ohne Wikilink, Einbettung und Tag, README-Link bleibt', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await QR(Q_ARGS, { stimmen: { opus: [qZ('blockierend')], sonnet: [qZ('blockierend')], codex: [] } }) const md = (e && e.bericht_md) || '' qSauber(soll, md) for (const z of [ '### G1: blockierend, Mehrheit (Opus, Sonnet)\n\n`src/a.py`, Stelle ' + Q_KLAR + '\n', '- O1 (Opus, blockierend, Stelle ' + Q_KLAR + '): ' + Q_KLAR + '\n Beleg: ' + Q_KLAR + '\n', '\n\nProjekt: [README](../../README.md)\n\n', ]) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z)) return fehler }) fall('quorum', 'Klartext im Bericht: Fundstelle und Begründung der Nachprüfung ohne Wikilink, Einbettung und Tag', async () => { const { fehler, soll } = pruefe() const stimmen = { opus: [qB('src/a.py', '10', 'blockierend')], sonnet: [qB('src/b.py', '5', 'hinweis')], codex: [] } const urteile = pr => ({ urteile: [...pr.matchAll(/^Befund ([A-Z]\d+):$/gm)].map(m => ({ id: m[1], urteil: 'bestaetigt', fundstelle: Q_ZITATE, begruendung: Q_ZITATE })) }) const { ergebnis: e } = await QR(Q_ARGS, { stimmen, urteile }) const md = (e && e.bericht_md) || '' qSauber(soll, md) const z = '\nNachprüfung: bestaetigt, ' + Q_KLAR + '. ' + Q_KLAR + '\n' soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z)) return fehler }) fall('quorum', 'Klartext im Bericht: Unbelegt und Nicht prüfbar ohne Wikilink, Einbettung und Tag', async () => { const { fehler, soll } = pruefe() const stimmen = { opus: [{ ...qZ('wichtig'), beleg: '' }], sonnet: [qB('src/b.py', '5', 'hinweis')], codex: [] } const { ergebnis: e } = await QR(Q_ARGS, { stimmen, nichtPruefbar: { sonnet: [Q_ZITATE] } }) const md = (e && e.bericht_md) || '' qSauber(soll, md) for (const z of [ '## Unbelegt\n\n- O1 (Opus, wichtig) `src/a.py`, Stelle ' + Q_KLAR + ': ' + Q_KLAR + '\n', '## Nicht prüfbar\n\n- Sonnet: ' + Q_KLAR + '\n', ]) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z)) return fehler }) fall('quorum', 'Klartext im Bericht: Hinweise des Laufs und Grund der Codex-Stimme ohne Wikilink, Einbettung und Tag', async () => { const { fehler, soll } = pruefe() const stimmen = { opus: [qB('src/a.py', '10', 'hinweis')], sonnet: [qB('src/b.py', '5', 'hinweis')] } const k = { stimmen, paket: { hinweise: [Q_ZITATE] }, codexEnde: { job_status: Q_ZITATE }, gruppen: () => ({ gruppen: [{ ids: [Q_ZITATE], begruendung: 'zitiert' }] }) } const { ergebnis: e } = await QR(Q_ARGS, k) const md = (e && e.bericht_md) || '' qSauber(soll, md) for (const z of [ '| Codex | nein: Codex-Auftrag endete mit ' + Q_KLAR + ' |', '## Hinweise des Laufs\n\n- ' + Q_KLAR + '\n', '\n- Codex ohne gültige Stimme (Codex-Auftrag endete mit ' + Q_KLAR + '); Haiku ist eingesprungen\n', '\n- Zusammenführen: unbekannte ID ' + Q_KLAR + ' verworfen\n', ]) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z)) return fehler }) fall('quorum', 'Klartext im Bericht eines Kundendokuments: Hub-Zeile bleibt Wikilink, Frontmatter bleibt, zitierte Syntax nicht', async () => { const { fehler, soll } = pruefe() const K = 'D:/Claude/IT Workspace/Beispiel/abgabe' const HUB = 'Übersicht: [[IT Workspace/Beispiel/abgabe/00 Übersicht|00 Übersicht.md]]' const args = { anlass: 'kundendokument', ordner: K, dateien: ['kapitel/01.md'], uebersicht: '00 Übersicht.md' } const paket = { anlass: 'kundendokument', art: 'doku', wurzel: K, basis: 'erstes Quorum', stand: 'sha256:' + 'c'.repeat(64), vorher: null, nummer: 1 } const { ergebnis: e } = await QR(args, { paket, stimmen: { opus: [qZ('blockierend')], sonnet: [qZ('blockierend')], codex: [] } }) const md = (e && e.bericht_md) || '' qSauber(soll, md.replace(HUB, '')) soll(md.includes('\n\n' + HUB + '\n\n') && md.split('[[').length === 2, 'Hub-Zeile nicht genau einmal als Wikilink: ' + JSON.stringify(md.split('\n').filter(x => x.includes('Übersicht')))) for (const z of ['---\ntype: note\n', '\ntags: [quorum]\n', '- O1 (Opus, blockierend, Stelle ' + Q_KLAR + '): ' + Q_KLAR + '\n']) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z)) return fehler }) // ---------------------------------------------------------------- Lauf ``` ### Mutanten in `.vault-tools/workflow-pruefung/mutanten_workflows.py` Drei bestehende Mutanten zielen auf Stellen, die jetzt `klartext()` tragen; ohne Anpassung meldet der Lauf sie als veraltet. Aus: ``` ("quorum", "Stelle fehlt in der Befundzeile", ", ${b.schwere}, Stelle ${b.stelle}): ${b.aussage}", ", ${b.schwere}): ${b.aussage}"), ``` wird: ```python ("quorum", "Stelle fehlt in der Befundzeile", ", ${b.schwere}, Stelle ${klartext(b.stelle)}): ${klartext(b.aussage)}", ", ${b.schwere}): ${klartext(b.aussage)}"), ``` Aus: ``` ("quorum", "Nachpruefung ohne Fundstelle", "${g.nachpruefung.urteil}, ${g.nachpruefung.fundstelle}.", "${g.nachpruefung.urteil}."), ``` wird: ```python ("quorum", "Nachpruefung ohne Fundstelle", "${g.nachpruefung.urteil}, ${klartext(g.nachpruefung.fundstelle)}.", "${g.nachpruefung.urteil}."), ``` Aus: ``` ("quorum", "Stelle fehlt in der Gruppenueberschrift", ", Stelle ${erste.stelle}", ""), ``` wird: ```python ("quorum", "Stelle fehlt in der Gruppenueberschrift", ", Stelle ${klartext(erste.stelle)}", ""), ``` Neue Mutanten, alle mit dem Namensanfang `Klartext:`. Die ersten bauen eine falsche Regel in `klartext()` ein (Klammerfolge, erste Klammer, Einbettung, Tag, Abschnittslink), die folgenden lassen je eine Stelle roh, der letzte entschärft den ganzen Bericht samt Hub-Zeile. Muster mit Backslash stehen als Python-Raw-String mit Präfix `r`, damit der Backslash wörtlich bleibt. Aus: ``` ("quorum", "Zusammenfuehren darf Dateien lesen", "Lies keine Dateien und fahre keine Befehle; du arbeitest nur mit dieser Liste.", "Lies die Dateien, wenn du sie brauchst."), ] ``` wird: ```python ("quorum", "Zusammenfuehren darf Dateien lesen", "Lies keine Dateien und fahre keine Befehle; du arbeitest nur mit dieser Liste.", "Lies die Dateien, wenn du sie brauchst."), ("quorum", "Klartext: Klammerfolge bleibt", r".replace(/\[{2,}/g, folge => folge.replace(/\[/g, '\\['))", ""), ("quorum", "Klartext: nur die erste Klammer entschaerft", r"folge => folge.replace(/\[/g, '\\['))", r"folge => '\\' + folge)"), ("quorum", "Klartext: Einbettung ausgenommen", r".replace(/\[{2,}/g,", r".replace(/(? folge.replace(/\[/g, '\\[')) .replace(/(^|[^\p{L}\p{N}_\\])#(?=[^\s#])/gu, '$1\\#') } function berichtVoll(a, p, x) { ``` Stimmentabelle, Grund einer nicht abgegebenen Stimme: Aus: ``` z.push(`| ${name} | ${s.abgegeben ? 'ja' : 'nein: ' + s.grund} | ${c.gesamt} | ${c.belegt} | ${c.bestaetigt} | ${c.alleinfunde} | ${laufzeit(s.dauer_s)} |`) ``` wird: ```javascript z.push(`| ${name} | ${s.abgegeben ? 'ja' : 'nein: ' + klartext(s.grund)} | ${c.gesamt} | ${c.belegt} | ${c.bestaetigt} | ${c.alleinfunde} | ${laufzeit(s.dauer_s)} |`) ``` Gruppenüberschrift, Befundzeilen, Beleg und Nachprüfung: Aus: ``` z.push(`### G${g.nr}: ${g.schwere}, ${STATUS_TEXT[g.status]} (${g.stimmen.map(n => NAMEN[n]).join(', ')})`, '', `\`${erste.datei}\`, Stelle ${erste.stelle}`, '') for (const b of g.mitglieder) z.push(`- ${b.id} (${NAMEN[b.stimme]}, ${b.schwere}, Stelle ${b.stelle}): ${b.aussage}`, ` Beleg: ${b.beleg}`) if (g.nachpruefung) z.push('', `Nachprüfung: ${g.nachpruefung.urteil}, ${g.nachpruefung.fundstelle}. ${g.nachpruefung.begruendung}`) ``` wird: ```javascript z.push(`### G${g.nr}: ${g.schwere}, ${STATUS_TEXT[g.status]} (${g.stimmen.map(n => NAMEN[n]).join(', ')})`, '', `\`${erste.datei}\`, Stelle ${klartext(erste.stelle)}`, '') for (const b of g.mitglieder) z.push(`- ${b.id} (${NAMEN[b.stimme]}, ${b.schwere}, Stelle ${klartext(b.stelle)}): ${klartext(b.aussage)}`, ` Beleg: ${klartext(b.beleg)}`) if (g.nachpruefung) z.push('', `Nachprüfung: ${g.nachpruefung.urteil}, ${klartext(g.nachpruefung.fundstelle)}. ${klartext(g.nachpruefung.begruendung)}`) ``` Unbelegt: Aus: ``` for (const b of x.unbelegt) z.push(`- ${b.id} (${NAMEN[b.stimme]}, ${b.schwere}) \`${b.datei}\`, Stelle ${b.stelle}: ${b.aussage}`) ``` wird: ```javascript for (const b of x.unbelegt) z.push(`- ${b.id} (${NAMEN[b.stimme]}, ${b.schwere}) \`${b.datei}\`, Stelle ${klartext(b.stelle)}: ${klartext(b.aussage)}`) ``` Nicht prüfbar: Aus: ``` const offen = Object.keys(x.stimmen).flatMap(n => (x.stimmen[n].nicht_pruefbar || []).map(t => `- ${NAMEN[n]}: ${t}`)) ``` wird: ```javascript const offen = Object.keys(x.stimmen).flatMap(n => (x.stimmen[n].nicht_pruefbar || []).map(t => `- ${NAMEN[n]}: ${klartext(t)}`)) ``` Hinweise des Laufs: Aus: ``` z.push(...(alleHinweise.length ? alleHinweise.map(h => '- ' + h) : ['keine']), '') ``` wird: ```javascript z.push(...(alleHinweise.length ? alleHinweise.map(h => '- ' + klartext(h)) : ['keine']), '') ``` ## Schritt 3 — Fälle und Mutanten in den Baum, Rot im Wortlaut `quorum.js` im Baum ist noch die alte Fassung; sie ist die falsche Umsetzung, an der jeder neue Fall rot werden muss. ``` A="$(cygpath -m -l /tmp)/quorum-task06" && cp "$A/k/.vault-tools/workflow-pruefung/faelle_workflows.mjs" "$A/k/.vault-tools/workflow-pruefung/mutanten_workflows.py" D:/Claude/.vault-tools/workflow-pruefung/ && node D:/Claude/.vault-tools/workflow-pruefung/faelle_workflows.mjs > "$A/rot.txt"; echo "EXIT $?"; grep -A 1 --no-group-separator '^FEHLER' "$A/rot.txt"; grep '^HINWEIS' "$A/rot.txt"; echo "ENDE" ``` Erwartet genau diese Ausgabe, Zeile für Zeile: ``` EXIT 1 FEHLER quorum: Klartext im Bericht: Stelle, Aussage und Beleg einer Gruppe ohne Wikilink, Einbettung und Tag, README-Link bleibt Obsidian-Syntax im Bericht: Wikilink, Einbettung, Tag FEHLER quorum: Klartext im Bericht: Fundstelle und Begründung der Nachprüfung ohne Wikilink, Einbettung und Tag Obsidian-Syntax im Bericht: Wikilink, Einbettung, Tag FEHLER quorum: Klartext im Bericht: Unbelegt und Nicht prüfbar ohne Wikilink, Einbettung und Tag Obsidian-Syntax im Bericht: Wikilink, Einbettung, Tag FEHLER quorum: Klartext im Bericht: Hinweise des Laufs und Grund der Codex-Stimme ohne Wikilink, Einbettung und Tag Obsidian-Syntax im Bericht: Wikilink, Einbettung, Tag FEHLER quorum: Klartext im Bericht eines Kundendokuments: Hub-Zeile bleibt Wikilink, Frontmatter bleibt, zitierte Syntax nicht Obsidian-Syntax im Bericht: Wikilink, Einbettung, Tag ENDE ``` - Jede Zeile unter einer `FEHLER`-Zeile beginnt mit sieben Leerzeichen. - Keine Zeile `HINWEIS`: Die Grundlinie des Quorums bleibt grün. - Weicht die Ausgabe ab: nichts anpassen, anhalten, `rot.txt` vollständig melden. ## Schritt 4 — Umsetzung in den Baum, grün, Byte-Vergleich ``` A="$(cygpath -m -l /tmp)/quorum-task06" && cp "$A/k/.claude/workflows/quorum.js" D:/Claude/.claude/workflows/ && node D:/Claude/.vault-tools/workflow-pruefung/faelle_workflows.mjs > "$A/gruen.txt"; echo "EXIT $?"; grep -E '^(FEHLER|HINWEIS)' "$A/gruen.txt"; tail -n 1 "$A/gruen.txt"; echo "ENDE" ``` Erwartet: `EXIT 0`, dann genau eine Zeile, die `0 gescheitert` enthält, dann `ENDE`. ``` A="$(cygpath -m -l /tmp)/quorum-task06" && cmp "$A/k/.claude/workflows/quorum.js" D:/Claude/.claude/workflows/quorum.js && cmp "$A/k/.vault-tools/workflow-pruefung/faelle_workflows.mjs" D:/Claude/.vault-tools/workflow-pruefung/faelle_workflows.mjs && cmp "$A/k/.vault-tools/workflow-pruefung/mutanten_workflows.py" D:/Claude/.vault-tools/workflow-pruefung/mutanten_workflows.py; echo "EXIT $?"; GIT_OPTIONAL_LOCKS=0 git -C D:/Claude status --porcelain -- .claude/workflows .vault-tools/workflow-pruefung; echo "ENDE" ``` Erwartet: `EXIT 0` ohne Ausgabe von `cmp`, dann je eine Zeile ` M .claude/workflows/quorum.js`, ` M .vault-tools/workflow-pruefung/faelle_workflows.mjs` und ` M .vault-tools/workflow-pruefung/mutanten_workflows.py` in der Reihenfolge, die git ausgibt, keine weitere, dann `ENDE`. ``` python D:/Claude/.vault-tools/plan-kopie/plan_bloecke.py nach D:/Claude/.vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/task-06-bericht-entschaerfen.md --projekt D:/Claude ``` Erwartet: `0 Befund(e).` ## Schritt 5 — Mutantenlauf im Hintergrund Der Lauf fährt für jeden Mutanten alle Fälle; zuletzt dauerte er etwa 25 Minuten, mit den neuen Mutanten etwas länger. Das ist länger als ein Aufruf des Bash-Werkzeugs, deshalb im Hintergrund. Starten, im Bash-Werkzeug mit `run_in_background: true`: ``` A="$(cygpath -m -l /tmp)/quorum-task06" && PYTHONIOENCODING=utf-8 python -u D:/Claude/.vault-tools/workflow-pruefung/mutanten_workflows.py > "$A/mutanten.txt" 2>&1; echo "EXIT $?" >> "$A/mutanten.txt" ``` Abfragen, im Bash-Werkzeug mit `timeout` 400000; wiederholen, bis die letzte Zeile mit `EXIT` beginnt: ``` sleep 300; A="$(cygpath -m -l /tmp)/quorum-task06" && tail -n 2 "$A/mutanten.txt" ``` Auswerten: ``` A="$(cygpath -m -l /tmp)/quorum-task06" && grep -E '^(UEBERLEBT|VERALTET)' "$A/mutanten.txt"; echo "---"; grep -E 'quorum: (Klartext: |Stelle fehlt in der Befundzeile|Nachpruefung ohne Fundstelle|Stelle fehlt in der Gruppenueberschrift)' "$A/mutanten.txt"; echo "---"; tail -n 2 "$A/mutanten.txt" ``` Erwartet: - Vor dem ersten `---` keine Zeile. - Zwischen den beiden `---` je eine Zeile für jeden Mutanten mit dem Namensanfang `Klartext:` aus dem Block oben und für die drei angepassten; jede beginnt mit `GEFANGEN`. - Danach eine Zeile, die auf `0 ueberlebt, 0 veraltet` endet, und `EXIT 0`. ## Meldung Melde alle Ausgaben der Schritte 1 bis 5 vollständig, dazu die Zahl hinter `geprueft:` aus Schritt 1 und den Wert von `'ersetzt'` aus Schritt 2. Läuft etwas anders als erwartet: nichts anpassen, melden, mit jeder `FEHLER`-, `UEBERLEBT`- oder `VERALTET`-Zeile und ihrem Text. ## Offene Punkte - **Obsidian-Bestätigung (Pascal oder Koordinator, vor der Abnahme):** In Obsidian 1.13.7 bestätigen, dass die gewählte Form keinen Link, keine Einbettung und keinen Tag erzeugt. Vorschlag: eine Probenotiz mit den vier Zeilen `\[\[a/b]]`, `!\[\[c]]`, `\#needs-review`, `\[\[\#x]]` als Fließtext anlegen, dann Leseansicht und Live-Vorschau ansehen, ausgehende Links, Tag-Bereich, Graph und `Review-Queue.base` prüfen und die Notiz wieder löschen. Fällt die Probe negativ aus, ist eine neue Nachbesserung nötig (andere Form, andere Erwartungstexte, andere Mutanten). - **Der erste echte Bericht** behält seine zwei toten Wikilinks. Ob sie von Hand in dieselbe Form gebracht werden, entscheidet der Koordinator; dieser Task fasst keinen Bericht an. - **Lauf mit echten Daten:** Beim nächsten echten Quorum den abgelegten Bericht mit `linkcheck.py` prüfen; erwartet ist kein toter Link aus dem Bericht. Zitiert dort eine Stimme Obsidian-Syntax, ist das zugleich die Probe in der App. - **Übersicht:** Tasktabelle um Task 06 ergänzen; das zieht der Koordinator nach. ## Risiken - **Weitere Markdown-Syntax im Freitext bleibt lebendig:** ein Markdown-Link der Form Klammer-Text-Klammer mit Pfad dahinter (relativ zum Berichtsordner meist tot), eine Raute mit Leerzeichen am Zeilenanfang eines mehrzeiligen Texts (Überschrift), ein senkrechter Strich im Grund der Stimmentabelle (zerlegt die Zeile). Nicht im Umfang; der Linkcheck meldet tote Markdown-Links nicht. - **Backslash in Code-Spannen sichtbar:** Steht zitierte Syntax im Freitext selbst zwischen Backticks, zeigt die Leseansicht dort den Backslash mit. Lesbar, aber nicht wörtlich. - **Lauf über 10 Minuten:** Bricht die Abfrage ab oder endet `mutanten.txt` ohne `EXIT`-Zeile, ist der Lauf nicht fertig oder gestorben; dann nicht neu starten, sondern melden.