Aufgeworfen in der Sitzung 2026-09-04 zur Ordnerorganisation. Hing an #56 (move --reconcile), geschlossen mit 9e41431. Nachbarn: #57 (Katalogtiefe), #58 (incoming/).
Erledigt am 2026-09-08 mit 7f74303, ausgeliefert als 4.8.0-beta.6.
Befund
Zwei Dinge, die auseinanderzuhalten waren:
Sharden war bereits automatisch. index_build.SHARD_THRESHOLD = 50, hergeleitet aus der wikieigenen Seite Index Scaling; index rebuild gibt einer Area über der Schwelle einen eigenen INDEX.md. Kein Mensch beteiligt.
Eine Area überhaupt anlegen machte niemand. Und ohne Areas gab es nichts zu sharden.
Stand des Korpus vorher: kb/concepts/ 80 Seiten / keine Areas, kb/entities/ 72 / 5 Areas, kb/sources/ 29 / keine, kb/comparisons/ 1 / keine. kb/concepts/INDEX.md war damit eine einzelne Tabelle mit 80 Zeilen — über der eigenen Schwelle — und teilte sich nie, weil Sharding pro Area passiert.
Ursache war eine Asymmetrie in den Type-Specs: types/entity.md deklarierte ein layout:, types/concept.md und types/source.md hatten ein Subtype-Feld, aber keines. Die Achse lag fertig da und wurde nicht benutzt.
Entscheidung: layout: für concept, vorerst nicht für source
Für Concepts trägt die Aufteilung: sechs Areas, die größte mit 28, alle unter der Schwelle. types/concept.md deklariert jetzt architectures/patterns/protocols/workflows/decisions/problems mit Anzeigetitel je Wert.
Für Sources wurde sie zurückgestellt, nicht verworfen — und die Begründung im ursprünglichen Body („25 von 29 sind notes") hat sich beim Nachmessen als falsch herausgestellt: notes ist ein Sammelbecken, kein Befund. Tatsächlich sind es 16 Session-Transkripte, 4 LLM-Analysen, 2 Tracker-Exporte und 3 echte eigene Notizen. Die Aufteilung würde also sehr wohl tragen — aber erst nach einer Umklassifizierung von 22 Seiten, und das ist ein korpusweiter Sweep nach instructions/dev/corpus-policy.md Tier 2 („planned only, never reactive"), der ein eigenes Issue und einen work/-Lauf braucht. Enum-Erweiterung und Sweep sind untrennbar, weil sonst der Floor „jeder deklarierte Subtype hat ≥1 Seite" bricht. Nachfolge-Issue: #66, mit der Frage nach einem eigenen Typ für AI-Transkripte dort beantwortet (nein — der Stack ankert auf page.kind == "source").
types/source.md bleibt bis dahin im Testbestand das dokumentierte Gegenbeispiel für „Subtype-Feld ohne layout:": test_get_layout_is_none_for_types_without_one, test_subtype_dir_is_none_without_layout_or_subtype und test_compute_target_dir_is_flat_for_a_type_without_layout hängen daran statt an concept. #66 führt diese Abhängigkeit als eigenen Punkt.
Der Auslöser in lint
Neuer Befund unsharded_collections in lint_core.py. Kriterium wie geplant: Collection über der Schwelle und ohne Areas und die Aufteilung drückt jede entstehende Area — inklusive des nicht platzierbaren Rests — auf die Schwelle oder darunter. Als Empfehlung, nicht als Failure; bewusst nicht in HARD_ERROR_KEYS, und anders als unlabelled_edges auch nicht migrationsgesteuert: es gibt keine Version, ab der eine ungeteilte Collection falsch wird.
Eine Ergänzung gegenüber dem geplanten Stand: der Befund unterscheidet, ob das Type-Spec das layout: bereits deklariert. Ohne es fehlt die erste Hälfte des Fixes (layout: nachtragen), mit ihm ist nur noch der Move offen — die Reportzeile sagt, welche der beiden gilt, statt beide Fälle mit einem Satz zu bedienen.
Zwei Dinge, die das Issue nicht vorhergesehen hatte
lint_core.py durfte nicht aus commands/index_build.py importieren.test_api.py::test_the_read_surface_cannot_reach_a_write_command prüft strukturell, dass chemenu.api kein Modul unter chemenu.commands lädt; der Import hätte den CLI-Kopf mitgezogen und die Read-Surface-Grenze gebrochen. SHARD_THRESHOLD, Area, Collection, area_titles() und group_pages() liegen deshalb neu in tools/chemenu/catalog.py, entlang derselben Linie wie lint_core.py: Korpusform hier, Darstellung in index_build.py. Der Boundary-Test hat den Fehler gefangen, bevor er gepublisht war — genau das, wofür er da ist.
_anchor() strich mit [^a-z0-9\s-] jeden Nicht-ASCII-Buchstaben ersatzlos. Die Karte verlinkte auf #ablufe, während die Überschrift im Shard #abläufe heißt. Jeder Deep-Link in einen Shard mit nicht-englischem Area-Titel wäre still tot gewesen. Vorher fiel es nicht auf, weil alle fünf Entity-Area-Titel zufällig ASCII sind; Abläufe ist der erste, der es nicht ist. Auf [^\w\s-] mit re.UNICODE umgestellt, mit eigenem Test.
Version: MINOR
Der Umzug ist ein Angebot, kein Zwang. group_pages liest das Dateisystem, nicht das layout:, also rendert der gemischte Zustand weiter — flache Bestandsseiten landen in Area „All", layout:-platzierte Neuanlagen in ihrer eigenen. Der gemischte Zustand meldet sich als lint-Befund Misplaced Pages, der seit jeher advisory ist. Ein Downgrade auf einen Stack ohne dieses layout: funktioniert weiter; nur die Anzeigetitel fallen auf .title() zurück. Kosmetik, kein Bruch der Austauschbarkeit in beiden Richtungen, also kein --major und kein instructions/migrate-corpus.md-Fall — es wird kein Fließtext umgeschrieben.
Akzeptanzkriterien
types/concept.md deklariert ein layout: für alle sechs concept_type-Werte, mit Verzeichnis und Anzeigetitel je Wert. Ein Test hält Layout und Schema-Enum deckungsgleich, damit kein Wert stillschweigend auf die Pluralisierungs-Fallback läuft.
Die Area-Titel kommen aus jedem Type-Spec statt nur aus entity — und pro Collection geschlüsselt, damit zwei Typen denselben Area-Namen für Verschiedenes benutzen dürfen. Liegt als catalog.area_titles() vor, nicht mehr als index_build._area_titles().
lint meldet eine Collection über der Schwelle ohne Areas, mit der Verteilung des Subtype-Felds, und nur wenn die Aufteilung trägt. Fünf Tests statt der geforderten zwei: einer der feuert (Concepts-Form), einer der unter der Schwelle schweigt (Sources-Form), einer der bei schiefer Verteilung schweigt (70/6/5 — die zweite Hälfte des Kriteriums, die eine reine Schwellenprüfung falsch beantwortet hätte), einer der nach dem Fix schweigt, einer für die Layout-fehlt-Variante der Meldung.
Der Befund ist eine Empfehlung, kein Failure — eigener Test, lint bleibt grün, wenn er der einzige Punkt im Report ist.
Nach move --reconcile liegen alle 80 Concept-Seiten in ihrer Area, mit unverändertem Titel, Body und Frontmatter. migrate verify --from HEAD: 182 compared, 0 added, 0 removed, 80 moved, 0 findings. Git hat den Commit unabhängig davon als 80 × rename … (100%) erfasst.
index rebuild erzeugt sechs Concept-Areas: Abläufe 28, Architekturen 20, Muster 17, Entscheidungen 7, Problemstellungen 5, Protokolle 3. Keine über der Schwelle, also kein eigener Shard — die Schwelle wirkt wieder als Schwelle statt als toter Wert.
kb/concepts/COLLECTION.md beschreibt die Areas; kb/CONTRACT.md unverändert (die Area-Regel stand dort bereits).
Ein neu angelegtes Concept landet ohne weiteres Zutun in seiner Area — test_new_concept_creates_page_in_its_subtype_area. An new hat sich dafür nichts geändert; das Type-Spec hat es getan.
Changelog-Eintrag, MINOR, mit dem Hinweis, dass move --reconcile für eine bestehende Instanz ein Angebot ist und kein Zwang.
Verifiziert
pytest 1055 passed · docs verify OK (52 Kommandos, 4 Collections, 7 Stage-Contracts, CHANGES.md auf 4.8.0-beta.6) · instructions verify OK (20 Instructions, 7 Skills, 14 publizierte Kopien deckungsgleich) · lint grün, weder Misplaced Pages noch der neue Befund; die zwei verbleibenden Sektionen (Orphan Pages, Redundant see-also) standen vorher schon da und sind beide advisory. CI zu 7f74303: Läufe 192 und 193, beide success.
Geändert: types/concept.md, types/type-spec.md, tools/chemenu/catalog.py (neu), tools/chemenu/commands/index_build.py, tools/chemenu/lint_core.py, neun Testdateien (das Fixture-Concept liegt jetzt in seiner Area), tools/CONTRACT.md, tools/README.md, README.md, CHANGES.md, VERSION, kb/concepts/COLLECTION.md, kb/log.md, plus die 80 bewegten Seiten und die neu erzeugten Katalogdateien.
docs/ ist unberührt geprüft: keine der vier Seiten schreibt Begründungen zu Katalog, Areas oder Subtype-Achsen auf. docs/ownership-and-templates.md nennt root: kb-Type-Specs als instanzeigen — genau die Eigentümerschaft, unter der types/concept.md hier geändert wurde, also kein Widerspruch, sondern die Regel im Vollzug.
Zwei Randnotizen aus dieser Sitzung
Die Versionsnummer kollidierte einmal.origin/main trug seit 63b4bb8 (2026-09-05) einen Commit, den der Checkout nie gesehen hatte, und hatte 4.8.0-beta.5 bereits vergeben. Der lokale version bump errechnete dieselbe Nummer aus veraltetem VERSION. Nach sync als beta.6 wiederholt. Kein Fehler im Tool — version bump rechnet korrekt aus dem, was der Baum weiß; wer nicht synct, weiß zu wenig. instructions/session-setup.md empfiehlt sync zum Sitzungsbeginn genau dafür. Derselbe Commit hatte auch instructions/wiki-query/SKILL.md bewegt, ohne instructions sync zu laufen — die publizierten Kopien waren gedriftet und wurden hier nachgezogen.
Ein Doku-Defekt aus 63b4bb8 mitgezogen: die README.md-Zeile las Use singular for entities: `HA Integration.md` (not `HA Integration.md`) — beide Seiten des „not" identisch, also eine Regel ohne Gegenbeispiel. kb/CONVENTIONS.md und kb/entities/COLLECTION.md hatten im selben Commit das korrekte Paar bekommen; README.md zieht jetzt mit HA Integrations.md nach.
Modelle in dieser Sitzung
Alle drei Phasen liefen auf Opus 5: Design und Versionsteil, der mechanische Mittelteil (Code, Tests, Bump), und diese Abschlussphase. Der Wechselvorschlag auf Sonnet/high wurde an beiden vorgesehenen Punkten angeboten (stack-dev Schritt 3, stack-close Schritt 1) und nicht angenommen — was für den unbewachten Mittelteil dieser Änderung eher zu viel als zu wenig war, für die zwei ungeprüften Ränder aber genau richtig.
Aufgeworfen in der Sitzung 2026-09-04 zur Ordnerorganisation. Hing an #56 (`move --reconcile`), geschlossen mit `9e41431`. Nachbarn: #57 (Katalogtiefe), #58 (`incoming/`).
**Erledigt am 2026-09-08 mit `7f74303`, ausgeliefert als `4.8.0-beta.6`.**
## Befund
Zwei Dinge, die auseinanderzuhalten waren:
- **Sharden** war bereits automatisch. `index_build.SHARD_THRESHOLD = 50`, hergeleitet aus der wikieigenen Seite `Index Scaling`; `index rebuild` gibt einer Area über der Schwelle einen eigenen `INDEX.md`. Kein Mensch beteiligt.
- **Eine Area überhaupt anlegen** machte niemand. Und ohne Areas gab es nichts zu sharden.
Stand des Korpus vorher: `kb/concepts/` 80 Seiten / keine Areas, `kb/entities/` 72 / 5 Areas, `kb/sources/` 29 / keine, `kb/comparisons/` 1 / keine. `kb/concepts/INDEX.md` war damit eine einzelne Tabelle mit 80 Zeilen — über der eigenen Schwelle — und teilte sich nie, weil Sharding pro Area passiert.
Ursache war eine Asymmetrie in den Type-Specs: `types/entity.md` deklarierte ein `layout:`, `types/concept.md` und `types/source.md` hatten ein Subtype-Feld, aber keines. Die Achse lag fertig da und wurde nicht benutzt.
## Entscheidung: `layout:` für `concept`, vorerst nicht für `source`
Für Concepts trägt die Aufteilung: sechs Areas, die größte mit 28, alle unter der Schwelle. `types/concept.md` deklariert jetzt `architectures`/`patterns`/`protocols`/`workflows`/`decisions`/`problems` mit Anzeigetitel je Wert.
Für Sources wurde sie **zurückgestellt, nicht verworfen** — und die Begründung im ursprünglichen Body („25 von 29 sind `notes`") hat sich beim Nachmessen als falsch herausgestellt: `notes` ist ein Sammelbecken, kein Befund. Tatsächlich sind es 16 Session-Transkripte, 4 LLM-Analysen, 2 Tracker-Exporte und 3 echte eigene Notizen. Die Aufteilung würde also sehr wohl tragen — aber erst nach einer Umklassifizierung von 22 Seiten, und das ist ein korpusweiter Sweep nach `instructions/dev/corpus-policy.md` Tier 2 („planned only, never reactive"), der ein eigenes Issue und einen `work/`-Lauf braucht. Enum-Erweiterung und Sweep sind untrennbar, weil sonst der Floor „jeder deklarierte Subtype hat ≥1 Seite" bricht. **Nachfolge-Issue: #66**, mit der Frage nach einem eigenen Typ für AI-Transkripte dort beantwortet (nein — der Stack ankert auf `page.kind == "source"`).
`types/source.md` bleibt bis dahin im Testbestand das dokumentierte Gegenbeispiel für „Subtype-Feld ohne `layout:`": `test_get_layout_is_none_for_types_without_one`, `test_subtype_dir_is_none_without_layout_or_subtype` und `test_compute_target_dir_is_flat_for_a_type_without_layout` hängen daran statt an `concept`. #66 führt diese Abhängigkeit als eigenen Punkt.
## Der Auslöser in `lint`
Neuer Befund `unsharded_collections` in `lint_core.py`. Kriterium wie geplant: Collection über der Schwelle **und** ohne Areas **und** die Aufteilung drückt jede entstehende Area — inklusive des nicht platzierbaren Rests — auf die Schwelle oder darunter. Als Empfehlung, nicht als Failure; bewusst nicht in `HARD_ERROR_KEYS`, und anders als `unlabelled_edges` auch nicht migrationsgesteuert: es gibt keine Version, ab der eine ungeteilte Collection falsch wird.
Eine Ergänzung gegenüber dem geplanten Stand: der Befund unterscheidet, ob das Type-Spec das `layout:` bereits deklariert. Ohne es fehlt die erste Hälfte des Fixes (`layout:` nachtragen), mit ihm ist nur noch der Move offen — die Reportzeile sagt, welche der beiden gilt, statt beide Fälle mit einem Satz zu bedienen.
## Zwei Dinge, die das Issue nicht vorhergesehen hatte
1. **`lint_core.py` durfte nicht aus `commands/index_build.py` importieren.** `test_api.py::test_the_read_surface_cannot_reach_a_write_command` prüft strukturell, dass `chemenu.api` kein Modul unter `chemenu.commands` lädt; der Import hätte den CLI-Kopf mitgezogen und die Read-Surface-Grenze gebrochen. `SHARD_THRESHOLD`, `Area`, `Collection`, `area_titles()` und `group_pages()` liegen deshalb neu in **`tools/chemenu/catalog.py`**, entlang derselben Linie wie `lint_core.py`: Korpusform hier, Darstellung in `index_build.py`. Der Boundary-Test hat den Fehler gefangen, bevor er gepublisht war — genau das, wofür er da ist.
2. **`_anchor()` strich mit `[^a-z0-9\s-]` jeden Nicht-ASCII-Buchstaben ersatzlos.** Die Karte verlinkte auf `#ablufe`, während die Überschrift im Shard `#abläufe` heißt. Jeder Deep-Link in einen Shard mit nicht-englischem Area-Titel wäre still tot gewesen. Vorher fiel es nicht auf, weil alle fünf Entity-Area-Titel zufällig ASCII sind; `Abläufe` ist der erste, der es nicht ist. Auf `[^\w\s-]` mit `re.UNICODE` umgestellt, mit eigenem Test.
## Version: MINOR
Der Umzug ist ein **Angebot**, kein Zwang. `group_pages` liest das Dateisystem, nicht das `layout:`, also rendert der gemischte Zustand weiter — flache Bestandsseiten landen in Area „All", `layout:`-platzierte Neuanlagen in ihrer eigenen. Der gemischte Zustand meldet sich als `lint`-Befund *Misplaced Pages*, der seit jeher advisory ist. Ein Downgrade auf einen Stack ohne dieses `layout:` funktioniert weiter; nur die Anzeigetitel fallen auf `.title()` zurück. Kosmetik, kein Bruch der Austauschbarkeit in beiden Richtungen, also kein `--major` und kein `instructions/migrate-corpus.md`-Fall — es wird kein Fließtext umgeschrieben.
## Akzeptanzkriterien
- [x] `types/concept.md` deklariert ein `layout:` für alle sechs `concept_type`-Werte, mit Verzeichnis und Anzeigetitel je Wert. Ein Test hält Layout und Schema-Enum deckungsgleich, damit kein Wert stillschweigend auf die Pluralisierungs-Fallback läuft.
- [x] Die Area-Titel kommen aus **jedem** Type-Spec statt nur aus `entity` — und pro Collection geschlüsselt, damit zwei Typen denselben Area-Namen für Verschiedenes benutzen dürfen. Liegt als `catalog.area_titles()` vor, nicht mehr als `index_build._area_titles()`.
- [x] `lint` meldet eine Collection über der Schwelle ohne Areas, mit der Verteilung des Subtype-Felds, und nur wenn die Aufteilung trägt. **Fünf** Tests statt der geforderten zwei: einer der feuert (Concepts-Form), einer der unter der Schwelle schweigt (Sources-Form), einer der bei schiefer Verteilung schweigt (70/6/5 — die zweite Hälfte des Kriteriums, die eine reine Schwellenprüfung falsch beantwortet hätte), einer der nach dem Fix schweigt, einer für die Layout-fehlt-Variante der Meldung.
- [x] Der Befund ist eine Empfehlung, kein Failure — eigener Test, `lint` bleibt grün, wenn er der einzige Punkt im Report ist.
- [x] Nach `move --reconcile` liegen alle 80 Concept-Seiten in ihrer Area, mit unverändertem Titel, Body und Frontmatter. `migrate verify --from HEAD`: **182 compared, 0 added, 0 removed, 80 moved, 0 findings**. Git hat den Commit unabhängig davon als 80 × `rename … (100%)` erfasst.
- [x] `index rebuild` erzeugt sechs Concept-Areas: Abläufe 28, Architekturen 20, Muster 17, Entscheidungen 7, Problemstellungen 5, Protokolle 3. Keine über der Schwelle, also kein eigener Shard — die Schwelle wirkt wieder als Schwelle statt als toter Wert.
- [x] `kb/concepts/COLLECTION.md` beschreibt die Areas; `kb/CONTRACT.md` unverändert (die Area-Regel stand dort bereits).
- [x] Ein neu angelegtes Concept landet ohne weiteres Zutun in seiner Area — `test_new_concept_creates_page_in_its_subtype_area`. An `new` hat sich dafür nichts geändert; das Type-Spec hat es getan.
- [x] Changelog-Eintrag, MINOR, mit dem Hinweis, dass `move --reconcile` für eine bestehende Instanz ein Angebot ist und kein Zwang.
## Verifiziert
`pytest` 1055 passed · `docs verify` OK (52 Kommandos, 4 Collections, 7 Stage-Contracts, `CHANGES.md` auf `4.8.0-beta.6`) · `instructions verify` OK (20 Instructions, 7 Skills, 14 publizierte Kopien deckungsgleich) · `lint` grün, weder *Misplaced Pages* noch der neue Befund; die zwei verbleibenden Sektionen (Orphan Pages, Redundant see-also) standen vorher schon da und sind beide advisory. CI zu `7f74303`: Läufe [192](https://gitea.nehmer.net/torben/chemenu/actions/runs/192) und [193](https://gitea.nehmer.net/torben/chemenu/actions/runs/193), **beide `success`**.
Geändert: `types/concept.md`, `types/type-spec.md`, `tools/chemenu/catalog.py` (neu), `tools/chemenu/commands/index_build.py`, `tools/chemenu/lint_core.py`, neun Testdateien (das Fixture-Concept liegt jetzt in seiner Area), `tools/CONTRACT.md`, `tools/README.md`, `README.md`, `CHANGES.md`, `VERSION`, `kb/concepts/COLLECTION.md`, `kb/log.md`, plus die 80 bewegten Seiten und die neu erzeugten Katalogdateien.
`docs/` ist unberührt geprüft: keine der vier Seiten schreibt Begründungen zu Katalog, Areas oder Subtype-Achsen auf. `docs/ownership-and-templates.md` nennt `root: kb`-Type-Specs als instanzeigen — genau die Eigentümerschaft, unter der `types/concept.md` hier geändert wurde, also kein Widerspruch, sondern die Regel im Vollzug.
## Zwei Randnotizen aus dieser Sitzung
- **Die Versionsnummer kollidierte einmal.** `origin/main` trug seit `63b4bb8` (2026-09-05) einen Commit, den der Checkout nie gesehen hatte, und hatte `4.8.0-beta.5` bereits vergeben. Der lokale `version bump` errechnete dieselbe Nummer aus veraltetem `VERSION`. Nach `sync` als `beta.6` wiederholt. Kein Fehler im Tool — `version bump` rechnet korrekt aus dem, was der Baum weiß; wer nicht synct, weiß zu wenig. `instructions/session-setup.md` empfiehlt `sync` zum Sitzungsbeginn genau dafür. Derselbe Commit hatte auch `instructions/wiki-query/SKILL.md` bewegt, ohne `instructions sync` zu laufen — die publizierten Kopien waren gedriftet und wurden hier nachgezogen.
- **Ein Doku-Defekt aus `63b4bb8` mitgezogen:** die `README.md`-Zeile las ``Use singular for entities: `HA Integration.md` (not `HA Integration.md`)`` — beide Seiten des „not" identisch, also eine Regel ohne Gegenbeispiel. `kb/CONVENTIONS.md` und `kb/entities/COLLECTION.md` hatten im selben Commit das korrekte Paar bekommen; `README.md` zieht jetzt mit `HA Integrations.md` nach.
## Modelle in dieser Sitzung
Alle drei Phasen liefen auf **Opus 5**: Design und Versionsteil, der mechanische Mittelteil (Code, Tests, Bump), und diese Abschlussphase. Der Wechselvorschlag auf Sonnet/high wurde an beiden vorgesehenen Punkten angeboten (`stack-dev` Schritt 3, `stack-close` Schritt 1) und nicht angenommen — was für den unbewachten Mittelteil dieser Änderung eher zu viel als zu wenig war, für die zwei ungeprüften Ränder aber genau richtig.
Changelog:status/blocked entfernt — #56 ist mit Commit 9e41431 (4.8.0-beta.1) geschlossen. Damit steht beides, worauf dieses Issue gewartet hat: wikitool move --reconcile als Weg, die 80 Concept-Seiten in einem Aufruf umzusortieren, und die Titel-Schlüsselung in migrate verify, ohne die der Vorbehalt im Abschnitt „Version: MINOR" dieses Bodys gegolten hätte (ein reiner Move lief vorher als „80 removed, 80 added, 0 compared" grün durch). Der Body bleibt sonst unverändert; die eigene Arbeit — layout: in types/concept.md, _area_titles() aus dem jeweiligen Type-Spec, der Collection-Schwellenbefund in lint — ist unberührt und noch offen.
**Changelog:** `status/blocked` entfernt — #56 ist mit Commit `9e41431` (4.8.0-beta.1) geschlossen. Damit steht beides, worauf dieses Issue gewartet hat: `wikitool move --reconcile` als Weg, die 80 Concept-Seiten in einem Aufruf umzusortieren, und die Titel-Schlüsselung in `migrate verify`, ohne die der Vorbehalt im Abschnitt „Version: MINOR" dieses Bodys gegolten hätte (ein reiner Move lief vorher als „80 removed, 80 added, 0 compared" grün durch). Der Body bleibt sonst unverändert; die eigene Arbeit — `layout:` in `types/concept.md`, `_area_titles()` aus dem jeweiligen Type-Spec, der Collection-Schwellenbefund in `lint` — ist unberührt und noch offen.
Randnotiz aus #57 (Commit 251e597), bevor hier jemand types/concept.md anfasst: TypeResolver.get_layout validiert layout: jetzt und weist jeden dir:-Wert ab, der kein einzelner Pfadabschnitt ist (kein /, kein \, nicht ./.., nicht leer). Für die sechs Concept-Areas ändert das nichts — workflow, architecture, pattern, decision, problem, protocol sind alle einstufig —, es schließt nur den Weg, über den ein Type-Spec eine zweite Verzeichnisebene hätte erzeugen können.
Ansonsten ist dieses Issue von #57 unberührt: die sechs Areas liegen auf Ebene 1, genau dort, wo der Katalog sie liest. Die Tiefengrenze aus kb/CONTRACT.md § Collections kostet also nichts, was hier gebraucht wird.
Randnotiz aus #57 (Commit `251e597`), bevor hier jemand `types/concept.md` anfasst: `TypeResolver.get_layout` validiert `layout:` jetzt und weist jeden `dir:`-Wert ab, der kein einzelner Pfadabschnitt ist (kein `/`, kein `\`, nicht `.`/`..`, nicht leer). Für die sechs Concept-Areas ändert das nichts — `workflow`, `architecture`, `pattern`, `decision`, `problem`, `protocol` sind alle einstufig —, es schließt nur den Weg, über den ein Type-Spec eine zweite Verzeichnisebene hätte erzeugen können.
Ansonsten ist dieses Issue von #57 unberührt: die sechs Areas liegen auf Ebene 1, genau dort, wo der Katalog sie liest. Die Tiefengrenze aus `kb/CONTRACT.md` § Collections kostet also nichts, was hier gebraucht wird.
Changelog: Body auf den Umsetzungsstand vom 2026-09-08 umgeschrieben. Alle Akzeptanzkriterien bis auf den Publish abgehakt, mit den gemessenen Zahlen statt der geplanten. Der Vorbehalt zur Stem-Schlüsselung ist raus — #56 steht, migrate verify meldet 80 moved statt 80 removed / 80 added. Zwei nicht geplante Punkte ergänzt: der Read-Surface-Boundary-Test erzwang ein neues Kernmodul tools/chemenu/catalog.py (lint_core darf nicht aus commands/ importieren), und _anchor() verschluckte Nicht-ASCII-Buchstaben, was erst mit dem ersten Area-Titel mit Umlaut (Abläufe) sichtbar wurde. Neuer Abschnitt „Offener Punkt": origin/main hat mit 63b4bb8 die Nummer 4.8.0-beta.5 bereits vergeben, der lokale Bump hat sie aus veraltetem VERSION ein zweites Mal errechnet — nach dem Fast-Forward als beta.6 zu wiederholen.
**Changelog:** Body auf den Umsetzungsstand vom 2026-09-08 umgeschrieben. Alle Akzeptanzkriterien bis auf den Publish abgehakt, mit den gemessenen Zahlen statt der geplanten. Der Vorbehalt zur Stem-Schlüsselung ist raus — #56 steht, `migrate verify` meldet `80 moved` statt `80 removed / 80 added`. Zwei nicht geplante Punkte ergänzt: der Read-Surface-Boundary-Test erzwang ein neues Kernmodul `tools/chemenu/catalog.py` (`lint_core` darf nicht aus `commands/` importieren), und `_anchor()` verschluckte Nicht-ASCII-Buchstaben, was erst mit dem ersten Area-Titel mit Umlaut (`Abläufe`) sichtbar wurde. Neuer Abschnitt „Offener Punkt": `origin/main` hat mit `63b4bb8` die Nummer `4.8.0-beta.5` bereits vergeben, der lokale Bump hat sie aus veraltetem `VERSION` ein zweites Mal errechnet — nach dem Fast-Forward als `beta.6` zu wiederholen.
Changelog: Geschlossen. Body auf den Endstand: alle neun Kriterien abgehakt, gemessene Zahlen statt geplanter, CI-Läufe 192/193 zu 7f74303 beide success nachgetragen. Der Abschnitt „Offener Punkt: Publish blockiert" ist raus — erledigt; die Versionskollision bleibt als Randnotiz stehen, weil sie erklärt, warum die Änderung als beta.6 und nicht als beta.5 ausgeliefert ist. Die Source-Entscheidung ist von „trägt nicht" auf „zurückgestellt, nicht verworfen" korrigiert: die ursprüngliche Begründung („25 von 29 sind notes") maß ein Sammelbecken und war falsch — Nachfolge als #66 (kind/decision, drei offene Fragen), inklusive der dort beantworteten Frage nach einem eigenen Typ für AI-Transkripte. Ergänzt: docs/-Veralterungsprüfung (keine der vier Seiten betroffen) und die Modellzuordnung der drei Sitzungsphasen.
**Changelog:** Geschlossen. Body auf den Endstand: alle neun Kriterien abgehakt, gemessene Zahlen statt geplanter, CI-Läufe 192/193 zu `7f74303` beide `success` nachgetragen. Der Abschnitt „Offener Punkt: Publish blockiert" ist raus — erledigt; die Versionskollision bleibt als Randnotiz stehen, weil sie erklärt, warum die Änderung als `beta.6` und nicht als `beta.5` ausgeliefert ist. Die Source-Entscheidung ist von „trägt nicht" auf „zurückgestellt, nicht verworfen" korrigiert: die ursprüngliche Begründung („25 von 29 sind `notes`") maß ein Sammelbecken und war falsch — Nachfolge als **#66** (`kind/decision`, drei offene Fragen), inklusive der dort beantworteten Frage nach einem eigenen Typ für AI-Transkripte. Ergänzt: `docs/`-Veralterungsprüfung (keine der vier Seiten betroffen) und die Modellzuordnung der drei Sitzungsphasen.
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.
Aufgeworfen in der Sitzung 2026-09-04 zur Ordnerorganisation. Hing an #56 (
move --reconcile), geschlossen mit9e41431. Nachbarn: #57 (Katalogtiefe), #58 (incoming/).Erledigt am 2026-09-08 mit
7f74303, ausgeliefert als4.8.0-beta.6.Befund
Zwei Dinge, die auseinanderzuhalten waren:
index_build.SHARD_THRESHOLD = 50, hergeleitet aus der wikieigenen SeiteIndex Scaling;index rebuildgibt einer Area über der Schwelle einen eigenenINDEX.md. Kein Mensch beteiligt.Stand des Korpus vorher:
kb/concepts/80 Seiten / keine Areas,kb/entities/72 / 5 Areas,kb/sources/29 / keine,kb/comparisons/1 / keine.kb/concepts/INDEX.mdwar damit eine einzelne Tabelle mit 80 Zeilen — über der eigenen Schwelle — und teilte sich nie, weil Sharding pro Area passiert.Ursache war eine Asymmetrie in den Type-Specs:
types/entity.mddeklarierte einlayout:,types/concept.mdundtypes/source.mdhatten ein Subtype-Feld, aber keines. Die Achse lag fertig da und wurde nicht benutzt.Entscheidung:
layout:fürconcept, vorerst nicht fürsourceFür Concepts trägt die Aufteilung: sechs Areas, die größte mit 28, alle unter der Schwelle.
types/concept.mddeklariert jetztarchitectures/patterns/protocols/workflows/decisions/problemsmit Anzeigetitel je Wert.Für Sources wurde sie zurückgestellt, nicht verworfen — und die Begründung im ursprünglichen Body („25 von 29 sind
notes") hat sich beim Nachmessen als falsch herausgestellt:notesist ein Sammelbecken, kein Befund. Tatsächlich sind es 16 Session-Transkripte, 4 LLM-Analysen, 2 Tracker-Exporte und 3 echte eigene Notizen. Die Aufteilung würde also sehr wohl tragen — aber erst nach einer Umklassifizierung von 22 Seiten, und das ist ein korpusweiter Sweep nachinstructions/dev/corpus-policy.mdTier 2 („planned only, never reactive"), der ein eigenes Issue und einenwork/-Lauf braucht. Enum-Erweiterung und Sweep sind untrennbar, weil sonst der Floor „jeder deklarierte Subtype hat ≥1 Seite" bricht. Nachfolge-Issue: #66, mit der Frage nach einem eigenen Typ für AI-Transkripte dort beantwortet (nein — der Stack ankert aufpage.kind == "source").types/source.mdbleibt bis dahin im Testbestand das dokumentierte Gegenbeispiel für „Subtype-Feld ohnelayout:":test_get_layout_is_none_for_types_without_one,test_subtype_dir_is_none_without_layout_or_subtypeundtest_compute_target_dir_is_flat_for_a_type_without_layouthängen daran statt anconcept. #66 führt diese Abhängigkeit als eigenen Punkt.Der Auslöser in
lintNeuer Befund
unsharded_collectionsinlint_core.py. Kriterium wie geplant: Collection über der Schwelle und ohne Areas und die Aufteilung drückt jede entstehende Area — inklusive des nicht platzierbaren Rests — auf die Schwelle oder darunter. Als Empfehlung, nicht als Failure; bewusst nicht inHARD_ERROR_KEYS, und anders alsunlabelled_edgesauch nicht migrationsgesteuert: es gibt keine Version, ab der eine ungeteilte Collection falsch wird.Eine Ergänzung gegenüber dem geplanten Stand: der Befund unterscheidet, ob das Type-Spec das
layout:bereits deklariert. Ohne es fehlt die erste Hälfte des Fixes (layout:nachtragen), mit ihm ist nur noch der Move offen — die Reportzeile sagt, welche der beiden gilt, statt beide Fälle mit einem Satz zu bedienen.Zwei Dinge, die das Issue nicht vorhergesehen hatte
lint_core.pydurfte nicht auscommands/index_build.pyimportieren.test_api.py::test_the_read_surface_cannot_reach_a_write_commandprüft strukturell, dasschemenu.apikein Modul unterchemenu.commandslädt; der Import hätte den CLI-Kopf mitgezogen und die Read-Surface-Grenze gebrochen.SHARD_THRESHOLD,Area,Collection,area_titles()undgroup_pages()liegen deshalb neu intools/chemenu/catalog.py, entlang derselben Linie wielint_core.py: Korpusform hier, Darstellung inindex_build.py. Der Boundary-Test hat den Fehler gefangen, bevor er gepublisht war — genau das, wofür er da ist._anchor()strich mit[^a-z0-9\s-]jeden Nicht-ASCII-Buchstaben ersatzlos. Die Karte verlinkte auf#ablufe, während die Überschrift im Shard#abläufeheißt. Jeder Deep-Link in einen Shard mit nicht-englischem Area-Titel wäre still tot gewesen. Vorher fiel es nicht auf, weil alle fünf Entity-Area-Titel zufällig ASCII sind;Abläufeist der erste, der es nicht ist. Auf[^\w\s-]mitre.UNICODEumgestellt, mit eigenem Test.Version: MINOR
Der Umzug ist ein Angebot, kein Zwang.
group_pagesliest das Dateisystem, nicht daslayout:, also rendert der gemischte Zustand weiter — flache Bestandsseiten landen in Area „All",layout:-platzierte Neuanlagen in ihrer eigenen. Der gemischte Zustand meldet sich alslint-Befund Misplaced Pages, der seit jeher advisory ist. Ein Downgrade auf einen Stack ohne dieseslayout:funktioniert weiter; nur die Anzeigetitel fallen auf.title()zurück. Kosmetik, kein Bruch der Austauschbarkeit in beiden Richtungen, also kein--majorund keininstructions/migrate-corpus.md-Fall — es wird kein Fließtext umgeschrieben.Akzeptanzkriterien
types/concept.mddeklariert einlayout:für alle sechsconcept_type-Werte, mit Verzeichnis und Anzeigetitel je Wert. Ein Test hält Layout und Schema-Enum deckungsgleich, damit kein Wert stillschweigend auf die Pluralisierungs-Fallback läuft.entity— und pro Collection geschlüsselt, damit zwei Typen denselben Area-Namen für Verschiedenes benutzen dürfen. Liegt alscatalog.area_titles()vor, nicht mehr alsindex_build._area_titles().lintmeldet eine Collection über der Schwelle ohne Areas, mit der Verteilung des Subtype-Felds, und nur wenn die Aufteilung trägt. Fünf Tests statt der geforderten zwei: einer der feuert (Concepts-Form), einer der unter der Schwelle schweigt (Sources-Form), einer der bei schiefer Verteilung schweigt (70/6/5 — die zweite Hälfte des Kriteriums, die eine reine Schwellenprüfung falsch beantwortet hätte), einer der nach dem Fix schweigt, einer für die Layout-fehlt-Variante der Meldung.lintbleibt grün, wenn er der einzige Punkt im Report ist.move --reconcileliegen alle 80 Concept-Seiten in ihrer Area, mit unverändertem Titel, Body und Frontmatter.migrate verify --from HEAD: 182 compared, 0 added, 0 removed, 80 moved, 0 findings. Git hat den Commit unabhängig davon als 80 ×rename … (100%)erfasst.index rebuilderzeugt sechs Concept-Areas: Abläufe 28, Architekturen 20, Muster 17, Entscheidungen 7, Problemstellungen 5, Protokolle 3. Keine über der Schwelle, also kein eigener Shard — die Schwelle wirkt wieder als Schwelle statt als toter Wert.kb/concepts/COLLECTION.mdbeschreibt die Areas;kb/CONTRACT.mdunverändert (die Area-Regel stand dort bereits).test_new_concept_creates_page_in_its_subtype_area. Annewhat sich dafür nichts geändert; das Type-Spec hat es getan.move --reconcilefür eine bestehende Instanz ein Angebot ist und kein Zwang.Verifiziert
pytest1055 passed ·docs verifyOK (52 Kommandos, 4 Collections, 7 Stage-Contracts,CHANGES.mdauf4.8.0-beta.6) ·instructions verifyOK (20 Instructions, 7 Skills, 14 publizierte Kopien deckungsgleich) ·lintgrün, weder Misplaced Pages noch der neue Befund; die zwei verbleibenden Sektionen (Orphan Pages, Redundant see-also) standen vorher schon da und sind beide advisory. CI zu7f74303: Läufe 192 und 193, beidesuccess.Geändert:
types/concept.md,types/type-spec.md,tools/chemenu/catalog.py(neu),tools/chemenu/commands/index_build.py,tools/chemenu/lint_core.py, neun Testdateien (das Fixture-Concept liegt jetzt in seiner Area),tools/CONTRACT.md,tools/README.md,README.md,CHANGES.md,VERSION,kb/concepts/COLLECTION.md,kb/log.md, plus die 80 bewegten Seiten und die neu erzeugten Katalogdateien.docs/ist unberührt geprüft: keine der vier Seiten schreibt Begründungen zu Katalog, Areas oder Subtype-Achsen auf.docs/ownership-and-templates.mdnenntroot: kb-Type-Specs als instanzeigen — genau die Eigentümerschaft, unter dertypes/concept.mdhier geändert wurde, also kein Widerspruch, sondern die Regel im Vollzug.Zwei Randnotizen aus dieser Sitzung
origin/maintrug seit63b4bb8(2026-09-05) einen Commit, den der Checkout nie gesehen hatte, und hatte4.8.0-beta.5bereits vergeben. Der lokaleversion bumperrechnete dieselbe Nummer aus veraltetemVERSION. Nachsyncalsbeta.6wiederholt. Kein Fehler im Tool —version bumprechnet korrekt aus dem, was der Baum weiß; wer nicht synct, weiß zu wenig.instructions/session-setup.mdempfiehltsynczum Sitzungsbeginn genau dafür. Derselbe Commit hatte auchinstructions/wiki-query/SKILL.mdbewegt, ohneinstructions synczu laufen — die publizierten Kopien waren gedriftet und wurden hier nachgezogen.63b4bb8mitgezogen: dieREADME.md-Zeile lasUse singular for entities: `HA Integration.md` (not `HA Integration.md`)— beide Seiten des „not" identisch, also eine Regel ohne Gegenbeispiel.kb/CONVENTIONS.mdundkb/entities/COLLECTION.mdhatten im selben Commit das korrekte Paar bekommen;README.mdzieht jetzt mitHA Integrations.mdnach.Modelle in dieser Sitzung
Alle drei Phasen liefen auf Opus 5: Design und Versionsteil, der mechanische Mittelteil (Code, Tests, Bump), und diese Abschlussphase. Der Wechselvorschlag auf Sonnet/high wurde an beiden vorgesehenen Punkten angeboten (
stack-devSchritt 3,stack-closeSchritt 1) und nicht angenommen — was für den unbewachten Mittelteil dieser Änderung eher zu viel als zu wenig war, für die zwei ungeprüften Ränder aber genau richtig.Changelog:
status/blockedentfernt — #56 ist mit Commit9e41431(4.8.0-beta.1) geschlossen. Damit steht beides, worauf dieses Issue gewartet hat:wikitool move --reconcileals Weg, die 80 Concept-Seiten in einem Aufruf umzusortieren, und die Titel-Schlüsselung inmigrate verify, ohne die der Vorbehalt im Abschnitt „Version: MINOR" dieses Bodys gegolten hätte (ein reiner Move lief vorher als „80 removed, 80 added, 0 compared" grün durch). Der Body bleibt sonst unverändert; die eigene Arbeit —layout:intypes/concept.md,_area_titles()aus dem jeweiligen Type-Spec, der Collection-Schwellenbefund inlint— ist unberührt und noch offen.Randnotiz aus #57 (Commit
251e597), bevor hier jemandtypes/concept.mdanfasst:TypeResolver.get_layoutvalidiertlayout:jetzt und weist jedendir:-Wert ab, der kein einzelner Pfadabschnitt ist (kein/, kein\, nicht./.., nicht leer). Für die sechs Concept-Areas ändert das nichts —workflow,architecture,pattern,decision,problem,protocolsind alle einstufig —, es schließt nur den Weg, über den ein Type-Spec eine zweite Verzeichnisebene hätte erzeugen können.Ansonsten ist dieses Issue von #57 unberührt: die sechs Areas liegen auf Ebene 1, genau dort, wo der Katalog sie liest. Die Tiefengrenze aus
kb/CONTRACT.md§ Collections kostet also nichts, was hier gebraucht wird.Changelog: Body auf den Umsetzungsstand vom 2026-09-08 umgeschrieben. Alle Akzeptanzkriterien bis auf den Publish abgehakt, mit den gemessenen Zahlen statt der geplanten. Der Vorbehalt zur Stem-Schlüsselung ist raus — #56 steht,
migrate verifymeldet80 movedstatt80 removed / 80 added. Zwei nicht geplante Punkte ergänzt: der Read-Surface-Boundary-Test erzwang ein neues Kernmodultools/chemenu/catalog.py(lint_coredarf nicht auscommands/importieren), und_anchor()verschluckte Nicht-ASCII-Buchstaben, was erst mit dem ersten Area-Titel mit Umlaut (Abläufe) sichtbar wurde. Neuer Abschnitt „Offener Punkt":origin/mainhat mit63b4bb8die Nummer4.8.0-beta.5bereits vergeben, der lokale Bump hat sie aus veraltetemVERSIONein zweites Mal errechnet — nach dem Fast-Forward alsbeta.6zu wiederholen.Changelog: Geschlossen. Body auf den Endstand: alle neun Kriterien abgehakt, gemessene Zahlen statt geplanter, CI-Läufe 192/193 zu
7f74303beidesuccessnachgetragen. Der Abschnitt „Offener Punkt: Publish blockiert" ist raus — erledigt; die Versionskollision bleibt als Randnotiz stehen, weil sie erklärt, warum die Änderung alsbeta.6und nicht alsbeta.5ausgeliefert ist. Die Source-Entscheidung ist von „trägt nicht" auf „zurückgestellt, nicht verworfen" korrigiert: die ursprüngliche Begründung („25 von 29 sindnotes") maß ein Sammelbecken und war falsch — Nachfolge als #66 (kind/decision, drei offene Fragen), inklusive der dort beantworteten Frage nach einem eigenen Typ für AI-Transkripte. Ergänzt:docs/-Veralterungsprüfung (keine der vier Seiten betroffen) und die Modellzuordnung der drei Sitzungsphasen.torben referenced this issue2026-09-08 17:32:39 +00:00