Umgesetzt und publiziert am 2026-09-04, Commit 9e41431, Stack-Kandidat 4.8.0-beta.1. Aufgeworfen in der Sitzung zur Ordnerorganisation; Nachbarn: #57 (Katalogtiefe), #59 (layout: für concept, hing hieran), #58 (incoming/), #16 (raw rename).
Was das Problem war
Nichts im Stack bewegte eine Seite zwischen Verzeichnissen. rename schrieb nie über eine Verzeichnisgrenze (target.path.parent / f"{new}.md"), und new_page._target_dir rechnete die Platzierung ausschließlich beim Anlegen. Änderte sich entity_type später, blieb die Datei still am alten Ort — und nichts meldete das: weder lint_core.py noch doctor.py verglichen den Ist-Ort einer Seite mit dem, was ihr Type-Spec berechnen würde. Der Zustand war unbehebbar und unsichtbar.
Gegen den realen Bestand geprüft: 3 Seiten lagen tatsächlich falsch — die drei aus #57 (kb/entities/projects/{kfchou,vanillaflava,yugasun}/*.md).
Was gebaut wurde
Eine Platzierungslogik, ein Ort.TypeResolver.compute_target_dir (plus TypeResolver.subtype_dir für den layout:-Teil) in tools/chemenu/type_resolver.py; new_page._target_dir und _page_subdir delegieren nur noch dorthin. lint und move rufen dieselbe Methode.
wikitool move — top-level registriert, wie rename/rm:
move --page "<Titel>" bewegt eine Seite an den berechneten Ort. Kein --to <dir>; das Ziel ist abgeleitet, nicht wählbar.
move --reconcile wendet dieselbe Regel auf den ganzen Bestand an, über lint_core.find_misplaced — dieselbe Funktion, die der Lint-Befund nutzt.
Beide Modi fassen weder Body noch Frontmatter an; der Titel ändert sich nie, also folgt kein Referenz-Update.
Idempotent: ein zweiter --reconcile-Lauf meldet „Nothing to move".
Ein belegtes Ziel (Rest einer Stem-Kollision) wird verweigert statt still überschrieben.
lint-Befund misplaced_pages mit Ist- und Soll-Pfad, plus dem Kommando zum Beheben. Advisory, nicht in HARD_ERROR_KEYS.
migrate verify auf Titel-Schlüsselung. Siehe „Der Fund unterwegs" unten.
--op move in log_append.VALID_OPS, mit den zugehörigen Zeilen in tools/CONTRACT.md, instructions/page-lifecycle.md (neuer Abschnitt „Move") und instructions/publish-cycle.md.
Entscheidungen
Kommandoform: top-level move, kein page-Unterbefehlsbaum.rename und rm — die Geschwister derselben Seitenlebenszyklus-Familie — sind top-level; ein Sub-App für genau ein Kommando daneben wäre eine zweite Konvention für dieselbe Sache. Der ursprüngliche Titel dieses Issues sagt noch page move; gebaut ist wikitool move.
Der Lint-Befund ist advisory. Als Hard Error wäre lint auf jeder Instanz mit handplatzierten Seiten sofort rot gewesen — auf dieser hier ab Tag eins, wegen der drei #57-Seiten. Ein handplatzierter Bestand ist keine kaputte Seite, und es gibt keine Version, ab der „nicht im berechneten Verzeichnis" falsch wird. Begründung steht im Kommentarblock über HARD_ERROR_KEYS, neben orphan_pages/redundant_see_also.
#56 zuerst, ohne den Bestand anzufassen.kb/ ist in diesem Commit unverändert. Die drei realen Seiten bleiben liegen; #57 zieht sie hoch — jetzt mit move --reconcile statt Handarbeit, zusammen mit seinem eigenen kb/CONTRACT.md-Satz und der group_pages-Ablehnung, die #56 nicht mitliefert.
Kein git mv-mit-mv-Fallback. Der ursprüngliche Vorschlag übernahm das aus #16, wo es hingehört: eine frisch abgelegte Rohdatei kann ungetrackt sein. Für kb-Seiten trägt das Argument nicht — rename und rm bewegen bzw. löschen längst über Path.rename/unlink, und publish staged ohnehin über git add -A, womit der Endzustand identisch ist. move folgt dem Präzedenzfall. Damit entfällt auch das gemeinsame Kleinstprimitiv, das dieses Issue und #16 laut ursprünglichem Text geteilt hätten: #16 schreibt es allein, wenn es drankommt.
Der Fund unterwegs
migrate verify schlüsselte Seiten über den repo-relativen Pfad (_shapes_at_revision/_shapes_now). Ein reiner Move ergab „N removed, N added, 0 compared, 0 findings" und lief grün durch — der eine mechanische Check, für den instructions/migrate-corpus.md Schritt 4 existiert, hätte bei genau der Operation nichts geprüft, die dieses Issue einführt. Ohne #59s Vorbehalt wäre das erst bei 80 verschobenen Concept-Seiten aufgefallen, und dann als grüner Lauf.
Behoben: PageShape trägt zusätzlich den Pfad, beide Shape-Dicts schlüsseln über den Titel (die einzige Identität einer Seite, AGENTS.md Invariante 2), und CorpusDiff.moved meldet einen reinen Ortswechsel separat — informativ, nie als Finding. compare() bleibt agnostisch gegenüber dem Schlüssel; die Titel-Schlüsselung sitzt bei den Aufrufern in migrate_cmd.py.
Ein eigener Bug dabei — die „nachher"-Seite lieferte absolute statt repo-relative Pfade in moved — wurde vom neuen Regressionstest gefangen, nicht von Augenmaß.
Was verifiziert wurde
pytest: 998/998 grün lokal (vorher 975; 23 neue Tests über test_page_ops.py, test_lint.py, test_type_resolver.py, test_corpus_diff.py, test_migrate_cmd.py).
tools/wikitool docs verify: grün (51 Kommandos dokumentiert, beide Richtungen).
CI-Lauf 176 — success, inklusive setup-instance-Replay gegen einen frischen dist export. Der parallele release-Lauf 177 lief folgenlos durch und legte korrekt keine Release an — 4.8.0-beta.1 ist ein Kandidat, neueste Release bleibt v4.7.4.
Gegen den echten Bestand trocken geprüft: move --page "wiki-skills" --dry-run und move --reconcile --dry-run melden genau die drei #57-Seiten, ohne zu schreiben. lint --json listet dieselben drei unter misplaced_pages.
Akzeptanzkriterien
move --page "<Titel>" bewegt die Datei an den Ort, den new für dieselbe Frontmatter berechnet hätte; die Platzierungslogik existiert genau einmal, geteilt mit new_page._target_dir über TypeResolver.compute_target_dir.
--dry-run zeigt Quell- und Zielpfad, ohne zu schreiben.
Nach move --reconcile: identische Menge der Seitentitel, jede Seite unter ihrem berechneten Verzeichnis, Body und Frontmatter unverändert — alle drei Eigenschaften geprüft, nicht nur die dritte.
--reconcile ist idempotent: ein zweiter Lauf bewegt nichts und meldet das.
lint meldet eine falsch platzierte Seite mit Ist- und Soll-Pfad. Zwei Tests: einer feuert, einer schweigt auf korrekt platzierten Seiten. Dazu ein dritter, der festhält, dass der Befund lint nicht rot macht.
migrate verify --from HEAD meldet nach einem reinen Move keine Seite als removed/added, sondern vergleicht sie als dieselbe. Regressionstest mit 3 verschobenen Seiten: compared == 3, added == 0, removed == 0.
Ein Test deckt die nicht versionierte Datei ab (git mv scheitert, mv greift).Gestrichen — kein git mv/mv-Fallback gebaut, siehe Entscheidung 4. Es gibt keinen Codepfad zu testen.
Zeilen in tools/CONTRACT.md für Kommando und Fehlerkontrakt; docs verify erzwingt die Kommandozeile in beide Richtungen. (Anmerkung: die Fehlerkontrakt-Tabelle prüft docs verifynicht mechanisch — das Kriterium überschätzte das Werkzeug. Die Zeile steht, geprüft ist sie von Hand.)
instructions/page-lifecycle.md beschreibt den Verschiebefall (neuer Abschnitt „Move", plus --op move im Abschluss).
Changelog-Eintrag, MINOR. Drop-in-Test: vorwärts reines Überkopieren, rückwärts liest ein älterer Stack eine verschobene Seite unverändert — die Identität ist der Titel, nicht der Ort.
Was das für die Nachbarn heißt
#59 ist nicht mehr auf #56 blockiert: die Stem-Schlüsselung, an der sein --minor-Argument hing, steht. Das status/blocked dort kann weg.
#57 hat jetzt sein Werkzeug. Sein eigener Teil — Tiefe 1 als Grenze in kb/CONTRACT.md, group_pages meldet statt still zu falten — ist unberührt.
#16 schreibt das git mv-Primitiv allein, falls es eines braucht; dieses Issue liefert keines.
Offen für später: der Kandidat 4.8.0-beta.1 wird mit version release geschlossen, wenn er reif ist — nicht Teil dieses Pakets.
Umgesetzt und publiziert am 2026-09-04, Commit `9e41431`, Stack-Kandidat `4.8.0-beta.1`. Aufgeworfen in der Sitzung zur Ordnerorganisation; Nachbarn: #57 (Katalogtiefe), #59 (`layout:` für `concept`, hing hieran), #58 (`incoming/`), #16 (`raw rename`).
## Was das Problem war
Nichts im Stack bewegte eine Seite zwischen Verzeichnissen. `rename` schrieb nie über eine Verzeichnisgrenze (`target.path.parent / f"{new}.md"`), und `new_page._target_dir` rechnete die Platzierung ausschließlich beim Anlegen. Änderte sich `entity_type` später, blieb die Datei still am alten Ort — und nichts meldete das: weder `lint_core.py` noch `doctor.py` verglichen den Ist-Ort einer Seite mit dem, was ihr Type-Spec berechnen würde. Der Zustand war unbehebbar *und* unsichtbar.
Gegen den realen Bestand geprüft: **3 Seiten lagen tatsächlich falsch** — die drei aus #57 (`kb/entities/projects/{kfchou,vanillaflava,yugasun}/*.md`).
## Was gebaut wurde
**Eine Platzierungslogik, ein Ort.** `TypeResolver.compute_target_dir` (plus `TypeResolver.subtype_dir` für den `layout:`-Teil) in `tools/chemenu/type_resolver.py`; `new_page._target_dir` und `_page_subdir` delegieren nur noch dorthin. `lint` und `move` rufen dieselbe Methode.
**`wikitool move`** — top-level registriert, wie `rename`/`rm`:
- `move --page "<Titel>"` bewegt eine Seite an den berechneten Ort. Kein `--to <dir>`; das Ziel ist abgeleitet, nicht wählbar.
- `move --reconcile` wendet dieselbe Regel auf den ganzen Bestand an, über `lint_core.find_misplaced` — dieselbe Funktion, die der Lint-Befund nutzt.
- Beide Modi fassen weder Body noch Frontmatter an; der Titel ändert sich nie, also folgt kein Referenz-Update.
- Idempotent: ein zweiter `--reconcile`-Lauf meldet „Nothing to move".
- Ein belegtes Ziel (Rest einer Stem-Kollision) wird verweigert statt still überschrieben.
**`lint`-Befund `misplaced_pages`** mit Ist- und Soll-Pfad, plus dem Kommando zum Beheben. Advisory, **nicht** in `HARD_ERROR_KEYS`.
**`migrate verify` auf Titel-Schlüsselung.** Siehe „Der Fund unterwegs" unten.
**`--op move`** in `log_append.VALID_OPS`, mit den zugehörigen Zeilen in `tools/CONTRACT.md`, `instructions/page-lifecycle.md` (neuer Abschnitt „Move") und `instructions/publish-cycle.md`.
## Entscheidungen
1. **Kommandoform: top-level `move`, kein `page`-Unterbefehlsbaum.** `rename` und `rm` — die Geschwister derselben Seitenlebenszyklus-Familie — sind top-level; ein Sub-App für genau ein Kommando daneben wäre eine zweite Konvention für dieselbe Sache. Der ursprüngliche Titel dieses Issues sagt noch `page move`; gebaut ist `wikitool move`.
2. **Der Lint-Befund ist advisory.** Als Hard Error wäre `lint` auf jeder Instanz mit handplatzierten Seiten sofort rot gewesen — auf dieser hier ab Tag eins, wegen der drei #57-Seiten. Ein handplatzierter Bestand ist keine kaputte Seite, und es gibt keine Version, ab der „nicht im berechneten Verzeichnis" falsch wird. Begründung steht im Kommentarblock über `HARD_ERROR_KEYS`, neben `orphan_pages`/`redundant_see_also`.
3. **#56 zuerst, ohne den Bestand anzufassen.** `kb/` ist in diesem Commit unverändert. Die drei realen Seiten bleiben liegen; #57 zieht sie hoch — jetzt mit `move --reconcile` statt Handarbeit, zusammen mit seinem eigenen `kb/CONTRACT.md`-Satz und der `group_pages`-Ablehnung, die #56 nicht mitliefert.
4. **Kein `git mv`-mit-`mv`-Fallback.** Der ursprüngliche Vorschlag übernahm das aus #16, wo es hingehört: eine frisch abgelegte **Rohdatei** kann ungetrackt sein. Für kb-Seiten trägt das Argument nicht — `rename` und `rm` bewegen bzw. löschen längst über `Path.rename`/`unlink`, und `publish` staged ohnehin über `git add -A`, womit der Endzustand identisch ist. `move` folgt dem Präzedenzfall. Damit entfällt auch das gemeinsame Kleinstprimitiv, das dieses Issue und #16 laut ursprünglichem Text geteilt hätten: **#16 schreibt es allein, wenn es drankommt.**
## Der Fund unterwegs
`migrate verify` schlüsselte Seiten über den repo-relativen **Pfad** (`_shapes_at_revision`/`_shapes_now`). Ein reiner Move ergab „N removed, N added, 0 compared, 0 findings" und lief grün durch — der eine mechanische Check, für den `instructions/migrate-corpus.md` Schritt 4 existiert, hätte bei genau der Operation nichts geprüft, die dieses Issue einführt. Ohne #59s Vorbehalt wäre das erst bei 80 verschobenen Concept-Seiten aufgefallen, und dann als grüner Lauf.
Behoben: `PageShape` trägt zusätzlich den Pfad, beide Shape-Dicts schlüsseln über den Titel (die einzige Identität einer Seite, AGENTS.md Invariante 2), und `CorpusDiff.moved` meldet einen reinen Ortswechsel separat — informativ, nie als Finding. `compare()` bleibt agnostisch gegenüber dem Schlüssel; die Titel-Schlüsselung sitzt bei den Aufrufern in `migrate_cmd.py`.
Ein eigener Bug dabei — die „nachher"-Seite lieferte absolute statt repo-relative Pfade in `moved` — wurde vom neuen Regressionstest gefangen, nicht von Augenmaß.
## Was verifiziert wurde
- `pytest`: **998/998 grün** lokal (vorher 975; 23 neue Tests über `test_page_ops.py`, `test_lint.py`, `test_type_resolver.py`, `test_corpus_diff.py`, `test_migrate_cmd.py`).
- `tools/wikitool docs verify`: grün (51 Kommandos dokumentiert, beide Richtungen).
- `tools/wikitool instructions verify`: grün (20 Instructions, 7 Skills, 14 publizierte Kopien deckungsgleich).
- **CI-Lauf [176](https://gitea.nehmer.net/torben/chemenu/actions/runs/176) — success**, inklusive `setup-instance`-Replay gegen einen frischen `dist export`. Der parallele `release`-Lauf [177](https://gitea.nehmer.net/torben/chemenu/actions/runs/177) lief folgenlos durch und legte korrekt **keine** Release an — `4.8.0-beta.1` ist ein Kandidat, neueste Release bleibt `v4.7.4`.
- Gegen den echten Bestand trocken geprüft: `move --page "wiki-skills" --dry-run` und `move --reconcile --dry-run` melden genau die drei #57-Seiten, ohne zu schreiben. `lint --json` listet dieselben drei unter `misplaced_pages`.
## Akzeptanzkriterien
- [x] `move --page "<Titel>"` bewegt die Datei an den Ort, den `new` für dieselbe Frontmatter berechnet hätte; die Platzierungslogik existiert genau einmal, geteilt mit `new_page._target_dir` über `TypeResolver.compute_target_dir`.
- [x] `--dry-run` zeigt Quell- und Zielpfad, ohne zu schreiben.
- [x] Nach `move --reconcile`: identische Menge der Seitentitel, jede Seite unter ihrem berechneten Verzeichnis, Body und Frontmatter unverändert — alle drei Eigenschaften geprüft, nicht nur die dritte.
- [x] `--reconcile` ist idempotent: ein zweiter Lauf bewegt nichts und meldet das.
- [x] `lint` meldet eine falsch platzierte Seite mit Ist- und Soll-Pfad. Zwei Tests: einer feuert, einer schweigt auf korrekt platzierten Seiten. Dazu ein dritter, der festhält, dass der Befund `lint` nicht rot macht.
- [x] `migrate verify --from HEAD` meldet nach einem reinen Move keine Seite als removed/added, sondern vergleicht sie als dieselbe. Regressionstest mit 3 verschobenen Seiten: `compared == 3, added == 0, removed == 0`.
- [x] ~~Ein Test deckt die nicht versionierte Datei ab (`git mv` scheitert, `mv` greift).~~ **Gestrichen** — kein `git mv`/`mv`-Fallback gebaut, siehe Entscheidung 4. Es gibt keinen Codepfad zu testen.
- [x] Zeilen in `tools/CONTRACT.md` für Kommando **und** Fehlerkontrakt; `docs verify` erzwingt die Kommandozeile in beide Richtungen. *(Anmerkung: die Fehlerkontrakt-Tabelle prüft `docs verify` **nicht** mechanisch — das Kriterium überschätzte das Werkzeug. Die Zeile steht, geprüft ist sie von Hand.)*
- [x] `instructions/page-lifecycle.md` beschreibt den Verschiebefall (neuer Abschnitt „Move", plus `--op move` im Abschluss).
- [x] Changelog-Eintrag, **MINOR**. Drop-in-Test: vorwärts reines Überkopieren, rückwärts liest ein älterer Stack eine verschobene Seite unverändert — die Identität ist der Titel, nicht der Ort.
## Was das für die Nachbarn heißt
- **#59** ist nicht mehr auf #56 blockiert: die Stem-Schlüsselung, an der sein `--minor`-Argument hing, steht. Das `status/blocked` dort kann weg.
- **#57** hat jetzt sein Werkzeug. Sein eigener Teil — Tiefe 1 als Grenze in `kb/CONTRACT.md`, `group_pages` meldet statt still zu falten — ist unberührt.
- **#16** schreibt das `git mv`-Primitiv allein, falls es eines braucht; dieses Issue liefert keines.
Offen für später: der Kandidat `4.8.0-beta.1` wird mit `version release` geschlossen, wenn er reif ist — nicht Teil dieses Pakets.
Changelog: Umgesetzt (move --page/--reconcile, lint-Befund advisory, migrate verify auf Titel-Schlüsselung umgestellt, CHANGES.md 4.8.0-beta.1 geschrieben). Kein git mv-Fallback gebaut (Path.rename wie rename/rm). Publish steht noch aus, pausiert auf Nutzeranweisung — Schließen folgt nach stack-close.
**Changelog:** Umgesetzt (`move --page`/`--reconcile`, `lint`-Befund advisory, `migrate verify` auf Titel-Schlüsselung umgestellt, `CHANGES.md` 4.8.0-beta.1 geschrieben). Kein `git mv`-Fallback gebaut (Path.rename wie rename/rm). Publish steht noch aus, pausiert auf Nutzeranweisung — Schließen folgt nach `stack-close`.
Changelog: Abschluss-Rewrite. Body auf Endzustand: Befund in die Vergangenheit gesetzt, die vier Entscheidungen (top-level move statt page move; Lint-Befund advisory; Bestand unangetastet; kein git mv-Fallback) stehen als getroffen statt als Vorschlag, Verifikation benannt (998 Tests, docs verify, instructions verify, CI-Lauf 176 success, Trockenlauf gegen den echten Bestand). Kriterium 7 (git mv/mv-Test) gestrichen mit Begründung; Kriterium 8 um die Anmerkung ergänzt, dass docs verify nur die Kommando-, nicht die Fehlerkontrakt-Tabelle mechanisch prüft. Neuer Abschnitt zu den Folgen für #59/#57/#16. Schließt das Issue.
**Changelog:** Abschluss-Rewrite. Body auf Endzustand: Befund in die Vergangenheit gesetzt, die vier Entscheidungen (top-level `move` statt `page move`; Lint-Befund advisory; Bestand unangetastet; kein `git mv`-Fallback) stehen als getroffen statt als Vorschlag, Verifikation benannt (998 Tests, `docs verify`, `instructions verify`, CI-Lauf 176 success, Trockenlauf gegen den echten Bestand). Kriterium 7 (`git mv`/`mv`-Test) gestrichen mit Begründung; Kriterium 8 um die Anmerkung ergänzt, dass `docs verify` nur die Kommando-, nicht die Fehlerkontrakt-Tabelle mechanisch prüft. Neuer Abschnitt zu den Folgen für #59/#57/#16. Schließt das Issue.
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
9e41431, Stack-Kandidat4.8.0-beta.1. Aufgeworfen in der Sitzung zur Ordnerorganisation; Nachbarn: #57 (Katalogtiefe), #59 (layout:fürconcept, hing hieran), #58 (incoming/), #16 (raw rename).Was das Problem war
Nichts im Stack bewegte eine Seite zwischen Verzeichnissen.
renameschrieb nie über eine Verzeichnisgrenze (target.path.parent / f"{new}.md"), undnew_page._target_dirrechnete die Platzierung ausschließlich beim Anlegen. Änderte sichentity_typespäter, blieb die Datei still am alten Ort — und nichts meldete das: wederlint_core.pynochdoctor.pyverglichen den Ist-Ort einer Seite mit dem, was ihr Type-Spec berechnen würde. Der Zustand war unbehebbar und unsichtbar.Gegen den realen Bestand geprüft: 3 Seiten lagen tatsächlich falsch — die drei aus #57 (
kb/entities/projects/{kfchou,vanillaflava,yugasun}/*.md).Was gebaut wurde
Eine Platzierungslogik, ein Ort.
TypeResolver.compute_target_dir(plusTypeResolver.subtype_dirfür denlayout:-Teil) intools/chemenu/type_resolver.py;new_page._target_dirund_page_subdirdelegieren nur noch dorthin.lintundmoverufen dieselbe Methode.wikitool move— top-level registriert, wierename/rm:move --page "<Titel>"bewegt eine Seite an den berechneten Ort. Kein--to <dir>; das Ziel ist abgeleitet, nicht wählbar.move --reconcilewendet dieselbe Regel auf den ganzen Bestand an, überlint_core.find_misplaced— dieselbe Funktion, die der Lint-Befund nutzt.--reconcile-Lauf meldet „Nothing to move".lint-Befundmisplaced_pagesmit Ist- und Soll-Pfad, plus dem Kommando zum Beheben. Advisory, nicht inHARD_ERROR_KEYS.migrate verifyauf Titel-Schlüsselung. Siehe „Der Fund unterwegs" unten.--op moveinlog_append.VALID_OPS, mit den zugehörigen Zeilen intools/CONTRACT.md,instructions/page-lifecycle.md(neuer Abschnitt „Move") undinstructions/publish-cycle.md.Entscheidungen
move, keinpage-Unterbefehlsbaum.renameundrm— die Geschwister derselben Seitenlebenszyklus-Familie — sind top-level; ein Sub-App für genau ein Kommando daneben wäre eine zweite Konvention für dieselbe Sache. Der ursprüngliche Titel dieses Issues sagt nochpage move; gebaut istwikitool move.lintauf jeder Instanz mit handplatzierten Seiten sofort rot gewesen — auf dieser hier ab Tag eins, wegen der drei #57-Seiten. Ein handplatzierter Bestand ist keine kaputte Seite, und es gibt keine Version, ab der „nicht im berechneten Verzeichnis" falsch wird. Begründung steht im Kommentarblock überHARD_ERROR_KEYS, nebenorphan_pages/redundant_see_also.kb/ist in diesem Commit unverändert. Die drei realen Seiten bleiben liegen; #57 zieht sie hoch — jetzt mitmove --reconcilestatt Handarbeit, zusammen mit seinem eigenenkb/CONTRACT.md-Satz und dergroup_pages-Ablehnung, die #56 nicht mitliefert.git mv-mit-mv-Fallback. Der ursprüngliche Vorschlag übernahm das aus #16, wo es hingehört: eine frisch abgelegte Rohdatei kann ungetrackt sein. Für kb-Seiten trägt das Argument nicht —renameundrmbewegen bzw. löschen längst überPath.rename/unlink, undpublishstaged ohnehin übergit add -A, womit der Endzustand identisch ist.movefolgt dem Präzedenzfall. Damit entfällt auch das gemeinsame Kleinstprimitiv, das dieses Issue und #16 laut ursprünglichem Text geteilt hätten: #16 schreibt es allein, wenn es drankommt.Der Fund unterwegs
migrate verifyschlüsselte Seiten über den repo-relativen Pfad (_shapes_at_revision/_shapes_now). Ein reiner Move ergab „N removed, N added, 0 compared, 0 findings" und lief grün durch — der eine mechanische Check, für deninstructions/migrate-corpus.mdSchritt 4 existiert, hätte bei genau der Operation nichts geprüft, die dieses Issue einführt. Ohne #59s Vorbehalt wäre das erst bei 80 verschobenen Concept-Seiten aufgefallen, und dann als grüner Lauf.Behoben:
PageShapeträgt zusätzlich den Pfad, beide Shape-Dicts schlüsseln über den Titel (die einzige Identität einer Seite, AGENTS.md Invariante 2), undCorpusDiff.movedmeldet einen reinen Ortswechsel separat — informativ, nie als Finding.compare()bleibt agnostisch gegenüber dem Schlüssel; die Titel-Schlüsselung sitzt bei den Aufrufern inmigrate_cmd.py.Ein eigener Bug dabei — die „nachher"-Seite lieferte absolute statt repo-relative Pfade in
moved— wurde vom neuen Regressionstest gefangen, nicht von Augenmaß.Was verifiziert wurde
pytest: 998/998 grün lokal (vorher 975; 23 neue Tests übertest_page_ops.py,test_lint.py,test_type_resolver.py,test_corpus_diff.py,test_migrate_cmd.py).tools/wikitool docs verify: grün (51 Kommandos dokumentiert, beide Richtungen).tools/wikitool instructions verify: grün (20 Instructions, 7 Skills, 14 publizierte Kopien deckungsgleich).setup-instance-Replay gegen einen frischendist export. Der parallelerelease-Lauf 177 lief folgenlos durch und legte korrekt keine Release an —4.8.0-beta.1ist ein Kandidat, neueste Release bleibtv4.7.4.move --page "wiki-skills" --dry-runundmove --reconcile --dry-runmelden genau die drei #57-Seiten, ohne zu schreiben.lint --jsonlistet dieselben drei untermisplaced_pages.Akzeptanzkriterien
move --page "<Titel>"bewegt die Datei an den Ort, dennewfür dieselbe Frontmatter berechnet hätte; die Platzierungslogik existiert genau einmal, geteilt mitnew_page._target_dirüberTypeResolver.compute_target_dir.--dry-runzeigt Quell- und Zielpfad, ohne zu schreiben.move --reconcile: identische Menge der Seitentitel, jede Seite unter ihrem berechneten Verzeichnis, Body und Frontmatter unverändert — alle drei Eigenschaften geprüft, nicht nur die dritte.--reconcileist idempotent: ein zweiter Lauf bewegt nichts und meldet das.lintmeldet eine falsch platzierte Seite mit Ist- und Soll-Pfad. Zwei Tests: einer feuert, einer schweigt auf korrekt platzierten Seiten. Dazu ein dritter, der festhält, dass der Befundlintnicht rot macht.migrate verify --from HEADmeldet nach einem reinen Move keine Seite als removed/added, sondern vergleicht sie als dieselbe. Regressionstest mit 3 verschobenen Seiten:compared == 3, added == 0, removed == 0.Ein Test deckt die nicht versionierte Datei ab (Gestrichen — keingit mvscheitert,mvgreift).git mv/mv-Fallback gebaut, siehe Entscheidung 4. Es gibt keinen Codepfad zu testen.tools/CONTRACT.mdfür Kommando und Fehlerkontrakt;docs verifyerzwingt die Kommandozeile in beide Richtungen. (Anmerkung: die Fehlerkontrakt-Tabelle prüftdocs verifynicht mechanisch — das Kriterium überschätzte das Werkzeug. Die Zeile steht, geprüft ist sie von Hand.)instructions/page-lifecycle.mdbeschreibt den Verschiebefall (neuer Abschnitt „Move", plus--op moveim Abschluss).Was das für die Nachbarn heißt
--minor-Argument hing, steht. Dasstatus/blockeddort kann weg.kb/CONTRACT.md,group_pagesmeldet statt still zu falten — ist unberührt.git mv-Primitiv allein, falls es eines braucht; dieses Issue liefert keines.Offen für später: der Kandidat
4.8.0-beta.1wird mitversion releasegeschlossen, wenn er reif ist — nicht Teil dieses Pakets.Changelog: Umgesetzt (
move --page/--reconcile,lint-Befund advisory,migrate verifyauf Titel-Schlüsselung umgestellt,CHANGES.md4.8.0-beta.1 geschrieben). Keingit mv-Fallback gebaut (Path.rename wie rename/rm). Publish steht noch aus, pausiert auf Nutzeranweisung — Schließen folgt nachstack-close.Changelog: Abschluss-Rewrite. Body auf Endzustand: Befund in die Vergangenheit gesetzt, die vier Entscheidungen (top-level
movestattpage move; Lint-Befund advisory; Bestand unangetastet; keingit mv-Fallback) stehen als getroffen statt als Vorschlag, Verifikation benannt (998 Tests,docs verify,instructions verify, CI-Lauf 176 success, Trockenlauf gegen den echten Bestand). Kriterium 7 (git mv/mv-Test) gestrichen mit Begründung; Kriterium 8 um die Anmerkung ergänzt, dassdocs verifynur die Kommando-, nicht die Fehlerkontrakt-Tabelle mechanisch prüft. Neuer Abschnitt zu den Folgen für #59/#57/#16. Schließt das Issue.torben referenced this issue2026-09-08 17:32:39 +00:00