Files
chemenu/raw/notes/Conversation Transcript - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02.md
T
torben 9843df99d3 ingest: raw/notes/Conversation Transcript - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02.md
Files changed:
- kb/concepts/INDEX.md
- kb/concepts/KB Stack Versioning.md
- kb/entities/INDEX.md
- kb/entities/projects/Chemenu.md
- kb/entities/tools/wikitool.md
- kb/index.md
- kb/log.md
- kb/sources/INDEX.md
- kb/sources/Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02.md
- raw/notes/Conversation Transcript - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02.md
2026-09-02 09:25:11 +02:00

14 KiB

Conversation Transcript - Version Part Nomenclature and Breaking Change Gate Session

Source: Claude Code session (claude-sonnet-5), chemenu workspace Collected: 2026-09-02 Participant: Torben Nehmer Fidelity: faithful summary transcript, not a verbatim log. The user's instructions and clarifications are quoted verbatim; the agent's own reasoning and file-reading steps are paraphrased; tool output blocks (docs verify, pytest, version bump --dry-run, the Mass-Update Gate refusal) are real, copied from the actual run, not reconstructed. No second-hand material - no subagent was used. No credentials appeared. Single topic, not cut.

Closes Gitea issue #26 (already closed in-session) with 2.5.0. Commit 31662dc on main. Covers picking the version part for a stack change, and adding a user-approval gate for breaking changes to stack-dev.

Turn 1 - /stack-dev kümmere dich um #26

The user invoked the stack-dev skill with the argument kümmere dich um #26. The agent read issue #26 via gitea-mcp (torben/chemenu#26, no comments yet).

The issue's own account (quoted from its body, since it is the source of the whole session): the stack's documentation conflated two questions when choosing a version bump part - "Muss der Korpus migriert werden?" and "Ist der Wechsel rückwärtskompatibel?" - and every place an agent would consult before a bump stated only the first:

Ort Wortlaut
instructions/dev/stack-dev/SKILL.md, Schritt 3 --major ⇔ "Existing content must be migrated"
tools/chemenu/version.py, module docstring "from 1.0.0 on the same rule reads as the familiar 'MAJOR means migration'"
INSTALL.md § Version und Updates "ab 1.0.0 liest sich dieselbe Regel als das gewohnte 'MAJOR heißt Migration'"

Only version bump --help had it right ("A bump that crosses the compatibility boundary additionally requires a migration document ... or --no-migration") - the one place read only after the decision is already made.

The issue's worked example was the 2.0.0 rebranding (torben/llm-wiki-test1torben/chemenu): no page in kb/ changed, so the first attempt at that bump was 1.9.0. Three things broke anyway - the release-feed URL baked into every existing instance's .wikitool-release.json (unrepairable by hand, per invariant 1), the release artefact's filename, and the Python package's import name. The correct bump was --major --no-migration. The issue's "Was 'fertig' heißt" checklist asked for: rewording the three prose locations to lead with compatibility rather than migration, a decision aid enumerating which kinds of break force MAJOR even with kb/ untouched, and a check that the three locations do not end up contradicting each other (invariant 8). It explicitly scoped out teaching docs verify to judge whether a bump chose the right part - "das ist eine Urteilsfrage und kein hartes Orakel."

The agent read the three prose locations plus tools/chemenu/commands/version_cmd.py (the existing --no-migration enforcement) and docs_verify.py (check_migration_for_boundary), to scope the fix before writing anything.

Turn 2 - scope questions, then the user's clarification

Before writing, the agent asked two AskUserQuestion questions:

  1. Whether the "Breaking Change" marker and approval requirement should be prose-only or also enforced in the tool. Options offered: "Prosa + Marker im Code (Empfehlung)" vs. "Nur Prosa". User picked the enforced option ("Prosa + Marker im Code (Empfehlung)") - a --breaking flag on version bump, checked by docs verify, on the reasoning that a pure-prose rule is exactly what drifted in the first place.
  2. Whether the pre-bump approval step should read like one of the codebase's actual gates (instructions/gates.md language: "anhalten ... auf ausdrückliche Freigabe warten") or as a softer "Decision points" entry. User picked the softer framing ("Als Entscheidungspunkt")
    • explicitly not gate language, to avoid diluting the weight of the three real, code-enforced gates (Mass-Update, Publish-Remote, Iteration Budget) by association.

The user then added the clarification that drove the rest of the session, quoted in full:

"genaue nomenklatur für major version change: die neue version ist kein drop-in replacement. Sobald irgendwie Hand angelegt werden muss, sei es durch den user oder durch ein Migrationsscript, ist es ein major version change. selbiges gilt, wenn ein update nicht rückgängig gemacht werden kann (Szenario: User aktualisiert den Stack von vA nach vB, vB hat beim User ein Problem, kann jetzt vA nicht mehr einfach so als Downgrade installiert werden, ist es ein breaking change) in allen Fällen muss bei einem Major version change ein "Breaking Change" vermerkt werden. breaking changes sind damit teuer. passe stack-dev so an, dass in diesen Fällen zwingend der user informiert, Alternativen aufgezeigt und eine freigabe eingeholt wird."

This sharpened the issue's "compatibility" question into a concrete two-sided test - forward (can the new version be copied over with zero hand-work, by user or script) and backward (can the old version still be reinstalled afterwards) - and made explicit that either half failing, independent of kb/, is what MAJOR actually means.

Turn 3 - what was written

instructions/dev/version-parts.md (new). The single source for the rule. Structure: the two-sided drop-in test (step 1); a catalogue of breaks that cross the boundary with kb/ untouched - update path, release-artefact name, package import name, a renamed command/flag/envvar, the shape of a machine-read file like .wikitool-kb.json (step 2); the plain compatible-part table for patch/minor (step 3); the mandatory stop-and-inform-the-user step before any boundary-crossing bump, with three named alternatives (avoid via a compatibility shim, defer and batch with the next crossing, split into a compatible half now and a deprecated-then-removed half later) plus a recommendation (step 4); --breaking usage (step 5); the pre-existing migration-document-or---no-migration step, now explicitly independent of compatibility (step 6); writing the entry body (step 7). Closes with the 2.0.0 case study as the worked example, matching the issue's own account.

Placed under instructions/dev/ (not linked from any distributed artifact) because tools/wikitool dist export prunes that directory wholesale - the agent's first draft linked to it from tools/CONTRACT.md and the version.py docstring, which tools/wikitool instructions verify correctly rejected:

ERROR Instruction layer issues:
  - version-parts.md: lives under instructions/dev/ but is referenced from
outside it and outside a dist:strip block - `dist export` removes
instructions/dev/ wholesale, so that reference would dangle in a distributed
instance. Remove the reference, or wrap it in a <!-- dist:strip-start/end -->
block if it belongs only to this dev instance.

Rejected approach: wrapping the reference in <!-- dist:strip-start/end --> markers so it would still resolve in this repo. Not used - the agent instead rewrote the three shipped locations (tools/CONTRACT.md, version.py docstring, version_cmd.py docstring) to state the short form of the rule standalone, with no pointer to the dev-only file, since a shipped instance never has it to point to.

instructions/dev/stack-dev/SKILL.md. Step 3's table changed from "Existing content must be migrated → --major" to "Not a drop-in replacement ... → --major", with a pointer to version-parts.md for the full test and catalogue. A new "Decision points" entry: if a change turns out not to be a drop-in replacement, stop - do not bump across the boundary on the session's own initiative; show the user the concrete break, what each instance must do, and the three alternatives from version-parts.md step 4, then wait for a go-ahead. Written in the softer "Decision points" register per the user's second answer above, not gate language.

tools/chemenu/version.py. Module docstring reworded: "MAJOR means migration" → "MAJOR breaks", with a new paragraph stating the two questions are independent and naming both markers. New constant BREAKING_CHANGE_MARKER = "**Breaking Change:**", alongside the existing MIGRATION_NONE_MARKER. insert_changes_entry() gained a breaking_reason parameter, writing the **Breaking Change:** line before the migration line - the break is what an operator acts on first.

tools/chemenu/commands/version_cmd.py. bump gained --breaking TEXT. Refused on a non-boundary-crossing bump; required on a boundary-crossing one, with a refusal message that also second-guesses the part choice itself: "If that sentence is hard to write because nothing actually breaks - no hand-work on update, and the old version can still be put back - then the bump is probably not --major." Verified live:

$ tools/wikitool version bump --major --title "smoke" --dry-run
ERROR 2.4.1 -> 3.0.0 crosses the compatibility boundary, so it is not a drop-in
replacement - re-run with --breaking "<what stops working, and what an instance
must do about it>".
If that sentence is hard to write because nothing actually breaks - no
hand-work on update, and the old version can still be put back - then the
bump is probably not --major.
$ tools/wikitool version bump --minor --title "smoke" --breaking "x" --dry-run
ERROR --breaking only applies to a bump that crosses the compatibility boundary;
2.4.1 -> 2.5.0 does not.

tools/chemenu/commands/docs_verify.py. New check_breaking_change_for_boundary(), independent of the existing check_migration_for_boundary() on purpose - the two check different things (did the corpus move vs. was the operator told the swap isn't drop-in), and a crossing with kb/ untouched can satisfy the first while failing the second. Wired into verify() alongside the existing check.

Tests. test_version_cmd.py: nine existing calls updated for the new breaking= parameter, plus three new tests (test_a_boundary_crossing_bump_without_breaking_is_refused, test_breaking_records_what_stops_working_in_the_changelog - including the ordering assertion that BREAKING_CHANGE_MARKER precedes MIGRATION_NONE_MARKER in the rendered entry - test_breaking_is_refused_on_a_compatible_bump). test_docs_verify.py: four new tests mirroring the existing migration-boundary tests but for the breaking-change marker. Full suite: 806 passed.

tools/CONTRACT.md and INSTALL.md. Both reworded to lead with the drop-in question and treat migration as the second, independent one; INSTALL.md additionally tells the reader which two lines to look for in release notes (Breaking Change: and Migration:) before applying an update.

CHANGES.md. New 2.5.0 entry written after the bump, body filled in by the agent (the tool leaves it empty by design) - includes the "what deliberately did not change" note that docs verify still does not judge whether the chosen part was correct, matching the issue's explicit scope-out.

Turn 4 - publish, twice

tools/wikitool version bump --minor --title "..." was run first (11 changed files, drop-in in both directions - the new requirement only binds the next boundary crossing, not retroactively). tools/wikitool publish --message "..." then hit the Mass-Update Gate (11 counted files ≥ threshold 10):

NEEDS USER CLEARANCE Mass-Update Gate: this publish would commit and push 11
counted files (>= threshold 10) to origin/main. ...

Per the gate's own instructions, the agent reproduced the full file-by-area breakdown and the --confirm <token> line in its reply and ran nothing further that turn. The user replied "freigegeben" for both the earlier publish authorization ("publish ist freigegeben") and, in a separate turn, this specific token. The agent then ran tools/wikitool publish --confirm 95ae372d5677 --message '...', which pushed commit 31662dc to origin/main, and verified git rev-parse HEAD origin/main matched afterward.

Turn 5 - issue closeout and this capture

The user asked to update issue #26 "wie vorgeschlagen" (per the agent's own end-of-turn suggestion), run instructions/capture-session.md, and in the same pass correct kb/entities/tools/wikitool.md. The agent posted a comment on #26 summarizing what shipped (including the parts that went beyond the issue's own checklist - the --breaking flag and the second docs verify check, added because plain prose was judged likely to drift again) and closed the issue. This transcript and the kb/ correction are the remaining two steps of that request.

Outcome

Version 2.4.12.5.0 (--minor: new capability, still drop-in both directions)
Commit 31662dc on main, pushed to origin
Files changed 11 (+432/-33): instructions/dev/version-parts.md (new), instructions/dev/stack-dev/SKILL.md, tools/CONTRACT.md, tools/chemenu/version.py, tools/chemenu/commands/version_cmd.py, tools/chemenu/commands/docs_verify.py, tools/chemenu/tests/test_version_cmd.py, tools/chemenu/tests/test_docs_verify.py, CHANGES.md, INSTALL.md, VERSION
Tests 806 passed (tools/chemenu/tests/), including 6 new
Verification tools/wikitool docs verify OK, tools/wikitool instructions verify OK (17 instructions, 6 skills, 12 published copies match), tools/wikitool doctor clean (only the expected WIKITOOL_SESSION_ID WARN)
Issues #26 closed, comment torben/chemenu#26 (issuecomment-474)
CI Not yet observed in this session - a VERSION move on main triggers a tagged release per .gitea/workflows/release.yml; not polled