Umgesetzt und publiziert am 2026-09-04, Commit 251e597, Stack-Kandidat 4.8.0-beta.2. Aufgeworfen in der Sitzung zur Ordnerorganisation, neben #56 (move, erledigt), #59 (layout: für concept) und #58 (incoming/).
Was das Problem war
index_build.group_pages las genau zwei Ebenen unter kb/:
Diese drei Verzeichnisse erschienen im Katalog nirgends; ihre Seiten standen in der Tabelle von kb/entities/projects/, als lägen sie direkt darin. Ein eigener Shard war für sie strukturell unerreichbar — area.own_shard wird nur für Areas auf Ebene 1 gesetzt.
Warum das ein Defekt und keine Wunschfunktion war: Doku und Realität widersprachen sich. kb/CONTRACT.md § Collections beschrieb genau eine Ebene und sagte nichts über eine zweite; der Baum hatte trotzdem eine. Der Katalog, der laut AGENTS.md Invariante 1 generiert und verlässlich ist, beschrieb den Baum an dieser Stelle falsch. Einen unterstützten Weg, wie die Verzeichnisse entstanden sein können, gab es nicht — sie waren von Hand angelegt.
Ein Detail, das den Befund eingegrenzt hat: die Katalogzeilen sind [[wikilinks]], keine Pfade (index_build._table). Der Katalog verlinkte also nicht ins Leere, er gruppierte nur falsch. Belegt durch den Publish: kb/index.md und die INDEX.md-Shards haben sich beim Hochziehen der drei Seiten nicht geändert.
Entscheidung: (b), die drei Verzeichnisse auflösen
Der Alternativentwurf (a) — group_pages rekursiv über beliebige Tiefe, Shard-Schwelle pro Ebene — ist verworfen. Die drei Verzeichnisse gruppierten nach Owner (kfchou, vanillaflava, yugasun), eine Achse, die der Katalog an keiner anderen Stelle kennt und die aus keinem Frontmatter-Feld folgt. Jede andere Area im Baum kommt aus einem Subtype (entity_type über layout:); diese drei kamen aus einer Ad-hoc-Entscheidung beim Anlegen. Den Katalog rekursiv zu machen, um eine Achse abzubilden, die nichts erzeugt und nichts prüft, kauft Komplexität für einen Einzelfall.
Tiefe 1 ist damit geschriebene Grenze in kb/CONTRACT.md § Collections. Falls später eine echte zweite Ebene auftaucht — eine Collection, deren Areas selbst über die Schwelle wachsen — wird (a) neu aufgemacht, dann mit einem Grund, der aus dem Schema kommt.
Was gebaut wurde
kb_scan.find_nested_pages(kb_dir, pages) — (title, page, depth) für jede Seite mehr als ein Verzeichnis unterhalb ihrer Collection. Reine Pfadtiefe, bewusst unabhängig von der Typauflösung: find_misplaced überspringt eine Seite ohne oder mit unauflösbarem type:, und genau die wäre sonst die verschachtelte Seite, die niemand meldet.
Lint-Befund nested_pages, mit Ist-Pfad und Tiefe — und hart, in HARD_ERROR_KEYS, anders als misplaced_pages. Begründung steht im Kommentarblock über HARD_ERROR_KEYS: eine fehlplatzierte Seite katalogisiert noch korrekt von der falschen Stelle aus, eine verschachtelte macht den generierten Katalog selbst falsch. Nicht migrations-gegatet, weil es keine Version gibt, ab der das erst falsch wird.
index rebuild warnt, statt abzulehnen.group_pages faltet unverändert wie zuvor; neu ist nur die Sichtbarkeit. Hart abzulehnen hätte auf jeder Fremdinstanz mit handverschachtelter Seite den Katalog blockiert — Handarbeit beim Upgrade, also die Major-Zeile aus version-parts.md statt des geplanten PATCH, und ein Widerspruch zu #56s Entscheidung 2.
TypeResolver.get_layout validiert layout: {dir: …} auf einen einzelnen Pfadabschnitt (kein /, kein \, nicht ./.., nicht leer). Ohne das wäre die neue Regel über den einzigen unterstützten Weg brechbar geblieben: ein Type-Spec mit dir: projects/owner hätte eine zweite Ebene erzeugt, die new selbst schreibt.
move entfernt ein Verzeichnis, das es geleert hat (--page wie --reconcile), symmetrisch zum mkdir(parents=True) auf der Zielseite. Nicht im ursprünglichen Entwurf, aber ohne das hätten kfchou/, vanillaflava/, yugasun/ ihren eigenen Fix überlebt: leer, für git unsichtbar, für einen verzeichnisbasierten Test sichtbar.
Verschiebemechanik
Wie vorgesehen über wikitool move --reconcile aus #56, nicht von Hand. Der Trockenlauf meldete exakt die drei Seiten und keine Kollision; die Stems sind verschieden, sie trafen unter kb/entities/projects/ auf 11 bestehende Dateien ohne Überschneidung. Ein belegtes Ziel hätte move ohnehin verweigert statt still überschrieben.
Was verifiziert wurde
pytest: 1008/1008 grün lokal (vorher 998; 10 neue Tests über test_kb_scan.py, test_lint.py, test_index_build.py, test_type_resolver.py, test_page_ops.py).
lint --fail-on-error: Exit 0, weder nested_pages noch misplaced_pages noch duplicate_titles.
migrate verify --from HEAD: compared == 182, added == 0, removed == 0, alle drei Seiten als moved gemeldet. Git hat sie unabhängig davon als 100%-Renames erkannt.
find kb -mindepth 3 -type d liefert nichts.
CI-Lauf 178 — success, inklusive setup-instance-Replay gegen einen frischen dist export. Der parallele Release-Lauf 179 lief folgenlos durch und legte korrekt keine Release an — 4.8.0-beta.2 ist ein Kandidat, neueste Release bleibt v4.7.4.
Publish über das Mass-Update-Gate: 20 gezählte Dateien, Exit 42, vom Nutzer nach Vorlage der vollständigen Dateiliste freigegeben.
Akzeptanzkriterien
Kein Verzeichnis unter kb/ liegt tiefer als kb/<collection>/<area>/. Als bleibende Eigenschaft über lint statt über pytest — siehe die Umformulierung unten.
Die drei Seiten sind unter kb/entities/projects/ erreichbar, mit unveränderten Titeln. find_duplicate_title_paths meldet keine Kollision (lint Exit 0).
migrate verify --from HEAD bestätigt, dass die drei Seiten dieselben sind: compared == 182, added == 0, removed == 0, alle drei unter moved. Die Titel-Schlüsselung aus #56 stand, der Lauf hat also tatsächlich verglichen statt gezählt.
kb/CONTRACT.md § Collections schreibt Tiefe 1 als Grenze fest, mit der Begründung aus diesem Issue und dem Verweis auf den harten Lint-Befund.
group_pages faltet eine Seite auf Tiefe 2 nicht mehr still: index rebuild warnt, lint meldet sie hart. Gewählt wurde „melden", nicht „ablehnen" — Begründung oben unter Was gebaut wurde.
Changelog-Eintrag, PATCH. Gefaltet in den offenen 4.8.0-Kandidaten (max-wins gegen #56s MINOR-Bewegung), Heading jetzt 4.8.0-beta.2.
Umformulierung des ersten Kriteriums. Der Originaltext verlangte „ein Test läuft über den Baum". Gebaut ist stattdessen der harte nested_pages-Befund, den CI in .gitea/workflows/ci.yml (Schritt Verify the development tree) bei jedem Push als wikitool lint --fail-on-error gegen den echten Korpus laufen lässt; die Mechanik selbst deckt pytest über Fixtures ab. Grund: dist_cmd schließt tools/chemenu/tests/nicht aus, ein pytest über config.KB_DIR wäre also in jeder ausgelieferten Instanz ein Korpus-Health-Check und würde dort rot, sobald jemand von Hand verschachtelt. Instanzgesundheit ist lint/doctors Aufgabe, nicht die der Stack-Suite. Die geforderte Eigenschaft — dauerhaft geprüft, nicht einmalig aufgeräumt — ist damit erfüllt, an der richtigen Stelle.
Was das für die Nachbarn heißt
#59 ist unberührt: seine sechs Concept-Areas liegen alle auf Ebene 1. Neu zu beachten ist nur, dass das dort einzuführende layout: in types/concept.md je dir: einen einzelnen Pfadabschnitt tragen muss — get_layout weist alles andere jetzt ab.
#58 ist unberührt; raw/ kennt diese Tiefengrenze nicht, dort ist die Tiefe ausdrücklich nicht das Problem.
#56 hat mit moves Verzeichnisaufräumen eine kleine Nachlieferung bekommen, die in seinem eigenen Paket noch nicht drin war.
Umgesetzt und publiziert am 2026-09-04, Commit `251e597`, Stack-Kandidat `4.8.0-beta.2`. Aufgeworfen in der Sitzung zur Ordnerorganisation, neben #56 (`move`, erledigt), #59 (`layout:` für `concept`) und #58 (`incoming/`).
## Was das Problem war
`index_build.group_pages` las genau zwei Ebenen unter `kb/`:
```python
parts = page.path.relative_to(kb_dir).parts
collection_name = parts[0]
area_name = parts[1] if len(parts) > 2 else ""
```
Alles unterhalb von `parts[1]` kollabierte in die Level-1-Area. Betroffen waren drei reale Seiten:
```
kb/entities/projects/kfchou/wiki-skills.md
kb/entities/projects/vanillaflava/wiki-skills-vanillaflava.md
kb/entities/projects/yugasun/llm-wiki-skills.md
```
Diese drei Verzeichnisse erschienen im Katalog nirgends; ihre Seiten standen in der Tabelle von `kb/entities/projects/`, als lägen sie direkt darin. Ein eigener Shard war für sie strukturell unerreichbar — `area.own_shard` wird nur für Areas auf Ebene 1 gesetzt.
**Warum das ein Defekt und keine Wunschfunktion war:** Doku und Realität widersprachen sich. `kb/CONTRACT.md` § Collections beschrieb genau eine Ebene und sagte nichts über eine zweite; der Baum hatte trotzdem eine. Der Katalog, der laut AGENTS.md Invariante 1 generiert und verlässlich ist, beschrieb den Baum an dieser Stelle falsch. Einen unterstützten Weg, wie die Verzeichnisse entstanden sein können, gab es nicht — sie waren von Hand angelegt.
Ein Detail, das den Befund eingegrenzt hat: die Katalogzeilen sind `[[wikilinks]]`, keine Pfade (`index_build._table`). Der Katalog verlinkte also nicht ins Leere, er gruppierte nur falsch. Belegt durch den Publish: `kb/index.md` und die `INDEX.md`-Shards haben sich beim Hochziehen der drei Seiten **nicht** geändert.
## Entscheidung: (b), die drei Verzeichnisse auflösen
Der Alternativentwurf (a) — `group_pages` rekursiv über beliebige Tiefe, Shard-Schwelle pro Ebene — ist verworfen. Die drei Verzeichnisse gruppierten nach **Owner** (`kfchou`, `vanillaflava`, `yugasun`), eine Achse, die der Katalog an keiner anderen Stelle kennt und die aus keinem Frontmatter-Feld folgt. Jede andere Area im Baum kommt aus einem Subtype (`entity_type` über `layout:`); diese drei kamen aus einer Ad-hoc-Entscheidung beim Anlegen. Den Katalog rekursiv zu machen, um eine Achse abzubilden, die nichts erzeugt und nichts prüft, kauft Komplexität für einen Einzelfall.
Tiefe 1 ist damit geschriebene Grenze in `kb/CONTRACT.md` § Collections. Falls später eine echte zweite Ebene auftaucht — eine Collection, deren Areas selbst über die Schwelle wachsen — wird (a) neu aufgemacht, dann mit einem Grund, der aus dem Schema kommt.
## Was gebaut wurde
**`kb_scan.find_nested_pages(kb_dir, pages)`** — `(title, page, depth)` für jede Seite mehr als ein Verzeichnis unterhalb ihrer Collection. Reine Pfadtiefe, bewusst unabhängig von der Typauflösung: `find_misplaced` überspringt eine Seite ohne oder mit unauflösbarem `type:`, und genau die wäre sonst die verschachtelte Seite, die niemand meldet.
**Lint-Befund `nested_pages`**, mit Ist-Pfad und Tiefe — und **hart**, in `HARD_ERROR_KEYS`, anders als `misplaced_pages`. Begründung steht im Kommentarblock über `HARD_ERROR_KEYS`: eine fehlplatzierte Seite katalogisiert noch korrekt von der falschen Stelle aus, eine verschachtelte macht den *generierten* Katalog selbst falsch. Nicht migrations-gegatet, weil es keine Version gibt, ab der das erst falsch wird.
**`index rebuild` warnt, statt abzulehnen.** `group_pages` faltet unverändert wie zuvor; neu ist nur die Sichtbarkeit. Hart abzulehnen hätte auf jeder Fremdinstanz mit handverschachtelter Seite den Katalog blockiert — Handarbeit beim Upgrade, also die Major-Zeile aus `version-parts.md` statt des geplanten PATCH, und ein Widerspruch zu #56s Entscheidung 2.
**`TypeResolver.get_layout` validiert `layout: {dir: …}`** auf einen einzelnen Pfadabschnitt (kein `/`, kein `\`, nicht `.`/`..`, nicht leer). Ohne das wäre die neue Regel über den einzigen *unterstützten* Weg brechbar geblieben: ein Type-Spec mit `dir: projects/owner` hätte eine zweite Ebene erzeugt, die `new` selbst schreibt.
**`move` entfernt ein Verzeichnis, das es geleert hat** (`--page` wie `--reconcile`), symmetrisch zum `mkdir(parents=True)` auf der Zielseite. Nicht im ursprünglichen Entwurf, aber ohne das hätten `kfchou/`, `vanillaflava/`, `yugasun/` ihren eigenen Fix überlebt: leer, für git unsichtbar, für einen verzeichnisbasierten Test sichtbar.
## Verschiebemechanik
Wie vorgesehen über `wikitool move --reconcile` aus #56, nicht von Hand. Der Trockenlauf meldete exakt die drei Seiten und keine Kollision; die Stems sind verschieden, sie trafen unter `kb/entities/projects/` auf 11 bestehende Dateien ohne Überschneidung. Ein belegtes Ziel hätte `move` ohnehin verweigert statt still überschrieben.
## Was verifiziert wurde
- `pytest`: **1008/1008 grün** lokal (vorher 998; 10 neue Tests über `test_kb_scan.py`, `test_lint.py`, `test_index_build.py`, `test_type_resolver.py`, `test_page_ops.py`).
- `tools/wikitool docs verify` und `instructions verify`: grün (51 Kommandos, 20 Instructions, 7 Skills).
- `lint --fail-on-error`: **Exit 0**, weder `nested_pages` noch `misplaced_pages` noch `duplicate_titles`.
- `migrate verify --from HEAD`: `compared == 182, added == 0, removed == 0`, alle drei Seiten als `moved` gemeldet. Git hat sie unabhängig davon als **100%-Renames** erkannt.
- `find kb -mindepth 3 -type d` liefert nichts.
- **CI-Lauf [178](https://gitea.nehmer.net/torben/chemenu/actions/runs/178) — success**, inklusive `setup-instance`-Replay gegen einen frischen `dist export`. Der parallele Release-Lauf [179](https://gitea.nehmer.net/torben/chemenu/actions/runs/179) lief folgenlos durch und legte korrekt **keine** Release an — `4.8.0-beta.2` ist ein Kandidat, neueste Release bleibt `v4.7.4`.
- Publish über das Mass-Update-Gate: 20 gezählte Dateien, Exit 42, vom Nutzer nach Vorlage der vollständigen Dateiliste freigegeben.
## Akzeptanzkriterien
- [x] Kein Verzeichnis unter `kb/` liegt tiefer als `kb/<collection>/<area>/`. **Als bleibende Eigenschaft über `lint` statt über pytest** — siehe die Umformulierung unten.
- [x] Die drei Seiten sind unter `kb/entities/projects/` erreichbar, mit unveränderten Titeln. `find_duplicate_title_paths` meldet keine Kollision (`lint` Exit 0).
- [x] `migrate verify --from HEAD` bestätigt, dass die drei Seiten dieselben sind: `compared == 182, added == 0, removed == 0`, alle drei unter `moved`. Die Titel-Schlüsselung aus #56 stand, der Lauf hat also tatsächlich verglichen statt gezählt.
- [x] `kb/CONTRACT.md` § Collections schreibt Tiefe 1 als Grenze fest, mit der Begründung aus diesem Issue und dem Verweis auf den harten Lint-Befund.
- [x] `group_pages` faltet eine Seite auf Tiefe 2 nicht mehr still: `index rebuild` warnt, `lint` meldet sie hart. **Gewählt wurde „melden", nicht „ablehnen"** — Begründung oben unter *Was gebaut wurde*.
- [x] Changelog-Eintrag, **PATCH**. Gefaltet in den offenen `4.8.0`-Kandidaten (max-wins gegen #56s MINOR-Bewegung), Heading jetzt `4.8.0-beta.2`.
**Umformulierung des ersten Kriteriums.** Der Originaltext verlangte „ein Test läuft über den Baum". Gebaut ist stattdessen der harte `nested_pages`-Befund, den CI in `.gitea/workflows/ci.yml` (Schritt *Verify the development tree*) bei jedem Push als `wikitool lint --fail-on-error` gegen den echten Korpus laufen lässt; die Mechanik selbst deckt pytest über Fixtures ab. Grund: `dist_cmd` schließt `tools/chemenu/tests/` **nicht** aus, ein pytest über `config.KB_DIR` wäre also in jeder ausgelieferten Instanz ein Korpus-Health-Check und würde dort rot, sobald jemand von Hand verschachtelt. Instanzgesundheit ist `lint`/`doctor`s Aufgabe, nicht die der Stack-Suite. Die geforderte Eigenschaft — dauerhaft geprüft, nicht einmalig aufgeräumt — ist damit erfüllt, an der richtigen Stelle.
## Was das für die Nachbarn heißt
- **#59** ist unberührt: seine sechs Concept-Areas liegen alle auf Ebene 1. Neu zu beachten ist nur, dass das dort einzuführende `layout:` in `types/concept.md` je `dir:` einen einzelnen Pfadabschnitt tragen muss — `get_layout` weist alles andere jetzt ab.
- **#58** ist unberührt; `raw/` kennt diese Tiefengrenze nicht, dort ist die Tiefe ausdrücklich nicht das Problem.
- **#56** hat mit `move`s Verzeichnisaufräumen eine kleine Nachlieferung bekommen, die in seinem eigenen Paket noch nicht drin war.
Body auf den Endstand umgeschrieben. Gegenüber dem Ausgangstext geändert:
Die beiden Wege (a)/(b) stehen nicht mehr als Vorschlag, sondern als getroffene Entscheidung mit Umsetzung.
Vier Entwurfspunkte, die der Ausgangstext offen ließ, sind entschieden und im Body begründet: melden statt ablehnen in group_pages/index rebuild, nested_pages hart statt advisory, layout:-dir-Validierung auf einen Pfadabschnitt, move räumt geleerte Verzeichnisse ab.
Das erste Akzeptanzkriterium ist umformuliert: der geforderte „Test über den Baum" ist der harte Lint-Befund, den CI gegen den echten Korpus laufen lässt, nicht ein pytest über config.KB_DIR — Grund im Body (die Suite wird mit dist export ausgeliefert).
Neu belegt statt vermutet: der Katalog verlinkte nie ins Leere (Zeilen sind Wikilinks), nachgewiesen dadurch, dass kb/index.md und die Shards sich beim Move nicht geändert haben.
Verifikationsabschnitt ergänzt (pytest 1008, CI 178/179, migrate verify, Mass-Update-Gate mit Freigabe).
Body auf den Endstand umgeschrieben. Gegenüber dem Ausgangstext geändert:
- Die beiden Wege (a)/(b) stehen nicht mehr als Vorschlag, sondern als getroffene Entscheidung mit Umsetzung.
- Vier Entwurfspunkte, die der Ausgangstext offen ließ, sind entschieden und im Body begründet: melden statt ablehnen in `group_pages`/`index rebuild`, `nested_pages` hart statt advisory, `layout:`-`dir`-Validierung auf einen Pfadabschnitt, `move` räumt geleerte Verzeichnisse ab.
- Das erste Akzeptanzkriterium ist umformuliert: der geforderte „Test über den Baum" ist der harte Lint-Befund, den CI gegen den echten Korpus laufen lässt, nicht ein pytest über `config.KB_DIR` — Grund im Body (die Suite wird mit `dist export` ausgeliefert).
- Neu belegt statt vermutet: der Katalog verlinkte nie ins Leere (Zeilen sind Wikilinks), nachgewiesen dadurch, dass `kb/index.md` und die Shards sich beim Move nicht geändert haben.
- Verifikationsabschnitt ergänzt (pytest 1008, CI 178/179, `migrate verify`, Mass-Update-Gate mit Freigabe).
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.
Umgesetzt und publiziert am 2026-09-04, Commit
251e597, Stack-Kandidat4.8.0-beta.2. Aufgeworfen in der Sitzung zur Ordnerorganisation, neben #56 (move, erledigt), #59 (layout:fürconcept) und #58 (incoming/).Was das Problem war
index_build.group_pageslas genau zwei Ebenen unterkb/:Alles unterhalb von
parts[1]kollabierte in die Level-1-Area. Betroffen waren drei reale Seiten:Diese drei Verzeichnisse erschienen im Katalog nirgends; ihre Seiten standen in der Tabelle von
kb/entities/projects/, als lägen sie direkt darin. Ein eigener Shard war für sie strukturell unerreichbar —area.own_shardwird nur für Areas auf Ebene 1 gesetzt.Warum das ein Defekt und keine Wunschfunktion war: Doku und Realität widersprachen sich.
kb/CONTRACT.md§ Collections beschrieb genau eine Ebene und sagte nichts über eine zweite; der Baum hatte trotzdem eine. Der Katalog, der laut AGENTS.md Invariante 1 generiert und verlässlich ist, beschrieb den Baum an dieser Stelle falsch. Einen unterstützten Weg, wie die Verzeichnisse entstanden sein können, gab es nicht — sie waren von Hand angelegt.Ein Detail, das den Befund eingegrenzt hat: die Katalogzeilen sind
[[wikilinks]], keine Pfade (index_build._table). Der Katalog verlinkte also nicht ins Leere, er gruppierte nur falsch. Belegt durch den Publish:kb/index.mdund dieINDEX.md-Shards haben sich beim Hochziehen der drei Seiten nicht geändert.Entscheidung: (b), die drei Verzeichnisse auflösen
Der Alternativentwurf (a) —
group_pagesrekursiv über beliebige Tiefe, Shard-Schwelle pro Ebene — ist verworfen. Die drei Verzeichnisse gruppierten nach Owner (kfchou,vanillaflava,yugasun), eine Achse, die der Katalog an keiner anderen Stelle kennt und die aus keinem Frontmatter-Feld folgt. Jede andere Area im Baum kommt aus einem Subtype (entity_typeüberlayout:); diese drei kamen aus einer Ad-hoc-Entscheidung beim Anlegen. Den Katalog rekursiv zu machen, um eine Achse abzubilden, die nichts erzeugt und nichts prüft, kauft Komplexität für einen Einzelfall.Tiefe 1 ist damit geschriebene Grenze in
kb/CONTRACT.md§ Collections. Falls später eine echte zweite Ebene auftaucht — eine Collection, deren Areas selbst über die Schwelle wachsen — wird (a) neu aufgemacht, dann mit einem Grund, der aus dem Schema kommt.Was gebaut wurde
kb_scan.find_nested_pages(kb_dir, pages)—(title, page, depth)für jede Seite mehr als ein Verzeichnis unterhalb ihrer Collection. Reine Pfadtiefe, bewusst unabhängig von der Typauflösung:find_misplacedüberspringt eine Seite ohne oder mit unauflösbaremtype:, und genau die wäre sonst die verschachtelte Seite, die niemand meldet.Lint-Befund
nested_pages, mit Ist-Pfad und Tiefe — und hart, inHARD_ERROR_KEYS, anders alsmisplaced_pages. Begründung steht im Kommentarblock überHARD_ERROR_KEYS: eine fehlplatzierte Seite katalogisiert noch korrekt von der falschen Stelle aus, eine verschachtelte macht den generierten Katalog selbst falsch. Nicht migrations-gegatet, weil es keine Version gibt, ab der das erst falsch wird.index rebuildwarnt, statt abzulehnen.group_pagesfaltet unverändert wie zuvor; neu ist nur die Sichtbarkeit. Hart abzulehnen hätte auf jeder Fremdinstanz mit handverschachtelter Seite den Katalog blockiert — Handarbeit beim Upgrade, also die Major-Zeile ausversion-parts.mdstatt des geplanten PATCH, und ein Widerspruch zu #56s Entscheidung 2.TypeResolver.get_layoutvalidiertlayout: {dir: …}auf einen einzelnen Pfadabschnitt (kein/, kein\, nicht./.., nicht leer). Ohne das wäre die neue Regel über den einzigen unterstützten Weg brechbar geblieben: ein Type-Spec mitdir: projects/ownerhätte eine zweite Ebene erzeugt, dienewselbst schreibt.moveentfernt ein Verzeichnis, das es geleert hat (--pagewie--reconcile), symmetrisch zummkdir(parents=True)auf der Zielseite. Nicht im ursprünglichen Entwurf, aber ohne das hättenkfchou/,vanillaflava/,yugasun/ihren eigenen Fix überlebt: leer, für git unsichtbar, für einen verzeichnisbasierten Test sichtbar.Verschiebemechanik
Wie vorgesehen über
wikitool move --reconcileaus #56, nicht von Hand. Der Trockenlauf meldete exakt die drei Seiten und keine Kollision; die Stems sind verschieden, sie trafen unterkb/entities/projects/auf 11 bestehende Dateien ohne Überschneidung. Ein belegtes Ziel hättemoveohnehin verweigert statt still überschrieben.Was verifiziert wurde
pytest: 1008/1008 grün lokal (vorher 998; 10 neue Tests übertest_kb_scan.py,test_lint.py,test_index_build.py,test_type_resolver.py,test_page_ops.py).tools/wikitool docs verifyundinstructions verify: grün (51 Kommandos, 20 Instructions, 7 Skills).lint --fail-on-error: Exit 0, wedernested_pagesnochmisplaced_pagesnochduplicate_titles.migrate verify --from HEAD:compared == 182, added == 0, removed == 0, alle drei Seiten alsmovedgemeldet. Git hat sie unabhängig davon als 100%-Renames erkannt.find kb -mindepth 3 -type dliefert nichts.setup-instance-Replay gegen einen frischendist export. Der parallele Release-Lauf 179 lief folgenlos durch und legte korrekt keine Release an —4.8.0-beta.2ist ein Kandidat, neueste Release bleibtv4.7.4.Akzeptanzkriterien
kb/liegt tiefer alskb/<collection>/<area>/. Als bleibende Eigenschaft überlintstatt über pytest — siehe die Umformulierung unten.kb/entities/projects/erreichbar, mit unveränderten Titeln.find_duplicate_title_pathsmeldet keine Kollision (lintExit 0).migrate verify --from HEADbestätigt, dass die drei Seiten dieselben sind:compared == 182, added == 0, removed == 0, alle drei untermoved. Die Titel-Schlüsselung aus #56 stand, der Lauf hat also tatsächlich verglichen statt gezählt.kb/CONTRACT.md§ Collections schreibt Tiefe 1 als Grenze fest, mit der Begründung aus diesem Issue und dem Verweis auf den harten Lint-Befund.group_pagesfaltet eine Seite auf Tiefe 2 nicht mehr still:index rebuildwarnt,lintmeldet sie hart. Gewählt wurde „melden", nicht „ablehnen" — Begründung oben unter Was gebaut wurde.4.8.0-Kandidaten (max-wins gegen #56s MINOR-Bewegung), Heading jetzt4.8.0-beta.2.Umformulierung des ersten Kriteriums. Der Originaltext verlangte „ein Test läuft über den Baum". Gebaut ist stattdessen der harte
nested_pages-Befund, den CI in.gitea/workflows/ci.yml(Schritt Verify the development tree) bei jedem Push alswikitool lint --fail-on-errorgegen den echten Korpus laufen lässt; die Mechanik selbst deckt pytest über Fixtures ab. Grund:dist_cmdschließttools/chemenu/tests/nicht aus, ein pytest überconfig.KB_DIRwäre also in jeder ausgelieferten Instanz ein Korpus-Health-Check und würde dort rot, sobald jemand von Hand verschachtelt. Instanzgesundheit istlint/doctors Aufgabe, nicht die der Stack-Suite. Die geforderte Eigenschaft — dauerhaft geprüft, nicht einmalig aufgeräumt — ist damit erfüllt, an der richtigen Stelle.Was das für die Nachbarn heißt
layout:intypes/concept.mdjedir:einen einzelnen Pfadabschnitt tragen muss —get_layoutweist alles andere jetzt ab.raw/kennt diese Tiefengrenze nicht, dort ist die Tiefe ausdrücklich nicht das Problem.moves Verzeichnisaufräumen eine kleine Nachlieferung bekommen, die in seinem eigenen Paket noch nicht drin war.Body auf den Endstand umgeschrieben. Gegenüber dem Ausgangstext geändert:
group_pages/index rebuild,nested_pageshart statt advisory,layout:-dir-Validierung auf einen Pfadabschnitt,moveräumt geleerte Verzeichnisse ab.config.KB_DIR— Grund im Body (die Suite wird mitdist exportausgeliefert).kb/index.mdund die Shards sich beim Move nicht geändert haben.migrate verify, Mass-Update-Gate mit Freigabe).torben referenced this issue2026-09-08 17:32:39 +00:00