Waisen und Quorum: Ausnahmemuster fuer Programm- und Vorlagendateien, Abschluss Waisen, Plan Quorum Task 06

waisen-ausnahmen.txt: Skills, Plugins, Testfixtures, Programmausgaben und Vorlagen
zaehlen nicht als Waisen. Waisen-Uebersicht: Abschluss mit Belegen je Fertig-heisst-Punkt.
Quorum: Task 06 entschaerft Freitext im Bericht (Fund aus dem ersten echten Lauf).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5.5 committed 2026-10-05 05:07:31 +02:00
1 parent 2d89815905
commit 7a74a8acb9
4 files changed
+536 -3

No files matched your search

@@ -5,7 +5,7 @@ status: draft
summary: "Nach dem Plan prüft ein neuer Workflow quorum vor Einspielen, neuer Version auf master und Abgabe eines Kundendokuments denselben Unterschied seit dem letzten Quorum mit Opus, Sonnet und Codex (Ersatz Haiku), zählt Mehrheiten, lässt Einzelbefunde von Sonnet nachprüfen und liefert Urteil und Bericht."
tags: [plan, vault-tools, codeworkflow]
created: 2026-09-29
updated: 2026-09-30
updated: 2026-10-05
---
# Quorum am Meilenstein
@@ -106,6 +106,7 @@ Nachgelesen im Quelltext des Plugins `openai-codex/codex/1.0.6` (unter `~/.claud
| 03 | `.vault-tools/workflow-pruefung/faelle_workflows.mjs`, `mutanten_workflows.py` | geändert | sonnet, low | 3 |
| 04 | `.claude/agents/quorum-stimme.md` (neu), `CLAUDE.md`, `AI/Regelwerk/codeworkflow.md` (geändert) | neu und geändert | sonnet, low | 1 |
| 05 | `.vault-tools/quorum/quorum_werkzeug.py`, `faelle_quorum_werkzeug.py`, `mutanten_quorum_werkzeug.py`, `.claude/workflows/quorum.js` | Nachbesserung nach der Prüfung von Welle 1 (2026-09-30): Teilgruppen nach Datei und Zeilen mit Toleranz drei, `GIT_OPTIONAL_LOCKS=0`, `--summe` in `ablegen`; Delta gegen den Baum | sonnet, low | 2 |
| 06 | `.claude/workflows/quorum.js`, `.vault-tools/workflow-pruefung/faelle_workflows.mjs`, `mutanten_workflows.py` | Nachbesserung nach dem ersten echten Lauf (2026-10-05): Freitext aus Stimmen und Nachprüfung erzeugt im Bericht keinen Wikilink, keine Einbettung und keinen Tag (`klartext()`); Delta gegen den Baum | sonnet, low | 4 |
Die Zieldateien sind disjunkt. **Welle 1** (Task 01, 02, 04) läuft parallel. **Welle 2** (Task 03) folgt, weil ihre Fälle und Mutanten gegen `quorum.js` aus Task 02 laufen.
@@ -0,0 +1,504 @@
---
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 `(?<!\S)#needs-review`, Zeile 66), `\#needs-review` also nicht.
- **Nicht gemessen:** Ob Obsidian 1.13.7 den Backslash vor Wikilink, Einbettung und Tag beachtet (Leseansicht, Live-Vorschau, Graph, Tag-Index, `Review-Queue.base`). Das bestätigen Pascal oder der Koordinator in der App, siehe „Offene Punkte“.
- Die Tag-Regel ist bewusst weiter als die, die Obsidian vermutlich anwendet (nur nach Leerraum): Sie entschärft auch eine Raute hinter `[`, `(` oder einem Anführungszeichen. Kostet nur einen Backslash im Quelltext.
Wo `klartext()` greift und wo nicht:
| Stelle in `bericht_md` | Quelle | entschärft |
|---|---|---|
| Stimmentabelle, Spalte abgegeben, Grund | Codex-Hilfsagent (`job_status`), Absturzmeldung | ja |
| Gruppenüberschrift, Stelle | Stimme | ja |
| Befundzeile, Stelle und Aussage; Beleg | Stimme | ja |
| Nachprüfung, Fundstelle und Begründung | Nachprüfung | ja |
| Unbelegt, Stelle und Aussage | Stimme | ja |
| Nicht prüfbar | Stimme | ja |
| Hinweise des Laufs | Paket, Zusammenführen (IDs, Stellen), Codex-Grund | ja |
| Hub-Zeile (`Projekt: …` bzw. `Übersicht: …`) | Workflow | nein, bleibt Link |
| Frontmatter, Urteil-Zeile, Basis und Stand | Workflow, Paket | nein |
| Datei in Backticks | Stimme | nein, Obsidian lässt Code-Spannen in Ruhe |
| Felder des JSON-Ergebnisses (`gruppen`, `hinweise`) | alle | nein, sie gehen nicht in den Vault |
Die Fälle und Mutanten unten binden genau diese Form. Fällt die Bestätigung in Obsidian negativ aus, braucht es eine neue Nachbesserung mit anderer Form, anderen Erwartungstexten und anderen Mutanten.
## Zieldateien
- `D:\Claude\.vault-tools\workflow-pruefung\faelle_workflows.mjs` (geändert)
- `D:\Claude\.vault-tools\workflow-pruefung\mutanten_workflows.py` (geändert)
- `D:\Claude\.claude\workflows\quorum.js` (geändert)
## Verbote
- Keine andere Datei im Vault, auch nicht `attrappe.mjs`, die übrigen Workflows, die Taskdateien 01 bis 05 und kein Bericht unter `docs/quorum/` eines Projekts.
- Außerhalb des Vaults nur der Arbeitsordner `/tmp/quorum-task06` in der Git-Bash. Ihn nicht löschen; der Koordinator räumt ihn nach der Prüfung.
- Kein Lauf eines echten Workflows, kein Aufruf des Codex-Plugins.
- Nichts von Hand schreiben oder umformulieren; die Blöcke wendet nur `plan_materialisiere.py` an, in den Baum kommen sie nur per `cp` aus dem Arbeitsordner. Ein Widerspruch zwischen Plan und Baum wird gemeldet.
- Während der Mutantenlauf läuft, keine Datei unter `.claude/workflows/` und `.vault-tools/workflow-pruefung/` ändern.
- Kein Commit, kein `git add`.
## Schritt 1 — Vorprüfung
```
python D:/Claude/.vault-tools/plan-kopie/plan_bloecke.py vor D:/Claude/.vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/task-06-bericht-entschaerfen.md --projekt D:/Claude
```
Erwartet: `0 Befund(e).` Merk dir die Zahl hinter `geprueft:`, Schritt 2 vergleicht damit. Bei einem Befund anhalten und melden.
```
GIT_OPTIONAL_LOCKS=0 git -C D:/Claude status --porcelain -- .claude/workflows .vault-tools/workflow-pruefung; echo "ENDE"; node D:/Claude/.vault-tools/workflow-pruefung/faelle_workflows.mjs | tail -n 1; echo "EXIT ${PIPESTATUS[0]}"
```
Erwartet: direkt `ENDE` (beide Ordner sauber), dann eine Zeile mit `0 gescheitert`, dann `EXIT 0`. Steht vor `ENDE` eine Zeile oder scheitert ein Fall: anhalten und melden.
## Schritt 2 — Blöcke in einem Arbeitsordner anwenden
Der Arbeitsordner trägt Kopien der drei Zieldateien. So kommen Fälle und Mutanten in Schritt 3 vor der Umsetzung in Schritt 4 in den Baum.
```
A="$(cygpath -m -l /tmp)/quorum-task06" && mkdir "$A" && mkdir -p "$A/k/.claude/workflows" "$A/k/.vault-tools/workflow-pruefung" && cp D:/Claude/.claude/workflows/quorum.js "$A/k/.claude/workflows/" && cp D:/Claude/.vault-tools/workflow-pruefung/faelle_workflows.mjs D:/Claude/.vault-tools/workflow-pruefung/mutanten_workflows.py "$A/k/.vault-tools/workflow-pruefung/" && python D:/Claude/.vault-tools/plan-kopie/plan_materialisiere.py D:/Claude/.vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum "$A/k" task-06-bericht-entschaerfen.md; echo "EXIT $?"
```
Erwartet: In der Zählerzeile `'neu': 0`, `'angehaengt': 0`, `'geloescht': 0` und `'ersetzt'` gleich der Zahl hinter `geprueft:` aus Schritt 1; dann `0 Befund(e).` und `EXIT 0`. Scheitert `mkdir "$A"`, weil der Ordner schon da ist: anhalten und melden, nichts löschen.
### Fälle in `.vault-tools/workflow-pruefung/faelle_workflows.mjs`
Neue Fälle am Ende des Abschnitts `quorum`. Jeder lässt Stimmen, Nachprüfung oder Hilfsagenten Wikilink, Einbettung, Tag und Abschnittslink zitieren und verlangt: im Bericht keins davon, der Text steht lesbar in der Form aus der Tabelle oben, die Linkzeile des Workflows bleibt ein Link.
Aus:
```
// ---------------------------------------------------------------- Lauf
```
wird:
```javascript
// Klartext im Bericht (Nachbesserung Task 06): Stimmen, Nachprüfung und Hilfsagenten zitieren
// Wikilink, Einbettung, Tag und Abschnittslink. Q_KLAR ist derselbe Text, wie er im Bericht
// stehen muss: lesbar, aber ohne Link, Einbettung und Tag.
const Q_ZITATE = '[[a/b]] ![[c]] #needs-review [[#x]]'
const Q_KLAR = '\\[\\[a/b]] !\\[\\[c]] \\#needs-review \\[\\[\\#x]]'
const qZ = schwere => ({ 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(/(?<!!)\[{2,}/g,"),
("quorum", "Klartext: Tag bleibt", r".replace(/(^|[^\p{L}\p{N}_\\])#(?=[^\s#])/gu, '$1\\#')", ""),
("quorum", "Klartext: Tag nur nach Leerraum", r"(^|[^\p{L}\p{N}_\\])#", r"(^|\s)#"),
("quorum", "Klartext: Gruppenueberschrift, Stelle roh", ", Stelle ${klartext(erste.stelle)}", ", Stelle ${erste.stelle}"),
("quorum", "Klartext: Befundzeile, Stelle roh", ", ${b.schwere}, Stelle ${klartext(b.stelle)})", ", ${b.schwere}, Stelle ${b.stelle})"),
("quorum", "Klartext: Befundzeile, Aussage roh", "}): ${klartext(b.aussage)}", "}): ${b.aussage}"),
("quorum", "Klartext: Beleg roh", " Beleg: ${klartext(b.beleg)}", " Beleg: ${b.beleg}"),
("quorum", "Klartext: Nachpruefung, Fundstelle roh", "${klartext(g.nachpruefung.fundstelle)}", "${g.nachpruefung.fundstelle}"),
("quorum", "Klartext: Nachpruefung, Begruendung roh", "${klartext(g.nachpruefung.begruendung)}", "${g.nachpruefung.begruendung}"),
("quorum", "Klartext: Unbelegt, Stelle roh", "Stelle ${klartext(b.stelle)}: ", "Stelle ${b.stelle}: "),
("quorum", "Klartext: Unbelegt, Aussage roh", ")}: ${klartext(b.aussage)}", ")}: ${b.aussage}"),
("quorum", "Klartext: Nicht pruefbar roh", "- ${NAMEN[n]}: ${klartext(t)}", "- ${NAMEN[n]}: ${t}"),
("quorum", "Klartext: Hinweise roh", "'- ' + klartext(h)", "'- ' + h"),
("quorum", "Klartext: Stimmentabelle, Grund roh", "'nein: ' + klartext(s.grund)", "'nein: ' + s.grund"),
("quorum", "Klartext: ganzer Bericht, Hub-Zeile entschaerft", r"return z.join('\n')", r"return klartext(z.join('\n'))"),
]
```
Welcher Fall welchen Mutanten fängt:
| Mutant | rot wird mindestens |
|---|---|
| Regel-Mutanten (Klammerfolge, erste Klammer, Einbettung, Tag, Tag nach Leerraum) | jeder neue Fall |
| Gruppenüberschrift, Befundzeile, Beleg | Stelle, Aussage und Beleg einer Gruppe |
| Nachprüfung, Fundstelle und Begründung | Fundstelle und Begründung der Nachprüfung |
| Unbelegt, Nicht prüfbar | Unbelegt und Nicht prüfbar |
| Hinweise, Stimmentabelle | Hinweise des Laufs und Grund der Codex-Stimme |
| ganzer Bericht | Bericht eines Kundendokuments (Hub-Zeile), dazu die bestehenden Hub-Fälle mit Wikilink |
### Umsetzung in `.claude/workflows/quorum.js`
Kopfkommentar:
Aus:
```
// Laufzeit: Date.now() und new Date() gibt es im Laufzeitsystem nicht. Die Codex-Laufzeit kommt
// aus dem Job, die der Claude-Stimmen steht im Bericht als nicht gemessen.
```
wird:
```javascript
// Laufzeit: Date.now() und new Date() gibt es im Laufzeitsystem nicht. Die Codex-Laufzeit kommt
// aus dem Job, die der Claude-Stimmen steht im Bericht als nicht gemessen.
// Bericht: Freitext aus Stimmen, Zusammenführen und Nachprüfung geht nur durch klartext() in
// bericht_md. Ein zitierter Wikilink, eine Einbettung oder ein Tag würde im Vault sonst lebendig;
// Hub-Zeile und Frontmatter baut der Workflow selbst, sie bleiben echte Links bzw. Felder.
```
Neue Funktion `klartext()` vor `berichtVoll()`:
Aus:
```
function berichtVoll(a, p, x) {
```
wird:
```javascript
// Freitext als Klartext für Obsidian: Jede öffnende eckige Klammer in einer Folge von zwei oder
// mehr bekommt einen Backslash, damit kein Wikilink und keine Einbettung entsteht. Eine Raute
// bekommt einen Backslash, wenn ihr kein Buchstabe, keine Ziffer, kein Unterstrich und kein
// Backslash vorausgeht und weder Leerraum noch eine zweite Raute folgt; so entsteht kein Tag.
// Die Leseansicht zeigt den Text wie zitiert. Zweimal angewendet ändert sich nichts mehr.
function klartext(s) {
return String(s)
.replace(/\[{2,}/g, folge => 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.