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
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-test1 →
torben/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:
- 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
--breakingflag onversion bump, checked bydocs verify, on the reasoning that a pure-prose rule is exactly what drifted in the first place. - Whether the pre-bump approval step should read like one of the codebase's actual gates
(
instructions/gates.mdlanguage: "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.1 → 2.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 |