Quorum: Task 06 (Freitext im Bericht entschaerft), Grund nennt blockierende ohne Beleg, Abschluss

quorum.js: klartext() schreibt Wikilinks, Einbettungen und Tags aus Freitext als Klartext
(Fund aus dem ersten echten Lauf); der Grund eines freien Urteils nennt blockierende Befunde
ohne Beleg. faelle_workflows.mjs 248 Faelle gruen, mutanten_workflows.py 145 Mutanten,
0 ueberlebt. codeworkflow.md: Regel zum Befund ohne Beleg und Ablageort beim Kundendokument
praezisiert, Vermerk 'noch nicht abgenommen' entfernt. LIESMICH.md fuer .vault-tools/quorum/.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5.5 committed 2026-10-05 06:36:34 +02:00
1 parent b2b52dd0e8
commit 612cd7fb54
6 files changed
+212 -21

No files matched your search

+24 -9
View File
@@ -41,10 +41,14 @@ export const meta = {
// verfälschen; dass der Agent keine Datei liest, sagt nur sein Auftrag, ein Fall sichert den Satz. // verfälschen; dass der Agent keine Datei liest, sagt nur sein Auftrag, ein Fall sichert den Satz.
// Nachprüfen: quorum-stimme auf sonnet für Gruppen ohne Mehrheit mit Schwere blockierend oder // Nachprüfen: quorum-stimme auf sonnet für Gruppen ohne Mehrheit mit Schwere blockierend oder
// wichtig. Ein blockierender Einzelbefund ohne Urteil sperrt wie ein bestätigter. // wichtig. Ein blockierender Einzelbefund ohne Urteil sperrt wie ein bestätigter.
// Urteil: frei heißt kein bestätigter und kein ungeprüfter blockierender Befund. Mit weniger als // Urteil: frei heißt kein bestätigter und kein ungeprüfter blockierender Befund mit Beleg; ein
// blockierender Befund ohne Beleg sperrt nicht (Plan, Offener Punkt 8), der Grund nennt ihn. Mit weniger als
// zwei abgegebenen Stimmen gibt es kein Urteil und keinen Bericht, die Basis bleibt die alte. // zwei abgegebenen Stimmen gibt es kein Urteil und keinen Bericht, die Basis bleibt die alte.
// Laufzeit: Date.now() und new Date() gibt es im Laufzeitsystem nicht. Die Codex-Laufzeit kommt // 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. // 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.
const ANLAESSE = ['einspielen', 'version', 'kundendokument'] const ANLAESSE = ['einspielen', 'version', 'kundendokument']
const RANG = { blockierend: 0, wichtig: 1, hinweis: 2 } const RANG = { blockierend: 0, wichtig: 1, hinweis: 2 }
const KUERZEL = { opus: 'O', sonnet: 'S', codex: 'C', haiku: 'H' } const KUERZEL = { opus: 'O', sonnet: 'S', codex: 'C', haiku: 'H' }
@@ -482,6 +486,17 @@ function berichtLeer(a, p, urteil, grund) {
return [kopf(a, p, urteil, `Urteil ${urteil}: ${grund}.`, 'nicht gelaufen'), '', `# Quorum ${p.nummer}: ${a.anlass}`, '', hubZeile(a), '', '## Urteil', '', `**${urteilText(urteil)}**: Seit dem letzten Quorum hat sich nichts geändert. Das Urteil stammt aus ${vorher}; keine Stimme ist gelaufen.`, ''].join('\n') return [kopf(a, p, urteil, `Urteil ${urteil}: ${grund}.`, 'nicht gelaufen'), '', `# Quorum ${p.nummer}: ${a.anlass}`, '', hubZeile(a), '', '## Urteil', '', `**${urteilText(urteil)}**: Seit dem letzten Quorum hat sich nichts geändert. Das Urteil stammt aus ${vorher}; keine Stimme ist gelaufen.`, ''].join('\n')
} }
// 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) { function berichtVoll(a, p, x) {
const namen = Object.keys(x.stimmen).filter(n => x.stimmen[n].abgegeben).map(n => NAMEN[n]).join(', ') const namen = Object.keys(x.stimmen).filter(n => x.stimmen[n].abgegeben).map(n => NAMEN[n]).join(', ')
const codexFeld = x.stimmen.codex.abgegeben ? 'abgegeben' : 'ersetzt' const codexFeld = x.stimmen.codex.abgegeben ? 'abgegeben' : 'ersetzt'
@@ -492,7 +507,7 @@ function berichtVoll(a, p, x) {
const s = x.stimmen[n] const s = x.stimmen[n]
const c = x.zaehlung[n] const c = x.zaehlung[n]
const name = n === 'haiku' ? 'Haiku (Ersatz für Codex)' : NAMEN[n] const name = n === 'haiku' ? 'Haiku (Ersatz für Codex)' : NAMEN[n]
z.push(`| ${name} | ${s.abgegeben ? 'ja' : 'nein: ' + s.grund} | ${c.gesamt} | ${c.belegt} | ${c.bestaetigt} | ${c.alleinfunde} | ${laufzeit(s.dauer_s)} |`) z.push(`| ${name} | ${s.abgegeben ? 'ja' : 'nein: ' + klartext(s.grund)} | ${c.gesamt} | ${c.belegt} | ${c.bestaetigt} | ${c.alleinfunde} | ${laufzeit(s.dauer_s)} |`)
} }
z.push('') z.push('')
if (x.stimmen.haiku) z.push('Haiku stand statt Codex im Quorum. Dieses Quorum war nur mit Claude besetzt.', '') if (x.stimmen.haiku) z.push('Haiku stand statt Codex im Quorum. Dieses Quorum war nur mit Claude besetzt.', '')
@@ -501,20 +516,20 @@ function berichtVoll(a, p, x) {
if (!x.gruppen.length) z.push('keine belegten Befunde', '') if (!x.gruppen.length) z.push('keine belegten Befunde', '')
for (const g of x.gruppen) { for (const g of x.gruppen) {
const erste = g.mitglieder[0] const erste = g.mitglieder[0]
z.push(`### G${g.nr}: ${g.schwere}, ${STATUS_TEXT[g.status]} (${g.stimmen.map(n => NAMEN[n]).join(', ')})`, '', `\`${erste.datei}\`, Stelle ${erste.stelle}`, '') 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 ${b.stelle}): ${b.aussage}`, ` Beleg: ${b.beleg}`) 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}, ${g.nachpruefung.fundstelle}. ${g.nachpruefung.begruendung}`) if (g.nachpruefung) z.push('', `Nachprüfung: ${g.nachpruefung.urteil}, ${klartext(g.nachpruefung.fundstelle)}. ${klartext(g.nachpruefung.begruendung)}`)
z.push('') z.push('')
} }
z.push('## Unbelegt', '') z.push('## Unbelegt', '')
if (!x.unbelegt.length) z.push('keine') if (!x.unbelegt.length) z.push('keine')
for (const b of x.unbelegt) z.push(`- ${b.id} (${NAMEN[b.stimme]}, ${b.schwere}) \`${b.datei}\`, Stelle ${b.stelle}: ${b.aussage}`) for (const b of x.unbelegt) z.push(`- ${b.id} (${NAMEN[b.stimme]}, ${b.schwere}) \`${b.datei}\`, Stelle ${klartext(b.stelle)}: ${klartext(b.aussage)}`)
z.push('', '## Nicht prüfbar', '') z.push('', '## Nicht prüfbar', '')
const offen = Object.keys(x.stimmen).flatMap(n => (x.stimmen[n].nicht_pruefbar || []).map(t => `- ${NAMEN[n]}: ${t}`)) const offen = Object.keys(x.stimmen).flatMap(n => (x.stimmen[n].nicht_pruefbar || []).map(t => `- ${NAMEN[n]}: ${klartext(t)}`))
z.push(...(offen.length ? offen : ['keine'])) z.push(...(offen.length ? offen : ['keine']))
z.push('', '## Hinweise des Laufs', '') z.push('', '## Hinweise des Laufs', '')
const alleHinweise = [...p.hinweise, ...laufHinweise] const alleHinweise = [...p.hinweise, ...laufHinweise]
z.push(...(alleHinweise.length ? alleHinweise.map(h => '- ' + h) : ['keine']), '') z.push(...(alleHinweise.length ? alleHinweise.map(h => '- ' + klartext(h)) : ['keine']), '')
return z.join('\n') return z.join('\n')
} }
@@ -621,7 +636,7 @@ const sperrend = gruppen.filter(g => g.schwere === 'blockierend' && (gilt(g) ||
const urteil = sperrend.length ? 'nicht_frei' : 'frei' const urteil = sperrend.length ? 'nicht_frei' : 'frei'
const grund = sperrend.length const grund = sperrend.length
? `${sperrend.filter(gilt).length} bestätigte und ${sperrend.filter(g => !gilt(g)).length} ungeprüfte blockierende Befunde` ? `${sperrend.filter(gilt).length} bestätigte und ${sperrend.filter(g => !gilt(g)).length} ungeprüfte blockierende Befunde`
: 'kein bestätigter und kein ungeprüfter blockierender Befund' : 'kein bestätigter und kein ungeprüfter blockierender Befund mit Beleg' + (unbelegt.some(b => b.schwere === 'blockierend') ? `; ${unbelegt.filter(b => b.schwere === 'blockierend').length} blockierende ohne Beleg stehen unter Unbelegt und sperren nicht` : '')
await ablesen('Ende', 0) await ablesen('Ende', 0)
const zaehlung = {} const zaehlung = {}
for (const n of Object.keys(stimmen)) { for (const n of Object.keys(stimmen)) {
+59
View File
@@ -0,0 +1,59 @@
---
type: guide
title: "Quorum am Meilenstein – Werkzeug und Ablauf"
status: active
summary: "Wie das Quorum gestartet, der Bericht abgelegt und das Werkzeug geprüft wird, dazu was der erste echte Lauf belegt hat."
tags: [vault-tools, codeworkflow]
created: 2026-10-05
updated: 2026-10-05
---
# Quorum am Meilenstein
Prüft vor dem Einspielen, vor einer neuen Minor- oder Major-Version auf master und vor der Abgabe eines Kundendokuments denselben Unterschied mit drei Stimmen: Opus, Sonnet und Codex, als Ersatz Haiku. Die Regel steht in `AI/Regelwerk/codeworkflow.md`, Abschnitt „Quorum am Meilenstein“. Hier steht nur die Bedienung.
## Starten
```text
# Code-Projekt, erstes Quorum dort: basis ist der Commit der letzten abgenommenen Version
workflow({ scriptPath: "D:/Claude/.claude/workflows/quorum.js" }, { anlass: "einspielen", projekt: "<Repo-Wurzel>", basis: "<commit>", suite_datei: "<datei mit Suite-Ausgabe>" })
# Kundendokument: Ordner im Vault, Dateien relativ dazu, Übersicht des Projekts
workflow({ scriptPath: "D:/Claude/.claude/workflows/quorum.js" }, { anlass: "kundendokument", ordner: "D:/Claude/<ordner>", dateien: ["<datei>", "..."], uebersicht: "<_MOC-datei>.md" })
```
- Der Arbeitsbaum muss außerhalb von `docs/quorum/` sauber sein.
- `suite` nur für eine Suite, die in neun Minuten durchläuft und nichts Unversioniertes schreibt; sonst die Suite selbst fahren und `suite_datei` nennen.
- `codex: false` setzt sofort Haiku ein, etwa wenn das Codex-Kontingent bekannt aufgebraucht ist.
## Bericht ablegen
```bash
# bericht_md aus dem Ergebnis als bericht.md in den Ordner paket schreiben, dann:
PYTHONIOENCODING=utf-8 python -X utf8 .vault-tools/quorum/quorum_werkzeug.py ablegen --paket <paket> --bericht <paket>/bericht.md --ziel <bericht_pfad> --summe <hex>
```
- Den fertigen Befehl liefert das Ergebnis als `ablegen_befehl`. Er setzt `stand` aus `paket.json` ein, damit kein Modell die Liste der Prüfsummen abschreibt.
- Danach den Bericht im Projekt committen.
## Prüfen
```bash
# Werkzeug: Fälle und Mutanten
python .vault-tools/quorum/faelle_quorum_werkzeug.py
python .vault-tools/quorum/mutanten_quorum_werkzeug.py
# Workflow: Fälle und Mutanten über die Attrappe (Mutantenlauf über 30 Minuten, im Hintergrund mit ausreichendem Zeitlimit starten)
node .vault-tools/workflow-pruefung/faelle_workflows.mjs
python .vault-tools/workflow-pruefung/mutanten_workflows.py
```
## Was belegt ist
- **Erster echter Lauf am 2026-09-30, Anlass einspielen, in einem Code-Projekt des Vaults:**
- Alle drei Stimmen haben gültig abgegeben, Codex nach 1 min 52 s, mit Lesebeleg.
- Urteil frei. Bestätigte Befunde je Stimme: Opus 3, Sonnet 2, Codex 3. Alleinfunde: Opus 2, Sonnet 1, Codex 3 (Tabelle „Stimmen“ im Bericht).
- `ablegen` hat den Stand als Commit eingesetzt, der Bericht verlinkt sein README.
- Der Verbrauch blieb im Rahmen der Ablesung (5 Stunden 6 %, Woche 77 % vorher und nachher).
- **Fund aus diesem Lauf:** Freitext der Stimmen hat im Bericht lebendige Wikilinks erzeugt. Seit Task 06 schreibt `quorum.js` Wikilinks, Einbettungen und Tags aus Freitext als Klartext mit Backslash.
- **Bekannte Grenzen (Prüfung vom 2026-10-05, klein):** `zeilen()` liest bei Aufzählungen nur die erste Zahl („Zeilen 72 und 134“ wird 72) und kennt Formen wie „Zeile 10 bis Zeile 14“ oder einen Geviertstrich nicht; eine zu breite Gruppe des Zusammenführ-Agenten kann so eine Mehrheit kosten. `pfadnorm()` entfernt keinen führenden Schrägstrich. `klartext()` setzt den Backslash auch vor `#` in HTML-Entitäten und URL-Fragmenten und lässt `%%…%%`, `<!--` und Code-Zäune in mehrzeiligen Belegen stehen.
- **Nicht gemessen:** die Laufzeit der Claude-Stimmen, weil das Laufzeitsystem keine Uhr hat. Ob Obsidian `\[\[x]]` und `\#tag` in jeder Ansicht als Text zeigt, ist nicht in der App geprüft.
@@ -1,7 +1,7 @@
--- ---
type: plan type: plan
title: "Quorum am Meilenstein: drei Stimmen, Mehrheit, Nachprüfung" title: "Quorum am Meilenstein: drei Stimmen, Mehrheit, Nachprüfung"
status: draft status: done
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." 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] tags: [plan, vault-tools, codeworkflow]
created: 2026-09-29 created: 2026-09-29
@@ -49,7 +49,7 @@ quorum.js (Args: anlass, projekt | ordner+dateien+uebersicht, basis?, kontext?,
└─ Urteil, Zählung je Stimme, bericht_md; verbrauch.js am Ende (wunsch 0) └─ Urteil, Zählung je Stimme, bericht_md; verbrauch.js am Ende (wunsch 0)
``` ```
Die Taskdateien 01 bis 04 enthalten den Code wörtlich. Die Taskdateien 01 bis 06 enthalten den Code wörtlich; 05 und 06 sind Nachbesserungen als Delta gegen den Baum.
## Codex: welcher Befehl, und warum so ## Codex: welcher Befehl, und warum so
@@ -108,7 +108,7 @@ Nachgelesen im Quelltext des Plugins `openai-codex/codex/1.0.6` (unter `~/.claud
| 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 | | 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 | | 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. Die Zieldateien sind disjunkt. **Welle 1** (Task 01, 02, 04) läuft parallel, Task 04 wegen `CLAUDE.md` nach dem Regeltext-Task des Waisen-Plans. **Welle 2** ist Task 05, **Welle 3** Task 03, weil sein Rot-Nachweis die Sicherung aus Task 05 braucht, **Welle 4** Task 06.
## Messungen vor und nach den Wellen ## Messungen vor und nach den Wellen
@@ -130,6 +130,7 @@ Die Zieldateien sind disjunkt. **Welle 1** (Task 01, 02, 04) läuft parallel. **
8. Byte-Vergleich: `welle_vergleich.py` je Welle gegen die Kopie mit angewendetem Plan, dazu `plan_bloecke.py nach` für Task 03 und 04 ohne Befund. 8. Byte-Vergleich: `welle_vergleich.py` je Welle gegen die Kopie mit angewendetem Plan, dazu `plan_bloecke.py nach` für Task 03 und 04 ohne Befund.
9. M1 wie oben: Exit 0 und JSON. 9. M1 wie oben: Exit 0 und JSON.
10. Lauf mit echten Daten wie unten: `status: fertig` mit Bericht, und die eingebaute Macke steht darin als Mehrheit oder als bestätigter Einzelbefund; danach ein Leerlauf mit `status: leer`. 10. Lauf mit echten Daten wie unten: `status: fertig` mit Bericht, und die eingebaute Macke steht darin als Mehrheit oder als bestätigter Einzelbefund; danach ein Leerlauf mit `status: leer`.
11. Task 06: Der erste echte Bericht und jeder folgende erzeugt im Linkcheck keinen toten Link aus zitiertem Freitext; in Obsidian zeigt eine Probenotiz mit `\[\[a/b]]`, `!\[\[c]]` und `\#tag` keinen Link, keine Einbettung und keinen Tag (Probe in der App, offen).
## Lauf mit echten Daten ## Lauf mit echten Daten
@@ -186,3 +187,19 @@ Der erste echte Meilenstein eines Projekts ist der zweite Lauf mit echten Daten;
14. **Codex-Modell und -Effort** setzt der Plan nicht; es gilt die Vorgabe der Codex-Konfiguration. 14. **Codex-Modell und -Effort** setzt der Plan nicht; es gilt die Vorgabe der Codex-Konfiguration.
15. **Messungen M1 bis M3** siehe oben; M2 und M3 gehen nur mit echtem Kontingent. 15. **Messungen M1 bis M3** siehe oben; M2 und M3 gehen nur mit echtem Kontingent.
16. **`updated` in `codeworkflow.md`** setzt der Koordinator beim Commit; Task 04 fasst das Frontmatter nicht an. `pending-review` entfällt, wenn Pascal Spec und Plan abnimmt, denn dann ist die Änderung angeordnet. 16. **`updated` in `codeworkflow.md`** setzt der Koordinator beim Commit; Task 04 fasst das Frontmatter nicht an. `pending-review` entfällt, wenn Pascal Spec und Plan abnimmt, denn dann ist die Änderung angeordnet.
## Abschluss 2026-10-05
| Fertig heißt | Ergebnis | Beleg |
|---|---|---|
| 1 bis 3 Werkzeug | erfüllt | `faelle_quorum_werkzeug.py` 44 von 44; Rot gegen die gesicherte alte Fassung genau die 5 erwarteten Fehler; `mutanten_quorum_werkzeug.py` 46 Mutanten, 0 überlebt, 0 veraltet |
| 4 bis 6 Workflow | erfüllt | `faelle_workflows.mjs` 248 Fälle, 0 gescheitert; Rot ohne `quorum.js` 88, mit alter Fassung 20, nach Task 06 5 erwartete Fehler; `mutanten_workflows.py` 145 Mutanten, 0 überlebt, 0 veraltet |
| 7 regelwerk_check | erfüllt | nur der alte Befund zu `frontmatter.md` |
| 8 Byte-Vergleich | erfüllt | je Welle Zieldateien byte-gleich mit der Kopie; `plan_bloecke nach` ohne Befund für Task 05 und 06 |
| 9 M1 | erfüllt | im ersten echten Lauf: `status` vor dem Lauf gelesen, Stimme gültig |
| 10 Lauf mit echten Daten | erfüllt | erster echter Lauf am 2026-09-30 in einem Code-Projekt des Vaults: drei gültige Stimmen, Codex 1 min 52 s, Urteil frei, Bericht über `ablegen` abgelegt |
| 11 Task 06 | erfüllt bis auf die App-Probe | Linkcheck findet im ersten Bericht keinen toten Link mehr; die Probe in Obsidian steht bei Pascal |
- **Prüfungen:** Welle 1 (Task 01, 02) durch einen `pruefer`, dann Task 03 bis 06 zusammen: 0 blockierend, 1 wichtig, 9 klein. Der wichtige Befund (Regel und Berichtsgrund beim blockierenden Befund ohne Beleg) ist behoben: Regel in `codeworkflow.md` und Grund in `quorum.js` nennen den Fall; das Verhalten bleibt nach Offenem Punkt 8.
- **Bewusst offen (klein):** Grenzen von `zeilen()`, `pfadnorm()` und `klartext()`, aufgeführt in `.vault-tools/quorum/LIESMICH.md`.
- **Vermerk „noch nicht abgenommen“** in `codeworkflow.md` am 2026-10-05 entfernt; Auslöser und Sperre gelten.
@@ -1535,6 +1535,91 @@ for (const [name, a, text] of [
}) })
} }
// 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 // ---------------------------------------------------------------- Lauf
let gescheitert = 0 let gescheitert = 0
const grundlinieGescheitert = new Set() const grundlinieGescheitert = new Set()
@@ -121,15 +121,15 @@ MUTANTEN = [
("quorum", "Zeilenbereich nur erste Zeile", "const bis = m[2] === undefined ? von : Number(m[2])", "const bis = von"), ("quorum", "Zeilenbereich nur erste Zeile", "const bis = m[2] === undefined ? von : Number(m[2])", "const bis = von"),
("quorum", "Stelle ohne Zeilen zaehlt als Zeile 0", "if (!m) return null", "if (!m) return [0, 0]"), ("quorum", "Stelle ohne Zeilen zaehlt als Zeile 0", "if (!m) return null", "if (!m) return [0, 0]"),
("quorum", "Befundzeilen der Gruppe fehlen", "for (const b of g.mitglieder) z.push(", "for (const b of []) z.push("), ("quorum", "Befundzeilen der Gruppe fehlen", "for (const b of g.mitglieder) z.push(", "for (const b of []) z.push("),
("quorum", "Stelle fehlt in der Befundzeile", ", ${b.schwere}, Stelle ${b.stelle}): ${b.aussage}", ", ${b.schwere}): ${b.aussage}"), ("quorum", "Stelle fehlt in der Befundzeile", ", ${b.schwere}, Stelle ${klartext(b.stelle)}): ${klartext(b.aussage)}", ", ${b.schwere}): ${klartext(b.aussage)}"),
("quorum", "Nachpruefungszeile fehlt", "if (g.nachpruefung) z.push(''", "if (false) z.push(''"), ("quorum", "Nachpruefungszeile fehlt", "if (g.nachpruefung) z.push(''", "if (false) z.push(''"),
("quorum", "Nachpruefung ohne Fundstelle", "${g.nachpruefung.urteil}, ${g.nachpruefung.fundstelle}.", "${g.nachpruefung.urteil}."), ("quorum", "Nachpruefung ohne Fundstelle", "${g.nachpruefung.urteil}, ${klartext(g.nachpruefung.fundstelle)}.", "${g.nachpruefung.urteil}."),
("quorum", "Unbelegt-Liste leer", "for (const b of x.unbelegt)", "for (const b of [])"), ("quorum", "Unbelegt-Liste leer", "for (const b of x.unbelegt)", "for (const b of [])"),
("quorum", "Nicht pruefbar leer", "(x.stimmen[n].nicht_pruefbar || [])", "([])"), ("quorum", "Nicht pruefbar leer", "(x.stimmen[n].nicht_pruefbar || [])", "([])"),
("quorum", "Hinweise des Laufs fehlen im Bericht", "const alleHinweise = [...p.hinweise, ...laufHinweise]", "const alleHinweise = []"), ("quorum", "Hinweise des Laufs fehlen im Bericht", "const alleHinweise = [...p.hinweise, ...laufHinweise]", "const alleHinweise = []"),
("quorum", "Lesemarke der Claude-Stimmen nicht geprueft", "lesemarke_ok: String(e.lesemarke).includes(p.lesemarke)", "lesemarke_ok: true"), ("quorum", "Lesemarke der Claude-Stimmen nicht geprueft", "lesemarke_ok: String(e.lesemarke).includes(p.lesemarke)", "lesemarke_ok: true"),
("quorum", "Lesemarke exakt verglichen", "lesemarke_ok: String(e.lesemarke).includes(p.lesemarke)", "lesemarke_ok: String(e.lesemarke) === p.lesemarke"), ("quorum", "Lesemarke exakt verglichen", "lesemarke_ok: String(e.lesemarke).includes(p.lesemarke)", "lesemarke_ok: String(e.lesemarke) === p.lesemarke"),
("quorum", "Stelle fehlt in der Gruppenueberschrift", ", Stelle ${erste.stelle}", ""), ("quorum", "Stelle fehlt in der Gruppenueberschrift", ", Stelle ${klartext(erste.stelle)}", ""),
("quorum", "Stand statt PAKET im Frontmatter", "'stand: PAKET'", "'stand: ' + p.stand"), ("quorum", "Stand statt PAKET im Frontmatter", "'stand: PAKET'", "'stand: ' + p.stand"),
("quorum", "ablegen_befehl fehlt", "ablegen_befehl: md ? ablegenBefehl(paket) : null", "ablegen_befehl: null"), ("quorum", "ablegen_befehl fehlt", "ablegen_befehl: md ? ablegenBefehl(paket) : null", "ablegen_befehl: null"),
("quorum", "ablegen_befehl auch ohne Bericht", "ablegen_befehl: md ? ablegenBefehl(paket) : null", "ablegen_befehl: ablegenBefehl(paket)"), ("quorum", "ablegen_befehl auch ohne Bericht", "ablegen_befehl: md ? ablegenBefehl(paket) : null", "ablegen_befehl: ablegenBefehl(paket)"),
@@ -149,6 +149,23 @@ MUTANTEN = [
("quorum", "Backslash im Ordner bleibt", "String(ordner).replace(/\\\\/g, '/')", "String(ordner)"), ("quorum", "Backslash im Ordner bleibt", "String(ordner).replace(/\\\\/g, '/')", "String(ordner)"),
("quorum", "Stimmen kennen einander", "Die anderen Stimmen kennst du nicht.", "Die anderen Stimmen sind Opus, Sonnet und Codex."), ("quorum", "Stimmen kennen einander", "Die anderen Stimmen kennst du nicht.", "Die anderen Stimmen sind Opus, Sonnet und Codex."),
("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", "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'))"),
] ]
+4 -6
View File
@@ -5,7 +5,7 @@ title: Codeworkflow
summary: Arbeitsweise für Code-Projekte — Standardschleife, Planaufteilung je Task, Subagenten und ihre Modellstufen und Effort, Gegenprüfung, Lauf mit echten Daten, Aufgabenmanagement. summary: Arbeitsweise für Code-Projekte — Standardschleife, Planaufteilung je Task, Subagenten und ihre Modellstufen und Effort, Gegenprüfung, Lauf mit echten Daten, Aufgabenmanagement.
tags: [regelwerk, codeworkflow, code-projekte] tags: [regelwerk, codeworkflow, code-projekte]
created: 2026-08-03 created: 2026-08-03
updated: 2026-09-30 updated: 2026-10-05
--- ---
# Codeworkflow # Codeworkflow
@@ -162,8 +162,6 @@ Seit 2026-09-26 auf Pascals Freigabe:
### Quorum am Meilenstein ### Quorum am Meilenstein
**Stand 2026-09-30: umgesetzt, aber noch nicht abgenommen.** Die Prüfung der letzten Nachbesserung und der erste echte Lauf stehen aus (Plan unter `.vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/`). Bis zur Abnahme läuft das Quorum nur auf Pascals Zuruf, und seine Sperre gilt noch nicht. Dieser Absatz fällt mit der Abnahme weg.
Pascals Entscheidung vom 2026-09-29. Was die Wellenprüfung übersehen hat, soll nicht in die Zielumgebung, auf master oder zum Kunden. Dafür prüfen drei Modelle aus zwei Familien unabhängig denselben Unterschied: Opus und Sonnet als `quorum-stimme`, dazu Codex über das Plugin. Die Wellenprüfung bleibt, das Quorum kommt dazu. Der Ablauf steht in `.claude/workflows/quorum.js`, die Werkzeuge unter `.vault-tools/quorum/`. Vor der Opus-Stimme liest der Workflow `verbrauch` ab; gebremst heißt kein Urteil und ein Neustart nach dem Fenster. Pascals Entscheidung vom 2026-09-29. Was die Wellenprüfung übersehen hat, soll nicht in die Zielumgebung, auf master oder zum Kunden. Dafür prüfen drei Modelle aus zwei Familien unabhängig denselben Unterschied: Opus und Sonnet als `quorum-stimme`, dazu Codex über das Plugin. Die Wellenprüfung bleibt, das Quorum kommt dazu. Der Ablauf steht in `.claude/workflows/quorum.js`, die Werkzeuge unter `.vault-tools/quorum/`. Vor der Opus-Stimme liest der Workflow `verbrauch` ab; gebremst heißt kein Urteil und ein Neustart nach dem Fenster.
**Auslöser, genau drei:** **Auslöser, genau drei:**
@@ -176,13 +174,13 @@ Regelwerk-Änderungen im Vault lösen kein Quorum aus.
**Kein Unterschied wird zweimal geprüft.** Der Lauf nimmt nur, was seit dem letzten Quorum dazukam: bei Code den Diff vom Stand des letzten Berichts bis HEAD, bei Dokumenten beim ersten Mal den ganzen Satz, danach die Dateien mit geänderter Prüfsumme. Das erste Quorum eines Code-Projekts braucht deshalb eine `basis`, etwa den Commit der letzten abgenommenen Version. Der Arbeitsbaum muss außerhalb von `docs/quorum/` sauber sein, weil die Stimmen ihn lesen. Ist der Unterschied leer, übernimmt der Lauf das Urteil des letzten Quorums, ohne eine Stimme zu starten. **Kein Unterschied wird zweimal geprüft.** Der Lauf nimmt nur, was seit dem letzten Quorum dazukam: bei Code den Diff vom Stand des letzten Berichts bis HEAD, bei Dokumenten beim ersten Mal den ganzen Satz, danach die Dateien mit geänderter Prüfsumme. Das erste Quorum eines Code-Projekts braucht deshalb eine `basis`, etwa den Commit der letzten abgenommenen Version. Der Arbeitsbaum muss außerhalb von `docs/quorum/` sauber sein, weil die Stimmen ihn lesen. Ist der Unterschied leer, übernimmt der Lauf das Urteil des letzten Quorums, ohne eine Stimme zu starten.
**Urteil.** Was zwei Stimmen an derselben Datei und Stelle melden, gilt, mit der höchsten Schwere der Gruppe. Einzelbefunde der Schwere blockierend und wichtig prüft ein Sonnet-Agent nach; Hinweise mit nur einer Stimme gehen ungeprüft in den Bericht. Ein Befund ohne Beleg zählt nicht für die Mehrheit. Frei heißt: kein bestätigter blockierender Befund und keiner, der ungeprüft blieb. Mit weniger als zwei abgegebenen Stimmen gibt es kein Urteil. **Urteil.** Was zwei Stimmen an derselben Datei und Stelle melden, gilt, mit der höchsten Schwere der Gruppe. Einzelbefunde der Schwere blockierend und wichtig prüft ein Sonnet-Agent nach; Hinweise mit nur einer Stimme gehen ungeprüft in den Bericht. Ein Befund ohne Beleg zählt nicht für die Mehrheit und wird nicht nachgeprüft. Frei heißt: kein bestätigter blockierender Befund und kein belegter, der ungeprüft blieb. Ein blockierender Befund ohne Beleg sperrt nicht; er steht im Bericht unter „Unbelegt“, und der Grund des Urteils nennt ihn. Lies ihn, bevor du einspielst. Mit weniger als zwei abgegebenen Stimmen gibt es kein Urteil.
**Ersatz.** Gibt Codex keine gültige Stimme ab, springt Haiku ein. Gültig heißt: Der Status des Plugins ist vor dem Lauf gelesen, der Job endet `completed`, sein Log ist gelesen und trägt kein „usage limit“, und die Antwort zitiert die Lesemarke aus dem Paket. Mit `codex: false` springt Haiku sofort ein, etwa wenn das Kontingent bekannt aufgebraucht ist. Der Bericht sagt, wenn Haiku statt Codex drinsteht. Steht das oft da, ist das Quorum faktisch nur mit Claude besetzt. **Ersatz.** Gibt Codex keine gültige Stimme ab, springt Haiku ein. Gültig heißt: Der Status des Plugins ist vor dem Lauf gelesen, der Job endet `completed`, sein Log ist gelesen und trägt kein „usage limit“, und die Antwort zitiert die Lesemarke aus dem Paket. Mit `codex: false` springt Haiku sofort ein, etwa wenn das Kontingent bekannt aufgebraucht ist. Der Bericht sagt, wenn Haiku statt Codex drinsteht. Steht das oft da, ist das Quorum faktisch nur mit Claude besetzt.
**Sperre.** Solange das Urteil nicht frei ist, also ein bestätigter oder ungeprüft gebliebener blockierender Befund offen ist oder der Lauf kein Urteil hat, gibt es kein Merge, keinen Push, kein Einspielen und keine Abgabe. Die Korrekturen entscheidest du, umgesetzt werden sie über die Standardschleife. Danach läuft kein neues Quorum: Eine `quorum-stimme` auf Sonnet, über das Agent-Werkzeug mit der Auftragsart Nachkontrolle gestartet, bestätigt je behobenem Befund, dass er behoben ist. Den Korrektur-Diff prüft das nächste Quorum mit, weil dessen Basis der Stand dieses Laufs ist. **Sperre.** Solange das Urteil nicht frei ist, also ein bestätigter oder ungeprüft gebliebener blockierender Befund mit Beleg offen ist oder der Lauf kein Urteil hat, gibt es kein Merge, keinen Push, kein Einspielen und keine Abgabe. Die Korrekturen entscheidest du, umgesetzt werden sie über die Standardschleife. Danach läuft kein neues Quorum: Eine `quorum-stimme` auf Sonnet, über das Agent-Werkzeug mit der Auftragsart Nachkontrolle gestartet, bestätigt je behobenem Befund, dass er behoben ist. Den Korrektur-Diff prüft das nächste Quorum mit, weil dessen Basis der Stand dieses Laufs ist.
**Bericht.** Der Workflow liefert `bericht_md`, `bericht_pfad` und `ablegen_befehl`. Du schreibst `bericht_md` in eine Datei und legst sie mit dem Befehl ab. Er setzt `stand` aus `paket.json` ein, damit die Liste der Prüfsummen durch kein Modell abgeschrieben werden muss. Danach committest du den Bericht: im Code-Projekt unter `docs/quorum/`, beim Kundendokument unter `quorum/` im Projektordner, Dateiname `JJJJ-MM-TT-<anlass>.md`. Der Bericht verlinkt README bzw. Übersicht. Sein Frontmatter trägt `nummer`, `basis` und `stand`, und daran setzt der nächste Lauf an. Ein Lauf ohne Urteil liefert keinen Bericht, die Basis bleibt dann die alte. Je Stimme stehen darin Befunde, bestätigte und Alleinfunde, also bestätigte Befunde, die nur diese Stimme hatte, dazu die Codex-Laufzeit und der Verbrauch vor und nach dem Lauf. **Bericht.** Der Workflow liefert `bericht_md`, `bericht_pfad` und `ablegen_befehl`. Du schreibst `bericht_md` in eine Datei und legst sie mit dem Befehl ab. Er setzt `stand` aus `paket.json` ein, damit die Liste der Prüfsummen durch kein Modell abgeschrieben werden muss. Danach committest du den Bericht: im Code-Projekt unter `docs/quorum/`, beim Kundendokument unter `quorum/` im übergebenen Dokumentordner, Dateiname `JJJJ-MM-TT-<anlass>.md`. Der Bericht verlinkt README bzw. Übersicht. Sein Frontmatter trägt `nummer`, `basis` und `stand`, und daran setzt der nächste Lauf an. Ein Lauf ohne Urteil liefert keinen Bericht, die Basis bleibt dann die alte. Je Stimme stehen darin Befunde, bestätigte und Alleinfunde, also bestätigte Befunde, die nur diese Stimme hatte, dazu die Codex-Laufzeit und der Verbrauch vor und nach dem Lauf.
**Was bleibt:** die Echtgeld-Regeln und die volle Schleife für Geld-, Sicherheits- und Netzpfade. Gekürzt wird erst, wenn die Ertragskurve es trägt. Nach fünf Quoren liest du die Berichte, schreibst die Auswertung als Dump und legst Pascal einen Vorschlag hin. Die Frage dabei: Findet das Quorum, was die Wellenprüfung übersieht, und welche Stimme trägt die Alleinfunde? Über Kürzungen entscheidet Pascal. **Was bleibt:** die Echtgeld-Regeln und die volle Schleife für Geld-, Sicherheits- und Netzpfade. Gekürzt wird erst, wenn die Ertragskurve es trägt. Nach fünf Quoren liest du die Berichte, schreibst die Auswertung als Dump und legst Pascal einen Vorschlag hin. Die Frage dabei: Findet das Quorum, was die Wellenprüfung übersieht, und welche Stimme trägt die Alleinfunde? Über Kürzungen entscheidet Pascal.