Dateinamensregel: Seitentitel müssen unter Windows und macOS gültige, eindeutige Dateinamen ergeben #155

Closed
opened 2026-09-27 08:45:39 +00:00 by torben · 4 comments
Owner

Teilpaket von #140. Entschieden: D4 (hart, 2026-09-27), D18 (Umfang, 2026-09-28), D28–D31 (Präzisierungen, 2026-09-29). Erledigt 2026-09-30: veröffentlicht mit 8be5e6e, Version 8.0.0-beta.1 (öffnet den 8.0.0-Kandidaten, erste Grenzüberschreitung; kein version release, das bleibt beim Betreiber).

Warum

Titel werden eins zu eins zu Dateinamen (new_page.py: f"{page_title}.md"). Daraus folgten zwei Probleme:

  • Unter Linux erlaubt, unter Windows ungültig. Ein Titel mit :, ?, " o. ä. ließ sich unter Linux anlegen und committen, brach danach aber jeden Windows-Klon der Instanz: git checkout scheitert an der Datei.
  • Groß-/Kleinschreibung und Normalisierung. Zwei Titel, die sich nur darin unterscheiden, kollidieren auf NTFS und auf APFS (Standardeinstellung); APFS behandelt zusätzlich NFC und NFD als denselben Namen. Unter Linux fiel beides nicht auf.

Mit nativem Windows-Support (#140 D8) gilt die Regel deshalb auf allen Plattformen, nicht nur unter Windows.

Nebenbefund, mitbehoben: new prüfte Kollisionen nur für root: kb. tools/wikitool new instruction --name gates überschrieb deshalb instructions/gates.md kommentarlos mit einem Gerüst. new überschreibt jetzt nie eine bestehende Datei, für keine Wurzel.

Regel (D18, präzisiert durch D28–D30)

Normativ steht sie einmal, in kb/CONTRACT.md § Titles are identifiers; umgesetzt in tools/chemenu/titles.py. Ein Titel darf nicht:

  • leer sein (D30)
  • eines der Zeichen < > : " / \ | ? * enthalten
  • Steuerzeichen enthalten (gemeldet als U+XXXX)
  • ein reservierter Name sein – maßgeblich ist der Teil vor dem ersten Punkt, ohne Rücksicht auf Groß-/Kleinschreibung und ohne abschließende Leerzeichen:
    • Windows: CON, PRN, AUX, NUL, COM0–COM9, COM¹–COM³, LPT0–LPT9, LPT¹–LPT³ (D30)
    • Stack (D28): INDEX, COLLECTION
  • auf einen Punkt oder ein Leerzeichen enden
  • mit einer bestehenden Seite kollidieren, nachdem beide nach NFC normalisiert und per casefold verglichen wurden (D29)

Geprüft wird immer der volle Seitentitel, also nach title_prefix (Source - …).

Entscheidungen

  • D28 Stack-eigene Namen (2026-09-29), mit Bestandsaufnahme. Geprüft wurde, welche Dateien der Stack dort ablegt, wo new schreibt:

    Ort Stack-Dateien Behandlung
    jedes Sammlungs- und Bereichsverzeichnis unter kb/ INDEX.md (generiert) global reserviert
    jedes Sammlungsverzeichnis COLLECTION.md, in ausgelieferten Instanzen COLLECTION.md.template global reserviert (COLLECTION deckt über „Teil vor dem ersten Punkt“ beide ab)
    kb/ selbst index.md, log.md, provenance.md, CONTRACT.md, CONVENTIONS.md(.template) nicht global reserviert: Kein instanziierbarer Typ legt Seiten direkt in kb/ ab, und „Log“ oder „Contract“ sind legitime Seitentitel. Die Verzeichnisprüfung fängt den Fall trotzdem ab.
    instructions/ (root: repo, Typ instruction) CONTRACT.md, bestehende Instruktionen, Skill-Verzeichnisse Verzeichnisprüfung
    README.md in einer Sammlung ist eine normale Seite normale Titelkollision

    Aus Titeln entstehen keine Verzeichnisse: Die Unterverzeichnisse kommen aus dem layout: des Typs, nicht aus Nutzertext.

  • D29 Normalisierung (2026-09-29): Kollisionsschlüssel casefold(NFC(titel)).

  • D30 Reservierte Namen vollständig (2026-09-29): COM0/LPT0 und COM¹²³/LPT¹²³ aufgenommen, dazu die Weigerung bei leerem Titel. Unsicher bleibt, ob jede Windows-Version alle davon sperrt; die strengere Regel kostet nichts. So steht es auch im CHANGES-Eintrag.

  • D31 #114 (2026-09-29): in diesem Paket aufgegangen und geschlossen; sein Schrägstrich-Kriterium steht unten.

Umsetzung (wie gebaut)

  1. tools/chemenu/titles.py (neu) – reine Funktionen ohne fail(): title_problems(title) liefert jeden Grund konkret (das Zeichen, Steuerzeichen als U+0009, den reservierten Namen); collision_key(name) = casefold(NFC(name)).
  2. commands/_util.py: check_title, check_collision(name, *, ignore=None) über collision_key im ganzen Titel-Namensraum von kb/, target_conflict/check_target_free(path, *, ignore=None) für das Zielverzeichnis jeder Wurzel. Nutzertext in Fehlermeldungen wird für Rich escaped.
  3. new: check_title für jeden Typ und jede Wurzel, check_collision für root: kb, check_target_free für alle – alles vor jedem Schreibzugriff, auch vor dem Tracker-Projekt bei new project.
  4. rename: im Umbenennungsmodus check_title(--to), check_collision und check_target_free mit der Seite selbst ausgenommen (Foo → FOO geht) – auch bei --dry-run. --from wird nie geprüft, damit rename die Abhilfe bleibt (D19).
  5. move: check_target_free statt exaktem exists(), im Einzel- und im --reconcile-Pfad; --reconcile überspringt ein solches Ziel und nennt die Gegenseite.
  6. lint: neuer Befund unportable_titles („Unportable Titles“) mit {title, path, problem}, Kollisionen nennen die Gegenseite, Bericht nennt die rename-Abhilfe. In HARD_ERROR_KEYS, bewusst nicht in MIGRATION_GATED_KEYS (keine Migration nach D19, kb_version rückt nicht vor). duplicate_titles (exakt gleicher Stamm) unverändert; exakte Duplikate meldet nur dieser.
  7. Records: new, rename, move und lint tragen die neuen Ablehnungen, Notes und Verweise; tools/CONTRACT.md per docs contract --apply neu erzeugt.
  8. Doku: kb/CONTRACT.md § Titles are identifiers (einzige normative Stelle), verlinkt aus kb/CONVENTIONS.md und dem Template; instructions/page-lifecycle.md (Rename- und Move-Absatz, ToC neu erzeugt), instructions/wiki-lint/SKILL.md, README.md (Naming Tips). tools/README.md macht keine Aussage über Titel – nichts zu ändern.
  9. Tests: test_titles.py (neu, 57 Fälle), dazu Ergänzungen in test_new_page.py, test_page_ops.py, test_lint.py.
  10. Version: version bump --major 7.1.0-beta.32 → 8.0.0-beta.1, --impact high, mit --breaking und --no-migration wie entworfen. Abweichung vom Entwurf: --title auf Englisch („Page titles must form valid, unique file names on Windows and macOS“), weil CHANGES.md englisch ist. Der Eintrag nennt auch die Korrektur des Überschreibens bei root: repo.

Akzeptanzkriterien

  • new <typ> --name "A: B" endet mit Exit 1 und nennt das Zeichen; es entsteht keine Datei. (test_new_refuses_a_forbidden_character_and_writes_nothing; im echten Checkout nachgestellt)
  • new --name "A/B" endet mit Exit 1; es entsteht weder A/ noch eine Datei (aus #114). (test_new_refuses_a_slash_and_creates_no_directory)
  • new --name "foo" endet unter Linux mit Exit 1, wenn eine Seite „Foo“ existiert; ebenso ein NFD-Titel gegen eine NFC-Seite. (test_new_refuses_a_title_that_differs_only_by_case, …_an_nfd_title_against_an_nfc_page)
  • new … --name "Index", "collection" und "COM¹" enden mit Exit 1. (test_new_refuses_reserved_and_malformed_names)
  • new project --name "A: B" legt auch kein Tracker-Projekt an. (test_new_project_with_a_bad_title_never_reaches_the_tracker)
  • new instruction --name gates endet mit Exit 1, und instructions/gates.md ist danach byte-gleich; ebenso --name contract neben instructions/CONTRACT.md. (test_new_instruction_never_overwrites_an_existing_file; gates im echten Checkout nachgestellt)
  • rename auf einen verbotenen oder kollidierenden Titel wird genauso verweigert, auch mit --dry-run; rename --from Foo --to FOO geht. (test_rename_refuses_an_unportable_or_colliding_target × dry-run, test_rename_may_change_only_the_case_of_the_page_itself, test_rename_dry_run_accepts_a_case_only_change)
  • rename von einem verbotenen Titel auf einen gültigen geht. (test_rename_is_the_remedy_for_a_page_that_breaks_the_rule)
  • lint meldet eine eingeschleuste Seite kb/entities/CON.md als ERROR; lint --fail-on-error endet mit Exit 1. (test_lint_reports_a_reserved_device_name_as_a_hard_error, test_unportable_titles_is_hard_whatever_the_corpus_version – hart heißt: --fail-on-error endet mit 1)
  • lint meldet zwei Seiten „Foo“/„foo“ als ERROR mit beiden Pfaden. (test_lint_reports_case_variants_with_both_paths)
  • Der Demo-Korpus bleibt lint-sauber (182 Seiten, unportable_titles leer).
  • kb/CONTRACT.md nennt die Regel; tools/CONTRACT.md ist neu erzeugt; docs verify, instructions verify und pytest (1680 passed) sind grün.
  • Der Major-Bump trägt --breaking und --no-migration nach D19.

Verifiziert

Lokal vor dem Publish: tools/wikitool docs verify OK („CHANGES.md documents version 8.0.0-beta.1“), tools/wikitool instructions verify OK, pytest 1680 passed. CI auf 8be5e6e: Lauf 431 (verify: Tests, Version gate, Dist-Export und Fresh-Instance-Replay) grün, Lauf 432 (Release-Workflow) grün.

Version

major, 8.0.0-beta.1 – öffnet den 8.0.0-Kandidaten (#153 folgt im selben Kandidaten, D19): Korpora mit solchen Titeln lassen sich nicht mehr ohne Handarbeit lint-sauber halten.

Teilpaket von #140. Entschieden: D4 (hart, 2026-09-27), D18 (Umfang, 2026-09-28), D28–D31 (Präzisierungen, 2026-09-29). **Erledigt 2026-09-30:** veröffentlicht mit `8be5e6e`, Version **8.0.0-beta.1** (öffnet den 8.0.0-Kandidaten, erste Grenzüberschreitung; kein `version release`, das bleibt beim Betreiber). ## Warum Titel werden eins zu eins zu Dateinamen (`new_page.py`: `f"{page_title}.md"`). Daraus folgten zwei Probleme: - **Unter Linux erlaubt, unter Windows ungültig.** Ein Titel mit `:`, `?`, `"` o. ä. ließ sich unter Linux anlegen und committen, brach danach aber jeden Windows-Klon der Instanz: `git checkout` scheitert an der Datei. - **Groß-/Kleinschreibung und Normalisierung.** Zwei Titel, die sich nur darin unterscheiden, kollidieren auf NTFS und auf APFS (Standardeinstellung); APFS behandelt zusätzlich NFC und NFD als denselben Namen. Unter Linux fiel beides nicht auf. Mit nativem Windows-Support (#140 D8) gilt die Regel deshalb auf **allen** Plattformen, nicht nur unter Windows. **Nebenbefund, mitbehoben:** `new` prüfte Kollisionen nur für `root: kb`. `tools/wikitool new instruction --name gates` überschrieb deshalb `instructions/gates.md` kommentarlos mit einem Gerüst. `new` überschreibt jetzt nie eine bestehende Datei, für keine Wurzel. ## Regel (D18, präzisiert durch D28–D30) Normativ steht sie einmal, in `kb/CONTRACT.md` § Titles are identifiers; umgesetzt in `tools/chemenu/titles.py`. Ein Titel darf nicht: - leer sein (D30) - eines der Zeichen `< > : " / \ | ? *` enthalten - Steuerzeichen enthalten (gemeldet als `U+XXXX`) - ein reservierter Name sein – maßgeblich ist der Teil vor dem ersten Punkt, ohne Rücksicht auf Groß-/Kleinschreibung und ohne abschließende Leerzeichen: - Windows: `CON`, `PRN`, `AUX`, `NUL`, `COM0`–`COM9`, `COM¹`–`COM³`, `LPT0`–`LPT9`, `LPT¹`–`LPT³` (D30) - Stack (D28): `INDEX`, `COLLECTION` - auf einen Punkt oder ein Leerzeichen enden - mit einer bestehenden Seite kollidieren, nachdem beide nach NFC normalisiert und per `casefold` verglichen wurden (D29) Geprüft wird immer der volle Seitentitel, also nach `title_prefix` (`Source - …`). ## Entscheidungen - **D28 Stack-eigene Namen (2026-09-29), mit Bestandsaufnahme.** Geprüft wurde, welche Dateien der Stack dort ablegt, wo `new` schreibt: | Ort | Stack-Dateien | Behandlung | |---|---|---| | jedes Sammlungs- und Bereichsverzeichnis unter `kb/` | `INDEX.md` (generiert) | global reserviert | | jedes Sammlungsverzeichnis | `COLLECTION.md`, in ausgelieferten Instanzen `COLLECTION.md.template` | global reserviert (`COLLECTION` deckt über „Teil vor dem ersten Punkt“ beide ab) | | `kb/` selbst | `index.md`, `log.md`, `provenance.md`, `CONTRACT.md`, `CONVENTIONS.md(.template)` | **nicht** global reserviert: Kein instanziierbarer Typ legt Seiten direkt in `kb/` ab, und „Log“ oder „Contract“ sind legitime Seitentitel. Die Verzeichnisprüfung fängt den Fall trotzdem ab. | | `instructions/` (`root: repo`, Typ `instruction`) | `CONTRACT.md`, bestehende Instruktionen, Skill-Verzeichnisse | Verzeichnisprüfung | | `README.md` in einer Sammlung | ist eine normale Seite | normale Titelkollision | Aus Titeln entstehen keine Verzeichnisse: Die Unterverzeichnisse kommen aus dem `layout:` des Typs, nicht aus Nutzertext. - **D29 Normalisierung (2026-09-29):** Kollisionsschlüssel `casefold(NFC(titel))`. - **D30 Reservierte Namen vollständig (2026-09-29):** `COM0`/`LPT0` und `COM¹²³`/`LPT¹²³` aufgenommen, dazu die Weigerung bei leerem Titel. Unsicher bleibt, ob jede Windows-Version alle davon sperrt; die strengere Regel kostet nichts. So steht es auch im CHANGES-Eintrag. - **D31 #114 (2026-09-29):** in diesem Paket aufgegangen und geschlossen; sein Schrägstrich-Kriterium steht unten. ## Umsetzung (wie gebaut) 1. **`tools/chemenu/titles.py`** (neu) – reine Funktionen ohne `fail()`: `title_problems(title)` liefert jeden Grund konkret (das Zeichen, Steuerzeichen als `U+0009`, den reservierten Namen); `collision_key(name)` = `casefold(NFC(name))`. 2. **`commands/_util.py`:** `check_title`, `check_collision(name, *, ignore=None)` über `collision_key` im ganzen Titel-Namensraum von `kb/`, `target_conflict`/`check_target_free(path, *, ignore=None)` für das Zielverzeichnis jeder Wurzel. Nutzertext in Fehlermeldungen wird für Rich escaped. 3. **`new`:** `check_title` für jeden Typ und jede Wurzel, `check_collision` für `root: kb`, `check_target_free` für alle – alles vor jedem Schreibzugriff, auch vor dem Tracker-Projekt bei `new project`. 4. **`rename`:** im Umbenennungsmodus `check_title(--to)`, `check_collision` und `check_target_free` mit der Seite selbst ausgenommen (`Foo` → `FOO` geht) – auch bei `--dry-run`. `--from` wird nie geprüft, damit `rename` die Abhilfe bleibt (D19). 5. **`move`:** `check_target_free` statt exaktem `exists()`, im Einzel- und im `--reconcile`-Pfad; `--reconcile` überspringt ein solches Ziel und nennt die Gegenseite. 6. **`lint`:** neuer Befund `unportable_titles` („Unportable Titles“) mit `{title, path, problem}`, Kollisionen nennen die Gegenseite, Bericht nennt die `rename`-Abhilfe. In `HARD_ERROR_KEYS`, bewusst **nicht** in `MIGRATION_GATED_KEYS` (keine Migration nach D19, `kb_version` rückt nicht vor). `duplicate_titles` (exakt gleicher Stamm) unverändert; exakte Duplikate meldet nur dieser. 7. **Records:** `new`, `rename`, `move` und `lint` tragen die neuen Ablehnungen, Notes und Verweise; `tools/CONTRACT.md` per `docs contract --apply` neu erzeugt. 8. **Doku:** `kb/CONTRACT.md` § Titles are identifiers (einzige normative Stelle), verlinkt aus `kb/CONVENTIONS.md` und dem Template; `instructions/page-lifecycle.md` (Rename- und Move-Absatz, ToC neu erzeugt), `instructions/wiki-lint/SKILL.md`, `README.md` (Naming Tips). `tools/README.md` macht keine Aussage über Titel – nichts zu ändern. 9. **Tests:** `test_titles.py` (neu, 57 Fälle), dazu Ergänzungen in `test_new_page.py`, `test_page_ops.py`, `test_lint.py`. 10. **Version:** `version bump --major` 7.1.0-beta.32 → 8.0.0-beta.1, `--impact high`, mit `--breaking` und `--no-migration` wie entworfen. Abweichung vom Entwurf: `--title` auf Englisch („Page titles must form valid, unique file names on Windows and macOS“), weil `CHANGES.md` englisch ist. Der Eintrag nennt auch die Korrektur des Überschreibens bei `root: repo`. ## Akzeptanzkriterien - [x] `new <typ> --name "A: B"` endet mit Exit 1 und nennt das Zeichen; es entsteht keine Datei. (`test_new_refuses_a_forbidden_character_and_writes_nothing`; im echten Checkout nachgestellt) - [x] `new --name "A/B"` endet mit Exit 1; es entsteht weder `A/` noch eine Datei (aus #114). (`test_new_refuses_a_slash_and_creates_no_directory`) - [x] `new --name "foo"` endet unter Linux mit Exit 1, wenn eine Seite „Foo“ existiert; ebenso ein NFD-Titel gegen eine NFC-Seite. (`test_new_refuses_a_title_that_differs_only_by_case`, `…_an_nfd_title_against_an_nfc_page`) - [x] `new … --name "Index"`, `"collection"` und `"COM¹"` enden mit Exit 1. (`test_new_refuses_reserved_and_malformed_names`) - [x] `new project --name "A: B"` legt auch kein Tracker-Projekt an. (`test_new_project_with_a_bad_title_never_reaches_the_tracker`) - [x] `new instruction --name gates` endet mit Exit 1, und `instructions/gates.md` ist danach byte-gleich; ebenso `--name contract` neben `instructions/CONTRACT.md`. (`test_new_instruction_never_overwrites_an_existing_file`; `gates` im echten Checkout nachgestellt) - [x] `rename` auf einen verbotenen oder kollidierenden Titel wird genauso verweigert, auch mit `--dry-run`; `rename --from Foo --to FOO` geht. (`test_rename_refuses_an_unportable_or_colliding_target` × dry-run, `test_rename_may_change_only_the_case_of_the_page_itself`, `test_rename_dry_run_accepts_a_case_only_change`) - [x] `rename` **von** einem verbotenen Titel auf einen gültigen geht. (`test_rename_is_the_remedy_for_a_page_that_breaks_the_rule`) - [x] `lint` meldet eine eingeschleuste Seite `kb/entities/CON.md` als ERROR; `lint --fail-on-error` endet mit Exit 1. (`test_lint_reports_a_reserved_device_name_as_a_hard_error`, `test_unportable_titles_is_hard_whatever_the_corpus_version` – hart heißt: `--fail-on-error` endet mit 1) - [x] `lint` meldet zwei Seiten „Foo“/„foo“ als ERROR mit beiden Pfaden. (`test_lint_reports_case_variants_with_both_paths`) - [x] Der Demo-Korpus bleibt lint-sauber (182 Seiten, `unportable_titles` leer). - [x] `kb/CONTRACT.md` nennt die Regel; `tools/CONTRACT.md` ist neu erzeugt; `docs verify`, `instructions verify` und `pytest` (1680 passed) sind grün. - [x] Der Major-Bump trägt `--breaking` und `--no-migration` nach D19. ## Verifiziert Lokal vor dem Publish: `tools/wikitool docs verify` OK („CHANGES.md documents version 8.0.0-beta.1“), `tools/wikitool instructions verify` OK, `pytest` 1680 passed. CI auf `8be5e6e`: [Lauf 431](https://gitea.nehmer.net/torben/chemenu/actions/runs/431) (verify: Tests, Version gate, Dist-Export und Fresh-Instance-Replay) grün, [Lauf 432](https://gitea.nehmer.net/torben/chemenu/actions/runs/432) (Release-Workflow) grün. ## Version major, 8.0.0-beta.1 – öffnet den 8.0.0-Kandidaten (#153 folgt im selben Kandidaten, D19): Korpora mit solchen Titeln lassen sich nicht mehr ohne Handarbeit lint-sauber halten.
torben added the prio/plannedsize/Marea/kbkind/decision labels 2026-09-27 08:45:39 +00:00
torben added kind/build and removed kind/decision labels 2026-09-28 16:54:55 +00:00
Author
Owner

Changelog: D18 ist wie empfohlen entschieden (Betreiber, 2026-09-28). Darum kind/decision → kind/build.

**Changelog:** D18 ist wie empfohlen entschieden (Betreiber, 2026-09-28). Darum `kind/decision` → `kind/build`.
torben added kind/decision and removed kind/build labels 2026-09-29 20:25:43 +00:00
Author
Owner

Changelog: Gegen den Baum geprüft (94deccb). Neu: Abschnitt „Umsetzung (Plan)“ (Modul titles.py, new/rename/lint, Records, Doku, Tests, Bump-Entwurf) und vier offene Präzisierungen D28–D31 (stack-eigene Namen INDEX/COLLECTION, Unicode-Normalisierung, vollständige Reserviert-Liste samt leerem Titel, Umgang mit #114). Akzeptanzkriterien ergänzt: Schrägstrich (aus #114), new project ohne Tracker-Seiteneffekt, rename bei --dry-run und von einem verbotenen Titel weg, Foo/foo-Kollision in lint. Korpus-Probelauf auch gegen D28–D30 sauber. Wegen D28–D31 kind/build → kind/decision.

**Changelog:** Gegen den Baum geprüft (`94deccb`). Neu: Abschnitt „Umsetzung (Plan)“ (Modul `titles.py`, `new`/`rename`/`lint`, Records, Doku, Tests, Bump-Entwurf) und vier offene Präzisierungen D28–D31 (stack-eigene Namen `INDEX`/`COLLECTION`, Unicode-Normalisierung, vollständige Reserviert-Liste samt leerem Titel, Umgang mit #114). Akzeptanzkriterien ergänzt: Schrägstrich (aus #114), `new project` ohne Tracker-Seiteneffekt, `rename` bei `--dry-run` und von einem verbotenen Titel weg, Foo/foo-Kollision in `lint`. Korpus-Probelauf auch gegen D28–D30 sauber. Wegen D28–D31 `kind/build` → `kind/decision`.
torben added kind/build and removed kind/decision labels 2026-09-29 20:57:22 +00:00
Author
Owner

Changelog: Die Präzisierungen D28–D31 sind entschieden (Betreiber, 2026-09-29, alle wie empfohlen). Darum kind/decision → kind/build. Neu dazu gekommen:

  • Bestandsaufnahme zu D28: Die Tabelle zeigt, welche Stack-Dateien dort liegen, wo new schreibt.
  • Nebenbefund, reproduziert: new überschreibt bei root: repo eine bestehende Datei, z. B. new instruction --name gates.
  • Neue Verzeichnisprüfung check_target_free, die für alle Wurzeln und auch für move greift.
  • Weitere Akzeptanzkriterien: NFD/NFC, Index/collection/COM¹ und das Überschreiben bei root: repo.
  • Regel: um D28–D30 ergänzt.
  • #114: geschlossen, weil es in diesem Paket aufgeht.
**Changelog:** Die Präzisierungen D28–D31 sind entschieden (Betreiber, 2026-09-29, alle wie empfohlen). Darum `kind/decision` → `kind/build`. Neu dazu gekommen: - **Bestandsaufnahme zu D28:** Die Tabelle zeigt, welche Stack-Dateien dort liegen, wo `new` schreibt. - **Nebenbefund, reproduziert:** `new` überschreibt bei `root: repo` eine bestehende Datei, z. B. `new instruction --name gates`. - **Neue Verzeichnisprüfung** `check_target_free`, die für alle Wurzeln und auch für `move` greift. - **Weitere Akzeptanzkriterien:** NFD/NFC, `Index`/`collection`/`COM¹` und das Überschreiben bei `root: repo`. - **Regel:** um D28–D30 ergänzt. - **#114:** geschlossen, weil es in diesem Paket aufgeht.
Author
Owner

Umgesetzt und veröffentlicht (8be5e6e, 8.0.0-beta.1); CI-Läufe 431/432 grün. Body auf den Endstand gebracht: Plan → „wie gebaut“, alle Akzeptanzkriterien abgehakt mit dem jeweiligen Test, Abschnitt „Verifiziert“ neu. Einzige Abweichung vom Entwurf: der Bump-Titel ist englisch, weil CHANGES.md englisch ist.

Umgesetzt und veröffentlicht (`8be5e6e`, 8.0.0-beta.1); CI-Läufe 431/432 grün. Body auf den Endstand gebracht: Plan → „wie gebaut“, alle Akzeptanzkriterien abgehakt mit dem jeweiligen Test, Abschnitt „Verifiziert“ neu. Einzige Abweichung vom Entwurf: der Bump-Titel ist englisch, weil `CHANGES.md` englisch ist.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#155