Repository navigation
v0.111.0
A release can no longer burn a version number: main is the candidate, CI gates the tag, and a drift guard refuses to ship commits the release did not create -- alongside the effects regime, release reconcile, and the removal of dev-branch releases, rlsbl push, the watch daemon, cargo publishing, changeset-file mode and every timeout environment variable
Context
The centrepiece is a reordering. rlsbl used to bump the version, commit, tag, push and create the GitHub Release, and only then find out whether CI agreed. When it did not, the version number was spent: a tag and a Release existed for a commit that could not publish, and the only way out was to skip a number and try again. The order is now candidate-first. The version-bump commit goes to the release branch untagged, rlsbl waits for CI on exactly that commit, and only a green verdict produces the tag, the Release and the changelog finalization. A red CI leaves you with an ordinary branch you can fix forward on, at the same version, as many times as it takes. Batch releases work the same way with one wait for the whole batch -- and one push, because the CI router filters paths per push, so a push per member would leave every other member's job skipped on the commit its tag was about to land on.
Two gates that were supposed to answer the same question, and did not, are now one predicate. The release gate judged the workflow RUN; the publish gate judged the releasing project's own CHECK RUNS. In a monorepo those come apart whenever the paths filter finds no change under a project: its job concludes skipped, the run still concludes success, so the release tagged and the publish gate then refused the same commit. Both sides now ask "did this project's CI pass here?", only success passes, and a skipped or absent check is a hard error that names the filter mismatch.
The forward drift guard is the other half of the rollback guard that already existed. A release pins HEAD when it starts and re-checks the branch at four points; any commit in that window it did not create -- a concurrent session sharing the worktree, most likely -- aborts the release by name instead of silently shipping under the tag.
A lot was deleted, and the deletions share a rationale: every one of them was a second way to do something rlsbl already did once. Dev-branch releases and rlsbl push are gone, so a release starts from a release branch or it does not start. RLSBL_RELEASE_PUSH is gone, replaced by an internal --no-verify on release pushes and a pre-push hook that knows which ref namespaces it owns. The timeout environment variables are gone in favour of config keys and explicit flags -- an env var that silently changes how long a release waits is exactly the kind of invisible state this tool exists to remove. The detached watch daemon is gone; the CI wait is in-process now, which is what the new ordering needed anyway. Cargo publishing and the crates.io launcher are gone because nothing in the fleet ships a crate. Changeset-file coverage mode is gone because no configuration ever selected it.
rlsbl also moved onto strictcli's effects regime as its reference consumer. Every subprocess launch, filesystem mutation and network call now routes through one internal module with a test that fails if a new one appears outside it, and every command is classified, so --dry-run records instead of acting. Confirmation is declared per command rather than inferred from "this mutates something", and the flag that skips it is --approve-consequential; --yes is gone. rlsbl monorepo snapshot --check split into snapshot-check for the same reason: one command cannot be both a pure read and a write.
Finally, rlsbl release reconcile closes the loop on history rewrites. safegit stopped requiring a handshake before rewriting a release-managed repository, so rlsbl detects the damage afterwards instead -- the changelog's hash resolution fails loudly, changelog remap --from-journal repairs the entries from safegit's journal, and release reconcile re-pushes the moved tags and recreates their Releases. It is fail-closed: a tag whose divergence the journal cannot explain is an error, never a force-push. This is why the release now requires safegit 0.25.0 or newer.
Breaking
- Removed the detached watch daemon.
--watch-async(onrelease run,release resume,release retry, andmonorepo release run) andrlsbl watch --stopare gone, along with the internal--as-daemon-childmode and the per-commit pidfile/log files under.rlsbl/. CI watching is now always in-process: pass--watchto block, or--no-watchand runrlsbl watch <sha>yourself later. - Dev-branch releases and
rlsbl pushremoved. A release can only start from a release branch —rlsbl release runandrlsbl monorepo release runnow hard-error anywhere else instead of fast-forward merging a dev branch into main. Therlsbl pushcommand is gone (pre-push-checkstays). Pushing a release branch by hand is now a hard error rather than a warning, and theRLSBL_RELEASE_PUSHbypass variable is deleted: release-internal pushes usegit push --no-verify. The generated pre-push hook is namespace-aware — it enforces onrefs/heads/*and exits 0 forrefs/tags/*andrefs/backups/*; re-runrlsbl scaffoldto pick it up. - Timeout environment variables removed.
RLSBL_PUSH_TIMEOUT,RLSBL_CHECK_TIMEOUT,RLSBL_HOOK_TIMEOUT,RLSBL_BUILD_TIMEOUTand per-targetRLSBL_BUILD_TIMEOUT_<TARGET>no longer do anything. Declare timeouts with thepush_timeout,check_timeout,hook_timeout, andbuild_timeoutkeys in.rlsbl/config.json, or pass the new--push-timeoutflag torelease run,release resume, andmonorepo release run. Shipped defaults are raised: push 120s to 300s, check 120s to 900s. - Cargo publishing target and crates.io launcher removed. The
cargorelease target andcargopublish pipeline no longer exist, along with the crates-wrapper launcher for Go binaries, the crates.io branches ofcheck-name,claim-name, andrelease yank, and the cargo CI/publish templates. Rust projects are no longer an rlsbl release target; aCargo.tomlin the repo is still recognized by theplaintarget's manifest detection. - Changeset-file coverage mode removed. The
coverage_unitconfig key is deleted along with thechangeset-filemode it selected: there are no pending changelog files,changelog addalways appends tounreleased.jsonl, and release finalization, undo, the pre-push coverage check, and the changelog checks are single-mode. Removecoverage_unitfrom.rlsbl/config.json-- it is now a dead key. - Releases now gate on CI before anything irreversible happens.
rlsbl release runpushes the version-bump commit to the release branch untagged, waits for the repository's own CI to go green on exactly that commit, and only then finalizes the changelog, tags the verified commit, pushes the tag and creates the GitHub Release. A red CI leaves no tag, no GitHub Release and no finalized changelog behind: the version number is not burnt, and the fix lands forward on the same version withrlsbl release resume. Repositories with no push-triggered workflow proceed without a gate; a repository that declares push-triggered CI but produces no runs for the candidate is a hard error. - Batch releases gate on one CI wait for the whole batch.
rlsbl monorepo release runpushes every member's version-bump commit untagged first, waits once for CI on the resulting branch tip, and only then finalizes, tags and releases each member — so every member tag points at the same CI-verified commit. A red batch leaves no tag, no GitHub Release and no finalized changelog for any member, so no version number is burnt anywhere in the batch. rlsbl release scrubnow requires safegit >= 0.25.0. It no longer sets theRLSBL_SCRUB_ORCHESTRATEDhandshake (safegit 0.25.0 stopped requiring it) and passes--approve-consequentialexplicitly to safegit's three scrub modes, whose destructive confirmation is no longer answered by--json. A history rewrite performed outsiderelease scrubis now caught by the changelog hash checks and repaired withrlsbl changelog remap --from-journalandrlsbl release reconcileinstead of being blocked up front.- Breaking: rlsbl runs on strictcli's effects regime.
--dry-run,--approve-consequential,--quietand the new--verboseare framework-owned flags now — rlsbl no longer declares them, and none of them has a short form, so-yis gone (there is no--yeson a strictcli app at all). Every command declares whether it isread_onlyormutating, and that classification decides one thing only: whether--dry-runrecords its effects instead of performing them. Confirmation is a separate, per-command declaration — a command prompts before it runs only if it declares itselfconsequential, and--approve-consequentialskips that prompt. A consequential command exits 1 on a non-interactive stdin unless the flag is passed, so a script, hook or CI step that drives one of rlsbl's consequential commands must pass--approve-consequential; everything else runs uninterrupted. Under--dry-runmutations are recorded rather than performed and printed as a numbered would-do log — including subprocesses: a preview no longer runs your release hooks, your tests, or any child command. - Breaking:
rlsbl monorepo snapshot --checkis nowrlsbl monorepo snapshot-check. One command could not be both: verifying the artifact is a pure read, regenerating it writes and commits. The writer previews completely under--dry-run— it decides staleness before writing, so the preview names both the write and the commit — and the checker never writes anything at all. CI steps using--checkmust switch to the new command. - Breaking: three commands stopped asking their own confirmation questions.
claim-name,release deprecateandrelease undoeach prompted on top of the framework's confirmation, in different words, with their own non-interactive error text. There is one confirmation per consequential command now, and the flag that skips it is--approve-consequential.release undoalso asked a second question halfway through a rollback — "Push changes to remote?" — whose default left the remote holding the release it had just undone locally; it now pushes as part of the rollback it was already authorized to perform. - Only genuinely dangerous commands ask for confirmation now. strictcli replaced the inferred "every mutating command prompts" rule with a declared
consequential, so 15 rlsbl commands ask before running --claim-name,deploy,release run/resume/retry/undo/deprecate/yank/scrub/reconcile,monorepo release run/mirror/absorb/extract/extract-releasable-- and everything else (commit,scaffold,changelog add,monorepo sync,dev install, ...) runs without interruption. The skip flag is--approve-consequential;--yesno longer exists.claim-namegains--force-publishfor the separate decision of publishing despite a taken-name check.
Features
- npm launcher first-run download mode. Launcher pipelines now take a required
downloadkey ("first-run"or"postinstall"). Infirst-runmode the npm wrapper does zero network I/O at install time and lazily downloads its checksum-verified binary on the first CLI invocation, caching it under a platform cache dir;postinstallkeeps the install-time download. PyPI launchers may only usefirst-run. - strictspec certificate deploy gate. A new opt-in preflight check,
strictspec-certificate-gate, consumes a strictspecstrictspec diffcertificate as aformat_versiondeploy gate. Enable it by adding astrictspec_gatesection (acertificatepath, and optionally anadjudicationfile) to.rlsbl/config.json; projects without the section are unaffected. Aviolatedclaim blocks the release,corpus-supported/provenclaims pass, and any otherwise-unsupported claim must be discharged by a committed adjudication file. A configured-but-missing or malformed certificate or adjudication file is a hard error. - Release files are validated with strictspec.
.rlsbl/releases/unreleased.tomlis now checked against a strictspec schema before parsing, so a malformed file, an invalidbump/modeenum value, an unknown key, or overlappinginclude/excludetargets is rejected with precise, field-level diagnostics. - External checks now receive the release context. Both freeform and structured
external_checkssubprocesses run withRLSBL_PROJECT_ROOT,RLSBL_LAST_TAG(empty string on a first release) andRLSBL_UNRELEASED_RANGEin their environment, resolved through the project's own monorepo tag glob and computed once per check run. Release-diff gates no longer need their owngit describe. See the availability matrix in the configuration docs. - The pre-release channel is documented. The release-workflow docs now cover
preidend to end: entering a channel with a normal bump, advancing and promoting withbump = "prerelease", stabilizing withpreid = "stable", the alpha < beta < rc < stable ordering rules, and what changes downstream (GitHub pre-release flag, npm dist-tag, changelog file naming).rlsbl release initscaffolds the key with an explanation. - Changelog lines carry a
format_version. Every linerlsbl changelog addwrites now records aformat_versionand is validated against a strictspec schema. Enforcement is opt-in per repo: whilechangelog_format_version_enforcedis absent from.rlsbl/config.jsonthechangelog-format-versioncheck warns; set it totrueandchangelog-format-version-gatehard-fails any changelog line missing the field, or tofalseto stay in legacy (mixed-format-tolerant) mode deliberately. - The documentation site is complete. Every public module in the package now carries a docstring, so the generated API reference documents 100% of symbols instead of leaving pages blank, and the scaffold, release-workflow, and other guide pages gained worked end-to-end usage examples.
--ci-timeout,--check-timeoutand--hook-timeoutare now flags.rlsbl release run,rlsbl release resumeandrlsbl monorepo release runaccept all four release timeouts on the command line, matching the existing--push-timeoutpattern: pass the flag to override for one invocation, or set the matching config key in.rlsbl/config.json. Previously only--push-timeouthad a flag, so a slow preflight suite or release hook could only be accommodated by editing the config file.- A release refuses to ship commits it did not create.
rlsbl release runpins HEAD when it starts and checks the branch against its own commit trail at four points — entry, candidate push, right after the CI gate, and the final push. A commit that lands on the release branch mid-flight (a concurrent session, an editor auto-commit) aborts the release by name, showing each foreign SHA and its subject, instead of silently joining the release under its changelog. Nothing is rolled back: the foreign work is preserved, and the version survives for a clean re-run. - New command:
rlsbl release reconcile. Repairs the release metadata a history rewrite invalidates but leaves behind — the remote's tags and the GitHub Releases attached to them. It reads safegit's rewrite journal to work out exactly which tags moved, force-pushes them with explicit leases, and recreates their Releases from the changelog. Usable after ANY out-of-band rewrite (a rawsafegit scrub,git filter-repo, someone else's rewrite), not just one driven byrlsbl release scrub. Fail-closed: a tag whose divergence the journal cannot explain is a hard error rather than a force-push.--dry-runprints the plan. - The changelog docs explain the auto-commit's HEAD move.
rlsbl changelog addcommitsunreleased.jsonl, which moves HEAD onto the changelog commit — so a followinggit commit --amendamends the wrong thing. The changelog reference now names the hazard, shows the exact sequence that produces it, and gives both ways out: record the entry last, or defer the commit with--no-commit. - Sandboxed test runners are now scaffolded. Declare a
test_sandboxsection in.rlsbl/config.jsonandrlsbl scaffoldemits an executable bubblewrap runner script: the repo bound read-only, the suite in a throwaway writable copy, no network, and only the toolchain caches you list. It exportsSTRICTTEST_SANDBOX=1for the stricttest floor. - New check:
stricttest-floor. Once a repo adopts the sandboxed test runner (or the stricttest plugin),rlsbl checkand the release preflight fail on a missing or non-executable runner, an incompletetest_sandboxsection, or a CI workflow that claims to use the runner but does not. Repos that have not adopted it see a visible skip. - Docs: the sandboxed test runner and the checks page.
docs/configuration.mddocuments the newtest_sandboxsection field by field, and the checks reference now lists the five project checks it had been missing. - Documentation corrections: the release workflow page now describes main-as-candidate ordering (candidate push, CI gate, fix-forward recovery with
rlsbl release resume) instead of the old commit-tag-push flow;ci_timeoutand the--ci-timeout/--check-timeout/--hook-timeoutoverrides are documented; the changelog and scaffold pages name the real--no-auto-commitflag; and the checks reference lists every check with correct per-tag counts.
Fixes
workspace-unbuildableno longer false-fails polyglot monorepos. When the repo root is not a uv workspace (no rootpyproject.toml), the check now runsuv syncin each pypi project's own directory instead of a root-leveluv sync --all-packagesthat died with "No pyproject.toml found". Repos that do have a root uv workspace are unchanged.- npm publish workflows now install dependencies first. All four npm publish templates (npm, launcher, pnpm, yarn) run a lockfile-strict install before publishing, so a package whose
prepackscript builds no longer fails at publish time with a missing dev toolchain. Re-runrlsbl scaffold(orrlsbl monorepo sync) to pick it up; the package must have a committed lockfile. - First monorepo releases no longer deadlock the publish gate. Each releasable member's CI router paths filter now includes the releasable's
CHANGELOG.md, so a release commit that touches only releasable metadata still triggers the project's CI and the publish gate sees a real conclusion instead of a skipped check. Re-runrlsbl monorepo syncto regenerate the router. - Go releases and launcher shims now target the prefixed monorepo tag. The generated Go publish job builds with goreleaser and uploads the archives and
checksums.txtto the real release tag itself, instead of delegating to goreleaser's publisher (which cannot address a<name>@vX.Y.Ztag and attached assets to a bare-vX.Y.ZRelease nobody reads). Launcher shims gain atagPrefixtemplate var, so their download URLs resolve in prefixed-tag repos. Re-runrlsbl scaffoldto regenerate. - The publish gate's CI-SHA marker is always present. The
rlsbl-ci-shamarker is now written into the GitHub Release notes even when the Release already exists (a resumed release, or one created out of band): it is added, or an outdated one replaced, instead of being skipped. The gate no longer falls back to gating on an arbitrary commit. - Renaming a releasable no longer trips the pre-push hook. The boundary alias tag pushed by
rlsbl monorepo rename-releasablenow uses--no-verify(like every other tool-driven push) and honours the project's configuredpush_timeoutinstead of a hardcoded 120 seconds. - The launcher publish probe checks the URL users actually download. The generated npm/PyPI launcher publish workflow built its asset-verification URL from the repo basename and a bare
vstrip, so in a prefixed-tag monorepo (<name>@v1.2.3) it probed a URL nobody fetches — passing while every install 404s. It now bakes in the wrapped producer'sassetProjectandtagPrefix, the same values the shim downloads with. Re-runrlsbl scaffoldto regenerate. - A fresh
rlsbl scaffoldnow passes its own checks. The auto-generated.rlsbl/config.jsonpipeline entry was missing the mandatorytargetlink, sorlsbl check --name config-schemafailed on the tool's own output — and, because publish-template resolution matches on that link, a Go library repo was handed the binary/goreleaser publish workflow (the second scaffold pass then reported "unchanged", so the documented scaffold-review-scaffold flow never healed it). Scaffold now emits the link, library repos get the library publish workflow on the first pass, and their next-steps no longer advertise a goreleaser build that never runs. - A repo's first commit is no longer invisible to changelog scope matching.
git diff-treeprints nothing for a parentless commit without--root, so the initial commit — the one that creates an entire project — looked like it touched no files:rlsbl changelog addrefused it as out of scope for its own project or releasable, and the coverage check silently skipped it. It is now matched like every other commit. - The docs no longer describe features that were removed. Generated and hand-written pages still advertised the deleted cargo/crates.io target across the README, the CLI and API reference, and the pipelines page (which counted 10 pipeline types instead of 9 and never documented the mandatory pipeline
targetkey). The dev-workflow page's editable-install table,--venvsupport list, CI-retry description, pre-push severities, andpre-push-checkstatus were all out of date, the last of these claiming a removed command "still works with a deprecation warning" when it hard-errors and blocks the push. - The publish gate's failure guidance is followable again. When CI is red on a released commit the gate no longer tells you to "re-run CI to green on this exact commit" (impossible when the failure is in the code there). It explains that rlsbl verifies CI before tagging, that re-dispatching the publish cannot change the answer, and points at the real remedy: fix forward, cut a new release, then
rlsbl release deprecatethe old one.rlsbl release resumealso regenerates CHANGELOG.md before finalization, so a fix-forward commit's changelog entry reaches readers. - Generated docs are no longer one release stale. The release now passes the version it is about to publish to
selfdoc genandselfdoc checkas--version-override. Previously selfdoc ran before the version bump was written to disk, so every version-bearing generated line (the CLI index's Version line, root-file version directives) was written for the previous version — and that churn tripped the doc-staleness check on the next release, aborting it until the operator committed the release's own regeneration artifacts and baseline-accepted. Requires a selfdoc that supports--version-override. - Two command help strings now tell the truth.
rlsbl pre-push-check --helpsaid it verified CHANGELOG.md; it is a retired stub that always exits 1 and points atrlsbl scaffold.rlsbl dev install --helpclaimed a system-wide install across seven targets; it now names which targets support--global,--venv, and--uninstallseparately. --check-timeoutnow governs config-declared external checks; previously they always ran on thecheck_timeoutconfig key or the shipped default regardless of the flag.- A release CI wait that runs out of time is now reported as an unresolved timeout with its own remediation (check the runs, resume when green, raise
--ci-timeout) instead of being misreported as a CI failure. The run-discovery grace is also budgeted inside--ci-timeout, so a short budget no longer collapses the completion wait to one second. rlsbl monorepo release runnow refuses to gate, tag and release a batch whose branch tip carries commits the batch did not create (a concurrent session's work riding in between the last candidate push and the CI gate). The abort names every foreign commit and rolls nothing back.rlsbl release --helpnow lists all 10 release subcommands (it omittedreconcileand claimed 9), andrlsbl monorepo --helplists all 18 monorepo subcommands (it claimed 16).- An invalid
--check-timeoutor--hook-timeoutvalue is now reported against the flag instead of as "Invalid check_timeout in .rlsbl/config.json". - Sandbox test runner reclaims its own leaked scratch. A killed
scripts/test.shused to leave a full copy of the repo underTMPDIRand a clone of the uv cache behind; enough of them filled a tmpfs/tmp. Every run now sweeps orphaned scratch dirs it owns before allocating new ones, skipping any whose owner process is still alive.scripts/test.sh --sweep-onlyruns just the sweep. - Scrub documentation corrected. The release-workflow page said safegit blocks direct destructive scrubs in rlsbl-managed repositories; that guard no longer exists. It now describes what a direct scrub leaves behind and points at
rlsbl release reconcile, and states the real safegit floor (0.25.0). - Sandbox test runner no longer leaks a repo copy on every run.
scripts/test.shended inexec bwrap, which replaced the shell and discarded the EXIT trap that removes the throwaway tree copy and the uv cache clone -- so successful runs leaked too, not just killed ones. bwrap now runs as a child and its exit status is forwarded after cleanup. - Project tests no longer break on a stdlib-shadowing module.
rlsblruns a project's pytest aspython -m pytest, which puts the project directory first onsys.path; a flat-layout module there with a stdlib name (anhtml.py, say) then shadowed the real one and broke pytest's own plugin loading. The invocation now passes-P(PYTHONSAFEPATH), which suppresses that injection without changing anything else. - Batch releases no longer half-publish.
rlsbl monorepo release runpushed one candidate per member, so the CI router — which filters paths per push — skipped every other member's CI job on the commit all the tags landed on, and those members' publish gates then refused to publish. The batch now publishes every member in a single push and gates that one commit, so each member's own CI runs on the commit its tag points at. rlsbl release resumeno longer breaks three ways. (1)--dry-runperformed the entire release for real — commits, tag, push to the release branch, GitHub Release, publish dispatches — because the dry-run gate existed only on the fresh-release path; it now previews the remaining steps and changes nothing. (2) A resume tagged whatever the branch tip happened to be and labelled it(CI-verified), because re-entering the release erased the recorded CI-verified commit; the tag now always lands on that commit, and a candidate that cannot be established is a hard error rather than a silent fall back to HEAD. (3) A concurrent session's commit could ride into a resumed release and reach the release branch under its version; once CI has passed, the release is sealed and any commit outside its own trail aborts by name, while a deliberate fix-forward before the CI gate is still adopted and re-gated.rlsbl monorepo snapshot --dry-runno longer writes and commits. The global--dry-runflag was accepted and dropped, so the command regeneratedsnapshot.jsonand committed it anyway. It now reports whether the artifact would change and writes nothing.- Batch-released versions keep their description and context. A member released via
rlsbl monorepo release runtakes its release description from the batch file, which was never archived per member — so the next changelog regeneration silently stripped the description and context from that version's.mdand itsCHANGELOG.mdsection. The metadata is now archived alongside the version like a standalone release's, and survives every regeneration. rlsbl release run --dry-runno longer deletes a leftover release state file. When a previous run finished but crashed before clearingin-progress.json, the auto-clear ran during previews too. A dry run now reports what it would clear and leaves the file in place.rlsbl <command> --dry-runworks without an argv rewrite. rlsbl carried a shim that moved--dry-run,--yes,--quietand--verbosein front of the command name because strictcli only recognized them there. strictcli 0.35.3 recognizes them anywhere in argv, so the shim is gone and the flags are handled by the framework exactly where you type them. rlsbl now requires strictcli >= 0.35.3.- Releases of selfdoc-using projects work again.
selfdoc'sgen,checkanddeploybecamemutatingcommands under strictcli's effects regime, so the release flow's subprocess calls hit the confirm protocol: they hard-errored on non-interactive stdin and prompted mid-release from a terminal. All three now pass--yes. - Releases of selfdoc-using projects work again. strictcli's confirm protocol now keys on a declared
consequentialrather than onmutating, and the--yesflag no longer exists on a strictcli app. The release flow'sselfdoc gen/selfdoc check/selfdoc deploysubprocesses no longer pass it; none of those commands is consequential, so none of them prompts. rlsbl commitandrlsbl changelog addwork again. strictcli replaced the inferred "mutating means confirm" rule with a declaredconsequential, and--yesno longer exists on a strictcli app. rlsbl was passing--yestosafegit commit, sorlsbl commitfailed outright andrlsbl changelog add's auto-commit left the entry uncommitted.release scrubnow passes--approve-consequentialto safegit's three scrub modes.- The
cloudflare-pagespipeline can deploy again.selfdoc deploynow declares itselfconsequential-- it makes a Cloudflare Pages deployment live, or force-pushesgh-pages-- so the bare invocation the pipeline used would hard-error on the release runner's non-interactive stdin, aborting the publish step. The pipeline passes--approve-consequential; the approval was already taken byrlsbl release runone level up.selfdoc genandselfdoc checkare not consequential and stay bare. - A private monorepo can no longer publish to a public registry.
rlsbl monorepo syncinlined every member's publish workflow into the root publish router purely on that file's existence, ignoring the member'spublish_mode. A member declaringpublish_mode: "none"that still carried a stale publish workflow got its jobs inlined, so the next<name>@v*release tag would have run a real registry publish. Suppressed members are now excluded, their stale publish workflows are deleted, and a generated router left with nothing to publish is removed. rlsbl scaffold --target <t>no longer deletes the other targets' files. On a multi-target repo the flag routed to the single-target scaffold path, so every file belonging to an unlisted target --ci-<other>.yml,.goreleaser.yml,VERSION,.npmignore-- fell out of the plan and was orphan-deleted along with its stored merge base, and the mergedpublish.ymlwas rewritten with only one target's jobs.--targetnow DECLARES a target (for ones auto-detection cannot find, such asplain): it is added to the project's target set and the scaffold always covers every target.- Re-scaffolding a project with CI service containers or a subdirectory target no longer crashes or invents merge conflicts. Scaffold applied its CI-workflow YAML rewrites (service-container injection, subdirectory
working-directoryinjection) after the three-way merge and stored the rewritten text as the merge base while the template side stayed raw -- so base and template differed by the whole rewrite on every run, any local edit in that region conflicted, and scaffold then parsed the conflict-marked output as YAML and died with a barewhile scanning a simple keyerror naming no file. The rewrites now run before the merge, and conflict-marked text is reported with the file name and the conflicting line ranges. - A Go project declaring
artifact: "library"no longer gets goreleaser files. Scaffold classified Go projects by scanning forpackage mainanywhere in the tree, so a declared library that ships a dev helper underscripts/got a.goreleaser.ymlaimed at that helper plus aversion.gowritten into it -- regenerated on every scaffold -- even though its publish workflow correctly ran no goreleaser job. The pipeline's declaredartifactis now authoritative; detection only applies when nothing is declared yet. - Dev nodes no longer get a publish workflow. A monorepo project marked
dev_node = true(orreleasable = false) cannot be released --rlsbl release runhard-errors on it -- yet scaffold still wrote apublish.ymlfor it whenever itspublish_modewas the ordinary default"ci". Publish scaffolding is now derived from the project's position in the workspace graph, and an existingpublish.ymlon such a project is swept on the next scaffold. - A Go target living in a subdirectory gets the right
artifactin its generated pipeline config. Scaffold auto-detected the artifact kind at the project root, where a subdirectory Go target has nogo.mod; detection failed and fell back to"binary", so a Go library undergo/was declared a binary. Detection now runs in the target's own directory. - A release can no longer tag a version that could never publish. The release CI gate judged the workflow RUN, while the publish gate judged the releasing project's own CHECK RUNS. In a monorepo those disagree whenever the CI router's paths filter finds no change under a project: its job concludes
skipped, the router's run still concludessuccess, so the release tagged and created a GitHub Release -- and the publish gate then correctly refused the same skipped check, leaving a tag and a Release for a version with no path to any registry and no re-run that could fix it. Both gates now decide "did this project's CI pass on this commit?" with one predicate built from the publish gate's own check-name matcher, in which onlysuccesspasses. A skipped or absent check is a hard error that names the filter mismatch and the remedy, and no version number is burnt. - Monorepo publishes are no longer blocked by a publish gate that could not find the release commit. The gate resolves the commit CI ran on from the
rlsbl-ci-shamarker in the GitHub Release body, but the gate job had neithercontents:readnor a repository context forgh release view, so the marker read failed silently and the gate fell back to the tag commit -- whose path-filtered CI is skipped in a monorepo, blocking every publish and every retry. Separately, the gate's check-name pattern dropped the per-target suffix for workspace projects, so a project whose CI comes fromci-<target>.ymlproduced a regex that never matched its real check run. All three are fixed, so a monorepo release publishes on the first attempt.