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
This commit is contained in:
+218
@@ -0,0 +1,218 @@
|
||||
# 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:
|
||||
|
||||
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.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 |
|
||||
Reference in New Issue
Block a user