• v7.0.0 4446424e01

    v7.0.0
    CI / verify (push) Successful in 58s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-22 20:56:08 +00:00 | 88 commits to main since this release

    7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join

    Author: Torben Nehmer

    Breaking Change:

    • docs verify now requires an adopted project type-spec (schema requiring state:) and its kb/gtd/ collection - an instance must adopt types/project.md(.schema.yaml) and kb/gtd/COLLECTION.md from their .template before docs verify passes again
    • superproductivity's provider section in .wikitool-tasks.json now requires 'access' ('api' or 'snapshot'), no default and no fallback between the two; 'db_path' no longer exists at all. An existing config must add 'access' and, if it used 'db_path', switch to 'backups_dir' (see INSTALL.md's example).

    Migration: none required - No kb/ page content changes - the break is confined to .wikitool-tasks.json, an instance-owned, gitignored file every operator already edits by hand per INSTALL.md's example.

    High impact

    • SP-Zugriffsweg explizit (access: api/snapshot, #133) und follow_up_at-Korrektur (dueWithTime/dueDay, #135)

    Medium impact

    • Typ project und Collection kb/gtd/: das Vorhaben als eigene Seitenart
    • Task-tracker provider layer, with a Super Productivity adapter
    • wikitool review: the weekly GTD review as a read-time join
    • wikitool new project: Seite und Tracker-Projekt unter einem Namen
    • Skill weekly-review: turning wikitool review's findings into decisions
    • Doku-Nachzug zu #119: die Verpflichtungsschicht erreicht Installation, Setup und docs/
    • task new: einen zweiten Schreibweg in den Tracker (ein Posten, keine Seite)
    • wiki-ingest: raw accept rückt hinter die Verpflichtungsentscheidung
    • Weekly review proposes task new/task close; tracker gains a closing write path

    Low impact

    • new project: Testabdeckung fuer die required-responsibility-Ablehnung
    • docs/-Nachzug: Exit 42 als Haltung, und die Adoption eines neu ausgelieferten Templates
    • Skill-Namensfamilien: weekly-review -> gtd-weekly-review, dritte Person in allen Descriptions
    • Veraltete Skill-Aufzaehlungen in der Instruction-Schicht nachgezogen
    • gtd-weekly-review: task new nachgezogen, veralteter Begründungszeiger korrigiert
    • instructions/CONTRACT.md drops other files' step counts from the copy-in-checklist rationale

    Dieser Kandidat bringt die Verpflichtungsschicht in den Stack: ein neuer Seitentyp project und
    die Collection kb/gtd/ geben dem Vorhaben (Ziel, Beteiligte, dauerhafter Status) eine eigene
    Seitenart neben dem Artefakt; eine Task-Tracker-Provider-Schicht (chemenu.tasks) verbindet das
    mit einem echten Tracker, mit Super Productivity als erstem Adapter; wikitool review liest beide
    Seiten zur Laufzeit zusammen statt sie zu synchronisieren, und das neue Skill
    gtd-weekly-review macht dessen Funde zu Entscheidungen. Der Tracker bekommt zwei neue
    Schreibwege dazu (task new, task close) neben den bestehenden Lesekommandos, und wiki-ingest
    stellt seither bei jeder Quelle die Verpflichtungsfrage in beide Richtungen. Zwei
    Grenzübertritte kommen mit: docs verify verlangt jetzt den adoptierten Typ project und die
    Collection kb/gtd/, und .wikitool-tasks.jsons Provider-Abschnitt verlangt ein explizites
    access (api/snapshot) ohne Fallback auf db_path. Der Rest der Bumps sind Nacharbeiten und
    kleinere Korrekturen an genau dieser Naht - Doku-Nachzug, Namensfamilien, eine korrigierte
    Zurückweisungs-Meldung, und die hier laufende Bereinigung veralteter Schrittzahlen in der
    Instruction-Schicht.

    Typ project und Collection kb/gtd/: das Vorhaben als eigene Seitenart

    Gitea #119 (Paket #123): ein neuer Seitentyp project fuer das Vorhaben - Ziel, Beteiligte,
    dauerhafter Status, offene Schleifen - abgegrenzt gegen das Artefakt (entity/codebase, seit
    6.2.0). types/project.md/.schema.yaml und die neue Collection kb/gtd/ (Bereiche haus/,
    finanzen/, technik/ ueber responsibility:) folgen exakt dem Muster, das entity/concept/
    source/comparison schon vorgeben - kein Code noetig fuer wikitool new project, types list
    oder den Template-Versand, alles daran ist bereits generisch.

    Neu ist nur eine Zeile Code: project tritt neben source in
    kb_collections.STACK_REQUIRED_TYPES, nach demselben "fordern statt besitzen"-Idiom (D16) - ein
    Type-Spec name: project, dessen Schema state: fuehrt, muss existieren, weil der
    Wochenrueckblick (#125) sonst nichts hat, wogegen er ein Tracker-Projekt abgleichen kann. Das
    macht docs verify zum Grenzuebertritt (siehe Breaking Change oben): eine Instanz, die die
    neue tools/-Fassung uebernimmt, ohne types/project.md.template und
    kb/gtd/COLLECTION.md.template zu adoptieren, faellt fortan durch, wo sie vorher bestand. Dabei
    aufgefallen und mitkorrigiert: kb_collections.declaration_issues()s Meldung fuer eine fehlende
    Pflicht-Collection nannte immer source, unabhaengig davon, welcher Typ tatsaechlich fehlte -
    jetzt benennt sie den Typ, den stack_required_collection_owners() tatsaechlich dafuer
    verantwortlich macht. kb/entities/COLLECTION.md traegt jetzt einen gtd:-Block (vorerst nur
    see-also), ohne den keine Kante von einer Entity auf ein Vorhaben autorisierbar waere - das ist
    der Block, auf den #118 wartet.

    Task-tracker provider layer, with a Super Productivity adapter

    Gitea #119 (Paket #124): die Schicht, ueber die wikitool an einen Aufgaben-Tracker kommt -
    ohne dass eine Instruction je erfaehrt, welcher es ist (D25). chemenu.tasks.protocol deklariert
    TaskReader/TaskWriter als getrennte Protocols, chemenu.tasks.superproductivity implementiert
    beide gegen Super Productivity, .wikitool-tasks.json (chemenu.tasks.config) traegt Provider,
    Verbindungsangaben und die drei Schwellwerte des Wochenrueckblicks (#125). wikitool doctor
    berichtet den konfigurierten Provider, seinen Lesepfad-Status und ob seine lokale REST-API
    antwortet - read-only, FAILt nur auf eine kaputte Konfiguration, nie auf einen nicht laufenden
    Tracker. Kein Kommando entsteht hier (#124s eigene Abgrenzung) - das ist #125/#126.

    Zwei Zwischenbefunde aus der Umsetzung, gegen den tatsaechlichen Quellcode von
    super-productivity/super-productivity (master, 2026-09-19) verifiziert:

    • Der Lesepfad liest keine db.json - die gibt es auf dem Desktop nicht, der Live-Zustand
      liegt in IndexedDB. Gelesen wird die neueste Datei unter dessen periodischen
      Dateisystem-Backups (electron/backup.ts, <userData>/backups/<timestamp>.json), deren
      Inhalt exakt die verifizierte Form hat.
    • Die lokale REST-API kann keine Projekte anlegen - GET /projects existiert,
      POST /projects nicht (electron/local-rest-api-handler.service.ts). Damit entfaellt fuer
      diesen Provider der in #119 D31 vorgesehene automatische Schreibpfad; create_project prueft
      weiterhin die Namenskollision (D8), verlangt dann aber menschliches Eingreifen statt es zu
      simulieren: SuperProductivityWriter.create_project wirft ein neues
      chemenu.errors.HumanInterventionRequired mit Anweisungen fuer den Menschen und einem
      verify(), das den Lesepfad danach erneut befragt statt der Bestaetigung einfach zu glauben.
      Dieselbe Klasse haengt sich an den bestehenden EXIT_NEEDS_CLEARANCE-Code (42) - keine neue
      benannte Gate, aber dieselbe Haltung: dem Menschen die Ausgabe zeigen und anhalten, statt eine
      Umgehung zu erfinden. Die CLI-seitige Uebersetzung (needs_clearance) folgt mit dem Kommando
      in #126; #124 liefert nur die Bibliotheksseite. #119s Umsetzungstabelle und #124s eigener
      Akzeptanzkriterien-Absatz sind entsprechend nachgezogen.

    Ausserdem verifiziert, ohne Designfolgen: Super Productivitys Someday/Maybe-Aequivalent ist der
    bestehende backlogTaskIds-Puffer je Projekt, keine eigene Tag-Konvention.

    wikitool review: the weekly GTD review as a read-time join

    Gitea #119 (Paket #125): das tragende Bauteil - wikitool review joint die Tracker-Seite
    (chemenu.tasks, #124) und die kb/gtd/-Projektseiten ueber den case-normalisierten Namen und
    gibt einen Bericht aus. Es speichert nichts, nicht einmal eine reports/-Datei (D3) - search
    ist das naechste Vorbild dafuer, und review ist deshalb genauso vom Iterationsbudget
    ausgenommen.

    Fuenf Pruefungen (#119 D10/D26), alle in chemenu.review.run_review: stalled (Tracker-
    Projekt ohne offene Posten, kb/-Seite state: active - dormant/completed/abandoned
    melden nie, D27), waiting_overdue (follow_up_at aelter als stalled_waiting_days),
    unpaged_project (Tracker-Projekt ohne kb/-Seite, aelter als unpaged_project_weeks),
    no_open_loop (kb/-Seite active, aber kein Tracker-Projekt dieses Namens oder keine
    offenen Posten - die Gegenrichtung des vorigen Abgleichs, D8s beidseitiger unmatched-Bericht),
    someday_stale (Someday-Posten seit someday_stale_months unveraendert, ueber Kalendermonate
    gerechnet statt ueber Tage / 30). Ein Tracker-Projekt ohne offene Posten mit aktiver kb/-Seite
    erfuellt zugleich stalled und no_open_loop - beide melden, das ist keine Dopplung, sondern zwei
    verschiedene Aussagen ueber denselben Zustand.

    Jeder Providerzugriff ist einzeln abgesichert: scheitert projects(), entfallen die vier darauf
    aufbauenden Pruefungen; scheitert someday_items(), entfaellt nur die fuenfte; scheitert
    open_items() fuer ein einzelnes Tracker-Projekt, faellt nur dieses eine aus den betroffenen
    Pruefungen heraus, der Rest laeuft weiter. Ein so unvollstaendiger Bericht setzt complete auf
    false, druckt trotzdem alles, was noch entschieden werden konnte, und die CLI beendet sich mit
    Exit 1 - nie mit einem leisen Teilbericht, der wie eine ruhige Woche aussieht. Fehlt
    .wikitool-tasks.json ganz, oder ist es kaputt, scheitert der Aufruf sofort und sagt das - das
    ist ein Konfigurationsfehler, kein Erreichbarkeitsproblem, und braucht deshalb keinen Teilbericht.

    --json traegt dieselben Befunde maschinenlesbar (findings/checks_run/checks_skipped/
    kb_project_count/complete); ein Test haelt beide Formen gegeneinander, wie es
    test_mcp_server.py fuer den MCP-Lesepfad gegen die CLI tut.

    wikitool new project: Seite und Tracker-Projekt unter einem Namen

    Gitea #119 (Paket #126): wikitool new project --name X --set responsibility=Y legt jetzt, wenn
    .wikitool-tasks.json einen Tracker konfiguriert, zusaetzlich ein gleichnamiges Tracker-Projekt
    an - ein Geburtsort, ein Name (D8/D31). Tracker vor Seite: erst steht die Tracker-Seite fest,
    erst danach wird die kb/-Seite geschrieben, damit ein Fehlschlag zwischen beiden immer im
    selben, bereits bekannten Zustand landet - "Tracker-Projekt ohne Seite", das reviews Pruefung 3
    ohnehin meldet - nie im unbekannten "Seite ohne Tracker-Projekt". Ist kein Tracker konfiguriert,
    bleibt es bei der reinen Seitenanlage, jetzt aber ausdruecklich als solche vermerkt statt
    stillschweigend.

    chemenu.tasks.build_reader/build_writer (neu in chemenu/tasks/__init__.py) sind die eine
    Dispatch-Tabelle von TasksConfig.provider auf einen konkreten Adapter, jetzt von review.py
    und new_page.py geteilt statt zweimal derselben if cfg.provider == "superproductivity".

    Fuer einen Provider ohne Schreibpfad (Super Productivity, #124: keine POST /projects) wirft
    create_project chemenu.errors.HumanInterventionRequired - das Kommando zeigt die Anweisung
    und beendet sich mit Exit 42, ohne irgendetwas anzulegen. Die offene Frage aus #126s eigenem
    Issue-Text war, wie ein zustandsloser CLI-Prozess bei einem erneuten Aufruf eine echte
    Namenskollision von "der Mensch hat gerade getan, worum genau dieses Kommando gebeten hat"
    unterscheidet - beides sieht am Lesepfad identisch aus (Tracker hat den Namen, kb/ noch keine
    Seite). Entschieden (mit dem Betreiber, nicht allein): ein explizites --resume, das ein Treffer
    im Tracker als bestaetigte Fortsetzung liest statt als Kollision - ohne --resume bleibt jeder
    Treffer eine Ablehnung samt Fundort, auch bei einem Wiederholungsaufruf. --resume ohne einen
    tatsaechlich fehlenden Tracker-Eintrag wirft dieselbe HumanInterventionRequired-Meldung erneut,
    keine stille Weiterarbeit auf Zuruf. --resume bei jedem anderen Typ wird abgelehnt.

    Ein erzwungener Fehlschlag der eigentlichen Seiten-Schreibaktion (Schritt 3) nach bereits
    bestaetigtem Tracker-Projekt ist eigens getestet: die Meldung nennt, dass die Tracker-Seite schon
    steht und nur die kb/-Seite fehlt, nie umgekehrt.

    new project: Testabdeckung fuer die required-responsibility-Ablehnung

    Gitea #119 (Paket #126s eigenes AC): --responsibility ist Pflicht und ein Wert ausserhalb des
    Enums wird abgelehnt - beides galt schon vorher generisch ueber types/project.schema.yamls
    required:/enum: (#123), ohne dass #126 dafuer neuen Code brauchte. Nachgetragen: ein Test,
    der das fuer den fehlenden Fall (--set responsibility=... ganz weggelassen) tatsaechlich belegt,
    statt es nur zu behaupten.

    Skill weekly-review: turning wikitool review's findings into decisions

    Gitea #119 (Paket #127): instructions/weekly-review/SKILL.md, publiziert nach .agents/skills/
    und .claude/skills/. wikitool review liefert fuenf Befunde (#125); dieser Skill fuehrt das
    Gespraech, das aus jedem eine Entscheidung macht - je Befund mindestens zwei Handlungsoptionen und
    ein Unterscheidungsmerkmal, wie in #127s Akzeptanzkriterien gefordert.

    Der Skill nennt bewusst keinen Provider, keine Datei- und keine API-Form (D25) - eine neue
    Regressionstest (test_weekly_review_skill_names_no_provider) haelt das am echten Repo-Inhalt
    fest, nicht nur als Review-Behauptung. Erinnert im Text an D28 (Personen in ## Beteiligte
    bleiben Erwaehnung, bekommen keine Seite) und D7 (die Seite fasst die Aufgabenliste nie
    zusammen). Die Kommandoflaeche bleibt bei review/new project; alles Aufgabenbezogene - eine
    naechste Aktion anlegen, follow_up_at verschieben, einen Someday-Eintrag streichen - bleibt eine
    Handlung im Tracker selbst, weil dafuer kein wikitool-Kommando existiert (D31).

    Doku-Nachzug zu #119: die Verpflichtungsschicht erreicht Installation, Setup und docs/

    Gitea #119, nach Abschluss von #122-#127: die sechs Umsetzungspakete haben ihre Vertragszeilen
    jeweils mitgebracht (tools/CONTRACT.md, kb/CONTRACT.md), aber drei Flaechen blieben zurueck,
    die kein Paket fuer sich allein besass - und keine davon faellt bei docs verify auf, weil dort
    keine Zeile fehlt, sondern Prosa.

    • INSTALL.md § Konfiguration kannte .wikitool-tasks.json nicht. Die Datei stand in
      tools/CONTRACT.mds doctor-Zeile und in reviews Fehlerkontrakt, also dort, wo ein Agent
      nachschlaegt - nur nicht dort, wo ein Mensch die Form nachschlaegt. Sie steht jetzt neben
      .wikitool-telemetry.json und .wikitool-remotes.json, mit vollstaendigem Beispiel, den drei
      Schwellwerten als Konfiguration statt Schema, und dem fuer Super Productivity getrennten
      Lese-/Schreibpfad. Die doctor-Beschreibung unter § Verifikation nennt den Tracker jetzt mit.
    • setup-instance.md bot den Tracker nie an. Eine neue Instanz bekam Typ und Collection ueber
      die generische Template-Adoption (Schritt 5), aber nichts fragte nach der anderen Haelfte des
      Rueckblicks. Neuer Entscheidungspunkt (Schritt 11, parallel zur Telemetrie): einmal fragen,
      .wikitool-tasks.json anlegen oder nichts tun - kein Tracker ist ein gueltiger Endzustand. Die
      Form steht nicht hier, sondern in INSTALL.md (Invariante 8). Folgenummern 12-16 nachgezogen,
      Skill-Liste um weekly-review ergaenzt.
    • upgrade-instance.md hatte keinen Pfad fuer ein neu ausgeliefertes .template. Genau der
      Fall, den 7.0.0 erzwingt: Schritt 5 sagte „new braucht keine Entscheidung", und die
      Schritt-6-Tabelle sagt, instanzeigene Dateien koennten dort gar nicht auftauchen - beides
      richtig und zusammen irrefuehrend, weil ein neues types/<name>.md.template fuer einen
      geforderten Typ sehr wohl eine Handlung braucht. Schritt 5 benennt diese eine Ausnahme jetzt,
      Schritt 9 traegt die Reparatur neben der TOC-Reparatur: die gewoehnliche Adoption, mit den zwei
      cp-Zeilen, ausdruecklich keine Datenmigration.

    Dazu die Begruendung selbst: docs/knowledge-and-commitment.md ist neu und haelt fest, warum
    Wissen und Verpflichtung zwei Schichten sind (verschiedene Halbwertszeiten), warum nicht
    synchronisiert wird (Muster 4, mit den drei verworfenen Anordnungen), warum der Join zur Lesezeit
    passiert und nichts speichert, warum ein Name die Pflichten eines Identifiers erbt, warum eine
    Seite ihre Aufgabenliste nie zusammenfasst, warum Archivierung ein state:-Wert ist, und warum
    keine Instruction je den Provider nennt. Das stand bisher ausschliesslich in Gitea #119 - und ein
    Issue ist genau das, was dist export nicht mitliefert: eine ausgelieferte Instanz bekam den
    Mechanismus ohne das Warum. AGENTS.md § File naming zaehlt jetzt sechs statt fuenf von dort
    verlinkte docs/-Seiten.

    tools/README.md § Layout fuehrt review.py und das Paket tasks/; und
    instructions/dev/doc-pull-through.md bekommt die zwei Zeilen, deren Fehlen dieser Nachzug ist:
    eine fuer eine instanzeigene Konfigurationsdatei (INSTALL.md § Konfiguration + der
    setup-instance-Entscheidungspunkt + doctors Vertragszeile), eine fuer einen neuen geforderten
    Seitentyp oder eine neue Collection (Type-Spec/COLLECTION.md + kb/CONTRACT.md + beide
    Adoptionspfade). Die Zeile zur docs/-Begruendung sagt jetzt zusaetzlich, dass eine Entscheidung
    ohne Seite die eigentliche Luecke ist, weil Begruendungen aus einem Issue nie ausgeliefert werden.

    docs/-Nachzug: Exit 42 als Haltung, und die Adoption eines neu ausgelieferten Templates

    Die Abschlusspruefung dieses Kandidaten (stack-close Schritt 3) hat zwei docs/-Seiten gefunden,
    deren Begruendung die Pakete #124/#126 bzw. #123 verschoben hatten, ohne dass jemand sie nachzog -
    beide unpruefbar, weil eine docs/-Seite per Konstruktion keinen normativen Satz traegt.

    • docs/why-gates-are-code.md kannte Exit 42 nur als Gate. Die Seite oeffnet mit „vier harte
      Grenzen", und seit #124/#126 verlaesst HumanInterventionRequired denselben Code, ohne eine
      fuenfte Gate zu sein. Neuer Abschnitt „Exit 42 is a posture, and it outgrew the gates": die vier
      Gates teilen eine Weigerung (die Operation waere moeglich, das Werkzeug fuehrt sie
      unbesehen nicht aus), der neue Fall ist das Gegenteil (die Operation ist gar nicht moeglich, kein
      Token koennte das aendern) - gemeinsam ist beiden nur, was der Exit-Code tatsaechlich sagt:
      anhalten, einem Menschen zeigen, nichts umgehen. Dazu die verworfene Alternative, die Luecke in
      der Instruction-Schicht zu beschreiben - genau die Prosa-Regel, gegen die diese Seite argumentiert.
    • docs/ownership-and-templates.md beschrieb die .template-Kategorie nur von innen. „Wird
      von einem Upgrade nie geschrieben" stimmt weiterhin fuer eine bereits adoptierte Datei; der Fall,
      dass ein Release ein Template fuer einen Typ liefert, den die Instanz noch gar nicht hat, stand
      nirgends - und genau der ist seit #123 der Grenzuebertritt. Die Kategorie nennt ihn jetzt als die
      eine Gestalt, in der „das Upgrade schreibt diese Datei nie" zu Arbeit wird, mit Verweis auf den
      Schritt in upgrade-instance.md, den Stack 7.0.0-beta.7 dort angelegt hat.

    Skill-Namensfamilien: weekly-review -> gtd-weekly-review, dritte Person in allen Descriptions

    Gitea #129: the published skill collection had drifted into three naming shapes where it should
    have three families. weekly-review (added earlier in this same candidate, never released)
    named neither its domain nor its distribution boundary, unlike wiki-* and stack-* either
    side of it - renamed to gtd-weekly-review, joining a new gtd- prefix for the commitment layer
    (kb/gtd/, types/project.md, docs/knowledge-and-commitment.md) that sits beside wiki- (the
    knowledge pipeline) and stack- (the stack's own development, under instructions/dev/).
    instructions/CONTRACT.md § "Writing an instruction" now states both the family-prefix
    convention and, separately, that a skill's description speaks in third person per Anthropic's
    skill-authoring guidance - all eight published skills' descriptions were rewritten to match
    ("Processes...", not "Process..."); the flat instructions/<name>.md form keeps its existing
    imperative-title convention, since its description is read on demand rather than injected into
    the system prompt. No --breaking line: the renamed skill was introduced by this same
    unreleased candidate, so no existing instance carries the old name to migrate away from.

    Veraltete Skill-Aufzaehlungen in der Instruction-Schicht nachgezogen

    Gitea #129s Abschlusspruefung: die Skill-Umbenennung hat sichtbar gemacht, dass mehrere Dokumente
    die Skill-Liste hart aufzaehlen und beim Wachsen der Liste still veralten. Vier Stellen waren
    falsch, eine davon schon vor diesem Paket:

    • instructions/CONTRACT.md § "When a skill carries a copy-in checklist" zaehlte „die zwei
      Skills mit Block und die drei ohne" - also fuenf von inzwischen acht. Die Zahlen im Einstiegssatz
      sind jetzt ganz raus (sie unterscheiden sich ohnehin zwischen diesem Repo und einer
      ausgelieferten Instanz, die instructions/dev/ nicht hat), die Aufzaehlung nennt alle
      ausgelieferten Skills, und die beiden Dev-Skills stehen in einem dist:strip-Block.
    • Dieselbe Passage nannte wiki-query mit sechs Schritten - es sind sieben, und zwar schon
      laenger. Die Regel selbst (ein Flow ab acht Schritten und still scheiternde Schritte) bleibt
      unveraendert; kein Skill wechselt dadurch die Seite.
    • instructions/CONTRACT.md § "outbound reference" sprach im Praesens von „this repo's seven
      skills", wo die Zahl zu einer datierten Messung (52 von 58 Links) gehoert - jetzt als „at the
      time" markiert, statt die Messung nachzurechnen.
    • instructions/bootstrap.md und die Scope-Abschnitte von stack-dev/stack-close listeten
      die Content-Skills ohne gtd-weekly-review auf.

    Dazu test_the_real_repo_publishes_the_six_wiki_skills -> ..._every_skill: der Test pruefte
    sechs der acht Skills und trug die veraltete Zahl im Namen; er nennt jetzt alle acht, nach
    Familien erklaert. Dass die Passage weiterhin Schrittzahlen fremder Dateien zitiert, die genauso
    still veralten koennen, ist als eigene Frage festgehalten und hier bewusst nicht geloest.

    SP-Zugriffsweg explizit (access: api/snapshot, #133) und follow_up_at-Korrektur (dueWithTime/dueDay, #135)

    Gitea #133: der Super-Productivity-Adapter unterscheidet jetzt zwei sich ausschliessende
    Zugriffswege, per Pflichtfeld access ohne Default und ohne Laufzeit-Ruckfall - "api" liest
    und (fuer #132 vorbereitet) schreibt ausschliesslich ueber die lokale REST-API und den aktuellen
    Zustand, "snapshot" liest ausschliesslich den juengsten Backup-Schnappschuss und ist von dort
    aus nie schreibbar. db_path entfaellt vollstaendig, backups_dirs Glob ist auf das echte
    Zeitstempelmuster (YYYY-MM-DD_HHmmss.json) gehaertet, und beide Wege blenden archivierte
    Tracker-Projekte aus - verifiziert gegen den tatsaechlichen Quellcode von
    super-productivity/super-productivity (master, 2026-09-20). SuperProductivityApiReader ist
    der neue Leser fuer access: "api", gegen eine Attrappe getestet, nie gegen eine laufende App.
    tools/wikitool new project verweigert auf einer access: "snapshot"-Instanz jetzt vollstaendig
    (Exit 1, weder Tracker-Projekt noch Seite) statt den nicht mehr moeglichen 42er-Menschenschritt zu
    versuchen; wikitool doctor und wikitool review berichten nur noch den tatsaechlich
    konfigurierten Weg, und jede Antwort von review nennt jetzt explizit, aus welchem Weg sie
    stammt (bei snapshot samt Alter des gelesenen Schnappschusses). Aufgeloest damit: #134, dessen
    Verdacht (--resume koennte gegen einen veralteten Schnappschuss verifizieren) durch den Wegfall
    der Projektanlage auf snapshot-Instanzen gegenstandslos wurde.

    Gitea #135, im selben Zug korrigiert: follow_up_at las bislang remindAt, das sich nur bei
    einer mit Uhrzeit terminierten und benachrichtigten Aufgabe fuellt - ein ganztaegiger, stiller
    Tickler (dueDay ohne dueWithTime, das haeufigste WAITING-Muster) hatte dadurch nie einen
    follow_up_at und fiel bei Pruefung 2 des Wochenrueckblicks still durch. Gelesen wird jetzt
    dueWithTime, sonst dueDay - Super Productivitys eigene Leseregel - nie deadline* (D9 bleibt
    in der Sache unveraendert, nur die falsche Berufung auf sie ist korrigiert). Bestehende Instanzen
    sehen dadurch rueckblickend mehr Befunde, nicht weniger.

    task new: einen zweiten Schreibweg in den Tracker (ein Posten, keine Seite)

    Gitea #132: eine Quelle kann Wissen und eine Verpflichtung zugleich tragen (eine Kundenreklamation
    etwa), und bislang hatte nur die Wissenshaelfte einen Schreibweg. chemenu.tasks.protocol.TaskWriter
    traegt jetzt eine zweite Methode, create_item - Projekt, Titel, optional WAITING mit
    follow_up_at, optional ein Freitext-Rueckverweis in notes -, darueber das neue Kommando
    wikitool task new. Anders als create_project schreibt sie tatsaechlich: bei Super Productivity
    existiert POST /tasks, wo POST /projects fehlt, also gibt es hier keinen
    HumanInterventionRequired-Fall. Das Kommando ordnet kein Projekt selbst zu - ein --project, das
    zu keinem Tracker-Projekt passt, oder --waiting ohne vorhandenen waiting-Tag scheitert laut,
    exit 1, statt zu raten oder einen Posten ohne seinen Status anzulegen. Die Eingangs-Ablage
    (--inbox) ist eine eigene, ausdrueckliche Form am Kommando, nie ein Ersatz fuer ein vergessenes
    --project.

    Verifiziert gegen super-productivity/super-productivity@master (2026-09-20): Super Productivitys
    INBOX_PROJECT ist zwar immer ein echtes Projekt-Entity im Store, aber
    selectUnarchivedProjects - der Selektor hinter GET /projects - filtert es ueber seine feste id
    unbedingt heraus. Ein per --inbox abgelegter Posten erscheint deshalb in keiner
    wikitool review-Pruefung, nicht weil eine Ausnahme dafuer noetig waere, sondern weil der Eingang
    in der Projektliste schlicht nie auftaucht - der Ingest-Skill nennt diese Kosten jetzt ausdruecklich,
    wenn er die Route anbietet.

    Der Ingest-Skill (instructions/wiki-ingest/SKILL.md) fragt in Schritt 5 jetzt auch nach einer
    Verpflichtung, nicht nur nach dem Wissen, und legt Titel und vorgeschlagenes Projekt in einem
    Bestaetigungsschritt vor (nie eine automatische Zuordnung, auch nicht bei einem eindeutigen
    search-Treffer). Der Posten wird vor der Quellenseite angelegt - dieselbe Tracker-vor-Seite-
    Reihenfolge, die new project schon haelt, hier mit eigenem Beleg: eine Rohdatei ohne Quellenseite
    meldet lint als uncovered_raw_files, eine stillschweigend verlorene Verpflichtung meldet
    nichts. Der bestehende Regressionstest, der sicherstellt, dass kein Skill den Tracker-Provider
    nennt, ist entsprechend auf wiki-ingest erweitert. docs/knowledge-and-commitment.md und
    tools/CONTRACT.md sind nachgezogen; Gitea #128 (der zweite Adapter) traegt jetzt create_item
    in seiner eigenen Flaeche.

    wiki-ingest: raw accept rückt hinter die Verpflichtungsentscheidung

    Gitea #136, eine offen gebliebene Teilfrage aus #132: raw accept lief dort weiterhin ganz am
    Anfang des Laufs, vor Lesen, Diskussion und Verpflichtungserkennung - scheiterte task new, fand
    sich eine bereits nach raw/ befoerderte Rohdatei vor, obwohl #132s eigenes Kriterium "keine
    Seite geschrieben und keine Rohdatei befoerdert" verlangte. docs/knowledge-and-commitment.md
    behauptete diese Eigenschaft seit demselben Commit bereits als Tatsache; der Baum beschrieb ein
    Design, das es nicht gab.

    wiki-ingests Schritte 1-5 sind neu geordnet: lesen, Metadaten, search, Diskussion inklusive
    Verpflichtung und task new, dann erst raw accept. Die Schritte 6-12 behalten ihre Nummern
    unveraendert, ebenso jeder Fremdverweis, der eine dieser Nummern nennt. Zwei Stellen sind dabei
    verschaerft, nicht nur verschoben: die fidelity/authority-Frage in Schritt 5 benennt jetzt
    ausdruecklich, dass die Datei zu diesem Zeitpunkt schon vollstaendig gelesen ist - der Moment, in
    dem die Versuchung, den Wert aus dem Inhalt zu erschliessen statt ihn zu erfragen, am groessten
    ist -, und derselbe Schritt benennt, dass seine Kollisionsverweigerung jetzt spaeter faellt, nach
    Lesen, Diskussion und moeglicherweise bereits angelegtem Tracker-Posten.

    Eine Ausnahme bleibt bewusst bestehen: ein Lauf, der wegen Volumen oder Breite an
    instructions/ingest-large-tree.md uebergibt, befoerdert weiterhin vor der Verpflichtungsfrage -
    dessen work new --input <pfad> verweigert jeden Pfad ausserhalb raw/, und der Lauf ist ohnehin
    nicht atomar, da er unit-weise ueber Tage publiziert und seine eigene Verpflichtungsfrage erst in
    Schritt 5d je Unit stellt. docs/knowledge-and-commitment.md nennt diese Ausnahme jetzt explizit,
    statt die Eigenschaft unbedingt zu behaupten. README.md und instructions/CONTRACT.md sind
    nachgezogen.

    Zwei Fragen, die sich beim Durchsehen der Naht zwischen Wissen und Verpflichtungen zusaetzlich
    zeigten - gtd-weekly-reviews veraltete Zaehlung der GTD-Kommandos, und ob der Weekly Review
    task new kuenftig anbieten soll -, sind bewusst nicht Teil dieser Aenderung: Gitea #137 und #138.

    gtd-weekly-review: task new nachgezogen, veralteter Begründungszeiger korrigiert

    Gitea #137, der liegengebliebene Pull-Through von #132 am GTD-Rand: instructions/gtd-weekly- review/SKILL.md behauptete zweimal, review und new project seien die einzigen zwei
    GTD-Kommandos, und begruendete die Haltung "der Nutzer handelt selbst in seinem Tracker" mit
    "because wikitool has no command for it" - seit #132s task new schlicht falsch. Der
    Begruendungszeiger fuer die schmale Kommandoflaeche zeigte zudem auf types/project.md und
    kb/gtd/COLLECTION.md; die Begruendung steht tatsaechlich in
    docs/knowledge-and-commitment.md § "Status has exactly one home", die #132 korrekt nachzog,
    waehrend dieses Skill unberuehrt blieb.

    Beide Stellen benennen task new jetzt als drittes, tatsaechlich vorhandenes Kommando, das dieses
    Skill bewusst nicht aufruft - die Haltung selbst ist unveraendert, nur ihre Begruendung ist jetzt
    eine Wahl statt eine Behauptung ueber eine fehlende Faehigkeit. Ob der Weekly Review task new
    kuenftig anbieten soll, bleibt unentschieden in Gitea #138; dieses Issue korrigiert nur, was
    nachweislich falsch dastand.

    Weekly review proposes task new/task close; tracker gains a closing write path

    Gitea #138 entschied die dort offene Frage: der Weekly Review bietet task new jetzt bei
    stalled/no_open_loop Option (a) an, nach ausdruecklicher Bestaetigung von Titel und Projekt in
    einer Frage - dieselbe Haltung wie im Ingest. Die zweite Haelfte derselben Naht war unentschieden
    liegen geblieben: eine Quelle kann eine Verpflichtung anlegen, aber nie schliessen. Der Tracker
    bekommt dafuer einen dritten, letzten Schreibweg, task close --id, der einen Posten erledigt
    markiert - niemals loescht, verifiziert gegen Super Productivitys master-Branch, dass
    PATCH /tasks/:id mit isDone: true bit-identisch zur eigenen "erledigt"-Checkbox der App ist.
    task list --project liefert dazu die Item-Ids, die task close und die Review-Funde fuer
    waiting_overdue/someday_stale jetzt mitfuehren. wiki-ingest stellt die Verpflichtungsfrage
    seither in beide Richtungen (oeffnen und schliessen), und ingest-large-tree Schritt 5d begruendet
    jetzt in einem Satz, warum diese Frage pro Unit gestellt wird statt einmal pro Baum.

    Die zwei Instruktionsstellen, an denen die Haltung zum Tracker-Schreibzugriff bislang doppelt
    stand, sind auf eine zusammengezogen; die zweite verweist nur noch.
    docs/knowledge-and-commitment.md § "Status has exactly one home" zaehlt die Kommandoflaeche
    korrekt (zwei Lese-, drei Schreibkommandos) und haelt fest, warum sie bei "anlegen" und "erledigt
    markieren" endet, nie bei "loeschen" oder "aendern".

    instructions/CONTRACT.md drops other files' step counts from the copy-in-checklist rationale

    instructions/CONTRACT.md § "When a skill carries a copy-in checklist" used to justify the
    threshold by citing each other skill's step count by number - evidence that the two-halves test
    (length and a silently-omittable step) is what actually decides wiki-ingest/wiki-lint, not a
    count fitted after the fact. Nothing kept those numbers in sync with the SKILL.md files they
    described: one of them had already drifted silently (wiki-query cited at six steps where it had
    been seven for a while), and the passage itself named only five of the eight skills that exist -
    neither wrong number failed any check, because docs verify reads presence, not another file's
    prose (instructions/dev/doc-pull-through.md).

    Of the three fixes considered - a docs verify check against a codified step-counting convention,
    a qualitative rewrite that drops the numbers, or tracking the sync as a manual doc-pull-through.md
    duty - the qualitative rewrite won: it is the only one of the three that removes the possibility of
    drift rather than catching or documenting it, and the per-skill counts were decoration for the
    two-halves test, never load-bearing for it. The passage now names which skills qualify and why,
    without citing a number that belongs to a file it does not own. Today's counts were checked against
    the actual files before this bump (wiki-ingest twelve, wiki-lint nine, wiki-manage two flows
    of seven, wiki-query seven, wiki-status five, gtd-weekly-review five, stack-dev six,
    stack-close four) - all correct, confirming the passage was not itself wrong, only unguarded.


    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
  • v6.2.0 3c9d669729

    v6.2.0
    CI / verify (push) Successful in 48s
    Release / release (push) Successful in 39s
    Stable

    torben released this 2026-09-19 15:45:07 +00:00 | 106 commits to main since this release

    6.2.0 - 2026-09-19 - Entity-Subtyp project nach codebase umbenannt

    Author: Torben Nehmer

    • Entity-Subtyp project nach codebase umbenannt

    Entity-Subtyp project nach codebase umbenannt

    entity_type: project bezeichnete in diesem Korpus ausnahmslos Codebasen; Gitea #119 zieht
    daraus einen eigenen Typ fuer das Vorhaben (gtd/project, Paket #123), wodurch der bisherige
    Wert homonym geworden waere. types/entity.schema.yaml und types/entity.md fuehren jetzt
    codebase statt project, kb/entities/projects/ heisst kb/entities/codebases/, und die elf
    betroffenen Seiten wurden ausschliesslich ueber wikitool touch/move umgezogen. Kein
    Grenzuebertritt: types/entity.md ist .template-basiert, dist upgrade schreibt nie die
    adoptierte Kopie einer Instanz, nur den mitgelieferten Standard daneben - eine bestehende
    Instanz behaelt ihren eigenen project-Wert unangetastet und uebernimmt die Umbenennung erst,
    wenn sie es sich vornimmt.


    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
  • v6.1.0 11c400c670

    v6.1.0
    CI / verify (push) Successful in 55s
    Release / release (push) Successful in 41s
    Stable

    torben released this 2026-09-17 07:18:09 +00:00 | 107 commits to main since this release

    6.1.0 - 2026-09-17 - Upgrade-Pfad und Iteration-Budget-Gate gehaertet, wiki/-Pfadliterale bereinigt

    Author: Torben Nehmer

    High impact

    • Session-Id-Fallback: Harness-Variable schliesst die Luecke zwischen Telemetrie-Join und Iteration-Budget-Gate

    Medium impact

    • Upgrade-Prozedur als eigene Instruktion statt als Prosa in INSTALL.md
    • Migrationsdokument prueft gegen eine festgehaltene Vorher-Ausgabe, Beispielverweis auf die .template-Form
    • dist upgrade: --take-release nimmt fuer einen lokal geaenderten Pfad die Release-Fassung
    • version notes antwortet auf einer ausgelieferten Instanz aus dem Release-Feed
    • new: scaffold materializes a schema default only for a required field

    Low impact

    • Stale wiki/ path literals swept out of tools/ and types/, with a test guarding against the next rename
    • CHANGES.md/Guard-Docstring: die Zahl der nachgezogenen Pfadliterale korrigiert (33, nicht 27)

    Dieser Kandidat sammelt, was ein getraceter 5.0.0-auf-6.0.0-Upgrade-Lauf auf einer echten Instanz
    offengelegt hat: ein fehlender Upgrade-Leitfaden, zwei falsche Verweise im Migrationsdokument,
    eine fehlende dritte Antwort in dist upgrade fuer eine lokal geaenderte Datei, und
    version notes, das auf einer ausgelieferten Instanz nie antworten konnte. Im selben Lauf zerfiel
    die Sitzung durch einen PID-basierten Session-Id-Fallback in 21 Telemetrie-Buckets, wodurch das
    Iteration-Budget-Gate strukturell unerreichbar blieb - behoben durch eine Registry bekannter
    Harness-Session-Variablen, mit einem SIGPIPE-Nebenbefund im CLI-Emitter gleich mit. Dazu,
    unabhaengig vom getraceten Lauf: ein Scaffold-Fix, der obligation: required nicht mehr in jede
    neue Instruktion schreibt, und eine Bereinigung von 33 stehengebliebenen wiki/-Pfadliteralen aus
    der wiki/-nach-kb/-Umbenennung, mit einem Test-Guard gegen die naechste Umbenennung.

    Kein Grenzuebertritt: jede Aenderung ist in beide Richtungen ein Drop-in, additiv gegenueber
    6.0.1.

    Upgrade-Prozedur als eigene Instruktion statt als Prosa in INSTALL.md

    Der Upgrade-Pfad einer ausgelieferten Instanz stand nur in INSTALL.md § "Eine Instanz
    aktualisieren" - einem Dokument fuer Menschen, das AGENTS.md § File naming ausdruecklich als
    "never by an agent as instruction" fuehrt. Ausgefuehrt wird er aber von einer Agent-Sitzung,
    jedes Mal. Der getracete 5.0.0-auf-6.0.0-Lauf auf einer echten Instanz zeigt, was daraus folgt:
    der erste Tool-Call listete instructions/ mit, fand keine passende Instruktion, oeffnete die
    naechstliegende (private-instance.md, der falsche der beiden Wege) und fiel dann auf INSTALL.md
    zurueck. migrate verify --from <commit vor dem Tausch> - INSTALL.md Schritt 6, erster
    Pruefschritt - lief in 33 Werkzeugaufrufen kein einziges Mal, und die Agent-Sitzung wurde nie neu
    gestartet, obwohl AGENTS.md im selben Commit +44/-3 bekommen hatte. Die anschliessende Migration
    lief damit unter dem alten Kontrollplan.

    Dahinter lagen drei Reihenfolgen nebeneinander: die in INSTALL.md, die im Abschlussbericht von
    dist upgrade, und die tatsaechlich gelaufene. Genau der Zustand, den Invariante 8 verbietet.

    instructions/upgrade-instance.md ist jetzt die eine Fassung: dreizehn Schritte von der
    Sitzungs-Id bis zum zweiten Publish, mit dem Sitzungsneustart an der Stelle, an der der neue
    Kontrollplan zu gelten anfaengt - nach dem Publish der Maschinerie, vor der Migrationskette, und
    mit migrate status als Wiedereinstiegspunkt fuer die neue Sitzung. manual: true, weil die
    Prozedur einmal pro Release laeuft und nie implizit aufgegriffen werden darf; ein Skill wuerde
    seine description dafuer in jede Sitzung legen. Auffindbar ist sie ueber den Abschlussbericht
    von dist upgrade, der statt einer eigenen Schrittliste jetzt die Datei nennt und das Kommando,
    bei dem der Lauf weitergeht (instructions sync). INSTALL.md behaelt, was ein Mensch vorher
    entscheidet, und den einen Sonderfall, den die Instruktion nicht abdecken kann, weil es sie dort
    noch nicht gibt: den ersten Sprung auf 4.5.0.

    Zwei Schritte der Instruktion sagen ausdruecklich, dass sie eine Luecke umgehen, und was sie
    ueberfluessig macht. Schritt 2 liest die Release-Notes von der Release-Seite statt mit
    version notes, weil eine Instanz ihre CHANGES.md als Stub bekommt und dist upgrade sie nie
    ueberschreibt - der Befehl kann dort nicht heute und nicht spaeter antworten. Schritt 6 nimmt fuer
    eine lokal veraenderte stackeigene Datei die Release-Fassung von Hand, weil es zu --keep-local
    kein Gegenstueck gibt; dabei geht der noetige Commit ueber publish --no-push, nicht ueber
    git commit - Invariante 5 kennt keine Ausnahme fuer "ist ja nur eine Vorbedingung", und genau
    diese Ausnahme hat sich der beobachtete Lauf genommen.

    instructions/session-setup.md sagt jetzt, dass ein export nur traegt, solange die Shell
    traegt. Mehrere Harnesses starten pro Tool-Call eine frische Shell - das Arbeitsverzeichnis
    ueberlebt, Shell-State nicht - und dann faellt jeder Aufruf auf seine eigene Parent-PID zurueck.
    Im gemessenen Lauf wurde eine Sitzung so zu 21 Telemetrie-Buckets mit hoechstens drei Aufrufen
    pro Bucket: das Iteration-Budget-Gate (60) und der Loop-Breaker (3 identische in Folge) konnten
    strukturell nicht ausloesen. Die Anleitung nennt deshalb die Inline-Form pro Aufruf und den
    Einzeiler, mit dem sich beantworten laesst, welcher Fall vorliegt.

    Verifiziert: docs verify (73 ausgelieferte Dokumente, 58 Referenzdateien),
    instructions verify (23 Instruktionen, 7 Skills) und 1276 Tests gruen - einer davon neu und auf
    genau die Stelle gerichtet, an der die Doppelung wieder entstehen wuerde: der Abschlussbericht
    von dist upgrade muss die Instruktion und ihr Wiedereinstiegskommando nennen, nicht eine zweite
    Kopie der Liste.

    Kein Grenzuebertritt: eine neue Instruktionsdatei und ein geaenderter Meldungstext sind in beide
    Richtungen ein Drop-in. Eine Instanz, die zurueckgeht, behaelt die Datei als ueberzaehlige Datei,
    und nichts liest sie automatisch - manual: true heisst genau das.

    Migrationsdokument prueft gegen eine festgehaltene Vorher-Ausgabe, Beispielverweis auf die .template-Form

    instructions/migrations/6.0.0-type-guidance-split.md verlangte in seinem Verifikationsschritt,
    die Ausgabe von types describe <name> muesse "read the same as it did before this migration" -
    ohne dass ein Schritt davor dieses Vorher festhielt. Eine Pruefung gegen einen Zustand, den
    niemand aufgeschrieben hat, faellt auf das Gedaechtnis des Ausfuehrenden zurueck, und bei ueber
    150 Zeilen Ausgabe je Typ ist das keins. Der getracete 6.0.0-Lauf hat entsprechend durch
    | head -250 und | tail -80 geprueft und "structurally identical to before" geurteilt; was
    das uebersah, lag in der Mitte der source-Ausgabe. Das Dokument schreibt die Ausgabe jetzt in
    einem eigenen Schritt vor der Aenderung in eine Datei und diffed hinterher, mit
    grep -c '^## Authoring guidance' als Ein-Zahl-Probe: zwei Koepfe sind richtig - einen setzt
    types describe selbst, einen bringt die Guidance-Datei mit.

    Als generisches Muster steht dasselbe jetzt in instructions/migrate-corpus.md § "Writing the
    migration document", weil es nicht an diesem einen Dokument haengt: migrate verify traegt seine
    Baseline im letzten Commit, ob jemand daran denkt oder nicht - eine Migration an der Maschinerie
    statt an kb/ hat gar keine, und genau dort entsteht die Behauptung, die sich nicht widerlegen
    laesst.

    Zweiter Fehler im selben Dokument: der Beispielverweis auf types/entity.md zeigt in einer
    ausgelieferten Instanz auf die beim Setup adoptierte Kopie - also auf genau den Vorher-Zustand,
    den der Schritt entfernen laesst. Der Nachher-Zustand liegt dort unter
    types/entity.md.template, und im Ursprungs-Repo existiert diese Datei ueberhaupt nicht:
    dist export re-keyt types/<name>.md erst beim Export. Der Satz konnte in einer Instanz also
    nicht bloss unguenstig sein, er konnte dort nie stimmen. Dazu sagt der Schritt jetzt die Sprache
    des Pointer-Absatzes - englisch, weil Anleitungsprosa an einen Agenten Control Plane ist,
    unabhaengig davon, wem die Datei gehoert - und dass das auch fuer behaltene lokale Prosa gilt:
    die wird uebersetzt, nicht umbenannt. Die Tabelle dazu wird verlinkt statt kopiert
    (types/type-spec.md § "Who owns a type-spec"), und ein behaltener Abschnitt bekommt einen
    eigenen Namen statt der Ueberschrift, die types describe schon selbst setzt.

    Derselbe Defekt eine Ebene hoeher, gefunden beim Nachmessen: types/source.md trug hier im
    Ursprungs-Repo noch einen Rest-Abschnitt ## Authoring guidance mit einem einzigen Bullet, der
    die title_prefix-Frontmatter wiederholte - types describe source gab drei Koepfe aus, die
    anderen drei Typen zwei. Die Sprachzentralisierung hat den Abschnitt uebersetzt, der
    Guidance-Split den Rest der Prosa ausgelagert und diesen Bullet stehenlassen. Die Datei wird beim
    Export zu types/source.md.template, also haette ihn jede neu aufgesetzte Instanz mit adoptiert.
    Entfernt, geprueft mit genau dem Muster, das der Schritt oben jetzt vorschreibt: Vorher-Datei,
    Diff, vier entfernte Zeilen und sonst nichts, alle vier Typen komponieren jetzt mit zwei Koepfen.

    Verifiziert: docs verify (73 ausgelieferte Dokumente, 58 Referenzdateien),
    instructions verify (23 Instruktionen, 7 Skills) und 1276 Tests gruen. Kein neuer Test: die
    Aenderung ist Prosa in zwei Instruktionen und ein entfernter Abschnitt aus einem Type-Spec -
    was hier mechanisch pruefbar waere, prueft docs verify bereits als Type-Spec gegen sein Schema.

    Kein Grenzuebertritt: in beide Richtungen ein Drop-in. Die Korrektur gilt denen, die noch
    upgraden - eine Instanz, die das Angebot bereits genommen hat, liest das Dokument nicht noch
    einmal. Fuer sie lohnt der eine Befehl, mit dem der Schaden hier gefunden wurde:
    grep -c '^## Authoring guidance' ueber types describe <name> fuer alle vier Typen, drei
    bedeutet einen Rest-Abschnitt im eigenen Type-Spec.

    dist upgrade: --take-release nimmt fuer einen lokal geaenderten Pfad die Release-Fassung

    dist upgrade kannte zwei Antworten auf eine lokal geaenderte Datei und die dritte, die man
    eigentlich will, war keine davon. --keep-local behaelt die Aenderung - und weil der neue Stamp
    die Release-Digest trotzdem schreibt, wird dieselbe Datei bei jedem kuenftigen Upgrade erneut
    gemeldet. Fuer eine Datei, die der Instanz gar nicht gehoert, ist das der dauerhaft falsche
    Zustand. Der andere angebotene Weg, "reconcile them by hand first", hatte kein Werkzeug: im
    getraceten 5.0.0-auf-6.0.0-Lauf war eine kb/CONTRACT.md durch ein Format-on-Save um
    Tabellen-Whitespace verschoben, und das kostete eine Handkopie aus dem entpackten Tarball, einen
    Commit nur zur Herstellung der Clean-Tree-Vorbedingung des naechsten Kommandos - und damit einen
    rohen git commit, an AGENTS.md Invariante 5 vorbei, die fuer "ist ja nur eine Vorbedingung"
    keine Ausnahme kennt.

    --take-release <pfad> ist die fehlende Antwort: schreibe fuer diesen Pfad die Release-Fassung,
    statt abzubrechen. Wiederholbar, weil der Pfad die Entscheidung benennt - --keep-local verliert
    nichts, --take-release verwirft eine lokale Aenderung, und die zwei sind darum nicht symmetrisch
    genug fuer ein pauschales Flag. Beide gelten pro Pfad und komponieren auf einem Aufruf, was der
    gemischte Fall braucht: eine Datei zuruecksetzen, eine andere behalten. Ohne --keep-local bricht
    ein blockierter Pfad, zu dem nichts gesagt wurde, weiter ab; ein --take-release-Pfad, der gar
    nicht blockiert ist, wird abgelehnt - auch im --dry-run, denn das ist ein Fehler im Argument
    und nicht ein Zustand des Baums, und ein still ignorierter Tippfehler haette ein erfolgreiches
    Upgrade gemeldet und die Aenderung behalten, die verworfen werden sollte.

    Anders als bei --keep-local ist die Drift danach weg und nicht bloss uebergangen: die Datei
    stimmt wieder mit der Digest ueberein, die der Stamp fuehrt, und verschwindet aus der Meldung.

    Dazu die Abbruchmeldung selbst, die den Fehlgriff mitverursacht hat. Sie nannte --keep-local und
    "reconcile by hand", sagte aber nicht, dass es zu --keep-local kein Gegenstueck gibt - der Lauf
    kuendigte woertlich an, "I'll let the upgrade take the release's version", und rief das Kommando
    ohne Flag auf. Jetzt nennt sie alle drei Antworten mit fertig eingesetzter Kommandozeile, im Muster
    des Mass-Update-Gates, und sagt ausdruecklich, dass keine davon der Default ist.

    instructions/upgrade-instance.md Schritt 6 traegt entsprechend nicht mehr die Drei-Schritt-Handreparatur, sondern die Entscheidung und den Dry-Run, mit dem man sie vorher sieht.

    version notes antwortet auf einer ausgelieferten Instanz aus dem Release-Feed

    version notes liest die lokale CHANGES.md. Eine ausgelieferte Instanz bekommt die aber als
    neunzeiligen Stub ohne einen einzigen Versionseintrag, und CHANGES.md steht in
    chemenu.ownership.is_upgrade_preserved - dist upgrade ueberschreibt sie also nie. Der Stub
    bleibt der Stub, dauerhaft. Der Befehl konnte dort nicht nur heute nicht antworten, sondern nie,
    und das an genau der Stelle, an der die Antwort am meisten zaehlt: dem Grenzuebertritt, vor dem
    Breaking Change: und Migration: gelesen werden muessen. Der getracete
    5.0.0-auf-6.0.0-Lauf kam nur weiter, weil er die Release-Notes ueber einen MCP-Server holte - ein
    Weg, den die Anleitung nicht nannte und den eine Instanz ohne erreichbaren Server gar nicht hat.

    Fehlt der Eintrag lokal, fragt der Befehl jetzt den Feed aus update_url - denselben, den
    version check benutzt - und druckt den body des Release, den release.yml im Ursprungs-Repo
    ohnehin aus version notes baut. Drei Praezisierungen halten das von einem stillen Netzaufruf
    auseinander:

    • Nur mit Release-Stamp. Ein Baum ohne .wikitool-release.json ist ein Dev-Checkout und
      behaelt die alte Fehlermeldung. Damit kann der neue Pfad im Ursprungs-Repo und in CI nicht
      betreten werden - auch nicht von release.ymls eigenem version notes.
    • stdout traegt nur die Notes. Die Zeile, welcher Feed gefragt wird, und die, welche Version
      geantwortet hat, gehen nach stderr. release.yml leitet stdout in die Datei um, die es als
      Release-Body postet; alles andere dort waere Inhalt im Release.
    • --offline verweigert den Aufruf und scheitert mit der release_url aus dem Stamp. Dieselbe
      Seite nennt auch jeder Fehlerfall des Feeds, damit ein Lauf, der die Notes nicht lesen kann,
      wenigstens weiss, wo sie stehen. Ein leerer body ist ebenfalls ein Fehler: eine leere Antwort
      darf nicht als "dieses Release hat nichts zu melden" durchgehen.

    Gefragt werden kann nur das neueste Release: update_url ist die einzige URL, die der Stamp
    fuehrt, und eine /releases/tags/<tag>-URL daraus zusammenzusetzen waere eine geratene
    API-Form statt einer gelesenen (Invariante 7). Antwortet der Feed eine andere Version als die
    gefragte, wird das auf stderr benannt und die Notes werden trotzdem gedruckt - das ist nicht der
    Randfall, sondern der Hauptfall, weil die Notes vor dem Tausch gelesen werden, wenn VERSION
    noch das Release nennt, das verlassen wird.

    instructions/upgrade-instance.md Schritt 2 und INSTALL.md § "Version und Updates" tragen
    entsprechend nicht mehr den Hinweis, dass der Befehl auf einer Instanz nicht antwortet; damit ist
    auch die letzte der beiden Werkzeugluecken aus dieser Instruktion heraus, und ihr Vorwort nennt
    keine mehr.

    Bei der Gelegenheit zwei Eintraege aus tools/CONTRACT.md § "Future considerations (not
    implemented)" entfernt, die dort seit ihrer Umsetzung falsch standen: der MCP-Server-Wrapper und
    dist upgrade selbst. Beide sind im selben Dokument weiter oben als existierend beschrieben.

    Session-Id-Fallback: Harness-Variable schliesst die Luecke zwischen Telemetrie-Join und Iteration-Budget-Gate

    Gemessen an einem getracten Lauf (33 wikitool-Aufrufe, eine Sitzung): unter Claude Code, dessen
    Bash-Tool jeden Aufruf in einer frisch initialisierten Shell ausfuehrt, fiel chemenu.session
    ohne gesetztes WIKITOOL_SESSION_ID auf os.getppid() zurueck - eine neue "Sitzung" pro Aufruf.
    Der Lauf zerfiel so in 21 Telemetrie-Buckets (hoechster Bucket: 3 von 33 Aufrufen), und das
    Iteration-Budget-Gate (60 Aufrufe, Loop-Breaker bei 3 identischen in Folge) sah nie mehr als 3 von
    60 - strukturell unerreichbar, obwohl AGENTS.md es als eine der vier code-durchgesetzten
    Sicherungen fuehrt. Derselbe Bruch traf den Telemetrie-Join: Hook-Events (prompt.submitted)
    trugen die Harness-UUID, wikitool.call-Events die wechselnde PID - kein gemeinsamer Schluessel,
    und eval score bewertete 1-3 Aufrufe statt 33.

    chemenu.session bekommt eine dritte Stufe zwischen der expliziten Variable und dem
    PID-Fallback: eine kleine Registry bekannter Harness-Session-Variablen (HARNESS_ENV_VARS),
    heute mit einem verifizierten Eintrag, CLAUDE_CODE_SESSION_ID. Verifiziert heisst: gegen eine
    echte Sitzung gemessen, dass die Variable ueber Tool-Aufrufe hinweg stabil bleibt (anders als die
    Shell-PID) und exakt der Wert ist, den der UserPromptSubmit-Hook in die Trace schreibt - der
    Wert wird unveraendert als Schluessel uebernommen, kein Praefix, keine Umschreibung, sonst waere
    der Join wieder zerstoert. Ein Eintrag wird nur nach genau dieser Verifikation aufgenommen: ein
    Variablenname, der zufaellig existiert und etwas anderes bedeutet, waere ein stillerer Fehler als
    der PID-Fallback, den er ersetzt.

    run_budgets Zustandsdatei (budget.json) traegt je Eintrag jetzt die Herkunft seiner Id; faellt
    dieselbe Id-Zeichenkette unter eine andere Herkunft als die gespeicherte, beginnt ein neuer
    Zaehler statt einen fremden zu erben - ein Eintrag ohne das Feld (vor dieser Aenderung
    geschrieben) behaelt seinen Count unveraendert. doctor ist jetzt dreiwertig (OK fuer eine
    explizite Variable oder eine erkannte Harness-Variable, WARN nur noch fuer den reinen
    PID-Fallback), und sowohl budget status als auch der session.start-Event der wikitool-
    Telemetriequelle nennen die Herkunft der Id.

    Im selben Lauf gemessener Nebenbefund auf der Emitter-Seite: ein durch eine geschlossene Pipe
    abgebrochener, ansonsten erfolgreicher Aufruf (... | head) stand mit exit_code: 1 in der
    Trace - Click faengt BrokenPipeError selbst ab und erzwingt sys.exit(1), ununterscheidbar von
    einem echten Fehler. cli.py installiert jetzt vor jedem Dispatch einen Wrapper um
    stdout/stderr, der einen EPIPE-Schreibfehler schluckt, bevor Click ihn sieht, und markiert den
    Trace-Eintrag stattdessen mit stdout_truncated: true bei unveraendertem, dem tatsaechlichen
    Kommandoerfolg entsprechendem exit_code.

    Reproduziert mit Tests, die echte Subprozesse statt In-Process-Aufrufe verwenden - os.getppid()
    ist sonst ueber die Testlaufzeit hinweg konstant: 61 Aufrufe aus je eigenem Prozess mit nur der
    Harness-Variablen loesen das Gate jetzt aus, drei identische ebenso den Loop-Breaker; vor dieser
    Aenderung waeren beide Tests gruen und blind gewesen.

    --minor: additiv (ein neues optionales source-Feld in budget.json, die Id faellt weiterhin
    auf getppid() zurueck, wo keine Variable greift), keine der beiden Drop-in-Richtungen verletzt.

    new: scaffold materializes a schema default only for a required field

    tools/wikitool new instruction --name "x" schrieb bislang obligation: required in jede neue
    Instruktion. obligation: ist ein Migrationsfeld (instructions/CONTRACT.md
    § instructions/migrations/) - eine gewoehnliche Instruktion ist keine Migration und hat nichts,
    was laufen muesste. Ursache: new_page._build_frontmatter() materialisierte jedes
    Schema-default: unbesehen; ueber alle acht types/*.schema.yaml gibt es genau zwei
    (entity/concepts provenance, in required:; instructions obligation:, nicht).

    Die Regel jetzt: ein Schema-default: wird nur fuer ein Feld materialisiert, das das Schema auch
    in required: fuehrt. Auf einem optionalen Feld ist ein default: eine Lese-Annahme (was ein
    fehlendes Feld bedeutet), keine Schreib-Vorgabe - sie hinzuschreiben macht aus der stillen
    Annahme eine ausgesprochene Behauptung. instruction.obligations eigene Lese-Annahme steht
    unveraendert und unabhaengig in kb_state.py (frontmatter.get("obligation") or REQUIRED).
    Der array-Zweig direkt daneben (leere Liste fuer ein unbesetztes Array-Feld wie tags:) ist
    davon ausdruecklich nicht betroffen - er bleibt fuer optionale wie Pflichtfelder gleich, weil ein
    fehlender Schluessel sonst den Template-Filter-Suffix woertlich in den Body schreiben wuerde
    ({related|bullets} -> das Wort "bullets").

    --patch: kein Bestandsdokument aendert sich (obligation: stand bislang nur explizit oder auf
    den beiden Migrationsdokumenten), keine Migration noetig, und ein zurueckgerolltes Werkzeug
    schriebe das Feld nur wieder mit.

    Stale wiki/ path literals swept out of tools/ and types/, with a test guarding against the next rename

    Die Wissensschicht wurde am 2026-08-21 von wiki/ nach kb/ umbenannt. Das Verzeichnis zog um,
    die Zeichenkette nicht: 33 Stellen nannten weiter einen Pfad, den es nicht mehr gibt. Gemeldet
    war davon eine - die Kopfzeile des Lint-Reports (Scanned N pages under `wiki/` ) - als
    kosmetischer Einzelfall. Der Scan selbst war immer korrekt: run_lint(kb_dir) laeuft ueber
    kb/, gezaehlt wird, was dort liegt. Falsch waren ausschliesslich die Beschriftungen.

    Dreizehn davon sind nutzersichtbar. Die Fehlermeldungen von xref, cite, touch,
    move, rm, rename, raw accept und log status nannten wiki/, ebenso die --help-Texte
    von cite sync --all, provenance rebuild-index --dry-run und move --reconcile. Dazu die
    description:-Felder in types/type-spec.schema.yaml, die ueber types describe und ueber jede
    Schema-Validierungsmeldung bei einem Agenten landen. Zwei Stellen waren doppelt falsch:
    git_publish.py und run_budget.py verwiesen auf wiki/concepts/Mass-Update Gate.md, waehrend
    die Seite unter kb/concepts/workflows/Mass-Update Gate.md liegt - dort war auch die
    Collection-Ebene veraltet.

    Nicht angefasst: raw/ (unveraenderlich, was immer dort steht) und die Alteintraege dieser
    Datei. Beide sind Aufzeichnungen dessen, was zu ihrer Zeit galt, keine Wegweiser - dieselbe
    Unterscheidung, die instructions/dev/issue-tracking.md fuer den Tracker trifft.

    Dass es vier Wochen unbemerkt blieb, ist der eigentliche Befund: kein Check liest ein Pfadliteral
    in Quelltext. docs verify kam dafuer nicht in Frage, weil es shipped_prose() liest, also
    Markdown - der Grossteil des Defekts sass in .py-Zeichenketten. Der Guard ist deshalb ein Test:
    tools/chemenu/tests/test_source_hygiene.py scannt jede .py-Datei unter tools/chemenu/ sowie
    tools/wikitool gegen eine Tabelle stillgelegter Stufenpfade. Die naechste Umbenennung traegt
    dort eine Zeile nach und bekommt jede vergessene Stelle als Testfehler, statt als Zeichenkette,
    die ein Jahr lang niemand liest. Die zwei Ausnahmen stehen bewusst als Liste mit Begruendung und
    nicht als geschickteres Muster: eine Fixture-URL, in der wiki ein Repository-Name ist, und die
    Guard-Datei selbst, die die stillgelegten Pfade ja gerade deklariert.

    kb/entities/projects/Chemenu.md trug denselben Fehler in einer Kerndaten-Zeile und wurde ueber
    touch nachgezogen. "Dreilagig" blieb dort stehen: das deckt sich mit der Concept-Seite
    Three-Layer Architecture, die reports/ ausdruecklich als vierte Phase neben den drei
    Schichten fuehrt.

    --patch: keine Schnittstelle aendert sich, kein Verhalten, keine Migration. Ein
    zurueckgerolltes Werkzeug gibt nur wieder die alten Beschriftungen aus.

    CHANGES.md/Guard-Docstring: die Zahl der nachgezogenen Pfadliterale korrigiert (33, nicht 27)

    Der Eintrag darueber nannte 27 nachgezogene Stellen und "rund die Haelfte davon nutzersichtbar".
    Beides war falsch. Die 27 stammten aus einem wc -l, das nur tools/**/*.py gezaehlt hatte -
    tools/wikitool, types/type-spec.md und die vier description:-Felder in
    types/type-spec.schema.yaml fehlten darin. Nachgezaehlt am Commit selbst
    (git show <sha> | grep -c '^-.*wiki/'): 33, davon 32 im Stack und eine auf der Seite
    Chemenu. Nutzersichtbar sind davon dreizehn, also gut ein Drittel und nicht die Haelfte.

    Derselbe Zahlendreher stand im Docstring von tools/chemenu/tests/test_source_hygiene.py, wo er
    kuenftigen Lesern erklaert, wogegen der Guard schuetzt - dort ebenfalls korrigiert. Dass diese
    Korrektur einen eigenen Bump braucht, ist kein Formalismus: der Docstring liegt unter tools/,
    und das Version-Gate in .gitea/workflows/ci.yml ist nach Pfad geschnitten, nicht nach Absicht.

    --patch: reine Prosakorrektur, kein Verhalten, keine Schnittstelle.


    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
  • v6.0.1 f3c80747a5

    v6.0.1
    CI / verify (push) Successful in 48s
    Release / release (push) Successful in 41s
    Stable

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

    6.0.1 - 2026-09-16 - docs toc/verify erreichen die .template-Form einer Referenzdatei

    Author: Torben Nehmer

    High impact

    • docs toc/verify erreichen die .template-Form einer Referenzdatei

    docs toc/verify erreichen die .template-Form einer Referenzdatei

    kb/CONVENTIONS.md.template war 105 Zeilen lang und trug keine TOC-Region. toc.target_files()
    berechnete den Dateisatz ueber die adoptierten Namen, und eine Datei auf .md.template faellt
    aus jedem dieser Walks heraus - also hat docs toc --apply das Template nie angefasst und
    docs verify es nie gelesen. Eine Instanz, die es nach instructions/setup-instance.md
    adoptiert, bekam damit eine kb/CONVENTIONS.md ohne Region und fiel am docs verify in
    Schritt 13 derselben Anleitung um - dem Befehl, mit dem das Setup endet. Ausgeliefert war das in
    6.0.0.

    Eine in Scope stehende Datei nimmt ihr <name>.template jetzt mit hinein: das Template ist
    dasselbe Dokument einen Schritt frueher in seinem Leben, und wer es auslaesst, laesst die
    adoptierte Kopie den Fehler erben. docs verify prueft im Ursprungs-Repo damit 57 statt 56
    Referenzdateien, in einer frisch exportierten Instanz 59.

    Ausgeloest hat es ein Wachstum um sechs Zeilen: f350999 hat das Template von 99 auf 105 Zeilen
    gebracht und damit ueber die Schwelle von 100. Seither war ci.yml auf jedem Push rot (Laeufe
    279 bis 289) - was als Flackern gelesen wurde, weil jeder Push zusaetzlich einen gruenen
    release.yml-Lauf erzeugt und die Paare wie Lauf und Wiederholung aussehen. Sie sind zwei
    verschiedene Workflows.

    Grenzuebertritt-Frage geprueft und verneint, gegen den dokumentierten Update-Weg: das Template ist
    stack-eigen (ownership.is_stack_owned - jede .template unter einer Content-Stage), steht nicht
    in UPGRADE_PRESERVED_PATHS, und dist upgrade schreibt es damit mit. Eine Instanz bekommt das
    reparierte Template also durch den Upgrade selbst, ohne Handarbeit; der Rueckweg funktioniert
    ebenso, weil die alte Maschinerie das Template gar nicht erst prueft. Handarbeit faellt nur an, wo
    eine Instanz ihr stack-eigenes Template lokal veraendert hat - dist upgrade meldet genau das als
    blocked und verlangt --keep-local.

    Verifiziert: docs verify/instructions verify gruen, 1275 Tests gruen (3 neu: das Template einer
    in Scope stehenden Datei steht im Dateisatz, ein .template ohne solche Datei daneben nicht
    (USER.md.template), und ein Template ueber der Schwelle ohne Region ist ein Befund - der letzte
    waere am heutigen Stand rot gewesen). Dazu der vollstaendige setup-instance.md-Replay gegen einen
    frischen dist export: doctor, docs verify, instructions verify und lint laufen in der
    frischen Instanz durch.


    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
  • v6.0.0 5d26698cd0

    v6.0.0
    CI / verify (push) Failing after 40s
    Release / release (push) Successful in 40s
    Stable

    torben released this 2026-09-15 19:38:53 +00:00 | 119 commits to main since this release

    6.0.0 - 2026-09-15 - search: Pfad und Titel vollstaendig, Trunkierung sichtbar

    Author: Torben Nehmer

    Breaking Change:

    • docs verify loest ab dieser Version jeden relativen Markdown-Link in den Referenzdateien auf und meldet ein totes Ziel als Fehler - auch in kb/CONVENTIONS.md und kb//COLLECTION.md, die eine Instanz selbst besitzt und die ein Drop-in-Copy der Maschinerie nicht ersetzt. Eine Instanz, deren eigene Konventions- oder Collection-Datei einen relativen Link mit falscher ../-Tiefe oder auf eine inzwischen geloeschte Datei traegt, sieht docs verify nach dem Update fehlschlagen, wo es vorher durchlief. Reparatur: den in der Meldung genannten Datei:Zeile-Link korrigieren - kein Werkzeuglauf, keine Inhaltsmigration.
    • docs verify prueft die TOC-Region ab dieser Version auch auf types/.md und docs/.md. Eine Instanz, die die Page-Type-Spec-Templates adoptiert hat, traegt types/source.md und types/concept.md ohne Region und sieht docs verify nach dem Update fehlschlagen, wo es vorher durchlief; dasselbe gilt fuer eine selbst angelegte oder lokal geaenderte docs/-Seite ueber 100 Zeilen. Reparatur: tools/wikitool docs toc --apply - ein Werkzeuglauf, keine Inhaltsmigration.

    Migration: none required - Keine kb/-Seite aendert ihre Form. Der Grenzuebertritt ist ein strengerer Check auf instanz-eigener Prosa, keine Schema- oder Frontmatteraenderung.

    High impact

    • SKILL.md: relative Links durch repo-root-relative Pfade ersetzt, docs verify/instructions verify pruefen Linkziele
    • docs verify: der Linkziel-Check erreicht auch die instanz-eigenen kb/CONVENTIONS.md und COLLECTION.md - daher Grenzuebertritt
    • TOC-Scope auf types/ und docs/ erweitert, Sprachregeln zentralisiert, --breaking akkumuliert
    • types/: Seiten-Type-Spec-Anleitungsprosa in stackeigene guidance-Datei ausgelagert

    Medium impact

    • docs verify: ein nur als .template ausgeliefertes Linkziel gilt als aufgeloest
    • Control-Plane-Sprache universell: Achse ist das Publikum, kein Instanz-Schalter
    • types/type-spec.schema.yaml enforced against real type-spec frontmatter
    • search: Pfad und Titel vollstaendig, Trunkierung sichtbar

    Low impact

    • gates.md/session-setup.md: die Budget-Ausnahme von version regrade haengt an der Aufrufform
    • types/type-spec.md: Ownership und Sprache getrennt benannt (Nachzug zu #99)

    Ausgangspunkt war ein realer Bruch: instructions sync kopiert jede SKILL.md in eine andere
    Verzeichnistiefe, und 52 von 58 relativen Links darin zeigten in der publizierten Kopie ins Leere,
    unbemerkt, weil kein Check je ein Linkziel gelesen hat. Die Reparatur - repo-root-relative Pfade
    statt ../-Links - zieht zwei neue mechanische Checks nach sich (instructions verify verbietet
    relative Links in SKILL.md, docs verify loest Linkziele in allen Referenzdateien auf), und der
    zweite Check erreicht auch instanz-eigene Prosa (kb/CONVENTIONS.md, COLLECTION.md), die ein
    Drop-in-Copy nicht ersetzt - der Grenzuebertritt, der diesen Kandidaten auf 6.0.0 eskaliert hat.
    Denselben Linkziel-Check bekommt die TOC-Pflicht gleich mit auf types/ und docs/ erweitert, und
    die Sprachregeln fuer die Control-Plane sind zu einer einzigen, publikumsbasierten Regel in
    AGENTS.md zentralisiert statt eines Instanz-Schalters. Daneben, unabhaengig vom Linkproblem: die
    Seiten-Type-Spec-Anleitungsprosa ist in eine stackeigene Guidance-Datei ausgelagert,
    type-spec.schema.yaml wird jetzt gegen echte Type-Spec-Frontmatter durchgesetzt, und search
    zeigt Pfad und Titel eines Treffers vollstaendig statt trunkiert.

    gates.md/session-setup.md: die Budget-Ausnahme von version regrade haengt an der Aufrufform

    Doku-Nachzug zu 5.1.0. Beide Dateien beschrieben die Budget-Ausnahme als feste Liste pro
    Kommandoname ("fixed allowlist"); version regrade ist die erste Ausnahme, die nur in einer
    Aufrufform liest - bar listet sie, mit Positionen schreibt sie CHANGES.md. Die Liste selbst
    bleibt an ihrem einen Ort (tools/CONTRACT.md), beide Stellen benennen jetzt aber, dass dort ein
    Eintrag pro Aufruf statt pro Namen gilt. Aufgefallen in der Schlussphase derselben Arbeit, deshalb
    ein eigener Patch-Bump: der Pfad instructions/ liegt im Version-Gate der CI.

    SKILL.md: relative Links durch repo-root-relative Pfade ersetzt, docs verify/instructions verify pruefen Linkziele

    instructions sync kopiert jede SKILL.md byteidentisch in .agents/skills/ und
    .claude/skills/ - eine andere Verzeichnistiefe als die Quelle, ohne deren Nachbardateien. 52 von
    58 relativen Markdown-Links in den sieben Skills zeigten deshalb in der publizierten Kopie ins
    Leere, unbemerkt, weil kein Check je ein Linkziel gelesen hat (Gitea-Meldung: ein session-setup.md-Read
    schlug in einer ausgelieferten Instanz fehl). Alle 58 Links sind jetzt repo-root-relative
    Klartextpfade (instructions/session-setup.md statt [session-setup.md](../session-setup.md)) -
    sie ueberleben die Kopie unveraendert, weil sie nicht von der Position der lesenden Datei abhaengen.
    instructions/CONTRACT.md § "A skill's outbound reference is a plain path, not a link" traegt die
    Regel.

    Zwei neue mechanische Checks verhindern das Wiederauftreten: instructions verify verbietet jeden
    relativen Markdown-Link in einer SKILL.md (check_skill_reference_paths), docs verify loest
    jeden relativen Link in den flachen Instructions und Contracts gegen den Arbeitsbaum auf
    (check_reference_targets, ueber denselben Dateisatz wie docs toc). Nebenbei behoben:
    instructions/dev/doc-pull-through.md hatte zwei Links mit falscher ../-Tiefe, unabhaengig vom
    Skill-Copy-Problem.

    docs verify: der Linkziel-Check erreicht auch die instanz-eigenen kb/CONVENTIONS.md und COLLECTION.md - daher Grenzuebertritt

    Nachtraegliche Neueinstufung des Bumps darueber, kein zusaetzlicher Code. check_reference_targets
    laeuft ueber den Dateisatz von docs toc, und fuenf Dateien darin gehoeren der Instanz statt dem
    Stack: kb/CONVENTIONS.md und die vier kb/<collection>/COLLECTION.md. Ein Drop-in-Copy von
    tools/, types/, instructions/ und AGENTS.md ersetzt sie nicht - ein toter relativer Link
    darin laesst docs verify nach dem Update fehlschlagen, wo es vorher durchlief. Genau die Form,
    die tools/README.md § Adding a command Schritt 5 als MAJOR-Zeile benennt ("a stricter check that
    newly fails on shipped content an instance already had"), und instructions/dev/version-parts.md
    entscheidet den Zweifelsfall zugunsten des Grenzuebertritts.

    Gemessen bricht heute nichts: die zweite bekannte Instanz traegt 25 relative Links in diesen fuenf
    Dateien, davon null tote; dieses Repo ebenso. Die Einstufung folgt der Reichweite des Checks, nicht
    einem beobachteten Schaden - der Preis einer unnoetigen MAJOR ist eine Release-Notiz, der Preis
    einer MINOR, die doch bricht, ist eine Instanz mit fehlschlagendem Update-Pfad unter einer
    Versionsnummer, die Drop-in versprochen hat. Aufgefallen ist es in der Schlussphase beim Lesen der
    eigenen Regel in tools/README.md, nicht durch einen Check - was docs/version-model.md ueber
    genau diese Stelle sagt ("a person looking at the diff ... not a validator"), hat sich hier
    wiederholt.

    docs verify: ein nur als .template ausgeliefertes Linkziel gilt als aufgeloest

    Defektbehebung am Check aus den beiden Bumps darueber, gefunden unmittelbar nach deren Publish.
    check_reference_targets meldete auf einem frisch exportierten Baum 13 tote Links - kb/CONTRACT.md
    neunmal, dazu german-terminology.md, kb-profiles.md und link-taxonomy.md - und zwar dafuer,
    dass der Export tut, was er soll. kb/CONVENTIONS.md und die vier kb/<name>/COLLECTION.md sind
    instanz-eigen: die Distribution traegt <name>.template, und die Instanz uebernimmt sie erst im
    Personalisierungsschritt von instructions/setup-instance.md durch Umbenennen. Zwischen
    dist export und diesem Schritt existiert die fertige Datei berechtigterweise nicht, waehrend die
    stack-eigenen Dateien sie unter ihrem kuenftigen Namen verlinken - korrekt, denn so wird sie heissen.

    Ein Linkziel gilt jetzt auch dann als aufgeloest, wenn daneben <ziel>.template liegt. Die
    Ausnahme ist eng: fehlt beides, bleibt es ein Befund. Damit beschreibt der Check nicht laenger
    "noch nicht personalisiert" als "kaputter Link" - diesen Zustand meldet doctor unter
    conventions praezise und zustaendig.

    CI war davon nie rot: der Replay in .gitea/workflows/ci.yml uebernimmt die Templates, bevor er
    docs verify aufruft, und der dokumentierte Weg in setup-instance.md stellt die Personalisierung
    (Schritt 5/6) ebenfalls vor die Verifikation (Schritt 13). Getroffen haette es jeden, der nach dem
    Export einmal zur Kontrolle docs verify aufruft. Aufgefallen ist es, weil die Verifikation des
    vorherigen Publishes den Arbeitsbaum geprueft hatte und nicht den exportierten - ausgerechnet bei
    einer Aenderung, deren ganzer Gegenstand Kopien in anderer Verzeichnistiefe sind.

    TOC-Scope auf types/ und docs/ erweitert, Sprachregeln zentralisiert, --breaking akkumuliert

    Drei Straenge, ausgeloest von einer Beobachtung: manche agentengeladene Referenzdatei trug keine
    TOC, und manche Instruction war teilweise deutsch.

    TOC-Scope. Die Pflicht aus 5.0.0 galt fuer AGENTS.md, die Stage-Contracts,
    kb/CONVENTIONS.md, jede COLLECTION.md und die flache instructions/**.md-Form. docs/ und die
    Seiten-Type-Specs fielen ohne genannten Grund heraus - waehrend SKILL.md und die Menschendoku
    ihren Ausschlussgrund im Docstring stehen hatten, was die beiden anderen Luecken wie Absicht
    aussehen liess. docs/ ist dabei genau der Fall, fuer den die Schwelle existiert:
    AGENTS.md § File naming fuehrt es als agentengeladen per Link, also am zweiten Hop. Beide sind
    jetzt drin; vier Dateien haben eine Region bekommen. SKILL.md bleibt die eine Ausnahme, und
    zwar belegt statt behauptet: die vendorte Guidance setzt den SKILL.md-Body auf die Ladeebene, die
    beim Triggern ganz gelesen wird, und richtet ihren eigenen TOC-Rat an die gebuendelten
    Referenzdateien daneben. Ein Type-Spec wird zwar auch ganz geladen, aber eben auch als Datei
    gelesen - deshalb traegt es eine Region, und types describe strippt sie aus seiner Ausgabe, weil
    dort der ganze Body ohnehin mitkommt.

    Sprache. Die Regel gab es schon ("the control plane stays English"), sie stand nur in
    kb/CONVENTIONS.md - einer Datei, die der Instanz gehoert und die sie umschreiben darf, waehrend
    die Regel stackeigene Dateien bindet. Sie ist nach AGENTS.md § File naming gezogen, zusammen mit
    einer zweiten, die vorher gar nicht geschrieben stand: ein Agent spricht die KB-Sprache der
    Instanz
    . Der Wert dafuer lebt weiter in kb/CONVENTIONS.mds language:; SOUL.mds eigene
    Sprache:-Zeile war damit eine Dublette und ist weg. instructions/setup-instance.md - 297 Zeilen,
    die einzige vollstaendig deutsche Instruction, verbatim an jede Instanz ausgeliefert - ist
    uebersetzt, samt description:. Die zwei deutschen Blockquotes in den Dev-Skills sind es auch; sie
    lesen sich jetzt als englisches Modell der Nachricht, die der Agent in der KB-Sprache ausspricht.

    Dieselbe Regel gilt fuer alles, was als Template ausgeliefert wird - eine Instanz adoptiert es,
    bevor sie ihre Sprache ueberhaupt gewaehlt hat. USER.md.template, SOUL.md.template und
    ENVIRONMENT.md.template waren vollstaendig deutsch und sind uebersetzt; kb/sources/ und
    kb/concepts/COLLECTION.md waren es in Teilen und ziehen jetzt mit kb/entities/ und
    kb/comparisons/ gleich, die es laengst waren. Bei den vier Seiten-Type-Specs laeuft der Schnitt
    mitten durch die Datei, und zwar entlang derselben Prosa/Identifier-Grenze, die kb/CONTRACT.md
    schon innerhalb einer Seite zieht: die Anleitungsprosa ist Anweisung an einen Agenten und damit
    Control Plane, der ## Template-Block und die layout:-Titel sind Seitentext und bleiben in der
    KB-Sprache - wikitool new entity scaffoldet also weiter deutsche Ueberschriften.
    kb/CONVENTIONS.md behauptete bis hierher, die Type-Specs folgten als Ganzes der KB-Sprache; der
    Satz ist auf den tatsaechlichen Schnitt nachgezogen.
    Mechanisch geprueft wird nichts davon: ein Stoppwort-Scan schluege auf dem zitierten Vokabular in
    kb-profiles.md und link-taxonomy.md falsch an. Stattdessen nennen
    instructions/CONTRACT.md § "Writing an instruction" und stack-dev die Regel an der Stelle, an
    der sie befolgt oder verloren wird.

    --breaking akkumuliert. Bis hierher ersetzte ein zweites --breaking die Zeile des
    Kandidaten - der Eintrag versprach dann einen Bruch und lieferte zwei. Genau dieser Kandidat ist der
    Fall: sein Linkziel-Uebertritt aus beta.1 und der TOC-Uebertritt von hier sind zwei Dinge, auf die
    ein Betreiber getrennt reagieren muss. Eine Begruendung bleibt flach auf der Markerzeile, ab der
    zweiten werden es Bullets; eine vor dieser Aenderung geschriebene Einzelzeile liest sich unveraendert
    als Ein-Element-Liste zurueck, also musste kein bestehender Eintrag angefasst werden. --migration:
    bleibt bewusst eine Einzelzeile - sie beantwortet eine Ja/Nein-Frage ueber den Kandidaten als Ganzes,
    und --migration-required ist ihr Ruecknahmepfad. Fuer eine falsche Breaking-Begruendung gibt es
    keinen; der Kandidat ist bis zum Release dev-lokal.

    Nebenbefund, den die Scope-Erweiterung sofort aufgedeckt hat: docs/version-model.md verlinkte nach
    instructions/dev/version-parts.md, das dist export wegschneidet - im Ursprungs-Repo gruen, in
    jeder ausgelieferten Instanz ein toter Link. Jetzt ein Klartextpfad mit dem Satz, warum er keiner
    ist.

    types/type-spec.md: Ownership und Sprache getrennt benannt (Nachzug zu #99)

    types/type-spec.md sagte weiterhin, Prosa, ## Template-Body und Sprache eines
    Seiten-Type-Specs gehoerten der Instanz, die bei anderer KB-Sprache "einfach die Datei
    uebersetzt" - genau das Gegenteil des Schnitts, den der Bump davor ausgeliefert hat. Aufgefallen
    in der Schlussphase, beim Nachdenken darueber, welche Sprachregel fuer einen instanz-eigenen
    neuen Seitentyp gilt.

    Der Abschnitt trennt die zwei Fragen jetzt: Ownership sagt, wer eine Zeile aendern darf, die
    Sprache folgt davon unabhaengig dem Publikum der Zeile - Anleitungsprosa an einen Agenten ist
    Control Plane und englisch, ## Template-Body und layout:-Titel sind Seitentext in der
    KB-Sprache, Feldnamen und Enum-Werte sind Identifier und werden nie uebersetzt. Als Tabelle, weil
    der Schnitt mitten durch eine Datei laeuft und eine Aufzaehlung im Fliesstext ihn genau deshalb
    nicht haelt. Der Satz bindet ausdruecklich auch einen Type-Spec, den eine Instanz sich selbst
    schreibt: der ist zwar durchgaengig instanzeigen, aber seine Anleitungshaelfte hat trotzdem einen
    Agenten als Leser.

    Control-Plane-Sprache universell: Achse ist das Publikum, kein Instanz-Schalter

    Die Sprachregel in AGENTS.md ruhte auf einer Begruendung, die schmaler war als sie selbst:
    "Every file in the table above belongs to the stack and ships to instances that share none of
    this instance's language choices, so:". Das traegt nur fuer ausgeliefertes Material und laesst
    offen, was fuer ein Control-Plane-Dokument gilt, das eine Instanz nur fuer sich selbst schreibt -
    eine eigene Instruction, ein selbst angelegter Seitentyp (types/ nimmt einen ohne Code-Aenderung
    auf), ein weiterer Stage-Contract. Genau dort fallen Ownership und Publikum auseinander: die Datei
    ist durchgaengig instanzeigen, ihre Anleitungshaelfte hat trotzdem einen Agenten als Leser.

    Der Vorsatz nennt jetzt die tatsaechliche Achse - die For-Spalte der Tabelle darueber, also wer
    die Zeile liest, und weder wem die Datei gehoert noch ob sie den Checkout je verlaesst. Regel 1
    sagt ausdruecklich, dass sie auch fuer ein nie ausgeliefertes Control-Plane-Dokument gilt und dass
    es neben kb/CONVENTIONS.mds language: bewusst keinen zweiten Sprachwert gibt.
    kb/CONVENTIONS.md und ihr .template sagen dasselbe von ihrer Seite aus: die
    Control-Plane-Sprache ist keine Einstellung, die diese Datei zurueckhaelt - es ist gar keine.

    Die Begruendung dazu steht als neue docs/-Seite
    (docs/language-boundaries.md), weil sie sonst in einem Jahr neu
    verhandelt wird: warum Englisch (der Stack redet fast nur ueber Identifier, und die sind
    englisch), warum kein Parameter (die Kosten traegt jede Datei, den Nutzen haette ein Dokument, das
    ohnehin nur ein Agent liest), und was die Entscheidung wieder aufmachen wuerde. Die Seite haelt
    zugleich fest, welches Argument falsch war: "Sprache folgt der Ownership" hat funktioniert,
    solange nur ausgeliefertes Material betrachtet wurde, und faellt am instanz-eigenen Typ.

    Nebenbei zwei Befunde derselben Ecke behoben. Der Docstring von
    dist_cmd.instance_owned_type_stems() behauptete weiter, "its prose, its template and its
    language are the instance's business" - Stand vor dem TOC-/Sprach-Bump oben. Und die
    Aufzaehlung der docs/-Seiten in AGENTS.md sagte "Four pages exist today", waehrend das
    Verzeichnis fuenf trug: docs/model-and-effort-selection.md fehlte, und zwar absichtlich, weil
    ein Link dorthin die Claude-Code-eigene Entscheidung in die anderen drei Harnesses laden wuerde.
    Der Satz zaehlt jetzt, was von hier aus verlinkt ist, und benennt die sechste Seite samt Grund.

    types/: Seiten-Type-Spec-Anleitungsprosa in stackeigene guidance-Datei ausgelagert

    Ein root: kb Type-Spec (entity, concept, source, comparison) hatte zwei Publika in
    einer Datei: Anleitungsprosa fuer den Agenten (When to use/When NOT to use/Authoring guidance),
    und Seitenmaterial (## Template-Block, layout:-Titel). Ownership gilt pro Datei, also wurde
    die ganze Datei beim Setup als .template adoptiert und danach nie wieder angefasst - eine
    Instanz, die ihre Type-Specs frueh adoptiert hat, las bis in alle Zukunft die Anleitung vom Tag
    ihrer Erzeugung, weil dist upgrade das .template neben die adoptierte Datei schrieb, nie die
    Datei selbst (docs/ownership-and-templates.md § "Where the file boundary strains").

    Der urspruengliche Vorschlag drehte den Schnitt um (Type-Spec stackeigen, Seitenmaterial heraus)
    und wurde beim Pruefen gegen setup-instance.md und evolve-subtypes.md verworfen: die
    Frontmatter-Konfiguration (layout:, Enum-Werte, base_dir) ist instanzeigener Inhalt, keine
    Stack-Maschinerie - beide Instructions weisen die Instanz an, Enum und layout:-Eintrag in
    derselben Aenderung zu setzen. Stattdessen bleibt der Type-Spec instanzeigen, und nur die
    maschinenabgeleitete Anleitungsprosa zieht in eine neue, stackeigene types/<name>.guidance.md,
    verknuepft ueber ein optionales guidance:-Frontmatterfeld (neuer, nicht instanziierbarer Typ
    type-guidance, wie lint-report ohne base_dir:). tools/wikitool types describe <name>
    komponiert beide Haelften weiterhin zu einer Antwort - ein Agent muss nie wissen, dass ein Typ aus
    zwei Dateien besteht. type_resolver.extract_template() liest das Template unveraendert allein
    aus types/<name>.md; kein zweiter Ladepfad fuer wikitool new.

    dist_cmd._plan_types()/find_leaks() teilten sich vorher name.split(".", 1)[0] als
    Stamm-Berechnung - beides haette entity.guidance.md faelschlich als instanzeigenen Stamm
    "entity" erkannt (die eine haette sie zum .template gemacht, die andere sie als Leak gemeldet).
    Neuer gemeinsamer Prädikat _owned_type_stem() prueft die exakte Endung (<stem>.md oder
    <stem>.schema.yaml), nicht den ersten Punkt.

    Grenzuebertritt-Frage bewusst geprueft und verneint: Drop-in in beide Richtungen (ein Type-Spec
    ohne guidance: verhaelt sich unveraendert, eine alte Maschinerie liest types/<name>.md wie
    zuvor und die Guidance-Datei ist fuer sie inert), also --minor statt --major. Die einmalige
    Adoption in einer bestehenden Instanz ist als instructions/migrations/6.0.0-type-guidance-split.md
    dokumentiert - obligation: offered, der erste Gebrauch dieses seit 4.0.0 existierenden, bis jetzt
    unbenutzten Mechanismus fuer ein instanzeigenes, upgradebares Machinery-File.

    Verifiziert: tools/wikitool docs verify/instructions verify gruen, 1261 Tests gruen (8 neu:
    get_guidance, das Template bleibt auf types/<name>.md allein geladen, die Guidance-Datei
    schifft verbatim neben einem .template-adoptierten Type-Spec statt als weiteres .template,
    ein dist upgrade schreibt verbesserte Guidance-Prosa in eine adoptierte Instanz obwohl deren
    Type-Spec selbst nie im Stamp stand, types describe komponiert beide Haelften in JSON und
    Textausgabe getrennt nachweisbar).

    types/type-spec.schema.yaml enforced against real type-spec frontmatter

    Bei der Vorbereitung der Aenderung oben fiel auf: types/type-spec.schema.yaml traegt
    additionalProperties: false, kannte aber root: und capture_fields: nicht, obwohl
    types/instruction.md bzw. types/source.md beide Felder tragen und type_resolver.get_root()/
    get_capture_fields() sie lesen. Gegen das Schema validiert waeren beide Type-Specs ungueltig
    gewesen. Dass es niemandem auffiel, war der eigentliche Befund: Type-Spec-Frontmatter wurde
    nirgends gegen sein eigenes Schema validiert - resolver.validate_frontmatter() lief nur ueber
    kb/-Seiten, neu erzeugte Seiten und Instruktionsdateien, nie ueber die Type-Specs selbst.
    TypeResolver._validate_type_spec(), der einzige Weg, den der Selbstbezug type: types/type-spec.md
    nimmt, prueft ausschliesslich, ob type/name/description vorhanden sind.

    Beide fehlenden Felder ergaenzt (root: als Enum kb/repo, capture_fields: als Liste wie
    page_ref_fields:), dazu guidance: (seit der Aenderung oben real benutzt, aber noch nie im
    Schema). docs verify bekommt eine neue Pruefung: jede Datei unter types/ mit
    type: types/type-spec.md validiert jetzt gegen types/type-spec.schema.yaml
    (check_type_spec_frontmatter(), wiederverwendet resolver.list_type_specs() statt eines zweiten
    Parse-Durchlaufs). types/type-spec.md § Validation Contract und die beiden docs verify-Zeilen
    in tools/CONTRACT.md nennen das jetzt.

    Daneben ein zweiter, unabhaengiger Befund derselben Aufraeumrunde behoben:
    instructions/dev/doc-pull-through.md verwies fuer docs/-Seiten weiter auf "AGENTS.md § File
    naming lists all four" - der Zaehler in AGENTS.md selbst war beim vorigen Bump schon auf fuenf
    (plus eine sechste, nur von CLAUDE.md aus verlinkte) korrigiert worden, diese eine verbliebene
    Stelle nicht.

    Grenzuebertritt-Frage geprueft und verneint: additiv in beide Richtungen - eine bestehende Instanz
    validiert bereits (0 Befunde gegen den realen Baum), und ein Type-Spec ohne die drei neuen Felder
    bleibt unveraendert gueltig. --patch, kein --breaking, keine neue Migration noetig.

    Verifiziert: tools/wikitool docs verify/instructions verify gruen, 1263 Tests gruen (2 neu:
    alle Type-Specs dieses Repos validieren gegen ihr eigenes Schema; ein Type-Spec mit einem dem
    Schema unbekannten Feld wird gemeldet, mit Dateiname und Feldname in der Meldung).

    search: Pfad und Titel vollstaendig, Trunkierung sichtbar

    Gemeldet wurde eine Sitzung, die nach wikitool search zusaetzlich grep -rl ueber kb/
    laufen liess. Der Grep war redundant - search ist ein rg-Lauf ueber kb/ und kann keine
    Seite verfehlen, die ein Grep findet -, aber die Ausgabe gab ihr drei Gruende dafuer, und die
    sind der eigentliche Befund.

    Die Tabelle nannte keinen Pfad, obwohl wiki-query verlangt, nur die Seiten zu lesen, auf
    die die Suche zeigt. Sie kappte ausserdem den Titel auf 34 Zeichen - im gemeldeten
    Transkript vier von fuenf Treffern -, und der Titel ist nach Invariante 2 der einzige
    Identifier einer Seite und das Argument, das touch, xref add und cite add nehmen. Die
    Sitzung hatte also weder etwas zum Oeffnen noch etwas zum Weiterreichen; grep -rl lieferte
    genau beides.

    Drittens war N result(s). die gekappte Zahl: run_search gab nur die beschnittene Liste
    zurueck, also konnte kein Adapter die Gesamtzahl melden, und 20 result(s). auf einer Anfrage
    mit 182 Treffern war von einem vollstaendigen Ergebnis nicht zu unterscheiden. Eine
    Vollstaendigkeitsaussage, zu der die Ausgabe nicht berechtigt war - der staerkste denkbare
    Anlass, ihr zu misstrauen.

    Die Zeile hat jetzt die Form score | kind/subtype | titel | pfad | summary, ohne
    Spaltenauffuellung. Titel und Pfad werden nie gekappt; die Summary ist das einzige verlustige
    Feld und steht deshalb am Ende, wo ein | in Prosa beim Trennen mit maxsplit=4 folgenlos
    bleibt (ein | im Titel schliesst die Wikilink-Syntax ohnehin aus). JSON als Default-Ausgabe
    wurde erwogen und verworfen: ein Treffer ist flach, JSON kostet dafuer ein Vielfaches an Tokens,
    und search existiert dafuer, Retrieval billig zu machen - der Fehler war ein fehlendes Feld,
    kein Parse-Problem. Wer Struktur braucht, hat --json, api.search und MCP.

    run_search gibt jetzt ein SearchResult mit Treffern, Gesamtzahl und Limit zurueck. Die
    Tabelle schreibt 50 of 182 result(s) - raise --limit (0 for all) or narrow the query., das
    JSON traegt total/truncated/limit neben count, dessen Bedeutung unveraendert bleibt
    (len(results)), und api.search sowie der MCP-search-Tool tragen dieselben Felder. Das
    Default-Limit steigt von 20 auf 50 und liegt als eine Konstante DEFAULT_LIMIT statt als drei
    Literale in drei Adaptern: gekappt wurden bisher vor allem die strukturellen Sweeps
    (--field '!sources'), die alphabetisch und nicht nach Relevanz sortiert sind, wo die Kappung
    also eine beliebige Scheibe der Antwort wegwirft statt ihres schwaechsten Endes. Sichtbar zu
    sein ist es, was ein endliches Default ueberhaupt erst unbedenklich macht.

    AGENTS.md § Routing traegt die Regel an genau einer Stelle - search ist erschoepfend, ein
    eigener Grep ueber kb/ fuegt nur die generierten Dateien hinzu, die Invariante 1 ohnehin
    verbietet; tools/CONTRACT.md traegt daneben nur den Mechanismus.

    Grenzuebertritt-Frage geprueft und verneint: kein Flag entfernt oder umbenannt, keine
    Umgebungsvariable, keine maschinengelesene Datei in ihrer Form veraendert, JSON rein additiv.
    Die Tabelle liest ein Agent, kein Skript, und ihre Aenderung verlangt keiner Instanz Handarbeit
    ab.

    Verifiziert: docs verify/instructions verify gruen, 1272 Tests gruen (9 neu: Pfad vorhanden;
    Titel und Pfad ungekappt bei langem Titel; eine Trefferzeile zerfaellt trotz | in der Prosa in
    ihre fuenf Felder; ein gekapptes Ergebnis nennt die Gesamtzahl, ein ungekapptes behauptet
    nichts; --limit 0 gilt nie als gekappt; die Gesamtzahl ueberlebt das Limit in run_search;
    api.search meldet dasselbe; alle drei Adapter teilen ein Default-Limit - der MCP-Golden-Test
    haelt die neuen Felder zwischen CLI und Server zusammen).


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

    Downloads
  • v5.1.0 1b0158fc8d

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

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

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

    Author: Torben Nehmer

    High impact

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

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

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


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

    Downloads
  • v5.0.1 9a2d7d34f5

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

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

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

    Author: Torben Nehmer

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

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

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

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

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


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

    Downloads
  • v5.0.0 87719bf396

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

    torben released this 2026-09-11 17:25:49 +00:00 | 136 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 --reconcile → index rebuild/migrate verify),
      über alle drei Subtype-Achsen (entity_type, concept_type, source_type — alle drei tragen
      subtype_field: und layout:; comparison keins von beidem). Zwei Regeln im Body: Wert
      und Sweep sind untrennbar (ein deklarierter Wert ohne Seite lädt zum Raten ein — genau der
      notes-Fall aus #66), und eine Aufnahmeschwelle von ≥3 Seiten, mit spec/image aus #66 als
      Gegenbeispiel und einer benannten-Ausnahme-Klausel für Fälle wie tracker bei zwei Seiten.
      manual: true verhindert, dass eine Taxonomie-Änderung in einen laufenden Ingest hineinstolpert
      — erwähnt aus kb-profiles.md, setup-instance.md und kb/sources/COLLECTION.md, aus keinem
      Skill, keiner AGENTS.md, keiner CLAUDE.md verlinkt.
    • kb/sources/COLLECTION.md benennt evolve-subtypes.md an der unclassified/-Zeile.

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

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

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

    Schließt #68.

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

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

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

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

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

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

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

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

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

    Vier Befunde in der Skill-Prosa, ein Publish

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Schließt #77.

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

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

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

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

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

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

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

    Schließt #73, #76.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

    Downloads
  • v4.7.4 cc38bcd700

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

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

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

    Author: Torben Nehmer

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

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


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

    Downloads
  • v4.7.3 6671af6a60

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

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

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

    Author: Torben Nehmer

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

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

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

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

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

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

    Downloads