• v5.1.0 1b0158fc8d

    v5.1.0
    CI / verify (push) Successful in 50s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-12 16:27:52 +00:00 | 4 commits to main since this release

    5.1.0 - 2026-09-12 - changelog: Kandidaten-Eintrag nach Impact gruppiert, version regrade zur Nachkorrektur, version release verlangt eine Zusammenfassung

    Author: Torben Nehmer

    High impact

    • changelog: Kandidaten-Eintrag nach Impact gruppiert, version regrade zur Nachkorrektur, version release verlangt eine Zusammenfassung

    Seit 4.4.0 sammelt ein laufender Kandidat alle Bumps in einem Eintrag; bei 5.0.0 wurde das
    mit 20 Bumps und ~1440 Zeilen unlesbar, weil die Liste chronologisch und ungewichtet war und die
    Release-Seite genau diesen Eintrag 1:1 uebernimmt (version notes, release.yml). Der Eintrag
    ist jetzt geschichtet statt einer einzigen Wand Text: version bump --impact high|medium|low
    (Default medium) graduiert jeden Bump, die Liste rendert nach High/Medium/Low gruppiert - ausser
    wenn alles medium ist, dann bleibt sie flach wie bisher, damit jeder alte Eintrag und jeder
    einfache Patch unveraendert bleibt. version regrade korrigiert eine Note im Nachhinein, gegen
    einen einzelnen Lesevorgang der ganzen Liste, bevor der Kandidat geschlossen wird. Direkt unter
    der Liste steht jetzt eine kurze Zusammenfassung, darunter je Bump ein eigener
    ### <Bump-Titel>-Changeset-Absatz; version release verweigert das Schliessen eines Kandidaten
    mit zwei oder mehr Bumps, solange diese Zusammenfassung fehlt (ein Kandidat mit genau einem Bump
    ist ausgenommen - sein Changeset ist bereits die Zusammenfassung, wie hier). Geschlossene
    Eintraege wie der zu 5.0.0 bleiben in der alten Form stehen: die Release-Seiten sind laut
    eigenem Footer unveraenderliche Snapshots, und instructions/dev/version-parts.md sowie
    docs/version-model.md zitieren den 2.0.0-Eintrag mit Abschnittsnamen.

    --breaking/**Migration:** sitzen jetzt oberhalb der Bump-Liste statt darunter, damit die fuer
    einen Operator wichtigste Zeile nicht unter einer moeglicherweise langen Liste verschwindet.


    This note is a snapshot of the CHANGES.md entry as it stood when the tag was cut, and
    is never edited afterwards. The maintained version of this text - including any later
    correction - is the entry for this version in CHANGES.md in the repository.

    Downloads
  • v5.0.1 9a2d7d34f5

    v5.0.1
    CI / verify (push) Successful in 51s
    Release / release (push) Successful in 40s
    Stable

    torben released this 2026-09-12 15:37:52 +00:00 | 5 commits to main since this release

    5.0.1 - 2026-09-12 - publish: ungeborene main ist kein detached HEAD, erster Push zu leerem Remote (schliesst #96, #97)

    Author: Torben Nehmer

    • publish: ungeborene main ist kein detached HEAD, erster Push zu leerem Remote (schliesst #96, #97)

    Zwei Defekte, die zusammen dazu führten, dass eine frisch aufgesetzte Instanz
    sich über keinen dokumentierten Weg initial veröffentlichen ließ. Beide sitzen
    in publish und wurden erst beim Einrichten einer 5.0.0-Instanz aus dem
    Release-Tarball sichtbar - der Pfad, den instructions/setup-instance.md
    Schritt 14 als den ersten schreibenden Aufruf einer neuen Instanz nennt.

    Ein ungeborener Branch ist kein detached HEAD. current_branch() fragte
    git rev-parse --abbrev-ref HEAD. Nach git init -b main, vor dem ersten
    Commit, zeigt HEAD auf einen Ref, der sich nicht auflöst: rev-parse endet
    mit Exit 128 und war damit von einem echten detached HEAD nicht zu
    unterscheiden. Der erste publish einer neuen Instanz brach deshalb mit
    "Refusing to push main from a detached HEAD" ab, und Invariante 5 schneidet
    den naheliegenden Ausweg (git commit von Hand) ab. Die Funktion liest jetzt
    git symbolic-ref --short -q HEAD - was HEAD benennt statt worauf es
    zeigt. Damit beantwortet die Branch-Prüfung den ungeborenen Fall korrekt,
    statt für ihn ausgesetzt werden zu müssen: sie vergleicht main mit main
    und lässt durch. Ein echter detached HEAD wird unverändert abgelehnt, und ein
    --branch, das nicht dem ausgecheckten entspricht, ebenfalls.

    Ein nie gepushter Branch ist nicht "nicht ahead". _local_ahead_of_remote()
    entscheidet, ob ein sauberer Working-Tree trotzdem etwas zu pushen hat, und
    gab False zurück, sobald kein Tracking-Ref existierte. Genau das ist der Fall
    bei einem frisch angelegten, leeren Remote-Repository: der lokale Commit stand,
    publish meldete "Nothing to commit" und pushte nie - beliebig oft
    wiederholbar. Unterschieden wird jetzt über git ls-remote --exit-code, dessen
    Exit-Code die drei Lagen ohne Textvergleich trennt (0 = Ref vorhanden,
    2 = Remote erreichbar und ohne diesen Ref, 128 = unerreichbar oder gar nicht
    konfiguriert); auf die Meldung zu matchen schiede aus, weil git sie übersetzt.
    Nur der mittlere Fall gilt als "ahead", und auch dort nur, wenn lokal
    überhaupt ein Commit existiert. Ein unerreichbares Remote behält bewusst das
    bisherige Verhalten, damit Offline- und Nur-lokal-Instanzen keine
    Verhaltensänderung sehen. remote_ref_exists() bleibt unangetastet - sein
    zweiter Aufrufer reconcile() meint damit weiterhin richtig "nichts zum
    Abgleichen da".

    tools/CONTRACT.md zieht beides nach: die publish-Zeile beschrieb den
    Strandungsfall bisher als gelöst, was für einen nie gepushten Branch nicht
    stimmte, und der Fehlerkontrakt benennt die Branch-Prüfung jetzt als
    eigenständigen Exit-1-Grund. instructions/setup-instance.md und INSTALL.md
    blieben inhaltlich richtig - sie hatten den Umweg nie beschrieben, sondern den
    Weg, der jetzt tatsächlich funktioniert.


    This note is a snapshot of the CHANGES.md entry as it stood when the tag was cut, and
    is never edited afterwards. The maintained version of this text - including any later
    correction - is the entry for this version in CHANGES.md in the repository.

    Downloads
  • v5.0.0 87719bf396

    v5.0.0
    CI / verify (push) Successful in 50s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-11 17:25:49 +00:00 | 7 commits to main since this release

    5.0.0 - 2026-09-11 - TOC-Pflicht in docs verify, Konfidenz-Mechanismus ersatzlos entfernt

    Author: Torben Nehmer

    • status/incoming: menschliche Stubs werden ausgearbeitet, nie so umgesetzt
    • page move: eine kb-Seite folgt ihrem Subtype ins Verzeichnis, das ihr Type-Spec berechnet
    • kb/CONTRACT.md: Tiefe 1 als Grenze - Katalog liest nur eine Area-Ebene
    • raw accept: incoming/ als abgeleiteter Rohablage-Eingang (schliesst #58)
    • raw accept: Stem-Eindeutigkeit im Typverzeichnis erzwingen, --replaces als einziger Weg daran vorbei (schliesst #64)
    • update entity naming conventions to use singular form for consistency
    • kb/concepts/ bekommt Areas: layout: fuer concept, Area-Titel aus jedem Type-Spec, Schwellen-Empfehlung im lint (schliesst #59)
    • source_type: Default streichen, unclassified als sichtbares Fach, layout: fuer source (schliesst #66)
    • raw accept: Datums-Shard statt Typverzeichnis, fidelity/authority am Drop-Punkt (schliesst #67)
    • source_type ist Instanzsache: Profilkatalog, Setup-Frage, evolve-subtypes-Instruction (#68)
    • instructions/CONTRACT.md: Skill-H1, Referenztiefe und Begruendungsprosa praezisiert (#71, #72, #79)
    • SKILL.md-Sanierung: Checklisten, Kommandolisten, wiki-status-Hard-Rule, wiki-query-Filingpruefung (#70, #74, #75, #78)
    • Ausgelieferte Doku zitiert keine Issue-Nummern mehr, docs verify prueft es (schliesst #77)
    • TOC-Pflicht fuer Referenzdateien ueber 100 Zeilen; session-setup.md/gates.md nennen die tatsaechliche Budget-Ausnahmeliste (schliesst #73, #76)
    • tools/CONTRACT.md: docs toc im Fehlerkontrakt, docs-verify-Zeile nennt die TOC-Pruefung; tools/README.md korrigiert das --major-Kriterium
    • dist export erzeugt die TOC-Region nach dem Marker-Strip neu (CI-Fund im Export-Replay)
    • Konfidenz-Mechanismus ersatzlos entfernt
    • Konfidenz-Mechanismus ersatzlos entfernt
    • version-parts.md dokumentiert den --migration-required-Ruecknahmepfad
    • wiki-status verweist auf session-setup.md (schliesst #84)
    • CLAUDE.md-Importkette entdrifted, Modellwahl nach docs/ verschoben (schliesst #81)
    • Telemetrie-Default nach Installationsform, Byte-Deckel und Session-Retention
    • tools/CONTRACT.md: raw accept Doku auf Datums-Shard und Capture-Felder nachgezogen (schliesst #89)
    • MCP submit-Tool: Quarantäne-Schreibpfad mit Upload Review Gate (schliesst #32)
    • AGENTS.md-Changelog-Absatz korrigiert: Contract-Prosa ist Sitzungsarbeit, doc-pull-through-Instruction ergaenzt
    • docs verify prueft die Kommando- und Fehlerkontrakttabelle in tools/CONTRACT.md getrennt, 10 fehlende Fehlerkontrakt-Zeilen nachgetragen (schliesst #91)
    • tools/CONTRACT.md als Nachschlage-Dokument strukturiert: ###-Gruppen in beiden Tabellen, spiegelnde Reihenfolge, Lead-in-Regel
    • incoming/.gitkeep als Datei- statt Verzeichnismuster trackbar (schliesst #88)
    • dist export-Doku: Typverzeichnis-Behauptung nach #67 korrigiert (schliesst #93)
    • raw/*/.gitkeep-Glob in dist-upgrade-Doku auf den flachen raw/.gitkeep-Anker korrigiert
    • Breiten-Auslöser für Ingests: eine einzelne, thematisch breite Quelle bekommt einen Extract-Pass statt einer Seite pro Namen
    • Breiten-Auslöser für Ingests: Extract-Pass statt einer Seite pro Namen, types/source.md nachgezogen
    • stack-close Schritt 3: ein Doku-Nachzug in einen versionierten Pfad braucht seinen eigenen Bump

    Breaking Change: docs verify verlangt eine aktuelle Inhaltsverzeichnis-Region () auf AGENTS.md, jedem Stage-/Collection-Contract und jeder flachen instructions/**.md-Datei ueber 100 Zeilen - eine bestehende Instanz mit einer eigenen instructions/*.md-Datei ueber 100 Zeilen ohne TOC sieht docs verify nach dem Tool-Update neu fehlschlagen, bis einmalig 'wikitool docs toc --apply' laeuft und der Diff committet wird. Ausserdem verschwinden wikitool confidence decay und confidence init-base ersatzlos, touch --confidence-base ebenso, und die Schemas fuer entity/concept verlieren confidence/confidence_base vollstaendig (additionalProperties: false) - jede bestehende Instanz muss den Korpus migrieren, sonst werden alle Entity-/Concept-Seiten beim Schema-Update sofort schemainvalide. Ablauf: instructions/migrations/5.0.0-confidence-removal.md

    Das Label status/incoming gibt es seit heute in Gitea: der Mensch legt einen
    Stub an — zwei Sätze, ein Verdacht, ein „wäre interessant" — und der Stack
    komplettiert ihn. instructions/dev/issue-tracking.md beschrieb es nicht, und
    das ist die gefährlichere Hälfte: ein Stub sieht aus wie ein Body, und der
    Body ist genau das, was eine Sitzung nach Schritt 2 als Spec glaubt. Zwei
    solche Issues lagen bereits offen auf dem Board (#60, #61).

    Neu in der Instruction ist deshalb ein eigener Abschnitt „Incoming stubs" mit
    dem Verbot als erstem Satz — ein status/incoming-Issue wird nie so umgesetzt,
    wie es dasteht — und der Ausarbeitung als sechsschrittigem Ablauf: den Wortlaut
    als Absichtserklärung lesen, gegen den Baum prüfen, den Originaltext wörtlich in
    den Kommentar retten, bevor der Rewrite ihn überschreibt, offene Fragen benennen
    statt beantworten (kind/decision), erst dann die vier Pflichtlabel, dann das
    Flag entfernen. Zwei Ausgänge wie bei status/unconfirmed: ausgearbeitet oder
    mit Begründung geschlossen.

    Der interessante Punkt ist Schritt 4: status/incoming ist das einzige Flag,
    das Schritt 4 nicht qualifiziert, sondern aussetzt. Die vier Pflichtachsen
    fehlen einem Stub nicht, sie sind noch nicht fällig — kind/, prio/ und
    size/ sind Antworten auf Fragen, die niemand gegen den Baum geprüft hat. Ein
    Stub auf Sicht durchzulabeln ist der Fehler, nicht das Weglassen: es lässt
    Ungeprüftes triagiert aussehen.

    Der Schaden, den das verhindert, ist derselbe wie bei #30, nur eine Stufe
    früher: dort schrieb ein Body einen Mechanismus vor und bekam dessen Bugs
    gebaut, hier schreibt ein Body gar nichts vor und bekommt die Lücke von
    demjenigen gefüllt, der ihn am schnellsten gelesen hat — inklusive Close, womit
    die Frage, für die der Stub stand, nie wieder gestellt wird.

    Keine neue Schrittnummer, bewusst: stack-close/SKILL.md und ältere
    CHANGES.md-Einträge verweisen namentlich auf „Schritte 2-3 und 7". Eine
    Umnummerierung hätte diese Verweise still falsch gemacht — genau der Zerfall,
    den dieselbe Datei in § Renames beschreibt.

    Geändert: instructions/dev/issue-tracking.md
    (Frontmatter, § When to run, Schritt 5, neuer § Incoming stubs, § What no tool
    checks, § Decision points) und die Routing-Zeile in
    instructions/dev/stack-dev/SKILL.md. Dev-only — dist export schließt
    instructions/dev/ aus, eine ausgelieferte Instanz sieht davon nichts, deshalb
    PATCH.

    Nachgezogen in derselben Sitzung, aber als eigenes Paket (#63, Commit
    a0aecfc): kb/concepts/Issue Label Scheme.md beschrieb weiter nur die beiden
    alten status/-Flags und zählte sechzehn statt siebzehn Labels. Das ist
    kb/-Inhalt und brauchte nach Invariante 3 erst eine Quelle — die Threadkopie
    von #62/#63 in raw/notes/ — also eine wiki-manage-Sitzung statt dieser
    hier. Die Seite trägt jetzt die dritte status/-Zeile samt Verbot und einen
    Kernpunkt dazu, dass dieses Flag die Vier-Achsen-Pflicht aussetzt.

    Offen bleibt die erste tatsächliche Anwendung der Regel: #60 und #61 sind
    weiterhin unausgearbeitete Stubs.

    wikitool move (#56): Nichts im Stack bewegte bisher eine Seite über eine
    Verzeichnisgrenze — rename schreibt laut eigenem Docstring nie über
    target.path.parent hinaus, und new_page._target_dir rechnete die
    Platzierung nur beim Anlegen. Ändert sich entity_type später, blieb die
    Datei still am alten Ort liegen, und nichts prüfte das nach: weder
    lint_core.py noch doctor.py verglichen den Ist-Ort einer Seite mit dem,
    was ihr Type-Spec berechnen würde.

    Die Platzierungslogik selbst gibt es jetzt genau einmal:
    TypeResolver.compute_target_dir (plus TypeResolver.subtype_dir für den
    layout:-Teil), new_page._target_dir delegiert nur noch dorthin. Darauf
    aufbauend zwei neue Stücke:

    • wikitool move --page "<Titel>" bewegt eine Seite an den berechneten Ort;
      --reconcile wendet dieselbe Regel auf den ganzen Bestand an und ist
      idempotent (ein zweiter Lauf meldet nichts mehr zu tun). Beide Modi fassen
      weder Body noch Frontmatter an, und der Titel — die einzige Identität einer
      Seite — ändert sich nie, also folgt kein Referenz-Update. Ein bereits
      belegtes Ziel (ein alter Stem-Kollisionsrest) wird verweigert statt still
      überschrieben. Top-level registriert, wie rename/rm, nicht unter einem
      page-Unterbefehl.
    • lint bekommt einen neuen Befund, Misplaced Pages, mit Ist- und
      Soll-Pfad. Bewusst nicht in HARD_ERROR_KEYS: ein von Hand platzierter
      Bestand ist keine kaputte Seite, nur eine, die move aufräumen könnte — auf
      dieser Instanz sind das aktuell die drei kb/entities/projects/*/-Seiten,
      die #57 separat behandelt.

    Ein Fund unterwegs, der ohne #56 unsichtbar geblieben wäre: migrate verify
    schlüsselte Seiten über den repo-relativen Pfad. Ein reiner Move ergab
    „N removed, N added, 0 compared" und lief grün durch — der eine mechanische
    Check, für den instructions/migrate-corpus.md existiert, hätte bei genau der
    Operation nichts geprüft, die dieses Issue einführt. Behoben: PageShape
    trägt jetzt zusätzlich den Pfad, _shapes_at_revision/_shapes_now
    schlüsseln über den Titel (die einzige Identität einer Seite), und
    CorpusDiff.moved meldet einen reinen Ortswechsel separat — informativ,
    niemals als Finding. Regressionstest deckt drei verschobene Seiten mit
    compared == 3, added == 0, removed == 0 ab.

    Bewusst nicht Teil dieses Pakets: die drei realen Seiten aus #57 bleiben
    liegen (kein Korpus-Publish hier, nur Stack), und raw rename (#16) — das
    git mv-mit-mv-Fallback aus #56s Entwurf war für Rohdateien gedacht;
    rename/rm/move bewegen kb-Seiten über ein einfaches Path.rename, weil
    publish ohnehin über git add -A staged.

    Geändert: tools/chemenu/type_resolver.py (compute_target_dir,
    subtype_dir), tools/chemenu/commands/new_page.py (delegiert),
    tools/chemenu/commands/page_ops.py (move_command),
    tools/chemenu/lint_core.py (find_misplaced, misplaced_pages),
    tools/chemenu/corpus_diff.py und tools/chemenu/commands/migrate_cmd.py
    (Titel-Schlüsselung, moved), tools/chemenu/commands/log_append.py (--op move), tools/CONTRACT.md, instructions/page-lifecycle.md,
    instructions/publish-cycle.md. MINOR: rückwärts liest ein älterer Stack eine
    verschobene Seite unverändert (Identität ist der Titel, nicht der Ort),
    vorwärts reines Überkopieren.

    Katalogtiefe (#57): index_build.group_pages las bislang genau zwei
    Pfadsegmente unter kb/ (parts[0] als Collection, parts[1] als Area) und
    faltete alles darunter still in die Level-1-Area. Real betroffen waren die
    drei Seiten aus #56s Befund, kb/entities/projects/{kfchou,vanillaflava, yugasun}/*.md — im generierten Katalog nicht als eigener Ort sichtbar,
    sondern als läge jede direkt in entities/projects/. kb/CONTRACT.md §
    Collections beschrieb bis heute nur eine Ebene, ohne eine zweite
    auszuschließen; der Baum hatte trotzdem eine, handplatziert, ohne
    unterstützten Weg dorthin.

    Entscheidung war (b) aus dem Issue: die drei Verzeichnisse auflösen statt den
    Katalog rekursiv zu machen. Die Gruppierungsachse dahinter — Owner
    (kfchou/vanillaflava/yugasun) — kommt aus keinem Frontmatter-Feld und
    aus keinem Type-Spec, sondern aus einer Ad-hoc-Entscheidung beim Anlegen; sie
    verdient keine zweite Verzeichnisebene. Tiefe 1 ist jetzt geschriebene Regel
    in kb/CONTRACT.md § Collections, mit dieser Begründung.

    Vier Stücke setzen das um:

    • kb_scan.find_nested_pages liefert (title, page, depth) für jede
      Seite mehr als ein Verzeichnis unterhalb ihrer Collection — reine
      Pfadtiefe, unabhängig davon, ob der Typ auflöst, damit auch eine Seite mit
      kaputtem type: nicht durchrutscht.
    • lint bekommt den Befund Nested Pages, und anders als
      misplaced_pages hart: eine fehlplatzierte Seite katalogisiert noch
      korrekt von der falschen Stelle aus, eine verschachtelte macht den
      generierten Katalog selbst falsch, und es gibt keine Version, ab der das
      toleriert würde.
    • index rebuild lehnt eine verschachtelte Seite nicht ab, sondern warnt
      (Entscheidung aus der Session: melden statt verweigern, damit ein
      Fremdinstanz-Upgrade mit handverschachtelten Seiten nicht hart bricht) —
      group_pages faltet weiterhin wie zuvor, die Warnung ist die neue
      Sichtbarkeit, nicht eine Verhaltensänderung der Faltung selbst.
    • TypeResolver.get_layout validiert layout: {dir: ...} jetzt auf
      einen einzelnen Pfadabschnitt (kein /, kein \, kein ./.., nicht
      leer) und schlägt fehl statt eine zweite Ebene über den einzigen
      unterstützten Weg — ein Type-Spec — entstehen zu lassen.

    Ein fünftes Stück, das das Issue selbst nicht explizit forderte, aber die
    move-Mechanik aus #56 sonst mit toten Verzeichnissen zurückgelassen hätte:
    move (--page wie --reconcile) entfernt jetzt ein Verzeichnis, das es
    durch den Wegzug seiner letzten Seite geleert hat — symmetrisch zum
    mkdir(parents=True) auf der Zielseite. Ohne das hätten kfchou/,
    vanillaflava/, yugasun/ den eigenen Fix überlebt, leer und für git
    unsichtbar, aber für einen verzeichnisbasierten Test sichtbar.

    Auf dieser Instanz angewendet: wikitool move --reconcile hat die drei
    Seiten nach kb/entities/projects/ gezogen und die drei leeren
    Owner-Verzeichnisse mitentfernt. migrate verify --from HEAD bestätigt
    compared == 182, added == 0, removed == 0, alle drei als moved markiert.
    Titelkollision trat wie im Issue erwartet keine auf.

    Geändert: tools/chemenu/kb_scan.py (find_nested_pages),
    tools/chemenu/lint_core.py (nested_pages, HARD_ERROR_KEYS),
    tools/chemenu/commands/index_build.py (Rebuild-Warnung),
    tools/chemenu/type_resolver.py (get_layout-Validierung),
    tools/chemenu/commands/page_ops.py (_rmdir_if_emptied), tools/CONTRACT.md,
    kb/CONTRACT.md § Collections, plus die drei realen Seiten unter
    kb/entities/projects/. PATCH: reine Codeänderung ohne Schnittstellenwechsel,
    gefaltet in den offenen 4.8.0-Kandidaten (max-wins gegen die MINOR-Bewegung
    aus #56); die drei bewegten Seiten sind Korpus dieser Instanz, kein
    ausgelieferter Inhalt.

    Schließt #57.

    raw accept (#58): raw/CONTRACT.mds Routing-Tabelle war bislang eine Regel für Menschen —
    wer eine Datei ablegt, wählt articles//documents//notes//assets/ selbst, und mehrere
    Dateien einer logischen Quelle waren im Dateisystem nicht als zusammengehörig erkennbar. Neu ist
    ein gitignorierter Eingang incoming/, der dieselben vier Typverzeichnisse spiegelt: der Mensch
    klassifiziert nur, indem er dort ablegt, tools/wikitool raw accept <datei> [<datei> ...] [--page "<Titel>"] berechnet die Beförderung nach raw/.

    Zwei Entscheidungen, gegen die ursprüngliche Skizze im Issue: ein Bundle-Verzeichnis
    (raw/<typ>/<stamm>/, benannt nach der ersten Datei) entsteht erst ab der zweiten Datei, nie
    einheitlich — damit sind die 29 heute flach liegenden Bestandsdateien keine Ausnahme, sondern
    bereits die Regelform, und die Frage „was passiert mit dem Bestand" beantwortet sich von selbst.
    Und der Typ wird über das Eingangs-Unterverzeichnis deklariert, nicht über ein --type-Flag: die
    Erklärung wird abgegeben, wenn der Mensch die Datei in der Hand hat, statt im Moment des
    accept-Aufrufs neu geraten werden zu müssen.

    --page deckt den Wachstumsfall ab: erweitert raw_files: einer bestehenden Source-Seite und
    faltet deren schon abgelegte Einzeldatei ins neue Bundle, sobald das die Seite über eine Datei
    hinaus wachsen lässt — ohne ein Fenster, in dem raw_files: ins Leere zeigt. Die dafür nötige
    Rückwärtssuche und der Mehrfach-Owner-Schutz sind keine neue Mechanik, sondern
    provenance.source_pages_by_raw_file, das lint schon für duplicate_raw_file_owners benutzt —
    ein Owner-Konflikt lehnt die Beförderung ab, statt eine andere Seite unbemerkt zu brechen.

    incoming/ ist für sources coverage und lint unsichtbar (beide laufen ausschließlich über
    config.iter_raw_files(config.RAW_DIR)), und dass keine Datei dort je committet werden kann, ist
    über docs_verify.REQUIRED_IGNORE_CANARIES bewiesen, nicht nur zugesichert. RAW_SUBDIRS
    (dist_cmd.py) bleibt die einzige Quelle der Vier-Verzeichnis-Liste: docs verify
    (check_raw_subdirs) hält raw/CONTRACT.mds Tabelle jetzt in beiden Richtungen dagegen, und
    dist export sät incoming/<typ>/.gitkeep neben raw/<typ>/.gitkeep; instructions/bootstrap.md
    legt den Eingang für einen bestehenden Klon nach, da er dort nie aus git kommt.

    Geändert: tools/chemenu/commands/raw_cmd.py (neu, raw accept), tools/chemenu/cli.py,
    tools/chemenu/commands/dist_cmd.py (RAW_SUBDIRS-Kommentar, incoming/*/.gitkeep),
    tools/chemenu/commands/docs_verify.py (check_raw_subdirs, incoming/-Ignore-Kanarie),
    .gitignore, raw/CONTRACT.md, tools/CONTRACT.md, instructions/bootstrap.md,
    instructions/wiki-ingest/SKILL.md. MINOR: eine Umsortierung des Bestands wäre die Grenze
    gewesen, findet aber unter der Bundle-erst-ab-zwei-Regel nicht statt — der Bestand bleibt
    unangetastet, keine fremde Instanz muss migrieren, vorwärts wie rückwärts reines Überkopieren.

    Schließt #58.

    raw accept prüfte Kollisionen bisher nur auf einzelnen Dateipfaden
    (dst.exists()), nie auf dem Bundle-Verzeichnis selbst. Weil der Bundle-Name
    aus dem Stem der Primärdatei entsteht, konnte eine zweite, unabhängige Quelle
    wortlos in das Bundle einer ersten wandern, sobald die Dateinamen zufällig
    nicht kollidierten — verifiziert mit einem Wegwerf-Test: handbuch.txt +
    anhang.md ohne --page landeten unbemerkt in einem bestehenden
    raw/documents/handbuch/. lint meldete nichts, weil beide Quellen ihre
    Dateien korrekt abdeckten.

    Die Menge der Namen auf raw/<typ>/-Ebene — Dateistämme plus
    Bundle-Verzeichnisnamen — ist jetzt eindeutig erzwungen (_occupied_stems in
    raw_cmd.py), unter Ausnahme dessen, was der Aufruf selbst schon besitzt: ein
    Bundle, das über --page wächst, oder ein bereits registriertes, sich
    fortsetzendes Bundle. Ein belegter Stem wird mit Exit 1 abgelehnt und nennt
    beide Auswege, ohne einen zu empfehlen — --replaces oder Umbenennen in
    incoming/.

    --replaces <raw-pfad> ist der einzige sanktionierte Weg, eine Rohdatei
    wortwörtlich zu ersetzen: genau eine eingehende Datei, identischer Dateiname,
    gleiches Typverzeichnis, Ablehnung bei mehr als einem Owner. raw_files:
    bleibt unverändert, es wird keine kb/-Seite geschrieben, und die Altfassung
    lebt ausschließlich in git log --follow weiter — kein Archivverzeichnis,
    kein Hash im Dateinamen, kein neues Frontmatter-Feld. Nach einem Ersatz nennt
    das Kommando die Source-Seite und ihre zitierenden Seiten, damit deren
    Nachzug im selben Commit passiert wie die Ersetzung.

    raw/CONTRACT.md § Rules trägt beide Regeln aus dieser Entscheidung
    (unveränderlich, aber ersetzbar als Ganzes); instructions/wiki-ingest/SKILL.md
    benennt den Kollisionsfall als Haltepunkt, an dem eine Sitzung die Meldung dem
    Menschen vorlegt statt selbst zu entscheiden — dieselbe Klasse wie AGENTS.md
    Invariante 6, auch ohne dass hier ein Exit-42-Gate greift.

    Geändert: tools/chemenu/commands/raw_cmd.py (_occupied_stems,
    _stem_collision_message, --replaces/_replace),
    tools/chemenu/tests/test_raw_cmd.py (16 neue Tests), raw/CONTRACT.md,
    tools/CONTRACT.md, instructions/wiki-ingest/SKILL.md. MINOR: raw accept
    wurde nie released (letztes Release v4.7.4), die Verschärfung kostet also
    keine Kompatibilitätsfrage, solange sie vor 4.8.0 landet.

    Schließt #64.

    kb/concepts/ bekommt Areas (#59): Sharden ist längst automatisch —
    index_build.SHARD_THRESHOLD = 50, hergeleitet aus der wikieigenen Seite
    Index Scaling — aber es passiert pro Area, und eine Area legt niemand an.
    kb/concepts/ hatte keine, also war die Schwelle dort ein toter Wert: 80 Seiten
    in einer einzigen Tabelle, weit über der eigenen Grenze, ohne dass je etwas
    gefeuert hätte. Die Ursache war eine Asymmetrie in den Type-Specs — entity
    deklarierte ein layout:, concept nicht, obwohl das Subtype-Feld fertig dalag.

    types/concept.md deklariert es jetzt für alle sechs concept_type-Werte
    (architectures/, patterns/, protocols/, workflows/, decisions/,
    problems/). Für source bewusst nicht: 25 von 29 Seiten sind notes, die
    Aufteilung ergäbe eine Area und vier Splitter, und kb/sources/ liegt mit 29
    Seiten ohnehin unter der Schwelle. Ein Subtype-Feld zu haben ist kein Grund, es
    als Achse zu benutzen.

    Zwei Dinge im Code, beide Folgen desselben Befunds. _area_titles() in
    index_build.py löste entity fest über find_type_by_name("entity") auf und
    las nur dessen layout: — jeder zweite Typ mit einem layout: hätte
    .title()-Namen auf dem Verzeichnisnamen bekommen statt der deklarierten Titel.
    Es liest jetzt jedes Type-Spec, und zwar pro Collection geschlüsselt, damit
    zwei Typen denselben Area-Namen für Verschiedenes benutzen dürfen. Und der neue
    lint-Befund meldet eine Collection über der Schwelle ohne Areas, mit der
    Verteilung ihres Subtype-Felds — als Empfehlung, nicht als Failure, und nur
    dann, wenn die Aufteilung jede entstehende Area unter die Schwelle drückt. Das
    begrenzt sich selbst in beide Richtungen: kb/comparisons/ mit einer Seite
    feuert nie, und die schlechte Aufteilung nach source_type unterbleibt von
    allein, ohne dass der Check etwas über Sources wüsste.

    Zwei Dinge fielen unterwegs an, die das Issue nicht vorhergesehen hatte.
    lint_core.py durfte SHARD_THRESHOLD/group_pages nicht aus
    commands/index_build.py importieren — test_api.py prüft strukturell, dass
    chemenu.api kein Modul unter chemenu.commands lädt, und der Import hätte den
    ganzen CLI-Kopf mitgezogen. Die Gruppierung liegt deshalb neu in
    tools/chemenu/catalog.py, entlang derselben Linie wie lint_core.py:
    Korpusform hier, Darstellung dort. Und _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. Vorher fiel das keinem auf,
    weil alle Entity-Area-Titel zufällig ASCII sind; Abläufe ist der erste, der es
    nicht ist.

    Auf dieser Instanz angewendet: wikitool move --reconcile hat alle 80
    Concept-Seiten in ihre Area gezogen, migrate verify --from HEAD bestätigt
    182 compared, 0 added, 0 removed, 80 moved, 0 findings — kein Titel, kein
    Body, kein Frontmatter-Feld angefasst. index rebuild erzeugt sechs Areas
    (Abläufe 28, Architekturen 20, Muster 17, Entscheidungen 7, Problemstellungen 5,
    Protokolle 3); keine über der Schwelle, also kein eigener Shard, und die
    Schwelle wirkt wieder als Schwelle.

    Geändert: types/concept.md (layout:), types/type-spec.md (wann ein
    layout: sich lohnt), tools/chemenu/catalog.py (neu),
    tools/chemenu/commands/index_build.py (area_titles, _anchor),
    tools/chemenu/lint_core.py (unsharded_collections),
    tools/chemenu/tests/ (Fixture-Concept liegt jetzt in seiner Area, plus neun
    neue Tests), tools/CONTRACT.md, tools/README.md, README.md,
    kb/concepts/COLLECTION.md, sowie die 80 bewegten Seiten unter kb/concepts/.

    MINOR, nicht MAJOR: der Umzug ist ein Angebot, kein Zwang. Eine
    bestehende Instanz, die move --reconcile nicht laufen lässt, bleibt
    funktionsfähig — group_pages liest das Dateisystem, nicht das layout:, also
    landen flache Bestandsseiten in der Area „All" und neu angelegte in ihrer
    eigenen; beides rendert. Der gemischte Zustand meldet sich als lint-Befund
    Misplaced Pages, der seit jeher advisory ist. Und ein Downgrade auf einen
    Stack ohne dieses layout: funktioniert weiter: die Verzeichnisse bleiben
    Verzeichnisse, nur die Anzeigetitel fallen auf .title() zurück. Kosmetik, kein
    Bruch der Austauschbarkeit in beiden Richtungen.

    Schließt #59.

    Nachzug an 63b4bb8: die Umstellung der Namenskonvention auf HA Integration
    hatte in README.md das Gegenbeispiel verloren — die Zeile las
    Use singular for entities: `HA Integration.md` (not `HA Integration.md`), beide
    Seiten des „not" identisch, also eine Regel ohne Fall, an dem sie greift.
    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.

    source_type hatte in types/source.schema.yaml ein default: notes — der Compiler wählte
    das Sammelbecken, sobald niemand widersprach, nicht ein Mensch. #59 hatte den Bestand deshalb
    für lopsided gehalten (25 von 29 Seiten notes) und source bewusst flach gelassen; nachgezählt
    nach dem, was die Seiten tatsächlich sind (Dateiname, author:, Rohdatei), waren es 16
    Session-Transkripte, 4 LLM-Analysen, 2 Tracker-Exporte und nur 3 echte Notizen. Der Default war
    der Fehler, nicht das Enum.

    Umgesetzt: default: gestrichen, wikitool new source verweigert jetzt ohne expliziten Wert.
    Enum neu: transcript, analysis, article, document, notes, tracker, unclassified
    spec und image entfallen (null Seiten, nie am echten Material bewährt). unclassified ist
    das neue, sichtbare Fach für eine Quelle, deren Kategorie noch nicht feststeht — eigene Area,
    beratender lint-Befund (unclassified_source_pages), keine harte Fehlerklasse. types/source.md
    deklariert jetzt ein layout: für alle sieben Werte.

    Auf dieser Instanz angewendet, in einem eigenen work/reclassify-source-types/-Lauf: 22 der 29
    Source-Seiten per wikitool touch --set source_type=<wert> auf ihren tatsächlichen Wert
    korrigiert (16 transcript, 4 analysis, 2 tracker), 7 unverändert. wikitool move --reconcile hat
    alle 29 danach in ihre Area gezogen (transcripts 16, analyses 4, articles 3, notes 3, trackers 2,
    documents 1, unclassified 0). migrate verify --from HEAD bestätigt 182 compared, 0 added, 0 removed, 29 moved, 22 findings — die 22 sind exakt die beabsichtigten source_type-Änderungen,
    kein Titel, kein Body, keine Wikilink- oder Zitatzahl angefasst. lint --fail-on-error grün,
    insbesondere ohne Misplaced Pages, Nested Pages oder den neuen Unclassified Source Pages.

    Geändert: types/source.schema.yaml (Enum, kein Default), types/source.md (layout:),
    types/type-spec.md (source als Beispiel für ein bewusst fehlendes layout: ersetzt —
    ein lopsided Feld wird repariert, nicht dauerhaft flach gelassen), tools/chemenu/lint_core.py
    (unclassified_source_pages, neu), tools/CONTRACT.md, instructions/wiki-ingest/SKILL.md
    (--set source_type=, unclassified als Ausweg), instructions/dev/corpus-policy.md
    (Floor-Ausnahme für unclassified), kb/sources/COLLECTION.md (Areas, Autorschaft trennt
    analysis von document), fünf Tests umgehängt (davon einer auf einen neuen
    Fixture-Type-Spec, weil source als „hat Subtype-Feld, kein layout:"-Beispiel wegfällt),
    sowie die 29 bewegten und 22 reklassifizierten Seiten unter kb/sources/.

    MINOR, geprüft am Drop-in-Test: eine bestehende Instanz besitzt ihr eigenes
    types/source.md (Auslieferung nur als .template), kopiert tools//types//instructions/
    über sich und bleibt unverändert funktionsfähig — kein Downgrade-Bruch, kein umgeschriebenes
    maschinengelesenes Format. Kein --breaking, kein Migrationsdokument.

    Schließt #66.

    raw accept: Datums-Shard statt Typverzeichnis, fidelity/authority am Drop-Punkt (#67,
    Paket B von vier — A ist #66 oben, C ist #68, D ist #69): zwei unabhängige Befunde, ein
    Codepfad.

    Befund 1: raw/CONTRACT.mds vier Typverzeichnisse (articles/, documents/, notes/,
    assets/) lösten keinen der drei Gründe ein, die einen Verzeichnis-Split rechtfertigen —
    raw/ wird nie durchgeblättert, keine Klausel dieser Datei galt je pro Verzeichnis, alle vier
    verrotten gleich (unveränderlich, nie gelöscht). Der Split kostete real: der Mensch trifft beim
    Ablegen in incoming/<typ>/ eine Routing-Entscheidung, die später blind nach source_type:
    abgeschrieben wird — genau darüber entstand der in #66 korrigierte Bias (raw/notes/ hielt laut
    altem Contract-Text „Gesprächsprotokolle", 16 der 25 Dateien dort waren tatsächlich Transkripte).

    Befund 2: was am Drop-Punkt bekannt ist und danach nirgends mehr — wie treu eine Erfassung ist
    und was das Material über seinen Gegenstand behaupten darf. Zwei neue, unabhängige Achsen auf
    types/source.md: fidelity (verbatim/published/secondhand/nontextual) und authority
    (normative/reporting/opinion), beide mit unknown als backfill-only-Wert.

    Umgesetzt:

    • raw accept adressiert eine Datei jetzt über raw/<YYYY>/<MM>/, berechnet aus dem
      Annahmedatum — eine reine Funktion von etwas Unveränderlichem, kann also nie rebalancieren und
      keinen [^cite-id]-Anker brechen. incoming/ wird flach; ein Unterverzeichnis wird toleriert
      und ignoriert statt inspiziert (alte incoming/<typ>/-Skripte laufen unverändert weiter).
      Bestandsdateien in raw/articles|documents|notes|assets/ bleiben unbewegt und weiter gültige
      --replaces-Ziele — das Layout war nirgends versioniert, es gibt also keine „zwei Korpusformen".
      Wächst eine bereits promotete Einzeldatei zum Bündel, entsteht das Bündel an ihrem eigenen
      Speicherort, nie im heutigen Shard — ein Bündel aus altem und neuem Datum hätte keine
      eindeutig richtige Adresse.
    • Stem-Eindeutigkeit (#64) gilt jetzt global über raw/, nicht mehr pro Typverzeichnis —
      ohne Typverzeichnisse als Grenze wäre die Prüfung sonst wirkungslos gegen ein Bündel in einem
      anderen Shard oder einem Alt-Verzeichnis.
    • fidelity/authority: neu in types/source.schema.yaml, ohne default: und bewusst
      nicht in required: (sonst bricht jede bestehende Instanz an der Validierung — die
      MINOR-Einstufung unten hängt daran). Erzwungen stattdessen im Werkzeug: raw accept verlangt
      beide Flags immer; trägt der Aufruf --page, schreibt es sie direkt auf die Zielseite, sonst
      druckt es die fertige new source --set fidelity=... --set authority=...-Folgezeile, und
      new source verweigert seinerseits ohne beide Werte. unknown ist backfill-only — weder
      raw accept noch new source dürfen es schreiben.
    • Capture-Felder sind fill-once, nicht auf touch.pys UNSETTABLE-Denylist: eine Denylist
      hätte auch den ersten (Backfill-)Schreibzugriff verboten, den der Migrationslauf braucht.
      touch --set <feld>=<wert> schreibt nur, solange das Feld fehlt, und verweist danach auf
      raw accept --replaces als einzigen Korrekturweg — der einzige Aufruf, der einen bereits
      gesetzten Capture-Wert überschreiben darf, weil eine korrigierte Erfassung eine neue Edition
      der Quelle ist, keine Bearbeitung der Seite. types/source.md deklariert die Feldliste selbst
      (capture_fields:), gelesen über TypeResolver.get_capture_fields statt an drei Stellen
      hartkodiert.
    • lint bekommt einen neuen beratenden Befund, Confidence Above Source Standing: die
      Autoritätsbewertung, die kb/CONVENTIONS.mds Confidence-Rubrik seit je verlangt („+0.1 für
      offizielle Doku"), aber nirgends festhielt. Eine stackseitige Obergrenzentabelle in
      kb/CONTRACT.md (reporting 0.8, opinion 0.6, secondhand/nontextual 0.7, normative/
      verbatim/published/unknown ohne Obergrenze) begrenzt confidence_base, ersetzt es aber
      nicht — eine Formel hätte zwei widersprechende Ableitungen derselben Zahl, und Autorität ist
      eine Obergrenze, kein Determinant. Nicht in HARD_ERROR_KEYS. Auf dieser Instanz meldet der
      Befund aktuell nichts: kein Bestand trägt die neuen Felder, das ist erwartet, nicht geprüft.
    • Aufgeräumt: dist_cmd.RAW_SUBDIRS und der darauf laufende docs verify-Check
      (check_raw_subdirs) entfallen ersatzlos, raw/CONTRACT.mds Routing-Tabelle beschreibt
      stattdessen den Shard, die Ignore-Kanarie wandert von incoming/documents/probe.pdf auf
      incoming/probe.pdf.

    MINOR, 4.8.0-beta.8 desselben Kandidaten — drei geprüfte Bedingungen: raw accept nimmt
    weiterhin Dateien aus incoming/<irgendwas>/ an, statt sie zu verweigern; fidelity/authority
    stehen nicht in required:; die zwei neuen Pflichtflags an raw accept sind eine
    Verhaltensänderung, aber dieselbe Einstufung, die #66s new source-Verweigerung im selben
    Kandidaten schon bekam. Kein --breaking, kein Migrationsdokument nötig — kein Bestand wird
    durch diesen Bump ungültig.

    Geändert: raw/CONTRACT.md, types/source.md, types/source.schema.yaml,
    tools/chemenu/commands/raw_cmd.py (Neufassung), tools/chemenu/commands/new_page.py
    (Capture-Feld-Pflicht), tools/chemenu/commands/touch.py (_capture_field_or_fail,
    Fill-once), tools/chemenu/type_resolver.py (get_capture_fields),
    tools/chemenu/commands/dist_cmd.py/docs_verify.py (Aufräumen), tools/chemenu/lint_core.py
    (neuer Befund), kb/CONTRACT.md (Obergrenzentabelle), tools/CONTRACT.md, .gitignore,
    instructions/bootstrap.md, instructions/wiki-ingest/SKILL.md (Schritt 1 und 6), zugehörige
    Tests.

    Der Backfill über den Bestand lief als eigener work/-Lauf (backfill-capture-fields,
    Commit 00220f8) hinterher, nach instructions/migrate-corpus.md und durch das
    Mass-Update-Gate: 29 Source-Seiten, nicht 31 wie zwischenzeitlich im Issue notiert — die
    höhere Zahl zählte INDEX.md und COLLECTION.md mit.

    Die Regel dieses Laufs war enger als die des laufenden Betriebs, weil ein nachgetragener
    Capture-Wert erschlossen ist und nicht erhoben: ein echter Wert nur dort, wo die Art des
    Artefakts ihn aus dem Material selbst festlegt, sonst unknown. Ergebnis: verbatim+reporting
    18 (16 Gesprächstranskripte, 2 Tracker-Exporte — beide wörtliche Mitschnitte, beide Protokoll
    statt Festlegung), secondhand+opinion 3 (LLM-Analysen), published+reporting 2,
    published+normative 1 (Karpathys Idea-File, das definierende Dokument seines eigenen
    Gegenstands), verbatim+normative 1 (das qmd-README, dessen Rohdatei ihre Treue selbst
    deklariert), unknown+normative 1, unknown+reporting 3.

    fidelity: unknown steht auf 4 der 29 Seiten, authority: unknown auf keiner. Die
    Asymmetrie ist der interessante Teil: wer für einen Gegenstand zuständig war, ließ sich überall
    aus dem Material beantworten — wie treu ein selbstverfasstes Cheat Sheet oder ein Anweisungsdokument
    „erfasst", nicht, weil der Enum für ein originär geschriebenes Artefakt keinen Wert hat. Das ist
    kein Backfill-Fehler, sondern genau die Grenze, die unknown markieren soll.

    Und der lint-Befund hat einen Fall — 67 sogar: nach dem Backfill melden 67 von 152 Seiten
    mit confidence_base mehr Konfidenz, als die Quellenlage trägt (47 gegen die 0.8-Grenze für
    reporting, 20 gegen die 0.6-Grenze für opinion). Das ist kein Fehlalarm und auch keine
    Nacharbeit dieses Eintrags: der Korpus ist zu gut der Hälfte aus Gesprächstranskripten kompiliert,
    und die Rubrik in kb/CONVENTIONS.md lässt Quellenzahl und Aktualität allein bis 0.95 laufen,
    während der Autoritätsterm der kleinste Summand ist. Ob daraus folgt, dass 67 Seiten überbewertet
    sind oder dass die 0.8-Grenze für einen selbstdokumentierenden Korpus zu eng ist, ist eine
    Entscheidung und keine Korrektur — sie hängt als Messung am Stub #60, der genau diesen Verdacht
    ohne Zahlen aufgeschrieben hatte. Die Transkripte wurden ausdrücklich nicht auf normative
    hochgestuft, nur damit der Report leiser wird.

    Schließt #67.

    source_type ist Instanzsache: Profilkatalog, Setup-Frage, evolve-subtypes-Instruction
    (#68, Paket C von vier — A ist #66, B ist #67, D ist #69): reine Doku- und Instruction-Arbeit,
    kein Korpus-Sweep.

    Befund: source_types sieben Werte (transcript, analysis, article, document, notes,
    tracker, unclassified) beschreiben diese Instanz, nicht den Stack — gegen drei
    hypothetische Zielinstanzen (Handball-Verein, Produktentwicklung, Pen-&-Paper) hat die Liste
    fast nichts gemeinsam, während fidelity/authority (#67) in allen vieren dieselben Werte
    bleiben. Architektonisch war das längst wahr (types/source.md trägt root: kb, dist export
    liefert es nur als .template), nur stellte nichts die Frage: instructions/kb-profiles.md
    riet im entities-Abschnitt „Adapt the area list first", sagte im sources-Abschnitt aber kein
    Wort zu source_type. Zweiter Befund, aus #66 mitgenommen: das unclassified-Fach bekam einen
    beratenden lint-Befund, aber nie eine Prozedur, es wieder zu leeren.

    Umgesetzt:

    • kb-profiles.mds sources-Abschnitt behandelt source_type jetzt wie entities seine Area-
      Liste: als das, was zuerst anzupassen ist, mit zwei ausformulierten Domänenprofilen
      (Handball-Verein, Pen-&-Paper) als Anschauung, und dem ausdrücklichen Gegenbeispiel
      fidelity/authority — die sind Stack-Vokabular und stehen nicht zur Wahl.
    • instructions/setup-instance.md Schritt 5 bekommt einen neuen Unterschritt: nach dem
      Anwendungsgebiet fragen, source_type-Vorschlag ableiten, Enum und layout: in derselben
      Bearbeitung setzen. Mit der Ansage, dass der Betreiber zum Setup-Zeitpunkt null Quellen hat und
      seine Taxonomie vor jedem Material rät — das Ergebnis ist ein Startpunkt, keine Festlegung, und
      unclassified bleibt in jedem Vorschlag erhalten.
    • Neue Instruction instructions/evolve-subtypes.md, manual: true: benennt die
      Weiterentwicklungsschleife, die werkzeugseitig schon vollständig existierte (Fach sehen →
      Wert samt layout: ergänzen → touch/move --reconcileindex rebuild/migrate verify),
      über alle drei Subtype-Achsen (entity_type, concept_type, source_type — alle drei tragen
      subtype_field: und layout:; comparison keins von beidem). Zwei Regeln im Body: Wert
      und Sweep sind untrennbar (ein deklarierter Wert ohne Seite lädt zum Raten ein — genau der
      notes-Fall aus #66), und eine Aufnahmeschwelle von ≥3 Seiten, mit spec/image aus #66 als
      Gegenbeispiel und einer benannten-Ausnahme-Klausel für Fälle wie tracker bei zwei Seiten.
      manual: true verhindert, dass eine Taxonomie-Änderung in einen laufenden Ingest hineinstolpert
      — erwähnt aus kb-profiles.md, setup-instance.md und kb/sources/COLLECTION.md, aus keinem
      Skill, keiner AGENTS.md, keiner CLAUDE.md verlinkt.
    • kb/sources/COLLECTION.md benennt evolve-subtypes.md an der unclassified/-Zeile.

    Die vom Vorbereitungs-Body übernommene, ursprünglich vierte Maßnahme entfiel: die
    corpus-policy.md-Floor-Ausnahme für unclassified steht dort bereits seit #66.

    MINOR, geprüft gegen den Drop-in-Test: eine Instanz kopiert instructions/ und types/
    über sich, nichts wird umbenannt oder entfernt, kein Kommando, kein Flag, kein
    maschinengelesenes Format. Kein --breaking, kein Migrationsdokument. 4.8.0-beta.9 desselben
    Kandidaten.

    Geändert: instructions/kb-profiles.md, instructions/setup-instance.md,
    instructions/evolve-subtypes.md (neu), kb/sources/COLLECTION.md.

    Schließt #68.

    instructions/CONTRACT.md § Writing an instruction: drei offene Fragen entschieden

    Drei Issues aus der #65-Analyse zeigten auf denselben Abschnitt. Alle drei
    enden dort, wo die Regel steht, nicht in einem Issue-Kommentar.

    Der Imperativ-Titel bindet eine Instruction, nicht ein SKILL.md (#71).
    Die Regel griff dem Wortlaut nach auf alle fünf Skills durch, deren H1
    Nomenphrasen sind. Geprüft gegen die Primärquelle: Anthropic normiert name
    und description und sagt zur Body-Überschrift nichts; die eigenen
    Beispiel-Skills heißen # PDF Processing, # BigQuery Data Analysis. Dazu
    das Sachargument — der H1 liegt auf keinem Retrieval-Pfad, weil über die
    Aufnahme eines Skills die description entscheidet, die ab Sessionstart im
    Kontext steht, während der Body erst beim Zugriff gelesen wird. In der Sitzung
    kam ein Beleg dazu, den keines der Issues kannte: der vendorierte
    commonplace-Korpus trägt dieselbe Imperativ-Titel-Regel, unabhängig
    entstanden, und macht im selben Absatz dieselbe Ausnahme („for promoted skills,
    the skill name is the title"). Die fünf Titel bleiben unverändert.

    Referenztiefe: Anthropics „one level deep" gilt gebündeltem Material (#72).
    Weg 2 der drei zur Wahl stehenden. Der Beleg für die Reichweite steht im
    vendorierten codex-skill-creator/SKILL.md: die Beispiele der Regel sind
    DOCX-JS.md, REDLINING.md, OOXML.md — alles Dateien im Skill-Bündel. Kein
    Skill dieses Repos hat heute eine solche Datei, die Regel bindet hier also
    wörtlich nichts. Für den Link von einem Skill auf einen repo-weiten Contract
    fallen die beiden Hälften der Frage auseinander: die Mechanik (Zweit-Hop wird
    womöglich nur angelesen) ist real und verzeichnisunabhängig, die Vorschrift ist
    für diesen Fall von Anthropic nicht belegt. Die geteilten Contracts bleiben
    geteilt — Invariante 8 hat sie dorthin gestellt, und § Frontload verlangt, dass
    ein Schritt ohne Vorkontext entscheidbar ist, nicht dass jede Regel an ihm
    wiederholt wird. Als Auflage bleibt das Billigere: ein Link sagt, was der
    Schritt aus der Datei braucht.

    Ob die Mechanik hier überhaupt beißt, wurde vor der Entscheidung im Eval-Aufbau
    nachgesehen, und die Antwort ist: nicht messbar. Die L2-Trajectory-Regeln lesen
    ausschließlich wikitool.call, gate.*, publish.commit und
    prompt.submitted — keine Dateizugriffe eines Agenten; auf Claude Code ist
    überhaupt kein Tool-Hook verdrahtet, ein head -100 hinterlässt also keine
    Spur. Die zweite Hälfte der Behauptung, was am Ende im Kontextfenster stand,
    erzeugt konstruktionsbedingt nirgends ein Event. Ein kausaler A/B-Vergleich
    bräuchte den L3-Runner, der entworfen und nicht gebaut ist. Das steht jetzt im
    Contract: eine Festlegung über Reichweite, keine Messung. Der Nebenfund — L2
    sieht auf dem primären Harness gar keine Tool-Calls — ist ein eigenes Issue
    wert und nicht Teil dieser Änderung.

    Wieviel Begründung ein Schritt tragen darf, ist jetzt messbar (#79). Der
    alte Satz („Keep reasoning out of the body […] keep only enough reasoning to
    decide edge cases") zog in zwei Richtungen, und die größte Instruction des
    Repos lebte in der Lücke. Neu sind eine Keep/Cut-Tabelle und zwei Tests:
    Substitution — die Passage streichen und den Schritt noch einmal lesen; rät
    ein Agent ohne Vorkontext jetzt, war es eine Entscheidungshilfe und sie bleibt,
    egal wie lang. Once — eine Entscheidungshilfe steht an dem Schritt, an dem die
    Entscheidung fällt, und an genau einem solchen (Invariante 8). Danach gemessen
    stand die source_type/Capture-Asymmetrie in wiki-ingest zweimal; sie steht
    jetzt einmal, in Schritt 1, und Schritt 6 trägt die Anweisung plus Verweis.
    Die beiden anderen in #79 genannten Stellen — Namenskollision in Schritt 1,
    ## Not Extracted in Schritt 6 — bestehen den Substitutionstest und bleiben.
    214 → 211 Zeilen; die Kürzung ist nicht der Zweck, die Eindeutigkeit ist es.

    PATCH, geprüft gegen den Drop-in-Test: eine Instanz kopiert
    instructions/ über sich, nichts wird umbenannt oder entfernt, kein Kommando,
    kein Flag, kein maschinengelesenes Format, und der Rückweg funktioniert
    genauso. Kein --breaking, kein Migrationsdokument.

    Geändert: instructions/CONTRACT.md (§ Writing an instruction, drei neue
    Unterabschnitte), instructions/wiki-ingest/SKILL.md (Schritte 1 und 6).

    Schließt #71, #72 und #79.

    Vier Befunde in der Skill-Prosa, ein Publish

    Der Rest der #65-Analyse, soweit er die fünf SKILL.md selbst betrifft. Vier
    Issues, fünf Dateien, kein Codeanteil.

    Die Hard Rule von wiki-status war falsch (#70). Sie sagte „read-only.
    Never writes, scaffolds, or modifies any file" — und Schritt 2 ruft lint,
    schreibt also einen Report, was Schritt 2 sogar selbst beschreibt. Ein Agent,
    der die Regel wörtlich nimmt, kann den Skill nicht ausführen; einer, der ihn
    ausführt, hat die stärkste Aussage des Dokuments gebrochen, bevor er Schritt 5
    erreicht. Das ist die teurere Sorte Widerspruch, weil die Hard Rule genau die
    Stelle ist, an der ein Konflikt entschieden wird. Sie lautet jetzt wie die von
    wiki-query — read-only gegenüber Wiki-Inhalt — und benennt den einen Write
    mitsamt Grund: reports/ ist gitignored und trägt keine Wiki-Seite. Schritt 5
    behauptet nicht mehr, es sei keine Datei geschrieben worden, sondern sagt, was
    mit der geschriebenen nicht passiert (Semantic Review bleibt leer, nichts
    wird ausgetragen — das ist wiki-lint Schritt 9). Der Decision Point „Never
    publishes — nothing was written" trägt jetzt den wahren Grund: unter kb/ hat
    sich nichts geändert, und der Report kann gar nicht in einen Commit geraten.

    wiki-ingest und wiki-lint bekommen einen Abhak-Block (#74). Anthropics
    Skill-Doku empfiehlt für „particularly complex workflows" eine Checkliste, die
    der Agent in die Antwort kopiert und mitführt. Zwölf Schritte fallen
    unzweifelhaft darunter. Der Ausschlag gibt aber nicht die Länge, sondern was
    still ausfällt: ## Not Extracted in Schritt 6, die Coverage-Prüfung in
    Schritt 10, die Lint-Kadenz in Schritt 12 — keiner davon erzeugt eine
    Fehlermeldung, wenn er ausbleibt.

    Die offene Frage des Issues — ob wiki-lint denselben Block bekommt — ist mit
    ja beantwortet: neun Schritte, davon 3-6 reines Judgment, und ein Lauf, der
    leise nur seine mechanische Hälfte gemacht hat, sieht aus wie ein
    vollständiger. Damit haben zwei von fünf Skills einen Block und drei nicht, und
    genau das wäre ohne festgeschriebenes Kriterium die nächste strukturelle
    Ungleichheit im Sinne von #78. instructions/CONTRACT.md § Writing an
    instruction trägt sie deshalb jetzt: ein Block, wenn ein Ablauf acht
    Schritte oder mehr hat und darin still ausfallende Schritte stehen. Beide
    Hälften nötig — ein langer Ablauf aus reinen Tool-Calls meldet seine Lücken
    selbst, weil der nächste Call ohne den vorigen scheitert. Der Abschnitt nennt
    die drei anderen Skills mit ihren Schrittzahlen, damit niemand aus Symmetrie
    einen vierten Block nachrüstet.

    wiki-query prüfte nicht, bevor es filete (#75). Die drei Kriterien
    (Synthese über mehrere Seiten, etwas noch nicht Dokumentiertes, wird wieder
    gefragt) standen im Filing-Schritt selbst, und der Skill darf mehrere Seiten
    je Sitzung anlegen — es gab also keine Stelle, an der jede geplante Seite
    einzeln gemessen wurde. Neuer Schritt 5 vor dem ersten new: Kandidaten
    benennen, jeden für sich gegen alle drei halten, ein Stapel wird nie als Stapel
    beurteilt. Wer durchfällt, wird nicht angelegt, sondern in der Antwort mit
    einem Satz genannt — der Nutzer kann ihn trotzdem verlangen. Der bisherige
    Filing-Schritt ist Schritt 6, log append Schritt 7, die Hard Rule zieht mit.
    Der Mass-Update-Gate-Hinweis bleibt, sagt aber jetzt dazu, dass er keine
    Ersatzprüfung ist: das Gate zählt Dateien und weiß nichts über Berechtigung,
    und ein Stapel unter der Schwelle ist von ihm nicht freigegeben, nur nicht
    angehalten worden. Dazu die session-setup.md-Zeile in derselben Form wie in
    den drei anderen — Schritt 7 läuft immer und der Filing-Pfad zieht new,
    xref add und die Rebuilds nach sich.

    Die Kommandolisten gingen mit den Schritten auseinander (#78).
    cite add fehlte in wiki-ingest und wiki-manage, obwohl beide es
    ausdrücklich vorschreiben; types describe fehlte in wiki-ingest, wo
    Schritt 6 die source_type-Werte daraus zieht; xref add fehlte in
    wiki-lint, wo Schritt 1 das Umlabeln einer schwachen Kante darauf stützt;
    publish fehlte in wiki-lint und wiki-query, wo je ein Decision Point es
    beim Namen nennt. In die andere Richtung: rm stand in wiki-lints Liste,
    ohne dass ein Schritt es begründet — die gefährlichere Richtung der Drift, weil
    rm Seiten löscht. Dazu log status (entscheidet den Trigger, gelaufen wird
    es von wiki-ingest) und die nie benutzten Flags lint --markdown und
    lint --json. Beide Streichungen stehen jetzt als Deliberately absent unter
    der Liste, mit Grund — sonst trägt sie jemand aus Vollständigkeit wieder ein.

    Nicht als Drift gezählt und bewusst gelistet geblieben: publish,
    log append, index rebuild und sources rebuild-index, wo ein Skill sie
    über publish-cycle.md delegiert. Ebenso xref remove in wiki-manage, das
    zum Unlinking-Fall gehört, den der Skill als Ganzes an page-lifecycle.md
    abgibt; auch das steht jetzt als Satz dort, nicht als stille Annahme.

    Der zweite Teil von #78 ist die Symmetrie: wiki-manage und wiki-status
    tragen jetzt einen Beispielblock wie die drei anderen. Fünf Skills mit
    demselben Aufbau sollten denselben Aufbau haben — ein fehlender Abschnitt liest
    sich sonst als Aussage („hier gibt es keine typischen Fälle"), die niemand
    gemeint hat.

    Die offene Frage aus #78 — ob instructions verify diesen Abgleich künftig
    selbst macht — ist mit ja beantwortet und als #83 ausgelagert. Trivial ist er
    nicht: die drei Ausnahmen oben (Delegation über publish-cycle.md, benannte
    Delegation an eine andere Instruction, „Deliberately absent") müsste ein Prüfer
    alle kennen, sonst meldet er bei jedem Lauf dieselben Stellen. Nur die dritte
    hat heute einen maschinenlesbaren Anker — den Absatz, den dieser Eintrag oben
    eingeführt hat.

    PATCH, geprüft gegen den Drop-in-Test: eine Instanz kopiert instructions/
    über sich, nichts wird umbenannt oder entfernt, kein Kommando, kein Flag, kein
    maschinengelesenes Format, und der Rückweg funktioniert genauso. Kein
    --breaking, kein Migrationsdokument.

    Geändert: instructions/wiki-status/SKILL.md, instructions/wiki-query/SKILL.md,
    instructions/wiki-ingest/SKILL.md, instructions/wiki-lint/SKILL.md,
    instructions/wiki-manage/SKILL.md, instructions/CONTRACT.md (§ Writing an
    instruction, neuer Unterabschnitt „When a skill carries a copy-in checklist").
    wiki-ingest bleibt mit 233 Zeilen unter Anthropics 500er-Schwelle.

    Schließt #70, #74, #75 und #78.

    Issue-Nummern in ausgelieferter Doku (#77): dist export lieferte
    Dateien aus, die im Fließtext auf Issue-Nummern dieses Trackers verwiesen —
    „flat since Gitea #67", „new source refuses without it (Gitea #66)". In einer
    verteilten Instanz zeigt das auf nichts. Der Leser kann den Verweis weder
    auflösen noch als unauflösbar erkennen, und eine Regel sieht damit so aus, als
    stütze sie sich auf einen Beleg, den niemand beibringen kann. Das Board liegt
    im Ursprungs-Repo, und instructions/dev/issue-tracking.md — die einzige Datei,
    die das überhaupt sagt — wird von dist export mit dem Rest von
    instructions/dev/ weggeschnitten. Gegenprobe zum eigenen Anspruch aus
    instructions/CONTRACT.md § „Writing an instruction": „self-contained enough
    for an agent with no prior context".

    Gemessen statt geschätzt: ein Export in ein leeres Verzeichnis, grep -rn '#[0-9]', ergab 43 Treffer in 16 Dateien außerhalb von tools/**/*.py
    raw/CONTRACT.md allein acht. Das Issue hatte zehn gelistet.

    Aufgelöst wurde nicht durch eine Markierung, sondern durch Umformulierung:
    die Nummer fällt weg, die Datierung geht in Worte. Aus „flat since Gitea
    #67" wird „flat since the addressing scheme dropped type directories", aus
    Pre-#67 files are not moved" wird „Files promoted under the old type
    directories are not moved
    ". Der Satz trägt sich damit selbst — es gibt keine
    repoweite Notation zu definieren und an genau einer Stelle zu halten
    (Invariante 8), und kein Leser von README.md muss AGENTS.md geladen haben,
    um sie aufzulösen. Rückverfolgbar bleibt es hier über git blame → Commit-
    Message; die tragen die Nummern ohnehin.

    Zwei Stellen, an denen der Zeiger der ganze Wert des Satzes war und in Worten
    nichts übrig geblieben wäre, stehen jetzt in einem
    <!-- dist:strip-start/end -->-Block: in instructions/CONTRACT.md (was ein
    Test der Kontextfenster-Behauptung kosten würde) und in EVALS.md (wo die
    Coverage-Lücken geschlossen werden). Im Dev-Repo sichtbar, im Export weg — die
    bestehende Konvention aus instructions/CONTRACT.md § instructions/dev/, hier
    zum zweiten Mal angewandt statt neu erfunden.

    docs verify prüft es jetzt — die offene Frage des Issues, mit Ja
    beantwortet. check_no_issue_references liest nicht den Arbeitsbaum, sondern
    den Text, den dist_cmd.build_plan() schreiben würde: dort leben ROOT_FILES,
    der instructions/dev/-Ausschluss und das .template-Rekeying schon, und der
    Text hat seine Marker-Blöcke bereits verloren. Deshalb ist ein Strip-Block
    automatisch exemptiert, ohne dass der Check ihn kennen müsste.

    Der Einwand aus instructions/dev/issue-tracking.md § „What no tool checks" —
    wikitool soll den Tracker nicht kennen — trägt hier nicht, und das ist die
    Grenze, die der Abschnitt jetzt selbst zieht: re.compile(r"#\d+") hat keinen
    Client, keine URL und keinen Begriff vom Zustand eines Issues. Der Check sieht
    eine Eigenschaft des Dokuments, nicht des Boards. Gemessen: null False
    Positives über den gesamten Export, weil Markdown-Anker aus Wortzeichen
    bestehen (](#gates) matcht nicht). Der erste Fund war prompt der Satz, den
    diese Sitzung selbst in tools/CONTRACT.md geschrieben hatte, um die Regel zu
    erklären.

    tools/**/*.py bleibt bewusst außen vor, mit ~90 Treffern in Docstrings und
    Kommentaren. Ein Code-Kommentar adressiert, wer die Zeile editiert, und das
    passiert ausschließlich im Ursprungs-Repo: dist export schneidet den
    stack-dev-Skill mit instructions/dev/ weg. Ein ausgeliefertes tools/ ist
    Laufzeit-Maschinerie, keine Lektüre. .gitignore und tools/.coveragerc sind
    aus demselben Grund nicht im Check — von Hand mitgezogen wurden sie trotzdem,
    sodass der Export heute in keiner Datei außerhalb .py eine Nummer trägt.

    MINOR, geprüft gegen den Drop-in-Test: kein Kommando, kein Flag, kein
    Dateiformat, keine Umbenennung; der Rückweg funktioniert unverändert, die alte
    Version führt den Check schlicht nicht aus. Kein --breaking, kein
    Migrationsdokument. Eine Konsequenz ist zu kennen: der Check liest auch die
    instanzeigenen kb/CONVENTIONS.md und kb/<collection>/COLLECTION.md, weil ein
    Export sie als .template mitnimmt. Eine Instanz, die dort ihre eigene
    Ticket-Nummer zitiert, bekommt beim nächsten docs verify ein Finding. Das ist
    kein Fehlalarm — ein Export dieser Instanz würde den Verweis weitergeben —
    aber es ist neu.

    Geändert: tools/chemenu/commands/docs_verify.py (neuer Check plus
    shipped_prose()), tools/chemenu/tests/test_docs_verify.py (sechs Tests:
    sauberer Baum, präparierte Datei, Anker-Nicht-Treffer, .py außerhalb des
    Scans, Strip-Block unsichtbar, verify bricht ab), tools/CONTRACT.md
    (Kommandotabelle und Fehlerkontrakt-Zeile), tools/README.md,
    instructions/dev/issue-tracking.md (neuer § Citing an issue in the repo, und
    § What no tool checks zieht die Grenze zwischen „was dieses Repo über den
    Tracker schreibt" und „dem Tracker selbst"), sowie die 16 Doku-Dateien:
    raw/CONTRACT.md, kb/CONTRACT.md, tools/CONTRACT.md, types/type-spec.md,
    types/source.schema.yaml, kb/sources/COLLECTION.md,
    kb/concepts/COLLECTION.md, instructions/wiki-ingest/SKILL.md,
    instructions/evolve-subtypes.md, instructions/bootstrap.md,
    instructions/kb-profiles.md, instructions/mcp-read-server.md,
    instructions/CONTRACT.md, README.md, EVALS.md, INSTALL.md,
    docs/pipeline-rationale.md, .gitignore, tools/.coveragerc.

    Schließt #77.

    Warum das eine MAJOR ist, obwohl kb/ unberührt bleibt (#73, #76): Anthropics
    Skill-Authoring-Doku verlangt für Referenzdateien über 100 Zeilen ein
    Inhaltsverzeichnis, damit ein Agent, der eine solche Datei nur mit head -100
    anliest, trotzdem die volle Abschnittsübersicht sieht — dieselbe Vorschau-Mechanik,
    die #72 schon für die Referenztiefe als real anerkannt hat. Ein von Hand
    gepflegtes Inhaltsverzeichnis wäre die nächste Drift-Quelle; also ist es jetzt eine
    dritte generierte Region neben xrefs und cites (<!-- wikitool:toc --> ...
    <!-- /wikitool:toc -->, tools/chemenu/toc.py), erzeugt und geprüft wie jede
    andere abgeleitete Kopie. wikitool docs toc [--apply] schreibt sie; docs verify
    prüft jetzt, dass sie auf jeder Datei aktuell ist, die AGENTS.md, ein
    Stage-/Collection-Contract oder die flache instructions/**.md-Form abdeckt (25
    Dateien in diesem Repo, instructions/dev/ eingeschlossen — strukturell dieselbe
    Dateiform, nur von dist export ausgenommen). Der Umfang ist berechnet, nie eine
    Handliste: er folgt AGENTS.md § File naming, nicht einer Link-Traversierung ab den
    fünf Content-Skills, und schließt types/<name>.md-Einzelspecs bewusst aus — die
    laufen über wikitool types describe, das den Inhalt neu rendert statt die Datei
    roh auszugeben, sodass die Vorschau-Mechanik dort gar nicht greift.

    Das ist grenzüberschreitend, weil docs verify damit eine neue Pflichtprüfung
    über bestehenden Inhalt bekommt: eine Instanz mit einer eigenen
    instructions/*.md-Datei über 100 Zeilen, an der nichts geändert wurde, sieht
    docs verify nach reinem Tool-Update neu fehlschlagen, bis einmalig
    wikitool docs toc --apply läuft und der Diff committet wird — derselbe
    Bruchtyp wie ein verschärftes Type-Spec-Pflichtfeld. Keine Migration nötig, weil
    kein kb/-Inhalt betroffen ist; der einmalige docs toc --apply-Lauf ist der
    volle Reparaturweg.

    Zwei Nebenfunde beim Bauen der TOC-Regel, beide vor dem Bump behoben, weil sie
    sonst denselben Bump falsch aussehen ließen: docs verifys
    ISSUE_REFERENCE_RE (#\d+) hielt numerierte-Schritt-Anker wie
    #2-fix-the-fidelity-before-writing-a-word für Issue-Zitate — die Regel nahm
    bisher an, dass ein Anker immer mit einem Buchstaben beginnt, was für
    nummerierte Überschriften (instructions/capture-session.md) nicht mehr gilt;
    behoben durch einen Lookbehind, der genau die ](#...-Linkfragment-Form
    ausschließt, ohne ein echtes (#66)-Zitat zu übersehen. Und
    instructions_cmd.dev_only_forbidden_references prüfte mit blankem
    name in text: ein TOC-Anker wie #where-stack-development-happens
    (instructions/private-instance.md) enthält „stack-dev" als reine Teilzeichenkette,
    ohne den Skill zu meinen — behoben durch eine wortgrenzengebundene
    Regex-Suche.

    Geändert: tools/chemenu/toc.py (neu), tools/chemenu/commands/docs_verify.py
    (check_toc_regions, docs toc-Kommando, ISSUE_REFERENCE_RE-Lookbehind),
    tools/chemenu/commands/instructions_cmd.py
    (dev_only_forbidden_references wortgrenzengebunden), tools/CONTRACT.md
    (docs toc-Zeile), tools/chemenu/tests/test_toc.py (neu, 15 Tests),
    tools/chemenu/tests/test_docs_verify.py (drei neue Tests: nummerierter Anker,
    geklammertes echtes Zitat, TOC-Region auf dem realen Baum),
    tools/chemenu/tests/test_instructions_cmd.py (ein neuer Test für die
    Teilzeichenketten-Kollision), sowie die 25 Referenzdateien, die jetzt eine
    TOC-Region tragen: AGENTS.md, kb/CONTRACT.md, kb/CONVENTIONS.md,
    kb/concepts/COLLECTION.md, raw/CONTRACT.md, tools/CONTRACT.md,
    types/type-spec.md, instructions/CONTRACT.md, instructions/gates.md,
    instructions/setup-instance.md, instructions/private-instance.md,
    instructions/link-taxonomy.md, instructions/kb-profiles.md,
    instructions/ingest-large-tree.md, instructions/capture-session.md,
    instructions/claude-code-model-selection.md, instructions/german-terminology.md,
    instructions/evolve-subtypes.md, instructions/mcp-read-server.md,
    instructions/migrate-corpus.md, instructions/migrations/3.0.0-authoring-conventions.md,
    instructions/migrations/4.0.0-link-taxonomy.md, instructions/dev/issue-tracking.md,
    instructions/dev/testing-conventions.md, instructions/dev/version-parts.md.

    Zusätzlich, unabhängig davon (#76): instructions/session-setup.md § Scope und
    instructions/gates.md behaupteten, die Budget-Ausnahme richte sich danach, ob
    ein Kommando das Wiki verändert. Tatsächlich zählt run_budget.py eine feste
    Allowlist (SKIP_COMMANDS/SKIP_COMMAND_PATHS) — lint schreibt nur ins
    gitignorte reports/, sieht also lesend aus, steht aber nicht auf der Liste und
    zählt wie jedes mutierende Kommando. Beide Dateien verweisen jetzt auf die Liste
    in tools/CONTRACT.md, statt sie mit einer falschen Faustregel zu umschreiben.
    Kein Versionsbezug — reine Prosa-Korrektur, im selben Bump mitgeführt.

    Nachgezogen in -beta.3: dist export erzeugt die TOC-Region jetzt nach
    dem Marker-Strip neu. Ein <!-- dist:strip-start/end -->-Block kann eine ganze
    Sektion umschließen — der in AGENTS.md umschließt ## Developing this stack —,
    sodass die ausgelieferte Datei eine Überschrift weniger hat, als das im
    Arbeitsbaum erzeugte Inhaltsverzeichnis auflistet. Die frische Instanz wäre
    damit beim allerersten docs verify über eine Datei gefallen, die niemand
    angefasst hat. Gefunden hat das die CI im Export-Replay („The distribution works
    as a fresh instance"), nicht pytest und nicht docs verify im Arbeitsbaum —
    beide sehen den gestrippten Text nie. Der Regressionstest sitzt jetzt in
    test_dist_cmd.py.

    Nachgezogen in -beta.2, weil docs verify die eigene Dokumentationstreue nur
    für die Existenz einer Kommandozeile prüft, nicht für deren Inhalt: die
    docs verify-Zeile in tools/CONTRACT.md nennt jetzt die TOC-Prüfung, die
    Fehlerkontrakt-Tabelle bekommt die fehlende docs toc-Zeile (Schritt 3 in
    tools/README.md § Adding a command verlangt beide Tabellen, geprüft wird nur
    eine), und tools/README.md § Adding a command Schritt 5 nannte als
    --major-Kriterium „wenn bestehender Inhalt migriert werden muss" — was
    instructions/dev/version-parts.md ausdrücklich verneint und was dieser Bump
    selbst widerlegt: grenzüberschreitend mit --no-migration.

    Schließt #73, #76.

    Konfidenz-Mechanismus ersatzlos entfernt (schließt #60). confidence,
    confidence_base, der Zeit-Decay und die Konfidenz-Rubrik sind aus Schema,
    Kommandos (confidence decay/init-base, touch --confidence-base), Lint
    (confidence_exceeds_source_standing), Suche (--sort -confidence,
    Konfidenzspalte) und jeder Doku-Stelle entfernt, die sie erwähnte.

    Der Mechanismus wurde gemessen, nicht nur für unschön befunden: 43 der 152
    betroffenen Seiten trugen nie mehr als den Schema-Vorgabewert 0.5, der Decay
    hat seit seiner Einführung keinen einzigen Wert bewegt (confidence_decay.py
    übersprang concept_type: decision, touch --confidence-base zog das
    abgeleitete Feld nie mit), und kein Konsument im Stack hing außer über
    corpus_diff.STRUCTURAL_FIELDS — eine Abhängigkeit von der Existenz des
    Feldes, nicht von seinem Wert — überhaupt an ihm. Vier Entwürfe für eine
    Reparatur (Rubrik nachjustieren, Quellenautorität anheben, aus
    authority × fidelity berechnen, zwei Schubladen für Reifegrad und
    Volatilität) scheiterten an denselben Messwerten. Die vollständige Studienlage
    steht in #85, wo das Thema als zurückgestellt geführt wird, nicht als
    verworfen.

    An die Stelle tritt nichts Neues: kb/CONVENTIONS.md bekommt eine
    Prosa-Hedging-Regel — nach Quellenlage hedgen statt nach Schwellenwert, siehe
    kb/CONVENTIONS.md § Hedging — und die Arbeitsliste ersetzt
    --field 'confidence<0.6' durch Prädikate auf tatsächlich aufgezeichneten
    Feldern (--field '!sources', --field provenance=general). Mit dem Feld
    stirbt auch confidence_exceeds_source_standing, der einzige automatische
    Abgleich zwischen einer Seite und der Standing ihrer Quellen; ein Ersatz ohne
    Zahl ist als Kandidat in #85 vorgemerkt, aber bewusst nicht Teil dieses Pakets.

    version bump bekommt dabei ein neues Flag, --migration-required: der
    laufende Kandidat hatte in einem früheren Bump --no-migration erklärt, und
    dieses Paket macht die Erklärung falsch. Die **Migration:** none required-Zeile ist maschinengeschrieben (Invariante 1 verbietet den
    Handgriff), und bislang gab es keinen Weg, sie zurückzunehmen, sobald ein
    späterer Bump doch eine Migration braucht. Das Flag entfernt die Zeile
    stattdessen und verlangt ein Migrationsdokument, das die neue Basisversion
    referenziert, bevor es das tut.

    Was das für eine bestehende Instanz bricht, steht in der **Breaking Change:**-Zeile oben. instructions/migrations/5.0.0-confidence-removal.md
    ist der mechanische Strip der zwei Felder über den betroffenen Korpus,
    mitsamt der Invariante, die ein automatisierter Lauf einhalten muss (kein
    modified:-Bump, byte-identischer Body, unveränderte Referenzarrays).

    Nachgezogen in -beta.6: instructions/dev/version-parts.md Schritt 6
    beschreibt den Rücknahmepfad jetzt selbst. tools/CONTRACT.md führte das neue
    Flag bereits, aber die Instruction, die eine Sitzung vor einem Bump liest,
    kannte den Fall nicht — dieselbe Sitzung ist genau darüber gestolpert. Der
    Abschnitt nennt auch, warum ihn nichts meldet: die Zeile ist
    maschinengeschrieben, docs verify genügt ihr bloßes Vorhandensein, und die
    Eskalationsprüfungen laufen nur auf dem Bump, der die Grenze zuerst
    überschreitet.

    Nachgezogen in -beta.7: instructions/wiki-status/SKILL.md trägt jetzt den
    session-setup.md-Verweis, den die anderen vier Content-Skills längst haben.
    Der Skill ruft in Schritt 2 lint auf, und lint steht nicht auf der
    Ausnahme-Allowlist in run_budget.py — er zählt wie jedes mutierende
    Kommando. Ohne exportierte WIKITOOL_SESSION_ID fällt die Zählung auf
    getppid() zurück, die Sitzung erbt also den Stand irgendeiner fremden Shell.
    Bis -beta.6 war das Fehlen des Verweises durch die falsche Regel gedeckt, die
    session-setup.md § Scope selbst aufstellte („verändert das Wiki"); seit sie
    die tatsächliche Allowlist nennt, ist es schlicht ein Loch.

    Die Alternative — lint durch etwas Befreites ersetzen und wiki-status
    wirklich budgetfrei machen — scheidet an der Sache aus: der Skill liest aus dem
    Report die Graph-Auswertung (Broken Links, Orphans, Most-Linked Pages,
    uncovered raw files), und kein befreites Kommando liefert die. doctor prüft
    die Installation, nicht den Korpus; search ist Retrieval. Ein wiki-status
    ohne lint wäre kein leichterer Skill, sondern ein leerer.

    Damit gilt über alle fünf Content-Skills dieselbe Aussage: ein Skill verlinkt
    session-setup.md genau dann, wenn er mindestens ein nicht-befreites
    wikitool-Kommando aufruft. Geprüft wird sie nicht — instructions verify
    kennt weder die Kommandolisten der Skills noch die Allowlist. Das wäre ein
    eigener Schnitt.

    Nachgezogen in -beta.8: CLAUDE.md importierte bislang USER.md, SOUL.md,
    ENVIRONMENT.md und instructions/claude-code-model-selection.md zusätzlich
    zu AGENTS.md — eine Harness-Drift, denn dieselben drei
    Personalisierungsdateien werden auf den anderen drei Harnesses (Codex CLI,
    Copilot, Vibe) allein durch AGENTS.mds eigene Anweisung gelesen, nie
    injiziert. CLAUDE.md importiert jetzt nur noch AGENTS.md; die Bedingung
    „if the runtime has not already injected them" in AGENTS.md §
    Personalization entfällt, weil kein Runtime mehr injiziert.

    instructions/claude-code-model-selection.md ist entfernt und als
    docs/model-and-effort-selection.md neu geschrieben, in Empfehlungsstimme
    statt als Instruktion: die Datei beschrieb überwiegend Handlungen, die eine
    Sitzung nicht selbst ausführen kann (das eigene Modell, /code-review-Stufen),
    und wurde im ganzen Baum nur von den beiden dev-only Skills stack-dev und
    stack-close referenziert, deren Links jetzt dorthin zeigen. Eine
    bestehende Instanz behält die entfernte Datei als Überbleibsel
    , bis sie
    wikitool dist upgrade --prune laufen lässt oder die Datei von Hand löscht —
    instructions verify meldet sie sonst neu als verwaist.

    AGENTS.md §§ Personalization, Environment und File naming sind an den
    Stellen gekürzt, die eine zweite Kopie einer Regel waren, die
    docs/ownership-and-templates.md oder eine Invariante schon trägt; § File
    naming verlinkt jetzt alle vier docs/-Seiten namentlich, was vorher
    nirgends geschah. USER.md und SOUL.md verlieren an derselben Stelle
    Rahmen- bzw. Herkunftsprosa, die USER.md.template bzw. ein Kommentar in
    SOUL.md selbst schon trägt.

    Telemetrie-Default nach Installationsform (#55): chemenu.telemetry.writer.enabled() war
    eine Zeile - immer an, WIKI_TRACE=0 das einzige Opt-out. Richtig für dieses Repo, dessen
    Traces das Messinstrument sind, mit dem der Stack sich selbst bewertet, aber die falsche
    Voreinstellung für eine ausgelieferte Instanz: dort hat niemand Telemetrie bestellt, und
    niemand liest EVALS.md, bevor die erste Datei geschrieben ist. Dazu kam eine zweite Lücke:
    keine Mengenbegrenzung irgendeiner Art - reports/telemetry/<session>/trace.jsonl wächst,
    solange die Instanz läuft, und nichts räumt je etwas weg.

    tools/chemenu/telemetry/policy.py (neu) löst jetzt beides an einer Stelle, gekeyt auf den
    aufgelösten Root, damit wikitool doctor, der Writer und der MCP-Server-Start-Guard dieselbe
    Antwort für denselben Checkout geben. Ein Git-Clone dieses Repos bleibt beim alten Verhalten
    (an, WIKI_TRACE=0 schaltet ab); eine per dist export ausgelieferte Instanz startet ab jetzt
    mit Telemetrie aus - erkannt an der ohnehin vorhandenen, maschinengeschriebenen
    .wikitool-release.json (Invariante 1). Wer sie dort anschalten will, legt eine
    .wikitool-telemetry.json an (pro Checkout, gitignored, kein .template - wie
    .wikitool-remotes.json); WIKI_TRACE überschreibt weiterhin beide Richtungen und schlägt die
    Datei.

    Zwei unabhängige, fail-silent durchgesetzte Mengendeckel greifen in beiden Installationsformen:
    ein Byte-Deckel pro Session-Trace (Default 5 MiB, ein stat vor jedem Append) und eine
    Retention über die Anzahl der Session-Verzeichnisse (Default 250). Die Retention reserviert den
    Platz der gerade entstehenden Session, statt sie mitzuzählen - sonst pendelt der Bestand
    dauerhaft bei keep+1 statt bei keep, weil jeder Lauf immer nur das räumt, was der vorige
    Lauf über dem Limit gelassen hat. Am Byte-Limit schreibt ein weiterer Aufruf nichts mehr außer
    einem einmaligen telemetry.limit-Event, per Exclusive-Create auf eine .limit-Sentinel-Datei
    ausgelost - derselbe Ein-Schreiber-Trick wie beim session.start-Header, für den Fall, dass
    mehrere Prozesse gleichzeitig auf denselben Trace schreiben. Eine Retention-Runde löscht
    ausschließlich trace.jsonl/.limit der überzähligen Verzeichnisse und rmdirt nur, wenn
    danach leer - nie rmtree, aus demselben Grund wie bei upstream merge (#30):
    reports/telemetry/ hält lokale, nicht rekonstruierbare Daten, die eval score liest.

    wikitool doctor bekommt einen neuen telemetry-Check (an/aus, warum, Menge gegen beide
    Deckel, nie FAIL, wie publish-remotes und environment). Der MCP-Server-Start-Guard
    (check_trace_destination) las WIKI_TRACE bisher selbst statt den Writer zu fragen - eine
    zweite Kopie derselben Regel, die Invariante 8 verletzte, bevor diese Änderung sie schließt; er
    fragt jetzt dieselbe Policy. instructions/setup-instance.md bekommt einen neuen
    Entscheidungspunkt (Schritt 10).

    MINOR, kein neuer Boundary-Crossing: additiv in beide Richtungen - eine bestehende Instanz
    kopiert die neue Maschinerie über sich und hört still auf zu schreiben, ohne Hand-Arbeit oder
    Migration; die alte Version zurücklegen stellt den alten Default wieder her, weil sie die neue
    Datei und die neuen Variablen schlicht ignoriert. Der Kandidat trägt seine --breaking-Zeile
    bereits aus einem früheren Bump (Confidence-Entfernung); diese Änderung fügt keine neue hinzu.

    Geändert: tools/chemenu/telemetry/policy.py (neu), tools/chemenu/telemetry/writer.py,
    tools/chemenu/telemetry/schema.py, tools/chemenu/commands/doctor.py,
    tools/chemenu/mcp/server.py, tools/chemenu/config.py, tools/chemenu/version.py,
    .gitignore, tools/chemenu/tests/conftest.py und die Telemetrie-/Doctor-/MCP-Server-Tests,
    .gitea/workflows/ci.yml, EVALS.md, INSTALL.md, INSTALL-MCP.md, reports/CONTRACT.md,
    tools/CONTRACT.md, instructions/setup-instance.md. Schließt #55.

    tools/CONTRACT.md beschrieb raw accept noch vor #67: Typverzeichnisse statt Datums-Shard,
    kein --fidelity/--authority, Stem-Eindeutigkeit "at raw/<type>/ level" statt global. Vier
    Zellen (beide raw accept-Zeilen der Kommandotabelle, beide im Fehlerkontrakt) waren an #67
    vorbeigeschrieben worden - raw/CONTRACT.md selbst war korrekt, docs verifys check_commands
    prüft nur Kommandonamen gegen die Tabelle, nicht deren Prosa gegen den Code. Reine
    Doku-Korrektur, kein Verhalten geändert. Schließt #89.

    Der MCP-Server bekommt ein sechstes, optionales Tool: submit, ein Schreibpfad für Dokumente
    von einem Aufrufer, der nicht dieses Terminal ist. #19s Eigenschaft "kein Tool schreibt" galt
    strukturell - nichts unter chemenu.commands war importierbar - und diese Formulierung wird mit
    einem echten Schreibpfad falsch. Die tragfähige Ersatzformulierung ist eine Positiv-Liste
    statt einer Abwesenheit: der Serverprozess darf in genau ein Verzeichnis schreiben,
    mcp-upload/, erzwungen durch eine einzige Funktion (chemenu.upload._write_atomic_within),
    die jeden aufgelösten Zielpfad gegen dieses eine Verzeichnis prüft - Tests decken ..,
    absolute Pfade und einen Symlink, der aus dem Verzeichnis hinausführt. Die alte
    Abwesenheitseigenschaft bleibt daneben unverändert bestehen: die Reviewer-Kommandos
    (upload accept/upload reject) liegen unter chemenu.commands und sind vom Server aus nicht
    erreichbar.

    Zwei Stufen vor raw/, zwei verschiedene Grenzen. mcp-upload/<id>/ hält Material, das
    niemand geprüft hat; wikitool upload accept <id> --confirm <token> befördert es nach
    incoming/, wo es sich nicht mehr von einer lokal abgelegten Datei unterscheidet und
    wiki-ingest Schritt 1 unverändert greift. Ohne Token verweigert upload accept mit Exit
    42
    - der vierte Gate des Stacks, Upload Review Gate, gleiche Form wie der
    Mass-Update-Gate: ein Token, der Id, Dateiname, Größe, Sha256 und Einreicher digestet, wird
    also ungültig, sobald sich das Manifest ändert. wikitool upload reject <id> --reason "<warum>"
    braucht keinen Gate - Ablehnen braucht keine Freigabe, nur Annehmen tut das - und löscht das
    Material, behält aber Grund und Sha256 im append-only mcp-upload/ledger.jsonl.

    Die Einreicher-Identität kommt ausschließlich aus einem HTTP-Header, nie aus einem
    Tool-Argument.
    identity_header (Default X-Forwarded-User) in .wikitool-upload.json
    nennt den Header; fehlt er auf der Anfrage, wird ohne jeden Schreibvorgang verweigert - eine
    unzurechenbare Einreichung ist damit unmöglich, nicht nur unerwünscht. Das Manifest hält neben
    submitter auch submitter_source (den Headernamen), damit der Datensatz sagt, worauf die
    Behauptung ruht, statt sie als Tatsache zu führen. Die Middleware muss den Header selbst setzen
    und eine vom Client mitgeschickte Kopie verwerfen - eine Deployment-Pflicht, dokumentiert in
    INSTALL-MCP.md Schritt 5 und instructions/ingest-queue.md, die der Prozess selbst nicht
    erzwingen kann.

    .wikitool-upload.json ist ein struktureller Opt-in, nicht bloß eine Konfiguration. Fehlt
    die Datei, wird das submit-Tool gar nicht erst registriert - anders als bei
    .wikitool-remotes.json, wo Abwesenheit "unbeschränkt" heißt, heißt sie hier "der Schreibpfad
    existiert nicht". Eine defekte Datei ist ein Startfehler des Servers (ValidationError beim
    Aufbau) und ein FAIL in wikitool doctors neuem upload-intake-Check - nie "keine
    Beschränkung". Weitere Schutzschichten in chemenu/upload.py: eine Größenprüfung auf der
    base64-Länge vor dem Dekodieren (mit einem Toleranzband von 2 Bytes für Padding, damit sie
    keine an der Grenze liegende, legitime Einreichung fälschlich ablehnt - der Nachdekodier-Check
    bleibt die exakte Durchsetzung), eine Endungs-Positivliste, ein rollierendes
    24-Stunden-Kontingent pro Einreicher (Anzahl und Bytes, aus dem Ledger berechnet, nie aus
    Verweigerungen), und eine Ablehnung doppelter Inhalte, solange die erste Einreichung noch
    wartet - unter Nennung der wartenden Id.

    incoming/ bleibt komplett unberührt als zweite, unabhängige Grenze. Ein Unterverzeichnis
    dort wird seit #67 toleriert und ignoriert, sodass eine Fremdeinreichung darunter still
    promotierbar gewesen wäre - deshalb ein eigenes Top-Level-Verzeichnis mcp-upload/, gitignored,
    ohne .gitkeep (die Schreibprimitive legt es selbst an). docs_verifys Ignore-Kanarien prüfen
    jetzt auch mcp-upload/probe.pdf und .wikitool-upload.json.

    MINOR, kein neuer Boundary-Crossing: additiv in beide Richtungen geprüft - eine bestehende
    Instanz kopiert die neue Maschinerie über sich und bekommt ein neues, standardmäßig
    unregistriertes Tool, ohne Hand-Arbeit oder Migration; kein .wikitool-kb.json-Feld, kein
    Type-Spec, kein umbenanntes Kommando oder Flag, kein geändertes Dateiformat. Die alte Version
    zurücklegen verliert nichts - der Schreibpfad existiert dort schlicht nicht. Der Kandidat trägt
    seine --breaking-Zeile bereits aus einem früheren Bump (Confidence-Entfernung); diese Änderung
    fügt keine neue hinzu.

    Geändert: tools/chemenu/upload.py (neu), tools/chemenu/commands/upload_cmd.py (neu),
    tools/chemenu/tests/test_upload.py (neu), tools/chemenu/tests/test_upload_cmd.py (neu),
    tools/chemenu/mcp/server.py, tools/chemenu/tests/test_mcp_server.py,
    tools/chemenu/commands/doctor.py, tools/chemenu/tests/test_doctor.py,
    tools/chemenu/commands/docs_verify.py, tools/chemenu/cli.py, tools/chemenu/config.py,
    .gitignore, tools/CONTRACT.md, AGENTS.md, instructions/gates.md,
    instructions/mcp-read-server.md, instructions/wiki-ingest/SKILL.md,
    instructions/ingest-queue.md (neu), raw/CONTRACT.md, docs/why-gates-are-code.md,
    INSTALL-MCP.md, README.md. Schließt #32.

    AGENTS.md behauptete, docs verify prüfe die Contract-Prosa selbst - das stimmt nicht (#90).
    check_cli_readme() prüft nur, ob der gebacktickte Pfad einer Tabellenzeile registriert ist, nie
    den Rest der Zelle: eine ausgetauschte Kommandobeschreibung oder ein neues --flag bleiben
    unsichtbar, und TABLE_CELL_RE läuft über das ganze Dokument statt über eine abgegrenzte
    "Kommandotabelle". AGENTS.md § Changelog zählte trotzdem "command tables, contracts, ignore
    canaries" als maschinell geprüft auf und grenzte die Sitzungspflicht auf README.md/EVALS.md/
    tools/README.md ein - tools/CONTRACT.md und jedes <stage>/CONTRACT.md standen damit auf
    keiner Liste, die je jemand nachzieht.

    Der Absatz nennt jetzt nur noch, was docs verifys eigene Zeile in tools/CONTRACT.md
    tatsächlich auflistet (Verweis statt Kopie), und macht jede Zellenprosa - Kommandotabelle,
    Fehlerkontrakt, Stage-Contract - ausdrücklich zur Sitzungsarbeit, neben den drei README-artigen
    Dateien. Die Docstring von check_cli_readme() behauptet nicht mehr, sie prüfe
    "tools/CONTRACT.md's command table" - sie beschreibt jetzt, dass TABLE_CELL_RE das ganze
    Dokument scannt und nur den gebacktickten Pfad liest, nie die restliche Zelle.

    Neue Instruction instructions/dev/doc-pull-through.md
    (dev-only) listet je berührter Fläche, welches Dokument eine Behauptung darüber trägt: beide
    Tabellen in tools/CONTRACT.md, der berührte <stage>/CONTRACT.md, AGENTS.md bei
    verschobener Regel/Gate/Invariante, die README-artigen Dateien, docs/ bei verschobener
    Begründung. stack-dev/SKILL.md bekommt dafür einen neuen Schritt 5 zwischen Versionsbump und
    Verify/Publish (jetzt Schritt 6) - ein Satz plus Link, die Liste bleibt in der Instruction; die
    Katalog-Liste in Schritt 2 und stack-close/SKILL.md Schritt 3 nennen die neue Instruction bzw.
    die Contracts jetzt ebenfalls. version-parts.md und testing-conventions.md korrigieren dabei
    zwei schon vorher falsche Schrittverweise auf stack-dev (Schritt 3 -> 4, Schritt 4 -> 6),
    gefunden beim Nachziehen der Umnummerierung.

    MINOR, kein neuer Boundary-Crossing: additiv und drop-in in beide Richtungen - eine
    bestehende Instanz kopiert die neue Instruction und die korrigierte Prosa über sich, ohne
    Migration oder Hand-Arbeit; kein Feld, kein Kommando, kein Flag ändert sich. Der Kandidat trägt
    seine --breaking-Zeile bereits aus einem früheren Bump; diese Änderung fügt keine neue hinzu.

    Geändert: AGENTS.md, tools/chemenu/commands/docs_verify.py,
    instructions/dev/doc-pull-through.md (neu), instructions/dev/stack-dev/SKILL.md,
    instructions/dev/stack-close/SKILL.md, instructions/dev/version-parts.md,
    instructions/dev/testing-conventions.md. Verifiziert: tools/wikitool docs verify,
    tools/wikitool instructions verify (nach instructions sync), volle pytest-Suite (1185
    passed). Schließt #90.

    docs verify prüfte § Commands und § Error contracts in tools/CONTRACT.md als einen Topf
    (#91).
    TABLE_CELL_RE sammelte das erste gebacktickte Wort jeder Tabellenzeile über das ganze
    Dokument, ohne Abschnittsgrenze - eine aus § Commands gelöschte Zeile fiel nicht auf, solange
    derselbe Name noch in § Error contracts stand, und § Error contracts wurde gegen nichts
    erzwungen. Gemessen am Baum vor diesem Fix (55 registrierte Kommandos): § Commands war
    vollständig, § Error contracts fehlten zehn Zeilen - budget reset, instructions list,
    instructions verify, log append, migrate status, sources rebuild-index, sources trace,
    types describe, upload show, version notes - und docs verify blieb grün.

    check_cli_readme() liest die beiden Tabellen jetzt über ihre ##-Überschrift ab (neue
    section_text()), unabhängig voneinander und je Tabelle in beide Richtungen; fehlt eine der
    beiden Überschriften, meldet die Funktion das explizit statt stillschweigend auf "ganzes
    Dokument" zurückzufallen. Ursache der zehn Lücken war überwiegend eine zweite, bisher unsichtbare
    Untugend: mehrere Kommandos teilten sich in § Error contracts eine Zeile der Form "a / b" -
    nur das erste Backtick-Wort einer Zeile zählte je als "dokumentiert", der Rest der Gruppe war für
    den (ungeprüften) Check unsichtbar. Behoben, indem jede betroffene Gruppe in eigene Zeilen mit
    eigenem, gegen den Code nachgesehenem Inhalt aufgeteilt wurde - dabei fielen nebenbei drei
    sachlich falsche Zeilen auf: sources coverage, types list, instructions list und budget status schlagen nie fehl, die alte gemeinsame Zeile behauptete das Gegenteil, weil sie das
    Verhalten des jeweils benachbarten Kommandos mit übernahm. Eine echte Formatierungslücke kam
    dazu: log appends Zeile stand im Quelltext ohne Zeilenumbruch hinter der von index rebuild /
    sources rebuild-index verschmolzen - für Menschen als Tabelle kaum lesbar und für die
    zeilenanfang-verankerte Regex unsichtbar.

    MINOR, kein neues Boundary-Crossing: additiv und in beide Richtungen drop-in - eine
    bestehende Instanz kopiert docs_verify.py und tools/CONTRACT.md über sich, ohne Hand-Arbeit
    oder Migration. Eine Ausnahme ist es wert, genannt zu werden: eine private Instanz mit einem
    lokal abweichenden tools/CONTRACT.md (instructions/private-instance.md) kann nach diesem
    Update zum ersten Mal an der strengeren Prüfung scheitern, wenn ihr eigener Baum dieselbe
    Fehlerkontrakt-Lücke trägt oder ihre Kommandotabelle anders benannte ##-Überschriften
    verwendet. Das ist kein Boundary-Crossing - kein Dateiformat ändert sich, keine bestehende
    Funktion verschwindet -, sondern derselbe bereits akzeptierte Fall, den jede Verschärfung von
    docs verify seit jeher mit sich bringt: der Fix legt eine bereits vorhandene
    Dokumentationslücke bloß, statt eine neue Anforderung einzuführen. Der Kandidat trägt seine
    --breaking-Zeile bereits aus einem früheren Bump; diese Änderung fügt keine neue hinzu.

    Geändert: tools/chemenu/commands/docs_verify.py (section_text, check_cli_readme),
    tools/CONTRACT.md (die docs verify-Zeile sowie zehn aufgeteilte bzw. nachgetragene
    Fehlerkontrakt-Zeilen), tools/chemenu/tests/test_docs_verify.py (neue Tests für
    Abschnittstrennung, fehlende/umbenannte Überschrift, § Error contracts in beiden Richtungen).
    Verifiziert: tools/wikitool docs verify, tools/wikitool instructions verify, volle
    pytest-Suite (1193 passed). Schließt #91.

    tools/CONTRACT.md ist jetzt ein Dokument zum Nachschlagen statt zum Durchlesen (#92).
    Mit 70 KB war es das größte Dokument im Repo - größer als AGENTS.md, kb/CONTRACT.md und
    raw/CONTRACT.md zusammen -, und 89 % davon lagen in den zwei Kommandotabellen. Gleichzeitig
    war es für gezielten Abruf bereits ideal gebaut, ohne dass es irgendwo stand: eine Zeile ist
    ein Kommando, physisch einzeilig, also liefert ein einziges grep '^| `<kommando>' beide
    Hälften seines Vertrags - was es tut und wie es fehlschlägt - und sonst nichts. AGENTS.md
    routete stattdessen mit "Full command reference" dorthin, was sich als Vollread liest.

    Drei Änderungen: die Datei trägt die Nachschlage-Regel samt grep-Zeile jetzt als Lead-in vor
    dem Inhaltsverzeichnis (genau einmal, Invariante 8 - AGENTS.mds Routing-Zelle beschreibt nur
    noch die Form und wiederholt die Zeile nicht); beide Tabellen sind in vierzehn ###-Gruppen
    gegliedert, wodurch docs toc erstmals Anker unterhalb ## erzeugt (vorher existierte im
    ganzen Repo genau ein Anker-Link in diese Datei); und § Error contracts steht nun in derselben
    Gruppen- und Zeilenreihenfolge wie § Commands, sodass die zwei Hälften eines Vertrags parallel
    liegen - vorher saß etwa links show einmal zwischen xref link-source und cite id, einmal
    zwischen version release und migrate list. Nebenbei repariert: die dist export-Zeile war
    über vier physische Zeilen umgebrochen und damit kein gültiger GFM-Tabellen-Datensatz mehr.

    Die Umstellung lief per Skript mit einer Behauptung über die Multimenge der Erst-Zellen vor und
    nach dem Schreiben, nicht per Augenschein - bei 117 Zeilen ist "keine verloren" nichts, was ein
    Review zusieht. Dass ### die Abschnittsgrenze aus #91 nicht bricht, ist jetzt ein eigener
    Test: section_texts Lookahead (?=^#{1,2}[ \t]|\Z) verlangt nach ein bis zwei # ein
    Space/Tab, was bei ### fehlschlägt - ohne diese Eigenschaft wäre § Commands an seiner ersten
    Gruppe abgeschnitten und jedes spätere Kommando als undokumentiert gemeldet worden.

    Bewusst nicht angefasst: die siebzehn Tabellenzellen über 900 Zeichen. Der naheliegende Schritt
    wäre, ihren Begründungsanteil nach docs/ zu schieben; die Gegenprobe an der größten Zelle
    (publish, 3.375 Zeichen) zeigt, dass die Kandidaten dafür - warum ein Token Dateiliste und
    Inhalte digestiert, warum die Publish-Remote-Gate kein Flag hat - für eine handelnde Session
    normativ verwertbar sind und nicht Hintergrund. Sie wegzukürzen hätte die Zelle verkleinert und
    die Handlungsfähigkeit gesenkt. Die Zellen sind lang, weil die Verträge dicht sind.

    PATCH, kein neues Boundary-Crossing: reine Dokumentstruktur, kein Format, keine Funktion, keine
    Hand-Arbeit bei Update oder Downgrade. Eine private Instanz mit eigenem tools/CONTRACT.md
    bekommt beim Merge Konflikte in den beiden Tabellen, weil deren Zeilen umsortiert wurden - das
    ist ein Merge-Konflikt in einer stack-eigenen Datei, den upstream merge ohnehin zugunsten der
    Upstream-Seite auflöst, kein Kompatibilitätsbruch.

    Geändert: tools/CONTRACT.md (Lead-in, ###-Gruppen in beiden Tabellen, gespiegelte
    Reihenfolge, TOC via docs toc --apply), AGENTS.md (Routing-Zelle für tools/),
    tools/chemenu/tests/test_docs_verify.py (zwei Tests: Abschnitt läuft über eigene
    Unterüberschriften hinweg, gruppierte Tabellen bleiben in beiden Richtungen geprüft).
    Verifiziert: tools/wikitool docs verify, tools/wikitool instructions verify, volle
    pytest-Suite (1195 passed). Typischer Zugriff: Median 229 statt 17.500 Tokens.
    Schließt #92.

    incoming/.gitkeep ist jetzt trackbar - Datei- statt Verzeichnismuster in .gitignore (#88).
    /incoming/ schloss bisher das Verzeichnis selbst aus, und Regel 2 im .gitignore-Kopf sagt
    genau, warum das eine Falle ist: git kann keine Datei wieder einschließen, deren Elternverzeichnis
    ausgeschlossen ist, also blieb ein !incoming/.gitkeep wirkungslos. Ein frischer Klon hatte den
    Eingang deshalb nie - instructions/bootstrap.md Schritt 2 (mkdir -p incoming) fing das für
    einen Menschen ab, der die Instruction liest, aber nicht für einen Checkout, den eine Maschine
    anlegt: der Korpus-Checkout des MCP-Read-Servers entsteht aus Klon plus fetch && reset --hard
    und läuft durch keinen Bootstrap, also fehlte der Eingang dort still, bis das erste Kommando ihn
    brauchte.

    Umgestellt auf /incoming/* plus !/incoming/.gitkeep - jetzt ignoriert das Muster den Inhalt,
    nicht das Verzeichnis, und die Negation greift tatsächlich. Gegengeprüft in beide Richtungen:
    git check-ignore --no-index incoming/.gitkeep meldet die Datei als nicht ignoriert,
    incoming/probe.pdf weiterhin als ignoriert - die Kanarie aus
    docs_verify.REQUIRED_IGNORE_CANARIES hält also unverändert. incoming/.gitkeep steht jetzt
    zusätzlich in REQUIRED_TRACKED_PATHS, damit eine künftige Rückkehr zur Verzeichnisform
    docs verify laut fehlschlagen lässt statt still zu wirken. Ein voller dist export +
    git init + git add -A-Replay bestätigt, dass incoming/.gitkeep im allerersten Commit einer
    frischen Instanz landet, während incoming/probe.pdf dort weiterhin ignoriert bleibt.

    instructions/bootstrap.md Schritt 2 entfällt ersatzlos - ein frischer Klon braucht ihn nicht
    mehr -, die übrigen Schritte rücken nach. mcp-upload/ bleibt bewusst bei der Verzeichnisform
    ohne .gitkeep: die Schreibprimitive des submit-Tools legt das Verzeichnis selbst an, sobald
    sie gebraucht wird, und ein Checkout, der das Tool nie aktiviert, braucht das Verzeichnis auch
    nie - anders als bei incoming/ gibt es dort keinen Frisch-Klon-Fall abzudecken. Der
    .gitignore-Kommentar zu mcp-upload/ benennt diesen Kontrast jetzt ausdrücklich, statt sich
    nur noch mit dem alten (identischen) Verhalten von incoming/ zu vergleichen.

    Geändert: .gitignore (beide Kommentarblöcke, neues Muster), incoming/.gitkeep (neu),
    tools/chemenu/commands/docs_verify.py (REQUIRED_TRACKED_PATHS), instructions/bootstrap.md
    (Schritt 2 entfernt, Rest umnummeriert), raw/CONTRACT.md, tools/CONTRACT.md (dist export-
    Zeile), README.md (Architekturdiagramm-Kommentar). Verifiziert: tools/wikitool docs verify,
    tools/wikitool instructions verify, volle pytest-Suite (1195 passed), manueller
    dist export-Replay. Schließt #88.

    dist export-Doku nennt keine Typverzeichnisse mehr, die seit #67 nicht mehr angelegt werden
    (#93).
    tools/CONTRACT.mds dist export-Zeile und der --help-Docstring des Kommandos
    selbst behaupteten wortgleich, ein frischer Export lege leere
    raw/{articles,documents,notes,assets}/ und die passenden incoming/-Typverzeichnisse an. #67
    hat die Typverzeichnisse aus dem Adressierungsschema von raw accept entfernt (Datums-Shard
    statt Typverzeichnis) und dabei den Export-Pfad auf zwei flache Anker verkürzt
    (raw/.gitkeep, incoming/.gitkeep - run_export), ohne die zwei Prosa-Stellen nachzuziehen,
    die noch die alte Struktur beschrieben. Nebenbefund aus der Sitzung zu #88.

    Beide Stellen beschreiben jetzt den tatsächlichen flachen Export. Gegengeprüft per
    tools/wikitool dist export in ein leeres Scratch-Verzeichnis: raw/ enthält nach dem Export
    nur .gitkeep (plus die verzeichniseigene CONTRACT.md), incoming/ nur .gitkeep - keine
    Typunterverzeichnisse unter keinem der beiden Wurzeln.

    Außerhalb der Akzeptanzkriterien dieses Issues, aber derselbe Fund: dieselbe Prosa-Drift bei
    raw/*/.gitkeep in der dist upgrade-Zeile (tools/CONTRACT.md), in
    docs/ownership-and-templates.md und im apply_upgrade-Docstring - andere Fundstelle, gleiche
    Ursache. In derselben Sitzung gleich mitkorrigiert, siehe die nächste Eintragszeile unten.

    Geändert: tools/CONTRACT.md (dist export-Zeile), tools/chemenu/commands/dist_cmd.py
    (--help-Docstring von export). Verifiziert: tools/wikitool docs verify,
    tools/wikitool instructions verify, volle pytest-Suite, manueller dist export-Replay in
    ein leeres Scratch-Verzeichnis. Schließt #93.

    raw/*/.gitkeep-Glob war seit #67 ebenfalls stale - korrigiert auf den flachen
    raw/.gitkeep-Anker.
    Nebenbefund beim Nachziehen von #93 in derselben Sitzung: dieselbe
    Typverzeichnis-Drift steckte noch an drei weiteren Stellen, die von einem mehrfachen
    raw/<typ>/.gitkeep sprachen, obwohl raw/ seit #67 flach ist und es nur noch den einen
    raw/.gitkeep-Anker gibt - chemenu.ownership.is_export_stub/is_upgrade_preserved matchen
    ohnehin per Dateiname statt Pfadtiefe, das Verhalten war also nie falsch, nur die illustrative
    Glob-Schreibweise in der Prosa. Betroffen: tools/CONTRACT.mds dist upgrade-Zeile,
    docs/ownership-and-templates.mds Aufzählung der seeded-once-Dateien, und der
    apply_upgrade-Docstring in dist_cmd.py.

    Geändert: tools/CONTRACT.md (dist upgrade-Zeile), docs/ownership-and-templates.md,
    tools/chemenu/commands/dist_cmd.py (--help-Docstring von dist upgrade). Verifiziert:
    tools/wikitool docs verify, tools/wikitool instructions verify, volle pytest-Suite.

    Ingests haben jetzt zwei Größenachsen: Volumen und Breite (#61). ingest-large-tree löste
    bislang ausschließlich auf Volumen aus - mehr als ~20 Rohdateien, mehr als ~15 raw_files:-
    Einträge -, und sein § Scope sagte das auch so: „This is about volume, not difficulty."
    Dazwischen lag eine Lücke: die einzelne, thematisch breite Quelle. Sie löst keinen
    Volumen-Trigger aus, wiki-ingest kannte keinen anderen, und was sie anrichtet, meldet kein
    lint-Befund.

    Der Korpus zeigt den Fall genau einmal, dafür deutlich. Source - LLM Wiki v2: eine Rohdatei,
    4 Entities + 26 Concepts, gegen einen Median von 6 über alle 29 Source-Seiten. Der naheliegende
    Verdacht - die Source-Seite werde vage - trägt nicht: alle 26 Concepts haben eine eigene Seite,
    die Breite wurde vollständig nachgezogen. Der Schaden sitzt eine Ebene tiefer und ist bimodal:
    14 der 30 Subjektseiten liegen unter 150 Wörtern (die dünnsten bei 71 bis 78), die übrigen bei
    541 bis 1.294, gegen einen Korpus-Median von 485. Eine Stub-Kohorte aus einem einzigen
    Durchgang
    . Keine andere Quelle im Korpus hat eine: bei 18 Gegenständen (Source - LLM Wiki Pattern) sind es null, und darunter ebenfalls. Daher die Schwelle bei ~20 statt am
    75.-Perzentil - ein Auslöser, der auf schadensfreien Seiten feuert, wird ignoriert.

    Breite wird nicht geschnitten, und das ist der Teil, der eine eigene Sektion bekommt. Zwei
    Regeln schließen den Weg: raw/ hält eine Datei „exactly as received", und eine Rohdatei hat
    genau einen Besitzer (lint meldet einen zweiten Anspruchsteller). Mehrere thematische
    Source-Seiten über einer Datei wären also nicht bloß unüblich, sondern ließen bei einer neuen
    Edition niemanden zuständig zurück. Der Schnitt vor raw accept bleibt davon unberührt - er
    ist das, was capture-session § 1 tut, und er ist nur möglich, weil das Transkript dort noch
    gar nicht existiert. Diese Abgrenzung steht jetzt in § 1 selbst, weil genau dort der
    Fehlschluss ansetzt, ein empfangenes Dokument ließe sich genauso zerlegen.

    Was die Breite stattdessen auslöst, ist der Extract-Pass vor dem Schreiben und die Regel, die
    er beliefert: ein Gegenstand bekommt eine Seite, wenn die Quelle Material für eine trägt.
    Eine beiläufige Erwähnung bekommt einen Wikilink von der Source-Seite und eine Zeile unter
    ## Not Extracted, keine eigene Seite. Eine Seite, die nur ihren eigenen Titel wiederholt, ist
    schlechter als die Erwähnung, aus der sie entstand: lint misst Struktur und nie Substanz, also
    meldet sie niemand, und die nächste Sitzung liest sie als abgedecktes Terrain und schaut nicht
    mehr in die Quelle.

    Der vendorierte commonplace-Korpus stützt den Verzicht auf einen Source-Split unabhängig vom
    Befund aus dem Baum, was ihn zum belastbareren Teil der Begründung macht: Atomizität ist dort
    ausschließlich mit Co-Loading für Entdeckung begründet und gilt damit der Library-Schicht
    (kb/entities/, kb/concepts/), nicht der Evidenz-Schicht; ein Mehr-Aussagen-Dokument ist per
    title-as-claim Referenz statt Prämisse, weshalb Breite seiner Rolle nicht schadet; und die
    Forderung, Forward-Lineage müsse den Betreiber unterbrechen, erreicht „eine Rohdatei, ein
    Besitzer" auf eigenem Weg.

    Nicht gebaut: ein lint-Befund gegen die Stub-Kohorte. Er wäre die Beobachtungsseite derselben
    Sache, braucht aber eine eigene Schwellenwertdiskussion - Wortzahl ist ein grober Proxy für
    Substanz - und liegt als eigenes Issue auf dem Board. Die Schwelle ~20 ruht auf einem einzigen
    Schadenspunkt und ist entsprechend vorläufig.

    MINOR, kein neues Boundary-Crossing: additiv in beide Richtungen. Eine bestehende Instanz
    bekommt einen zusätzlichen Auslöser und eine zusätzliche Regel in Dateien, die der Stack
    besitzt; nichts an kb/, keinem Schema, keinem Kommando und keinem Flag ändert sich, und ein
    Downgrade nimmt beides ersatzlos zurück. Der Kandidat steht ohnehin auf MAJOR, der Bump
    erhöht also nur seinen Zähler.

    Geändert: instructions/ingest-large-tree.md (§ When to run auf zwei Achsen, Tier-Tabelle,
    neue § A broad source is not cut, § Scope, ein Decision Point, description),
    instructions/wiki-ingest/SKILL.md (Schritt 2 verweist jetzt auf die Schwellenliste statt sie
    zu wiederholen, Schritt 7 trägt die Seiten-Regel, Schritt 8 und ein Decision Point ziehen nach),
    instructions/capture-session.md (§ 1 Abgrenzung), types/source.md (## Not Extracted ist
    jetzt auf beiden Achsen Pflicht; entities:+concepts: jenseits von ~20 als Gegenstück zum
    bestehenden raw_files:-Signal). Verifiziert: tools/wikitool docs verify,
    tools/wikitool instructions verify, volle pytest-Suite (1195 passed), docs toc --apply und
    instructions sync für die generierten Regionen und die veröffentlichten Kopien.
    Schließt #61.

    stack-close Schritt 3 sagt jetzt, dass ein Doku-Nachzug seinen eigenen Bump braucht.
    Direkt aus dem vorigen Paket gelernt: dessen Abschlussphase zog types/source.md nach, ohne
    VERSION zu bewegen, und CI-Run 249 fiel an der Version Gate um. Die Sitzung las den Nachzug
    als Dokumentation - types/source.md ist aber beides, Dokument und ausgelieferte
    Verhaltensbeschreibung, und die Gate ist nach Pfad gefasst, nicht nach Absicht
    (^(tools/|types/|instructions/|AGENTS\.md$|[^/]+/CONTRACT\.md$)).

    Das ist kein Ausrutscher, sondern eine Reihenfolge, die sich wiederholt: stack-dev Schritt 4
    bumpt die eigentliche Änderung, und Schritt 3 dieses Skills läuft danach - in genau den
    Pfaden, die die Gate bewacht. Schritt 3 trägt den Satz deshalb jetzt selbst, samt der Falle
    ("nur Dokumentation") und des Auswegs (version bump --patch im selben Zug, was beim laufenden
    Kandidaten ohnehin nur den Zähler bewegt). Nicht nach doc-pull-through.md gelegt, obwohl es
    dort thematisch auch passte: die Entscheidung fällt in dem Schritt, der den Commit auslöst, und
    zwei Kopien wären die Dopplung, die Invariante 8 verbietet.

    Geändert: instructions/dev/stack-close/SKILL.md (Schritt 3). Verifiziert:
    tools/wikitool docs verify, tools/wikitool instructions verify, volle pytest-Suite,
    instructions sync für die veröffentlichte Kopie. Kein Issue - direkt korrigiert.


    This note is a snapshot of the CHANGES.md entry as it stood when the tag was cut, and
    is never edited afterwards. The maintained version of this text - including any later
    correction - is the entry for this version in CHANGES.md in the repository.

    Downloads
  • v4.7.4 cc38bcd700

    v4.7.4
    CI / verify (push) Successful in 57s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-04 18:51:38 +00:00 | 53 commits to main since this release

    4.7.4 - 2026-09-04 - bootstrap.md nennt den session-id-WARN nach frischem Bootstrap explizit als erwartet

    Author: Torben Nehmer

    • bootstrap.md nennt den session-id-WARN nach frischem Bootstrap explizit als erwartet

    Nach einem frischen Clone plus instructions/bootstrap.md zeigte tools/wikitool doctor
    durchgehend OK, außer session-id: WARN - ohne Einordnung, ob das ein Bootstrap-Defekt ist.
    WIKITOOL_SESSION_ID wird laut instructions/session-setup.md bewusst pro Arbeitssitzung
    gesetzt, nicht pro Clone; ein Export in bootstrap.md selbst würde nur für den Bootstrap-Lauf
    gelten, nicht für die tatsächliche Arbeitssitzung danach (die nach dem Neustart in Schritt 6 in
    einer neuen Shell beginnt). bootstrap.md bekommt deshalb einen neuen Schritt 7, der den WARN
    als erwarteten Zustand benennt - analog zum bereits dokumentierten personalization: FAIL in
    Schritt 4 - und auf session-setup.md verweist, statt den Export in Bootstrap nachzubauen.
    Schließt #54.


    This note is a snapshot of the CHANGES.md entry as it stood when the tag was cut, and
    is never edited afterwards. The maintained version of this text - including any later
    correction - is the entry for this version in CHANGES.md in the repository.

    Downloads
  • v4.7.3 6671af6a60

    v4.7.3
    CI / verify (push) Successful in 54s
    Release / release (push) Successful in 36s
    Stable

    torben released this 2026-09-04 18:31:10 +00:00 | 54 commits to main since this release

    4.7.3 - 2026-09-04 - eval: gate-not-self-opened prueft REMOVED_FLAGS gegen das eigene Kommando

    Author: Torben Nehmer

    Die Trajektorien-Regel gate-not-self-opened (tools/chemenu/evals/trajectory.py) hat jedes
    Argument jedes wikitool.call gegen REMOVED_FLAGS = {"--yes": "publish", "-y": "publish"}
    geprüft, ohne je das eigene command-Feld des Aufrufs gegenzulesen. --yes/-y sind nur auf
    publish entfernt worden - auf rm --page <Titel> --yes sind sie ein gültiger, dokumentierter
    Flag. Ergebnis: jeder rm --yes-Aufruf wurde als Invarianten-Verstoß gemeldet ("an agent
    inventing a flag the tool never accepts"), obwohl das Tool ihn akzeptiert hatte.

    In den vorhandenen Telemetrie-Traces unter reports/telemetry/ betraf das 111 rm-Aufrufe
    über 8 Sessions, davon 27 allein in publish-cleanup/u3 - jede davon fälschlich FAILED
    gescort. Kein bestehender Test hätte das gefangen: tools/chemenu/tests/test_evals.py prüfte
    REMOVED_FLAGS ausschließlich über publish --yes, nie über ein anderes Kommando.

    Fix: die Bedingung liest jetzt attrs.get("command") == REMOVED_FLAGS[arg] mit. Neuer
    Regressionstest test_yes_on_a_command_that_still_has_it_is_not_a_finding deckt genau den
    rm --yes-Fall ab und schlägt gegen den unfixed Code nachweislich fehl.

    Keine Verhaltensänderung an wikitool selbst - ausschließlich an der Scoring-Logik unter
    tools/chemenu/evals/.

    • eval: gate-not-self-opened prueft REMOVED_FLAGS gegen das eigene Kommando

    This note is a snapshot of the CHANGES.md entry as it stood when the tag was cut, and
    is never edited afterwards. The maintained version of this text - including any later
    correction - is the entry for this version in CHANGES.md in the repository.

    Downloads
  • v4.7.2 e00eae08e8

    v4.7.2
    CI / verify (push) Successful in 57s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-04 17:22:51 +00:00 | 56 commits to main since this release

    4.7.2 - 2026-09-04 - Coverage-Untergrenze bei 85 %, gegen beobachtete 87,0 %

    Author: Torben Nehmer

    • Coverage-Untergrenze 85 % in tools/.coveragerc

    Die Suite hat jetzt einen Boden: fail_under = 85 in tools/.coveragerc, gemessen gegen 87,0 %
    (CI-Lauf 163, 6498 Statements, 975 Tests). Damit ist Gitea #10 geschlossen — das Issue, das die
    Messung eingerichtet und die Schwelle danach absichtlich zurückgehalten hat, bis die Zahl
    beobachtet war.

    Die Beobachtung ist der eigentliche Inhalt dieses Bumps. Zwischen der ersten Messung (86,9 % von
    5105 Statements, 730 Tests, Lauf 87, Stack 1.8.1) und heute ist der gemessene Code um ein Viertel
    gewachsen und die Suite um ein Drittel, über 38 grüne Läufe — und die Quote hat sich um einen
    Zehntelpunkt bewegt. Eine Untergrenze, die auf dieser Beobachtung steht, ist etwas anderes als
    eine gegriffene Zahl.

    85 und nicht 87, und das ist keine Bequemlichkeit. Der Coverage-Bericht unterscheidet drei
    Sorten ungedeckter Zeilen, und nur eine davon bedeutet Arbeit (EVALS.md § „How much of the
    stack the suite reaches"). Ein neuer dünner Typer-Wrapper senkt den Gesamtwert, ohne dass
    irgendetwas schlechter geworden wäre — seine Logik liegt daneben und ist dort getestet. Eine
    Schwelle auf dem gemessenen Wert würde genau an diesem Commit rot, und eine Schwelle, die aus
    einem Nicht-Grund rot wird, wird gesenkt statt verdient. Das ist die Fehlerweise, die #10
    verhindern wollte, nur von der anderen Seite. Die zwei Punkte sind der Platz, den die Taxonomie
    verlangt.

    fail_under steht in der Konfiguration und nicht als --cov-fail-under im CI-Schritt: so sitzt
    die Zahl neben der Begründung, die sie erzeugt hat, und gilt für jeden --cov-Lauf statt nur für
    den einen, den CI schreibt.

    Was der Boden nicht tut: die drei echten Lücken schließen (provenance_cmd.py 44 %,
    migrate_cmd.py 65 %, type_resolver.py 79 %). Er friert den erreichten Stand ein. Diese Liste
    ist die einzige, die sich nicht bewegt hat, während alles um sie herum wuchs — migrate_cmd.py
    ist sogar von 71 % gefallen, weil das Modul gewachsen ist und die neuen Zeilen ungetestet ankamen.
    Das ist Gitea #51.

    Mitgenommen, weil es dieselbe Frage beantwortet: der Coverage-Bericht ist als Artefakt
    abrufbar, über die Run-Seite. Die Actions-Artefakt-Endpunkte melden dafür total_count: 0, weil
    upload-artifact@v3 über die ältere Artifact-API ablegt, die diese Endpunkte nicht lesen. Eine
    leere Liste ist kein fehlgeschlagener Upload — steht jetzt in EVALS.md und im Kommentar an der
    Coverage report-Stufe, damit die naheliegende „Korrektur" auf v4 (hier eingeschränkt) niemandem
    mehr einfällt.


    This note is a snapshot of the CHANGES.md entry as it stood when the tag was cut, and
    is never edited afterwards. The maintained version of this text - including any later
    correction - is the entry for this version in CHANGES.md in the repository.

    Downloads
  • v4.7.1 24593c5608

    v4.7.1
    CI / verify (push) Successful in 52s
    Release / release (push) Successful in 35s
    Stable

    torben released this 2026-09-04 16:49:59 +00:00 | 58 commits to main since this release

    4.7.1 - 2026-09-04 - redundant_see_also in tools/CONTRACT.md und wiki-lint dokumentiert; xref-remove-Falle benannt

    Author: Torben Nehmer

    • redundant_see_also in tools/CONTRACT.md und wiki-lint; xref-remove-Falle beim Aufräumen benannt

    Die Doku-Hälfte von 4.7.0, beim Abschluss nachgezogen. Der Befund war ausgeliefert, aber
    tools/CONTRACT.mds lint-Zeile zählt die Befunde auf und kannte ihn nicht - eine Instanz hätte
    eine Sektion im Report gefunden, die ihr Contract nicht erklärt. docs verify prüft, dass die
    Kommandotabelle existiert, nie was in einer Zeile steht; genau die Lücke, für die AGENTS.md
    „a stack change is not finished until the human docs describe it" geschrieben ist.

    Die eigentliche Änderung ist aber die Warnung in wiki-lint, und sie ist keine Prosa-Politur.
    Der neue Befund liest sich wie etwas, das Schritt 7 („repariere, was mechanisch ist") abräumt, und
    der naheliegende Griff wäre xref remove - das die Referenz beidseitig löscht. Angewandt auf
    Wine see-also Wine GE neben Wine GE depends-on Wine hätte das die schwache und die
    spezifische Kante entfernt, und das Paar sagte danach gar nichts mehr. Ein Befund, dessen
    offensichtliche Reparatur Daten zerstört, ist schlechter als kein Befund: Schritt 1 nennt die
    Falle jetzt beim Namen und verweist auf xref add (fasst nur die Quellseite an) oder aufs
    Berichten. Dieselbe Asymmetrie hat in #30 schon einmal Daten gekostet.


    This note is a snapshot of the CHANGES.md entry as it stood when the tag was cut, and
    is never edited afterwards. The maintained version of this text - including any later
    correction - is the entry for this version in CHANGES.md in the repository.

    Downloads
  • v4.7.0 3916cb9541

    v4.7.0
    CI / verify (push) Successful in 1m2s
    Release / release (push) Successful in 37s
    Stable

    torben released this 2026-09-04 16:47:41 +00:00 | 59 commits to main since this release

    4.7.0 - 2026-09-04 - Link-Katalog: authored, alternative-to, addresses; entity→entity-Lineage; Lint-Befund gegen redundante see-also

    Author: Torben Nehmer

    • Link-Katalog: authored, alternative-to, addresses; Lint-Befund gegen redundante see-also

    Drei neue Label, zwei geschlossene Autorisierungslücken und ein Lint-Befund - ausgelöst von der
    anstehenden ersten produktiven Instanz. Katalog und COLLECTION.md gehen über dist export in
    jede neue Instanz; was hier fehlt, fehlt dort ab Tag eins, und nachträglich ist eine
    Katalogerweiterung eine Korpus-Migration statt einer Datenzeile. Gitea #43 und #49.

    Die Messung, die den Zuschnitt bestimmt hat. Die 180 see-also-Kanten des Korpus zerfallen
    exakt in drei Klassen: 57 (32 %) sind Spiegel einer bereits typisierten Gegenkante, 70 (39 %)
    sind wechselseitige see-also-Paare, 53 (29 %) stehen einseitig. Die erste Klasse ist kein
    Vokabularproblem - Wine see-also Wine GE steht neben Wine GE depends-on Wine, RAG see-also NotebookLM neben NotebookLM implements RAG. Der Katalog war für ein Drittel der Fälle längst
    ausreichend; es hat sie nur nichts gemeldet. Genau dafür ist der Lint-Befund unten da, und er ist
    der Grund, warum diese Version mehr ist als zwei Katalogzeilen.

    authored (operationales Register). „hat das Ziel als einmaligen Akt geschaffen." Der Katalog
    kannte fortlaufende Rechenschaft (owns) und fortlaufende Arbeit (maintains), aber nicht den
    historischen Ursprung - Urheberschaft stand im Korpus deshalb in vier unvereinbaren Formen
    nebeneinander: source.author als Freitext, owns, see-also und ein Prosa-Bullet. Eine davon
    war sachlich falsch: Vannevar Bush owns Memex behauptet laufende Rechenschaft für einen 1974
    Verstorbenen, und eine falsche maschinenlesbare Kante ist schlechter als eine schwache, weil sie
    geglaubt wird. Geschrieben wird das Label auf der Entity-Seite (Andrej Karpathy authored LLM Wiki Pattern) - die Gegenrichtung authored-by auf der Concept-Seite hätte xref remove
    gebraucht, das beidseitig abräumt, statt xref add, das relabelt. Vier Kanten im Korpus
    umgestellt, keine verloren.

    alternative-to (operationales Register, selbst-dual). „erfüllt denselben Zweck wie das Ziel,
    so dass ein Leser, der zwischen beiden wählt, beide will." Belegt durch rund 30 Paare, darunter
    die sieben Agent-CLIs, die untereinander ausschließlich see-also tragen - keine einzige
    typisierte Kante. Abgegrenzt gegen contrasts (behauptet einen lesenswerten Unterschied) und
    compares-with (wiegt auf benannten Dimensionen ab und führt in dieser Instanz auf eine
    kb/comparisons/-Seite): zwei Agent-CLIs sind austauschbar, zwei gegensätzliche Entwurfsprinzipien
    sind es nicht.

    Der Katalog sagt jetzt ausdrücklich, dass ein selbst-duales Label einmal pro Paar geschrieben
    wird. Ohne diesen Satz wäre aus einer 22-Kanten-see-also-Clique eine 22-Kanten-
    alternative-to-Clique geworden und nichts gewonnen: sieben austauschbare Werkzeuge sind 21
    Paare, beidseitig deklariert 42 Kanten, von denen die zweiten 21 nichts sagen.

    addresses (konzeptionelles Register). „ist eine Antwort auf das Problem, das das Ziel
    beschreibt." types/concept.md deklariert problem und decision als eigene Subtypen, und der
    Katalog hatte kein Label, das eine Entscheidung mit dem Problem verbindet, das sie löst - eine
    Collection konnte ein Problem benennen und nie sagen, was dagegen unternommen wurde. Abgegrenzt
    gegen rests-on, das das Ziel als Prämisse nimmt statt als zu lösendes Problem. Im Korpus nur
    vier belegte Paare, also dünn nach dem sonst geltenden „erst der Anwendungsfall"-Maßstab; die
    Ausnahme ist bewusst und gilt dem Auslieferungszeitpunkt, der die Kosten umdreht.

    Zwei Autorisierungslücken entity→entity. kb/entities/COLLECTION.md erlaubte bisher keine
    Lineage zwischen zwei Entities - ein Fork, eine Neuimplementierung, ein Nachbau war nicht
    ausdrückbar; derived-from und adapted-from sind jetzt freigegeben. Ebenso implements, für
    eine Entity, die eine als Entity geführte Konvention umsetzt. Ein autorisiertes Label ohne
    Live-Nutzung ist ausdrücklich in Ordnung (instructions/dev/corpus-policy.md).

    Lint-Befund redundant_see_also. Meldet eine see-also-Kante, deren Gegenrichtung bereits
    ein typisiertes Label trägt. Gegen den Korpus dieser Instanz meldet er genau die gemessenen 57.
    Advisory, nicht hart, aus zwei Gründen zugleich: eine schwache Kante neben einer spezifischen
    ist redundant, nicht kaputt - und der Befund kommt lange nach den Korpora, die er beurteilt, also
    würde eine harte Einstufung jede bestehende Instanz mit dem Upgrade rot schalten, das ihn
    ausliefert. Anders als unlabelled_edges ist er auch nicht migrations-gegatet: es gibt keine
    Version, ab der die Redundanz zum Fehler wird, nur einen Sweep, zu dem jemand kommt oder nicht.

    links.SEE_ALSO ist damit das einzige Katalog-Label, das das Werkzeug beim Namen kennt. Das ist
    eine begründete Ausnahme, keine Aufweichung: see-also ist der erklärte letzte Ausweg des
    Katalogs und behauptet nur, dass nichts Besseres passte - was der einzige Grund ist, warum lint
    eine Kante als schwächer als eine andere über demselben Paar beurteilen kann. Alles andere am
    Vokabular bleibt in instructions/link-taxonomy.md und den outbound:-Blöcken.

    Nicht dabei, bewusst. Der Sweep der 180 bestehenden Kanten (#48) - diese Version ändert außer
    den vier Urheberschaftskanten keine Korpus-Kante. Verworfen wurden außerdem variant-of (die
    Wine-Forks tragen bereits depends-on Wine), implemented-by (Spiegel von implements, den die
    Inbound-View rendert), sibling-of für die Concept-Cliquen (Über-Verlinkung, kein fehlendes Wort)
    und builds-on (Vokabularkollision mit extends/derived-from/adapted-from).


    This note is a snapshot of the CHANGES.md entry as it stood when the tag was cut, and
    is never edited afterwards. The maintained version of this text - including any later
    correction - is the entry for this version in CHANGES.md in the repository.

    Downloads
  • v4.6.1 72b2b4424f

    v4.6.1
    CI / verify (push) Successful in 54s
    Release / release (push) Successful in 36s
    Stable

    torben released this 2026-09-04 13:58:09 +00:00 | 60 commits to main since this release

    4.6.1 - 2026-09-04 - DEVELOPMENT.md im Kommandotabellen-Check, selbstbeschriftete Release-Notes, Prosa-Korrekturen (#47 Block 3)

    Author: Torben Nehmer

    • DEVELOPMENT.md im Kommandotabellen-Check, selbstbeschriftete Release-Notes, Prosa-Korrekturen (#47 Block 3)

    Block 3 aus #47 - die beiden Nebenbefunde entschieden und umgesetzt - plus drei Prosa-Korrekturen
    an 4.6.0, die eine Bewertung des eigenen Ergebnisses gefunden hat.

    DEVELOPMENT.md gehört in docs_verify.STAGE_READMES (entschieden: ja). Das Gegenargument
    bei der Aufnahme war, die Liste führe bisher nur ausgelieferte Dokumente, und DEVELOPMENT.md
    wird von dist_cmd.ROOT_FILES bewusst nicht ausgeliefert. Beim Hinsehen löst es sich auf:
    check_readmes_have_no_command_table überspringt eine Datei, die nicht existiert. In einer
    ausgelieferten Instanz ist der Eintrag damit schlicht wirkungslos, im Entwicklungs-Checkout - dem
    einzigen Ort, an dem die Datei existiert und also driften kann - greift er. Dafür spricht der
    Anlass: genau diese Datei trug einmal eine Tabelle, die für jeden Verify-Befehl ein zweites Mal
    beschrieb, was er prüft, und sie musste von Hand entfernt werden, weil nichts sie mit etwas
    verglich. Zwei Tests: einer, der die Tabelle in DEVELOPMENT.md meldet, und einer, der
    festhält, dass eine fehlende gelistete Datei übersprungen und nicht als Fund gemeldet wird - der
    Instanz-Fall, an dem die Entscheidung hing. Der Konstantenname ist jetzt enger als sein Inhalt;
    das steht als Kommentar daneben, statt eine Umbenennung durch zwei Aufrufstellen zu ziehen.

    Veröffentlichte Release-Notes veralten weiter - sie sagen es jetzt selbst (entschieden:
    Schnappschuss akzeptieren, statt einen Korrekturweg zu bauen). Eine nach dem Tag korrigierte
    CHANGES.md erreicht die Release-Seite nicht: gitea-mcp kennt kein Release-Edit, und
    Löschen-und-neu-Anlegen würde die angehängten Tarball-Assets vernichten, auf die INSTALL.md und
    version check zeigen. Bei v4.4.0 ist das real eingetreten. Statt eines Korrekturwegs für einen
    Text, den niemand editieren kann, trägt der Schnappschuss jetzt eine Fußzeile, die sagt, dass er
    einer ist und wo die gepflegte Fassung liegt - eine veraltete Notiz kostet einen Leser damit einen
    Klick statt einer falschen Überzeugung. Angehängt in release.yml und nicht in version notes:
    das Kommando ist ein allgemeiner Extraktor, dessen andere Aufrufer (lokale Vorschau, eine Pipe)
    keine Release-Seiten-Fußzeile erben sollen. .gitea/-Änderung, also ohne eigenen Bump-Anspruch -
    sie fährt hier mit.

    Drei Prosa-Korrekturen an 4.6.0. Der 4.6.0-Eintrag und der Docstring von
    touches_stack_machinery behaupteten, das Prädikat prüfe „denselben Pfad-Umfang, den der
    CI-Versions-Gate selbst verwendet". Das stimmt nicht: CI matcht [^/]+/CONTRACT\.md$, also genau
    eine Pfadebene, das Prädikat matcht CONTRACT.md in jeder Tiefe. Folgenlos im Verhalten - ein
    Über-Match druckt eine Zeile zu viel, nie eine zu wenig -, aber es war eine behauptete Äquivalenz,
    die keine ist, geschrieben in genau der ungeprüften Prosa-Phase, um die #47 sich dreht. Docstring
    und tools/CONTRACT.md benennen die Differenz jetzt und begründen sie (bei einer Erinnerung ist
    Über-Matchen die richtige Richtung). Drittens: stack-close beschrieb den eigenen Skill-Schnitt
    zu stark („es gibt keinen nächsten Schritt mehr, an dem vorbei zu rutschen wäre"). Wahr für die
    Prozedur, die nicht mehr im Kontext steht; nicht wahr für den Auslöser - stack-devs „invoke
    it now" ist weiterhin ein Satz, und die publish-Notiz nennt den Skill bewusst nicht beim Namen.
    Zwei der drei Kettenglieder bleiben Selbstdisziplin. Der Skill sagt das jetzt selbst, statt sich
    als Garantie zu verkaufen, die er nicht ist.

    Verifiziert: tools/wikitool docs verify, tools/wikitool instructions verify,
    .venv/bin/python -m pytest -q (969 passed, 2 davon neu), release.yml gegen den YAML-Parser
    und das Heredoc als Trockenlauf gegen eine Beispiel-Notiz.


    This note is a snapshot of the CHANGES.md entry as it stood when the tag was cut, and
    is never edited afterwards. The maintained version of this text - including any later
    correction - is the entry for this version in CHANGES.md in the repository.

    Downloads
  • v4.6.0 d34924d640

    v4.6.0
    CI / verify (push) Successful in 58s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-04 13:33:22 +00:00 | 62 commits to main since this release

    4.6.0 - 2026-09-04 - stack-dev/stack-close skill split, publish stack-machinery note, model-selection fix (#47 Block 2)

    Author: Torben Nehmer

    • stack-dev/stack-close skill split, publish stack-machinery note, model-selection fix (#47 Block 2)

    Block 2 aus #47 (Vorschlag E, am 2026-09-04 entschieden): die ungeprüfte Schlussphase einer
    Stack-Sitzung - Issue-Body-Rewrite, docs/-Veralterung, Changelog-Prosa - hatte bisher keinen
    eigenen Haltepunkt, sondern einen Prosa-Break in stack-dev Schritt 6. Der ist zweimal
    hintereinander verschluckt worden (#42, #30), beide Male mit echtem Fund im nachgeholten
    Durchgang. Ein dritter Prosa-Haltepunkt hätte dieselbe Wette verloren, die
    docs/why-gates-are-code.md für Gates schon verliert - also keine Prosa-Lösung mehr, sondern ein
    struktureller Schnitt.

    Neuer Skill stack-close, dev-only wie stack-dev. stack-dev endet nach tools/wikitool publish mit einem Stop statt mit einem sechsten Schritt; die Schlussphase existiert nur noch als
    eigener Skill, den eine Sitzung aufrufen muss - es gibt keinen „nächsten Schritt" mehr, an dem
    vorbei sie rutschen könnte. stack-close trägt drei Dinge: den Modell-Rückwechsel-Hinweis (wie
    zuvor), die Body-Rewrite-Disziplin aus issue-tracking.md Schritte 2-3 und 7, und neu die
    Handover-Pflicht über die ganze Sitzung: benannt wird das Modell für Design/Versionsstelle
    (Schritt 3), für die mechanische Mitte, und für diese Schlussphase - alle drei, auch wenn sie
    identisch sind. Eine Handover-Zeile, die nur eine billige Schlussphase meldet, schweigt genau
    dann, wenn die ebenso ungeprüfte Design-Phase auch billig lief und niemand dort gewechselt hat.

    Ein Agenten-Zuschnitt (Schlussphase als eigener Subagent mit eigenem Modell) wurde geprüft und
    verworfen: ein Fork erbt in Claude Code zwingend das Elternmodell, ein frischer Subagent den
    Sitzungskontext nicht - die Kombination, die der Zuschnitt bräuchte, gibt es nicht, und selbst
    wenn: der Input der Schlussphase ist das akkumulierte Sitzungswissen, das ein kalter Agent aus
    Diff und Issue neu ableiten müsste. Volle Begründung im Body von #47.

    instructions/claude-code-model-selection.md korrigiert, im dist-strip-Block: die
    Übersicht „stack-dev Schritt 3 und 6" ist falsch geworden, seit Schritt 6 nicht mehr existiert.
    Sie benennt jetzt beide Haltepunkte an ihrem tatsächlichen Ort - Schritt 3 in stack-dev,
    der zweite am Anfang von stack-close.

    stack-dev Schritt 3 ehrlicher formuliert (Vorschlag C): nicht mehr „ab hier alles
    mechanisch", sondern mit benannter Ausnahme - Changelog-Prosa (Schritt 4), eine berührte
    docs/-Seite, neue Menschendoku, der Prosa-Anteil einer Instruction. Dazu die Einschränkung aus
    #30: „durch Tests abgedeckt" gilt nur für das, was die Tests treffen - zwei
    datenvernichtende Bugs in upstream merge liefen an einem grünen pytest/docs verify/instructions verify/CI vorbei, weil kein Test den Fall traf, nicht weil ein
    schwächeres Modell schlechteren Code für den getesteten Fall geschrieben hätte.

    Neu: tools/wikitool publish selbst erinnert an die Phasengrenze. Berührt das Changeset
    tools/, types/, instructions/, AGENTS.md oder ein <stage>/CONTRACT.md - derselbe
    Umfang, den ein Versions-Bump selbst abdeckt -, druckt publish nach der Erfolgsmeldung eine
    Zeile, dass die folgende Phase von keinem der drei Checks abgedeckt ist. Kein Gate, keine
    Änderung am Exit-Code, für eine gewöhnliche Content-Publish stumm; harness- und
    instanzneutral formuliert, ohne jede Erwähnung eines Trackers, weil publish von jedem
    Skill genutzt wird, nicht nur von stack-dev. git_publish.touches_stack_machinery() plus
    vier neue Tests (test_git_publish.py): zwei für die reine Klassifikationsfunktion
    (positiv/negativ), zwei Integrationstests gegen einen echten Publish - die Notiz erscheint genau
    einmal bei einer instructions/-Änderung und bleibt aus bei einer gewöhnlichen kb/-Änderung.
    tools/CONTRACT.mds publish-Zeile trägt die Kurzfassung, absichtlich ohne den Dateinamen
    version-parts.md zu nennen - die Datei liegt unter instructions/dev/ und würde in einer
    ausgelieferten Instanz ins Leere zeigen, während tools/CONTRACT.md selbst ausgeliefert wird.

    Verifiziert: tools/wikitool instructions sync (7 Skills, stack-close neu), tools/wikitool docs verify, tools/wikitool instructions verify, .venv/bin/python -m pytest -q (967
    passed, 4 davon neu).

    #47 bleibt offen für Block 3 (DEVELOPMENT.md in STAGE_READMES, veraltete Release-Notes).

    Downloads