Heute bekommt jeder wikitool version bump sofort eine fixierte, dauerhafte Versionsnummer, unabhängig davon, ob je ein Release dazu erscheint. Fünf Minor-Bumps hintereinander ohne Release ergeben fünf verschiedene Nummern, von denen die älteren vier nie ausgeliefert wurden — Nummern werden verbraucht, ohne dass ein Release sie eingelöst hätte. Messbar: 66 Einträge in CHANGES.md, 4.3.3 nach vier Tagen; allein die Sitzung vom 2026-09-03 hat zwei Releases für zwei Prosa-Änderungen erzeugt.
Verschärft wird das durch das Version-Gate in ci.yml: jeder Push, der Stack-Pfade berührt, muss VERSION bewegen. Nummern werden damit in Commit-Granularität verbraucht, nicht in Release-Granularität.
Nachtrag 2026-09-03, nach dem Abschluss: die Formulierung „nie ausgeliefert" oben ist ungenau, und sie ist so auch in die 4.4.0-Prosa gewandert. Unter dem alten Modell wurde jeder Bump zu einem echten, getaggten Release, weil release.yml auf jede VERSION-Bewegung feuert. Der Schaden war kein Phantom-Release, sondern dass „Release" seine Bedeutung verlor: vier echte Releases in sechs Stunden (4.3.0–4.3.3), zwei davon für reine Prosa. Korrigiert in 4.4.1-beta.1; siehe #47 für den Prozessfehler, durch den das unbemerkt bleiben konnte.
Entscheidungen
Betreiber, 2026-09-02 — das Modell:
Der Stack führt zwischen zwei Releases einen laufenden Versionskandidaten, keinen neuen pro Bump.
Wächst der Umfang seit dem letzten Release, eskaliert der Kandidat entlang Patch → Minor → Major, statt eine neue Nummer danebenzustellen.
Während dieser Zeit läuft der Baum als Pre-Release mit SemVer-Suffix -beta.n.
Erst ein expliziter Release-Vorgang fixiert die Nummer und beendet die Pre-Release-Phase.
Betreiber, 2026-09-03 — die vier offenen Mechanikfragen:
Zustandsort:VERSION trägt den Suffix selbst (4.4.0-beta.3). Kein neues Zustandsfile. Der letzte Release ist der neueste suffixfreie ##-Eintrag in CHANGES.md; die Eskalationsstufe ist die Differenz zwischen Kandidatenbasis und letztem Release, also abgeleitet statt gespeichert. Damit behält CIs Version-Gate seinen Anker („VERSION hat sich bewegt") und version show sagt weiter, was tatsächlich installiert ist.
Eskalation: max-wins pro bump-Aufruf. Der Aufrufer deklariert weiterhin --patch/--minor/--major als Urteil; version-parts.md bleibt die Autorität dafür. Der Kandidat nimmt das Maximum gegen den letzten Release. Keine Klassifikation aus Commits oder Diff — version.py verwirft das im Modul-Docstring bereits mit dem richtigen Grund: publish schreibt Content-Commits in dasselbe Repo, jeder Ingest würde als Release gelesen.
Beta-Sichtbarkeit: Betas werden nie veröffentlicht. Pre-Release ist ein Zustand dieses Dev-Checkouts. release.yml feuert nur bei suffixfreier VERSION. Folge, die die Versionsstelle dieser Änderung trägt: eine ausgelieferte Instanz sieht nie ein Beta, also trifft ihr Parser nie eine VERSION-Form, die er nicht kennt.
Changelog-Form: ein einziger offener Eintrag pro Kandidat. Ein Heading ## X.Y.Z-beta.n - <datum> - <titel>; jeder Bump aktualisiert Version und Datum und hängt seinen --title an eine maschinenverwaltete Liste am Kopf des Eintrags. Die Prosa darunter bleibt Autorenarbeit. version notes druckt damit genau einen Eintrag pro Release.
Betreiber, 2026-09-03 (Nachtrag) — Menschendoku: der Release-Vorgang bekommt eine DEVELOPMENT.md im Repo-Root. Heute gibt es für die Erzeuger-Seite keine Menschendoku: INSTALL.md beschreibt nur, wie man ein Release konsumiert, README.md erwähnt Releases mit keinem Wort, und der Ablauf ist nur aus einem Agenten-Skill plus einer Workflow-Datei zusammenzulesen. Die Doku fährt in diesem Issue mit statt vorher, weil sich der Ablauf hier ändert — vorher geschrieben würde sie einen Prozess beschreiben, den es nach dieser Umsetzung nicht mehr gibt.
Die Mechanik konkret
VERSION hält entweder eine Release-Version X.Y.Z (direkt nach einem Release) oder einen Kandidaten X.Y.Z-beta.n.
version bump --patch|--minor|--major --title "…":
Liest den letzten Release R (neuester suffixfreier Changelog-Eintrag) und den aktuellen VERSION-Stand.
Bisherige Stufe s = die Stelle, um die die Kandidatenbasis über R hinausgeht; keine, wenn VERSION == R.
Neue Stufe = max(s, angeforderte Stufe) in der Ordnung patch < minor < major.
Neue Basis = R.bumped(neue Stufe). n = altes n + 1, bzw. 1 bei einem frisch eröffneten Kandidaten.
Schreibt VERSION, aktualisiert Heading und Titelliste des offenen Eintrags — oder legt den Eintrag an.
Randfall ohne letzten Release: eine frische Distribution hat einen Changelog ohne versionierten Eintrag (top_changes_version liefert dort heute schon None, und das ist ein gültiger Zustand). Dann gibt es kein R; die Kandidatenbasis ist VERSION.bumped(angeforderte Stufe) und die Eskalation startet bei dieser Stufe. Nicht als Fehler behandeln.
Die Eskalation ist monoton: nie automatisch zurück. Ein zurückgenommener Umfang senkt den Kandidaten nicht (siehe „Nicht in diesem Issue").
version release [--title "…"] [--dry-run] fixiert: streicht den Suffix aus VERSION und setzt das Heading auf ## X.Y.Z - <heute> - <titel>. Woher der Titel kommt: ohne --title bleibt der Titel stehen, den der letzte Bump gesetzt hat; mit --title wird er ersetzt. Das ist der Normalfall und kein Luxus — ein Kandidat hat mehrere Bumps mit je eigenem Titel gesammelt, und der Release will eine zusammenfassende Überschrift statt der zufällig letzten. Committet nicht und pusht nicht (Invariante 5) — der anschließende publish bewegt VERSION auf main und löst release.yml aus. Der nächste bump eröffnet dann einen neuen Eintrag, weil der oberste suffixfrei ist; dafür ist kein weiterer Zustand nötig.
Was das an bestehendem Code bricht
Vier Stellen, die das neue Modell still beschädigen würde. Sie sind der eigentliche Umfang dieses Issues, nicht die Suffix-Arithmetik.
#
Stelle
Was passiert
Fix
1
.gitea/workflows/release.yml
Trigger ist paths: [VERSION] auf main. Unter dem neuen Modell feuert das bei jedem Bump und veröffentlicht Betas.
Guard im Schritt „Resolve the version", vor dessen API-Abfrage: enthält VERSION einen Suffix, endet der Job sauber (Skip, kein Fail). Die Reihenfolge ist nicht kosmetisch — die bestehende releases/tags/-Abfrage würde sonst für jeden Beta-Bump eine Runde gegen die API drehen.
Beide vergleichen compat_key des obersten mit dem des zweitobersten Eintrags. Zwischen 4.4.0-beta.2 und 4.4.0-beta.1 liegt keine Grenze — der Breaking-Change-Zwang wäre faktisch abgeschaltet.
„Vorgänger" wird der letzte Release, nicht der vorige Eintrag. Regressionstest ist Pflicht.
3
kb_state.chain() / next_link()
SemVer ordnet 4.4.0-beta.1 < 4.4.0, also fällt eine Migration mit Ziel 4.4.0 aus dem Intervall (kb_version, stack_version]. Die Instanz hat die Maschinerie und bekommt die Migration nicht angesagt.
Migrationsziele gegen die Kandidatenbasis vergleichen. read_kb_version() verweigert einen Prerelease: eine Inhaltsform hat kein Beta.
Der Grenzübertritt passiert jetzt in dem Bump, der nach MAJOR eskaliert, nicht mehr an einer festen Nummer.
Pflichtangabe greift beim Eskalationsbump; die Zeile bleibt über spätere Bumps im offenen Eintrag stehen, ein Folgebump muss sie nicht wiederholen. version-parts.md Schritt 4 (Gespräch mit dem Betreiber vor einem Grenzübertritt) gilt ab jetzt an genau diesem Punkt.
Nicht betroffen, geprüft: das Version-Gate in ci.yml bleibt gültig, weil VERSION bei jedem Bump durch n+1 weiterhin wandert. Das ist beabsichtigt und soll so bleiben. Auch version release passiert das Gate — es prüft nur „Stack-Pfad ohne VERSION-Bewegung", nicht die Gegenrichtung.
Umsetzungsplan
Alle neun Schritte umgesetzt: version.py (Datentyp/Ordnung, Kandidatenlogik), version_cmd.py (bump umgestellt, release neu), docs_verify.py/kb_state.py/dist_cmd.py/doctor.py (alle vier Funde behoben), release.yml-Guard, Agenten-Doku (version-parts.md mit neuem Abschnitt „The candidate model" und angepassten Schritten 4–8, tools/CONTRACT.md, CHANGES.md-Kopf, docs/version-model.md), DEVELOPMENT.md neu, Version-Bump dieser Änderung selbst.
Tests
Gilt instructions/dev/testing-conventions.md. Alle Fälle aus der ursprünglichen Testliste (Ordnung inkl. numerischem Beta-Vergleich, max-wins-Eskalation, Randfall ohne letzten Release, --breaking-Persistenz über Folgebumps, beide Regressionen für Fund 2 und 3, version release mit/ohne --title, version notes für einen Kandidaten) haben einen Test in test_version_cmd.py, test_docs_verify.py, test_migrate_cmd.py und test_doctor.py. Der release.yml-Guard bleibt wie geplant ungetestet (Shell in CI).
Akzeptanzkriterien
Version versteht -beta.n: Parsing, totale Ordnung, base, compat_key über die Basis. Tests dafür.
version bump führt einen laufenden Kandidaten mit max-wins-Eskalation gegen den letzten Release; n zählt pro Bump. Tests dafür, inklusive Randfall ohne letzten Release.
CHANGES.md trägt genau einen offenen Eintrag pro Kandidat, mit maschinenverwalteter Titelliste in <!-- wikitool:bumps -->-Markern.
version release fixiert die Nummer, beendet die Pre-Release-Phase, committet nichts, und behandelt --title wie oben beschrieben. Test dafür.
Fund 1: release.yml überspringt eine Kandidaten-VERSION sauber, vor der API-Abfrage.
Fund 2: Grenzübertritts-Checks in docs verify messen gegen den letzten Release. Regressionstest.
Fund 3: Migrationskette und kb_version rechnen gegen die Kandidatenbasis. Regressionstest.
Fund 4: --breaking/--no-migration greifen am Eskalationsbump und bleiben über Folgebumps erhalten.
tools/CONTRACT.md: Zeile für version release in Kommandotabelle und Fehlerkontrakt.
instructions/dev/version-parts.md, CHANGES.md-Kopf und docs/version-model.md beschreiben die neue Semantik.
DEVELOPMENT.md existiert, beschreibt den Release-Ablauf unter dem neuen Modell, ist bewusst nicht in dist_cmd.ROOT_FILES (mit Begründung im Code), hat eine Zeile in AGENTS.md § File naming und einen Zeiger aus README.md.
Version-Bump nach der neuen Regel: MINOR, 4.3.3 → 4.4.0-beta.1 → version release → 4.4.0. Changelog-Eintrag geschrieben.
docs verify, instructions verify, pytest grün; CI grün inklusive dist-export-Replay.
Umsetzung und Ausrollen (Stand 2026-09-03, abgeschlossen)
Vollständig umgesetzt, gepublisht, verifiziert. Publish freigegeben durch den Betreiber (Mass-Update Gate, 21 Dateien, Token 815a67d6a9fd), Commit d29d400 auf origin/main. Verifiziert:
CI (ci.yml, Run #91, verify): success - volle Testsuite, docs verify, instructions verify, dist export-Replay gegen eine frische Instanz, alles grün gegen den gepushten Stand.
Release (release.yml, Run #92, release): success - VERSION war beim Push bereits release-fixiert (4.4.0, kein Suffix mehr), der Guard aus Fund 1 hat den Job also nicht übersprungen, sondern regulär durchlaufen lassen.
Release-Artefakt:v4.4.0 existiert (Release-ID 88, veröffentlicht 2026-09-03T20:22:38Z), mit Tarball und Prüfsumme.
Damit ist der komplette Ablauf einmal live durchlaufen: Bump → Prosa → Verify → version release → Publish (mit Gate-Freigabe) → CI → Release.
Nachspiel, gleicher Tag: die gesamte Prosa dieser Umsetzung entstand ohne den Modellwechsel, den stack-dev Schritt 6 dafür vorsieht — der Break wurde übersprungen. Ein nachgeholter Durchgang (4.4.1-beta.1) hat darin einen echten Faktenfehler gefunden, der sowohl im Changelog als auch in docs/version-model.md stand (siehe Nachtrag ganz oben), dazu eine veraltete 2.x-Angabe in version-parts.md und eine überflüssige Kommandotabelle in DEVELOPMENT.md. Der Prozessfehler dahinter ist #47.
Abhängigkeiten
#7 (wikitool dist upgrade) war status/blocked auf dieses Issue. Die Kopplung ist nach Fund 3 enger als ursprünglich notiert: sowohl die Drei-Wege-Klassifikation in #7 als auch die Migrationskette hängen daran, dass ein Prerelease auf seine Basis zurückgeführt wird, bevor verglichen wird. Dieses Issue ist erledigt, #7 ist entsperrt.
Nicht in diesem Issue
Release-Drafter / automatisierte Release-Notes aus PRs — hat seit 2026-09-03 ein eigenes Issue: #46, prio/waiting, Auslöser ist die PR-Einführung.
De-Eskalation eines Kandidaten. Ein zurückgenommener Umfang senkt die Stufe nicht. Kein Flag dafür — wenn es je gebraucht wird, ist es ein bewusster Eingriff und ein eigenes Issue.
Modell entschieden in der Diskussion vom 2026-09-02, im Rahmen der Aufarbeitung von #38, #30, #28, #7, #27 als Fallouts des Entwicklungsprozesses. Siehe auch #41 (Issue-Management), das denselben Sitzungsblock eröffnet hat. Die vier Mechanikfragen und die vier Funde am bestehenden Code stammen vom 2026-09-03; die DEVELOPMENT.md-Anforderung und die Bereitschaftsprüfung vom selben Tag, nach dem Landen von 4.3.2 und 4.3.3. Umsetzung, Publish und Release ebenfalls vom 2026-09-03, im direkten Anschluss.
## Befund
Heute bekommt jeder `wikitool version bump` sofort eine fixierte, dauerhafte Versionsnummer, unabhängig davon, ob je ein Release dazu erscheint. Fünf Minor-Bumps hintereinander ohne Release ergeben fünf verschiedene Nummern, von denen die älteren vier nie ausgeliefert wurden — Nummern werden verbraucht, ohne dass ein Release sie eingelöst hätte. Messbar: 66 Einträge in `CHANGES.md`, `4.3.3` nach vier Tagen; allein die Sitzung vom 2026-09-03 hat zwei Releases für zwei Prosa-Änderungen erzeugt.
Verschärft wird das durch das Version-Gate in `ci.yml`: jeder Push, der Stack-Pfade berührt, muss `VERSION` bewegen. Nummern werden damit in Commit-Granularität verbraucht, nicht in Release-Granularität.
> **Nachtrag 2026-09-03, nach dem Abschluss:** die Formulierung „nie ausgeliefert" oben ist ungenau, und sie ist so auch in die 4.4.0-Prosa gewandert. Unter dem alten Modell wurde **jeder** Bump zu einem echten, getaggten Release, weil `release.yml` auf jede `VERSION`-Bewegung feuert. Der Schaden war kein Phantom-Release, sondern dass „Release" seine Bedeutung verlor: vier echte Releases in sechs Stunden (`4.3.0`–`4.3.3`), zwei davon für reine Prosa. Korrigiert in 4.4.1-beta.1; siehe #47 für den Prozessfehler, durch den das unbemerkt bleiben konnte.
## Entscheidungen
**Betreiber, 2026-09-02 — das Modell:**
- Der Stack führt zwischen zwei Releases **einen** laufenden Versionskandidaten, keinen neuen pro Bump.
- Wächst der Umfang seit dem letzten Release, eskaliert der Kandidat entlang Patch → Minor → Major, statt eine neue Nummer danebenzustellen.
- Während dieser Zeit läuft der Baum als Pre-Release mit SemVer-Suffix `-beta.n`.
- Erst ein expliziter Release-Vorgang fixiert die Nummer und beendet die Pre-Release-Phase.
**Betreiber, 2026-09-03 — die vier offenen Mechanikfragen:**
1. **Zustandsort:** `VERSION` trägt den Suffix selbst (`4.4.0-beta.3`). Kein neues Zustandsfile. Der letzte Release ist der neueste suffixfreie `##`-Eintrag in `CHANGES.md`; die Eskalationsstufe ist die Differenz zwischen Kandidatenbasis und letztem Release, also abgeleitet statt gespeichert. Damit behält CIs Version-Gate seinen Anker („VERSION hat sich bewegt") und `version show` sagt weiter, was tatsächlich installiert ist.
2. **Eskalation:** max-wins pro `bump`-Aufruf. Der Aufrufer deklariert weiterhin `--patch/--minor/--major` als Urteil; `version-parts.md` bleibt die Autorität dafür. Der Kandidat nimmt das Maximum gegen den letzten Release. Keine Klassifikation aus Commits oder Diff — `version.py` verwirft das im Modul-Docstring bereits mit dem richtigen Grund: `publish` schreibt Content-Commits in dasselbe Repo, jeder Ingest würde als Release gelesen.
3. **Beta-Sichtbarkeit:** Betas werden **nie** veröffentlicht. Pre-Release ist ein Zustand dieses Dev-Checkouts. `release.yml` feuert nur bei suffixfreier `VERSION`. Folge, die die Versionsstelle dieser Änderung trägt: eine ausgelieferte Instanz sieht nie ein Beta, also trifft ihr Parser nie eine `VERSION`-Form, die er nicht kennt.
4. **Changelog-Form:** ein einziger offener Eintrag pro Kandidat. Ein Heading `## X.Y.Z-beta.n - <datum> - <titel>`; jeder Bump aktualisiert Version und Datum und hängt seinen `--title` an eine maschinenverwaltete Liste am Kopf des Eintrags. Die Prosa darunter bleibt Autorenarbeit. `version notes` druckt damit genau einen Eintrag pro Release.
**Betreiber, 2026-09-03 (Nachtrag) — Menschendoku:** der Release-Vorgang bekommt eine **`DEVELOPMENT.md`** im Repo-Root. Heute gibt es für die *Erzeuger*-Seite keine Menschendoku: `INSTALL.md` beschreibt nur, wie man ein Release konsumiert, `README.md` erwähnt Releases mit keinem Wort, und der Ablauf ist nur aus einem Agenten-Skill plus einer Workflow-Datei zusammenzulesen. Die Doku fährt in diesem Issue mit statt vorher, weil sich der Ablauf hier ändert — vorher geschrieben würde sie einen Prozess beschreiben, den es nach dieser Umsetzung nicht mehr gibt.
## Die Mechanik konkret
`VERSION` hält entweder eine Release-Version `X.Y.Z` (direkt nach einem Release) oder einen Kandidaten `X.Y.Z-beta.n`.
`version bump --patch|--minor|--major --title "…"`:
1. Liest den letzten Release `R` (neuester suffixfreier Changelog-Eintrag) und den aktuellen `VERSION`-Stand.
2. Bisherige Stufe `s` = die Stelle, um die die Kandidatenbasis über `R` hinausgeht; keine, wenn `VERSION == R`.
3. Neue Stufe = `max(s, angeforderte Stufe)` in der Ordnung patch < minor < major.
4. Neue Basis = `R.bumped(neue Stufe)`. `n` = altes `n` + 1, bzw. 1 bei einem frisch eröffneten Kandidaten.
5. Schreibt `VERSION`, aktualisiert Heading und Titelliste des offenen Eintrags — oder legt den Eintrag an.
**Randfall ohne letzten Release:** eine frische Distribution hat einen Changelog ohne versionierten Eintrag (`top_changes_version` liefert dort heute schon `None`, und das ist ein gültiger Zustand). Dann gibt es kein `R`; die Kandidatenbasis ist `VERSION.bumped(angeforderte Stufe)` und die Eskalation startet bei dieser Stufe. Nicht als Fehler behandeln.
Die Eskalation ist **monoton**: nie automatisch zurück. Ein zurückgenommener Umfang senkt den Kandidaten nicht (siehe „Nicht in diesem Issue").
`version release [--title "…"] [--dry-run]` fixiert: streicht den Suffix aus `VERSION` und setzt das Heading auf `## X.Y.Z - <heute> - <titel>`. **Woher der Titel kommt:** ohne `--title` bleibt der Titel stehen, den der letzte Bump gesetzt hat; mit `--title` wird er ersetzt. Das ist der Normalfall und kein Luxus — ein Kandidat hat mehrere Bumps mit je eigenem Titel gesammelt, und der Release will eine zusammenfassende Überschrift statt der zufällig letzten. Committet nicht und pusht nicht (Invariante 5) — der anschließende `publish` bewegt `VERSION` auf `main` und löst `release.yml` aus. Der nächste `bump` eröffnet dann einen neuen Eintrag, weil der oberste suffixfrei ist; dafür ist kein weiterer Zustand nötig.
## Was das an bestehendem Code bricht
Vier Stellen, die das neue Modell still beschädigen würde. Sie sind der eigentliche Umfang dieses Issues, nicht die Suffix-Arithmetik.
| # | Stelle | Was passiert | Fix |
|---|---|---|---|
| 1 | `.gitea/workflows/release.yml` | Trigger ist `paths: [VERSION]` auf `main`. Unter dem neuen Modell feuert das bei **jedem** Bump und veröffentlicht Betas. | Guard im Schritt „Resolve the version", **vor** dessen API-Abfrage: enthält `VERSION` einen Suffix, endet der Job sauber (Skip, **kein** Fail). Die Reihenfolge ist nicht kosmetisch — die bestehende `releases/tags/`-Abfrage würde sonst für jeden Beta-Bump eine Runde gegen die API drehen. |
| 2 | `docs_verify.check_breaking_change_for_boundary` / `check_migration_for_boundary` | Beide vergleichen `compat_key` des obersten mit dem des zweitobersten Eintrags. Zwischen `4.4.0-beta.2` und `4.4.0-beta.1` liegt keine Grenze — der Breaking-Change-Zwang wäre faktisch abgeschaltet. | „Vorgänger" wird der **letzte Release**, nicht der vorige Eintrag. Regressionstest ist Pflicht. |
| 3 | `kb_state.chain()` / `next_link()` | SemVer ordnet `4.4.0-beta.1 < 4.4.0`, also fällt eine Migration mit Ziel `4.4.0` aus dem Intervall `(kb_version, stack_version]`. Die Instanz hat die Maschinerie und bekommt die Migration nicht angesagt. | Migrationsziele gegen die **Kandidatenbasis** vergleichen. `read_kb_version()` verweigert einen Prerelease: eine Inhaltsform hat kein Beta. |
| 4 | `version_cmd.bump_command`, `--breaking` / `--no-migration` | Der Grenzübertritt passiert jetzt in dem Bump, der nach MAJOR eskaliert, nicht mehr an einer festen Nummer. | Pflichtangabe greift beim Eskalationsbump; die Zeile bleibt über spätere Bumps im offenen Eintrag stehen, ein Folgebump muss sie nicht wiederholen. `version-parts.md` Schritt 4 (Gespräch mit dem Betreiber vor einem Grenzübertritt) gilt ab jetzt an genau diesem Punkt. |
Nicht betroffen, geprüft: das Version-Gate in `ci.yml` bleibt gültig, weil `VERSION` bei jedem Bump durch `n+1` weiterhin wandert. Das ist beabsichtigt und soll so bleiben. Auch `version release` passiert das Gate — es prüft nur „Stack-Pfad ohne VERSION-Bewegung", nicht die Gegenrichtung.
## Umsetzungsplan
Alle neun Schritte umgesetzt: `version.py` (Datentyp/Ordnung, Kandidatenlogik), `version_cmd.py` (`bump` umgestellt, `release` neu), `docs_verify.py`/`kb_state.py`/`dist_cmd.py`/`doctor.py` (alle vier Funde behoben), `release.yml`-Guard, Agenten-Doku (`version-parts.md` mit neuem Abschnitt „The candidate model" und angepassten Schritten 4–8, `tools/CONTRACT.md`, `CHANGES.md`-Kopf, `docs/version-model.md`), `DEVELOPMENT.md` neu, Version-Bump dieser Änderung selbst.
## Tests
Gilt `instructions/dev/testing-conventions.md`. Alle Fälle aus der ursprünglichen Testliste (Ordnung inkl. numerischem Beta-Vergleich, max-wins-Eskalation, Randfall ohne letzten Release, `--breaking`-Persistenz über Folgebumps, beide Regressionen für Fund 2 und 3, `version release` mit/ohne `--title`, `version notes` für einen Kandidaten) haben einen Test in `test_version_cmd.py`, `test_docs_verify.py`, `test_migrate_cmd.py` und `test_doctor.py`. Der `release.yml`-Guard bleibt wie geplant ungetestet (Shell in CI).
## Akzeptanzkriterien
- [x] `Version` versteht `-beta.n`: Parsing, totale Ordnung, `base`, `compat_key` über die Basis. Tests dafür.
- [x] `version bump` führt einen laufenden Kandidaten mit max-wins-Eskalation gegen den letzten Release; `n` zählt pro Bump. Tests dafür, inklusive Randfall ohne letzten Release.
- [x] `CHANGES.md` trägt genau einen offenen Eintrag pro Kandidat, mit maschinenverwalteter Titelliste in `<!-- wikitool:bumps -->`-Markern.
- [x] `version release` fixiert die Nummer, beendet die Pre-Release-Phase, committet nichts, und behandelt `--title` wie oben beschrieben. Test dafür.
- [x] Fund 1: `release.yml` überspringt eine Kandidaten-`VERSION` sauber, vor der API-Abfrage.
- [x] Fund 2: Grenzübertritts-Checks in `docs verify` messen gegen den letzten Release. Regressionstest.
- [x] Fund 3: Migrationskette und `kb_version` rechnen gegen die Kandidatenbasis. Regressionstest.
- [x] Fund 4: `--breaking`/`--no-migration` greifen am Eskalationsbump und bleiben über Folgebumps erhalten.
- [x] `tools/CONTRACT.md`: Zeile für `version release` in Kommandotabelle **und** Fehlerkontrakt.
- [x] `instructions/dev/version-parts.md`, `CHANGES.md`-Kopf und `docs/version-model.md` beschreiben die neue Semantik.
- [x] **`DEVELOPMENT.md` existiert**, beschreibt den Release-Ablauf unter dem neuen Modell, ist bewusst **nicht** in `dist_cmd.ROOT_FILES` (mit Begründung im Code), hat eine Zeile in `AGENTS.md` § File naming und einen Zeiger aus `README.md`.
- [x] Version-Bump nach der neuen Regel: MINOR, `4.3.3` → `4.4.0-beta.1` → `version release` → `4.4.0`. Changelog-Eintrag geschrieben.
- [x] `docs verify`, `instructions verify`, `pytest` grün; CI grün inklusive dist-export-Replay.
## Umsetzung und Ausrollen (Stand 2026-09-03, abgeschlossen)
**Vollständig umgesetzt, gepublisht, verifiziert.** Publish freigegeben durch den Betreiber (Mass-Update Gate, 21 Dateien, Token `815a67d6a9fd`), Commit `d29d400` auf `origin/main`. Verifiziert:
- **CI** (`ci.yml`, Run [#91](https://gitea.nehmer.net/torben/chemenu/actions/runs/136), `verify`): `success` - volle Testsuite, `docs verify`, `instructions verify`, `dist export`-Replay gegen eine frische Instanz, alles grün gegen den gepushten Stand.
- **Release** (`release.yml`, Run [#92](https://gitea.nehmer.net/torben/chemenu/actions/runs/137), `release`): `success` - `VERSION` war beim Push bereits release-fixiert (`4.4.0`, kein Suffix mehr), der Guard aus Fund 1 hat den Job also nicht übersprungen, sondern regulär durchlaufen lassen.
- **Release-Artefakt:** [v4.4.0](https://gitea.nehmer.net/torben/chemenu/releases/tag/v4.4.0) existiert (Release-ID 88, veröffentlicht 2026-09-03T20:22:38Z), mit Tarball und Prüfsumme.
Damit ist der komplette Ablauf einmal live durchlaufen: Bump → Prosa → Verify → `version release` → Publish (mit Gate-Freigabe) → CI → Release.
**Nachspiel, gleicher Tag:** die gesamte Prosa dieser Umsetzung entstand ohne den Modellwechsel, den `stack-dev` Schritt 6 dafür vorsieht — der Break wurde übersprungen. Ein nachgeholter Durchgang (4.4.1-beta.1) hat darin einen echten Faktenfehler gefunden, der sowohl im Changelog als auch in `docs/version-model.md` stand (siehe Nachtrag ganz oben), dazu eine veraltete `2.x`-Angabe in `version-parts.md` und eine überflüssige Kommandotabelle in `DEVELOPMENT.md`. Der Prozessfehler dahinter ist #47.
## Abhängigkeiten
#7 (`wikitool dist upgrade`) war `status/blocked` auf dieses Issue. Die Kopplung ist nach Fund 3 enger als ursprünglich notiert: sowohl die Drei-Wege-Klassifikation in #7 als auch die Migrationskette hängen daran, dass ein Prerelease auf seine Basis zurückgeführt wird, bevor verglichen wird. **Dieses Issue ist erledigt, #7 ist entsperrt.**
## Nicht in diesem Issue
- **Release-Drafter / automatisierte Release-Notes aus PRs** — hat seit 2026-09-03 ein eigenes Issue: **#46**, `prio/waiting`, Auslöser ist die PR-Einführung.
- **De-Eskalation eines Kandidaten.** Ein zurückgenommener Umfang senkt die Stufe nicht. Kein Flag dafür — wenn es je gebraucht wird, ist es ein bewusster Eingriff und ein eigenes Issue.
- **Ausgelieferte Betas.** Entschieden gegen; siehe Entscheidung 3.
## Vorgeschichte
Modell entschieden in der Diskussion vom 2026-09-02, im Rahmen der Aufarbeitung von #38, #30, #28, #7, #27 als Fallouts des Entwicklungsprozesses. Siehe auch #41 (Issue-Management), das denselben Sitzungsblock eröffnet hat. Die vier Mechanikfragen und die vier Funde am bestehenden Code stammen vom 2026-09-03; die `DEVELOPMENT.md`-Anforderung und die Bereitschaftsprüfung vom selben Tag, nach dem Landen von 4.3.2 und 4.3.3. Umsetzung, Publish und Release ebenfalls vom 2026-09-03, im direkten Anschluss.
Update 2026-09-03: #39/#40 sind gelandet, ohne direkten Einfluss auf die hier offenen Mechanik-Fragen (Zustandsort, Eskalationsberechnung, Pre-Release-Vergleichslogik). Position in der Reihenfolge unverändert: nach #28/#38, vor #30/#7 - #7 hängt direkt an diesem Issue.
**Update 2026-09-03:** #39/#40 sind gelandet, ohne direkten Einfluss auf die hier offenen Mechanik-Fragen (Zustandsort, Eskalationsberechnung, Pre-Release-Vergleichslogik). Position in der Reihenfolge unverändert: nach #28/#38, vor #30/#7 - #7 hängt direkt an diesem Issue.
Changelog: Body von Befund+offenen Fragen auf einen umsetzbaren Plan umgeschrieben.
Neu: alle vier offenen Mechanikfragen entschieden (VERSION trägt den Suffix, max-wins pro Bump-Aufruf, Betas werden nie veröffentlicht, ein offener Changelog-Eintrag pro Kandidat), dazu die konkrete Bump-Arithmetik, ein achtstufiger Umsetzungsplan mit bindender Reihenfolge, eine Testliste und ein Abschnitt „Nicht in diesem Issue".
Vier Funde am bestehenden Code ergänzt, die vorher in keiner Anforderung standen: release.yml würde unter dem neuen Modell bei jedem Bump feuern und Betas veröffentlichen; die beiden Grenzübertritts-Checks in docs verify vergleichen den obersten mit dem zweitobersten Eintrag und wären zwischen zwei Betas faktisch abgeschaltet; kb_state.chain() lässt eine Migration mit Ziel 4.4.0 bei installiertem 4.4.0-beta.1 aus dem Intervall fallen; --breaking/--no-migration haben unter Eskalation keinen festen Zeitpunkt mehr. Fund 2 und 3 sind Regressionstests im Akzeptanzkriterienblock.
Alte Abschnitte „Offene Punkte" und die Kurzform der Akzeptanzkriterien entfallen — ersetzt durch Entscheidungen, Plan und die neue, geschnittene Kriterienliste. Release-Drafter-Recherche ist kein Kriterium mehr, sondern explizit zurückgestellt (Trigger: PR-Einführung), noch ohne eigenes Issue.
kind/decision → kind/build: die offenen Entscheidungen sind getroffen, es fehlt nur noch Umsetzungszeit. prio/planned und size/L unverändert.
**Changelog:** Body von Befund+offenen Fragen auf einen umsetzbaren Plan umgeschrieben.
Neu: alle vier offenen Mechanikfragen entschieden (VERSION trägt den Suffix, max-wins pro Bump-Aufruf, Betas werden nie veröffentlicht, ein offener Changelog-Eintrag pro Kandidat), dazu die konkrete Bump-Arithmetik, ein achtstufiger Umsetzungsplan mit bindender Reihenfolge, eine Testliste und ein Abschnitt „Nicht in diesem Issue".
Vier Funde am bestehenden Code ergänzt, die vorher in keiner Anforderung standen: `release.yml` würde unter dem neuen Modell bei jedem Bump feuern und Betas veröffentlichen; die beiden Grenzübertritts-Checks in `docs verify` vergleichen den obersten mit dem zweitobersten Eintrag und wären zwischen zwei Betas faktisch abgeschaltet; `kb_state.chain()` lässt eine Migration mit Ziel `4.4.0` bei installiertem `4.4.0-beta.1` aus dem Intervall fallen; `--breaking`/`--no-migration` haben unter Eskalation keinen festen Zeitpunkt mehr. Fund 2 und 3 sind Regressionstests im Akzeptanzkriterienblock.
Alte Abschnitte „Offene Punkte" und die Kurzform der Akzeptanzkriterien entfallen — ersetzt durch Entscheidungen, Plan und die neue, geschnittene Kriterienliste. Release-Drafter-Recherche ist kein Kriterium mehr, sondern explizit zurückgestellt (Trigger: PR-Einführung), noch ohne eigenes Issue.
`kind/decision` → `kind/build`: die offenen Entscheidungen sind getroffen, es fehlt nur noch Umsetzungszeit. `prio/planned` und `size/L` unverändert.
Changelog:DEVELOPMENT.md als Anforderung aufgenommen, Bereitschaftsprüfung durchgeführt, drei dabei gefundene Lücken geschlossen.
Neu als Plan-Schritt 8 und Akzeptanzkriterium: DEVELOPMENT.md im Repo-Root für die Erzeuger-Seite des Release-Vorgangs — mit den vier Nebenbedingungen (bewusst nicht in dist_cmd.ROOT_FILES, Begründung im Code; darf deshalb nach instructions/dev/ verlinken; Zeile in AGENTS.md § File naming; Zeiger aus README.md). Alter Schritt 8 ist jetzt 9.
Bei der Bereitschaftsprüfung aufgefallen und ergänzt:
Marker-Konvention war unbestimmt. „Generierte Region wie seit 4.0.0" hätte eine frische Sitzung raten lassen. Jetzt festgenagelt auf <!-- wikitool:<name> --> aus blocks.py, mit der ausdrücklichen Auflage, blocks.BLOCKSnicht zu erweitern — dessen Namen speisen xref, cite und den Lint-Check unbalanced_markers, und CHANGES.md ist keine Seite.
Titelherkunft bei version release fehlte. Ein Kandidat sammelt mehrere Bump-Titel; welcher im Release-Heading landet, stand nirgends. Jetzt: --title optional, ohne ihn bleibt der letzte Bump-Titel stehen.
Randfall ohne letzten Release fehlte. Eine frische Distribution hat keinen versionierten Changelog-Eintrag. Jetzt als Mechanik und als Test benannt.
Kleinere Präzisierungen: Guard in release.yml muss vor die API-Abfrage (sonst dreht jeder Beta-Bump eine Runde gegen die API); Ordnungstest beta.9 < beta.10 ergänzt (numerisch, nicht lexikografisch); version release passiert das CI-Version-Gate, geprüft.
Neuer Abschnitt „Bereitschaft": umsetzungsbereit, Body ist die vollständige Übergabe für eine frische Sitzung. Empfehlung zur Sitzungsführung ergänzt — Design ist erledigt, also direkt in der mechanischen Phase starten (Sonnet/high nach 4.3.3).
**Changelog:** `DEVELOPMENT.md` als Anforderung aufgenommen, Bereitschaftsprüfung durchgeführt, drei dabei gefundene Lücken geschlossen.
Neu als Plan-Schritt 8 und Akzeptanzkriterium: `DEVELOPMENT.md` im Repo-Root für die Erzeuger-Seite des Release-Vorgangs — mit den vier Nebenbedingungen (bewusst **nicht** in `dist_cmd.ROOT_FILES`, Begründung im Code; darf deshalb nach `instructions/dev/` verlinken; Zeile in `AGENTS.md` § File naming; Zeiger aus `README.md`). Alter Schritt 8 ist jetzt 9.
Bei der Bereitschaftsprüfung aufgefallen und ergänzt:
- **Marker-Konvention war unbestimmt.** „Generierte Region wie seit 4.0.0" hätte eine frische Sitzung raten lassen. Jetzt festgenagelt auf `<!-- wikitool:<name> -->` aus `blocks.py`, mit der ausdrücklichen Auflage, `blocks.BLOCKS` **nicht** zu erweitern — dessen Namen speisen `xref`, `cite` und den Lint-Check `unbalanced_markers`, und `CHANGES.md` ist keine Seite.
- **Titelherkunft bei `version release` fehlte.** Ein Kandidat sammelt mehrere Bump-Titel; welcher im Release-Heading landet, stand nirgends. Jetzt: `--title` optional, ohne ihn bleibt der letzte Bump-Titel stehen.
- **Randfall ohne letzten Release fehlte.** Eine frische Distribution hat keinen versionierten Changelog-Eintrag. Jetzt als Mechanik und als Test benannt.
Kleinere Präzisierungen: Guard in `release.yml` muss **vor** die API-Abfrage (sonst dreht jeder Beta-Bump eine Runde gegen die API); Ordnungstest `beta.9 < beta.10` ergänzt (numerisch, nicht lexikografisch); `version release` passiert das CI-Version-Gate, geprüft.
Neuer Abschnitt „Bereitschaft": **umsetzungsbereit**, Body ist die vollständige Übergabe für eine frische Sitzung. Empfehlung zur Sitzungsführung ergänzt — Design ist erledigt, also direkt in der mechanischen Phase starten (Sonnet/high nach 4.3.3).
Changelog: Umsetzung abgeschlossen, alle Akzeptanzkriterien erfüllt und angehakt.
Umgesetzt wie geplant: Version mit -beta.n, escalate() (max-wins), version bump/version release auf dem neuen Modell, alle vier Funde am Bestand behoben (inkl. Regressionstests), release.yml-Guard, DEVELOPMENT.md neu, docs/version-model.md und instructions/dev/version-parts.md erweitert, tools/CONTRACT.md ergänzt. Version-Bump dieser Änderung: 4.3.3 → 4.4.0-beta.1 → version release → 4.4.0.
Neuer Abschnitt „Umsetzung" ersetzt „Bereitschaft": Stand ist implementiert und lokal grün (pytest, docs verify, instructions verify, ein manueller dist export), aber noch nicht gepusht - publish braucht die ausdrückliche Freigabe des Betreibers. Issue bleibt deshalb offen, bis das passiert ist und CI durchgelaufen ist.
**Changelog:** Umsetzung abgeschlossen, alle Akzeptanzkriterien erfüllt und angehakt.
Umgesetzt wie geplant: `Version` mit `-beta.n`, `escalate()` (max-wins), `version bump`/`version release` auf dem neuen Modell, alle vier Funde am Bestand behoben (inkl. Regressionstests), `release.yml`-Guard, `DEVELOPMENT.md` neu, `docs/version-model.md` und `instructions/dev/version-parts.md` erweitert, `tools/CONTRACT.md` ergänzt. Version-Bump dieser Änderung: `4.3.3` → `4.4.0-beta.1` → `version release` → `4.4.0`.
Neuer Abschnitt „Umsetzung" ersetzt „Bereitschaft": Stand ist implementiert und lokal grün (pytest, `docs verify`, `instructions verify`, ein manueller `dist export`), aber noch nicht gepusht - `publish` braucht die ausdrückliche Freigabe des Betreibers. Issue bleibt deshalb offen, bis das passiert ist und CI durchgelaufen ist.
Changelog: Publish freigegeben und durchgelaufen, CI und Release grün, Issue schließt.
CI (ci.yml Run #91) und Release (release.yml Run #92) beide success gegen Commit d29d400. v4.4.0 ist veröffentlicht. Abschnitt „Bereitschaft"/„Umsetzung" durch „Umsetzung und Ausrollen" ersetzt, mit den Run-Links und dem Release-Nachweis. #7 als entsperrt vermerkt.
**Changelog:** Publish freigegeben und durchgelaufen, CI und Release grün, Issue schließt.
CI (`ci.yml` Run #91) und Release (`release.yml` Run #92) beide `success` gegen Commit `d29d400`. [v4.4.0](https://gitea.nehmer.net/torben/chemenu/releases/tag/v4.4.0) ist veröffentlicht. Abschnitt „Bereitschaft"/„Umsetzung" durch „Umsetzung und Ausrollen" ersetzt, mit den Run-Links und dem Release-Nachweis. #7 als entsperrt vermerkt.
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.
Befund
Heute bekommt jeder
wikitool version bumpsofort eine fixierte, dauerhafte Versionsnummer, unabhängig davon, ob je ein Release dazu erscheint. Fünf Minor-Bumps hintereinander ohne Release ergeben fünf verschiedene Nummern, von denen die älteren vier nie ausgeliefert wurden — Nummern werden verbraucht, ohne dass ein Release sie eingelöst hätte. Messbar: 66 Einträge inCHANGES.md,4.3.3nach vier Tagen; allein die Sitzung vom 2026-09-03 hat zwei Releases für zwei Prosa-Änderungen erzeugt.Verschärft wird das durch das Version-Gate in
ci.yml: jeder Push, der Stack-Pfade berührt, mussVERSIONbewegen. Nummern werden damit in Commit-Granularität verbraucht, nicht in Release-Granularität.Entscheidungen
Betreiber, 2026-09-02 — das Modell:
-beta.n.Betreiber, 2026-09-03 — die vier offenen Mechanikfragen:
VERSIONträgt den Suffix selbst (4.4.0-beta.3). Kein neues Zustandsfile. Der letzte Release ist der neueste suffixfreie##-Eintrag inCHANGES.md; die Eskalationsstufe ist die Differenz zwischen Kandidatenbasis und letztem Release, also abgeleitet statt gespeichert. Damit behält CIs Version-Gate seinen Anker („VERSION hat sich bewegt") undversion showsagt weiter, was tatsächlich installiert ist.bump-Aufruf. Der Aufrufer deklariert weiterhin--patch/--minor/--majorals Urteil;version-parts.mdbleibt die Autorität dafür. Der Kandidat nimmt das Maximum gegen den letzten Release. Keine Klassifikation aus Commits oder Diff —version.pyverwirft das im Modul-Docstring bereits mit dem richtigen Grund:publishschreibt Content-Commits in dasselbe Repo, jeder Ingest würde als Release gelesen.release.ymlfeuert nur bei suffixfreierVERSION. Folge, die die Versionsstelle dieser Änderung trägt: eine ausgelieferte Instanz sieht nie ein Beta, also trifft ihr Parser nie eineVERSION-Form, die er nicht kennt.## X.Y.Z-beta.n - <datum> - <titel>; jeder Bump aktualisiert Version und Datum und hängt seinen--titlean eine maschinenverwaltete Liste am Kopf des Eintrags. Die Prosa darunter bleibt Autorenarbeit.version notesdruckt damit genau einen Eintrag pro Release.Betreiber, 2026-09-03 (Nachtrag) — Menschendoku: der Release-Vorgang bekommt eine
DEVELOPMENT.mdim Repo-Root. Heute gibt es für die Erzeuger-Seite keine Menschendoku:INSTALL.mdbeschreibt nur, wie man ein Release konsumiert,README.mderwähnt Releases mit keinem Wort, und der Ablauf ist nur aus einem Agenten-Skill plus einer Workflow-Datei zusammenzulesen. Die Doku fährt in diesem Issue mit statt vorher, weil sich der Ablauf hier ändert — vorher geschrieben würde sie einen Prozess beschreiben, den es nach dieser Umsetzung nicht mehr gibt.Die Mechanik konkret
VERSIONhält entweder eine Release-VersionX.Y.Z(direkt nach einem Release) oder einen KandidatenX.Y.Z-beta.n.version bump --patch|--minor|--major --title "…":R(neuester suffixfreier Changelog-Eintrag) und den aktuellenVERSION-Stand.s= die Stelle, um die die Kandidatenbasis überRhinausgeht; keine, wennVERSION == R.max(s, angeforderte Stufe)in der Ordnung patch < minor < major.R.bumped(neue Stufe).n= altesn+ 1, bzw. 1 bei einem frisch eröffneten Kandidaten.VERSION, aktualisiert Heading und Titelliste des offenen Eintrags — oder legt den Eintrag an.Randfall ohne letzten Release: eine frische Distribution hat einen Changelog ohne versionierten Eintrag (
top_changes_versionliefert dort heute schonNone, und das ist ein gültiger Zustand). Dann gibt es keinR; die Kandidatenbasis istVERSION.bumped(angeforderte Stufe)und die Eskalation startet bei dieser Stufe. Nicht als Fehler behandeln.Die Eskalation ist monoton: nie automatisch zurück. Ein zurückgenommener Umfang senkt den Kandidaten nicht (siehe „Nicht in diesem Issue").
version release [--title "…"] [--dry-run]fixiert: streicht den Suffix ausVERSIONund setzt das Heading auf## X.Y.Z - <heute> - <titel>. Woher der Titel kommt: ohne--titlebleibt der Titel stehen, den der letzte Bump gesetzt hat; mit--titlewird er ersetzt. Das ist der Normalfall und kein Luxus — ein Kandidat hat mehrere Bumps mit je eigenem Titel gesammelt, und der Release will eine zusammenfassende Überschrift statt der zufällig letzten. Committet nicht und pusht nicht (Invariante 5) — der anschließendepublishbewegtVERSIONaufmainund löstrelease.ymlaus. Der nächstebumperöffnet dann einen neuen Eintrag, weil der oberste suffixfrei ist; dafür ist kein weiterer Zustand nötig.Was das an bestehendem Code bricht
Vier Stellen, die das neue Modell still beschädigen würde. Sie sind der eigentliche Umfang dieses Issues, nicht die Suffix-Arithmetik.
.gitea/workflows/release.ymlpaths: [VERSION]aufmain. Unter dem neuen Modell feuert das bei jedem Bump und veröffentlicht Betas.VERSIONeinen Suffix, endet der Job sauber (Skip, kein Fail). Die Reihenfolge ist nicht kosmetisch — die bestehendereleases/tags/-Abfrage würde sonst für jeden Beta-Bump eine Runde gegen die API drehen.docs_verify.check_breaking_change_for_boundary/check_migration_for_boundarycompat_keydes obersten mit dem des zweitobersten Eintrags. Zwischen4.4.0-beta.2und4.4.0-beta.1liegt keine Grenze — der Breaking-Change-Zwang wäre faktisch abgeschaltet.kb_state.chain()/next_link()4.4.0-beta.1 < 4.4.0, also fällt eine Migration mit Ziel4.4.0aus dem Intervall(kb_version, stack_version]. Die Instanz hat die Maschinerie und bekommt die Migration nicht angesagt.read_kb_version()verweigert einen Prerelease: eine Inhaltsform hat kein Beta.version_cmd.bump_command,--breaking/--no-migrationversion-parts.mdSchritt 4 (Gespräch mit dem Betreiber vor einem Grenzübertritt) gilt ab jetzt an genau diesem Punkt.Nicht betroffen, geprüft: das Version-Gate in
ci.ymlbleibt gültig, weilVERSIONbei jedem Bump durchn+1weiterhin wandert. Das ist beabsichtigt und soll so bleiben. Auchversion releasepassiert das Gate — es prüft nur „Stack-Pfad ohne VERSION-Bewegung", nicht die Gegenrichtung.Umsetzungsplan
Alle neun Schritte umgesetzt:
version.py(Datentyp/Ordnung, Kandidatenlogik),version_cmd.py(bumpumgestellt,releaseneu),docs_verify.py/kb_state.py/dist_cmd.py/doctor.py(alle vier Funde behoben),release.yml-Guard, Agenten-Doku (version-parts.mdmit neuem Abschnitt „The candidate model" und angepassten Schritten 4–8,tools/CONTRACT.md,CHANGES.md-Kopf,docs/version-model.md),DEVELOPMENT.mdneu, Version-Bump dieser Änderung selbst.Tests
Gilt
instructions/dev/testing-conventions.md. Alle Fälle aus der ursprünglichen Testliste (Ordnung inkl. numerischem Beta-Vergleich, max-wins-Eskalation, Randfall ohne letzten Release,--breaking-Persistenz über Folgebumps, beide Regressionen für Fund 2 und 3,version releasemit/ohne--title,version notesfür einen Kandidaten) haben einen Test intest_version_cmd.py,test_docs_verify.py,test_migrate_cmd.pyundtest_doctor.py. Derrelease.yml-Guard bleibt wie geplant ungetestet (Shell in CI).Akzeptanzkriterien
Versionversteht-beta.n: Parsing, totale Ordnung,base,compat_keyüber die Basis. Tests dafür.version bumpführt einen laufenden Kandidaten mit max-wins-Eskalation gegen den letzten Release;nzählt pro Bump. Tests dafür, inklusive Randfall ohne letzten Release.CHANGES.mdträgt genau einen offenen Eintrag pro Kandidat, mit maschinenverwalteter Titelliste in<!-- wikitool:bumps -->-Markern.version releasefixiert die Nummer, beendet die Pre-Release-Phase, committet nichts, und behandelt--titlewie oben beschrieben. Test dafür.release.ymlüberspringt eine Kandidaten-VERSIONsauber, vor der API-Abfrage.docs verifymessen gegen den letzten Release. Regressionstest.kb_versionrechnen gegen die Kandidatenbasis. Regressionstest.--breaking/--no-migrationgreifen am Eskalationsbump und bleiben über Folgebumps erhalten.tools/CONTRACT.md: Zeile fürversion releasein Kommandotabelle und Fehlerkontrakt.instructions/dev/version-parts.md,CHANGES.md-Kopf unddocs/version-model.mdbeschreiben die neue Semantik.DEVELOPMENT.mdexistiert, beschreibt den Release-Ablauf unter dem neuen Modell, ist bewusst nicht indist_cmd.ROOT_FILES(mit Begründung im Code), hat eine Zeile inAGENTS.md§ File naming und einen Zeiger ausREADME.md.4.3.3→4.4.0-beta.1→version release→4.4.0. Changelog-Eintrag geschrieben.docs verify,instructions verify,pytestgrün; CI grün inklusive dist-export-Replay.Umsetzung und Ausrollen (Stand 2026-09-03, abgeschlossen)
Vollständig umgesetzt, gepublisht, verifiziert. Publish freigegeben durch den Betreiber (Mass-Update Gate, 21 Dateien, Token
815a67d6a9fd), Commitd29d400auforigin/main. Verifiziert:ci.yml, Run #91,verify):success- volle Testsuite,docs verify,instructions verify,dist export-Replay gegen eine frische Instanz, alles grün gegen den gepushten Stand.release.yml, Run #92,release):success-VERSIONwar beim Push bereits release-fixiert (4.4.0, kein Suffix mehr), der Guard aus Fund 1 hat den Job also nicht übersprungen, sondern regulär durchlaufen lassen.Damit ist der komplette Ablauf einmal live durchlaufen: Bump → Prosa → Verify →
version release→ Publish (mit Gate-Freigabe) → CI → Release.Nachspiel, gleicher Tag: die gesamte Prosa dieser Umsetzung entstand ohne den Modellwechsel, den
stack-devSchritt 6 dafür vorsieht — der Break wurde übersprungen. Ein nachgeholter Durchgang (4.4.1-beta.1) hat darin einen echten Faktenfehler gefunden, der sowohl im Changelog als auch indocs/version-model.mdstand (siehe Nachtrag ganz oben), dazu eine veraltete2.x-Angabe inversion-parts.mdund eine überflüssige Kommandotabelle inDEVELOPMENT.md. Der Prozessfehler dahinter ist #47.Abhängigkeiten
#7 (
wikitool dist upgrade) warstatus/blockedauf dieses Issue. Die Kopplung ist nach Fund 3 enger als ursprünglich notiert: sowohl die Drei-Wege-Klassifikation in #7 als auch die Migrationskette hängen daran, dass ein Prerelease auf seine Basis zurückgeführt wird, bevor verglichen wird. Dieses Issue ist erledigt, #7 ist entsperrt.Nicht in diesem Issue
prio/waiting, Auslöser ist die PR-Einführung.Vorgeschichte
Modell entschieden in der Diskussion vom 2026-09-02, im Rahmen der Aufarbeitung von #38, #30, #28, #7, #27 als Fallouts des Entwicklungsprozesses. Siehe auch #41 (Issue-Management), das denselben Sitzungsblock eröffnet hat. Die vier Mechanikfragen und die vier Funde am bestehenden Code stammen vom 2026-09-03; die
DEVELOPMENT.md-Anforderung und die Bereitschaftsprüfung vom selben Tag, nach dem Landen von 4.3.2 und 4.3.3. Umsetzung, Publish und Release ebenfalls vom 2026-09-03, im direkten Anschluss.torben referenced this issue2026-09-03 04:33:45 +00:00
Update 2026-09-03: #39/#40 sind gelandet, ohne direkten Einfluss auf die hier offenen Mechanik-Fragen (Zustandsort, Eskalationsberechnung, Pre-Release-Vergleichslogik). Position in der Reihenfolge unverändert: nach #28/#38, vor #30/#7 - #7 hängt direkt an diesem Issue.
Changelog: Body von Befund+offenen Fragen auf einen umsetzbaren Plan umgeschrieben.
Neu: alle vier offenen Mechanikfragen entschieden (VERSION trägt den Suffix, max-wins pro Bump-Aufruf, Betas werden nie veröffentlicht, ein offener Changelog-Eintrag pro Kandidat), dazu die konkrete Bump-Arithmetik, ein achtstufiger Umsetzungsplan mit bindender Reihenfolge, eine Testliste und ein Abschnitt „Nicht in diesem Issue".
Vier Funde am bestehenden Code ergänzt, die vorher in keiner Anforderung standen:
release.ymlwürde unter dem neuen Modell bei jedem Bump feuern und Betas veröffentlichen; die beiden Grenzübertritts-Checks indocs verifyvergleichen den obersten mit dem zweitobersten Eintrag und wären zwischen zwei Betas faktisch abgeschaltet;kb_state.chain()lässt eine Migration mit Ziel4.4.0bei installiertem4.4.0-beta.1aus dem Intervall fallen;--breaking/--no-migrationhaben unter Eskalation keinen festen Zeitpunkt mehr. Fund 2 und 3 sind Regressionstests im Akzeptanzkriterienblock.Alte Abschnitte „Offene Punkte" und die Kurzform der Akzeptanzkriterien entfallen — ersetzt durch Entscheidungen, Plan und die neue, geschnittene Kriterienliste. Release-Drafter-Recherche ist kein Kriterium mehr, sondern explizit zurückgestellt (Trigger: PR-Einführung), noch ohne eigenes Issue.
kind/decision→kind/build: die offenen Entscheidungen sind getroffen, es fehlt nur noch Umsetzungszeit.prio/plannedundsize/Lunverändert.Changelog:
DEVELOPMENT.mdals Anforderung aufgenommen, Bereitschaftsprüfung durchgeführt, drei dabei gefundene Lücken geschlossen.Neu als Plan-Schritt 8 und Akzeptanzkriterium:
DEVELOPMENT.mdim Repo-Root für die Erzeuger-Seite des Release-Vorgangs — mit den vier Nebenbedingungen (bewusst nicht indist_cmd.ROOT_FILES, Begründung im Code; darf deshalb nachinstructions/dev/verlinken; Zeile inAGENTS.md§ File naming; Zeiger ausREADME.md). Alter Schritt 8 ist jetzt 9.Bei der Bereitschaftsprüfung aufgefallen und ergänzt:
<!-- wikitool:<name> -->ausblocks.py, mit der ausdrücklichen Auflage,blocks.BLOCKSnicht zu erweitern — dessen Namen speisenxref,citeund den Lint-Checkunbalanced_markers, undCHANGES.mdist keine Seite.version releasefehlte. Ein Kandidat sammelt mehrere Bump-Titel; welcher im Release-Heading landet, stand nirgends. Jetzt:--titleoptional, ohne ihn bleibt der letzte Bump-Titel stehen.Kleinere Präzisierungen: Guard in
release.ymlmuss vor die API-Abfrage (sonst dreht jeder Beta-Bump eine Runde gegen die API); Ordnungstestbeta.9 < beta.10ergänzt (numerisch, nicht lexikografisch);version releasepassiert das CI-Version-Gate, geprüft.Neuer Abschnitt „Bereitschaft": umsetzungsbereit, Body ist die vollständige Übergabe für eine frische Sitzung. Empfehlung zur Sitzungsführung ergänzt — Design ist erledigt, also direkt in der mechanischen Phase starten (Sonnet/high nach 4.3.3).
Changelog: Umsetzung abgeschlossen, alle Akzeptanzkriterien erfüllt und angehakt.
Umgesetzt wie geplant:
Versionmit-beta.n,escalate()(max-wins),version bump/version releaseauf dem neuen Modell, alle vier Funde am Bestand behoben (inkl. Regressionstests),release.yml-Guard,DEVELOPMENT.mdneu,docs/version-model.mdundinstructions/dev/version-parts.mderweitert,tools/CONTRACT.mdergänzt. Version-Bump dieser Änderung:4.3.3→4.4.0-beta.1→version release→4.4.0.Neuer Abschnitt „Umsetzung" ersetzt „Bereitschaft": Stand ist implementiert und lokal grün (pytest,
docs verify,instructions verify, ein manuellerdist export), aber noch nicht gepusht -publishbraucht die ausdrückliche Freigabe des Betreibers. Issue bleibt deshalb offen, bis das passiert ist und CI durchgelaufen ist.Changelog: Publish freigegeben und durchgelaufen, CI und Release grün, Issue schließt.
CI (
ci.ymlRun #91) und Release (release.ymlRun #92) beidesuccessgegen Commitd29d400. v4.4.0 ist veröffentlicht. Abschnitt „Bereitschaft"/„Umsetzung" durch „Umsetzung und Ausrollen" ersetzt, mit den Run-Links und dem Release-Nachweis. #7 als entsperrt vermerkt.