• v4.1.0 cfe925a76c

    v4.1.0
    CI / verify (push) Successful in 53s
    Release / release (push) Successful in 36s
    Stable

    torben released this 2026-09-03 04:22:53 +00:00 | 80 commits to main since this release

    4.1.0 - 2026-09-03 - Link-Taxonomie: Lint-Findings hart ab kb_version 4.0.0, outbound: an das Type-Spec gebunden, part-of/composition als Inversenpaar

    Author: Torben Nehmer

    Der Rest von Issue #40, nachdem die Korpus-Migration durch ist: die beiden aufgeschobenen
    Lint-Findings werden hart, und die drei Befunde aus dem Abschlusskommentar des Migrationslaufs
    werden aufgelöst.

    unlabelled_edges und unauthorised_labels sind harte Fehler — aber an kb_version
    gebunden, nicht an ein Datum.
    Der Weg, den legacy_citation_markers genommen hat, war ein
    Umlegen in einer späteren Version: eine Instanz, die die Zitat-Migration noch schuldete, lebte
    danach mit rotem Lint. Das Ledger kann die Frage inzwischen beantworten, also tut es das.
    Unterhalb kb_version 4.0.0 bleiben beide beratend — genau das Fenster, in dem
    instructions/migrations/4.0.0-link-taxonomy.md der Instanz sagt, sie solle den halb
    konvertierten Korpus Einheit für Einheit publizieren; ein Check, der dabei fehlschlägt, würde
    den Korpus verweigern, dessen Fortschritt er misst. Ab 4.0.0 ist eine kahle Titelangabe in
    related: keine Seite mehr, die auf ihre Umstellung wartet, sondern eine Kante, deren Autor
    nicht gesagt hat, was sie behauptet. hard_error_keys() liefert die jeweils geltende Menge,
    HARD_ERROR_KEYS bleibt die vollständige.

    outbound: ist an das Type-Spec gebunden. kb/sources/ und kb/comparisons/
    autorisierten Label, die dort strukturell nicht schreibbar waren: keiner der beiden Type-Specs
    führte ein related:. Folgenlos war das nicht — die einzige Comparison-Seite des Korpus trug
    - **compares-with:** [[amd-pstate]] als handgeschriebene Prosa, ohne Marker-Region, ohne
    Frontmatter, für lint unsichtbar. Also ein Identifier zurück im Fließtext, gut vier Stunden
    nachdem 4.0.0 genau das beendet hatte. Eine leere Autorisierung liest sich als Lizenz.

    Aufgelöst nach dem, was die beiden Contracts jeweils selbst sagen: comparison bekommt ein
    related: (die compares-with-Kante gegen jedes Subjekt ist die eine Aussage, für die die
    Seite existiert), kb/sources/ verliert seinen outbound:-Block ersatzlos (dessen Contract
    sagt ausdrücklich, seine Verknüpfungen seien der mechanische Provenance-Pfad und keine
    Autorenkanten). Neu prüft docs verify die Kombination: ein outbound:-Block auf einer
    Collection, in die kein Typ mit related: schreibt, ist ein Befund und nennt beide Richtungen
    der Reparatur.

    composition / part-of ist das dritte Inversenpaar, neben depends-on / required-by
    und runs-on / hosts. Aus der Messung, nicht vom Schreibtisch: der u3-Lauf hatte entschieden,
    die Gegenseite eines composition bekomme see-also, weil part-of ein Spiegel wäre. Ist es
    nicht — der Satz des Elternteils zählt seine Teile auf, der des Kindes benennt das Ganze, zu
    dem es gehört, und ein Leser, der auf dem Kind landet, braucht den zweiten. Übrig blieben 16
    see-also-Kanten für eine Beziehung, für die der Katalog ein Wort hat; sie sind auf part-of
    umgestellt. Ein Inversenpaar macht die Gegenkante weiterhin nicht zur Pflicht — Richtung
    wird verfasst, nicht gespiegelt —, es legt nur fest, welches Label sie trägt, wenn jemand sie
    schreibt.

    Stack- und Korpusänderung laufen hier in einem Zug, entgegen der sonstigen Trennung. Der
    neue docs verify-Check würde eine bestehende 4.0.x-Instanz beim bloßen Kopieren der neuen
    Maschinerie fehlschlagen lassen, weil deren kb/sources/COLLECTION.md den outbound:-Block
    noch trägt — nach instructions/dev/version-parts.md
    Schritt 1 ein Grenzübertritt. Statt dafür eine 5.0.0 zu lösen, ist die Ursache mitbeseitigt:
    die Collection-Contracts dieser Instanz sind angepasst, und dist export leitet die
    COLLECTION.md.template daraus ab, also liefert jede neue Distribution die korrigierte Form
    aus. Für eine bereits bestehende 4.0.x-Instanz bleibt eine Handbewegung übrig, und sie wird
    hier benannt statt versteckt: die zwei outbound:-Zeilen aus kb/sources/COLLECTION.md
    löschen. Das neue related: im comparison-Type-Spec erreicht sie ohnehin nicht — die vier
    Page-Type-Specs gehören seit 4.0.0 der Instanz und werden nur als .template ausgeliefert.

    Offen aus #40 bleibt nichts mehr; Befund 2 des Migrationslaufs (dem Katalog fehlt ein Register
    für Urheberschaft) ist als eigenes Issue erfasst.

    Downloads
  • v4.0.1 c8c238523a

    v4.0.1
    CI / verify (push) Successful in 53s
    Release / release (push) Successful in 39s
    Stable

    torben released this 2026-09-02 21:14:49 +00:00 | 82 commits to main since this release

    4.0.1 - 2026-09-02 - Issue-Board: vier Pflicht-Label-Familien und Body-als-Wahrheit

    Author: Torben Nehmer

    Das Issue-Schema aus 1.2.1 hatte zwei Pflichtachsen und einen ausdrücklich begründeten Verzicht
    auf eine dritte: eine Taxonomie mit mehr Achsen brauche eigene Pflege, und das Board habe einen
    einzigen Betreuer. Diese Begründung ist entfallen, weil die Pflege inzwischen maschinell
    passiert - Body-Rewrites und Kommentare laufen über eine LLM-Sitzung, Menschen fassen in der
    Regel nur Labels an. Damit sind vier Achsen bezahlbar (Issue #41).

    Pflicht auf jedem offenen Issue sind jetzt vier Label: area/ (kb, distribution,
    corpus, workflow, process - kein area/tools, Tooling wird nach der bedienten Domäne
    einsortiert, nicht nach Codeort), kind/ (decision, build, defect), prio/
    (blocking, planned, waiting - reine Umbenennung von 1/2/3) und size/ (S, M,
    L; XS entfällt). Dazu zwei optionale Flags: status/blocked für Abhängigkeit von einem
    anderen offenen Issue, status/unconfirmed für einen ungeprüften Verdacht, unter dem size
    und prio vorläufig sind. Ein unconfirmed-Issue endet in der Triage entweder ohne Flag und
    mit verbindlichen Werten oder geschlossen mit Begründung - die Prozessentsprechung zu
    Invariante 3.

    Der Issue-Body ist ab jetzt aktuelle Wahrheit, nicht Ursprungstext. Die Umsetzung eines
    Issues zieht sich über mehrere, zeitlich getrennte Sitzungen, und der Body ist das einzige, was
    sie verbindet: eine Sitzung muss aus ihm allein rekonstruieren können, was entschieden und was
    offen ist. Er wird deshalb umgeschrieben statt ergänzt. Jeder Rewrite bekommt einen Kommentar,
    der ausschließlich benennt, was sich geändert hat - keine Vollkopie des alten Stands, weil ein
    Mensch zwei Fließtexte nicht diffen kann und eine Kopie pro Revision damit keine Historie ist,
    sondern nur eine weitere Kopie.

    Geändert: instructions/dev/issue-tracking.md (Schritte 2,
    3, 5 neu; Schritt 4 um area/ und kind/ erweitert; der Entscheidungspunkt „Two labels feel
    too coarse?" entfällt) und die Beschreibungszeile in instructions/dev/stack-dev/SKILL.md. Für
    eine ausgelieferte Instanz ändert sich nichts: dist export schließt instructions/dev/
    vollständig aus, weshalb dies ein PATCH ist und kein MINOR - dieselbe Begründung wie bei
    1.2.1. Noch offen aus #41: kb/concepts/Issue Label Scheme.md beschreibt weiterhin das
    zweiachsige Schema und braucht eine eigene wiki-manage-Sitzung.

    Downloads
  • v4.0.0 177c7e9ce8

    v4.0.0
    CI / verify (push) Successful in 55s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-02 16:42:22 +00:00 | 90 commits to main since this release

    4.0.0 - 2026-09-02 - Prosa ist kein Identifier: Link-Taxonomie als Enum, generierte Regionen mit Markern

    Author: Torben Nehmer

    Breaking Change: Beziehungslabel sind Enum-Werte in related: statt Freitext im Body-Bullet, toolgefuehrte Abschnitte liegen zwischen Marker-Paaren statt hinter ihrer Ueberschrift, und xref add schreibt nur noch eine Kante statt beider Richtungen. tools/chemenu/sections.py ist geloescht. Eine bestehende Instanz muss sections: in kb/CONVENTIONS.md auf links/footnotes umstellen, outbound: in jede COLLECTION.md eintragen, die {section.*}-Variablen aus ihren Page-Type-Templates entfernen und den Korpus umstellen - sonst scaffoldet new die Variablen woertlich in neue Seiten. Ablauf: instructions/migrations/4.0.0-link-taxonomy.md

    Der Stack benutzte an drei Stellen Prosa als Identifier, und jede hat messbar etwas
    gekostet. Die Überschrift eines Abschnitts war seine Adresse (^## Beziehungen$), was die
    KB-Sprache zu einer Compiler-Konstante machte und das Ende der Region zur Schätzung - sie lief
    bis zur nächsten Überschrift, davor bis zum Dateiende, und hat auf acht Seiten still Inhalt
    gelöscht. Das Beziehungslabel stand nur im Body-Bullet, also konnte nichts das Vokabular prüfen:
    gemessen am Korpus 152 distinkte Label in 337 Bullets gegen dreizehn dokumentierte, 102 davon
    genau einmal vorkommend. Und xref add spiegelte jede Kante, was ## Siehe auch mit 555
    Bullets ohne Label füllte - 353 davon beweisbar redundant.

    Was jetzt Identifier ist. Eine Region liegt zwischen <!-- wikitool:links --> bzw.
    <!-- wikitool:footnotes --> und wird vollständig aus dem Frontmatter gerendert, Überschrift
    eingeschlossen. Ein Label ist ein Maschinenwert in related: (- depends-on: Hermes), gezogen
    aus instructions/link-taxonomy.md und pro Ziel autorisiert von der Quell-Collection
    (outbound: im COLLECTION.md, Commonplaces ADR-019). Der Body-Bullet ist eine Darstellung
    dieser Daten, nicht ihr zweiter Aufbewahrungsort.

    Gelöscht, ersatzlos: tools/chemenu/sections.py komplett, heading_re, der
    Alias-Mechanismus, PRE_CONVENTIONS_NAMES, cite_block_heading, provenance.__getattr__, die
    {section.*}-Template-Variablen, xrefs Abschnittssuche. Kein Überschriftentext liegt mehr in
    Python - bis auf zwei kosmetische Fallbacks, und die sind harmlos geworden: der Marker trägt die
    Identität, also rendert ein falscher Default falsche Wörter statt Struktur zu zerlegen, und der
    nächste Write repariert es.

    Kanten sind direktional, und das war keine Geschmacksfrage. Die per-Collection-Autorisierung
    ist mit einer automatisch gespiegelten Gegenkante logisch unverträglich: die Spiegelhälfte
    entsteht in einer Collection, deren Regeln der Autor nie gelesen hat. Entweder schriebe das
    Werkzeug unautorisierte Kanten, oder die Regel "die Quellcollection entscheidet" löst sich auf.
    Der Navigationseinwand wird dabei besser beantwortet als vorher: wikitool links show --page
    berechnet die Eingangssicht über den Korpus, vollständig und ohne Pflege, und das gerenderte
    Bullet ist ein gewöhnlicher [[wikilink]] - ein Backlink-Panel zeigt es ohnehin. Die erzwungene
    Gegenkante garantierte nie Vollständigkeit, nur dass jemand daran gedacht hat.

    Der Orphan-Check meldet dadurch mehr, und das ist die Prüfung bei der Arbeit: sie misst jetzt
    Erreichbarkeit statt "ist xref gelaufen".

    obligation: trennt zwei Achsen, die vorher eine waren. migration_kind: sagt wie
    gearbeitet wird, neu obligation: required|offered ob überhaupt. Eine offered-Migration ist
    ein Angebot für eine Datei, die der Instanz gehört - sie blockiert nie, steht nicht in der Kette,
    und migrate done verbucht sie im Ledger, ohne kb_version zu bewegen. Genau daran hing ein
    Entwurfsfehler, den erst der Test gezeigt hat: Offers gegen kb_version zu filtern hätte jede
    Offer verschwinden lassen, sobald irgendein unbeteiligter Pflichtschritt lief. Dazu ist die
    Erkennungshälfte aktiviert, die seit ihrer Einführung ungelesen dalag - die sha256 pro Datei in
    .wikitool-release.json beantwortet jetzt "editiert oder nur empfangen", also ob eine Offer
    kopiert werden darf oder von Hand abgeglichen werden muss.

    types/ teilt sich entlang root:. root: kb heißt Wissensseite heißt Instanz: die vier
    Page-Type-Specs samt Schemas gehen als .template, instruction/lint-report/type-spec
    verbatim. Damit ist die deutsche Prosa in jenen vier Dateien korrekt statt Migrationsschuld -
    es war die richtige Sprache an einem Ort mit falsch deklariertem Eigentümer. Was der Stack von
    der Type-Schicht noch verlangt, ist eine Zeile: ein Type-Spec name: source, dessen Schema
    raw_files fordert. STACK_REQUIRED_COLLECTIONS entfällt als separate Liste - die pflichtige
    Collection wird aus dem base_dir dieses Typs abgeleitet.

    Warum das MAJOR ist. Vorwärts: sections: hat eine andere Form, outbound: fehlt, und die
    in 3.0.0 übernommenen Page-Type-Templates enthalten {section.*}-Variablen, die es nicht mehr
    gibt - new schriebe sie wörtlich in neue Seiten. Rückwärts: 4.0.0 schreibt gelabelte Kanten,
    die 3.0.0s Schema als type: string ablehnt. Beide Hälften des Drop-in-Tests fallen.

    Der Korpus dieser Instanz ist noch nicht umgestellt. Diese Version liefert die Maschinerie;
    lint meldet die 480 noch ungelabelten Kanten als Findings, nicht als Fehler, weil das genau das
    Fenster ist, für das .wikitool-kb.json existiert. malformed_edges und unbalanced_markers
    sind dagegen sofort hart - keines beschreibt eine unkonvertierte Seite, nur eine kaputte. Die
    Beförderung der beiden anderen kommt, wenn der Korpus sie bestehen kann.

    Downloads
  • v3.0.0 502971d147

    v3.0.0
    CI / verify (push) Successful in 53s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-02 13:05:10 +00:00 | 91 commits to main since this release

    3.0.0 - 2026-09-02 - Autorenkonventionen nach Eigentum geschnitten: kb/CONVENTIONS.md, deklarierte Collections

    Author: Torben Nehmer

    Breaking Change: kb/CONTRACT.md ist um alles gekuerzt, was eine Instanz selbst entscheidet; das steht jetzt in einer neuen, instanzeigenen kb/CONVENTIONS.md, aus der der Compiler die drei toolgefuehrten Abschnittsnamen liest. Eine bestehende Instanz muss diese Datei anlegen, auf jedem kb/*/COLLECTION.md profile: und required_by_stack: deklarieren und kb/CONTRACT.md aus dem Release nachziehen - sonst FAILt doctor und docs verify bricht. Ablauf: instructions/migrations/3.0.0-authoring-conventions.md

    kb/CONTRACT.md war eine Datei mit zwei Autoritäten. Der eine Teil ist code-erzwungen und in
    jeder Instanz gleich; der andere - § Language komplett, das Beziehungslabel-Vokabular, die
    Tonfall-Beispiele samt deutscher Buzzword-Liste, die Confidence-Rubrik, das ADR-Präfix - ist
    Konvention, die jede Instanz für sich entscheidet, und wurde trotzdem als bindender Contract
    verbatim ausgeliefert. Wer bei Schritt 5 von setup-instance.md "Englisch" antwortete, hatte
    danach kb/CONTRACT.md, vier Type-Specs und tools/chemenu/sections.py lokal geändert -
    und private-instance.mds Decision Point sagt für so einen Merge-Konflikt: Upstream-Seite
    nehmen. Für diese Instanz hieß das: KB-Sprache zurück auf Deutsch.

    Der Schnitt läuft jetzt danach, wer den Satz ändern darf. kb/CONTRACT.md behält, was
    wikitool erzwingt; neu daneben liegt kb/CONVENTIONS.md, die genauso bindet und der
    Instanz gehört. Unterschied ist Eigentum, nicht Autorität - deshalb liefert die Distribution nur
    kb/CONVENTIONS.md.template, exakt der USER.md/SOUL.md-Split ein Verzeichnis tiefer. Dazu
    instructions/kb-profiles.md: der Katalog erprobter Profile, ausdrücklich Palette und kein
    Enum
    . Übernommen wird der Text in die Instanzdatei, nie ein Verweis auf den Katalog - ein
    Verweis wäre wieder genau die Konstruktion, die dieser Release beendet.

    sections.py hält keine Überschrift mehr. RELATIONSHIPS = "Beziehungen" war die Stelle,
    an der die Konvention in Code übergelaufen war: solange sie dort stand, konnte kein Template die
    Sprache umstellen. Neu ist tools/chemenu/conventions.py, das die drei Namen aus
    kb/CONVENTIONS.md liest; sections.py löst sie per PEP 562 bei jedem Zugriff auf, wie
    config seine Pfade - ein Modulkonstante hätte den Wert an den Baum gebunden, in dem der Prozess
    gestartet ist. Aus demselben Grund ist provenance.CITE_BLOCK_HEADING ein __getattr__ und
    render_cite_block(heading=None) löst innerhalb des Aufrufs auf. Der Alias-Mechanismus, den das
    Modul schon hatte, ist der Migrationspfad: erkannt wird die kanonische Form plus die
    deklarierten section_aliases: plus das, was dieser Stack vor der Konventionsdatei geschrieben
    hat. Ohne Datei antwortet dieser Fallback - richtig für jeden Korpus, der ihn erreichen kann,
    denn der wurde unter genau diesen Namen geschrieben; doctor ist die laute Hälfte davon.

    Die vier Page-Type-Specs schreiben ## {section.relationships} statt einer Überschrift.
    Neue Template-Variablen {section.relationships} / {section.see_also} / {section.footnotes},
    gefüllt aus der Instanzdeklaration. Damit ändert eine anderssprachige Instanz keine Datei unter
    tools/ oder types/
    mehr - was Schritt 5 von setup-instance.md von fünf Editierstellen
    über drei Schichten auf eine Entscheidung reduziert.

    COLLECTION.md bekommt Frontmatter. Bisher wurde eine Collection rein an der Dateipräsenz
    erkannt; die Deklaration brauchte einen Träger, sonst wäre der Ortsschnitt nur durch einen
    Prosaschnitt ersetzt worden. profile: nennt den übernommenen Katalogeintrag (Freitext - eine
    selbst angelegte Collection hat dort keinen), required_by_stack: sagt, ob wikitool die
    Collection namentlich auflöst. Das zweite ist nicht die Wahl der Instanz: docs verify
    prüft es beidseitig gegen kb_collections.STACK_REQUIRED_COLLECTIONS. Heute steht dort genau
    sources - sources coverage, die [^cite-id]-Auflösung und kb/provenance.md hängen an dem
    Namen, entities an keinem.

    Das zweite Leck der Merge-Prozedur ist zu. git checkout HEAD -- kb raw holte alles unter
    beiden Stages auf den Vor-Merge-Stand - auch kb/CONTRACT.md und raw/CONTRACT.md. Änderte der
    Upstream einen davon, warf die Prozedur das Update still weg, und die Kontrollzeile meldete dabei
    leer, bestätigte den Fehler also, statt ihn zu fangen. private-instance.md nimmt die
    Upstream-Seite jetzt für die drei Maschinerie-Pfade unter den Content-Stages zurück
    (kb/CONTRACT.md, kb/CONVENTIONS.md.template, raw/CONTRACT.md) und schließt sie aus der
    Kontrollzeile aus. Dieselbe Altlast in der Tarball-Richtung: INSTALL.md Schritt 3 fasste kb/
    gar nicht an und zog kb/CONTRACT.md damit nie nach - jetzt ausdrücklich benannt.

    Verworfen, gemessen: sources/ aus kb/ herausziehen. Der Graph ist einwurzelig
    (kb_scan.iter_kb_pages macht ein rglob über kb/, darauf sitzen Link-Graph, Orphan-Check,
    index rebuild und search), und Source-Seiten sind darin der dichteste Knotentyp. Ein Hoist
    machte jede Graph-Operation dauerhaft zweiwurzelig, um ein Verzeichnis umzubenennen. Vor allem
    aber kann der Ort Eigentum ohnehin nicht kodieren, sobald Collections offen sind: eine selbst
    angelegte liegt im selben kb/ wie die Defaults. Eigentum ist eine deklarierte Eigenschaft -
    daher das Frontmatter oben. Gitea #39 trägt die Ablehnung im Volltext.

    Warum das MAJOR ist. Die Rückwärtshälfte des Drop-in-Tests hält - 2.5.0 ignoriert beide neuen
    Deklarationen folgenlos. Die Vorwärtshälfte nicht: nach dem Kopieren der Maschinerie FAILt
    doctor auf der fehlenden kb/CONVENTIONS.md, docs verify bricht auf den undeklarierten
    Collections, und kb/CONTRACT.md muss aus dem Release nachgezogen werden. Ein Shim war die
    Alternative (doctor nur WARN, Pflichtfelder tolerant) und wurde verworfen: er hätte genau den
    Zustand normalisiert, in dem eine Instanz glaubt, sie habe entschieden, während in Wahrheit der
    Fallback antwortet - für eine englische Instanz hieße das ## Beziehungen in englischen Seiten.
    Die Handarbeit ist eine Datei und zwei Frontmatter-Zeilen je Collection; keine einzige kb/-Seite
    ändert sich, weshalb migrate done 3.0.0 --pages 0 ehrlich und kein Platzhalter ist.

    Downloads
  • v2.5.0 31662dc3ff

    v2.5.0
    CI / verify (push) Successful in 54s
    Release / release (push) Successful in 37s
    Stable

    torben released this 2026-09-02 07:20:31 +00:00 | 93 commits to main since this release

    2.5.0 - 2026-09-02 - Versionsstelle: Kompatibilitaet statt Inhaltsmigration, Breaking-Change-Vermerk erzwungen

    Author: Torben Nehmer

    Die Doku des Stacks führte für die Wahl der Versionsstelle zwei Fragen zusammen, die nicht
    dieselbe sind: muss der Korpus migriert werden? und ist die neue Version ein
    Drop-in-Ersatz?
    An allen drei Stellen, die einer Session vor dem Bump begegnen - die Tabelle
    in stack-dev, der Modul-Docstring in version.py, INSTALL.md § Version und Updates -
    stand nur die erste. Nur version bump --help formulierte es richtig, also ausgerechnet die
    Stelle, die man erst liest, wenn die Entscheidung gefallen ist. Bei 2.0.0 hat genau diese
    Lücke zu 1.9.0 geführt (Gitea #26; der Eintrag zu 2.0.0 trägt den Fall im Volltext).

    Die Regel, jetzt an einer Stelle. Neu ist
    instructions/dev/version-parts.md: der Drop-in-Test in zwei Hälften - vorwärts (kopieren,
    fertig, keine Handarbeit durch Nutzer oder Migrationsskript) und rückwärts (die alte Version
    lässt sich noch zurückinstallieren) -, der Katalog der Brüche, die die Grenze bei völlig
    unangetastetem kb/ überschreiten (Update-Pfad, Artefaktname, Import-Name, Flags, Envvars,
    Shape maschinengelesener Dateien), und der Fall 2.0.0 als Fallbeispiel. instructions/dev/,
    also nicht in einer ausgelieferten Instanz - die Datei entscheidet, wer den Stack entwickelt.
    Die ausgelieferten Artefakte tragen deshalb die Kurzform der Regel, aber keinen Verweis auf
    sie: instructions verify meldet einen solchen Verweis, weil er nach dist export ins Leere
    zeigen würde.

    Breaking Changes sind teuer, also genehmigungspflichtig. stack-dev hat einen
    Entscheidungspunkt bekommen: stellt sich eine Änderung als nicht drop-in heraus, wird nicht
    aus eigener Initiative über die Grenze gebumpt. Vorgelegt werden der konkrete Bruch, was jede
    bestehende Instanz von Hand tun muss, und die Alternativen - Bruch per Shim vermeiden,
    aufschieben und mit dem nächsten bündeln, oder in einen kompatiblen und einen brechenden Teil
    mit Deprecation-Fenster spalten -, dazu eine Empfehlung. Dann Freigabe abwarten.

    --breaking ist neu und bei jedem Grenzübertritt Pflicht. Es schreibt eine
    **Breaking Change:**-Zeile in den Eintrag, vor die Migrationszeile: das ist, was der Operator
    einer bestehenden Instanz beim Lesen der Release-Notes zuerst braucht. docs verify prüft den
    neuesten grenzüberschreitenden Eintrag darauf, analog zu **Migration:** none required und aus
    demselben Grund - eine Prosa-Regel driftet, ein Marker nicht. Auf einem kompatiblen Bump wird
    --breaking verweigert, damit es keine Gewohnheit wird. Beide Prüfungen bleiben getrennt:
    check_migration_for_boundary fragt, ob der Inhalt sich bewegt, check_breaking_change_for_boundary,
    ob dem Operator gesagt wurde, dass der Tausch keiner zum Drüberkopieren ist. Ein Rebranding
    besteht die erste und fällt durch die zweite.

    Was ausdrücklich nicht passiert: docs verify prüft weiterhin nicht, ob die Stelle
    richtig gewählt
    wurde. Kein Validator unterscheidet ein umbenanntes Flag von einem neuen; das
    bleibt Urteil, und dieser Eintrag existiert, weil das Urteil eine Anleitung braucht, keinen
    Automaten.

    Berührt: instructions/dev/version-parts.md (neu), instructions/dev/stack-dev/SKILL.md,
    tools/chemenu/version.py, tools/chemenu/commands/version_cmd.py,
    tools/chemenu/commands/docs_verify.py, tools/CONTRACT.md, INSTALL.md. Sechs neue Tests.

    Downloads
  • v2.4.1 83018fcc7d

    v2.4.1
    CI / verify (push) Successful in 53s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-02 05:30:49 +00:00 | 97 commits to main since this release

    2.4.1 - 2026-09-02 - INSTALL-MCP.md: Menschendoku fuer den Leseserver, und sie wird mit ausgeliefert

    Author: Torben Nehmer

    Nachtrag zu 2.4.0. Der Server war ausgeliefert und für Menschen nur über tools/CONTRACT.md und
    eine Instruktion beschrieben — beide für Agenten geschrieben. INSTALL-MCP.md schließt das,
    neben INSTALL.md und in derselben Form: Voraussetzungen, sechs Schritte, Verifikation,
    Troubleshooting.

    Was drinsteht, das sonst nirgends zusammen stand: die Client-Konfiguration als JSON-Block;
    dass WIKI_TRACE=0 nicht optional ist und wie die Startverweigerung aussieht; dass
    --host 0.0.0.0 bewusst nicht der Default ist; und der Verweis auf die
    Traefik-ForwardAuth-Middleware unter https://gitea.nehmer.net/torben/gitea-mcp-forward-auth
    samt dem, was sie erwartet (Bearer-Token gegen SHA-256-Hashes, AUTH_PROXY_TOKEN_HASHES_DIR).
    Der Verifikationsabschnitt ist ein lauffähiges Skript — wörtlich so ausgeführt, bevor es hier
    landete.

    dist export liefert die Datei mit. ROOT_FILES ist eine Allowlist, also hätte sie sonst
    keine Instanz erreicht: ein ausgelieferter Server, dessen Bedienungsanleitung im Ursprungs-Repo
    zurückbleibt, ist einer, den der Empfänger durch Quelltextlesen findet. INSTALL.md bekommt
    zusätzlich CHEMENU_ROOT und WIKI_TRACE/WIKI_TRACE_DIR in seine Konfigurationstabelle —
    beide waren seit 2.3.0 bzw. seit jeher wirksam und dort nicht aufgeführt.

    Dateien: INSTALL-MCP.md (neu), INSTALL.md, README.md,
    tools/chemenu/commands/dist_cmd.py.

    Downloads
  • v2.4.0 576df2cddd

    v2.4.0
    CI / verify (push) Successful in 52s
    Release / release (push) Successful in 37s
    Stable

    torben released this 2026-09-02 05:22:23 +00:00 | 98 commits to main since this release

    2.4.0 - 2026-09-02 - MCP-Leseserver: zweiter Konsument auf demselben Kern

    Author: Torben Nehmer

    Letzter Schritt der Sequenz aus #36, inhaltlich Issue #19. chemenu bekommt einen zweiten
    Konsumenten: search, types, describe_type, lint und status über MCP. Kein CLI mit
    angeschraubter Netzwerkschnittstelle — CLI und Server sind zwei Adapter auf dem Kern, den 2.3.0
    freigelegt hat.

    tools/chemenu/mcp/, im Repo statt als eigenes Artefakt. Der Golden-Test, der die
    Serverantworten gegen die --json-Formen der CLI hält, läuft nur mit beiden Seiten in einer
    Testsuite; getrennt würde aus einem Contract eine Versionsabsprache. Der Test ruft wikitool als
    Subprozess gegen denselben Baum auf, über CHEMENU_ROOT — womit er nebenbei die Root-Auflösung
    von außen mitprüft.

    Zwei Transports. stdio zum Entwickeln und Testen ohne Netz, streamable-http für die
    Auslieferung — der einzige, vor den sich die Authentifizierungs-Middleware überhaupt setzen kann,
    weil sie ein HTTP-Reverse-Proxy ist. sse ist über das SDK erreichbar und wird bewusst nicht
    angeboten: der abgelöste Remote-Transport, jetzt darauf zu bauen verschiebt den Wechsel nur.
    --host/--port gibt es, weil der Default auf Loopback bindet und ein Container hinter einem
    Proxy eine Adresse braucht, die der Proxy erreicht — eine Eigenschaft der Software, nicht einer
    Installation. Beide Transports sind gegen den echten Korpus gegengeprüft.

    Kein Schreibpfad, strukturell. Weder der Server noch chemenu.api importiert irgendetwas
    unter chemenu.commands, also existieren new, touch, xref, cite, publish, migrate
    und version bump in dieser Reichweite gar nicht, statt aus einer Liste gefiltert zu werden. Ein
    Test importiert das Servermodul in einem frischen Interpreter und sieht in sys.modules nach;
    ein zweiter ruft alle fünf Tools auf und vergleicht den Dateibaum, HEAD und
    git status --porcelain vorher/nachher.

    Jede Antwort trägt ihren Commit. commit und as_of in jedem Payload; null heißt, der
    bediente Baum hat uncommittete Änderungen und die Antwort entspricht keiner Revision. Der Stempel
    ist die Revision, aus der die Seiten tatsächlich gelesen wurden — zwischen Laden und Stempeln
    kann der Baum sich bewegen, deshalb reicht der Ladepfad seine Revision durch, statt noch einmal
    zu fragen. Das war beim ersten Durchlauf falsch: types/lint/status lasen die zuletzt
    gecachte Revision und stempelten null, obwohl der Baum sauber war.

    Telemetrie in den bedienten Baum wird beim Start verweigert, nicht stillschweigend
    umgeleitet. Tracing ist per Default an und schreibt nach reports/telemetry/ im Repo — genau das
    Verzeichnis, das der Sync per git reset --hard wegräumen darf. WIKI_TRACE=0 oder
    WIKI_TRACE_DIR außerhalb des Korpus. Heute schreibt auf diesem Pfad nichts (der Emitter hängt an
    cli.main() und den Gates), die Sperre ist gegen später.

    Fehler an der Protokollgrenze. Ein ChemenuError wird zum ToolError des SDK — eine
    absichtliche Ablehnung, deren Text den Aufrufer erreicht. Alles andere bleibt ein Absturz, dessen
    Text auf dem Server bleibt. Ein kaputtes Prädikat ist das Argument des Aufrufers, also muss die
    Zeile mitreisen, die sagt, was stattdessen zu schreiben ist.

    Bewusst nicht enthalten: Authentifizierung und Rate Limiting (Middleware vor dem Prozess),
    Deployment (private Infrastruktur), der Iteration Budget Gate — er begrenzt eine Agenten-Session
    und nicht einen Nutzer, weshalb Retrieval von ihm befreit ist; ihn hier als Rate Limiter zu
    benutzen würde ihn dazu verwässern.

    Die Abhängigkeit ist optional (tools/requirements-mcp.txt): eine Instanz, die nur die CLI
    benutzt, soll dafür nicht pydantic, starlette, uvicorn und cryptography installieren müssen. CI
    installiert sie, denn ein übersprungener Golden-Test ist genau der Weg, auf dem Server und CLI
    unbemerkt auseinanderlaufen.

    Betrieb und Sync-Mechanismus: instructions/mcp-read-server.md.
    Polling (git fetch && git reset --hard) statt Webhook — kein eingehender Endpunkt, keine
    Signaturprüfung. reset --hard ist dort tragend und keine Bequemlichkeit: ein abgedrifteter Baum
    antwortet zwar richtig, parst aber bei jeder Anfrage neu und stempelt jede Antwort mit null.

    Dateien: chemenu/mcp/ (neu: server.py, __main__.py), chemenu/api.py,
    tools/requirements-mcp.txt (neu), instructions/mcp-read-server.md (neu), tools/CONTRACT.md,
    tools/README.md, .gitea/workflows/ci.yml, tests/test_mcp_server.py (neu).

    Downloads
  • v2.2.2 32a9b8eb3f

    v2.2.2
    CI / verify (push) Successful in 45s
    Release / release (push) Successful in 37s
    Stable

    torben released this 2026-09-01 17:18:21 +00:00 | 102 commits to main since this release

    2.2.2 - 2026-09-01 - Doku-Verdrahtung: Publish-Remote Gate im Werkzeugvertrag, Projektseite auf oeffentlich

    Author: Torben Nehmer

    Nachziehen dessen, was 2.2.0 und die Veröffentlichung offen gelassen haben. Gefunden durch eine
    Durchsicht auf lose Enden, nicht durch einen Fehlschlag — docs verify deckt den Fall nicht ab,
    weil es Kommando-Namen gegeneinander prüft, nicht ob ein neuer Fehlerfall beschrieben ist.

    tools/CONTRACT.md kannte das Publish-Remote-Gate nicht. Der Werkzeugvertrag ist die Stelle,
    an der pro Kommando steht, was ein Fehlschlag bedeutet und ob ein Retry sicher ist — und
    publish hatte seit 2.2.0 einen dritten Weg zu Exit 42, der dort nirgends stand. Ergänzt in
    beiden Richtungen: in der Kommandozeile (URL statt Remote-Name, pushurl wird gelesen, fehlende
    Datei heißt unbeschränkt, kaputte Datei ist ein Fehler) und im Fehlerkontrakt, wo der
    entscheidende Unterschied zu den anderen beiden Gates steht — es gibt keine --confirm-Zeile,
    die der Agent nachreichen könnte.

    instructions/gates.md verwies nicht auf die Prozedur, für die das Gate gebaut wurde.
    Jetzt verlinkt, mit dem Hinweis auf Schritt 4: Das Gate gehört vor den ersten publish, später
    hinzugefügt schützt es das Fenster nicht, das es schließen soll.

    Die Projektseite beschrieb sich selbst falsch. kb/entities/projects/Chemenu.md nannte
    Chemenu ein „persönliches IT-Wissens-Wiki" mit dem Zweck „Persönliche IT-Wissensbasis" — seit
    der Veröffentlichung schlicht unzutreffend, und es ist die Seite, die ein Fremder über das
    Projekt liest. Neu gefasst: deterministischer Wissenskompiler, diese Instanz seit 2026-09-01
    öffentlich als Testbett und Demo, Lizenz benannt.

    Die historische Aussage über die monolithische AGENTS.md (~30 KB) bleibt — sie war zu ihrer
    Zeit korrekt und ist belegt. Sie ist jetzt als Ausgangspunkt datiert statt als Gegenwart
    formuliert, dieselbe Unterscheidung, die #29 für die Issue-Texte trifft: ein Pfad als Wegweiser
    wird nachgezogen, ein Pfad als Beleg bleibt stehen und bekommt ein Datum.

    Dateien: tools/CONTRACT.md, instructions/gates.md,
    kb/entities/projects/Chemenu.md, kb/entities/INDEX.md (generiert).

    Downloads
  • v2.2.1 b2f7dec122

    v2.2.1
    CI / verify (push) Successful in 43s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-01 17:06:03 +00:00 | 103 commits to main since this release

    2.2.1 - 2026-09-01 - private-instance: der Demo-Korpus wandert beim Merge doch mit - Prozedur korrigiert

    Author: Torben Nehmer

    instructions/private-instance.md behauptete in 2.2.0, ein git merge upstream/main löse
    Änderungen am Demo-Korpus stillschweigend auf, weil die private Instanz ihn einmal gelöscht hat:
    deleted-in-ours, unmodified-in-theirs. Das war nicht gemessen, sondern angenommen — und es
    ist falsch. Ein Nachbau mit einem Upstream, der seinen Korpus bewegt, zeigt drei verschiedene
    Verhalten:

    Upstream tut git merge upstream/main tut
    ändert eine Seite, die du gelöscht hast CONFLICT (modify/delete) — und lässt die Upstream-Fassung im Arbeitsbaum liegen. Ein git add -A beim Auflösen holt die Demo-Seite zurück.
    legt eine neue Seite an staged sie stillschweigend. Kein Konflikt, keine Meldung.
    löscht eine Seite, die du auch gelöscht hast nichts. Der einzige harmlose Fall.

    Die mittlere Zeile ist die gefährliche, weil nichts sie ankündigt. Ein Upstream, der einen
    Demo-Korpus ausliefert und ihn als Testbett benutzt, legt Seiten an — und jede einzelne
    landet in der privaten Instanz und taucht dort in lint, index, search und
    confidence decay auf. Genau diese Doppelnutzung beschreibt Issue #28.

    Korrigiert: Die Update-Prozedur hält den Merge mit --no-commit offen, zwingt kb/ und
    raw/ danach auf den eigenen Stand zurück (git rm --cached, rm -rf, git checkout HEAD --)
    und schließt ihn erst dann. Solange der Merge offen ist, zeigt HEAD noch auf den Commit davor,
    und genau das macht den Schritt sauber. Anschließend eine Kontrolle, die man nicht überlesen
    kann:

    git diff --name-only $BEFORE HEAD -- kb raw     # muss leer sein
    

    Das Rezept ist wörtlich so nachgespielt worden, wie es jetzt in der Datei steht — mit einem
    Upstream, der gleichzeitig eine Seite ändert, eine anlegt, eine löscht und dasselbe unter
    raw/ tut. Ergebnis: Stack aktualisiert, nur eigener Inhalt übrig, Kontrolle leer,
    Arbeitsbaum sauber.

    Auch die Decision Points korrigiert. „Konflikt in kb/ per Hand als keep deleted
    auflösen" stand vorher da und ist der Rat, der in die Falle führt: git add -A committet die
    Fassung, die git im Arbeitsbaum liegen gelassen hat.

    Dateien: instructions/private-instance.md.

    Downloads
  • v2.2.0 7263f85936

    v2.2.0
    CI / verify (push) Successful in 44s
    Release / release (push) Successful in 36s
    Stable

    torben released this 2026-09-01 16:09:37 +00:00 | 105 commits to main since this release

    2.2.0 - 2026-09-01 - Publish-Remote Gate: publish schreibt nur an erklaerte Ziele

    Author: Torben Nehmer

    Der Stack bekommt sein drittes Gate. Die beiden bestehenden fragen, ob eine Änderung zu groß
    ist und ob ein Rebase gefährlich ist. Dieses fragt, was darunter liegt: ob das überhaupt das
    richtige Repository ist.

    Das Problem entsteht erst durch die private Instanz. Ein Checkout mit eigenem Inhalt hat
    typischerweise zwei Remotes — sein eigenes und das öffentliche Upstream, von dem er
    Stack-Updates zieht. Git unterscheidet die beim Push nicht, also legt ein falsches --remote
    einen privaten Korpus auf ein öffentliches Repository. Das ist nicht billig rückholbar, und
    zwar nachweislich: Beim Veröffentlichen dieses Repos blieb die gesamte alte History nach dem
    Force-Push per SHA abrufbar, bis auf dem Server die Reflogs verfielen und git gc --prune=now
    lief. Ein Force-Push bewegt den Branch, nicht die Objekte.

    .wikitool-remotes.json nennt die erlaubten Push-URLs. Nicht die Remote-Namen: Eine
    Namensliste ließe ein publish durch, dessen origin umgebogen wurde, und genau das ist der
    Fall, den das Gate fangen soll. Gelesen wird die pushurl, wenn der Remote eine setzt, denn
    dorthin schreibt git push tatsächlich.

    Pro Checkout und gitignored, aus demselben Grund wie ENVIRONMENT.md: Zwei Klone pushen an
    zwei verschiedene Orte, eine committete Kopie würde einem privaten Klon also mitteilen, das
    öffentliche Upstream sei ein legitimes Ziel für seinen eigenen Inhalt. Fehlt die Datei, gilt
    keine Beschränkung
    — ein Checkout mit einem Remote und ohne Privates hat nichts zu schützen,
    und eine Pflichtdatei würde aus einer Sicherung Papierkram machen. Eine kaputte Datei ist
    dagegen ein Fehler und kein „keine Beschränkung": Eine beschädigte Sicherung darf sich nicht
    wie eine abgeschaltete verhalten.

    Kein Token, keine Flagge. Die anderen beiden Gates lösen sich mit einem --confirm <token>,
    weil ihre Frage („ist diese Änderung richtig?") für genau ein Changeset beantwortbar ist. Dieses
    fragt „gehört dieser Inhalt in jenes Repository?", und das ist eine stehende Eigenschaft des
    Checkouts, kein Einzelfallurteil. Der Weg daran vorbei ist ein bewusster Edit des Nutzers.
    Ein Agent, der die Datei anfasst, um an einer Verweigerung vorbeizukommen, öffnet ein Gate aus
    eigenem Antrieb — Invariante 6.

    doctor meldet den Zustand statt ihn zu erzwingen: OK mit Anzahl der Ziele, OK bei
    Abwesenheit mit einem Remote, und WARN bei mehr als einem Remote ohne Allowlist — also genau in
    der Form, die eine private Instanz annimmt, sobald sie das Upstream hinzufügt.

    Und die Prozedur, für die das Gate gebaut wurde. instructions/private-instance.md (neu)
    beschreibt die dritte Instanz-Form neben „frisch aufsetzen" und „Repo klonen": eine private
    Arbeitsinstanz, die Stack-Updates von einem öffentlichen Upstream per git merge zieht und
    deren eigener Inhalt nie zurückwandert. Der Grund, warum das dem Tarball-Weg vorzuziehen ist,
    steht dort ausformuliert — cp -r hat keinen Drei-Wege-Merge und keine Konflikterkennung.
    Schritt 4 der Anleitung ist das Gate, und zwar ausdrücklich vor dem ersten publish:
    später hinzugefügt schützt es das Fenster nicht, das es schließen soll.

    Dateien: config.PUBLISH_REMOTES_FILENAME, git_publish.read_allowed_push_urls(),
    push_url_for(), publish_remote_refusal() und die Prüfung vor dem Reconcile-Schritt,
    doctor.check_publish_remotes(), .gitignore, instructions/gates.md,
    instructions/private-instance.md (neu), AGENTS.md (Gate-Liste und Bootstrap-Routing),
    12 neue Tests in test_git_publish.py.

    Downloads