• v4.5.1 87a47cc237

    v4.5.1
    CI / verify (push) Successful in 56s
    Release / release (push) Successful in 35s
    Stable

    torben released this 2026-09-04 13:02:36 +00:00 | 63 commits to main since this release

    4.5.1 - 2026-09-04 - issue-tracking - destructive-step invariants, comment-vs-body authority, rename sweep

    Author: Torben Nehmer

    • issue-tracking - destructive-step invariants, comment-vs-body authority, rename sweep

    Block 1 aus #47 (gemeinsam mit #29): drei Ergänzungen an
    instructions/dev/issue-tracking.md, ausgelöst durch zwei Fehlerklassen, die
    in derselben Sitzung am Stack aufgetreten waren.

    • Schritt 1 trägt jetzt, dass destruktive Schritte im Body die Invariante
      nennen müssen, die sie nicht verletzen dürfen, und dass ein
      Akzeptanzkriterium eine prüfbare Eigenschaft ist, keine Tätigkeit. Auslöser
      war #30: der Body schrieb wörtlich "Arbeitsverzeichnis entfernen" für den
      upstream merge-Ablauf, und genau das wurde zum datenvernichtenden Bug -
      ein shutil.rmtree auf eine Stage mit gitignorierten, nicht
      rekonstruierbaren Daten.
    • Schritt 2 trägt jetzt die Lesesicht auf Body und Kommentare, die es
      bisher nur aus Autorensicht gab: der Body ist der Stand, Kommentare sind
      Historie; ein erkennbar veralteter Body wird richtiggestellt statt
      umgangen; widersprüchliche Kommentare werden nach Beleg aufgelöst, nicht
      nach Datum. Auslöser war ebenfalls #30 (ein Kommentar empfahl das Gegenteil
      dessen, was der Body später festlegte) und #10 (ein seit Tagen veralteter
      Body gegen drei widersprechende Kommentare, zwei davon sich selbst
      widersprechend).
    • Neuer Abschnitt "Renames and other decay in the tracker" nach Schritt 7:
      ein Rename ist erst fertig, wenn auch die offenen Issues nachgezogen sind,
      weil wikitool diesen Tracker nicht kennt und nicht kennenlernen soll. Mit
      der Wegweiser-vs-Beleg-Unterscheidung aus #29 und dem Hinweis, dass auch
      verschwundene kb/-Seiten und private Infrastrukturangaben Issue-Texte
      altern lassen. Ein neuer Trigger in "When to run" verweist darauf.

    Keine der drei Ergänzungen verschiebt die bestehende Nummerierung der
    Schritte 1-7 - die Querverweise darauf (u. a. aus
    instructions/dev/stack-dev/SKILL.md auf Schritt 7, aus
    kb/concepts/Issue Label Scheme.md auf Schritt 2) bleiben also gültig, ohne
    angefasst zu werden.

    Verifiziert: tools/wikitool instructions verify, tools/wikitool docs verify, beide grün (Prosa-only, kein Interface geändert, PATCH).

    #47 bleibt offen (Block 2: der Skill-Schnitt aus Vorschlag E; Block 3: die
    beiden Nebenbefunde). #29 bleibt ebenfalls offen: dieser Block deckte nur den
    Regelabsatz, nicht den noch ausstehenden Pfad-Durchgang durch #4, #5, #15,
    #21, #16, #26 - der war nicht Teil des Auftrags für diesen Block.

    Downloads
  • v4.5.0 0ba94c63d1

    v4.5.0
    CI / verify (push) Successful in 53s
    Release / release (push) Successful in 37s
    Stable

    torben released this 2026-09-04 10:51:12 +00:00 | 65 commits to main since this release

    4.5.0 - 2026-09-04 - Beide Update-Wege in Code: upstream merge fuer Clones, dist upgrade fuer Tarball-Instanzen

    Author: Torben Nehmer

    • Faktenkorrekturen in der 4.4.0-Prosa; DEVELOPMENT.md ohne zweite Kommandobeschreibung
    • wikitool upstream merge/verify: code procedure for taking a stack update, ownership.py as the shared stack/instance boundary
    • upstream merge: combined-commit regression test (edit+add+delete+contract+template+contract-delete in one commit)
    • upstream merge: keep gitignored local data under a content stage, refuse a merge git never opened, report what actually changed
    • dist upgrade: apply a stack update, not just detect one (#7)
    • dist upgrade: Stamp-Semantik nach --keep-local benannt, docs/ownership-and-templates.md auf drei Eigentumsklassen nachgezogen

    Die Prosa zu 4.4.0 - Changelog-Eintrag, docs/version-model.md, instructions/dev/version-parts.md,
    DEVELOPMENT.md - entstand ungeprüft: kein maschineller Check liest sie, und die Sitzung, die sie
    schrieb, hat den dafür vorgesehenen Modellwechsel aus stack-dev Schritt 6 übersprungen. Ein
    nachgeholter Durchgang hat drei Fehler gefunden, von denen der erste der teuerste war.

    Der Befund war an einer Stelle schlicht falsch. Changelog und docs/version-model.md
    behaupteten, unter dem alten Modell seien Nummern vergeben worden, die „nie ausgeliefert" wurden -
    im Changelog sogar mit einer erfundenen Zahl („fünf Minor-Bumps ... vier nie ausgeliefert"). Das
    Gegenteil stimmt: weil release.yml auf jede VERSION-Bewegung feuerte, wurde jeder Bump zu
    einem echten, getaggten Release. Der 2026-09-03 hat vier davon in sechs Stunden erzeugt (4.3.0
    bis 4.3.3), zwei für reine Prosa-Änderungen. Der Schaden war nie ein Phantom-Release, sondern
    dass „Release" aufhörte, etwas zu bedeuten - vier Upgrades an einem Nachmittag sind für einen
    Konsumenten kein kleineres Versprechen, sondern ein unleserliches. Beide Stellen tragen jetzt den
    tatsächlichen Vorgang samt Beleg.

    Dazu zwei kleinere Korrekturen: version-parts.md nannte den Stack weiterhin 2.x (er steht bei
    4.x), und DEVELOPMENT.md trug eine Tabelle, die für jeden Verify-Befehl ein zweites Mal
    beschrieb, was er prüft - eine Kopie dessen, was tools/CONTRACT.md hält und docs verify dort
    gegen die CLI prüft. Die Tabelle ist raus; dass die Datei selbst außerhalb der von diesem Check
    abgedeckten Dokumente liegt, steht jetzt an ihrer Stelle. Der Code-seitige Teil davon - ob
    DEVELOPMENT.md in docs_verify.STAGE_READMES gehört - hängt an #47, zusammen mit der Lücke im
    Skill, die den übersprungenen Modellwechsel überhaupt erst unauffällig gemacht hat.

    Kein Verhalten geändert, nur Prosa - und bewusst als laufender Kandidat gelassen statt fixiert:
    CIs Version-Gate verlangt die VERSION-Bewegung, ein Release verlangt sie nicht. Genau dafür gibt
    es das Modell aus 4.4.0.

    Zweiter Bump auf demselben Kandidaten (#30): git merge upstream/main behandelt einen
    bewegten Korpus asymmetrisch - eine gelöschte, upstream-geänderte Seite meldet sich als Konflikt,
    eine neu angelegte Seite wird still gestaged, nur eine beidseitig gelöschte Seite ist harmlos. Die
    Prosa-Prozedur in private-instance.md § "Taking a stack update" hat das geschlossen, aber mit
    vier eigenen Fehlern: der Pfadsatz stand dreifach (im Skript, im Kontroll-Grep, implizit in
    dist_cmd.py); eine vom Upstream gelöschte Maschinerie-Datei wurde von git checkout MERGE_HEAD -- <pfad> still ignoriert, weil das Skript kein set -e hatte; ein echter Konflikt in
    tools//types//instructions/ endete in einem von der Prosa nirgends erwähnten offenen Merge;
    und ein neuer Maschinerie-Pfad unter einer Content-Stage hätte die Literal-Liste nie erreicht.

    Die Eigentumsgrenze ist jetzt ein Prädikat statt einer Liste: chemenu/ownership.py,
    is_stack_owned(relative), wahr für <stage>/CONTRACT.md direkt unter einer Content-Stage
    (kb, raw, work, reports) und für jeden Pfad, der dort auf .template endet - nicht
    rekursiv, kb/<collection>/COLLECTION.md bleibt seit #39 instanzeigen. dist_cmd.py liest das
    Modul jetzt statt einer eigenen _CONTENT_ALLOWED_NAMES-Liste zu pflegen, und
    CONTRACT_ONLY_STAGES leitet sich aus ownership.CONTENT_STAGES ab statt die drei Stage-Pfade
    ein zweites Mal aufzuschreiben - ein Test hält fest, dass beide Sichten nicht auseinanderlaufen
    können.

    Neu: wikitool upstream merge [--remote upstream] [--branch main] [--no-fetch] und wikitool upstream verify --since <rev> [--until HEAD]. merge prüft Vorbedingungen (sauberer Baum, kein
    laufendes Merge, Remote löst auf), warnt statt zu blockieren, wenn .wikitool-remotes.json fehlt,
    hält den Merge offen (--no-commit --no-ff), zwingt jede Content-Stage auf die lokale Seite
    zurück, holt dann über die Vereinigungsmenge der Bäume von MERGE_HEAD und HEAD genau die
    stack-eigenen Pfade zurück - inklusive einer Löschung, falls der Upstream einen Maschinerie-Pfad
    entfernt hat -, verweigert bei verbliebenen unaufgelösten Pfaden ohne zu committen, committet
    sonst und verifiziert den entstandenen Commit mit derselben Logik wie verify - ein Fund dort
    wird laut gemeldet und nicht automatisch zurückgerollt. Nicht idempotent (AGENTS.md § Tool
    error contract), nicht budget-exempt; verify liest nur und ist wie migrate verify von der
    Budget-Gate ausgenommen. Die Mass-Update-Gate greift bei einem Merge-Commit strukturell nicht -
    das steht jetzt als eigener Absatz in instructions/gates.md, mit upstream merges eigener
    Nachkontrolle als der Sicherung, die hier tatsächlich trägt.

    private-instance.md § "Taking a stack update" verweist jetzt auf den Befehl statt das Skript
    auszuschreiben; die Pfadtabelle bleibt als Erklärung stehen, ist aber nicht mehr die operative
    Liste. Vorschlag B (eigenes Demo-Repo) bleibt zurückgestellt, siehe #30.

    23 neue Tests unter test_upstream_cmd.py (35 Fälle mit der parametrisierten
    is_stack_owned-Tabelle) decken die Fälle aus der Spezifikation ab:
    gelöschte vs. geänderte vs. neu angelegte Seiten, kb/CONTRACT.md- und Template-Änderungen,
    kb/entities/COLLECTION.md bleibt lokal, eine gelöschte raw/CONTRACT.md landet, ein neuer
    Template-Pfad landet, ein offener work/-Lauf landet nicht, ein echter tools/-Konflikt lässt
    den Merge offen, ein schmutziger Baum wird unberührt abgewiesen, "bereits aktuell" ist ein No-op,
    die Publish-Remote-Gate-Warnung, verify gegen einen von Hand verpfuschten Merge, und die
    dist_cmd/ownership-Konsistenz.

    Dritter Bump auf demselben Kandidaten: die im Akzeptanzkriterium geforderte Kombinationsprobe
    fehlte noch - ein einzelner Upstream-Commit, der Editieren, Anlegen, Löschen einer Seite, eine
    Contract-Änderung, eine Template-Änderung und eine Contract-Löschung gleichzeitig bewegt. Jetzt
    als test_one_upstream_commit_mixing_every_case_at_once nachgetragen; alle sechs Erwartungen in
    einem upstream merge-Aufruf verifiziert.

    Vierter Bump: zwei Fehler, die ein Review-Durchgang nach dem Publish gefunden hat. Beide
    waren in der ersten Fassung enthalten, beide hätten Daten vernichtet, und keiner der bestehenden
    Tests hat sie berührt.

    Erstens: die Content-Stage wurde als Ganzes gelöscht. _restore_stage_to_local hieß in der
    ersten Fassung shutil.rmtree(stage_dir) — die wörtliche Übersetzung des rm -rf kb raw aus der
    Prosa-Prozedur. Für kb/ und raw/ ist das harmlos, weil dort nichts Ignoriertes liegt. Für die
    beiden Stages, die dieses Issue neu in den Satz aufgenommen hat, ist es das nicht: reports/
    ist bis auf seinen Contract komplett gitignored und trägt genau die Daten, die nirgends sonst
    existieren — die Telemetrie-Traces, aus denen eval score liest, gespeicherte Eval-Berichte,
    alte Lint-Reports. In dieser Instanz standen zum Zeitpunkt des Fundes 497 Trace-Verzeichnisse
    unter reports/telemetry/; ein einziger upstream merge hätte sie alle gelöscht, und zwar
    stillschweigend, weil git von ignorierten Dateien nichts meldet. Die Stage wird jetzt über die
    getrackten Pfade beider Bäume zurückgesetzt statt über das Verzeichnis; ignorierte lokale
    Daten bleiben unberührt. Leergewordene Verzeichnisse werden aufgeräumt, aber nur wirklich leere.

    Zweitens: ein Merge, den git nie eröffnet hat, hätte die Maschinerie gelöscht. Der Exit-Code
    von git merge --no-commit --no-ff wird bewusst ignoriert (Konflikte unter den Content-Stages
    sind erwartet). Nur: wenn git das Merge gar nicht erst eröffnet — unverwandte Historien, eine
    ignorierte Datei im Weg —, gibt es kein MERGE_HEAD, _tree_paths("MERGE_HEAD") liefert die
    leere Menge, und jeder stack-eigene Pfad in HEAD fällt damit in den Zweig „der Upstream hat
    ihn gelöscht": kb/CONTRACT.md, raw/CONTRACT.md und sämtliche Templates werden entfernt. Der
    Kommando-Ablauf prüft jetzt nach dem Merge-Aufruf, dass tatsächlich ein Merge offen ist, und
    bricht sonst ab, ohne den Baum angefasst zu haben. Beide Fehler haben je einen Regressionstest,
    und beide Tests wurden gegen die alte Fassung laufen gelassen, um zu zeigen, dass sie sie
    tatsächlich fangen.

    Dazu eine Ehrlichkeitskorrektur an der Erfolgsmeldung: sie zählte die wiederhergestellten
    Pfade, nicht die geänderten — ein Merge, der eine Datei bewegt, meldete vier oder fünf. Sie fragt
    jetzt git diff zwischen Vor- und Nach-Commit, kennzeichnet Löschungen, und stimmt damit mit dem
    überein, was ein Leser nachprüfen würde. docs/ownership-and-templates.md hat einen Abschnitt
    bekommen, warum die Grenze ein Prädikat und keine Liste ist — die Begründung, die dieses Issue
    erarbeitet hat, gehörte in die Hintergrunddoku und nicht nur in einen Changelog-Eintrag.

    Fünfter Bump: wikitool dist upgrade (#7), der zweite der beiden Update-Wege. upstream merge oben bedient eine Instanz mit gemeinsamer Git-History; dist upgrade bedient eine
    Instanz aus einem Tarball, ohne History, die bislang eine rein manuelle Prozedur in
    INSTALL.md durchlaufen musste - Schritt 4 verlangte einen sha256-Vergleich von Hand gegen den
    files-Block der alten .wikitool-release.json.

    Die tragende Regel: die Schreibmenge ist genau der files-Block der neuen
    .wikitool-release.json, minus was ein Export aus einer leeren Vorlage neu sät
    (chemenu.ownership.is_export_stub, wie bisher schon für kb/log.md/.gitkeep) oder einmalig
    sät und danach der Instanz gehört (chemenu.ownership.is_upgrade_preserved, neu für
    .wikitool-kb.json und CHANGES.md), plus der Stamp selbst. Jeder Kandidatpfad wird gegen die
    alte Instanz-Summe klassifiziert: unverändert wird geräuschlos überschrieben, neu im Release
    wird angelegt, lokal verändert oder gelöscht wird nie still überschrieben - der Lauf bricht
    mit der vollständigen Liste ab, außer --keep-local sagt ausdrücklich, dass die Dateien liegen
    bleiben sollen. --prune entfernt zusätzlich aus dem Release entfallene Dateien, aber nur
    solche, die seit der Installation unverändert sind.

    Die Migrationskette nach dem Tausch wird aus den instructions/migrations/ des neuen Baums
    ermittelt (kb_state.load_migrations bekam dafür einen directory-Parameter) und nur
    gemeldet, nie ausgeführt - es gibt bewusst kein migrate run. Eine bereits gegen die
    installierte Maschinerie offene Kette lässt den Befehl abbrechen, bevor er die Quelle
    überhaupt öffnet. kb_state.divergent_files() (bisher nur von migrate status gelesen) ist
    jetzt eine dünne Hülle um das neue, zwei-Baum-fähige compare_against_stamp() - gleiches
    Verhalten für den bestehenden Aufrufer, wiederverwendbar für dist upgrades eigenen Vergleich.

    Quelle ist immer ein bereits vorhandenes Verzeichnis oder .tar.gz - kein Download, das bleibt
    allein version checks Sache. Ein Tarball muss genau ein Top-Level-Verzeichnis enthalten (die
    Form, in der release.yml es baut) und wird gegen eine .sha256-Beidatei geprüft, falls eine
    danebenliegt (fehlt sie: WARN, kein Abbruch). Weitere Abbruchgründe vor jedem Schreiben: fehlende
    lokale VERSION/.wikitool-kb.json/Stamp mit files-Block, ein schmutziger Arbeitsbaum (kein
    Git-Repo ist ein WARN, keine Sperre), eine Vorab-Version (-beta.N) ohne --pre, sowie ein
    Downgrade; Gleichstand ist ein No-op. Ein Grenzübertritt der Kompatibilität wird laut gemeldet,
    blockiert aber nicht. Committet und pusht nichts (Invariante 5).

    Gegenüber dem ersten Entwurf des Issues zwei Korrekturen, die dort auch nachgetragen sind: der
    files-Block wurde entgegen der ursprünglichen Annahme bereits vor diesem Bump gelesen
    (divergent_files/migrate status), und die Migrationskette war ursprünglich falsch begründet

    • sie kann nur aus dem neuen Baum kommen, nicht durch eine andere Abfragereihenfolge aus der
      alten Instanz. 24 neue Tests unter test_dist_upgrade.py decken die Klassifikation, alle
      Abbruchgründe, --keep-local, --prune und beide Quellformen (Verzeichnis und Tarball,
      inklusive der sha256- und Top-Level-Prüfung) ab.

    Bewusst nicht angetastet: instructions/private-instance.md (der Clone-Weg ändert sich nicht,
    INSTALL.md benennt jetzt beide Wege nebeneinander) und die Frage, wie dist upgrade mit
    Collection-Templates umgeht, deren Namen eine fremde Instanz gar nicht hat - es verhält sich wie
    upstream merge und schreibt sie, was ein eigenes Issue gegen den Export wäre, keins gegen das
    Upgrade.

    Sechster Bump: die ungeprüfte Phase nachgeholt. Der Abschluss des vorigen Bumps lief auf
    Sonnet, und stack-dev Schritt 6 verlangt dort genau zwei Dinge, die kein Check erzwingt:
    Issue-Body und docs/-Veralterung. Der Body war gemacht, die docs/-Prüfung nicht - sie wurde
    benannt statt durchgeführt. Nachgeholt auf Opus, mit einem Fund.

    docs/ownership-and-templates.md § „The consequence in practice" beschrieb ein Upgrade als
    Zweiteilung: verbatim überschreiben, .template-gestützte Dateien liegen lassen - und begründete
    den ersten Teil damit, dass verbatim ausgelieferte Dateien „safe to replace wholesale" seien,
    weil sie „never instance-specific to begin with" waren. Genau diese Annahme trifft dist upgrade
    nicht: eine Instanz kann eine verbatim ausgelieferte Datei angefasst haben, und die sha256 je
    Datei existiert, um das zu erkennen, statt es vorauszusetzen. Dazu fehlte die dritte Klasse ganz -
    die einmalig gesäten, danach instanzeigenen Pfade (.wikitool-kb.json, CHANGES.md,
    kb/log.md, raw/*/.gitkeep), die im Stamp stehen wie jede andere Datei und deshalb aktiv
    ausgeschlossen werden müssen. Die Seite nennt jetzt drei Klassen und die engere praktische Regel:
    überschreibe die verbatim ausgelieferten Dateien, die diese Instanz nicht angefasst hat.

    Dazu eine Präzisierung in tools/CONTRACT.md, die vorher nirgends stand: nach --keep-local
    wird der neue Stamp trotzdem vollständig geschrieben, trägt also die Release-Summe auch für
    Dateien, die bewusst nicht geschrieben wurden. Der Stamp ist die Vergleichsbasis für den
    nächsten Lauf, kein wörtliches Inventar der Platte - und genau das hält eine übersprungene Datei
    bei jedem weiteren Lauf als abweichend gemeldet, statt sie nach einmaligem Überspringen still als
    aktuell zu führen.

    Downloads
  • v4.4.0 d29d400dd3

    v4.4.0
    CI / verify (push) Successful in 47s
    Release / release (push) Successful in 35s
    Stable

    torben released this 2026-09-03 20:22:38 +00:00 | 72 commits to main since this release

    4.4.0 - 2026-09-03 - Versionskandidat statt Bump-pro-Release: VERSION traegt -beta.N, version release fixiert

    Author: Torben Nehmer

    • Versionskandidat statt Bump-pro-Release: VERSION traegt -beta.N, version release fixiert

    Bisher bekam jeder version bump sofort eine fixierte Nummer, und weil release.yml auf jede
    VERSION-Bewegung feuert, wurde daraus sofort ein Release: Nummern entstanden in
    Commit-Granularität statt in Release-Granularität. Der 2026-09-03 hat so vier Releases in sechs
    Stunden erzeugt (4.3.0 bis 4.3.3), zwei davon für reine Prosa-Änderungen - alle vier echt,
    keines davon eine Einheit, an der ein Konsument sich hätte orientieren können. VERSION trägt
    jetzt zwischen zwei Releases einen laufenden Kandidaten (X.Y.Z-beta.N):
    --major/--minor/--patch eskaliert diesen Kandidaten max-wins gegen den letzten Release, statt
    eine neue Nummer danebenzustellen, und geht dabei nie zurück.

    Version versteht den Suffix, mit einer expliziten Ordnung
    (4.4.0-beta.1 < 4.4.0-beta.2 < 4.4.0, numerisch nach N, nicht lexikografisch). CHANGES.md
    trägt genau einen offenen Eintrag pro Kandidat: der erste Bump eröffnet ihn, jeder weitere
    aktualisiert Heading und die maschinenverwaltete Bump-Titel-Liste in
    <!-- wikitool:bumps --> (Marker-Konvention aus blocks.py, aber bewusst nicht in
    blocks.BLOCKS - diese Region gehört zu CHANGES.md, nicht zu einer Seite). version release
    ist neu und fixiert einen Kandidaten: Suffix weg, Eintrag geschlossen, committet und pusht nichts.

    Vier Stellen am Bestand angepasst, die das Kandidatenmodell sonst still beschädigt hätten:
    release.yml überspringt einen suffixbehafteten VERSION-Push sauber, bevor die Releases-API
    gefragt wird, statt jeden Beta-Bump zu veröffentlichen; die Grenzübertritts-Checks in
    docs verify (check_migration_for_boundary, check_breaking_change_for_boundary) messen jetzt
    gegen den letzten Release (version_mod.last_release) statt gegen den zweitobersten Eintrag,
    der zwischen zwei Betas keine Grenze mehr hergibt; kb_state.chain()/next_link() vergleichen
    gegen die Kandidatenbasis, weil eine Migration mit Ziel 4.4.0 sonst bei installiertem
    4.4.0-beta.1 aus dem Intervall fällt (4.4.0-beta.1 < 4.4.0); read_kb_version() verweigert
    einen Prerelease, weil eine Inhaltsform kein Beta kennt. dist export schreibt VERSION und den
    Stamp weiterhin ehrlich mit Suffix, aber .wikitool-kb.json bekommt die Basis.

    Menschendoku für die Erzeuger-Seite: DEVELOPMENT.md im Repo-Root, bewusst nicht in
    dist_cmd.ROOT_FILES (Begründung als Kommentar dort), mit Zeile in AGENTS.md § File naming und
    Zeiger aus README.md. docs/version-model.md hat einen neuen Abschnitt, warum eine Nummer erst
    durch ein Release verbraucht wird.

    Downloads
  • v4.3.3 b1883befc7

    v4.3.3
    CI / verify (push) Successful in 49s
    Release / release (push) Successful in 36s
    Stable

    torben released this 2026-09-03 19:33:51 +00:00 | 73 commits to main since this release

    4.3.3 - 2026-09-03 - Modellwahl nach Pruefbarkeit statt nach Aufgabenname; stack-dev bricht an den Phasenwechseln fuer den Model-Switch

    Author: Torben Nehmer

    instructions/claude-code-model-selection.md routete bisher nach Skill: eine Zeile "Stack
    development -> Opus/high" fuer alles, was tools/, types/ oder instructions/ anfasst. Das ist
    zu grob in beide Richtungen - es verteuert die lange, mechanische Mitte einer Stack-Sitzung, und es
    sagt nichts darueber, dass Anfang und Ende derselben Sitzung anders zu behandeln sind.

    Die neue Achse ist "was faengt hier einen Fehler ab". Wo ein Check in Code steht - pytest,
    docs verify, instructions verify, CI, die Gates - kostet der Fehler eines schwaecheren Modells
    eine Runde und faellt auf. Wo die einzige Durchsetzung eine Sitzung ist, die Prosa liest, faellt
    derselbe Fehler gar nicht auf: er wird ausgeliefert und bleibt stehen. Das ist dasselbe Argument,
    das docs/why-gates-are-code.md fuer Gates fuehrt, angewandt auf die Modellwahl.

    Stack-Entwicklung ist damit nicht mehr eine Zeile, sondern drei:

    Phase Was einen Fehler faengt Modell
    Design, Versionsstelle, Grenzuebertritts-Urteil nichts Opus/high
    Code, Tests, mechanische Doku-Synchronisation pytest, CI, docs verify Sonnet/high
    Issue-Abschluss, docs/-Veralterung, Changelog-Prosa nichts, per Konstruktion Opus/high

    Die Mitte ist die lange Phase und die mit den Checks - dort liegt die Ersparnis. Die beiden
    Raender sind kurz (Minuten, nicht Stunden), haben aber keinen maschinellen Waechter: wikitool
    kennt den Issue-Tracker bewusst nicht, und eine docs/-Seite traegt keinen normativen Satz, also
    gibt es dort nichts zu verifizieren. Sie oben zu lassen ist billig und schuetzt genau die Arbeit,
    die still scheitert.

    Zwei Praezisierungen dazu: Effort ist der billigere Hebel als das Modell - medium steht fuer
    Stack-Arbeit bewusst in keiner Zeile, weil Mehrdatei-Konsistenz das ist, was ein reduzierter
    Effort zuerst aufgibt; high ist die Untergrenze, sobald mehr als eine Datei oder ein Contract
    betroffen ist. Und die Asymmetrie ist benannt: eine unnoetige Opus-Phase kostet einmal Geld, eine
    ungepruefte Sonnet-Phase kann etwas ausliefern, das nie wieder jemand ansieht.

    Damit die Tabelle ueberhaupt wirksam wird, braucht sie Haltepunkte. Eine Sitzung kann ihr
    eigenes Modell nicht wechseln - das ist /model und gehoert dem Nutzer. Eine Empfehlung, die
    niemand zum richtigen Zeitpunkt ausspricht, aendert nichts. instructions/dev/stack-dev/SKILL.md
    bekommt deshalb zwei ausdrueckliche Breaks:

    • Neuer Schritt 3 - "Settle the design before building", mit dem Angebot zum Wechsel nach
      unten, sobald der Plan steht und die Arbeit mechanisch wird. Einmal aussprechen, dann so oder
      so weiterarbeiten.
    • Schritt 6 (Abschluss) bricht in die Gegenrichtung - ab dort greift wieder kein Check. Mit
      der ausdruecklichen Auflage, die Arbeit unabhaengig von der Antwort zu tun: nach dem Publish
      auf einen Modellwechsel zu blockieren wuerde genau den Zustand hinterlassen, den Schritt 6
      verhindern soll. Lief die Phase auf dem billigeren Modell, gehoert das in die Uebergabe statt
      ins Schweigen.

    Ein auftauchender Grenzuebertritt ist unter den Decision points ebenfalls als Anlass zum Wechsel
    nach oben benannt: docs verify prueft, dass ein Uebertritt sich dokumentiert, nie dass die
    Stelle richtig gewaehlt war.

    Die uebrigen Schritte sind unveraendert und nur umnummeriert (alt 3-5 -> neu 4-6).

    Downloads
  • v4.3.2 56ecfc7fee

    v4.3.2
    CI / verify (push) Successful in 47s
    Release / release (push) Successful in 36s
    Stable

    torben released this 2026-09-03 18:49:07 +00:00 | 74 commits to main since this release

    4.3.2 - 2026-09-03 - stack-dev: Issue-Abschluss ist ein nummerierter Schritt, kein Zeiger in einer Routing-Liste

    Author: Torben Nehmer

    Nachfassen zu 4.1.2 (#44), das die Regel geschaerft, aber den Weg zu ihr nicht geaendert hat.
    instructions/dev/issue-tracking.md bekam damals Schritt 7 ("Closing is the last body update,
    not a comment"); instructions/dev/stack-dev/SKILL.md bekam nur eine umformulierte Zeile in
    seiner Routing-Liste. Eine Stunde spaeter schloss #45 auf exakt dieselbe Weise: gruendlicher
    Abschlusskommentar ueber einem Body mit unangehakten Kriterien.

    Die Ursache lag nicht am Text der Regel, sondern an ihrer Erreichbarkeit. Die nummerierten
    Schritte des Skills endeten bei "Verify before publishing". Ein Issue zu schliessen war ueberhaupt
    kein Schritt - es hing an einem Zeiger innerhalb von Schritt 2, und Schritt 2 ist eine
    Routing-Tabelle aus fuenf "read X before Y"-Eintraegen, keine Checkliste. Eine Sitzung folgt dem
    Spine, den sie im Kontext hat; was nur hinter einem Link steht, wird genau in dem Moment nicht
    aufgeschlagen, in dem es greift - am Ende einer langen Sitzung, wenn der Kontext am vollsten und
    die verbleibende Instruktionsflaeche am duennsten ist.

    Verschaerfend arbeitete der Blurb gegen seine eigene Regel: fett gesetzt war "keep it current as
    the state moves, not at the end". Wer den Body unterwegs ungefaehr gepflegt hatte, las daraus
    Konformitaet - der eigentliche Abschlusstest stand nur in der verlinkten Datei.

    Geaendert:

    • Neuer Schritt 5 in stack-dev/SKILL.md - "Close the issue with a body rewrite, not a
      comment", mit dem Test inline (Kriterien abgehakt oder mit Begruendung gestrichen,
      Entscheidungen als entschieden formuliert, kein Praesens ueber einen behobenen Defekt,
      Verifikation benannt) und dem Verweis auf Schritt 7 fuer die volle Form. Damit steht der
      Abschluss auf dem Spine.
    • Schritt-2-Blurb rebalanciert - beide Haelften binden jetzt sichtbar: fortlaufende Pflege
      und der Rewrite vor dem Schliessen, mit Verweis auf Schritt 5.

    Nichts davon ist maschinell pruefbar, und das bleibt richtig so: wikitool kennt den Tracker
    nicht und darf ihn nicht lernen, weil es an Instanzen ausliefert, die kein Board haben
    (issue-tracking.md § "What no tool checks"). Der Skill-Spine ist die einzige Durchsetzung, die
    es geben kann - was der Grund ist, den Schritt zu nummerieren statt ihn zu verlinken.

    Verallgemeinerbar: eine Regel, die in eine verlinkte Instruction geschrieben wird, erreicht
    Sitzungen nur, wenn die nummerierten Schritte des zustaendigen Skills sie in dem Moment
    ansteuern, in dem sie greift.

    Downloads
  • v4.3.1 4e80a07ac7

    v4.3.1
    CI / verify (push) Successful in 48s
    Release / release (push) Successful in 36s
    Stable

    torben released this 2026-09-03 17:43:36 +00:00 | 75 commits to main since this release

    4.3.1 - 2026-09-03 - docs/ befuellt - Stack-Hintergrund fuer vier Themen, Pflegeklausel in AGENTS.md ergaenzt

    Author: Torben Nehmer

    Gitea #45: die von #38 angelegte, bis dahin leere docs/ bekommt ihre ersten vier Seiten - frisch
    geschrieben, nicht durch Umzug aus kb/ befuellt, jede ohne normativen Satz und mit Verweis auf
    das bindende Dokument statt einer Wiederholung seiner Regeln:

    • docs/pipeline-rationale.md - warum raw -> types/tools -> kb -> reports vier getrennte Stufen
      sind und was "never re-derive, always compile" praktisch bedeutet
    • docs/why-gates-are-code.md - warum Mass-Update-, Publish-Remote- und Iteration-Budget-Gate in
      tools/wikitool statt in einer Instruktion stehen
    • docs/ownership-and-templates.md - der Unterschied zwischen stack-eigenen, verbatim
      ausgelieferten Dateien und instanz-eigenen .template-Dateien
    • docs/version-model.md - warum Drop-in-Kompatibilitaet und Migrationsbedarf zwei unabhaengige
      Fragen sind, illustriert an der 2.0.0-Fallstudie

    AGENTS.md § Changelog: neue Klausel zur Pflege von docs/, ergaenzt neben der bestehenden
    Regel zu README.md/EVALS.md/tools/README.md. Eine docs/-Seite veraltet nicht wie ein
    README bei jedem neuen Flag, sondern nur, wenn die aufgeschriebene Begruendung selbst nicht mehr
    traegt - per Konstruktion ungeprueft, da die Seite keinen normativen Satz enthaelt, den
    docs verify pruefen koennte.

    Downloads
  • v4.3.0 0b8ca746fa

    v4.3.0
    CI / verify (push) Successful in 49s
    Release / release (push) Successful in 36s
    Stable

    torben released this 2026-09-03 17:04:16 +00:00 | 76 commits to main since this release

    4.3.0 - 2026-09-03 - docs/ als ausgelieferter Hintergrund-Ort; Decision-Seiten bleiben in kb/, Decay-Skip fuer concept_type: decision

    Author: Torben Nehmer

    Gitea #38: dist export lieferte bislang keine einzige kb/-Seite aus - eine frische Instanz
    bekam den Stack, aber keinen Grund für seine Form. Die dokumentierte adr-NNN--Konvention in
    kb/concepts/COLLECTION.md existierte zudem nur auf Papier: keine der sieben
    concept_type: decision-Seiten folgte ihr, und confidence_decay() lief bedingungslos über sie

    • ein Kategorienfehler, weil Zeitablauf eine Entscheidung nicht falscher macht, nur Supersession
      tut das.

    Neu: docs/ - ein inertes Verzeichnis für Stack-Hintergrund (warum der Stack so gebaut ist,
    nicht was diese Instanz entschieden hat). Keine Frontmatter, kein Typ, kein Index, kein Lint,
    keine Decay, keine Provenance, keine COLLECTION.md. dist export liefert es verbatim aus, wie
    instructions/ und types/. Befüllung folgt in Gitea #45.

    Verworfen, nach Prüfung: ein Umzug der sieben Decision-Seiten nach decisions/. Der
    Subtyp-Floor aus #28 verlangt mindestens eine Seite je deklariertem concept_type, und ein
    Umzug hätte decision auf null gebracht; dazu zeigen 89 Wikilinks aus kb/ sowie
    tool-eigene Frontmatter-Arrays auf die sieben, und links.py/xref add kennen kein Ziel
    außerhalb kb/. Die sieben bleiben in kb/concepts/, ebenso ein zweiter, separat erwogener
    Rename (docs verifyparity verify) - der wäre nur nötig gewesen, wenn ein Befehl auf das
    Verzeichnis docs/ wirkt, und keiner tut das.

    Geändert:

    • confidence_decay() überspringt concept_type: decision strukturell (kategorische Ausnahme,
      nicht als Brücke gebaut - Begründung im Docstring).
    • kb/concepts/COLLECTION.md § Decisions ersetzt die tote ADR-Vorlage durch die real gelebte
      Form: eine Entscheidung ist eine gewöhnliche Concept-Seite, organische Prosa, kein
      adr-NNN--Präfix, **Status:** optional, Supersession per supersedes-Link.
    • kb/CONVENTIONS.md § Naming und instructions/kb-profiles.md (Profil german) korrigiert -
      beide dokumentierten noch die verworfene adr-NNN--Namensregel.
    • AGENTS.md § File naming und § Routing: docs/-Zeile, plus die Regel, dass docs/ keinen
      normativen Satz trägt (das hält Invariante 8 heil - was binden würde, gehört in einen
      Contract).
    • tools/CONTRACT.md: Klarstellung, dass docs verify Dokumentations-Parität prüft, nicht das
      docs/-Verzeichnis, sowie docs/ in der dist export-Zeile ergänzt.

    Additiv und in beide Richtungen drop-in: eine bestehende Instanz ohne docs/ exportiert
    weiterhin identisch (leerer _copy_tree-Treffer), eine Instanz mit docs/ bekommt es ab jetzt
    mitgeliefert. Kein Feld, kein Kommando ändert sein Verhalten für bestehenden Inhalt.

    Migration: none required.

    Berührt: tools/chemenu/commands/confidence_decay.py, tools/chemenu/commands/dist_cmd.py,
    tools/chemenu/tests/test_confidence_decay.py, tools/chemenu/tests/test_dist_cmd.py,
    kb/concepts/COLLECTION.md, kb/CONVENTIONS.md, instructions/kb-profiles.md, AGENTS.md,
    tools/CONTRACT.md.

    Downloads
  • v4.2.0 9b461421e8

    v4.2.0
    CI / verify (push) Successful in 52s
    Release / release (push) Successful in 37s
    Stable

    torben released this 2026-09-03 06:14:11 +00:00 | 77 commits to main since this release

    4.2.0 - 2026-09-03 - Korpus-Kuratierungsrichtlinie: Untergrenzen und Leitplanke für reaktive Fixes

    Author: Torben Nehmer

    Ein Demo-Korpus will klein und stabil sein, ein Testbett groß, unordentlich und in Bewegung -
    dieses Repo verlangt seit der Veröffentlichung beides vom selben kb/ (Gitea #28). Die Sitzung
    vom 2026-09-02 hatte Fixture, --with-demo und ein zweites Repo bereits verworfen; offen blieb
    nur, wie kuratiert "kuratiert genug" heißt und welche Leitplanke reaktive Fixes bekommen.

    Neu: instructions/dev/corpus-policy.md. Fünf Untergrenzen, jede mit einer bestehenden
    wikitool-Prüfung messbar, keine davon durch neuen Tool-Code: jeder Seitentyp und jeder
    deklarierte Subtyp mit mindestens einer Seite, mindestens fünf Seiten mit mindestens drei
    Quellen, ein bis zehn Orphan-Seiten, im Schnitt mindestens vier ausgehende Wikilinks pro Seite.
    Gemessen am 2026-09-03: 181 Seiten, alle Typ-/Subtyp-Floors erfüllt, 12 Seiten mit ≥3 Quellen, 3
    Orphans, Ø 6,2 ausgehende Links - der Korpus war bereits groß genug, ohne dass eine einzige
    Seite eigens dafür angelegt werden musste. Eine Untergrenze wird nie durch eine erfundene Seite
    gefüllt, sondern durch eine echte Quelle beim nächsten passenden Ingest - Invariante 3 gilt
    unverändert.

    Die Leitplanke für reaktive Fixes unterscheidet drei Stufen: punktuelle Änderungen (immer
    erlaubt, gewöhnliche Arbeit), korpusweite Änderungen (nur geplant, mit eigenem Issue und
    work/-Run - trifft eine Session das Mass-Update-Gate während sie etwas anderes tat, holt sie
    sich nicht den --confirm-Token, sondern stoppt und legt ein Issue an) und reaktive Eingriffe
    in Korpusinhalt, um einen Test grün zu machen oder einen Tool-Bug zu umgehen (nie erlaubt,
    Invariante 7). Das Verhältnis zu kb_dir/raw_dir und test_pipeline_l0.py bleibt wie im
    ursprünglichen Befund: kleiner, isolierter Fall in der Fixture, großer, vernetzter Fall in
    kb/ - keine Fixture-Extraktion aus dem Korpus.

    Dev-only und rein additiv - kein Feld, kein Kommando, keine Datei außerhalb von
    instructions/dev/ ändert sich, daher --minor ohne --breaking.

    Migration: none required.

    Berührt: instructions/dev/corpus-policy.md (neu),
    instructions/dev/stack-dev/SKILL.md (Schritt 2, Routing-Zeile).

    Downloads
  • v4.1.2 41f5dfe1cd

    v4.1.2
    CI / verify (push) Successful in 52s
    Release / release (push) Successful in 38s
    Stable

    torben released this 2026-09-03 04:42:13 +00:00 | 78 commits to main since this release

    4.1.2 - 2026-09-03 - Issue-Abschluss ist ein Body-Rewrite, nicht nur ein Kommentar

    Author: Torben Nehmer

    Aufgefallen beim Schließen von #44: der Abschlussbericht stand als Kommentar da, der Body
    darunter weiterhin als offene Arbeit — Abschnitt „Zu entscheiden" über eine längst getroffene
    Entscheidung, ungehakte Checkliste, Präsens über einen Defekt, den es nicht mehr gab.

    Die Regel gab es dafür schon: Schritt 2 von instructions/dev/issue-tracking.md sagt, der Body
    ist die aktuelle Wahrheit und wird umgeschrieben, wenn sich der Stand ändert. Nur ließ die
    Formulierung offen, wann — und Schritt 7 („Close with what actually happened") war vollständig
    erfüllbar, ohne den Body anzufassen. Ein Abschlussbericht im Kommentar fühlt sich beim Schreiben
    vollständig an; dass der Body dabei zurückbleibt, merkt erst der nächste Leser.

    Schritt 2 ist deshalb schärfer geworden: der Body ist das Plan-File dieses Stacks. Dasselbe,
    was das Plan-Dokument eines Harness ist, und genauso gepflegt — fortlaufend, sobald etwas darin
    nicht mehr stimmt, nicht am Ende. Der Maßstab ist der Abbruch, nicht der Meilenstein: eine
    Session kann jederzeit enden, und was der Body in diesem Moment sagt, ist die vollständige
    Übergabe. Eine frische Session muss zu jedem Zeitpunkt allein aus dem Body weiterarbeiten
    können, ohne Kommentare rückwärts zu lesen und ohne einen Menschen, der es neu erklärt. Entschieden
    ersetzt die Frage, erledigt hakt das Kriterium ab, verworfen steht mit Begründung dort, wo das
    Kriterium stand.

    Schritt 7 ist damit kein Sonderakt mehr, sondern die letzte dieser Aktualisierungen: erst Body
    auf den Endstand, dann schließen, dann die Changelog-Zeile aus Schritt 3. Wer Schritt 2 befolgt
    hat, ist fast fertig; wer nicht, zahlt die ganze Schuld im schlechtesten Moment — der
    geschlossene Body ist die Fassung, die danach alle lesen und niemand mehr aufsucht. #44 steht
    als Beispiel drin.

    Schritt 3 zieht die Konsequenz: ein Kommentar pro Session-Umfang, nicht pro Edit. Ein
    fortlaufend gepflegter Body mit einem Changelog-Kommentar je Änderung wäre Lärm; triviale Pflege
    braucht gar keinen. Der stack-dev-Skill sagt es beim Aufgreifen mit, weil dort die Entscheidung
    fällt, ob eine Session den Body überhaupt anfasst.

    Und die ehrliche Antwort auf die Frage nach dem Tooling: es gibt keins, und es soll keins
    geben.
    wikitool kennt diesen Tracker nicht. Es wird an Instanzen ausgeliefert, die unter
    dieser URL keine Issues haben, während instructions/dev/ von dist export gepruned wird —
    ein Gitea-Client im ausgelieferten Tool wäre eine Dev-Abhängigkeit, die jede Instanz mitträgt,
    um ein Board zu prüfen, das keine von ihnen hat. Der Tracker ist ausschließlich über
    gitea-mcp erreichbar, also in einer Session, durch einen Agenten.

    Kein docs verify fängt also einen geschlossenen Issue, dessen Body offen klingt, einen Body,
    der seinen eigenen Kommentaren widerspricht, oder ein fehlendes Pflichtlabel. Das steht jetzt
    als eigener Abschnitt „What no tool checks" in der Instruktion — nicht als Bedauern, sondern als
    Begründung dafür, warum die Reihenfolge in Schritt 7 ausgeschrieben ist statt aus Schritt 2
    erschlossen zu werden.

    Downloads
  • v4.1.1 23307c3c5f

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

    torben released this 2026-09-03 04:36:40 +00:00 | 79 commits to main since this release

    4.1.1 - 2026-09-03 - Testisolation: kb_dir repointet config.ROOT, lint löst Kollektionen gegen den übergebenen Baum auf

    Author: Torben Nehmer

    Issue #44, gefunden beim Bau der Migrations-Gate-Tests für 4.1.0: die kb_dir-Fixture baute
    ihren Baum unter tmp_path, ließ config.ROOT aber auf dem echten Checkout stehen. Jeder
    Codepfad, der eine Datei über config.ROOT/config.KB_DIR auflöst statt über das übergebene
    Verzeichnis, traf damit das echte Repository.

    Der laute Fall war ein Test, der kb_state.write_kb_state() rief und dabei das
    .wikitool-kb.json des Repos überschrieb — Applied-Ledger leer statt zwei Einträgen. In
    git status sofort sichtbar und reversibel; bei einer gitignorierten Datei wäre es das nicht
    gewesen.

    Der stillere Fall ist der teurere. lints Kollektions-Lookup löste eine Seite gegen
    config.KB_DIR auf. Für eine Seite unter tmp_path/kb/ warf das ValueError, die Funktion
    antwortete „keine Kollektion", und die Label-Autorisierung übersprang die Kante wortlos.
    unauthorised_labels war damit faktisch ungetestet — jeder Test, der das Finding hätte
    auslösen können, bekam eine leere Liste und behauptete nichts. Ein grüner Lauf, der wie eine
    Zusicherung aussah.

    Der Fix ist der Codepfad, nicht die Fixture. run_lint() bekommt ein Verzeichnis
    übergeben und löst jetzt auch intern dagegen auf; authorised_labels() bekommt denselben Baum
    gereicht, statt auf config.KB_DIR zurückzufallen. Der Regressionstest lintet einen Baum, von
    dem ROOT bewusst wegzeigt — genau der Fall, den die alte Auflösung verschluckte. Eine Funktion,
    die ein Verzeichnis entgegennimmt, löst dagegen auf: keine Fixture kann diese Form von außen
    reparieren.

    Beide Korpus-Fixturen repointen jetzt. kb_dir tut, was raw_dir längst tat — ROOT auf
    das eigene tmp_path, plus use_shipped_type_specs(). Der Suite-Lauf kippte dadurch keinen
    einzigen Test. Die lokale rooted_kb-Umgehung aus 4.1.0 entfällt damit; die Auswahl zwischen
    zwei fast gleichen Fixturen war Wissen, das nirgends stand.

    Und ein Wächter für die ganze Klasse. repository_tree_guard (session-scoped, autouse)
    vergleicht git status --porcelain vor und nach dem Lauf und lässt die Suite scheitern, wenn
    sich im Checkout etwas bewegt hat — zwei git status-Aufrufe pro Lauf, deshalb per Default an.
    Er vergleicht vorher gegen nachher statt einen sauberen Baum zu verlangen, sagt also nichts über
    die unveröffentlichte Arbeit des Entwicklers. Den Verursacher benennt er nicht;
    CHEMENU_TREE_GUARD=each prüft nach jedem Test und tut es. Ohne git oder außerhalb eines
    Repositorys sind beide still.

    Was der Wächter nicht sieht: eine Prüfung, die unter Test nichts tut, schreibt keine Datei.
    Dagegen hilft nur ein Test, der das Finding tatsächlich auslöst — der neue tut das.

    instructions/dev/testing-conventions.md hat dafür einen eigenen Abschnitt („Which tree a test
    writes into"), einen Schritt in der Checkliste und die Regel für neue Fixturen.

    Downloads