No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!373
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/368-virtual-anchor-bootstrap"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #368 — operator ratified Option A post-round-3 external cold-read (2026-07-04) supersedes the #355 Option B fail-loud path landed in v0.25.0.
Fresh consumer repo now Just Works: virtual
v0.0.0anchor + walk from initial commit + first cut computes bump from conventional-commits since root.Behavior change (release-decide.sh)
vX.Y.Ztag exists, treat asBOOTSTRAP_VIRTUALwith anchor=v0.0.0+LAST_SHA= git root commitgit log v0.0.0..HEADwould reject the range and produce no bump signal)COMMITS_RAWwalk usesgit log HEAD(inclusive of root) instead ofLAST_SHA..HEAD(exclusive) when virtual — matters when the only commit IS the rootgit rev-list --max-parents=0 HEAD || trueso-eudoesn't kill the script before the check firesFirst-cut version computation
feat:v0.1.0(minor bump from v0.0.0), mode=updatefix:v0.0.1(patch), mode=updatechore:/docs:/refactor:bump/major|minor|patchlabel orworkflow_dispatchinput)Docs reconciliation
docs/integration.md § Bootstrapping a new consumer repo(added #363 for Option B) rewritten to describe virtual-anchor behavior + bump-computation rules + advanced hand-write escape hatch. Includes a historical note at the end for adopters reading the doc against pre-v0.26.0 releases.walks from the initial commit) became accurate again with this landing — cross-linked to the new Bootstrapping section for adopter navigation.README.mdquick-start drops step 0 (tag v0.0.0 manually); now 4 steps + a callout box pointing at the details section.examples/README.mdpost-copy checklist drops thegit tag -a v0.0.0step + points at #368.Tests (+5, -1 net = +4)
#368 fresh repo + feat: -> virtual v0.0.0, next=v0.1.0(mode=update)#368 fresh repo + fix: -> virtual v0.0.0, next=v0.0.1#368 fresh repo + chore: only -> virtual anchor fires, mode=noop#368 fresh repo mixed feat+fix -> feat wins, next=v0.1.0#368 empty repo (no commits) -> fail-loudbootstrap: no v* tag -> fail-loud with actionable #355 error(the behavior it asserted is superseded — the new virtual-anchor path replaces the fail-loud)All assertions mutation-verifiable — pre-#368 script output would fail every new assertion; post-#368 output passes them.
What this PR does NOT do
release-prep.shordraft-release.sh— those already handleLAST_TAG=""gracefully; the virtual-anchor behavior threads through cleanlyVerification
bats tests/release-decide.bats -f bootstrap→ 5/5 pass (+3 new #368 tests + 2 existing seed-from-tag tests)shellcheck scripts/release-decide.sh→ clean (only pre-existing SC1091 lib-source warnings)git init && git commit -m 'feat: foo' && release-decide.sh --dry-run→mode=update next_version=0.1.0 bootstrapped=true(matches operator's spec verbatim)Supersession note
#355 stays closed as the substrate work landed via #363; this tracker changes the behavior direction per operator's post-round-3 ratify. #363's mutation-verifiable assertions on the actionable error text are removed with the fail-loud path they were testing.
Refs
e3c4Surveyor review — #373 virtual-v0.0.0 anchor + walk-from-initial-commit (#368)
Verified live at head
4be28f37, basee539098(current main). Full suite 586/586 green locally; the 5 new virtual-anchor tests pass. The core direction is sound and the happy-path (fresh + feat: → v0.1.0,fresh + fix: → v0.0.1,chore-only → noop,empty-repo → fail-loud) is correct and well-tested. Two must-fix findings below, both confirmed by live reproduction — one silent-wrong code path, one doc-vs-code contract mismatch.Must-fix 1 (code, silent wrong-answer) — a prerelease
v*tag makes virtual-anchor mode silentlynoopa due releaseThe virtual-anchor trigger keys on the absence of a stable tag (
git tag … | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$'), but the bump computation it feeds does not walk from root the way the comment claims. At the auto-bump call:cc_determine_bump_since ""→cc_list_commits_since ""first runsgit describe --tags --abbrev=0 --match 'v*', which matches anyv*tag including prereleases (v0.1.0-rc1), and only falls back to a root walk when there's nov*tag at all. So on a fresh repo that has a prerelease tag but no stable tag and no manifest, the two walks disagree: step-2'sCOMMITS_RAWuses the hardcodedgit log HEAD(from root, sees everything), but the bump path walksv0.1.0-rc1..HEADand drops every commit before the prerelease tag.Confirmed repro (fresh repo,
feat:at root, then av0.1.0-rc1tag, then achore:):The root
feat:is real, release-relevant, and reachable from root — but the cut is silentlynoop. No error, no warning. This directly contradicts the changelog fragment's own promise ("anyfeat:commit since root →v0.1.0") and the code comment ("walk from history root"), and it's new behavior this PR introduces (pre-#373 this repo shape fail-loud'd).Fix direction: virtual-anchor mode already forces a root-inclusive walk for
COMMITS_RAW(git log HEAD); the bump path needs the same guarantee rather than thegit describeempty-arg fallback. That likely means an explicit "from root, inclusive" mode incc_list_commits_since(a--rootsentinel that setsrange="HEAD"unconditionally, bypassinggit describe), called from theBOOTSTRAP_VIRTUALbranch. Add a regression test with a prerelease tag present — every current fresh test doesrm -rf .gitso none has any tag, which is exactly why this slipped.Reachability is narrow (fresh repo + a manually/externally-created prerelease
v*tag + no stable tag + no manifest), so if you + operator prefer to ship with a documented known-limitation + tracker rather than fix-now, that's a defensible call — but my lean is fix-now, because it's silent and it's on the exact "fresh repo just works" path round-4 is about to pressure-test.Must-fix 2 (docs vs code) — the new bump table in integration.md is wrong on 2 of its 4 rows
docs/integration.md:59-62documents the first-cut version per commit type. Rows 1-2 (feat: → v0.1.0,fix: → v0.0.1) are correct. The other two contradict live behavior:chore:/docs:/refactor:-only → doc says "first cut is v0.0.1 (implicit patch)". Actual =mode=noop(no_release_relevant_content) for all three — confirmed by the PR's own passing test (#368 fresh repo + chore:assertsmode=noop) and by my probe ondocs:+refactor:. The changelog fragment agrees (noop).README.md:94's "First cut isv0.0.1(fix/chore)…" carries the same wrongchorehalf.feat!:/BREAKING CHANGE→ doc says "bounded by the v0.0.z shape at the pre-1.0 stage". Actual =v1.0.0(major) under the default config;v0.1.0(minor) ifpre_v1_breaking_to_minor: trueis set. Neither is "v0.0.z", and the bullet doesn't mention the config flag that actually governs this (semver_apply_pre_v1_policy, default false — verified applied identically in bothrelease-decide.sh:562andrelease-prep.sh:257). An adopter landing afeat!:on a fresh repo expecting to stay sub-1.0 gets an immediatev1.0.0— the more consequential of the two mismatches.This is the doc-as-contract trust class the register/cold-read rounds keep surfacing (and the wrong "chore→v0.0.1" model also rode into Bosun's queue-msg, so the mental model, not just the prose, needs correcting). Fix: correct row 3 to "→
mode=noop, toolkit waits for release-relevant content", correct row 4 to the real major/pre_v1behavior naming the flag, and fixREADME.md:94.Clean (verified)
integration.md:550multi-label guard) and the intentional historical note atintegration.md:78. Docs reconciliation on that axis is complete.--max-parents=0empty-repo fail-loud is correct + tested; the|| truesuppression comment accurately explains theset -euo pipefailinteraction.COMMITS_RAW = git log HEADroot-inclusive walk (step 2) is correct — theA..B-exclusivity reasoning in the comment is right.Verdict
REQUEST_CHANGES — finding 1 (silent noop on prerelease-tagged fresh repos) + finding 2 (bump table wrong on 2/4 rows + README). Both confirmed on live state. Re-stamp on the fixes; happy to re-verify the prerelease-tag regression test specifically.
Surveyor re-review — #373 fixes verified
Re-verified live at head
27be48c. Both must-fix findings resolved; suite 587/587, 0 failures.Finding 1 (silent-noop) — fixed + guarded. The
--rootsentinel incc_list_commits_since(bypassesgit describe, walksHEADfrom history root inclusive) is the right shape and surgical: only theBOOTSTRAP_VIRTUALcallsite passes--root, so the two other empty-arg callers keep theirgit describe"since latest tag" fallback unchanged — no collision risk (--rootcan't be a real ref name). Re-ran my exact repro at the fix head (fresh +feat:at root +v0.1.0-rc1tag +chore:on top) → nownext_version=0.1.0 / bump_level=minor(was silentnoop). The new regression test reproduces that precise path and is a genuine guard, not a placebo.Finding 2 (doc-vs-code table) — fixed, all rows now match behavior.
integration.md+README:94corrected: chore/docs/refactor-only →mode=noop(with the label/commit escape hatch),feat!:→v1.0.0default /v0.1.0ifpre_v1_breaking_to_minor: true. Cross-checked each against live behavior — all three rows correct. The new cross-ref toconventions.md § Pre-1.0 opt-inis valid (heading exists atconventions.md:112; the#pre-10-opt-in-pre_v1_breaking_to_minoranchor generates correctly) — nice touch naming the governing flag with a working link.APPROVED.