• 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