rlsbl v0.113.0 /Changelog
On this page

#Changelog

#0.113.0

Release-path unblocks: the CI gates stop refusing commits whose jobs actually passed, the monorepo router gains a run_all dispatch that runs every member's CI on one commit, and advisory warnings no longer abort a release.

Context

Every change here was reproduced against a real release that could not proceed.

A matrix job GitHub skipped emits one unsuffixed check run, while the same job run emits one per leg -- so a re-run that executed every leg could never supersede the earlier skip, and both the CI wait and the publish gate refused a commit whose jobs had all passed. Supersession is now decided by a strictly later check run for the same job; a failing leg still blocks, and nothing else can cover a skip.

The router's run_all dispatch input is the sanctioned way out of the adjacent trap: a resumed candidate that touches only some members leaves the rest filtered out by paths, and the previous remedy was to invent churn commits to widen the window. Nothing is waived -- the jobs still have to pass.

The candidate-window guard was always told a push was about to happen, so a resume with nothing left to push compared the candidate against itself and refused, naming a push that was not happening. It now reads the window the plan actually derived.

The pgdesign gate now matches pgdesign's own severity model, so a project with zero errors and some advisory warnings is releasable; and monorepo extract / extract-releasable are no longer consequential, since their history rewrite runs on a throwaway clone and touches nothing published or already pulled.

#Features

  • Monorepo CI router: run every member's jobs on one commit. The generated ci-router.yml now declares a run_all workflow_dispatch input. Dispatching with -f run_all=true short-circuits the paths filter so every member's CI jobs run on that exact commit -- the sanctioned way to unblock a release whose resumed candidate touches only some members, without inventing churn commits to widen the window. Nothing is waived: the jobs still have to pass. Run rlsbl monorepo sync to regenerate the router.

#Fixes

  • Advisory pgdesign warnings no longer abort releases. The pgdesign release gate now runs pgdesign check --tag validation --ignore-warnings, matching pgdesign's own severity model: warn-severity results are advisory and only errors block a release. Previously a project with zero errors but any warnings could not be released at all.
  • A skipped matrix job no longer blocks a release its legs passed. GitHub emits one unsuffixed check run for a job it skipped, but one per leg for a job it ran, so a re-run that executed every leg could not supersede the earlier skip and both CI gates refused a commit whose jobs had all passed. A skipped check is now superseded by a strictly later check run for the same job. A failing leg still blocks, and nothing else can cover a skip.
  • A resume with nothing new to push is no longer refused as an "empty window". The pre-push candidate-window guard was always told a push was about to happen, so when the release branch was already at the candidate it compared that commit against itself and refused, naming a push that was not happening. It now uses the window the plan actually derived.
  • rlsbl monorepo extract and monorepo extract-releasable no longer interrupt with a confirmation prompt. Their history rewrite runs on a throwaway clone, so nothing published or already-pulled is touched.

#0.112.0

A release that previews itself and verifies its own outcome: release run --dry-run now records the real mutating first half through the candidate push, and a live release probes every target's registry before it calls itself done.

Context

This release is the rlsbl half of an ecosystem-wide fix campaign, and it closes one theme: the release engine used to report success on the basis of what it DID, not what actually HAPPENED, and its preview used to describe a release rather than perform one with the writes withheld.

Two flagship changes carry that theme.

The --dry-run preview of release run was the weakest preview in the tool -- a summary followed by an empty would-do body -- because the mutating phase interleaved reads and writes, and under the effects regime a read issued after a recorded write cannot be answered. That phase is now split on the CI seam: Phase A (version bump through candidate push) is a plan-builder plus an executor, where the builder reads and derives and the executor only issues effects, so a preview records every mutation for real and prints the actual argv and byte counts. Below an explicit boundary line, Phase B -- everything that depends on CI's verdict -- is DECLARED as a table rather than guessed at. A preview cannot push by construction rather than by a flag check.

Outcome verification is the other half. After the CI wait, each publishable target's registry is probed on a bounded retry budget, and a version the registry is not serving is a hard error naming the registry and the remedy. This catches the green-but-empty publish -- a workflow that concluded successfully having uploaded nothing -- which a workflow-status check structurally cannot. Under --no-watch nothing is probed and the run says out loud that the outcome is unverified.

Around those sit the defects the same campaign surfaced by hitting them live. The exit-code family: a release ending on a failed deploy, hook, subtree push or mirror release exited 0 with its state cleared; a batch could announce completion over an empty released list; a stale completed plan could swallow a freshly written batch file. The batch flow: stranded members are resumable inside the batch, the empty-candidate-window guard runs per member before the push instead of after a full CI wait, and the finalize commit the batch itself creates is now pushed rather than left behind. CI classification: an infrastructure-killed run is recognized ahead of the code-failure signatures it incidentally matches and its failed jobs are rerun once, because a resumed release pushes nothing and could never produce a fresh run.

The rest are smaller and mostly mechanical: dry_run_supported declarations on the five commands that cannot honestly preview themselves (they now refuse at parse time and say so in --help), artifact-scoped secret scans in every publish template (a whole-tree scan blocked a release on a fixture and left a scarred version), the pgdesign @latest -> @v0 and pgdesign validate -> pgdesign check --tag validation fixes in both the CI template and the release path, a new dep-floors check so a release requiring new framework behavior must declare the >= floor a consumer will actually resolve, the display trio (unreleased, status, monorepo status agreeing on real coverage), and the infra-release CHANGELOG assembly bug that silently omitted every infra version's heading.

Internally, rlsbl now dogfoods the stricttest pytest plugin instead of carrying its own copy of the test-isolation floor, and its lock moves to the released strictcli that ships dry_run_supported.

#Features

  • **New dep-floors check.** A release that requires new behavior from a sibling framework package now has to declare a >= floor at that version, or the check blocks the release. Your dev lock resolves the new framework, so your suite passes -- but a consumer installing the published artifact resolves whatever the declared floor allows and gets an older framework without the behavior. The check compares pyproject.toml against uv.lock and package.json against package-lock.json (Go needs nothing: require lines are already the minimums), and names the dependency, the locked version, and the exact constraint to write. Opt in with the internal_dep_floors config key; monorepo siblings are enforced automatically.
  • The releasable CI run-everything hook is documented. The monorepo and release-workflow docs now explain why every member of a releasable carries the releasable's CHANGELOG.md in its CI paths filter: it makes a release commit verifiable for members whose own directory the release did not touch (a first release, where the version write is a no-op), at the cost of running every member's CI jobs on any release of that releasable. The publish gate's refusal to accept a skipped check is stated as deliberate.
  • A release now verifies that the registry actually served the version. Until now a release verified only its process -- CI green, tag pushed, publish workflow dispatched -- and then announced success. A publish job that concluded without producing an artifact (a skipped matrix leg, a gate that refused, an upload that 4xx'd into a retry that never happened) ended as a green release with nothing published, and nothing said so. After the CI wait, every publishable target's registry is now probed for the new version on a bounded retry budget, and a version still not being served is a hard error naming the registry and the remedy. In a batch, every member is probed for its own version and all failures are reported together. This runs on the --watch path only: under --no-watch the publish workflow is still in flight, so nothing is probed and the run says out loud that the outcome is unverified.
  • **rlsbl release run --dry-run now previews the release by running its first half with every mutation recorded.** It used to print a summary and an empty would-do body -- the deepest and most consequential command in rlsbl shipping its weakest preview. The mutating phase interleaved reads and writes, and under the effects regime a read issued after a recorded write cannot be answered, so no arrangement of those calls could survive its own preview. The version bump, keyword tagging, lockfile syncs, build, release commit and candidate push are now built as a plan and then issued: the preview records all of it, prints the real argv and byte counts, and reaches the candidate push -- which renders carrying «step N output», the framework's name for the commit the recorded commit step would have created. Below that a boundary line reads everything below depends on CI's verdict, and Phase B (the CI gate, changelog finalization, the tag, the GitHub Release, publishing, deploys, post-release hooks) is declared as a table rather than guessed at. Exit 0, nothing written, nothing pushed -- and a preview cannot push by construction, not by a flag check. A batch preview is the members' plans in order.

#Fixes

  • **rlsbl release resume no longer refuses over rlsbl's own state file.** A failed release leaves .rlsbl/releases/in-progress.json (and, after a scrub, scrub-result.json) in the tree untracked, and the clean-tree gate counted them as uncommitted work — so the resume you were told to run refused to start. Those two files are now exempt by path, in both the standalone and releasable homes; a genuinely dirty tree still blocks, and the refusal now names the files that are actually blocking it. The scaffold's .gitignore template ignores them too.
  • Publish workflows scan built artifacts, not the source tree. Every scaffolded publish workflow ran gitleaks dir . over the whole checkout, so a secret-shaped string in a file that never ships (a fixture, a doc, a deliberately public client ID) could block a release mid-flight and leave a scarred version with no assets. Each ecosystem now scans what it is about to publish: npm/yarn/pnpm pack the tarball and scan it, go scans goreleaser's dist/, maven scans the assembled jars, and deno/docker/hex/zig -- which have no pre-publish artifact -- drop the scan instead of scanning the tree. Scans now pass the project's .gitleaks.toml explicitly, so allowlisting a false positive works in CI. Re-run rlsbl scaffold to pick this up.
  • A release that ends with a failed step now exits nonzero. Deploy failures, post-release hook failures, a failed subtree push, a failed mirror GitHub Release and a failed post-hoc snapshot were reported as warnings and the release still exited 0 with its state file cleared -- so CI and scripts read a partially-failed release as success. The completion summary is now the single decision point: any failed step exits 1 and keeps the in-progress state, so rlsbl release resume re-attempts exactly those steps. The subtree push and mirror release are tracked steps now (SUBTREE_PUBLISHED, MIRROR_RELEASED) instead of bare warnings.
  • **rlsbl monorepo release run no longer announces a completion it cannot evidence.** A member whose release call exited 0 unwound past the batch's own bookkeeping and was silently dropped from the batch, and the run still printed Batch release complete: over an empty list and exited 0. A zero exit is now only accepted when the member left in-progress state behind (it is genuinely mid-flight and finishes on the verified candidate); otherwise the batch hard-errors. A run that released nothing exits nonzero instead of announcing completion.
  • **A completed batch plan no longer swallows a fresh unreleased.toml.** When every item in the resolved plan sidecar was already released, rlsbl monorepo release run archived the batch file along with the plan and exited 0 -- and because plans are matched by item set and bump intent, a brand-new batch file written for the NEXT release validated against the stale plan and got renamed into the archive without releasing anything. The batch file is now never touched on that path; only the stale plan is archived, and the command exits nonzero naming the plan and the remedy.
  • **rlsbl status finds CI workflows named per target.** A repo whose CI lives in ci-go.yml or ci-pypi.yml reported CI: missing, because status probed only .github/workflows/ci.yml. It now lists every CI source it discovers -- the same set the monorepo router and the release CI gate are built from.
  • Scaffolded pgdesign CI no longer fails on both of its steps. The generated workflow installed pgdesign with @latest, which resolves a phantom v1.0.0 permanently cached on the Go module proxy, and then ran pgdesign validate ., a command deleted in pgdesign 0.12.0. The install now pins @v0 (with an in-place comment explaining the phantom version) and the schema step runs pgdesign check --tag validation. Re-run rlsbl scaffold to pick this up.
  • **rlsbl unreleased and rlsbl status no longer disagree about coverage.** unreleased counted autogenerated and changelog-only commits as MISSING work while status exempted them, so the two commands reported different coverage for the same repo. unreleased now reports covered/tracked (N exempted) and marks exempt commits [EXEMPT]. Both also read a releasable member's entries from the releasable's changes directory instead of reporting "JSONL changelog not set up", and scope commits across every member of the releasable.
  • **rlsbl monorepo status reports real changelog coverage.** The Unreleased column counted CHANGELOG.md bullet lines, which describe the previous release -- a package whose unreleased commits had no changelog entry at all still looked documented. The column is now Coverage and reports covered/tracked (N exempted) per row.
  • Infra releases now get a CHANGELOG section. Generated changelogs silently omitted every infra release's ## X.Y.Z heading, because an infra release's unreleased.jsonl is empty by definition and the generator skipped empty sections. The ### Infrastructure block now renders as intended.
  • Releases of pgdesign-schema projects work again. rlsbl release run invoked pgdesign validate, a command deleted in pgdesign 0.12.0, so the build step aborted with a misleading "pgdesign validate failed" message on every pgdesign-target release. It now runs pgdesign check --tag validation from the schema directory.
  • **--dry-run no longer crashes on the project lock.** Every preview that took rlsbl's advisory lock -- monorepo release run --dry-run, scaffold --dry-run, release scrub --dry-run -- died with io.UnsupportedOperation: fileno before it could preview anything, because the lock file was routed through the recording seam and fcntl.flock needs a real file descriptor. The lock is process infrastructure, not a previewable effect: it is now taken (and cleaned up) for real in both modes.
  • A preview no longer creates real temporary files or directories. claim-name --dry-run left a real staging directory behind on every run, and a previewed changelog append left a stray .tmp in .rlsbl/changes/: tempfile creates its entry in every mode while the matching cleanup was only recorded. Temp files and directories now go through the effect seam, so a preview records what it would create and creates nothing.
  • **rlsbl monorepo status --help no longer describes a column that does not exist.** The help text promised "the number of unreleased commits for every project" and "projects with zero unreleased commits are shown as up-to-date"; the column has been real JSONL changelog coverage for some time -- covered/tracked with an (N exempted) suffix, or no changelog. The help now says so.
  • Monorepo CI router documentation corrected. Both the release-workflow and monorepo pages still described the reusable-workflow era: that monorepo sync rewrites each CI workflow's trigger to workflow_call: and generates a router that dispatches to per-project workflows, and that the <router job key> / <ci job name> check-run naming follows from calling member CI as a reusable workflow. The router inlines every job instead (GitHub rejects a workflow referencing 20+ reusable workflows), writes no root copy, removes stale ones, and sets that check-run name explicitly so branch protection rules and the publish gate's regexes keep matching.
  • A release candidate that cannot trigger its own CI is now refused in a second, not after a full CI wait. The generated monorepo router gates each project's CI job on the paths a push touched, computed against that push's own before-SHA. When the window matched none of the releasing project's filters its job concluded skipped, the publish gate refused the skipped check, and the release deadlocked on a tag that could never publish -- discovered only after the whole CI cycle. The window is computable from the diff, so it is now checked before the push: an empty one is a hard error naming the project's filters, what the window actually changed, and how to widen it. Nothing is pushed, tagged or finalized, and the version number is not burnt.
  • A batch release resumes its own stranded members instead of dying on them. A batch that died between its pass-1 commits and its pass-2 completion left every member holding an in-progress.json. Re-running the batch handed each of those members back to release run, whose contract is that an in-progress release must be resumed rather than restarted -- so the batch refused on state it had created itself, with no way to finish the group as a group. Members left mid-flight are now seeded straight into pass 2 and covered by this run's single candidate push and CI gate.
  • Every release entry point loads the shared env file, and a missing one is a hard error. release run, release resume, deploy and monorepo release run did not all load it, so a deploy or post-release hook that needed a secret got a different environment depending on which command reached it. The load is now one shared helper on all four paths, and a configured env file that does not exist stops the release with the path instead of continuing into a hook that will fail obscurely.
  • External checks are no longer told a shallow clone has never been released. RLSBL_LAST_TAG="" is a signal meaning 'this project has no releases yet', and a check reading it takes its first-release branch. Any failure to resolve the last tag was flattened into that same empty string, so a clone with truncated history looked brand-new to every external check and silently widened each one's commit range. An unresolvable tag is now a hard error naming the remedy (git fetch --unshallow); only a genuinely untagged project produces the empty value.
  • A CI-SHA marker that cannot be reconciled aborts the release instead of warning. The marker is the publish gate's only precise statement of which commit CI proved green; without it the gate falls back to whatever commit the workflow happens to observe. A failure to read or write it onto an existing GitHub Release used to print a warning and continue, so the release proceeded to publish under a verdict nobody had established. It now aborts before tagging, preserving the state for rlsbl release resume.
  • Release output redirected to a file or a CI log now appears in the right order and survives a killed run. Python block-buffers stdout as soon as it is not a terminal, so a release emitted its progress in 8KB gulps that landed after the stderr warnings they belonged next to, and a run killed mid-block lost its final lines -- exactly the output an operator needs when a release dies. Both streams are now line-buffered for the whole CLI process.
  • **A release no longer aborts on its own scrub-result.json.** The unexpected-files guard exempted in-progress.json but not the scrub result written alongside it, so a release following a release scrub stopped on a file rlsbl had created itself.
  • A batch release refuses a candidate push that cannot trigger every member's CI. The per-member release path has checked its push window against the router's paths filter since the empty-window guard landed, but the batch orchestrator's own single candidate push went unguarded. The guard now runs once per member before that push -- per member, not against the union of the members' filters, because a union passes as soon as one member's paths are touched, which is precisely the half-skipped batch the two-pass design exists to prevent.
  • **rlsbl deploy --dry-run no longer previews a deploy the branch forbids.** The preview gate sat above the branch restriction, so running it on a branch outside the target's only_on list printed a full deploy plan and exited 0 -- a confident description of a deploy that could never run. The restriction is evaluated first now, so a preview refuses exactly where the live run refuses.
  • **rlsbl release run --dry-run no longer narrates work it only recorded.** The preview issues the first half of a real release with every mutation recorded rather than performed, but two steps spoke through channels the preview does not silence: the ecosystem-keyword tagger printed Tagged package.json with "rlsbl" keyword (it narrates through its own quiet flag, not the release log), and the stale-artifact cleaner reported the files it had cleared. Both claimed completed work in the past tense for writes that never happened. Both are silent under --dry-run now, and unchanged in a live run.
  • **rlsbl release run --dry-run no longer crashes when the candidate is already CI-verified.** Previewing a resume past the CI gate -- or a batch member the orchestrator had already gated -- skips Phase A entirely, then reached the Phase-A/Phase-B boundary with no plan to render and died with UnboundLocalError. The preview now states plainly that Phase A was already done, and renders the boundary and the declared Phase-B table as it does on every other path.
  • **A malformed pipelines value is a config error now, not a crash.** pipelines is a map of pipeline name to pipeline config; a scalar there -- "pipelines": "none" being the tempting one -- is truthy, so it slipped past every check on the release path and killed rlsbl release run mid-preflight with AttributeError: 'str' object has no attribute 'items', naming no config key at all. Any non-map value is now refused with a ConfigError naming the two real shapes: "pipelines": {} to publish nowhere, "publish_mode": "none" to suppress publishing entirely. The identical crash reached through go_introspect's pipeline scan is fixed with it.
  • **The five commands that refuse --dry-run now say so before they parse anything, and in --help.** commit, release init, monorepo init, monorepo remove and monorepo release init cannot honestly preview themselves, and used to refuse from inside the handler -- after argument validation, so rlsbl monorepo remove --dry-run answered with a missing-argument error instead of the refusal, and nothing in --help or the dumped schema said the command would refuse at all. The refusal is a framework declaration now: it fires at parse time, reads --dry-run is not supported by command '<path>': <reason>, and every one of the five renders a "Dry run:" section in its help. The reasons themselves are unchanged.
  • A batch release no longer leaves its last commit unpushed. rlsbl monorepo release run committed the archived batch release file after the final member's push and never published it, so every repository that completed a batch release stayed permanently one commit ahead of its remote and the next release's preflight met a diverged branch. The batch now pushes that commit as its own final step, refusing to carry any commit it did not create.
  • A CI run killed by infrastructure no longer strands a release. When a provider outage killed every job on runner acquisition or action download, rlsbl read the run as a deterministic code failure and refused to retry -- and a resumed release pushes nothing, so no fresh run could ever appear and the release became unrunnable. Such runs are now recognized as infrastructure failures ahead of the code-failure signatures they incidentally match, and rlsbl reruns their failed jobs once by itself. When it genuinely does not retry, the abort message now names the manual gh run rerun <id> --failed and rlsbl release resume remedy.

#0.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

  • **Release files now require a format_version field.** .rlsbl/releases/unreleased.toml must carry format_version = 1; rlsbl release init stamps it automatically. An in-flight release file created before this change is rejected until you re-run rlsbl release init to re-scaffold it.
  • Removed the detached watch daemon. --watch-async (on release run, release resume, release retry, and monorepo release run) and rlsbl watch --stop are gone, along with the internal --as-daemon-child mode and the per-commit pidfile/log files under .rlsbl/. CI watching is now always in-process: pass --watch to block, or --no-watch and run rlsbl watch <sha> yourself later.
  • **Dev-branch releases and rlsbl push removed.** A release can only start from a release branch — rlsbl release run and rlsbl monorepo release run now hard-error anywhere else instead of fast-forward merging a dev branch into main. The rlsbl push command is gone (pre-push-check stays). Pushing a release branch by hand is now a hard error rather than a warning, and the RLSBL_RELEASE_PUSH bypass variable is deleted: release-internal pushes use git push --no-verify. The generated pre-push hook is namespace-aware — it enforces on refs/heads/* and exits 0 for refs/tags/* and refs/backups/*; re-run rlsbl scaffold to pick it up.
  • Timeout environment variables removed. RLSBL_PUSH_TIMEOUT, RLSBL_CHECK_TIMEOUT, RLSBL_HOOK_TIMEOUT, RLSBL_BUILD_TIMEOUT and per-target RLSBL_BUILD_TIMEOUT_<TARGET> no longer do anything. Declare timeouts with the push_timeout, check_timeout, hook_timeout, and build_timeout keys in .rlsbl/config.json, or pass the new --push-timeout flag to release run, release resume, and monorepo release run. Shipped defaults are raised: push 120s to 300s, check 120s to 900s.
  • Cargo publishing target and crates.io launcher removed. The cargo release target and cargo publish pipeline no longer exist, along with the crates-wrapper launcher for Go binaries, the crates.io branches of check-name, claim-name, and release yank, and the cargo CI/publish templates. Rust projects are no longer an rlsbl release target; a Cargo.toml in the repo is still recognized by the plain target's manifest detection.
  • Changeset-file coverage mode removed. The coverage_unit config key is deleted along with the changeset-file mode it selected: there are no pending changelog files, changelog add always appends to unreleased.jsonl, and release finalization, undo, the pre-push coverage check, and the changelog checks are single-mode. Remove coverage_unit from .rlsbl/config.json -- it is now a dead key.
  • Releases now gate on CI before anything irreversible happens. rlsbl release run pushes 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 with rlsbl 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 run pushes 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 scrub now requires safegit >= 0.25.0.** It no longer sets the RLSBL_SCRUB_ORCHESTRATED handshake (safegit 0.25.0 stopped requiring it) and passes --approve-consequential explicitly to safegit's three scrub modes, whose destructive confirmation is no longer answered by --json. A history rewrite performed outside release scrub is now caught by the changelog hash checks and repaired with rlsbl changelog remap --from-journal and rlsbl release reconcile instead of being blocked up front.
  • Breaking: rlsbl runs on strictcli's effects regime. --dry-run, --approve-consequential, --quiet and the new --verbose are framework-owned flags now — rlsbl no longer declares them, and none of them has a short form, so -y is gone (there is no --yes on a strictcli app at all). Every command declares whether it is read_only or mutating, and that classification decides one thing only: whether --dry-run records its effects instead of performing them. Confirmation is a separate, per-command declaration — a command prompts before it runs only if it declares itself consequential, and --approve-consequential skips 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-run mutations 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 --check is now rlsbl 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 --check must switch to the new command.
  • Breaking: three commands stopped asking their own confirmation questions. claim-name, release deprecate and release undo each 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 undo also 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; --yes no longer exists. claim-name gains --force-publish for the separate decision of publishing despite a taken-name check.

#Features

  • npm launcher first-run download mode. Launcher pipelines now take a required download key ("first-run" or "postinstall"). In first-run mode 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; postinstall keeps the install-time download. PyPI launchers may only use first-run.
  • strictspec certificate deploy gate. A new opt-in preflight check, strictspec-certificate-gate, consumes a strictspec strictspec diff certificate as a format_version deploy gate. Enable it by adding a strictspec_gate section (a certificate path, and optionally an adjudication file) to .rlsbl/config.json; projects without the section are unaffected. A violated claim blocks the release, corpus-supported/proven claims 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.toml is now checked against a strictspec schema before parsing, so a malformed file, an invalid bump/mode enum value, an unknown key, or overlapping include/exclude targets is rejected with precise, field-level diagnostics.
  • External checks now receive the release context. Both freeform and structured external_checks subprocesses run with RLSBL_PROJECT_ROOT, RLSBL_LAST_TAG (empty string on a first release) and RLSBL_UNRELEASED_RANGE in their environment, resolved through the project's own monorepo tag glob and computed once per check run. Release-diff gates no longer need their own git describe. See the availability matrix in the configuration docs.
  • The pre-release channel is documented. The release-workflow docs now cover preid end to end: entering a channel with a normal bump, advancing and promoting with bump = "prerelease", stabilizing with preid = "stable", the alpha < beta < rc < stable ordering rules, and what changes downstream (GitHub pre-release flag, npm dist-tag, changelog file naming). rlsbl release init scaffolds the key with an explanation.
  • **Changelog lines carry a format_version.** Every line rlsbl changelog add writes now records a format_version and is validated against a strictspec schema. Enforcement is opt-in per repo: while changelog_format_version_enforced is absent from .rlsbl/config.json the changelog-format-version check warns; set it to true and changelog-format-version-gate hard-fails any changelog line missing the field, or to false to 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-timeout and --hook-timeout are now flags.** rlsbl release run, rlsbl release resume and rlsbl monorepo release run accept all four release timeouts on the command line, matching the existing --push-timeout pattern: pass the flag to override for one invocation, or set the matching config key in .rlsbl/config.json. Previously only --push-timeout had 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 run pins 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 raw safegit scrub, git filter-repo, someone else's rewrite), not just one driven by rlsbl release scrub. Fail-closed: a tag whose divergence the journal cannot explain is a hard error rather than a force-push. --dry-run prints the plan.
  • The changelog docs explain the auto-commit's HEAD move. rlsbl changelog add commits unreleased.jsonl, which moves HEAD onto the changelog commit — so a following git commit --amend amends 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_sandbox section in .rlsbl/config.json and rlsbl scaffold emits 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 exports STRICTTEST_SANDBOX=1 for the stricttest floor.
  • **New check: stricttest-floor.** Once a repo adopts the sandboxed test runner (or the stricttest plugin), rlsbl check and the release preflight fail on a missing or non-executable runner, an incomplete test_sandbox section, 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.md documents the new test_sandbox section 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_timeout and the --ci-timeout/--check-timeout/--hook-timeout overrides are documented; the changelog and scaffold pages name the real --no-auto-commit flag; and the checks reference lists every check with correct per-tag counts.
  • Five commands say why they cannot preview. rlsbl commit, release init, monorepo init, monorepo remove and monorepo release init reject --dry-run with a reason instead of accepting it and doing nothing informative: each is a single small mutation whose preview could only restate what you typed, or scaffolds a file whose whole point is that you edit it. Every other mutating command records a real preview.

#Fixes

  • **workspace-unbuildable no longer false-fails polyglot monorepos.** When the repo root is not a uv workspace (no root pyproject.toml), the check now runs uv sync in each pypi project's own directory instead of a root-level uv sync --all-packages that 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 prepack script builds no longer fails at publish time with a missing dev toolchain. Re-run rlsbl scaffold (or rlsbl 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-run rlsbl monorepo sync to 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.txt to the real release tag itself, instead of delegating to goreleaser's publisher (which cannot address a <name>@vX.Y.Z tag and attached assets to a bare-vX.Y.Z Release nobody reads). Launcher shims gain a tagPrefix template var, so their download URLs resolve in prefixed-tag repos. Re-run rlsbl scaffold to regenerate.
  • The publish gate's CI-SHA marker is always present. The rlsbl-ci-sha marker 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-releasable now uses --no-verify (like every other tool-driven push) and honours the project's configured push_timeout instead 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 v strip, 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's assetProject and tagPrefix, the same values the shim downloads with. Re-run rlsbl scaffold to regenerate.
  • **A fresh rlsbl scaffold now passes its own checks.** The auto-generated .rlsbl/config.json pipeline entry was missing the mandatory target link, so rlsbl check --name config-schema failed 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-tree prints 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 add refused 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 target key). The dev-workflow page's editable-install table, --venv support list, CI-retry description, pre-push severities, and pre-push-check status 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 deprecate the old one. rlsbl release resume also 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 gen and selfdoc check as --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 --help said it verified CHANGELOG.md; it is a retired stub that always exits 1 and points at rlsbl scaffold. rlsbl dev install --help claimed a system-wide install across seven targets; it now names which targets support --global, --venv, and --uninstall separately.
  • --check-timeout now governs config-declared external checks; previously they always ran on the check_timeout config 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 run now 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 --help now lists all 10 release subcommands (it omitted reconcile and claimed 9), and rlsbl monorepo --help lists all 18 monorepo subcommands (it claimed 16).
  • An invalid --check-timeout or --hook-timeout value 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.sh used to leave a full copy of the repo under TMPDIR and 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-only runs 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.sh ended in exec 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. rlsbl runs a project's pytest as python -m pytest, which puts the project directory first on sys.path; a flat-layout module there with a stdlib name (an html.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 run pushed 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 resume no longer breaks three ways.** (1) --dry-run performed 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-run no longer writes and commits.** The global --dry-run flag was accepted and dropped, so the command regenerated snapshot.json and 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 run takes 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 .md and its CHANGELOG.md section. The metadata is now archived alongside the version like a standalone release's, and survives every regeneration.
  • **rlsbl release run --dry-run no longer deletes a leftover release state file.** When a previous run finished but crashed before clearing in-progress.json, the auto-clear ran during previews too. A dry run now reports what it would clear and leaves the file in place.
  • The PyPI token no longer travels in a command line. rlsbl claim-name --target pypi passed it as uv publish --token <secret>, where a process listing could read it — and, once previews started printing the commands they would run, a would-do log would have printed it too. It rides UV_PUBLISH_TOKEN in the child's environment now.
  • **rlsbl <command> --dry-run works without an argv rewrite.** rlsbl carried a shim that moved --dry-run, --yes, --quiet and --verbose in 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's gen, check and deploy became mutating commands 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 consequential rather than on mutating, and the --yes flag no longer exists on a strictcli app. The release flow's selfdoc gen / selfdoc check / selfdoc deploy subprocesses no longer pass it; none of those commands is consequential, so none of them prompts.
  • **rlsbl commit and rlsbl changelog add work again.** strictcli replaced the inferred "mutating means confirm" rule with a declared consequential, and --yes no longer exists on a strictcli app. rlsbl was passing --yes to safegit commit, so rlsbl commit failed outright and rlsbl changelog add's auto-commit left the entry uncommitted. release scrub now passes --approve-consequential to safegit's three scrub modes.
  • **The cloudflare-pages pipeline can deploy again.** selfdoc deploy now declares itself consequential -- it makes a Cloudflare Pages deployment live, or force-pushes gh-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 by rlsbl release run one level up. selfdoc gen and selfdoc check are not consequential and stay bare.
  • A private monorepo can no longer publish to a public registry. rlsbl monorepo sync inlined every member's publish workflow into the root publish router purely on that file's existence, ignoring the member's publish_mode. A member declaring publish_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 merged publish.yml was rewritten with only one target's jobs. --target now DECLARES a target (for ones auto-detection cannot find, such as plain): 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-directory injection) 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 bare while scanning a simple key error 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 for package main anywhere in the tree, so a declared library that ships a dev helper under scripts/ got a .goreleaser.yml aimed at that helper plus a version.go written into it -- regenerated on every scaffold -- even though its publish workflow correctly ran no goreleaser job. The pipeline's declared artifact is 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 (or releasable = false) cannot be released -- rlsbl release run hard-errors on it -- yet scaffold still wrote a publish.yml for it whenever its publish_mode was the ordinary default "ci". Publish scaffolding is now derived from the project's position in the workspace graph, and an existing publish.yml on such a project is swept on the next scaffold.
  • **A Go target living in a subdirectory gets the right artifact in its generated pipeline config.** Scaffold auto-detected the artifact kind at the project root, where a subdirectory Go target has no go.mod; detection failed and fell back to "binary", so a Go library under go/ 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 concludes success, 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 only success passes. 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-sha marker in the GitHub Release body, but the gate job had neither contents:read nor a repository context for gh 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 from ci-<target>.yml produced a regex that never matched its real check run. All three are fixed, so a monorepo release publishes on the first attempt.

#0.110.2

CI-portable hermetic test sandbox; ships the full 0.110 cycle to registries

Context

v0.110.0/v0.110.1 were gate-blocked by red CI (the new sandbox needed runner portability work: bwrap 0.11 source build + AppArmor sysctl, pinned Go with GOTOOLCHAIN=local, pinned uv, git-filter-repo on the runner, fixture dependency + PEP-517 build-backend cache warming, checkout credential persistence disabled); this patch completes it -- the full suite now runs inside the read-only no-network sandbox on CI, and the publish gate certifies it.

#Infrastructure

  • CI-portable hermetic test sandbox; ships the full 0.110 cycle to registries

#0.110.1

Portable test sandbox: CI-compatible bwrap flags (fix-forward for the unpublished v0.110.0)

Context

v0.110.0 was tagged and GitHub-released but never published to npm/PyPI: the publish workflow gates on CI, and CI was red because scripts/test.sh used the bubblewrap overlayfs options (--overlay-src/--tmp-overlay), which the stock apt bubblewrap on the CI runner is built without. This patch replaces the overlay with universally-supported bwrap flags (read-only download-cache + file:// GOPROXY with a tmpfs GOMODCACHE for Go; a copy-on-write reflink clone of the uv cache), turning CI green so this release cycle ships to the registries.

#Fixes

  • Test sandbox works on bubblewrap builds without overlayfs. The sandboxed test runner (scripts/test.sh) no longer relies on --overlay-src/--tmp-overlay, so it runs on the stock apt bubblewrap shipped on CI runners.

#0.110.0

Absorb/extract round-trip overhaul, mirror reconciler, commit-aware tag guards, infra bump type, CI service containers, dual-registry formalization, handler safety hardening

Context

This cycle hardens the release machinery along several fronts. The dry-run incident prompted prompt EOF handling, a push rehearsal step, and elimination of **_kwargs in handlers in favor of registry-enforced signatures. The bump-type rename (hotfix -> infra) came with a fleet archive migration; legacy bump values now hard-error rather than silently coerce. Absorb now rewrites history to the package prefix with tag import and changelog remap, eliminating the double-append class of bug. Extract gained tag translation, commit-map remap, and a self-commit so absorb/extract round-trips stay coherent. Monorepo mirror became a tool-owned plan/apply reconciler (force-with-lease with an exact tripwire). Tag push guards are now commit-aware (the silent-skip path is removed). CI service containers are declarative with an enforce check. Dual-registry support formalizes scan-depth pins and a mixed-scheme guard. record-gif was removed. The CI suite now runs inside a hermetic bwrap test sandbox: repo read-only, a throwaway writable copy, private tmpfs, no network, plus an always-on env-poisoning floor and a bare-run refusal guard.

#Breaking

  • **rlsbl record-gif removed.** The vhs-based demo-GIF recorder command has been removed.
  • monorepo mirror is now an idempotent plan/apply reconciler: the mirror is a tool-owned derived artifact, --dry-run prints a plan (converged/behind/scaffold-missing/contract-violated/virgin), convergence force-pushes with lease, a tripwire refuses mirrors carrying foreign hand-authored commits, and scaffold failures are hard errors.
  • **Bump type hotfix renamed to infra.** Use bump = "infra" for infrastructure-only releases; the legacy hotfix value is rejected, and encountering it in an archived release file is a hard error during changelog regeneration.
  • **monorepo absorb reshaped: absorb <source_repo> <dest_path>.** The second positional is now the destination path (with --name defaulting to its basename), plus new --registry-name and --releasable flags. Replaces the old absorb <source_path> <package_name> signature.

#Features

  • **rlsbl push --dry-run.** Runs all preflight checks and shows what would be pushed without pushing.
  • **rlsbl push --dry-run now rehearses the push.** After the preflight checks it runs a real dry-run rehearsal, surfacing remote-side rejections (protected branch, stale ref) without performing a real push.
  • **monorepo check-names honors registry_name.** When a workspace project declares registry_name, it is checked verbatim on the registry (prefix/suffix are ignored), since that field is the package's true registry identity.
  • CI service containers. Declare services (image, ports, env, health checks, setup commands with SQL/command verification) and test_env in .rlsbl/config.json; rlsbl scaffold renders them into the per-target test CI workflow, and the new requires-services check hard-errors when a declared service is not provisioned on disk.
  • **monorepo absorb now rewrites history instead of a verbatim subtree add.** It relocates the source's full commit history under the destination prefix, imports version tags under the monorepo tag scheme, remaps JSONL changelog hashes to the new commits, routes changelogs to the releasable dir under --releasable, and self-commits the result.
  • **monorepo extract now produces a coherent standalone repo.** It translates the package's monorepo-scheme tags to standalone v{version} (multi-member releasable extracts keep the releasable scheme and recreate the [[releasables]] grouping), prunes foreign packages' tags, remaps changelog hashes to the rewritten commits (dropping entries whose commits were pruned), and self-commits the migrated state.
  • Sandboxed test runner. scripts/test.sh runs the suite inside a bwrap sandbox with the repo read-only, a throwaway writable copy, private tmpfs, and no network.

#Fixes

  • Fix. claim-name now honors --dry-run (previously it published the placeholder package even in a dry run) and asks for confirmation before publishing; --yes skips the prompt.
  • Confirmation prompts fail cleanly on non-interactive stdin. claim-name, push, release deprecate, and release undo now emit a clear error naming the consequence and suggesting --yes, instead of an EOFError traceback, when stdin is closed.
  • Mixed-scheme monorepo guard. A monorepo member directory that declares both a Go target (path-based {path}/v* tags) and an @-style target ({name}@v*) now fails with a clear error instead of silently picking an ordering-dependent tag prefix. A new mixed-tag-schemes workspace check reports the same condition.
  • Tag push safety. Releases now verify that a matching remote tag points at the same commit before skipping the tag push; a divergent or unverifiable remote tag is a hard error instead of a silent skip.
  • Pre-flight tag collision check. A release now aborts before any local mutation if its computed tag already exists on the remote (or the remote cannot be reached to verify), instead of failing only at push time after the version bump and commit.
  • **monorepo add gains --registry-name and an honest --dry-run.** --registry-name records a project's registry identity in workspace.toml. --dry-run now fully validates and reports what would happen without adding the project, scaffolding, or syncing -- previously it silently scaffolded and synced for real.
  • absorb/extract hard-error up front on broken target declarations. monorepo absorb and monorepo extract now abort before any history rewrite when a package's .rlsbl/config.json exists but declares no targets key, instead of silently importing wrongly-schemed version tags that break version resolution later.
  • Hardened release push guard. The pre-push hook now honors the RLSBL_RELEASE_PUSH bypass only during an actual in-progress release (an in-progress.json state file must exist), so a stray environment variable can no longer silently disable the manual-push guard.
  • Git operations are anchored to their project root. Release, undo, deploy, status, and scrub now pass an explicit working directory to every branch/push/commit git call instead of relying on the process working directory, and the commit helpers refuse to write into a different repository than the one intended. This prevents release actions from accidentally operating on the wrong repository.

#0.109.1

Scaffold truthful status labels, crates wrapper checksum verification, corrected monorepo positional bindings, and Go dead-modules testdata exclusion

Context

Found via the new CLI wiring coverage (which itself now covers all commands via the deterministic strictcli 0.32.x coverage check) plus fleet verification.

#Fixes

  • Scaffold truthfulness. Byte-identical files are reported as unchanged instead of updated during re-scaffold.
  • Crates wrapper integrity. The generated build.rs now sha256-verifies the downloaded release archive against checksums.txt and hard-fails on any mismatch.
  • Fixed swapped arguments in monorepo extract/absorb/extract-releasable. The two positional arguments now bind in the documented order shown in each command's help; previously the first argument you passed was silently bound to the second slot (and vice versa).
  • **Dead-package check no longer flags Go testdata/ fixtures or test-only packages.** The Go dead-code detector now applies the same test-context exclusion as other languages, so package directories under testdata/ (at any depth) and packages consisting solely of *_test.go files are never reported as dead.

#0.109.0

Structured external checks, declarative dead-module exclusions, check-name --json, launcher shim generation, and deterministic check verdicts

Context

Check verdicts no longer depend on which tools a machine has installed: missing tools now hard-fail instead of silently skipping, both in checks and in release test runners. external_checks gained an explicit structured/freeform schema with rlsbl-composed argv and competing-scope guards (breaking: the kind key is now required). dead-modules gained a reason-gated committed exclusion list. check-name gained --json plus a fix making multi-target checks actually check every target. The launcher pipeline kind is now end-to-end functional with checksum-verified download shims. rename-releasable crash recovery and batch-archival guards were hardened, and the release preflight now prints a positive success summary.

#Breaking

  • Structured external checks. external_checks entries now require an explicit kind marker (structured or freeform); missing/unknown kind and unknown keys are hard errors. Structured entries (mypy, ruff-check, ruff-format) run via rlsbl-composed, shell-free uv run argv, honor the configured check timeout, and emit a competing-scope guard that rejects tool config which would silently override or narrow the declared paths.

#Features

  • Releases now confirm which preflight checks passed. The release log prints a positive summary ("Preflight: N checks passed (...)") for every check that ran, including config-declared external checks, so a green preflight is visible rather than silent.
  • Launcher packages now ship working download logic. npm and PyPI artifact: "launcher" pipelines generate platform-aware shims that download the wrapped binary from its GitHub Release, SHA-256-verify it against the release's checksums.txt before installing, and fail loudly on tampering or a missing asset. Scaffold fills the launcher manifest's non-name fields once (never inventing the package name), and a publish workflow verify step probes both the binary asset and checksums.txt so goreleaser naming drift becomes a red CI job instead of broken installs.
  • Declarative dead-module exclusions. Files that are legitimately unreachable by design (demo apps, tool configs) can be listed in .rlsbl/dead-modules.toml with a mandatory reason; entries never act as entry points (no laundering), and stale entries hard-fail a new dead-modules-stale check.
  • **check-name --json.** New flag emits machine-readable results: one JSON object for a single name+target, an array otherwise. Each object carries a unified structured_conflicts list of {name, rule} entries keyed by stable per-mechanism tokens (npm-moniker, pypi-separator, pypi-ultranorm, crates-separator, stdlib).

#Fixes

  • rename-releasable crash recovery. A crash mid-rename no longer lets a re-run push the alias tag over an uncommitted rename with a stale publish gate; re-runs now heal completely. The command also asks for confirmation before pushing (bypass with --yes) and no longer demands gh auth for renames that create no tag.
  • Batch release safety. A failed push no longer lets the batch repair pass archive the batch as complete on local-only tag evidence; a batch with an unfinished (in-progress) release is left intact for resume or rollback.
  • Checks no longer lie on misconfigured machines. github-release, workspace-unbuildable, and ruff-lint now hard-fail when their tool is missing (or gh is unauthenticated) instead of silently skipping; ruff-lint reports exact violation counts (was ~10x inflated line counts) and enforces a minimum ruff version.
  • Releases no longer silently skip tests. When a required test tool (uv, pytest, npm, mvn) is missing, or a package.json/pom.xml manifest is corrupt or absent for a declared target, the test step now hard-fails instead of passing silently.
  • rlsbl check-name --target npm --target pypi now checks every target, not just the first. Multi-target name checks previously stopped after the first registry.

#0.108.0

Launcher artifact kind for wrapper-package publishing, handler migration to ctx-first signatures, and rename-releasable argument order fix.

Context

Adds artifact: "launcher" pipeline kind that produces npm/PyPI shim packages downloading pre-built binaries from GitHub Releases. All 52 command handlers migrated to strictcli v0.29.0 ctx-first + Outcome signatures. The monorepo rename-releasable positional argument order now matches mv convention (<old> <new>).

#Features

  • New feature. Launcher artifact kind (artifact: "launcher") for wrapper-package publishing. Launcher pipelines produce npm/PyPI shim packages that download a pre-built binary from a GitHub Release at install or first-run time. Includes config validation (wraps, binary_source required), wrapper-producer check, needs dependency chain in CI workflows, and URL verify-before-publish step.

#Fixes

  • Fix. Corrected positional argument order for monorepo rename-releasable: now reads <old> <new> matching mv/git mv convention.

#0.107.0

State-model completion with primary-target designation, legacy field deletion, rename-releasable command, save_workspace in-place editing, claim-name conflict enumeration, and fleet sweep script.

#Features

  • New rlsbl monorepo rename-releasable <old> <new> command: renames a releasable group across workspace.toml and its state directory, regenerates publish gate prefixes, and pushes a boundary alias tag for the current version when the tag format contains {name}. Idempotent and crash-safe.

#Fixes

  • Publish gate marker read is now retried. The gate retries reading the rlsbl-ci-sha marker from the GitHub Release body (5 attempts, 5s apart) before falling back to $GITHUB_SHA, so GitHub API read-replica lag on a freshly created release no longer causes the gate to poll the wrong commit and time out.
  • check-name / claim-name conflict enumeration. PyPI and crates.io normalization collisions now list every conflicting package and the normalization rule, matching npm's existing behavior. Previously only the first collision was shown.

#0.106.0

Publish-gate reliability (check-run dedup + recorded CI SHA), in-place release retry, snapshot-before-tag, post-push failure-state fixes, per-target publish paths, unscoped npm wrapper, required Go artifact key, and claim-name conflict enumeration.

Context

This release hardens the publish pipeline against retry-poisoning and post-tag commit drift: the CI gate now resolves the exact commit CI ran on from a recorded marker and collapses retried check-runs to the latest, so a superseded failure can no longer block a legitimate publish, and retries rerun in place rather than spawning divergent state. It also completes the path-aware publishing model: each target publishes from its own subdirectory, the npm binary wrapper uses bare unscoped names, and Go pipelines must declare an explicit binary/library artifact key.

#Breaking

  • npm binary wrapper config changed. The npm binary wrapper (Go/Zig targets) now activates via {"npm_wrapper": {"enabled": true}} instead of the removed scope key, and publishes bare per-platform package names (<bin>-linux-x64) instead of scoped @scope/name names. A stale scope/npm_scope key is now a hard error.
  • **Go pipelines now require an explicit artifact key.** Every type: "go" pipeline must declare artifact as "binary" or "library" -- there is no default. The validation error names both values and includes an auto-detected suggestion.

#Features

  • **Documented check_timeout/RLSBL_CHECK_TIMEOUT and the test config block.** The configuration reference now covers the check subprocess timeout budget and per-target test-selection filters (e.g. pytest markers).
  • **Pipeline docs cover the required go artifact key.** docs/pipelines.md documents that go pipelines must declare artifact (binary/library, no default), how the library tag/version handling works, and that private modules use publish_mode "none".

#Fixes

  • Publish gate resilience. The CI gate now collapses retried check-runs to the latest per name (a stale failure from a superseded CI run no longer blocks publishing forever) and resolves the exact release commit from an rlsbl-ci-sha marker in the GitHub Release body, falling back to $GITHUB_SHA for older releases.
  • Publish retry from main, and router regeneration on upgrade. A workflow_dispatch retry of the monorepo publish router fired at ref=main with inputs.tag set now matches the releasing project's jobs (and docker/zig publish steps) instead of silently skipping them. The publish cache also records the rlsbl version, so an upgrade that changes the router generator forces monorepo sync to regenerate publish.yml even when no member workflow changed.
  • Auto-retry reruns CI in place. When rlsbl watch auto-retries a failed CI run, it now reruns the same run in place (gh run rerun) instead of dispatching a fresh workflow run. This avoids creating a duplicate check-run (which poisoned the publish gate) and retries on the failed commit rather than branch HEAD. If the rerun also fails, its failing-step log tail is printed alongside the run URL.
  • Monorepo release tag is now the pushed tip. The monorepo snapshot commit is created before the release tag instead of after the push, so the tag points at the exact commit CI runs on.
  • Docker/Zig publish skip-detection. Publish workflows dispatched at a branch with an explicit tag input no longer mis-detect the already-published state; the skip check now uses the resolved release tag.
  • Cleaner release push failures. A push that fails after the release is tagged now prints a concise error with resume guidance instead of a Python traceback, and a tag push that only stalled transiently is retried and, on success, continues straight into GitHub Release creation instead of stopping.
  • Safer batch-release completion. A monorepo batch item is only treated as released (and skipped/archived) when its tag is confirmed on the remote, not merely tagged locally; the batch file is never archived while any item still has an in-progress release. This prevents a failed-to-push release from being silently dropped.
  • check-name / claim-name now list all conflicting packages. When an npm name collides with multiple existing packages after punctuation stripping, every colliding package is shown (not just the first), with the normalization rule stated explicitly.
  • **Go library pipeline fails loudly on a missing artifact key.** Selecting the publish workflow with no artifact configured now raises a clear error instead of silently assuming a binary.
  • Go library publish workflow verifies the right module and version. The module path is baked from go.mod at scaffold time (correct for monorepo subdirectory modules) and the version is derived correctly from plain (v1.2.3), releasable (<name>@v1.2.3), and subdir (<subdir>/v1.2.3) release tags. Private modules should use publish_mode "none".
  • Retry-safe publish tag resolution. Go and Maven Central publish workflows now derive the release tag from the workflow_dispatch tag input (falling back to the ref), so retrying a publish at ref=main no longer mis-resolves the version to main.
  • Subdirectory publish workflows for single-target projects. Scaffolding a lone target that lives in a subdirectory now produces a publish workflow that runs in that subdirectory (correct working-directory and rewritten package paths), instead of a root-anchored one.
  • Correct working-directory for monorepo root publishers with subdirectory targets. Inlined publish jobs for a root package whose target lives in a subdirectory now run in that subdirectory.
  • Multi-target local publish. In a standalone release, each pipeline now publishes from its own linked target's subdirectory instead of the primary target's path, so multi-target projects with targets in distinct subdirectories publish the correct artifacts.
  • Per-target stale-artifact cleaning. The pre-build artifact clean now clears each subdirectory target's own dist/ (not just the project root's), so a multi-target release's secret scan is scoped to freshly built artifacts across all targets.
  • Monorepo subdir publish paths. A monorepo root publisher with a target in a subdirectory now renders composed packages-dir/version-file inputs in the generated publish workflow instead of root-anchored ones, so the correct artifacts and version files are used.

#0.105.1

Fix CI regression in dry-run preflight + monorepo recovery dispatch.

#Fixes

  • Fix. Monorepo publish router now defines workflow_dispatch.inputs.tag so retry dispatch resolves the correct project. Gate resolver falls back to the tag input. Also, rlsbl release retry auto-scaffold now generates correct monorepo/releasable tag format instead of standalone v1.2.3.

#0.105.0

Check outcome model adoption, undo plan-phase rework, ResolvedTarget unification, idempotent publish, publish_mode enum, scaffold base-healing, dev-sync sentinel, per-target test config.

Context

Breaking changes: publish_mode replaces private config key; --force-overwrite removed from scaffold; migrate command removed; pipeline target field now required; check implementations use reporter API.

New features: idempotent publish with registry probes; recovery dispatch across all publish templates; scaffold base-healing from history; dev-sync overlay sentinel and drift check; per-target test markers config; external-check name validation; malformed config hard-errors (test blocks, lint TOML, hook timeout, batch_limits); changelog entry type validation; smarter CI watch with failure classification; configurable-budget hints in timeout messages; npm name-collision errors name the conflicting package; robust per-version changelog regeneration; first-release guard against destroyed tags; undo completeness guard for multi-commit releases.

#Breaking

  • **Breaking: private config key replaced by publish_mode; rlsbl migrate removed.** .rlsbl/config.json now requires "publish_mode": "ci" (publish via CI pipelines) or "publish_mode": "none" (suppress publishing). A config still carrying private is a hard error naming the exact edit. scaffold --private is replaced by --publish-mode <ci|none>; private repos must choose explicitly, public repos default to ci. The private-publish-workflow check is renamed publish-mode-workflow. Run scripts/sweep_publish_mode.py --fix to migrate configs. The rlsbl migrate command is removed -- it delegated to migrable (TOML) while rlsbl configs are JSON, so every invocation errored since it shipped.
  • **scaffold --force-overwrite removed.** The flag wholesale-overwrote every managed file and destroyed local edits; it is deleted. Re-scaffolding a legacy repo now heals missing bases and three-way merges instead, and a file with no reconstructable base hard-errors with remediations rather than silently overwriting.
  • **Pipelines now require an explicit target link.** Each pipeline entry must declare a target (a target name it publishes for, or null for a targetless deploy). Missing fields and references to non-existent targets are hard errors.
  • Breaking. Migrated check system from CheckResult to strictcli's reporter-based check API (ErrorReporter/WarnReporter + CheckOutcome).

#Features

  • Per-target test config. .rlsbl/config.json gains an optional test section: set test.pypi.markers (e.g. "not integration") to append -m <markers> to the built-in pytest run, so slow or credentialed integration tests can be excluded from release/check test runs.
  • Dev-sync overlay sentinel. rlsbl dev sync writes a sentinel recording the path-source overlay it applied, a new dev-overlay-drift check flags when the working copy's overlay has drifted from that record, and a malformed sentinel now hard-errors instead of being silently treated as empty.
  • Smarter watch on CI failure. rlsbl watch now classifies CI failures before retrying and always prints the tail of the failure log, so you see why a run failed instead of a bare retry.
  • Configurable-budget hints in timeout messages. Timeout errors now point to the environment variable that controls the budget, and the built-in pytest run is invoked as python -m pytest so it always uses the project interpreter rather than a system-installed pytest.
  • Scaffold heals missing merge bases from history. Re-scaffolding a project that predates merge-base tracking now reconstructs each managed file's base from its most recent rlsbl scaffold commit and runs a normal three-way merge (conflicts surface as markers, never silent overwrites) instead of skipping the merge and advising a destructive overwrite. Reconstruction is read-only during analysis, so --dry-run reports Would heal: and writes nothing; healed bases are persisted so future runs need not re-reconstruct.
  • New feature. Go pipelines distinguish library vs binary via the artifact config key. Library projects get a lightweight module-availability verification publish template instead of goreleaser. Auto-detected during scaffold.
  • New feature. npm wrapper publish jobs support provenance attestation. When provenance is enabled, --provenance flag and id-token: write permission are added to the generated workflow.
  • Idempotent publish. Pipeline publish() methods probe the linked target's registry before publishing; already-published versions are skipped. Per-pipeline resume via published_targets in release state. PyPI publish passes --check-url. Already-exists errors (npm E403, PyPI 400, crates.io) treated as success.
  • Go probe rework. Go target publication_probe now uses git ls-remote --tags instead of Go module proxy HTTP GET. Avoids proxy cache lag and works for private modules.
  • CI template probes. Go, Docker, Maven Central, and Zig publish templates now check if already published before running build/publish steps.
  • npm wrapper per-package probes. Each platform package probes npm registry before publishing; wrapper meta-package gated on its own probe step.
  • Recovery dispatch. All 13 publish templates gain a tag workflow_dispatch input for retry dispatch. Checkout uses inputs.tag with release.tag_name fallback. Concurrency keyed on tag. retry.toml gains tag field; dispatch passes -f tag=.
  • New feature. Added pure/impure partition for dry-run preflight checks, showing which checks would run without executing impure ones.
  • New feature. External checks now use strictcli's check provider pattern for proper registration before filtering.

#Fixes

  • **Invalid batch_limits config now hard-errors.** Wrong-typed keys, a non-dict batch_limits block, and unresolvable exclusion commit hashes are rejected loudly instead of being silently ignored.
  • Malformed lint and hook config now hard-errors. Malformed lint TOML, an invalid parser value, wrong-typed lint config fields, and an invalid RLSBL_HOOK_TIMEOUT value are rejected with a clear error instead of being used as-is or silently ignored.
  • Changelog entry type is validated. rlsbl changelog add --type now rejects any value that is not one of feature, fix, or breaking for user-facing entries.
  • Safer release rollback. Rollback now preserves tracked finalize files, drops the misleading force-push advice, checks for residual state, and the pre-publish secret scan is scoped to freshly built artifacts.
  • First-release path guarded against destroyed tags. The first-release flow no longer breaks when tags have been destroyed, and the destroyed-tag guard message now diagnoses a changed tag_format as a likely cause.
  • Robust per-version changelog regeneration. Regeneration now handles a read-only per-version .md file and writes it atomically, and duplicate-commit messaging is stabilized.
  • Clearer npm name-collision errors. When an npm package name collides with an existing moniker, the error now names the concrete conflicting package.
  • External checks run even with customized hooks. Release preflight now runs external checks regardless of hook customization, and the dry-run preflight reports honestly what it would do.
  • External-check names are validated. External-check names must match [a-z][a-z0-9-]*, which excludes glob metacharacters that could pattern-match a built-in check, and a name colliding with a built-in check is now a hard error.
  • **Malformed test config blocks are gated during release.** A malformed test section (unknown targets/options, bad marker types) is now rejected before any release mutation instead of surfacing later.
  • **release undo reworked for safety.** It now fully unwinds multi-commit releases (previously only one commit was reverted, stranding version files at the undone version); --dry-run previews the complete plan and changes nothing (the flag was previously ignored, so a dry run still deleted tags and the Release and pushed a revert); and undoing the latest release now requires registry evidence -- a published release is refused and routed to release yank/release deprecate instead of being destroyed.
  • Undo hardening. The release commit walk now refuses partial undo when a foreign commit blocks the version-bump commit. INCONCLUSIVE evidence gate prints remediation guidance. The audit commit exception is narrowed to CalledProcessError.
  • Fix. npm, cargo, and deno pipelines now publish from the target's directory (cwd=dir_path) so subdirectory targets build and publish correctly. Secret scan covers per-target dist/ directories.
  • Fix. Merged publish workflow generation now uses pipeline template_mappings for template resolution instead of hardcoded target-name paths, so pipeline config drives template selection.
  • Bug fix. The undo completeness guard error message no longer references undefined variables.

#0.104.1

Root-publisher publish gate, pytest rootdir pin, batch release idempotency, push-timeout resume resilience

Context

Four bug fixes addressing release-flow reliability:

  • Monorepo sync now generates a gated publish router for root-only publishers (was silently skipped)
  • Member CI pytest invocations pin --rootdir . to prevent workspace-root escape
  • Batch releases persist a resolved plan sidecar for idempotent resume
  • Post-TAGGED push failures are now resumable instead of triggering destructive rollback

#Fixes

  • Fix. Monorepo sync now generates a gated publish router for workspaces whose sole publisher is the root package. New required publish_gate_check_regex config key specifies which CI check the gate matches. Previously, root-only publishers silently skipped gate generation and publishes ran ungated.
  • Fix. Member CI jobs now pin --rootdir . on pytest invocations, preventing rootdir from escaping to the workspace root. New member-pytest-config workspace check hard-errors when a member lacks its own pytest config while a workspace-root conftest exists.
  • Fix. Batch releases now persist a resolved plan sidecar; on resume, already-released items are skipped and a stale-but-complete batch file is auto-archived. Previously, a mid-batch resume re-released completed items and a stale batch file persisted across releases.
  • Fix. A push failure after the TAGGED step is now classified as resumable -- the local tag, release state, and finalized files are preserved, and the exact resume command is printed. Previously, push timeouts triggered a full rollback that deleted the tag, state file, and committed changelog files, making release resume unusable.

#0.104.0

Mandatory npm provenance declaration with a preflight visibility guard, plus monorepo release-correctness fixes: structured-target scaffold config, loud scaffold-commit failures, virtual-workspace-root check skipping, releasable-aware orphan detection, mode-aware CI-sync check, two-level lint config, and per-releasable status rows.

Context

This release bundles Phases 4, 5, and 6.

Phase 6 (npm provenance) makes the npm publish pipeline's provenance behaviour explicit and safe. npm build-provenance attestations require a PUBLIC GitHub source repository and GitHub Actions OIDC, so:

  • npm-type pipelines now require an explicit boolean provenance key in

.rlsbl/config.json (no implicit default -- the correct value depends on repository visibility). This is a breaking config change for existing npm pipelines; add "provenance": true for public repos or false for private.

  • The scaffolded publish workflow (npm/pnpm/yarn variants) emits --provenance

only when the pipeline declares it, via a namespaced {{#if npm.provenance}} template conditional. --access public stays hardcoded on purpose (scoped packages are forbidden in this ecosystem; unscoped public packages require it). The yarn variant, which was previously missing provenance entirely, is now consistent with the others.

  • A pre-mutation preflight guard probes repository visibility via

gh repo view --json isPrivate when (and only when) an npm pipeline requests provenance, and aborts on a private or non-GitHub repo with the three ways out spelled out. No network call happens when provenance is not requested.

  • Local (non-CI) npm publishes never pass --provenance, since OIDC

build-provenance is impossible outside GitHub Actions.

Phases 4 and 5 are the monorepo release-correctness fixes already landed on main: scaffold no longer downgrades structured publish targets, scaffold-commit failures are now loud, virtual uv workspace roots skip inapplicable package/publish checks, changelog orphan detection respects a releasable's custom tag format, the workspace-ci-synced check validates the inlined ci-router.yml, releasable members can share a single releasable-level lint config, and rlsbl monorepo status shows one row per releasable with its real tag, version, coverage, and members.

#Breaking

  • **npm pipelines now require an explicit provenance boolean.** The npm publish workflow emits --provenance only when provenance: true, a pre-release guard blocks provenance: true on private or non-GitHub repositories, and local publishes never attest provenance. Add "provenance": true (public repos) or false (private) to each npm pipeline in .rlsbl/config.json.

#Features

  • Releasable member packages can now share a single lint config at the releasable level. library-lint reads .rlsbl-monorepo/releasables/<name>/lint/<language>.toml when a member has no lint override of its own, and rlsbl monorepo cleanup removes a member's .rlsbl/lint/ only when it is byte-identical to that shared config -- a genuine per-member override is preserved.

#Fixes

  • Release init now fails loudly when it cannot commit the scaffolded release file. A failed auto-commit is no longer silently swallowed, which previously left an uncommitted release file that could block later releases.
  • **rlsbl monorepo release init now reports an actionable error for a releasable whose config has an empty targets list.** The banned empty-targets config surfaces a clear remedy (remove the key or set private: true) instead of a confusing no-eligible-releasables error.
  • **rlsbl init no longer downgrades structured target entries to plain strings.** Registering a target preserves existing entries such as {"name": "go", "path": "go/"}, keeping subdirectory paths intact.
  • **rlsbl changelog edit --id now prints a clean error when no entry matches** instead of crashing with an internal error.
  • Virtual uv workspace roots no longer report spurious check failures. A pyproject.toml with [tool.uv.workspace] but no [project] table is recognized as a non-package root: version, name, config-schema, and publish-workflow checks now skip with a clear reason instead of failing on a missing version or private key.
  • Changelog orphan detection no longer raises false positives for releasables with a custom tag format. A changelog entry whose commit is within the releasable's own tag range is no longer flagged as a stale orphan because of an unrelated v* tag elsewhere in the repo.
  • **The workspace-ci-synced check now validates the inlined ci-router.yml instead of looking for per-project {name}-ci.yml files.** Monorepos synced with the current inline router no longer report spurious missing-workflow failures; a project is verified present by its inlined router jobs.
  • **rlsbl monorepo status now shows one row per releasable with the releasable's real tag, version, changelog coverage, and member list.** Releasable members previously showed Tag (none) because the tag glob was derived per-member instead of from the releasable's tag format.

#0.103.2

Fix release-edit CHANGELOG.md lookup for explicit-mode releasables and carry the package prefix on amended releasable changelog entries.

Context

GitHub Release notes sync (rlsbl release edit) now works for explicit-mode releasables instead of failing with 'CHANGELOG.md not found', and amended releasable changelog entries now render with their [name] prefix like added entries.

#Fixes

  • rlsbl release edit now finds CHANGELOG.md for explicit-mode releasables (previously failed with "CHANGELOG.md not found").
  • rlsbl changelog amend now sets the packages prefix on amended releasable entries, so they render with the [name] prefix like added entries.

#0.103.1

release init can no longer clobber a filled release file (refuse-unless-pristine + atomic create).

#Fixes

  • release init and monorepo release init can no longer clobber a filled-in release file; they refuse unless the file is a pristine scaffold and no-op idempotently when it is

#0.103.0

Schema dump correctness, CI working-directory injection, import name auto-detection, and configuration validation in checks.

#Breaking

  • Breaking. Failed or timed-out strictcli schema dumps now abort the release instead of printing a warning.

#Features

  • New feature. Schema dump version field now matches the release being cut, not the previous version.
  • New feature. Scaffolded CI workflows for subdirectory targets now include the correct working-directory.
  • New feature. monorepo sync auto-detects Python import names that differ from package names, populating import_name in workspace.toml.
  • New feature. rlsbl check now validates targets and release.mode configuration, catching errors previously only found during release.

#0.102.0

Rollback safety guard and documentation directive extras.

#Features

  • New feature. Release rollback now detects concurrent commits from other sessions and refuses to proceed, preventing data loss from blanket git reset.
  • New feature. Documentation directives require selfdoc-core, installable via pip install rlsbl[docs].

#0.101.1

CI fixes: gitleaks install in CI workflows, inline monorepo CI router (GitHub rejects routers with >20 reusable workflow calls), reusable-call guard. Release blog posts now generate via the selfblog CLI.

Context

A monorepo's CI router had been broken since June 24 because GitHub Actions rejects workflows with more than 20 reusable workflow calls. The router is now inlined per-package, a guard check prevents regenerating routers that exceed the limit, and gitleaks is installed in CI so the secret-scan gate works in workflows.

#Fixes

  • Bug fix. Monorepo CI router now inlines jobs instead of reusable workflow calls -- GitHub rejects routers with more than 20 uses: calls, which silently broke CI for large monorepos.
  • Release blog posts now generate via the selfblog CLI (selfblog post generate --from-release); selfdoc gen/check are unchanged

#0.101.0

Dev-branch workflow, changeset-file coverage mode, crates.io target, yank/deprecate split, secret scan gate, PR-mode removal, external check providers, entry id field, rlsbl push, remap tooling + post-rewrite hook.

Context

Large feature release spanning multiple areas:

  • Dev-branch workflow: support for releasing from non-main branches with branch-aware version management.
  • Changeset-file coverage mode: alternative to commit-based coverage that tracks changes via explicit changeset files.
  • Crates.io target: new pipeline target for Rust crate publishing.
  • Yank/deprecate split: separated yank (registry removal) from deprecate (soft deprecation with messaging).
  • Secret scan gate: pre-push and pre-release checks for accidentally committed secrets.
  • PR-mode removal: removed the PR-based release workflow in favor of direct branch releases.
  • External check providers: pluggable check system allowing third-party validation providers.
  • Entry id field: unique identifiers on changelog entries for stable cross-referencing.
  • rlsbl push: dedicated push command with safety checks replacing raw git push.
  • Remap tooling + post-rewrite hook: SHA remapping infrastructure for commit rewriting workflows.

#Breaking

  • Yank/deprecate split. release yank is now registry-aware removal (npm deprecate, cargo yank, Go retract, PyPI checklist). release deprecate is the soft GitHub-only flag. release undo supports non-latest stillborn releases with layered evidence gate.
  • PR-mode removal. Removed the PR-based release flow (zero adopters, 5 latent bugs). release.mode config key is now a hard error. Consolidated config validation bans targets: [] and stale PR-mode state.

#Features

  • External check providers. Projects can declare subprocess checks in .rlsbl/config.json under external_checks. Each entry specifies a name, shell command, check tag, and optional dependencies. Checks run during rlsbl check --tag and the release preflight. Non-zero exit is a hard fail with no bypass.
  • crates.io target. Full release pipeline for Rust crates: check-name with hyphen/underscore normalization, claim-name with permanence confirmation, CI publish workflow with OIDC Trusted Publishing, and version bump/publish sequencing.
  • Changeset-file coverage mode. New coverage_unit config key supports changeset-file as an alternative to per-commit coverage. Changelog add writes pending files, prepush uses diff-based coverage, release finalization concatenates pending files, and all commands are mode-aware.
  • Entry id field. Changelog entries now include a stable ULID-style id field, auto-generated by changelog add. Used by changelog amend and changelog edit for unambiguous entry selection.
  • Dev-branch workflow. rlsbl release run from a non-release branch fast-forward merges the release branch to HEAD before releasing. New rlsbl push command for dev branches with branch guard, changelog coverage preflight, and behind-remote detection.
  • Secret scan gate. Pre-publish gitleaks-based artifact scanning detects secrets in build artifacts (.whl, .tar.gz, .tgz, .zip). Hard fail when secrets are found, configurable via .gitleaks.toml allowlists.
  • Changelog remap command. New rlsbl changelog remap command remaps stale commit hashes in JSONL files from multiple sources (--map-file, --from-journal, --stdin). Auto-installed post-rewrite hook triggers remap on amend/rebase.

#0.100.1

Fix CI_CHECK_REGEX in publish router to match actual GitHub check-run names

Context

The monorepo CI router generates job keys from CI workflow filenames (e.g. core-ci from core-ci.yml), but the publish router gate was using project names (e.g. core) in the regex. This caused the gate to never find matching check runs, timing out after the grace window.

#Fixes

  • Fixed CI_CHECK_REGEX in publish router. The gate job regex now matches actual GitHub check-run names (CI filename-based, e.g. name-ci / test) instead of project-name-based names that never matched.

#0.100.0

Remove vestigial hashes.json from scaffold metadata

Context

hashes.json was never read for any scaffold decision -- it was write-only dead state. This release removes all production code that maintained it, adds automatic cleanup of existing hashes.json files on next scaffold run, and deletes the associated tests.

#Breaking

  • Removed hashes.json from scaffold metadata. The file was vestigial (never read for any decision). Existing hashes.json files are automatically deleted on next scaffold run.

#0.99.0

Versioned JSONL merge during migrate-releasable, packages prefix in CHANGELOG rendering, absorb changelog routing fix

Context

migrate-releasable previously orphaned versioned changelog files, leaving only unreleased.jsonl in the releasable directory. This release adds full changelog consolidation: versioned JSONL files and release metadata are merged during migration. CHANGELOG rendering for releasable members now includes the package name prefix for clarity in multi-package projects.

#Features

  • Versioned JSONL merge during migrate-releasable. Released changelog files are now consolidated into the releasable directory alongside unreleased entries, preserving full version history after migration.
  • Packages prefix in CHANGELOG rendering. Version sections for releasable members now include the package name prefix (e.g., core/0.5.0) so multi-package changelogs are unambiguous.

#Fixes

  • Absorb routes changelogs to releasable directory. When absorbing a project into a monorepo with a releasable name, changelog files are placed in the releasable's changes directory instead of the package-level directory.

#0.98.2

Fix CI test failure: preid test no longer depends on local git tag history.

#Fixes

  • Fix. CI test reliability: preid test now mocks tag_exists_locally directly instead of depending on local git tag history.

#0.98.1

Fix scaffold hash pruning that broke CI by over-aggressively deleting hashes.json entries.

#Fixes

  • Scaffold hash pruning no longer removes entries for files not actively deleted during the scaffold run.

#0.98.0

JVM monorepo analysis, maven-central pipeline, CI templates for 5 targets, build hardening

Context

Major additions for JVM/Android monorepo support: dead-module detection, circular dependency analysis, Gradle version catalog parsing, GPG signing validation, and maven-central pipeline scoping with preflight checks. CI template coverage expanded to dart, flutter, pgdesign, native-android, and native-ios targets, with a capability filter so the workspace-ci-synced check skips targets that lack template support.

Build checks are now pre-push (not post-push) and fatal -- build failures abort the release instead of being warnings. Version catalog resolution uses fallthrough instead of hard errors for missing keys. Scaffold now cleans up orphaned bases, stale hashes, and empty directories.

#Breaking

  • Build failures abort releases. Build checks are now pre-push (not post-push) and failures are fatal. Build timeout is configurable via build_timeout in config.
  • Version catalog fallthrough. Missing keys in Gradle version catalogs now fall through to raw string resolution instead of raising errors.

#Features

  • New helper. Extracted tag_exists_locally and tag_exists_on_remote helpers from duplicated tag-probe logic across release flows.
  • Scaffold cleanup. Orphan base sweep, stale hash pruning, and empty directory cleanup during scaffold operations.
  • Maven-central pipeline. Preflight validation tag, pipeline-scoping gate, publishSetup hints with maven-central requirements, and pipeline documentation.
  • Gradle catalog parsing. Resolve libs.<alias> references from Gradle version catalogs when scanning Maven dependencies.
  • Dead JVM module detection. Build class-to-file index from JVM sources and detect modules with no inbound references.
  • Circular JVM dependency detection. Detect circular dependencies between JVM modules in the workspace dependency graph.
  • CI templates. Added CI template support for dart, flutter, pgdesign, native-android, and native-ios targets. The workspace-ci-synced check now filters by target capability so projects with unsupported targets are skipped.
  • Signing check. GPG signing configuration validation for maven-central metadata.

#Fixes

  • Fix false-pass. Maven-publish source/javadoc detection no longer matches bare maven-publish plugin references, preventing false positives.
  • Fix warn-cascade. Self-gating logic no longer suppresses its own warnings. Monorepo init now rejects directories inside an existing rlsbl-managed repo.
  • Fix test-suite-workspace. Maven is now a recognized target in workspace test validation.

#0.97.1

Internal quality pass: circular-dep cycle resolution, CLI help expansion, selfdoc-check zero warnings, dev-group UX fixes.

Context

Completes the quality cleanup phase: resolved all three import cycles (cycle 2 extracted to resolve.py, cycles 1+3 structural via workspace_types.py, parameter injection, prepush_utils.py/constraints.py extraction), expanded CLI help text to meet documentation checks, polished 42 docstrings for SEO compliance, and parameterized 14 callers for dev-group workspace-root UX.

#Fixes

  • Fix. release init and monorepo release init now auto-commit the scaffolded unreleased.toml, eliminating the manual commit step that blocked --no-allow-dirty releases.
  • Fix. All per-sub-project commands now show a workspace-root-specific error message instead of the misleading 'Run rlsbl monorepo add' when invoked from the monorepo workspace root.
  • Expanded CLI help text. All command, flag, and argument help strings now meet the 50-character minimum for documentation quality checks.
  • Improved CLI help text for documentation quality. Shortened overlong command descriptions and added numeric data points to meet SEO007 and SEO008 checks.

#0.97.0

Multi-artifact releasables (per-member publishing at the shared version, collision-proof asset naming), version-consistency enforcement for published members, monorepo-init bootstrap fix

Context

Completes the six-todo scope of the Phase 10 multi-artifact releasable effort.

#Features

  • Per-member publish loop. In releasable mode, each non-private member with publish pipelines is published independently at the shared version. Private members are skipped, pipeline-less members are logged, and resume tracks per-member progress.
  • Releasable version-consistency checks. Published members' manifests must match the releasable version. Dead-workspace-packages exempts published releasable members.
  • Per-member asset upload. In releasable mode, each publishing member uploads assets with member-name-prefixed filenames for deterministic naming.

#Fixes

  • Monorepo init bootstrap fix. rlsbl monorepo init no longer requires a pre-existing .rlsbl/ directory, enabling fresh workspace initialization. Refuses nested workspaces.

#0.96.0

Publish workflows now gate on CI success for the release commit (no more publish-races-CI), with a ref-based retry contract and a fix for dispatch runs skipping all router jobs.

Context

Closes the wesktop-v0.7.0-class failure mode where a package was published while CI was failing. A gate job (single source of truth in rlsbl/publish_gate.py) now runs first in every publish workflow: it polls check runs for the release SHA (ref-based, so workflow_dispatch retries at the tag ref work), uses explicit conclusion semantics, and hard-errors on failed, cancelled, or skipped CI. The gate is wired across templates, the merged-workflow generator, the npm wrapper, and the monorepo router; router conditions are ref-based (fixing dispatch runs skipping all jobs), publish concurrency is per-ref, and the gate job requests checks:read. Verified live on a staging sandbox repo across 6 scenarios.

#Features

  • Publish gating. Publish workflows now wait for CI to pass on the release commit before publishing: a gate job polls the commit's CI check runs (ref-based, so dispatch retries at the tag ref work) and hard-errors on failed, cancelled, or skipped CI. Monorepo publish routers use ref-based tag matching, fixing workflow_dispatch runs skipping all jobs.
  • Docs: publish gating. New release-workflow.md section covering the publish gate's conclusion semantics, the retry-dispatch-at-tag-ref contract, and monorepo router gating.

#0.95.0

Local dev overlays via rlsbl dev sync, the cross-repo path-source ban, a release-time version-skew guard, detached CI watching with --watch-async/rlsbl watch --stop, and history-consistent scrubs on safegit 0.22.0.

Context

This release is a hard-constraints sweep: committed lockfiles must be registry-pure (the new cross-repo-path-sources check bans out-of-repo [tool.uv.sources] paths, and scaffolded pypi CI now runs uv sync --locked), and nothing degrades silently -- the version-skew guard hard-errors when a dev-overlaid dependency's local checkout is ahead of its PyPI release, and unpublished overlays or registry failures are hard errors, never skips. Local editable development moves to rlsbl dev sync overlays instead of committed path sources. Scrubs are now orchestrated with safegit 0.22.0: persisted SHA maps, in-history JSONL remapping via --remap-shas-in, journal-backed recovery for no-match validation scrubs, and a cleanup_ok gate, so history rewrites keep changelog metadata consistent end to end.

#Features

  • **New rlsbl dev sync command: local editable overlays.** Overlay local editable checkouts of sibling projects onto the project's locked environment, driven by a git-invisible dev-sources.toml.local-only file at the project root -- develop against sibling checkouts without committing machine-local [tool.uv.sources] path dependencies. Runs one uv sync --inexact excluding every overlaid package, then uv pip install -e <path> per entry, so locked registry wheels never clobber the local checkouts. Requires UV_NO_SYNC=1 in the environment (hard error otherwise, since a bare uv run would silently wipe the overlays); every overlay-file problem is a hard error, never a silent no-op.
  • **rlsbl release scrub now produces history-consistent changelogs.** The scrub hands safegit (>= 0.22.0 required) repeatable --remap-shas-in globs covering every changelog directory, so commit hashes inside the JSONL files are remapped at EVERY commit of the rewritten history -- historical versions stay self-consistent, not just HEAD. rlsbl's own worktree remap step became a validation gate; dangling hashes (e.g. abbreviated hashes, or scrubs run outside orchestration) are repaired from safegit's persisted rewrite journal, and CHANGELOG regeneration is now asserted byte-identical instead of rewritten.
  • Scrub aborts safely when safegit's post-rewrite cleanup failed. cleanup_ok: false from safegit is now a hard error before anything is committed or pushed, with the cleanup errors and prune remediation printed; a resumed run re-checks whether the prune has since completed and continues once no pre-rewrite object resolves.
  • **rlsbl watch --stop [sha] stops a detached background watcher.** With a SHA it stops that watcher; without one it stops the single live watcher or lists candidates. Sends SIGTERM, escalates to SIGKILL after 5s, and cleans up stale pidfiles left by dead watchers.
  • **--watch-async releases without blocking on CI.** release run, release resume, release retry, and monorepo release run now accept --watch-async (mutually exclusive with --watch; exactly one of the pair is required) to spawn a detached background watcher and return immediately. The watcher logs to .rlsbl/watch-<sha>.log.local-only, keeps auto-retry, publish audit, and desktop notifications working, and refuses to double-spawn for the same commit. --no-watch-async is equivalent to --no-watch.
  • Docs: detached watching documented. The release workflow page now covers --watch-async, the per-commit log/pidfile locations, and rlsbl watch --stop.
  • **New check: cross-repo-path-sources.** Committed pyproject.toml files must not declare [tool.uv.sources] path entries that resolve outside the repository. In-repo paths and workspace = true sources stay legal. Runs under the project tag and unconditionally during rlsbl release run.
  • Scaffolded pypi CI is locked and tests. ci.yml now runs uv sync --locked (registry-pure lockfiles are guaranteed by the new ban check) and adds a uv run pytest step when the project declares pytest. The UV_NO_SOURCES env escape hatch is gone.
  • New release guard: version skew. rlsbl release run now reads dev-sources.toml.local-only and hard-errors when an overlaid dependency's local checkout is ahead of its latest PyPI release ("release the dependency first"). Unpublished overlay packages and registry failures are hard errors too -- never a silent skip.

#Fixes

  • **Fixed rlsbl dev install for path-scoped targets.** Each target's install command now runs in that target entry's declared directory instead of the project root, so multi-target projects (e.g. a Go binary with npm/ and pypi/ wrapper subdirectories) install correctly: npm finds its package.json and uv finds its pyproject.toml. Uninstall likewise resolves the package name from the target's own manifest.
  • **Clearer rlsbl dev install skip messages.** When a target has nothing to install (e.g. a Go library with no main packages), the skip line now shows the target's actual reason instead of the generic "install not yet supported" message.
  • Fix. rlsbl dev install and rlsbl dev sync invoked at a monorepo workspace root now explain to cd into a sub-project instead of misleadingly suggesting rlsbl monorepo add.
  • Fix. rlsbl --help now reports accurate command counts (49 commands, 15 top-level, 4 groups), derived from the live command registry instead of stale hand-maintained literals.
  • **rlsbl watch publish-workflow audit works from subdirectories.** The audit now looks for .github/workflows at the git repo root instead of the current directory, so watching from a monorepo package directory no longer falsely warns that a publish workflow is missing.

#0.94.0

Overhauls the releasable release flow with provable post-release step tracking and incremental resume, moves releasable state and release files into the releasable directory, and hardens the Go pipeline and release-scrub with hard errors in place of silent skips. Includes breaking changes: Go pipelines now require explicit install_paths, and pipeline publish failures are fatal.

Context

This release is a correctness push for the releasable model plus a philosophy sweep replacing silent skips with hard errors.

The releasable release flow previously wrote per-package artifacts to an arbitrary "representative" member package and had no record of which post-release steps had completed, making partial failures unrecoverable and unverifiable. Post-release steps are now tracked with provable completeness and incremental resume, releasable state and release files live under the releasable's own directory, representative writes are eliminated, changelogs are generated canonically per releasable plus a combined root changelog, and a new releasable-residue check with a monorepo cleanup command guards against stray artifacts.

The Go pipeline moved from filename-based main.go globbing to toolchain-based entry-point detection with mandatory install_paths, because silent install-skips left stale binaries. The same hard-errors-over-silent-skips principle now applies to pipeline publish failures (fatal) and release-scrub (file/recipe modes actually working, real-binary e2e harness, validation gate, explicit-lease pushes, committed no-content audit archive, orchestration handshake, min safegit 0.21.1). Also: watch reliability fixes, per-SHA CI concurrency groups, member-context resolution unification, changelog --dry-run fix, and an unversioned-boundary check.

#Breaking

  • **Go pipelines with local: true must declare install_paths.** The new mandatory config key lists the main-package dirs to go install (e.g. ["./cmd/x"]); each path is validated against go list. The old go install ./... auto-detection is gone -- it silently skipped cmd-layout projects with non-main.go entry files forever. Publish failures (missing go toolchain, unreadable go.mod, proxy notification failure, install failure) are now raised instead of warn-and-continue.
  • strictcli schema dumps can no longer be silently skipped. Go entry-point detection for strictcli projects uses go list (handles cmd/x/cli.go entry files, whose schema dump was previously skipped on every release). If go.mod requires strictcli but the entry point cannot be determined, release validation aborts with a hard error instead of silently shipping a stale schema.
  • Pipeline publish failures now abort the release. A failed publish (previously downgraded to a warning) preserves the release state and exits with an error; run rlsbl release resume to re-attempt it. For local: true pipelines the publish step IS the release — warn-and-continue silently shipped unpublished releases.

#Features

  • New check. unversioned-boundary (tag: workspace) errors when a releasable project has a runtime dependency on a project with releasable = false that is not dev-only -- such dependencies ship changes inside consumer releases with no changelog coverage. Sibling of dev-only-boundary.
  • Release state tracking overhauled with a canonical step list. The in-progress guard message now derives its step count from the real list (fixing the hardcoded n/6 count), names the missing steps, and auto-clears state files from releases that provably completed (all steps marked, no fatal failures) instead of hard-blocking.
  • Post-release steps are now tracked and resumable. Asset upload, pipeline publish, deploy, post-release hooks, and snapshot regeneration each record success or failure in the release state file. rlsbl release resume skips already-completed post-release steps instead of re-running the whole phase, and non-fatal failures (deploy, hooks, snapshot) are loudly named in a completion summary.
  • Releasable release state no longer pollutes the representative member package. In-progress release state (and scrub-result.json) for releasable releases now lives under .rlsbl-monorepo/releasables/<name>/releases/ instead of the representative member's .rlsbl/releases/. rlsbl release resume also works from the workspace root (it finds the one releasable with in-flight state and errors clearly when two are mid-release), and legacy state at the old location is detected with a migration hint instead of being silently ignored.
  • Release workflow docs now cover state tracking and resume. The pipeline-order table documents the post-release steps (assets, publish, deploy, hooks, snapshot) with their fatality classes, and a new section explains the in-progress state file, its releasable location, and rlsbl release resume.
  • New. rlsbl release scrub supports recipe mode: pass a safegit scrub run TOML recipe to apply multi-operation scrubs with the same metadata repair, push, and release-recreation flow.
  • New. rlsbl release scrub sets RLSBL_SCRUB_ORCHESTRATED=1 in the safegit scrub subprocess environment, so future safegit releases can require orchestrated scrubs in rlsbl-managed repos.
  • New. A releasable-residue workspace check (hard error) flags per-package release-state residue in releasable member packages, and a new rlsbl monorepo cleanup command removes it via saferm and commits the deletions. Per-package .rlsbl/hooks/ directories are now preserved by cleanup (per-package script hooks are a live feature), and members whose path is the workspace root are exempt (protects workspace-level .rlsbl/ and the combined root CHANGELOG.md).
  • New. The scrub metadata commit now carries a machine-greppable Scrub-remap: <old_head>..<new_head> trailer for audit trails.
  • New. Successful scrubs now leave a committed audit archive (.rlsbl/scrubs/scrub-<newhead>.json, or under the releasable directory in explicit monorepo mode) instead of deleting the scrub state. The archive schema is hard-whitelisted -- commit SHAs, tag refnames, reason, mode, and step list only -- so a committed scrub artifact can never re-introduce scrubbed content.
  • Recipe-mode scrubbing is now CLI-reachable. rlsbl release scrub --recipe <recipe.toml> runs a safegit scrub recipe; the mode was already implemented in the scrub flow but never registered on the CLI. File mode no longer demands the match-only --replace/--mangle flags that made it unusable from the command line.

#Fixes

  • rlsbl watch no longer re-watches or re-retries a retry run that reappears in the late re-poll -- previously a failed workflow's retry could be dispatched twice on the same commit
  • rlsbl watch retry deduplication now also applies when only a single CI run is being watched -- previously single-run watches could double-retry a failed workflow
  • rlsbl watch auto-retry now waits for the actual dispatched retry run to appear instead of attaching to the just-failed original run (or another known run) and reporting a bogus retry result
  • Scaffolded CI workflows now use a per-SHA concurrency group (cancel-in-progress dedupes re-runs of the same commit only), so back-to-back pushes can no longer cancel an earlier commit's CI run; the monorepo CI router gets the same group and called project workflows have workflow-level concurrency stripped
  • Fix. Releasable-level private: false and targets config is now consistently inherited everywhere a member package's publish status is resolved: Go companion tag creation, the go-companion-tags check, and primary target path resolution now agree with version sync instead of silently treating such members as private.
  • Fix. rlsbl changelog add/amend/edit --dry-run no longer writes anything: previously the global --dry-run flag was silently dropped, so entries were appended, auto-committed, and --allow-batch even modified config.json. Dry-run now runs all validation and prints the entry JSON that would be written.
  • Go project detection now uses the go toolchain. cmd-layout projects whose entry file is not named main.go (e.g. cmd/x/cli.go) are correctly classified as binaries: goreleaser and version.go get scaffolded, npm wrapper jobs are included, and .goreleaser.yml gets the right main: instead of a silent . fallback. version.go is scaffolded into the detected main-package dir (a root package main file without func main used to break go build ./... in cmd-layout projects), and goreleaser ldflags now inject main.Version matching var Version (the lowercase main.version injection was a no-op).
  • **release retry, release yank, and release edit now work for releasable members whose targets are configured only at the releasable level.** Previously retry failed with a config error (or "no package.json/pyproject.toml/go.mod found") and yank/edit could mis-resolve the release tag, because these commands ignored releasable-level config inheritance.
  • **rlsbl check now fails loudly on broken releasable member configs.** The go-companion-tags check reports a failure naming the member when its config.json is corrupt or resolves no targets, instead of silently skipping the member (which let the check disagree with the release flow about which members are published).
  • Check reference docs corrected. The docs claimed 50 checks, used the obsolete dev-node-boundary name, and omitted nine existing checks; the check reference, monorepo workspace-check table, and README now match the actual 57-check inventory, including the internal preflight/preflight-changelog/maven tags.
  • Fix. A hung go install during local Go pipeline publishing no longer stalls the release forever: the call is bounded by a 300s timeout and expiry is a hard error.
  • Fix. Releasing a monorepo releasable with a corrupt member config.json now aborts during Go companion-tag collection instead of silently releasing without that member's Go module proxy tag.
  • Fix. The go-companion-tags workspace check now fails loudly when a releasable's version file cannot be read, instead of silently skipping that releasable.
  • Fix. Releasable releases now write the canonical CHANGELOG.md into the releasable directory (where rlsbl changelog generate and the changelog-entry check already expected it) instead of the representative member's directory, and additionally generate a combined root CHANGELOG.md covering all releasables of the workspace. The GitHub-Release-notes recovery hint now points at the releasable changes directory.
  • Fix. rlsbl release scrub --file now works: it invokes safegit scrub file with the correct CLI (positional path, --from), instead of passing flags safegit rejects. File mode had never worked. A scrub that finds nothing to rewrite now reports "No matches found" instead of crashing on empty safegit output.
  • Fix. Releasable releases no longer pollute the representative member: the .rlsbl/version marker is only refreshed for actually-scaffolded standalone projects (never in releasable mode), a private representative's manifests are no longer version-bumped or keyword-tagged (the releasable version file remains the source of truth), and stale batch-limit exclusions are cleaned from the releasable-level config.json where changelog add --allow-batch writes them.
  • Fix. rlsbl release scrub --dry-run now shows real match and commit counts. It previously read keys that don't exist in safegit's dry-run JSON and always reported "0 commits would be rewritten, 0 tags affected".
  • Fix. PR-mode releases of releasables now write pending.json into the releasable's releases directory (via the release state-dir resolver), and the release-finalize workflow locates it by globbing both the standalone and releasable locations (hard error on ambiguity) -- previously the workflow hardcoded the repo-root path and was broken for non-root representatives. Dispatch workflows are now also collected from the workspace root's .github/workflows/.
  • Fixed strictcli self-detection false positive. _go_mod_has_strictcli matched the strictcli module path anywhere in go.mod, so the strictcli Go library's own module declaration counted as a dependency and rlsbl hard-errored demanding an entry point, blocking releases of any repo whose module path matches a consumed-tool prefix. Detection now only considers require directives (single-line and block forms).
  • Fix. rlsbl release scrub now requires safegit >= 0.21.1 (the version its file/recipe/dry-run contracts are built against) and no longer crashes on safegit versions with build suffixes like 0.21.1+dirty.
  • Fix. rlsbl release undo is now releasable-aware: it unfinalizes the version JSONL and regenerates CHANGELOG.md at the releasable level (canonical file plus combined root file) instead of the representative member's .rlsbl/changes/, and it clears the preserved in-progress release state after a successful rollback so a previously failed release no longer hard-blocks the next rlsbl release run. Failed-release rollback also cleans orphaned finalized JSONL files from the releasable changes dir.
  • Fix. Post-scrub changelog remapping now maps abbreviated commit hashes (unique-prefix match against the rewrite map) and reports every hash it could not map. Previously only exact full-SHA matches were remapped and everything else was silently skipped, leaving dangling references after a history rewrite.
  • Fix. rlsbl release scrub now remaps commit hashes in releasable-level changelog directories (.rlsbl-monorepo/releasables/*/changes/), not just per-project .rlsbl/changes/. Explicit-mode monorepo changelogs were previously left with dangling pre-rewrite hashes.
  • Fix. rlsbl release resume now works when invoked from a monorepo workspace root: it finds the single releasable with an in-flight release instead of exiting with a 'not inside any registered project' error.
  • Fix. rlsbl release scrub validates after remapping that every commit hash in every changelog file resolves against the rewritten history, and aborts loudly BEFORE committing or force-pushing if any hash is dangling (resume state is kept).
  • Fix. The release-workflow documentation no longer claims releases are atomic: it documents the preserved-state/resume model, corrects the post-release hook's pipeline step number, and documents the releasable location of scrub-result.json.
  • Fix. rlsbl changelog generate --auto-commit no longer crashes with an UnboundLocalError in releasable mode.
  • Fix. rlsbl release scrub now commits the deletion of tracked .validated caches (previously left as a dirty working tree after the scrub), persists its remap/delete file lists so resumed runs commit them too, and aborts before force-pushing if the metadata commit fails.
  • Fix. rlsbl changelog amend and rlsbl changelog edit now regenerate the canonical releasable CHANGELOG.md and the combined root CHANGELOG.md in releasable mode, instead of writing a CHANGELOG.md into the member package's directory.
  • Fix. Post-scrub force-pushes now use --force-with-lease with explicit expected values captured from the remote before the rewrite -- for the branch AND every tag. Bare --force-with-lease is useless after a scrub (the rewrite updates remote-tracking refs) and tags were previously pushed with plain --force and failures merely warned; a rejected push (remote changed mid-scrub) is now a hard error that preserves resume state.
  • Fix. During a post-scrub force-push, the pre-push changelog coverage check now prints "history rewrite detected: old remote head unresolvable — coverage check skipped" instead of passing silently and vacuously when the pre-rewrite remote head no longer resolves.
  • Fix. Post-scrub CHANGELOG regeneration is skipped for projects without a .rlsbl/changes/ directory, so releasable members no longer get a fabricated stub CHANGELOG.md. The scrub flow is now covered end-to-end against the real pinned safegit binary in CI.
  • Fix. Releasable single releases now store and archive their release file (unreleased.toml → v{x}.toml, plus the blog body) under the releasable's own directory (.rlsbl-monorepo/releasables/<name>/releases/) instead of the representative member's .rlsbl/releases/ — so file-based releasable releases no longer recreate per-package residue that fails the releasable-residue check. release init, release run, release undo, rollback cleanup, and the changelog metadata backfill all use the new location; a release file left at the old member location is a hard error with a migration hint.
  • Fix. rlsbl release scrub now loudly reports changelog hashes the JSONL remap could not handle (unmapped hashes and ambiguous abbreviated hashes, per file) instead of silently leaving them unchanged.
  • Fix. rlsbl release scrub no longer force-pushes a stray non-tag refname from the rewrite's tag list — non-refs/tags/ refnames are skipped with a warning. Post-rewrite changelog hash validation now always resolves against the scrubbed repository, not the process working directory.
  • Fix. rlsbl monorepo status no longer shows "no changelog" for releasable member packages — the Unreleased column now reads the releasable-level CHANGELOG.md.
  • Fix. rlsbl release retry for releasable members now keeps retry.toml under the releasable's releases directory instead of the member package's .rlsbl/releases/.
  • Fix. Successful releases no longer leave a stale in-progress.json behind when --watch is used, which previously blocked the next release with 'a previous release is in progress'.

#0.93.0

Migrated all boolean CLI flags to explicit-default style via strictcli.

Context

All bool flags now use --flag/--no-flag syntax with explicit defaults. Multiple renames: --no-commit to --auto-commit, --no-tag to --auto-tag, --no-resolve to --validate-hashes, --force to --force-overwrite on scaffold. --watch and --allow-dirty are now required flags with no default.

#Breaking

  • Breaking: Multiple flag renames: --no-commit to --auto-commit, --no-tag to --auto-tag, --no-resolve to --validate-hashes, --force to --force-overwrite on scaffold. --watch is now required (must pass --watch or --no-watch). --allow-dirty and --hard are now required. --force on deploy deleted. All bool flags require explicit defaults.

#0.92.0

Dispatch preid inputs, PR-mode resume fix, end-to-end pre-release integration test

#Features

  • Dispatch workflow. Release dispatch now supports all bump types (prerelease, hotfix) and pre-release identifiers (alpha, beta, rc, stable) as workflow_dispatch inputs.

#Fixes

  • PR-mode resume. PR-mode release resume now correctly preserves release_mode in the state file, preventing PR-mode releases from resuming as imperative.

#0.91.1

Fix critical pre-release wiring bugs and CI template pre-release awareness

#Fixes

  • Fix. preid now correctly wired through compute_release_version to bump_version. Pre-release versions (e.g., 0.43.0-alpha.0) are now produced correctly during releases.
  • Fix. CI publish templates now handle pre-release versions. npm templates add --tag {preid} for pre-releases. Docker template skips :latest tag for pre-releases.
  • Fix. Release group help text updated to list all 8 subcommands including resume and scrub.

#0.91.0

Pre-release channels, PR-based release flow, idempotent release resume, registry version detection, and remote release dispatch.

Context

This release adds three major capabilities: (1) pre-release channel support with PEP 440 translation and npm dist-tags, (2) PR-based release flow as an alternative to direct push, and (3) idempotent release resume for recovering from mid-release failures. Also adds registry version drift detection and a workflow_dispatch template for remote release triggers.

#Features

  • Idempotent release flow. New rlsbl release resume command. In-progress state file tracks completed steps; per-step guards enable safe resumption after mid-release failures.
  • Pre-release channel support. preid field in release TOML, bump = "prerelease" type, PEP 440 translation for PyPI, npm --tag dist-tags, Docker :latest skip, GitHub --prerelease flag, and changelog consolidation for stable releases.
  • PR-based release flow. release.mode = "pr" in config creates a release/vX.Y.Z branch and PR instead of pushing directly. Finalize workflow dispatches publish after merge. PR-mode undo closes the PR and deletes the release branch.
  • Registry version detection. rlsbl status --registry queries npm, PyPI, Go, and crates.io for published versions and shows drift between local and registry versions.
  • Remote release trigger. release-dispatch.yml scaffold template with workflow_dispatch inputs for --bump and --description.

#Fixes

  • Partial push rollback fix. Tag push failure no longer rolls back already-pushed commits.

#0.90.2

Fix test mocks for Python 3.14 compatibility

Context

ensure_github_topic and watch run_cmd migrated from run() to run_gh() but test mocks still targeted run(), causing failures on Python 3.14 CI

#Fixes

  • Bug fix. Fixed watch test mocks for run_gh migration in TestRePoll.

#0.90.1

Fix tagging test for Python 3.14 compatibility

Context

ensure_github_topic migrated from run() to run_gh() but the test mocks still targeted run()

#Fixes

  • Bug fix. Fixed test mock for run_gh migration in tagging module.

#0.90.0

Quick bump shortcut, run_gh centralization, changelog preflight unification

Context

Three major improvements in this release:

  1. Quick bump shortcut: rlsbl release run --bump patch --description "Fix X" allows simple releases without creating a release file first. The release file ceremony remains available for complex releases.
  1. run_gh centralization: All 33 repo-scoped gh CLI invocations now go through a single run_gh() function, replacing the old gh_env() pattern. This provides a cleaner API with consistent error handling and environment setup.
  1. Changelog preflight unification: Changelog validation is now unified into the preflight check system via app.run_checks(tag_expr="preflight-changelog"), eliminating duplicate validation logic between the release flow and the check system.

Also includes fixes for get_push_timeout crashing with config=None, uv.lock tracking, and a load_workspace scoping bug.

#Features

  • **New: run_gh() centralized GitHub CLI wrapper.** All 33 repo-scoped gh invocations now go through run_gh(), replacing the old gh_env() pattern with a cleaner API.
  • New: Changelog validation unified into preflight check system. app.run_checks(tag_expr="preflight-changelog") eliminates duplicate validation between release flow and check system.
  • New: Quick bump shortcut. rlsbl release run --bump patch --description "Fix X" bypasses the release file ceremony for simple releases.

#Fixes

  • Fix: get_push_timeout no longer crashes when config=None.
  • Fix: uv.lock is now tracked in git (was incorrectly gitignored).
  • Fix: load_workspace scoping bug in release flow — local imports no longer shadow module-level import.

#0.89.0

Preflight check integration and unreachable-code false positive fix

Context

Release flow now delegates test and lint validation to the check system via app.run_checks(tag_expr="preflight"), eliminating duplicate wrapper functions that historically diverged and caused release failures. Unreachable-code lint rule no longer treats inline comments as statements.

#Features

  • Release preflight checks via check system. The release flow now runs test-suite and library-lint checks via app.run_checks(tag_expr="preflight") instead of duplicate wrapper functions, with per-member iteration for releasable releases.

#Fixes

  • Unreachable-code false positive fix. Tree-sitter comment nodes are no longer treated as statements, fixing false positives and false negatives in the unreachable-code lint rule.

#0.88.1

Fix release lint to respect lint_allow from workspace.toml

#Fixes

  • Bug fix. Release lint now respects lint_allow from workspace.toml, matching the behavior of rlsbl check --tag quality.

#0.88.0

Configurable check timeout, centralized GitHub repo resolution with SSH alias support, push-access verification, library lint exceptions, and stderr surfacing for failed commands

Context

This release consolidates several reliability and usability improvements. The run_gh() migration replaces scattered remote-URL parsing across all commands with a single utility that handles SSH aliases and the github_repo config key. Push-access verification catches GH_TOKEN mismatches before the release flow runs. Library lint exceptions (lint_allow) support per-package overrides for forbidden-import checks. Configurable check timeout lets projects with slow test suites raise the default 120s limit.

#Features

  • Configurable check timeout. RLSBL_CHECK_TIMEOUT env var and check_timeout config key let you control test and lint timeouts (default remains 120s).
  • Centralized GitHub repo resolution. New github_repo config key and automatic SSH alias handling for all gh commands, replacing scattered remote-URL parsing with a single run_gh() utility.
  • Centralized GitHub repo resolution. Migrated yank, undo, release-retry, watch, PR, and check commands to run_gh().
  • Centralized GitHub repo resolution. Migrated checks, tagging, and private-repo detection to run_gh().
  • Push access verification. validate_gh_push_access checks the authenticated user has push access before releasing, with a hard error and actionable message on mismatch.
  • Library lint exceptions. lint_allow field in workspace.toml for per-package forbidden-import exceptions (e.g., allowing a library to import its own test utilities).

#Fixes

  • Stderr surfacing. Push and GitHub Release creation failures now show the actual error message from gh/git push instead of a generic failure.

#0.87.0

Ruff integration, unreachable-code detection, non-workspace test runner, and validate_release_targets union fix

Context

Added ruff as a dev dependency with ruff-lint quality check. Built custom unreachable-code detection in the Python AST linter using tree-sitter. Restructured the pypi test runner to probe where pytest is declared and build the correct uv run invocation per project (workspace member vs standalone). validate_release_targets now validates against the union of all releasable member targets. Removed silent error swallowing in member version sync and dead-code convention fallback.

#Features

  • New feature. Unreachable code detection in the Python AST linter -- flags code after unconditional return/raise and after exhaustive if/elif/else blocks.
  • New feature. detect_uv_workspace_root utility for detecting whether a project is a uv workspace member.
  • New feature. ruff-lint quality check runs ruff on the project during rlsbl check --tag quality. Skips gracefully when ruff is not installed.
  • New feature. Pypi test runner detects where pytest is declared per project (dependency groups, optional extras, uv dev deps) and builds the correct uv run invocation. Hard error when pytest is not declared. Workspace members preserve existing behavior.

#Fixes

  • Bug fix. Config read errors in member version sync now propagate to the release rollback handler instead of being silently skipped.
  • Bug fix. detect_python_package_root no longer returns a nonexistent convention path -- returns None when no package directory exists on disk.
  • Maintenance. Removed unused imports and variables across the codebase (ruff F401/F841 cleanup).
  • Bug fix. validate_release_targets now validates against the union of all releasable member targets, not just the representative member.

#0.86.0

AST-based __version__ bumping, dunder-version-missing check, and releasable test runner fix

Context

The __version__ bumping system was rewritten from regex to AST parsing, eliminating an entire class of missed version bumps (typed annotations, pre-release strings). A new dunder-version-missing check catches the pgdesign bug class where a version constant exists under the wrong name. detect_python_package_root gained uv build-backend and src/ layout support. The releasable test runner now dispatches the correct test command per member target type.

#Features

  • New feature. detect_python_package_root now supports uv build-backend module-root config, src/ layout filesystem detection, and raises a hard error on ambiguous package layouts.
  • New feature. __version__ bumping rewritten with AST parsing — now correctly handles typed annotations (__version__: str = "1.0.0") and pre-release version strings.
  • New feature. New dunder-version-missing project check detects pypi targets where __init__.py has a version-like constant (e.g., VERSION = "1.0.0") but no __version__.

#Fixes

  • Bug fix. Releasable test runner now detects the correct target type per member package instead of using the releasable-level registry for all members.
  • Bug fix. Member package version sync errors now propagate to the release rollback handler instead of being silently swallowed as warnings.

#0.85.0

Hotfix release type, TYPE_CHECKING import detection, ci-synced dev_node skip, root .rlsbl coexistence guard.

#Features

  • New feature. if TYPE_CHECKING: imports are now excluded from both deps-undeclared and deps-unused checks, eliminating false positives for typing-only imports.
  • New feature. bump = "hotfix" release type for infrastructure-only releases. Bypasses the user-facing entry requirement, generates a ### Hotfix changelog section from the release description, and errors if user-facing entries are present.
  • New feature. root-rlsbl-conflict check errors when both .rlsbl/ and .rlsbl-monorepo/ exist at the workspace root. Scaffold now skips entirely at workspace roots.

#Fixes

  • Fix. workspace-ci-synced check no longer fails for dev_node projects missing CI workflows.

#0.84.0

Go companion tags, workspace root CI fix, uvNoSources lint, BaseTarget fix.

#Features

  • New feature. Automatic Go companion tags during releasable releases. When a releasable contains non-private Go packages, {path}/v{version} tags are created alongside the primary releasable tag for Go module proxy compatibility.
  • New feature. Release rollback and release undo now clean up companion Go tags alongside the primary release tag.
  • New feature. go-companion-tags workspace check validates that Go releasable members have companion tags for the current version.

#Fixes

  • Fix. BaseTarget.template_vars() returns TemplateVars instead of plain dict, ensuring auto-namespacing works for all targets including those inheriting the base implementation.
  • Fix. {{#if uvNoSources}} conditional in pypi CI template now uses namespaced form {{#if pypi.uvNoSources}} for consistency with all other template conditionals.
  • Fix. Scaffold no longer generates CI workflows at monorepo workspace roots. The ci-router already handles per-package CI; root-level CI with import checks always failed.

#0.83.1

Fix release finalization leaving deleted unreleased.toml uncommitted.

#Fixes

  • Fix. Release finalization now properly commits the deletion of unreleased.toml after archiving, preventing a dirty working tree.

#0.83.0

Remove publish.json, releasable-owned targets, no empty release files, monorepo release subgroup.

Context

Breaking changes: publish.json config file removed (unused by all projects, 4-level inheritance simplified to 2-level). Release finalization no longer creates empty unreleased.toml. Monorepo release commands restructured into a subgroup (monorepo release -> monorepo release run, release-init -> release init, release-order -> release order). In explicit releasable mode, targets are releasable-owned and cannot be overridden by per-package config.

#Breaking

  • Breaking. Removed publish.json config file support. All config fields now live in config.json only. The 4-level inheritance chain is now 2-level (releasable config.json -> per-package config.json). The migrate-publish-config command is removed.
  • Breaking. Release finalization no longer creates an empty unreleased.toml. Run release init or monorepo release init to scaffold the next release file.
  • Breaking. Monorepo release commands restructured: monorepo release -> monorepo release run, monorepo release-init -> monorepo release init, monorepo release-order -> monorepo release order.

#Features

  • New feature. In explicit releasable mode, targets are owned by the releasable — per-package targets config cannot override the releasable's targets. Fixes release init failing to detect targets when members have targets: [].

#0.82.1

TemplateVars auto-namespacing, check-name polish, template fixes.

#Features

  • New feature. TemplateVars dict subclass auto-generates namespaced template variable keys, ensuring all scaffold modes (single-target, multi-target, monorepo sync) resolve variables consistently.

#Fixes

  • Fix. check-name --target github no longer shows redundant 'GitHub repos' in the Checked summary.
  • Fix. Monorepo check-names --target help text now includes github.

#0.82.0

Check system scope adapter, detect_targets config inheritance, template var hard errors, workspace-targets fix, check-name --target github, batch release bug fixes.

Context

Major architectural changes: declarative scope adapter for the check system (requires strictcli 0.21.0), detect_targets() now uses 4-level config inheritance with a two-tier rule (missing targets key is a hard error when config.json exists), and unresolved template variables are now hard errors instead of silent pass-through.

Breaking: projects with .rlsbl/config.json that lack an explicit "targets" key will now get a hard error with auto-detected hints. Template vars that previously survived as literal {{varName}} in output files will now error at scaffold/sync time.

#Breaking

  • Unresolved template variables are now hard errors. Scaffold and sync operations raise ConfigError instead of emitting a warning when template variables cannot be resolved. Namespace mismatches in cargo/go/npm templates fixed; optional variables wrapped in {{#if}} blocks.

#Features

  • Release init warning for explicit-mode monorepos. release init now warns when run in a monorepo with [[releasables]], directing users to monorepo release-init instead.
  • **check-name --target github support.** GitHub is now a first-class target for name availability checks, showing repo count and org-scoped availability.
  • **detect_targets config inheritance with two-tier rule.** Target detection now supports 4-level config precedence via read_project_config (releasable config dir for monorepo inheritance). Two-tier rule: no config.json means auto-detect from manifests; config exists without targets key raises ConfigError with hints.
  • Scope adapter for check system. Checks now declare scope tokens (e.g. workspace, non_dev_only, library) in checks.toml, and a scope adapter pre-filters context before check functions run. Removes isinstance boilerplate and inline filtering from all check implementations. Also adds a new scaffold-gitignore-stale check that warns when project .gitignore files are missing rlsbl-specific entries.

#Fixes

  • Batch release fix. Skip redundant environment validation (gh auth, clean tree, branch/remote checks) per sub-project in batch release mode -- the batch orchestrator already validates upfront.
  • **check-name no longer leaks GitHub API calls.** Previously, every npm/pypi/go name check also ran an unconditional GitHub lookup. GitHub checks now only run when --target github is specified.
  • **workspace-targets check improvements.** Skip dev_only and releasable=false projects in per-project target validation. Add union-per-releasable verification: each releasable must have at least one target across all its member packages.
  • Fix. sync_workspace skips uv sync when no pyproject.toml exists at workspace root, fixing pre-push failures in non-Python monorepos.

#0.81.7

Add 120s subprocess timeouts to Maven lint and all test runners

#Fixes

  • Add subprocess timeouts. Maven lint and all test runners now have 120-second timeouts, preventing the release pipeline from hanging indefinitely on slow or stuck subprocesses.

#0.81.6

Fix PyPI scanner false positives for prefixed package names

#Fixes

  • Fix PyPI scanner false positives. deps-undeclared no longer reports false positives when a workspace project's PyPI name differs from its workspace name (e.g., orxtra-transport vs transport).

#0.81.5

Fix CI: autouse saferm mock for scaffold tests

#Fixes

  • Fix CI test failures. Autouse saferm mock fixture applied to all scaffold tests.

#0.81.4

Fix CI: mock saferm in all scaffold tests

#Fixes

  • Fix CI test failures. All scaffold tests now mock saferm for CI environments.

#0.81.3

Fix CI: mock saferm in scaffold orphan tests

#Fixes

  • Fix CI test failure. Scaffold orphan detection tests now mock saferm for CI environments.

#0.81.2

Fix coverage check for releasable monorepos, Maven lint support, gradle lockfile sync, batch_limits inheritance

#Features

  • Maven lint support. lint_library() now runs detekt, checkstyle, or ./gradlew check for maven library projects via the new MavenLinter class.

#Fixes

  • Fix coverage check ignoring releasable JSONL entries. rlsbl check --tag changelog from the workspace root now correctly reads releasable-level changelogs instead of falling back to per-package paths. Also prevents crash on implicit-mode workspaces.
  • Fix gradle lockfile sync. Lockfile sync now checks for ./gradlew in the project directory instead of requiring gradle on PATH.
  • Batch limits inherit from releasable config. rlsbl changelog add now respects releasable-level batch_limits settings, and auto-created exclusions are written to releasable-level config when in releasable mode.
  • Fix phantom hashes.json entries. Scaffold no longer leaves stale entries in hashes.json for config files removed by the releasable config skip.

#0.81.1

Fix CI test failure: mock saferm in migration tests

#Fixes

  • Fix CI test failure. Migration tests now mock saferm for CI environments where it is not installed.

#0.81.0

Config-driven hooks, releasable config inheritance, Maven Central publishing, JVM monorepo support, Go strictcli detection, and migration CLI

Context

This release completes 9 phases of work spanning the full plan: release-blocking bug fixes (lockfile sync, changelog scope, selfdoc dirty tree), Go strictcli detection for 9 projects, releasable-level config inheritance with 4-source precedence, config-driven hooks replacing bash scripts (breaking), migration CLI for releasable model adoption, Maven Central publishing via the vanniktech plugin, JVM dependency graph and import scanning, Gradle version catalog support, and extended artifact cleanup.

Hook scripts are deprecated in favor of config entries. Existing customized scripts still work with a migration warning. New projects will not have hook scripts scaffolded.

#Breaking

  • Config-driven hooks replace bash scripts. Hook commands are now defined in config.json with dual syntax (string shorthand or structured objects with cmd/dir/env). Hook template scaffolding removed. Existing customized scripts still work with a migration warning.

#Features

  • Go strictcli detection. detect_strictcli() now finds Go projects using the strictcli library, scanning go.mod and entry points. Schema dump runs go run instead of uv run for Go projects.
  • Releasable-level config inheritance. Per-package config is now merged on top of releasable-level defaults at load time. Scaffold skips writing per-package config.json/publish.json when identical to the releasable config.
  • **rlsbl monorepo migrate-releasable command.** One-command migration to the releasable model: consolidates changelogs and versions, creates migration tags, cleans up per-package artifacts (hooks, bases, lint, CHANGELOG.md, version, redundant config).
  • Maven Central publishing support. New maven-central pipeline type delegating to the vanniktech Gradle plugin. Publish workflow template with GPG signing. POM metadata validation check. Maven test execution (./gradlew test / mvn test). CI templates updated to Java 25, setup-java@v5, setup-gradle@v6.
  • JVM monorepo support. Gradle/Maven dependency graph parsing for monorepo impact and release ordering. Java and Kotlin import scanning for dependency validation checks. read_metadata implemented for maven targets.
  • Gradle version catalog support. read_version/write_version work with libs.versions.toml via explicit version_catalog_key config. Build step (./gradlew build/mvn package), lint detection (detekt/checkstyle), and gradle.lockfile sync added for maven targets.

#Fixes

  • Workspace root lockfile sync. Release flow now syncs lockfiles at the workspace root for uv, npm, and Go workspaces after version bumps. Go workspace go.work.sum is handled separately with go work sync.
  • Fix multi-releasable changelog scope. Changelog validation in release flow now correctly scopes to the releasable's member projects instead of checking all commits in the repo.
  • Commit selfdoc outputs during release. Selfdoc-generated files are now committed immediately after selfdoc gen with an Autogenerated: true trailer, preventing dirty-tree failures on release retry.

#0.80.0

Batch release improvements: monorepo-wide lock, upfront validation, --watch support, dry-run summary

#Features

  • Batch monorepo-wide lock. rlsbl monorepo release acquires a workspace-level lock before the batch loop, preventing concurrent batch releases from interleaving.
  • Upfront validation for batch release. gh auth, clean tree, and branch checks run once before releasing any package, failing fast on misconfiguration.
  • **--watch/--no-watch for monorepo release.** Watches CI for the last release's commit after the entire batch completes. Per-package watch hints suppressed during batch.
  • Dry-run summary for batch release. Shows each releasable's bump type and description in release order.

#Fixes

  • **Fix: uv build --out-dir dist in publish template.** Ensures build artifacts land in the expected directory for PyPI upload.
  • Fix: shallow clone test failure in CI.

#0.79.2

Fix migration consolidation bugs, CI template variable, and shallow clone test failure

#Fixes

  • Fix: migration consolidation bugs. consolidate_changelogs() now deduplicates cross-package entries, auto-creates batch exclusions for oversized entries, and creates a consolidation tag to prevent scope expansion failures.
  • **Fix: unreplaced minRequiredPython in CI template comment.** Template now uses un-namespaced variable name that works in both single and multi-target scaffold.
  • Fix: status command test failure in CI shallow clones.

#0.79.1

Raise CI coverage threshold to 90% with 4873 tests at 91.78% coverage

#Features

  • CI coverage threshold raised to 90%. Test suite expanded to 4873 tests with 91.78% coverage.

#0.79.0

Fix publish workflow releasable tags, UV_NO_SOURCES for monorepos, namespace package scanning

Context

Three gaps discovered during first real-world use of the releasable model: (1) publish workflow ignored releasable tags, (2) UV_NO_SOURCES broke monorepo CI for workspace-source projects, (3) deps-unused produced false positives for namespace packages. Also adds template engine conditionals, extract/absorb CLI commands, migrate-publish-config command, and removes implicit releasable mode (monorepos must define [[releasables]]).

#Breaking

  • Remove implicit releasable mode. Monorepo workspaces must define [[releasables]] in workspace.toml. Hard error if missing.

#Features

  • Template engine conditionals. {{#if varName}}...{{/if}} blocks for conditional template content.
  • **New command: rlsbl migrate-publish-config.** Splits publishing fields from .rlsbl/config.json into .rlsbl/publish.json.
  • **New commands: rlsbl monorepo extract, absorb, extract-releasable.** First-class operations for moving packages in and out of monorepos.

#Fixes

  • Fix: publish workflow releasable tags. Monorepo publish router uses releasable tag prefixes instead of per-project tags in explicit mode.
  • Fix: UV_NO_SOURCES omitted for monorepo members. CI template conditionally includes UV_NO_SOURCES only for projects with path-based sources. Monorepo sync strips it as a safety net.
  • Fix: namespace package import scanning. deps-unused correctly detects workspace imports through namespace packages (e.g., from orxt.protocols import X). Auto-discovers namespace-to-project mapping from source layout.

#0.78.0

Releasable model: groups of packages sharing a version, changelog, and release lifecycle

Context

Introduces the releasable model — a ground-up redesign of monorepo versioning. Instead of per-package versioning (N versions, N changelogs, N tags), packages can be grouped into releasables that share a single version, changelog, and release lifecycle. The model covers the full stack: data model, version management, changelog scoping, release flow, hooks, scaffold, migration tooling, extract/absorb operations, and single-project unification.

Breaking: dev_node is split into dev_only (boundary guardrail) and releasable = false (not versioned). The --dev-node CLI flag is renamed to --dev-only. The dev-node-boundary check is renamed to dev-only-boundary.

#Breaking

  • dev_node split. dev_node is now two independent concepts: dev_only (boundary guardrail preventing runtime dependencies) and releasable = false (not versioned). The --dev-node CLI flag is renamed to --dev-only. The dev-node-boundary check is renamed to dev-only-boundary.

#Features

  • Releasable model. Packages can be grouped into releasables sharing a version, changelog, and release lifecycle. Define [[releasables]] in workspace.toml and assign packages with releasable = "name" or releasable = false.
  • Per-releasable version files. In explicit mode, each releasable stores its version at .rlsbl-monorepo/releasables/{name}/version. Version consistency checks treat this as the source of truth.
  • Per-releasable changelog. In explicit mode, changelogs live at .rlsbl-monorepo/releasables/{name}/changes/. Commit coverage, validation, and CHANGELOG.md generation are scoped to releasables. Entries gain an optional packages field for per-package attribution.
  • Publishing config separation. Publishing-related fields (targets, private, pipelines, push_timeout, tag) can now live in .rlsbl/publish.json separate from .rlsbl/config.json. Hard error if both files have publishing fields.
  • Release flow on releasables. rlsbl release run creates one commit and one tag per releasable. Batch release iterates releasables in dependency order. Release file supports [releasables.name] sections. Undo, retry, yank, and edit commands adapted.
  • Per-releasable hooks. Two-level hook system: per-releasable hooks at .rlsbl-monorepo/releasables/{name}/hooks/ plus per-package hooks. Tests and lint aggregate across all member packages.
  • Per-package cleanup utilities. cleanup_per_package_release_state() removes dead .rlsbl/changes/ and .rlsbl/releases/ for releasable members. verify_minimal_rlsbl() checks for unexpected files.
  • Scaffold releasable directories. In explicit mode, rlsbl scaffold and rlsbl monorepo sync create per-releasable directory structure. Per-package changelog infrastructure is skipped for releasable members. monorepo add gains --releasable flag.
  • Migration tooling. detect_migration_state() analyzes workspace readiness. consolidate_changelogs() merges per-package changelogs into per-releasable. consolidate_versions() unifies versions. create_migration_tag() creates releasable-format tags.
  • Standalone releasable model. Single-project repos internally use the releasable abstraction. Optional .rlsbl/releasable.toml for explicit configuration.
  • Extract and absorb. cmd_extract() extracts a package or releasable from a monorepo into a new repo via git filter-repo. cmd_absorb() brings an external repo into a monorepo via git subtree add. Changelog migration included.

#Fixes

  • Audit fixes. Release checks use releasable tag format. Prepush checks are releasable-aware. Post-release hooks use multi-level system. Publishing config conflict detection handles edge cases.

#0.77.1

Fix stale commit-level batch exclusion cleanup

Context

clean_stale_exclusions now removes commit-level batch exclusions when all referenced commits have been finalized, matching the existing entry-level cleanup behavior.

#Fixes

  • Bug fix. Stale commit-level batch exclusions are now cleaned from config.json during release finalization, matching the existing cleanup for entry-level exclusions.

#0.77.0

GitError/PostReleaseError exceptions, detached HEAD/shallow clone/push timeout handling, variant delay, dep_validation realpath

Context

Final cleanup round: completed the exception hierarchy (GitError for git infrastructure, PostReleaseError for post-mutation failures), fixed three unhandled edge cases (detached HEAD, shallow clone, push timeout), added rate-limiting delay to variant checks, standardized dep_validation.py on realpath, and triaged the hardening roadmap into focused todos.

#Breaking

  • Breaking. Added GitError and PostReleaseError exception types. Last 2 sys.exit calls in execute.py converted to PostReleaseError.

#Features

  • New feature. check-name variant checks now respect the --delay flag for rate-limiting, using sequential checking instead of concurrent threads when a delay is set.

#Fixes

  • Bug fix. Detached HEAD now raises a clear GitError instead of silently returning "HEAD" as the branch name, which caused dangerous behavior in push_if_needed and release undo.
  • Bug fix. Shallow clones now produce a clear GitError instead of silently falling back to all-history range. Consolidated duplicate tag discovery functions into a single get_last_version_tag() in utils.py.
  • Bug fix. Push timeout now raises GitError with an actionable message instead of a raw Python traceback.
  • Bug fix. Standardized dep_validation.py path normalization on realpath, consistent with the rest of the codebase. Fixes potential mismatches with symlinked project directories.

#0.76.0

Split release.py into a package, ExemptionRegistry, path normalization, orphan messaging

Context

The 1985-line release.py monolith was split into 6 modules with clean boundaries. 27 sys.exit calls converted to a proper exception hierarchy. The 24-parameter _run_release_mutating signature replaced with a ReleaseState dataclass. Changelog exemptions centralized into an ExemptionRegistry. Path normalization standardized on realpath. Orphan check messaging improved with entry context.

#Breaking

  • Breaking. Split release.py (1985 lines) into a 6-module release/ package: __init__.py (orchestrator), execute.py, validate.py, hooks.py, publish.py, rollback.py. Replaced the 24-parameter _run_release_mutating signature with a ReleaseState dataclass. Converted 27 sys.exit calls to ReleaseValidationError/HookError exceptions.

#Features

  • New feature. Centralized changelog exemption logic into ExemptionRegistry with ordered predicates and per-rule unit tests. Fixed batch exclusion hash matching (abbreviated hashes now resolve to full SHAs).

#Fixes

  • Bug fix. Changelog orphan check now reports entry context (file, line, how many commits are valid) instead of bare hash errors. Detects partially stale entries where all commits are either unresolvable or out of range.
  • Bug fix. Standardized path normalization on realpath across root discovery, containment checks, and lint exclusion. Fixes potential mismatches when project directories are accessed through symlinks.

#0.75.1

Fix workspace test runner uv sync

Context

The workspace test runner was running uv sync per sub-project instead of once at the workspace root, breaking monorepos with cross-project dependencies.

#Fixes

  • Bug fix. Workspace test runner now runs uv sync --all-packages at the workspace root instead of per sub-project, fixing monorepo test suites with cross-project dependencies.

#0.75.0

Normalization collision detection, ultranorm by default, claim-name command, exit codes

Context

check-name was reporting names as available when registries would reject them due to normalization collisions (e.g., llmloop vs llm-loop on PyPI, toolstream vs tool-stream on npm). This release fixes the false positives, adds the claim-name command for definitive name reservation, and introduces proper exit codes.

#Breaking

  • Breaking. Removed _normalize_npm_moniker internal function (replaced by normalize_npm from targets/utils). Removed the --ultranormalized-variants flag (ultranorm is now always-on for PyPI).

#Features

  • New feature. check-name now detects normalization collisions as hard errors. Names like llmloop that collide with llm-loop on PyPI, or toolstream that collides with tool-stream on npm, are correctly reported as taken instead of a soft warning.
  • New feature. Ultranormalization (visual-ambiguity detection like c1i/cli) now runs by default for PyPI. The dead --ultranormalized-variants flag and its misleading tip have been removed.
  • New feature. check-name exit codes now distinguish results: 0 for available, 1 for taken/collision, 2 for error.
  • New command. rlsbl claim-name <name> --target npm|pypi publishes a minimal placeholder package to definitively reserve a name on a registry.

#Fixes

  • Bug fix. check-name npm moniker search failure is now a hard error instead of silently reporting the name as available.

#0.74.1

Fix first release on empty remote

Context

When a project had a configured remote but no commits had been pushed yet, the remote-ahead check would abort because origin/{branch} did not exist. The check now detects this and skips gracefully.

#Fixes

  • Bug fix. Release no longer aborts on first push to an empty remote. When origin/{branch} does not exist after a successful fetch, the remote-ahead check is skipped with an informational message instead of hard-aborting.

#0.74.0

Blog post integration for releases, richer hook environment variables, and assembly support in post-release hooks.

Context

Adds blog = true support in release TOML to auto-generate blog posts via selfdoc during releases. Enriches hook env vars with bump type, previous version, and description. Adds conditional selfdoc assembly push to the post-release hook template for unified documentation deployment.

#Features

  • Blog post integration. New blog = true field in release TOML generates a blog post during release via selfdoc post generate --from-release. Supports user-authored body file, changelog integration, and archival.
  • Richer hook environment. Pre-checks, pre-release, and post-release hooks now receive RLSBL_BUMP_TYPE, RLSBL_PREV_VERSION, and RLSBL_DESCRIPTION environment variables.
  • Assembly support in post-release hooks. Post-release hook template now conditionally runs selfdoc assembly push when assembly config is present.

#0.73.0

Changelog edit command, dev_node release prohibition, config hardening, and coverage reporting

#Breaking

  • Breaking. Dev node projects can no longer be released. rlsbl release run and rlsbl release edit now error with "dev_node projects cannot be released". Remove dev_node = true from workspace.toml to make a project releasable.

#Features

  • New command. rlsbl changelog edit amends entries in released (read-only) JSONL files -- temporarily unlocks the file, appends the entry, re-locks, regenerates CHANGELOG.md, and syncs GitHub Release notes.
  • New. CI now reports test coverage via pytest-cov.

#Fixes

  • Fix. The private config key is now required -- missing or corrupted config files produce a hard error instead of silently continuing.
  • Fix. Retry gh release create with race-condition detection when the release already exists.

#0.72.0

Silent exception audit, release robustness fixes, dev-node batch guard

Context

Systematic audit replaced silent exception swallowing with explicit warnings or hard errors across all commands. Release undo and create now handle GitHub API failures gracefully. Dev-node projects in batch releases now hard-error instead of silently skipping.

#Breaking

  • Dev-node batch release guard. rlsbl monorepo release now hard-errors if a dev_node project is included in a batch release, instead of silently skipping it.

#Fixes

  • Silent exception audit. Replaced silent exception swallowing across all commands with explicit warnings or hard errors. Safety guardrails now fail loudly instead of silently bypassing checks.
  • Fix CheckResult status. Use 'fail' instead of 'error' to comply with strictcli constraints.
  • Graceful release failure handling. rlsbl release run now handles gh release create failures gracefully instead of crashing.
  • Undo pre-check. rlsbl release undo now checks that the GitHub Release exists before attempting to delete it.

#0.71.1

Release undo traceback logging, per-version .md canonical regeneration, test isolation fixes, and CI JUnit XML reporting.

#Fixes

  • Fix. release undo error handlers now log tracebacks instead of silently swallowing exceptions.
  • Fix. Per-version .md files are now regenerated from the archived release .toml after finalization, ensuring metadata is sourced from the canonical file.

#0.71.0

Scaffold-conflict check consolidation, scope-aware deps-unused, release finalization fixes, and dev_node layer exemption.

Context

Post-0.70.0 cleanup wave driven by loose ends from the previous session plus cross-session review. The two overlapping scaffold-conflict checks merged into one (paired-marker detection, recursive .rlsbl/ scan, path:line reporting). deps-unused became scope-aware: guarded imports satisfy only optional deps, so a hard dependency imported only inside try/except ImportError is now flagged. Release finalization gained two fixes: backfilled per-version .md files are committed (no more dirty tree after release) and the new version's .md gets description/context from day one. Layers auto-exempt dev_node projects. All 14 selfdoc SEO warnings eliminated. test_check.py migrated off unittest.TestCase.

#Breaking

  • Check consolidation. The scaffold-conflict-markers check is removed; scaffold-conflicts absorbs it: it now scans all of .rlsbl/ recursively, reports each conflicted file as path:line, and no longer false-positives on bare ======= lines (e.g. markdown setext underlines).

#Features

  • Custom asset build commands are now echoed to stdout before execution, providing an audit trail of the exact shell command run during release asset builds.
  • New check. scaffold-conflicts errors when scaffold-managed files or CI workflows contain unresolved merge conflict markers, blocking releases until resolved.

#Fixes

  • Fix. release run no longer errors when push_timeout is unset; it defaults to 120 seconds as documented.
  • Fix. release run re-run after a partial failure could silently overwrite an already-finalized changelog file; it now aborts with a clear error.
  • Fix. Advisory lock files (.rlsbl/lock) are now gitignored by scaffold, so a crashed release can no longer dirty the working tree.
  • Fix. release undo left the release file finalized: the bump type, description, and context vanished from unreleased.toml and a stale read-only vX.Y.Z.toml remained; undo now restores it (preserving any new user content with a warning).
  • Fix. Releases no longer leave a dirty working tree: backfilled older per-version changelog .md files are now included in the finalization commit, and the new version's .md now contains the release description and context.
  • deps-unused is now scope-aware. Imports inside try/except ImportError only satisfy optional deps (scope dev/peer); a hard dependency (runtime/explicit) imported only inside a guard is flagged with a dedicated error suggesting to declare it optional or import it unconditionally.
  • Fix. get_push_timeout now raises ConfigError instead of ValueError for invalid RLSBL_PUSH_TIMEOUT or push_timeout config values.
  • Fix. The layers-violations check now auto-exempts dev_node projects; they no longer need manual layer assignments.
  • Fix. rlsbl deploy --dry-run now displays local_steps before remote steps, mirroring execution order.
  • Docs. Fixed stale references to the old monolithic checks.py after the split into the checks/ package.

#0.70.0

Custom exception hierarchy, checks.py modularization, import scanning fixes, deploy local_steps, and 9 truncation removals.

Context

Codebase investigation surfaced 50 curiosities; 13 were elevated to decisions and executed across 11 phases. The biggest structural changes: 105 bare ValueErrors replaced with 5 typed exception classes, and the 1,978-line checks.py split into a 6-module package. Import scanning got two correctness fixes (guarded imports for deps-unused, root-relative test context detection). The deploy module gained local_steps for pre-SSH commands like cross-compilation. All silent truncation was purged (9 instances). Test suite gained xdist (96s -> 27s), parametrization, and real CI runs.

#Breaking

  • Custom exception hierarchy replaces ValueError. rlsbl now raises ConfigError, WorkspaceError, ChangelogError, VersionError, and ReleaseFileError instead of bare ValueError. Code that catches ValueError from rlsbl APIs must update to catch the new types.
  • Ultranorm variant cap is now a hard error. check-name with highly-ambiguous names now errors instead of silently warning and continuing with incomplete results.

#Features

  • npm CI templates handle missing lockfiles. Templates now use npm ci when a lockfile exists and fall back to npm install when absent. Zero-dependency wrapper packages are auto-detected and skip CI generation.
  • npm wrapper improvements. bin/cli.js now verifies Python >= 3.11, checks rlsbl is installed (with clear error messages), and supports Windows via python3/python/py fallback.
  • Deploy: local_steps field. Deploy targets can now run local commands (cross-compilation, file transfers) before SSH-based remote steps.
  • target-count selfdoc directive. New directive dynamically generates the release target count with a link to the targets documentation page.

#Fixes

  • Remove silent data truncation. Changelog commit messages and batch exclusion reasons are no longer silently truncated at 72/60 characters.
  • Remove display truncation caps. Unreleased subjects, pre-push missing commits, OTA native files, orphan hashes, and discover descriptions now show full content instead of capping at arbitrary limits.
  • Changelog regeneration preserves release metadata. Descriptions and context from archived release .toml files are now read during CHANGELOG.md regeneration, fixing data loss on subsequent regenerations.
  • deps-unused no longer flags guarded imports. Imports inside try/except ImportError blocks now count as used, fixing false positives for optional dependencies.
  • Test context detection uses root-relative matching. src/test/ is no longer falsely classified as test code. Added testdata/ and integration_test/ patterns.
  • BFS and topological sort use correct data structures. Workspace graph BFS uses deque (O(1) dequeue) and topological sort uses heapq instead of re-sorting.
  • Spec docs corrected. Fixed incorrect claim that spec target has no ci_templates.
  • Deploy docs corrected. Fixed config key from deploy_targets to deploy.

#0.69.2

Deno publish idempotency, orphan sweep script

#Features

  • Feature. Deno publish template is now idempotent with JSR version-exists pre-check.

#0.69.1

Scrub tag matching, cmd_amend fix, strictcli public API, todo cleanup

#Fixes

  • Fix. Release scrub correctly identifies which project a monorepo tag belongs to using prefix matching instead of version-only lookup. Also caches workspace loading (5 calls reduced to 1).
  • Fix. changelog amend uses writable_jsonl context manager, preventing file permission leak if duplicate commit check exits early.

#0.69.0

Managed-files registry, lint config filtering, publish idempotency, monorepo path exemption

#Features

  • Feature. Scaffold now tracks generated files in .rlsbl/managed-files.json and automatically deletes orphans on re-scaffold. Hash comparison protects user-modified files. Separate from hashes.json (change detection).
  • Feature. Scaffold only generates lint configs for declared targets (pypi→python.toml, npm→npm.toml, go→go.toml). Unused configs are orphan-detected on next scaffold.
  • Feature. All publish workflow templates are now idempotent (safe to retry). PyPI uses skip-existing, npm/cargo/hex use version-exists pre-checks.

#Fixes

  • Fix. .rlsbl-monorepo/ paths are now auto-exempted from changelog coverage requirements.

#0.68.4

scaffold --force preserves gitignore entries

#Fixes

  • Fix. scaffold --force no longer destroys user-added .gitignore entries. The additive merge (only add, never remove) now applies regardless of --force.

#0.68.3

CI template UV_NO_SOURCES fix

#Fixes

  • Fix. CI template sets UV_NO_SOURCES=1 env var at workflow level, covering both uv sync and uv run commands.

#0.68.2

CI uses --no-sources for portable uv sync

#Fixes

  • Fix. CI template uses uv sync --no-sources to ignore local path overrides and resolve from PyPI in CI. Works correctly in both standalone projects and monorepo workspaces.
  • Fix. test-suite check skips gracefully at workspace root instead of hard-erroring.

#0.68.1

Per-target CI files, YAML indent fix, monorepo sync update

#Features

  • Feature. Multi-target projects now generate per-target CI workflow files (ci-pypi.yml, ci-go.yml, etc.) instead of a single ci.yml from the primary target. npm wrapper packages (no test script) skip CI generation entirely. Old ci.yml is automatically cleaned up on re-scaffold.
  • Feature. Monorepo sync handles per-target CI files: globs ci-*.yml from sub-projects, syncs each with project-name prefix, cleans up stale files.

#0.68.0

release scrub command, V4 hook removal

#Breaking

  • Breaking. Removed pre-push-check command. Run rlsbl scaffold to upgrade your hook to the current format (rlsbl check --tag prepush).

#Features

  • Feature. New rlsbl release scrub command wraps safegit scrub with automatic release metadata cleanup: remaps JSONL changelog hashes, regenerates CHANGELOG.md, force-pushes rewritten history, and recreates GitHub Releases for affected tags. Includes error recovery via scrub-result.json.

#Fixes

  • Fix. Merged publish.yml now uses correct 6-space indentation for GitHub Actions steps, matching the standard 2-space-per-level convention. Previously, multi-pipeline projects got 4-space indentation due to missing ruamel.yaml indent configuration.

#0.67.1

V4 shim workspace fix, check-name multi-target, zero SEO warnings

#Features

  • Feature. Comprehensive documentation update: rewrote pre-push hook section, added test-suite-workspace docs, monorepo pre-push behavior, deps-unused note, and eliminated all SEO warnings (was 104, now 0).

#Fixes

  • Fix. Deprecated pre-push hook (old format) now creates proper workspace context at monorepo root, enabling all prepush checks including test-suite-workspace.
  • Fix. check-name --target now supports multiple values. Passing --target npm --target pypi checks both registries instead of silently ignoring the first. Invalid target names produce a hard error.

#0.67.0

Monorepo pre-push support, test-suite-workspace check, release rollback cleanup

#Features

  • Feature. New test-suite-workspace check runs tests for affected workspace projects during pre-push. Detects changed files from push refs, maps to projects via watch patterns, skips dev_node projects.

#Fixes

  • Fix. Pre-push hook now works from monorepo root directory. test-suite check hard-errors in workspace context with a clear message directing to test-suite-workspace.
  • Fix. Release rollback now cleans up orphaned generated files (versioned JSONL, markdown, release TOML) that previously blocked retry with 'working tree not clean'.

#0.66.4

Dynamic check-count directive, quality count fix, exclusion cleanup integration test

#Features

  • Feature. New check-count selfdoc directive auto-generates the check count from checks.toml, eliminating manual count maintenance in docs.

#0.66.3

deps-undeclared respects try/except ImportError for optional imports

#Fixes

  • Fix. deps-undeclared check no longer flags imports inside try/except ImportError or try/except ModuleNotFoundError blocks. These are standard Python optional-dependency patterns and are now correctly recognized as intentionally unguarded.

#0.66.2

Auto-cleanup stale exclusions, DRY refactor manual-warning, hook V5 e2e test

#Features

  • Feature. Stale batch_limits exclusions (referencing version: unreleased) are automatically removed from config.json during release finalization.

#0.66.1

Fix uv sync stripping pytest in workspaces, add workspace-unbuildable check

#Features

  • Feature. New workspace-unbuildable check detects workspace members with broken build configs before uv sync --all-packages fails at release time.

#Fixes

  • Fix. uv sync now uses --all-packages to prevent stripping pytest from workspace member venvs. Fixes test runner falling through to system Python in uv workspaces.

#0.66.0

Prepush check system: test-suite check, version-tag skip removal, hook V5

Context

Major overhaul of the pre-push hook system. All pre-push logic migrated into the rlsbl check system under a new 'prepush' tag. The hook now runs 'rlsbl check --tag prepush' with 4 checks (changelog coverage, gitignore guard, manual-push warning, test-suite). Version-tag skip removed -- checks always run. Shared testing.py module extracted from release flow.

#Features

  • Feature. New prepush check tag with 4 checks: prepush-changelog-coverage, prepush-gitignore-guard, prepush-manual-warning, and test-suite. Pre-push hook now runs rlsbl check --tag prepush instead of the deprecated rlsbl pre-push-check. Version-tag skip removed -- all checks always run. Fast-fail ordering via depends_on: test-suite skips if changelog coverage fails.
  • Feature. Extracted shared test-running logic to rlsbl/testing.py. Release flow and test-suite check both use the same function, eliminating duplication.
  • Feature. Updated documentation for prepush check system, fixed stale check counts.

#0.65.4

Fix CI test failure from --yes flag in safegit commit calls

#Fixes

  • Fix. Updated commit command tests to expect the new --yes flag in safegit calls.

#0.65.3

safegit --yes, doc gaps, SEO fixes, retry dedup test

#Features

  • Feature. Passes --yes to safegit commit calls, future-proofing against interactive prompts in safegit.
  • Feature. Documentation expanded: glob pattern reference for layers, user-level config docs, flag-to-config mapping table, enriched module docstrings for SEO, and expanded section intros across 5 pages.
  • Feature. Added concurrent retry deduplication test verifying only one retry fires per workflow name when multiple runs fail in parallel.

#0.65.2

Watch retry dedup, no-runs exit 0, docs polish

#Features

  • Feature. Documentation expanded: new utility commands guide, fixed pipeline per-type inaccuracies, added Flutter release config and deploy env var expansion docs, improved section intros for AI citation.

#Fixes

  • Fix. Watch auto-retry now deduplicates by workflow name, preventing multiple retries when parallel runs from the same workflow fail. Watch exits 0 (not 1) when no CI runs are found, since the release already succeeded.

#0.65.1

Fix CI test failure from batch hint message

#Fixes

  • Fix. Batch size check test updated to account for hint message in failure details.

#0.65.0

Documentation overhaul and notification UX fix

Context

Comprehensive docs rewrite (13 new guide pages, 2600+ lines), notification click-to-open fix, pipeline env var validation ordering fix, and new --allow-batch flag for changelog add.

#Features

  • Feature. Comprehensive documentation overhaul: 13 new guide pages covering release workflow, changelog, scaffold, checks, deploy, dev workflow, import scanning, dependency validation, pipelines, layers, and native targets. Expanded targets, monorepo, and configuration references. Fixed stale check counts.
  • Feature. changelog add --allow-batch flag auto-creates a batch_limits exclusion when an entry exceeds the commit limit, preventing agents from splitting cohesive entries. Batch size check now runs at add-time (not just during release validation).

#Fixes

  • Fix. Desktop notification no longer auto-opens browser unconditionally; URL opens only when the user clicks the notification action button.
  • Fix. Local pipeline env var validation now runs before mutating operations, preventing partial state on missing credentials.

#0.64.2

Fix Docker metadata false positive in scaffold check, add SSH host validation for subtree remotes.

#Features

  • New feature. Validate that subtree remote SSH hosts match the origin remote host, catching misconfigured mirror URLs early.

#Fixes

  • Fix. Exclude Docker metadata-action type=semver,pattern= lines from the scaffold-unreplaced-vars check. These lines use {{version}}, {{major}}, {{minor}}, and {{patch}} as Docker's own template syntax, not unreplaced rlsbl variables.

#0.64.1

Fix dead-modules false positives and watch test notification leak.

#Features

  • New. Auto-retry failed CI workflows once before reporting failure.
  • New. Open GitHub Actions page on CI failure and Release page on success when watch notifications fire.

#Fixes

  • Fix. Exclude .selfdoc, _build, and browser asset directories from dead-modules scan to prevent false positives.
  • Fix. Mock notifications and URL opens in watch tests to prevent desktop notification spam during test runs.

#0.64.0

Monorepo mirror command, native-android and native-ios targets.

#Features

  • New command. monorepo mirror copies monorepo sub-project state (version, changelog, docs) into a standalone mirror repository for independent distribution.
  • New target. native-android release target for Android application projects using Gradle, with automatic mutual exclusion against the maven target to prevent conflicts.
  • New target. native-ios release target for iOS application projects with Xcode and Tuist support.

#0.63.2

Fix CI test failure when selfdoc not installed.

#Fixes

  • Fix. CI test no longer fails when selfdoc is not installed in the test environment.

#0.63.1

Pass RLSBL_DIST_DIR env var to custom_assets build commands.

#Features

  • New feature. Custom asset build commands now receive the dist directory via the RLSBL_DIST_DIR environment variable, eliminating the need for hardcoded paths.

#0.63.0

Pipeline separation: versioning and publishing are now independent concerns.

Context

Breaking: publish() and build_assets() removed from targets, replaced by pipeline types. DocsTarget removed (use cloudflare-pages pipeline). flutter-ios/flutter-android merged into single flutter target. The publish config key is no longer recognized -- use pipelines. New: 9 built-in pipeline types, custom_assets, hooks override built-ins, 5 new checks, pipeline introspect table. Fix: multi-target array bugs in snapshot/graph/status.

#Breaking

  • Pipeline system replaces target.publish(). The publish config key is no longer recognized; use pipelines instead. BaseTarget no longer has publish() or build_assets() methods.
  • DocsTarget removed. Docs deployment is now a cloudflare-pages pipeline, not a target.
  • Flutter targets merged. flutter-ios and flutter-android merged into a single flutter target.

#Features

  • Pipeline infrastructure. 9 built-in pipeline types: npm, pypi, go, cargo, deno, hex, maven, docker, cloudflare-pages.
  • custom_assets support. Arbitrary artifact builds via pipeline config.
  • Hooks override built-ins. Customized pre-release hooks skip built-in tests/lint.
  • 5 new checks. private-publish-workflow, scaffold-conflict-markers, npm-private-mismatch, target-version-readable, selfdoc-version-drift.
  • Pipeline introspect table. Shows registered pipeline types in selfdoc output.

#Fixes

  • Multi-target bug fixes. Fixed snapshot, graph, and status commands to handle multi-target projects instead of taking only the first target.

#0.62.0

Capability-gated publish/build_assets, Swift inheritance cleanup, version_file dir_path, and bug fixes.

Context

Breaking: BaseTarget.build_assets() returns [] instead of raising NotImplementedError. publish() and build_assets() calls are now gated on target.capabilities. SwiftAppleTarget inherits from SwiftTarget. version_file() accepts optional dir_path for dynamic filename resolution. Plain exclusion list fixed. Flutter table cells corrected.

#Breaking

  • Breaking. BaseTarget.build_assets() returns an empty list instead of raising NotImplementedError. Custom targets that relied on the exception should declare build_assets in their capabilities frozenset.

#Features

  • Refactor. SwiftAppleTarget now inherits from SwiftTarget, eliminating 9 duplicated methods.
  • New feature. publish() and build_assets() are now gated on target capabilities. Targets that don't declare the capability are skipped instead of calling no-op methods.
  • New feature. version_file() now accepts an optional dir_path parameter. DenoTarget uses it to resolve deno.json vs deno.jsonc dynamically.

#Fixes

  • Fix. Release confirmation prompt now shows the correct version filename for Deno projects using deno.jsonc.
  • Fix. PlainTarget no longer auto-detects alongside the docs target when both VERSION and selfdoc.json are present.
  • Fix. Flutter targets now show pubspec.yaml (flutter) in the target table instead of --- for detection files.

#0.61.1

Fix feature matrix column order and add directive tests.

#Fixes

  • Fix. Restored original display order for feature matrix columns (was inadvertently alphabetized).

#0.61.0

Target introspection: capabilities, ecosystem, and auto-generated target table.

Context

All 18 release targets now declare capabilities (publish, build_assets, read_name, read_metadata, ci_templates, dev_install), ecosystem labels, and auto-detectability. A new table-targets selfdoc directive auto-generates a comprehensive target details table, replacing the stale manual table. MATRIX_COLUMNS is now derived from TARGETS.keys() for DRY consistency.

#Features

  • Refactor. Feature matrix directive now uses selfdoc.tables.render_markdown_table and generate_feature_matrix_data() returns raw data instead of markdown.
  • New feature. All 18 release targets now declare capabilities, ecosystem, and auto_detectable attributes. Capabilities are a frozenset of strings (publish, build_assets, read_name, read_metadata, ci_templates, dev_install) describing what each target supports. Ecosystem is a human-readable label. Auto-detectable indicates whether the target can be auto-detected from project files.
  • New feature. Auto-generated target details table via :-: table-targets selfdoc directive, showing all 18 targets with ecosystem, detection files, version file, tag formats, capabilities, and dev install commands.
  • Refactor. MATRIX_COLUMNS in checks is now derived from TARGETS.keys() instead of a hardcoded tuple, ensuring the feature matrix stays in sync when targets are added.

#Fixes

  • Docs. Replaced stale manual target table in docs with auto-generated directive. Fixed all target count references across docs and CLI help strings (was 12/14/17, now 18).

#0.60.1

Feature matrix n/a support for toolchain-native checks

#Features

  • Feature matrix. Support "n/a" cells for checks where the toolchain already handles the concern natively (e.g., circular-deps for Go modules).

#0.60.0

Go workspace dependency validation, circular dependency detection, Dart dead-module detection, and feature matrix selfdoc directive.

#Features

  • Go workspace dependency validation. GoImportScanner enables deps-unused, deps-undeclared, deps-runtime-test-only, and deps-dev-in-lib checks for Go projects.
  • Circular dependency detection. New circular-deps quality check detects intra-package circular import chains using Tarjan's SCC algorithm. Per-language severity: Python=warn, npm=error. Go is skipped (compiler catches cycles).
  • Dart dead-module detection. The dead-modules check now supports Dart projects, finding unreachable files via entry-point reachability from pubspec.yaml barrel files and bin/ scripts.
  • Feature matrix directive. New table-feature-matrix selfdoc directive auto-generates a language support table in docs.

#0.59.1

Fix false-positive deps-undeclared findings in root-project dep scan when sibling project source files are present.

#Fixes

  • Fix. Root-project dep scan no longer produces false-positive deps-undeclared findings from sibling project source files.

#0.59.0

Unified manifest detection, PlainTarget auto-detection, monorepo sync template resolution, required template variables, and fixes for sub-directory scaffold operations and release-file commit exemption.

#Features

  • PlainTarget auto-detection. VERSION-only projects are now auto-detected without opt-in configuration.
  • Unified manifest detection. Target detection files are now derived from the target registry as a single source of truth, eliminating hardcoded manifest lists.
  • Required template variables. process_template now errors on missing critical variables at scaffold time instead of silently leaving placeholders.
  • Monorepo sync template resolution. monorepo sync now resolves template variables in per-project CI workflows instead of copying them verbatim.

#Fixes

  • Scaffold auto-commit and hook install from sub-directories. Scaffold auto-commit and pre-push hook installation now work correctly when run from monorepo sub-project directories.
  • Release file commits auto-exempted. Commits that only touch .rlsbl/releases/ files are now automatically exempted from changelog coverage checks.

#0.58.0

New dead-workspace-packages check for detecting unused library packages in monorepo workspaces

#Features

  • New check. dead-workspace-packages detects workspace library packages never imported by any sibling, with specific messaging for test-only imports.

#Fixes

  • Improved. Expanded monorepo release init command description for better selfdoc SEO.

#0.57.1

Fix Python 3.14 compatibility in monorepo release-init

#Fixes

  • Fix. monorepo release-init no longer crashes on Python 3.14 when rendering commented-out package sections. The tomlkit.dumps() call was replaced with tomlkit.item().as_string() to avoid a ValueError when serializing arrays.

#0.57.0

Batch release-init filtering, retry.toml cleanup, and feature support matrix

#Features

  • New. monorepo release-init now accepts a --packages flag to scaffold only specific packages. Packages with zero unreleased commits are automatically commented out with an explanation.
  • New. Feature support matrix: get_feature_matrix() and generate_feature_matrix_markdown() functions showing which checks support which targets.

#Fixes

  • Fix. retry.toml is now cleaned up when validation fails during release retry, in both the init and retry handlers. Previously, an invalid retry.toml could be left behind, blocking subsequent retries.

#0.56.0

New checks (license-file, scaffold-unreplaced-vars, dead-modules-npm), batch release-init improvements, and scaffold/release fixes

Context

LICENSE is no longer scaffold-managed -- projects own their LICENSE file, enforced by the new license-file check. Batch release-init now supports Flutter targets and includes TOML comments. Several fixes for retry.toml cleanup, bare scaffold on plain targets, workspace-unregistered false positives, and shared release config validation.

#Breaking

  • LICENSE removed from scaffold. Projects must now manage their own LICENSE file. The license-file check enforces its presence.

#Features

  • npm dead module detection. New dead-modules-npm check detects unreachable modules in npm packages by tracing entry-point reachability through the dependency graph.
  • New checks. license-file errors if LICENSE is missing, empty, or contains template variables. scaffold-unreplaced-vars detects leftover template variables in scaffold-generated files.
  • Batch release-init improvements. Flutter targets now get per-target config sections. Release files include TOML comments and the context field.

#Fixes

  • Fix retry.toml blocking release. release retry now cleans up the scaffolded retry.toml when validation fails, preventing it from blocking subsequent release run attempts.
  • Fix bare scaffold for plain targets. rlsbl scaffold now reads targets from config.json for already-scaffolded projects that use plain targets, instead of erroring.
  • Fix workspace-unregistered false positives. The check now skips private package.json files and parent directories of registered projects.
  • Shared release config validation. Single and batch release paths now use a shared validator, fixing missing Flutter validation in the batch path.

#0.55.0

Monorepo release-init command, Go dead-modules detection, scaffold and workspace check fixes

#Features

  • New command. monorepo release-init scaffolds a batch release file with per-package sections for all workspace projects.
  • New feature. dead-modules check now supports Go projects. Detects unreferenced internal/ packages via tree-sitter import scanning.

#Fixes

  • Fix. Template sheltering now handles dotted placeholders like {{zig.projectName}}, preventing YAML corruption in multi-target scaffold.
  • Fix. Scaffold with explicit --target now uses the current directory as project root, fixing dev_node detection for plain-target projects in monorepos.
  • Fix. Workspace stale-entries and unregistered checks now recognize all 16 target manifest types plus .rlsbl/config.json as a universal project indicator.

#0.54.2

Batch release description enforcement, scan cache optimization, lint exclusion todo cleanup

#Fixes

  • Fix. Batch release files now enforce mandatory description per package, matching single-release behavior.
  • Fix. Dep quality checks now share a cached import scan instead of scanning 4x per workspace project.

#0.54.1

Make release description mandatory

#Breaking

  • Breaking. Release description is now mandatory in unreleased.toml. Every release must have a short summary.

#0.54.0

Dev node architecture, release descriptions, quality checks, and graph enhancements

Context

The changelog_exempt flag (v0.53) was renamed to dev_node because agents misinterpreted exempt as a desired behavior rather than a graph position. Dev nodes now fully opt out of the changelog system instead of being exempted from enforcement.

#Breaking

  • Breaking. Renamed changelog_exempt to dev_node. Dev node projects no longer participate in the changelog system — no JSONL entries, no CHANGELOG.md, no pre-push enforcement. Release does version bump + tag + GitHub Release (body from release description). Boundary guardrail prevents non-dev-nodes from depending on dev_nodes at runtime.

#Features

  • Internal. Added WorkspaceProject typed wrapper and project field on ProjectContext for structured workspace project access.
  • New feature. Release description and context fields in unreleased.toml. Description appears in CHANGELOG.md under version heading. Context appears as collapsible details block. Description mandatory for dev_node releases.
  • New feature. Three quality checks: deps-runtime-test-only (warning: runtime dep only used in tests), deps-dev-in-lib (error: dev dep imported in lib code), dead-modules (warning: Python modules with zero intra-project references).

#Fixes

  • Fix. Library lint now excludes test and example files by default. Go *_test.go, Python test_*.py/tests/, npm *.test.ts/__tests__/, and examples/ dirs are no longer flagged for stdout usage.

#0.53.0

#Breaking

  • Breaking. Renamed internal workspace flag to changelog_exempt. The new name describes the behavior (changelog enforcement exemption), preventing misuse.

#Features

  • Internal. Added scope field to Dependency namedtuple (runtime/dev/peer/explicit). Reverse dependency graph now supports scope-filtered traversal.

#0.52.0

#Breaking

  • Breaking. All target methods (template_mappings, read_name, write_version) now require ctx parameter (no default). Fixed _is_library CWD bug in template_mappings.

#Features

  • New feature. internal = true workspace flag. Internal projects are fully exempt from changelog enforcement: no JSONL entries needed, pre-push check skips them, no user-facing requirement. Version bumps and GitHub Releases still work.
  • New feature. deps-unused check now scans JS/TS imports via tree-sitter. npm workspace dependencies (workspace:*) are no longer false-positived as unused.

#Fixes

  • Fix. Go target no longer misdetects cmd-layout projects (cmd/*/main.go) as libraries. Scaffold now correctly includes publish.yml and .goreleaser.yml for these projects.
  • Fix. Standalone multi-target publish workflows now inject working-directory, packages-dir, and version-file paths for targets in subdirectories. Refactored to structured YAML.

#0.51.0

#Breaking

  • Breaking. Renamed monorepo_root to workspace_root on ProjectContext. Deleted ProjectCheckContext — all check functions now receive ProjectContext with pre-loaded config.
  • Breaking. All target methods (template_vars, shared_template_mappings, read_name, write_version, publish) now accept ctx: ProjectContext instead of project_root. Fixed latent bugs where dart write_version and docker read_name didn't receive config.

#Features

  • Internal. Added [tool.uv.sources] for editable strictcli install.

#0.50.0

#Breaking

  • Breaking. Config utility functions (should_tag, get_push_timeout, push_if_needed, read_deploy_config, get_publish_config) now accept a config dict instead of project_root. write_project_config returns the updated dict.
  • Breaking. All target publish() methods now accept ctx: ProjectContext instead of project_root. Config is read from ctx.config — no redundant disk reads during publish.

#Features

  • Internal. Extracted shared _rel_to_git_root utility, eliminating duplicate path normalization in release flow.
  • Internal. Added regression test for release abort cleanup (unexpected-files scenario).

#0.49.2

#Features

  • Internal. Commands that don't read config no longer load it at startup. Scaffold commands use ProjectContext for direct config access.
  • Internal. Added release integration tests exercising the full release flow against real git repos (no mocked subprocess calls).

#Fixes

  • Fix. Increased npm check-name timeout from 10s to 30s to reduce CI flakiness on cold-cache runners.
  • Fix. Changelog finalization now uses git-relative paths consistently, matching the version-bump commit's path format.
  • Fix. Monorepo release lock file now uses the correct root path.

#0.49.1

#Fixes

  • Fix. Monorepo release commits now use correct relative paths for version-bumped files.

#0.49.0

#Breaking

  • Breaking. Release, pre-push check, deploy, and undo commands now receive ProjectContext instead of bare project_root. Config is read once at startup via ctx.config instead of re-reading from disk at each call site.

#Fixes

  • Fix. Configuration docs no longer trigger stale-description warnings on every release. The version field is excluded from the selfdoc.json schema table.
  • Fix. Adapted check system bootstrap for strictcli v0.11 API (required app field in checks.toml).

#0.48.2

#Fixes

  • Fix. Lockfile modifications from version bump no longer falsely flagged as concurrent changes during release.

#0.48.1

#Fixes

  • Fix. rlsbl scaffold no longer crashes for Go, Zig, and Docker projects (missing project_root argument in template_vars/shared_template_mappings).

#0.48.0

#Breaking

  • Breaking. All internal APIs now require explicit project_root parameter (no silent CWD fallback). Eliminates the last footgun from the os.chdir removal.

#Fixes

  • Fix. Release aborts (e.g., unexpected dirty files) now revert version-bumped files instead of leaving the working tree dirty. Prevents selfdoc hash oscillation on subsequent releases.
  • Fix. Corrupt .rlsbl/config.json now raises a clear error with the file path instead of silently returning empty config.
  • Fix. Release lock file is now created relative to the project root, not CWD.

#0.47.0

#Breaking

  • Breaking. release init scaffolds bump = "" (was "patch") and release retry scaffolds ref = "" (was the tag). Both fields must now be set explicitly. Scaffolded files include explanatory TOML comments.

#Features

  • New flag. watch --run-id monitors CI runs by ID instead of commit SHA. Accepts multiple values (--run-id 123 --run-id 456). Mutually exclusive with the SHA positional arg.
  • Retry + watch integration. release retry now captures dispatched workflow run IDs and passes them to watch --run-id, so monitoring works correctly even when the dispatch ref differs from the release tag.
  • CI sync rewrite. Monorepo CI sync transforms rewritten from text-based to structured YAML (ruamel.yaml round-trip mode). working-directory is now injected per-job (not workflow-level), correctly handling cross-project CI workflows.
  • Monorepo scoping. status and unreleased now filter commits by project directory in monorepo mode, showing only commits that touch the current project's files.

#Fixes

  • Fix. changelog add in monorepo sub-projects no longer writes to the root JSONL file. Path resolution uses explicit project root instead of CWD.
  • Fix. Config reads (push_timeout, publish config, deploy config, etc.) no longer fail silently after CWD shifts during monorepo releases. All config functions accept explicit project_root.
  • Fix. Release no longer falsely aborts with 'unexpected modified files' when project_root is an absolute path.

#0.46.0

#Breaking

  • Breaking. --update flag removed from scaffold. Bare rlsbl scaffold is now idempotent — creates what's missing, three-way merges what exists. Run it anytime.

#0.45.1

#Fixes

  • Bug fix. Scaffold now creates non-workflow files (like .npmignore) from all targets, not just the primary.
  • Bug fix. Docker publish template no longer has {{version}} replaced by the literal version. Added escape syntax (\{{...}}) to the template engine.
  • Bug fix. Scaffold now uses the correct subdirectory path for npm targets instead of always looking at the repo root.
  • Docs. release retry help text updated to describe dispatch-only behavior.

#0.45.0

#Breaking

  • Breaking. push_timeout must now be explicitly set in .rlsbl/config.json or via RLSBL_PUSH_TIMEOUT env var. The implicit 120-second default is removed.

#Features

  • Improvement. The plain release target now bumps pyproject.toml version when present, keeping it in sync with VERSION.

#Fixes

  • Bug fix. release run now correctly applies directory scoping for monorepo changelog validation, matching rlsbl check --tag changelog behavior.
  • Bug fix. Changelog checks now use the target-specific tag pattern in monorepos (e.g., go/v* for Go targets instead of go@v*).

#0.44.0

#Breaking

  • Breaking. release retry is now dispatch-only. It no longer deletes/re-creates the GitHub Release or re-uploads assets. It dispatches workflows from retry.toml via gh workflow run. Schema simplified to 3 fields: version, dispatch, ref.

#0.43.1

#Features

  • Scaffold. npm targets now get a .npmignore file with sensible defaults, preventing non-npm content from being published.

#Fixes

  • Bug fix. release retry now always dispatches all workflows listed in retry.toml, not just as a fallback when no runs are found. Fixes CI never being re-triggered when Publish triggers naturally.

#0.43.0

#Breaking

  • Breaking. edit-release, undo, and yank are now subcommands of the release group: release edit, release undo, release yank.
  • Breaking. release retry is now file-driven via retry.toml. Auto-scaffolds the file with defaults when not present. The version positional argument is removed.

#Features

  • Improvement. release yank now shows a confirmation prompt before destructive operations, with --yes to bypass.
  • Improvement. release run now detects monorepo root and errors with guidance to use monorepo release.

#Fixes

  • Bug fix. Multi-target scaffold now correctly includes workflow_dispatch in merged publish workflows.
  • Bug fix. selfdoc.json version is now auto-bumped during release even when docs is not in the configured targets. The version-consistency check also detects drift independently of target config.
  • Bug fix. Monorepo changelog coverage now only requires entries for commits touching the package's own files, not all commits in the range.
  • Bug fix. release yank now uses correct monorepo tag format instead of hardcoding v{version}.

#0.42.0

#Breaking

  • Breaking. rlsbl release is now a command group. Use rlsbl release run (was rlsbl release), rlsbl release init (was rlsbl release-init), and the new rlsbl release retry.
  • Breaking. rlsbl release run requires --watch or --no-watch. --watch auto-monitors CI to completion; --no-watch prints the watch hint.

#Features

  • New command. rlsbl release retry re-creates a GitHub Release to re-trigger CI/CD workflows, with asset re-upload and automatic gh workflow run fallback.
  • Scaffold. All generated CI and Publish workflow templates now include workflow_dispatch trigger for manual re-triggering after GitHub Actions outages.
  • Watch. rlsbl watch now suggests rlsbl release retry when no CI runs are found but a GitHub Release exists for the commit.
  • New target. pgdesign release target for database schema projects.

#Fixes

  • Bug fix. rlsbl check commands no longer crash in monorepo context with WorkspaceGraph.__init__() missing required argument.

#0.41.7

#Features

  • New feature. Releases are now blocked when there are no user-facing changelog entries. Shown as a warning during rlsbl check, hard error during rlsbl release.
  • New feature. changelog add and changelog amend now detect duplicate commits -- hard error when the same commit appears in an entry with matching type, warning when types differ.

#Fixes

  • Fix. DocsTarget version sync now also updates the last entry in selfdoc.json versions array, preventing VER003 lint failures after release.

#0.41.6

#Features

  • New feature. Run selfdoc gen --no-commit before selfdoc check during release, ensuring docs are regenerated from source before validation.

#Fixes

  • Fix. selfdoc check failures now print a clean error message instead of a raw Python traceback.

#0.41.5

#Fixes

  • Fix. Detect npm moniker collisions for separator-free names. Previously, check-name pgspec --target npm reported "available" even though pg-spec exists and npm considers them identical. Now generates insertion variants (inserting -, ., _ at every position) and checks each. Also fixed dot separator handling -- dots are now treated identically to dashes and underscores, matching npm's actual normalization.

#0.41.4

#Fixes

  • Fix: selfdoc hashes no longer left dirty after release. Content hashes are refreshed after the version bump so the working tree stays clean post-release.

#0.41.3

#Features

  • DocsTarget is now a versioned target. Reads and writes version from selfdoc.json. The version-consistency check now includes docs targets instead of skipping them.

#0.41.2

#Features

  • Go assets use goreleaser for cross-compilation. When goreleaser is installed, build_assets() produces binaries for linux/darwin/windows x amd64/arm64. Falls back to host-only go build when goreleaser is unavailable.

#0.41.1

#Breaking

  • **Breaking: .claude/settings.json removed from scaffold templates.** Claude Code does not require this file. Projects with customized hooks keep their existing file.

#Features

  • Config validation and migration. Release flow now validates publish.<target>.assets config. scaffold --update auto-detects missing private key. New config-schema check (29 checks total). Removed get_max_asset_size_mb() implicit default.

#Fixes

  • **Fix: library-lint check skips non-library projects.** Standalone projects no longer produce 523 spurious lint errors in rlsbl check --all.

#0.41.0

#Breaking

  • **Breaking: private config key is now required.** No implicit default. Set "private": true for private repos or "private": false for public repos. Private repos are blocked from publishing to public registries.
  • Breaking: private hook template removed. The post-release-private.sh.tpl scaffold template is deleted. Asset upload is now a built-in release step configured via publish.<target>.assets. A new private-hook-stale check detects legacy private hooks.
  • Breaking: CLAUDE.md removed from scaffold templates. Projects using selfdoc root file templates should manage CLAUDE.md via docs/_CLAUDE.md. Scaffold no longer creates or updates CLAUDE.md.
  • **Breaking: run_cmd legacy signature removed.** The release command no longer accepts run_cmd(registry, args, flags). Use ReleaseConfig from rlsbl.release_file instead.

#Features

  • **New command: changelog amend.** Amend historical changelog entries in released JSONL files. Unlocks the file, appends the entry, re-locks, regenerates CHANGELOG.md, and syncs GitHub Release notes via edit-release.
  • **Per-target build_assets().** New target method for building distributable artifacts. Implemented for PyPI (uv build), npm (npm pack), Go (go build), and Cargo (cargo build --release).
  • Release asset upload. Configure publish.<target>.assets: true and max_asset_size_mb to build and upload artifacts to GitHub Releases during rlsbl release. Size guard aborts if artifacts exceed the limit.
  • Auto-dump strictcli schema during release. Projects with strictcli in their dependencies automatically run --dump-schema during release. Changed .strictcli/schema.json is included in the release commit.
  • Selfdoc check runs earlier in release pipeline. Moved from after lint to after pre-checks hook, catching staleness errors before tests run.

#Fixes

  • Fix: noop commit warnings eliminated. Monorepo sync, monorepo snapshot, and validation cache no longer warn when nothing changed. Prints a clear skip message instead.
  • **Fix: release-init no longer refuses empty files.** An empty or whitespace-only unreleased.toml left after finalization no longer blocks release-init.
  • Fix: version-consistency check no longer fails on DocsTarget. Targets with no version file (like docs) are skipped in the version comparison.

#0.40.1

#Fixes

  • Fix. CI workflow updated for the check to check-name rename.

#0.40.0

#Breaking

  • **Breaking: removed --skip-tests, --skip-lint, --skip-docs, --skip-remote-check, and --no-tag flags from the release command.** Tests, lint, and remote checks now always run. Selfdoc check is automatically skipped when docs target is excluded in the release file. Only --allow-dirty remains as a release-specific flag.

#Features

  • selfdoc root file templates. README.md and CLAUDE.md are now auto-generated by selfdoc from docs/_README.md and docs/_CLAUDE.md templates. Edit the templates, not the generated files.

#0.39.0

#Breaking

  • Breaking: file-based releases. rlsbl release now reads from .rlsbl/releases/unreleased.toml instead of CLI args. Run rlsbl release-init to scaffold the file. The file declares bump type, included/excluded targets, and per-target config.
  • **Breaking: rlsbl doctor, monorepo lint, and changelog validate removed.** Replaced by the unified rlsbl check command. Use rlsbl check --all, rlsbl check --tag workspace, or rlsbl check --tag changelog.
  • **Breaking: --dry-run, --yes, --quiet are now global flags.** They apply to all commands. Both rlsbl --dry-run release and rlsbl release --dry-run work.
  • Breaking: PyYAML replaced with ruamel.yaml. Projects extending rlsbl's YAML handling must use the ruamel.yaml API.
  • Breaking: rlsbl check (name availability) renamed to rlsbl check-name to make room for the unified check system.

#Features

  • Unified check system. 27 checks migrated to strictcli's @app.check() framework. Run rlsbl check --all, filter by tag (--tag project, --tag changelog, --tag workspace, --tag release), or by name (--name lock). Supports dependency ordering, JSON output, dry-run, and verbose modes.
  • Dart target and workspace scanner. New dart target type: reads/writes pubspec.yaml version field with round-trip formatting preservation. DartScanner detects intra-workspace dependencies in Dart monorepos.
  • Flutter iOS/Android targets with Shorebird OTA. Separate flutter-ios and flutter-android targets with platform-specific tags. Release file mode field (ota/build) controls release type. Native file detection blocks OTA when platform code changes. Build number (+N) management opt-in via config.
  • **monorepo graph command.** Export the workspace dependency graph as JSON, DOT (Graphviz), or indented text. Filter with --root (transitive deps), --reverse (transitive rdeps), and --depth.
  • **monorepo snapshot command.** Generates .rlsbl-monorepo/snapshot.json summarizing all packages, versions, deps, and graph structure. --check mode for CI staleness detection. Auto-regenerates during monorepo releases.
  • Architectural layer rules. Define [layers] in workspace.toml with ordered layers and glob-based package assignments. The layers-violations check enforces dependency direction. Supports unrestricted packages, forbidden targets, and explicit cross-layer allowances.
  • Dependency-import validation. New deps-unused and deps-undeclared checks scan source files (Python via tree-sitter, Dart via regex) to verify declared dependencies match actual imports. Whitelist via .rlsbl-monorepo/dep-overrides.toml.
  • **monorepo impact command.** Analyze blast radius of changes: input a package name, file paths, or git range (--since). Outputs direct/transitive dependents, test scope, and release candidates in JSON or text.
  • Coordinated multi-package releases. monorepo release reads a batch release file (.rlsbl-monorepo/releases/unreleased.toml) and releases packages in topological order. New deps-stale check detects outdated intra-workspace constraints. Advisory constraint propagation after each release.

#0.38.2

#Fixes

  • Fix. Publish router no longer fails for plain-target releases. Added a no-op job so GitHub Actions workflows succeed even when all conditional publish jobs are skipped.

#0.38.1

#Features

  • Hook-generated files included in release commit. Files modified or created by pre-checks and pre-release hooks are now automatically detected and included in the release commit, instead of being left dirty.

#Fixes

  • Fix PyPI build for subdirectory targets. uv build --out-dir no longer produces a doubled path (e.g. pypi/pypi/dist) when the pypi target lives in a subdirectory.

#0.38.0

#Breaking

  • Monorepo publish router inlines jobs. monorepo sync now inlines each sub-project's publish steps directly into the root publish.yml instead of calling per-project reusable workflows via workflow_call. This fixes PyPI Trusted Publishing (OIDC), which rejects tokens from reusable workflows. Per-project source workflows stay on: release (standalone-ready). Old {name}-publish.yml wrappers are auto-deleted on sync.

#Features

  • Publish router hash cache. monorepo sync now caches SHA256 hashes of per-project publish workflows in .rlsbl-monorepo/publish-cache.json and skips router regeneration when nothing changed.

#Fixes

  • **Fix Zig npm-wrapper needs: dependency.** build_npm_publish_jobs() no longer hardcodes needs: [goreleaser] -- Zig projects now correctly use needs: [build-and-upload].
  • **Fix monorepo sync self-reference with path=".".** Projects with path="." no longer wrap the generated router as their own publish workflow.

#0.37.0

#Features

  • New feature. Auto-sync lockfiles during release. After version bump, rlsbl detects uv.lock, package-lock.json, and go.sum, runs the appropriate sync command, and includes modified lockfiles in the release commit.

#Fixes

  • Fix. Restore path-based changelog-only commit exemption alongside the Autogenerated trailer check. Manual commits touching only .rlsbl/changes/ or CHANGELOG.md are now correctly skipped during coverage validation.
  • Fix. Skip gitignored lockfiles during release lockfile sync. Previously, syncing a lockfile that was in .gitignore caused git add to fail and aborted the release.

#0.36.1

#Fixes

  • Fix. Monorepo target detection now resolves project paths relative to the monorepo root, not the current directory. Fixes changelog validation and status reporting when running from inside a sub-project.
  • Fix. uv build and uv publish now run from the sub-project directory in monorepo mode, fixing build failures for PyPI targets.

#0.36.0

#Features

  • New feature. Asset size guard in private post-release hook -- checks files in dist/ against max_asset_size_mb (default 2MB) before uploading to GitHub Releases.

#0.35.1

#Features

  • Cleanup. Changelog commit exemption now uses only the Autogenerated: true trailer; old path-pattern fallback removed.

#Fixes

  • Bug fix. Release finalization now generates the per-version .md file alongside the JSONL.
  • Bug fix. Release flow no longer false-positives on files written by hooks, lint, or changelog generation.

#0.35.0

#Features

  • **rlsbl release warns about stale changelog entries in monorepo mode.** During finalization, entries referencing commits outside the unreleased range are flagged to stderr (warn-only; no entries stripped) so out-of-range entries don't silently carry into the next release cycle.
  • **Template action versions now use {{action "name"}} placeholders.** GitHub Action versions in scaffold templates are resolved at scaffold time from rlsbl/data/action_versions.toml, eliminating duplication between the table and 60+ template references. Updates to action versions now require editing only the table.

#Fixes

  • Release flow no longer crashes when extracting the changelog entry. Fixed a regression introduced in v0.34.0 where the in-memory preview of generate_changelog used ## Unreleased as the heading but entry extraction looked for ## X.Y.Z, returning None and crashing later in GitHub Release notes generation. The preview call now passes version_override=new_version so the heading matches.
  • **max_entries_per_commit default raised from 2 to 5.** The previous default 2 was too strict and contradicted the documented practice that one commit can appear in multiple JSONL entries (e.g., fix + feature). Default now matches the existing max_commits_per_entry default of 5.

#0.34.0

#Features

  • Configurable changelog batch-limits validation. rlsbl changelog validate now checks that no entry has more than max_commits_per_entry (default 5) commits and that no commit appears in more than max_entries_per_commit (default 2) entries. Both limits configurable per project via the batch_limits section in .rlsbl/config.json. Known violations can be silenced via an exclusions list with a required reason field for audit purposes.

#0.33.0

#Features

  • **rlsbl dev install expanded.** New --global (default) and --venv flags. Added support for hex, deno, zig, and swift targets in addition to pypi/npm/go/cargo. For pypi/npm, --venv installs into the project's local environment (uv sync / npm install).

#Fixes

  • PyPI publish error names both env vars. When the PyPI publish credential is missing and no token_var override is configured, the error message now reads 'no PYPI_TOKEN or TWINE_PASSWORD' instead of hardcoding only PYPI_TOKEN.
  • Em-dash in status warning. rlsbl status's 'commits ahead' warning now uses an em-dash separator (— run) instead of two hyphens (-- run).
  • **Empty release_branches config is now an error.** Previously an explicit empty list silently fell back to ['main','master'] and disabled the manual-push warning. To opt out of the warning, remove the release_branches key entirely; to limit to specific branches, list them.
  • **rlsbl release no longer pollutes the working tree on abort.** Previously, when a pre-mutation check (selfdoc, tests, lint, hooks) failed, the regenerated CHANGELOG.md was left in the working tree and had to be manually reverted. The write is now deferred until after all pre-checks pass.

#0.32.0

#Features

  • Status warning for unreleased commits. rlsbl status now prints a prominent ! N commits ahead of <tag> warning when the current project has unreleased commits since its last tag.
  • Centralized GitHub Actions versions. All scaffolded workflows now read action versions from a single table (rlsbl/data/action_versions.toml), and actions/checkout has been bumped to v6 across every template ahead of the June 2026 Node 20 deprecation.
  • **scaffold --dry-run.** Preview which files would be created, updated, conflicted, or unchanged without writing anything.
  • **scaffold --update regenerates stale pre-push hooks.** Known-old hook contents are recognized by content hash and overwritten with the current template; user-customized hooks are preserved and a unified diff is printed for manual review.
  • Pre-push hook warns on manual release-branch pushes. Pushing to main/master outside rlsbl release now prints a prominent warning (configurable via release_branches in .rlsbl/config.json). The push is not blocked.
  • Config-driven per-target publish gating. A new publish section in .rlsbl/config.json declares local: true|false and token_var overrides per target, replacing ad-hoc env-var-presence checks across all 7 publishable targets.
  • **New rlsbl dev install command.** Locally installs the current project in editable mode based on its target (pypi → uv tool install -e ., npm → npm link, go → go install ./..., cargo → cargo install --path .). Supports --uninstall, and in monorepos: --all, --include, --exclude.
  • **User-owned ci-custom.yml and publish-custom.yml.** Customize CI without three-way merge conflicts by adding your own jobs in a separate workflow file that rlsbl scaffold never touches.
  • **--no-commit flag on rlsbl monorepo init/add/sync.** Consistent with scaffold and changelog; enables batched migrations into a single commit.

#Fixes

  • Go monorepo tag bug fix. Release in a Go monorepo no longer produces malformed tags like auth-gatewayv0.1.0 when the workspace.toml path lacks a trailing slash — the separator is now inserted defensively, and workspace paths are normalized at load time.
  • Monorepo publish workflows now run on release. The generated publish router declares per-target permissions: and secrets: inherit, fixing the startup_failure that affected every monorepo publish run on GitHub Actions.
  • **scaffold --update no longer commits files with merge conflict markers.** Conflicted files are excluded from the auto-commit and listed so they can be resolved manually.
  • Docs target no longer fails when Cloudflare credentials are missing. DocsTarget.publish() gracefully skips selfdoc deploy when CF_ACCOUNT_ID or CF_PAGES_API_TOKEN is absent, instead of producing a hard subprocess error on every release.
  • **Autogenerated: trailer uses git's --trailer flag.** rlsbl-created commits now record the trailer via git commit --trailer instead of embedding it in the commit message body. This makes the trailer correctly parseable by git interpret-trailers and other git tooling, which previously couldn't read the embedded form.

#0.31.0

#Breaking

  • Breaking. Go targets in monorepos now use path-based tags (go/v0.1.1) instead of name-based ([email protected]), matching Go module proxy requirements. All tag discovery and matching updated.

#Features

  • Feature. Go CLI projects automatically run go install after release to update the local binary.
  • Feature. selfdoc check runs automatically during release when selfdoc.json exists. Skip with --skip-docs.

#Fixes

  • Fix. Commits touching only changelog infrastructure files (.rlsbl/changes/, CHANGELOG.md) are again exempt from coverage, even without the Autogenerated trailer.
  • Fix. Release flow now rolls back locally on push failure: deletes the tag, resets commits, and prints recovery instructions. Branch and tag are pushed in a single git push command.
  • Fix. rlsbl undo now handles the two-commit release pattern (version bump + changelog finalize), reverting both and restoring JSONL state.

#0.30.1

#Features

  • Feature. All commit_files() calls now default to autogenerated=True, ensuring every rlsbl-generated commit carries the Autogenerated: true trailer.
  • Feature. Pre-push hook now blocks pushes when unreleased.jsonl, .validated, or CHANGELOG.md are gitignored.

#Fixes

  • Fix. .validated cache is no longer gitignored; tracked per project rules.
  • Fix. Pre-push hook now reads all JSONL files (unreleased + versioned) for coverage, fixing release pushes being blocked after changelog finalization.

#0.30.0

#Breaking

  • Breaking. Changelog coverage exemption now uses the Autogenerated: true git trailer instead of file-path pattern matching and commit-message regexes. Auto-generated commits must carry this trailer to be exempt.

#Features

  • New command. rlsbl commit commits files with an Autogenerated: true git trailer, marking them as auto-generated for changelog coverage exemption.
  • Feature. Changelog finalization, rlsbl changelog add, and rlsbl changelog generate auto-commits now carry the Autogenerated: true trailer.
  • Feature. Pre-push hook skips JSONL coverage check when pushing a version tag (standalone v* or monorepo name@v*).

#0.29.1

#Fixes

  • Fix. Release no longer aborts when the .validated changelog cache is updated during validation.

#0.29.0

#Features

  • New command. rlsbl monorepo lint detects unregistered projects and stale workspace entries.

#Fixes

  • Fix. Pre-push hook now recognizes monorepo release commit messages and filters coverage checks to only commits affecting each sub-project.
  • Fix. Release commit now includes all version files modified by targets (__init__.py for PyPI, build.zig.zon for Zig, dynamically-chosen file for Maven).
  • Fix. Built-in tests, lint, and hook scripts now run from the correct sub-project directory in monorepo mode.
  • Fix. Changelog validation, rlsbl status, and rlsbl changelog validate now use project-scoped tags (<name>@v*) in monorepo mode.
  • Fix. rlsbl undo now correctly finds project-scoped tags and matches monorepo release commit messages.

#0.28.1

#Fixes

  • Fix: status applies changelog-only exemptions. rlsbl status now excludes changelog-only commits from the JSONL coverage count, consistent with changelog validate. Shows exemption count when applicable.
  • Fix: gitignore uses set-union merge on scaffold update. scaffold --update now appends new entries to .gitignore without three-way merge, eliminating predictable conflicts. No lines are removed or reordered.
  • Fix: backfill script uses CWD as default project root. scripts/backfill_changelog.py now operates on the current directory instead of hardcoding rlsbl's own root. Accepts --project-root for explicit override.

#0.28.0

#Breaking

  • Revert: strict type validation removed. Changelog entry types are freeform strings again. Non-standard types render under "Other" in generated CHANGELOG.md. Formalization deferred until real-world usage patterns emerge.

#Features

  • **changelog generate auto-commits.** Generated CHANGELOG.md and per-version .md files are now auto-committed after generation. Use --no-commit to skip.

#Fixes

  • **Fix: monorepo publish workflow renamed to publish.yml.** The monorepo publish router was named publish-router.yml, which broke PyPI Trusted Publishing OIDC claims when projects moved between standalone and monorepo layouts. Now always publish.yml in both contexts.
  • Fix: monorepo sync handles trailing slashes, version-file paths, and packages-dir. Sync no longer produces double-slash globs from workspace.toml paths with trailing slashes. Action inputs like go-version-file are rewritten to include the sub-project path. pypa/gh-action-pypi-publish gets packages-dir injected when working-directory is set.
  • **Fix: monorepo publish workflow renamed to publish.yml.** The monorepo publish router was named publish-router.yml, which broke PyPI Trusted Publishing OIDC claims. Now always publish.yml in both standalone and monorepo contexts.
  • Fix: monorepo sync handles trailing slashes, version-file paths, and packages-dir. Sync no longer produces double-slash globs. Action inputs like go-version-file are rewritten for sub-project paths. pypa/gh-action-pypi-publish gets packages-dir injected.

#0.27.0

#Breaking

  • Breaking: JSONL changelog required. rlsbl release now requires .rlsbl/changes/ to exist. The manual CHANGELOG.md heading check fallback has been removed. Run rlsbl scaffold --update to set up JSONL changelogs.
  • Breaking: old lint.toml ignore format removed. The deprecated flat ignore key in .rlsbl/lint.toml is no longer supported. Use per-language config in .rlsbl/lint/python.toml etc.
  • **Breaking: get_* method aliases removed from BaseTarget.** Target implementations now use bare method names (version_file, template_dir, etc.) matching the ReleaseTarget protocol.
  • Breaking: lint returns empty results when no language detected. Previously defaulted to Python scanning; now requires a language marker file (pyproject.toml, go.mod, package.json).

#0.26.1

#Fixes

  • Fix: Watch CI hint points to the correct commit. The release command now captures the pushed SHA before running post-release hooks, so rlsbl watch targets the release commit instead of a post-hook auto-commit.

#0.26.0

#Features

  • **rlsbl yank command.** Deprecate or delete past releases. Soft yank (rlsbl yank v1.2.3) marks the GitHub Release as a pre-release with a deprecation notice. Hard yank (rlsbl yank v1.2.3 --hard) deletes the GitHub Release and its git tag entirely.
  • **PyPI release bumps __version__.** Python source files containing __version__ are now updated alongside pyproject.toml during release, keeping runtime version introspection in sync.
  • Strict type validation for JSONL entries. Changelog entries with user_facing: true now require type to be one of feature, fix, or breaking. Unrecognized types are rejected during validation and changelog add.

#Fixes

  • Fix: scaffold stays in sub-project directory. In monorepos, rlsbl scaffold no longer walks up to the repo root when project files already exist in the current directory. Previously this caused scaffold to operate on the wrong project.

#0.25.4

  • No user-facing changes.

#0.25.3

#Fixes

  • Fix: tag-based range for changelog validation. Unreleased commit detection now uses <last_tag>..HEAD instead of origin/main..HEAD. This correctly answers "what hasn't been released" rather than "what hasn't been pushed," and works regardless of branch naming convention or remote configuration.

#0.25.2

  • No user-facing changes.

#0.25.1

#Fixes

  • Fix: built-in lint only runs on library projects. The pre-release lint check no longer runs on CLI applications, avoiding false positives for print() calls and entry points. Lint runs only for monorepo projects with library = true.
  • Fix: CHANGELOG.md regenerated after JSONL finalization. The release command now regenerates CHANGELOG.md after renaming unreleased.jsonl to the versioned file, so the changelog heading shows the version number instead of "Unreleased".

#0.25.0

#Features

  • Structured JSONL changelog. Changelog entries are now backed by structured JSONL files in .rlsbl/changes/. Each entry maps a description to commit hashes with user_facing and type fields. rlsbl changelog add/validate/generate commands manage entries. rlsbl release auto-validates coverage and generates CHANGELOG.md. Pre-push blocks on missing coverage. rlsbl unreleased uses exact hash matching. rlsbl status shows coverage info. Scaffold creates the directory for new projects. changelog add auto-commits entries (with --no-commit opt-out). Coverage validation skips changelog-only commits. Internal commit logic centralized via commit_files helper. Pre-push hook compatible with strictcli argument handling.
  • Changelog backfill script. scripts/backfill_changelog.py migrates an existing CHANGELOG.md into JSONL files by parsing entries, mapping them to commits via keyword matching, and writing per-version JSONL files.

#Fixes

  • Fix: selfdoc CLI documentation. Replaced incompatible code-help directives with auto-generated CLI docs via selfdoc gen, fixing the build warning after the strictcli migration.
  • Fix: monorepo commands commit without safegit. monorepo init, add, and sync now properly fall back to plain git when safegit is not installed, instead of silently skipping the commit.
  • Fix: pre-push hook compatibility with strictcli. The pre-push hook no longer passes extra arguments that strictcli rejects. Release pushes (containing a version bump commit) skip the JSONL coverage check entirely since validation already ran during rlsbl release.

#0.24.0

#Features

  • **rlsbl edit-release [version].** New command that updates GitHub Release notes from CHANGELOG.md. Auto-detects the current version if none is given. Supports --dry-run to preview without changes.

#0.23.1

  • No user-facing changes.

#0.23.0

#Breaking

  • Breaking: hooks are now scaffold-managed. pre-release.sh and post-release.sh are no longer user-owned. scaffold --update merges template improvements into existing hooks via three-way merge.
  • **Breaking: rlsbl init removed.** The init alias for scaffold has been dropped. Use rlsbl scaffold directly.
  • **Breaking: --registry flag removed.** Use --target instead. The --registry flag was deprecated since 0.20.0.

#Features

  • Per-command help. CLI dispatch migrated to strictcli. Every command and monorepo subcommand now has its own --help with typed flags, arguments, and descriptions.
  • Multi-ecosystem library lint. Library boundary lint now supports Go and npm in addition to Python. Go lint detects forbidden imports (net/http, cobra, urfave/cli), fmt.Println, and func main(). npm lint detects forbidden imports (express, koa, commander, etc.), console.log, and bin entry points.
  • AST and regex lint backends. Each language has two lint implementations: tree-sitter AST (accurate, default) and regex (lightweight fallback). Select via parser = "regex" in .rlsbl/lint.toml.
  • Per-language lint config. Lint rules are now configured in .rlsbl/lint/python.toml, go.toml, and npm.toml with [forbidden-imports], [stdout], and [entry-point] sections. Scaffold generates these with sensible defaults. The old lint.toml ignore list is deprecated; a warning is shown if detected.
  • Built-in pre-release checks. rlsbl release now runs tests and lint automatically before releasing. No hook customization needed for standard checks. Skip with --skip-tests or --skip-lint.
  • Two-hook model. New pre-checks.sh hook runs before built-in checks (for setup tasks). The existing pre-release.sh runs after (for custom validation).
  • Standalone library lint. rlsbl doctor --check library-lint now works on any project, not just monorepo libraries.

#Fixes

  • Fix: lint false positives from vendored code. The lint file walker no longer descends into .venv/, node_modules/, __pycache__/, and other non-source directories.

#0.22.1

#Features

  • Name check short-circuiting. When a name is already taken, variant checking and GitHub repo lookups are now skipped, reducing API calls from 6 to 1-2 per taken name. Ultranormalization breaks after the first conflict instead of checking all 64 variants.
  • Reason-specific explanations. Check results now explain WHY a name was rejected: stdlib module conflict, npm moniker collision, or visual similarity. Previously only the status ("taken") was shown.
  • PyPI caveats for available names. Available PyPI names now always show the prohibited names list warning and suggest --ultranormalized-variants when the flag wasn't used.
  • Steps-run summary. Verbose check output ends with a "Checked:" line listing which validations ran (registry, stdlib, variants, GitHub, ultranormalization, moniker similarity).
  • Multi-name summary. Batch checks now print an aggregate summary ("N available, M taken") and the delay setting after the table.
  • Rate limit retry visibility. HTTP 429 retries now print "Rate limited, retrying in Ns..." to stderr instead of sleeping silently.

#0.22.0

#Features

  • Library boundary lint. Tag monorepo projects with library = true in workspace.toml (or monorepo add --library true) to enforce library discipline. rlsbl doctor --check library-lint detects forbidden imports (argparse, flask, click, etc.), print() calls, and CLI entry points in library source files via Python AST analysis. Logging usage is flagged as a warning. Violations block releases via the pre-release hook. Supports .rlsbl/lint.toml ignore lists for false positives.
  • **Library column in monorepo status.** Shows which projects are tagged as libraries. Dynamic column — hidden when no projects have library = true.
  • PyPI check: Simple API. Switched from the JSON API to the Simple API for availability checks. Fixes false "available" results for package names that are registered but have no releases.
  • PyPI check: stdlib collision detection. Names that conflict with Python standard library modules (like queue, json, os) are now reported as taken without hitting the network.
  • **PyPI check: --ultranormalized-variants.** New flag that generates visual-similarity variants (l/1/i and o/0 substitutions) and checks each against PyPI. Catches names like cli that PyPI rejects due to collision with cl1.
  • npm check: moniker similarity detection. The npm check now queries the search API to detect moniker conflicts — names that npm considers identical after stripping punctuation. Catches cases like selfdoc conflicting with self-doc.
  • **rlsbl doctor --check <name>.** Run a single diagnostic check by name instead of all checks. Useful in CI and pre-release hooks.

#Fixes

  • Fix: scaffold template variable resolution for secondary targets. Multi-target projects (e.g., pypi+npm) no longer leave {{registryUrl}} unresolved when npm is a secondary target.

#0.21.2

#Features

  • Hook output is now visible. Pre-release and post-release hooks stream stdout/stderr to the terminal in real-time. On failure, the actual exit code is shown instead of a generic error message.
  • **RLSBL_HOOK_TIMEOUT environment variable.** Configure a timeout in seconds for release hooks. Default is no timeout (hooks run to completion), fixing the previous 120-second hard limit that silently killed long-running test suites.

#0.21.1

#Features

  • **rlsbl release --allow-dirty.** Release with uncommitted changes in the working tree. Pre-existing dirty files are tracked and excluded from the unexpected-files safety check, so genuinely new unexpected files are still caught.
  • **monorepo add passes --target to scaffold.** When an explicit target is specified (e.g., --target plain), it is forwarded to the scaffold subprocess instead of relying on auto-detection. Removes a special case where monorepo add pre-created a VERSION file for plain targets.

#0.21.0

#Features

  • **Cross-registry depends_on in workspace.toml.** Monorepo projects can now declare explicit dependencies on siblings in any ecosystem: depends_on = ["framework"]. These edges are respected by release-order, counted in status Deps/Rdeps columns, and shown in outdated with status "explicit". Use monorepo add --depends-on name1,name2 to set them during project registration.
  • **plain target for no-build-system projects.** Register template directories, shared config, or other non-code projects with monorepo add --target plain. Uses a plain VERSION file, creates GitHub Releases with changelog notes, but generates no CI or publish workflows. Standard tag format.
  • **--target flag on monorepo add.** Explicitly specify any target type, bypassing auto-detection. Works for all registered targets, not just plain.
  • Reusable npm binary wrapper. The platform-specific npm package distribution pattern (esbuild/biome/turbo-style) is now a shared module used by both Go and Zig targets. Platform list is configurable via npm_wrapper.platforms in project config.
  • Zig target. New release target for Zig projects. Detects build.zig.zon, manages versions via VERSION file with best-effort .zon sync, generates CI (via mlugg/setup-zig) and publish workflows that cross-compile for 6 platforms from a single runner. Supports optional npm binary wrapper distribution.
  • CI router fix for projects without workflows. monorepo sync no longer includes projects without CI workflows in the CI router, preventing references to non-existent workflow files.

#0.20.0

#Breaking

  • **Breaking: rlsbl check now requires --target.** The implicit default of checking both npm and PyPI has been removed. Specify --target npm, --target pypi, or --target go explicitly.

#Features

  • Multi-name checking. rlsbl check foo bar baz --target pypi checks multiple names in one invocation and prints a compact table. Single-name invocations still show the verbose format with variants.
  • **Rate limiting for rlsbl check.** New --delay flag (default 200ms) throttles between names. HTTP 429 responses from PyPI/Go/GitHub trigger automatic retry with exponential backoff.
  • **rlsbl monorepo check-names.** Batch-check name availability for all workspace projects. Supports --prefix and --suffix to evaluate namespacing strategies (e.g., --prefix www- --target pypi).
  • **rlsbl monorepo outdated.** For each project, shows its intra-workspace dependency constraints against current sibling versions. Reports versioned deps as ok/outdated, and labels workspace/path references.
  • **Dependency columns in rlsbl monorepo status.** New Deps and Rdeps columns show how many workspace siblings each project depends on and how many depend on it. Hidden when no intra-workspace dependencies exist.
  • Monorepo-aware pre-push changelog check. The pre-push hook now detects monorepo context and only checks changelogs for projects whose files appear in the pushed commits.
  • **Per-subcommand help for rlsbl monorepo.** Each subcommand now has its own --help with usage and description.
  • Path dependency rewriting for PyPI. When building a PyPI package in a monorepo, path dependencies (e.g., core @ {root:uri}/../core) are automatically rewritten to versioned constraints in a temp copy. The working tree is never modified.
  • **rlsbl monorepo release-order.** Shows the topological order to release projects in, based on intra-workspace dependencies. Detects cycles.

#0.19.1

  • No user-facing changes.

#0.19.0

#Features

  • **rlsbl deploy [name] command.** Deploy to configured targets via SSH with health checks (HTTP, TCP, script) and automatic rollback on failure. Configure targets in .rlsbl/config.json under the deploy key. Supports --dry-run to preview without executing and --force to override branch restrictions.
  • Deploy CI workflow template. rlsbl scaffold generates a deploy workflow when deploy targets are configured.
  • Project root discovery. rlsbl now finds the project root automatically, so commands work from any subdirectory (like git, cargo, npm).
  • Scaffold untracks gitignored files. rlsbl scaffold runs git rm --cached on tracked files matching new .gitignore entries.
  • **Monorepo: lock files moved to .rlsbl-monorepo/.** rlsbl release no longer creates a spurious .rlsbl/ directory at the repo root.
  • Deploy after publish. rlsbl release runs deploy automatically after publish completes. Deploy failures do not undo the release — retry with rlsbl deploy <name>.

#0.18.1

#Features

  • **env_file config key.** Set "env_file": "~/path/to/.env" in .rlsbl/config.json to load environment variables before release. Useful for secrets needed by target publish steps (e.g., CLOUDFLARE_API_TOKEN for docs deploys).

#0.18.0

#Breaking

  • **Breaking: removed --include and --exclude flags from rlsbl release.** Target selection for releases is now config-only via release_targets in .rlsbl/config.json.

#Features

  • Go binary distribution: Homebrew tap. Go binary projects can now scaffold Homebrew tap support. Set {"homebrew": {"tap": "homebrew-tap"}} in .rlsbl/config.json to add a brews: section to the goreleaser config and pass HOMEBREW_TAP_TOKEN to the publish workflow. Users install via brew install user/tap/tool.
  • Go binary distribution: npm wrapper. Go binary projects can scaffold npm binary wrapper packages (the esbuild/biome/turbo pattern). Set {"npm_wrapper": {"scope": "@user"}} to generate platform-specific packages for 6 platforms (linux/darwin/win32, x64/arm64), a wrapper package with optionalDependencies, a bin script, and a publish workflow that packages goreleaser archives into npm packages. Users install via npm install -g @user/tool.

#0.17.0

#Features

  • Cross-target metadata in templates. Multi-target projects now merge template variables from all targets during scaffold, not just the primary. Variables are namespaced by target ({{pypi.minRequiredPython}}, {{npm.minRequiredNode}}). CI templates include runtime version references from project manifests (requires-python, engines.node, go directive, rust-version).
  • **rlsbl doctor: metadata consistency checks.** Three new checks validate that package name, license, and description are consistent across targets. Names are normalized per registry conventions (npm scope stripping, PyPI PEP 503, Go module path). Mismatches produce warnings, not failures.
  • Per-target subdirectory paths. Targets can now live in subdirectories. Configure with {"name": "npm", "path": "npm/"} in the targets array (plain strings still default to project root). Version sync, build, publish, doctor, status, and scaffold all resolve per-target paths. Useful for projects that ship wrapper packages alongside the main artifact.

#0.16.1

#Features

  • npm scaffold: lockfile warning. rlsbl scaffold now warns when an npm project has no lockfile (package-lock.json, pnpm-lock.yaml, or yarn.lock) and adds a "run npm install" step to the next steps output. Prevents broken CI from npm ci failing on first push.

#0.16.0

#Features

  • **rlsbl doctor command.** Diagnoses release state with 7 checks: stale lock file, version consistency across targets, local/remote tag existence, GitHub Release existence, branch sync, changelog coverage. --fix auto-repairs safe issues (stale locks, missing remote tags, missing GitHub Releases).
  • Lock file hardening. .rlsbl/lock added to gitignore template. atexit handler ensures lock cleanup on process exit. New is_stale() helper for doctor.
  • Go scaffold: goreleaser ldflags. Goreleaser template now includes -X main.version={{.Version}} for version injection at build time.
  • Go scaffold: dynamic goreleaser main field. main: field auto-detects root vs cmd/<name>/ project structure via {{goreleaserMain}} template variable.
  • Go scaffold: version.go generation. Binary Go projects get a scaffolded version.go with ReadBuildInfo fallback for go install users. Libraries are excluded.
  • npm: package manager detection. NpmTarget detects pnpm, yarn, or npm by searching for lock files from the project directory up to the git root.
  • npm: per-manager CI and publish templates. Separate CI and publish workflow templates for npm, pnpm, and yarn. pnpm templates include pnpm/action-setup@v4. All CI templates now include an install step before testing.

#0.15.0

#Features

  • Deleted built-in config migration engine. Removed rlsbl/lib/ (ConfigMigrator, schema_loader, ~440 LOC), the rlsbl config subcommand tree, and all associated tests (~1,500 LOC). Config migrations are now handled by the external migrable tool.
  • **rlsbl migrate command.** Shells out to migrable migrate --config-dir .rlsbl. Supports --dry-run and --status. Gives install instructions if migrable is not found.
  • **--json flag for rlsbl status.** Outputs structured JSON with name, version, target, branch, tag, clean, changelog, ci, publish.
  • Documented hidden flags. --no-commit and --skip-shared now appear in rlsbl scaffold --help.
  • Go root main detection. rlsbl scaffold warns when a Go project has its main package in cmd/<name>/ instead of the project root, since go install module@latest won't work.

#0.14.0

#Features

  • Router watch paths. Projects in a monorepo can declare extra file patterns to watch via --watch on monorepo add or the watch key in workspace.toml. The CI router generates multi-line path filters including both the project directory and all watch entries.
  • Swift target split. New swift-apple target for Apple-platform Swift projects (macOS-only CI). The existing swift target keeps macOS+ubuntu for server-side Swift. swift-apple requires explicit declaration in .rlsbl/config.json (no auto-detection).
  • Subtree publishing. Monorepo projects with subtree_remote configured get automatic git subtree pushes after release. The project's subdirectory is split and pushed to a mirror repo with plain semver tags, enabling SPM consumption. A GitHub Release is also created on the mirror. Failures are non-fatal.
  • Explicit target enforcement. rlsbl scaffold now warns when using auto-detected targets and always writes the detected target to .rlsbl/config.json, ensuring subsequent runs use explicit config.
  • **monorepo status enhanced.** Optional Watch column (path count) and Remote column (subtree URL) appear when projects use these features.
  • **monorepo sync warns for Swift projects** without subtree_remote, since SPM can't resolve monorepo-style prefixed tags.
  • 12 registered targets. swift-apple joins npm, pypi, go, swift, cargo, deno, docker, hex, maven, spec, docs.

#0.13.1

#Features

  • **Monorepo sync adds working-directory.** Synced CI workflows now include defaults: run: working-directory: {path} so steps run in the correct project subdirectory.
  • Monorepo add shows guidance. When no target is detected, the error now suggests which manifest files to create.
  • Dynamic merged publish workflows. Multi-target projects now get dynamically generated publish workflows composed from each target's individual template. Supports all 11 targets, replaces the static npm+pypi+go-only merged template.

#0.13.0

#Features

  • Monorepo support. New rlsbl monorepo command family for managing multi-project repos with independent versioning. Projects remain fully standalone — extracting to a solo repo requires zero config changes. - monorepo init creates a .rlsbl-monorepo/workspace.toml workspace manifest. - monorepo add <path> registers a project (auto-detects target, auto-scaffolds, auto-syncs CI). - monorepo remove <path> unregisters a project. - monorepo list shows all registered projects. - monorepo status shows version, latest tag, target, and unreleased changelog entries per project. - monorepo sync copies per-project CI workflows to root, rewrites triggers to workflow_call, generates a CI router (paths-filter dispatch) and publish router (tag-prefix dispatch), sets copies read-only with source header.
  • Scoped releases in monorepos. rlsbl release inside a monorepo project automatically prefixes tags ([email protected]), scopes version reads/writes and changelog to the project subdirectory, and uses a scoped commit message.
  • **Monorepo-aware rlsbl status.** Shows monorepo tag format and project count hint when inside a monorepo project.
  • Scaffold triggers monorepo sync. Running rlsbl scaffold inside a monorepo project automatically syncs workflows to root.

#0.12.0

#Features

  • 7 new release targets. swift (SPM), cargo (Rust/crates.io), deno (JSR), hex (Elixir/hex.pm), maven (Gradle/Maven), docker, spec (versioned specifications). Each with CI/publish templates and hybrid publish support.
  • Opt-in target config. Projects declare targets in .rlsbl/config.json "targets" array. Auto-detection remains as fallback for existing projects.
  • Hybrid publish. rlsbl release publishes locally when ecosystem token is available (NPM_TOKEN, CARGO_REGISTRY_TOKEN, HEX_API_KEY, etc.). Falls back to CI otherwise.
  • Private registry workflow. rlsbl scaffold --private (or auto-detected) skips publish.yml, generates a post-release hook that uploads artifacts to GitHub Releases. Prints consumer install instructions.
  • tomlkit replaces TOML regex. pyproject.toml editing (version bumps, keyword injection) now uses tomlkit for correct round-trip editing with comment preservation. Fixes edge cases with [project.urls] sub-tables.
  • detect_targets() covers all targets. Auto-detection now finds all 11 registered targets, not just npm/pypi/go.

#0.11.3

#Features

  • **--include/--exclude release flags.** Control which targets run during release. Replaces --skip-docs.
  • **release_targets config.** Declare baseline targets in .rlsbl/config.json to avoid auto-detect surprises.

#0.11.2

#Features

  • Watch re-polls for late-starting workflows. After initial runs complete, waits 5 seconds and re-polls for workflows that started late (e.g., Publish triggered by GitHub Release creation). Fixes missing Publish detection.

#0.11.1

#Features

  • Release aborts if behind remote. Fetches origin before releasing and exits if local branch is behind. Use --skip-remote-check for offline releases.
  • Watch reports which workflows ran. After CI completes, prints a summary table of workflow names and results. Warns if a publish workflow exists on disk but didn't trigger.

#0.11.0

#Features

  • Docs system extracted to selfdoc. rlsbl docs commands removed. DocsTarget now detects selfdoc.json and delegates to the selfdoc CLI. Install selfdoc separately for documentation generation.
  • **rlsbl check includes GitHub repo search.** Shows repo count as informational context (not an availability check) after registry checks.
  • Codehome target removed. The plugin target and rlsbl register command were premature and have been removed pending a more mature design.

#0.10.1

#Features

  • Codehome target rewrite. Now root-scoped: each plugin (or plugin group) lives in its own repo with plugin.json. Standard v1.2.3 tags, no namespacing. Push is delivery.
  • **rlsbl register command.** Prints the JSON registry entry for the current plugin repo (name, repo URL, description, plugins provided).
  • Scaffold template for codehome. rlsbl scaffold --target codehome creates CI workflow that validates plugin.json.
  • Plugin validation. Release validates plugin.json has required fields (name, version, description) and valid semver.

#0.10.0

#Features

  • Codehome plugin target. --target codehome --scope plugins/<name> releases individual plugins from a monorepo. Reads/writes plugin.toml, creates namespaced tags ([email protected]).
  • Docs target. Auto-generate documentation from Python docstrings and deploy to Cloudflare Pages or GitHub Pages. rlsbl docs init/build/serve/deploy commands. Zero external dependencies (stdlib ast + built-in MD/HTML converter).
  • **rlsbl targets command.** Lists all available targets with detection status, scope type, and version file.
  • Multi-target release. Secondary root-scoped targets (e.g., docs) auto-run build+deploy during release. Use --skip-docs to opt out.
  • **--target and --scope CLI flags.** --target selects the release target explicitly. --scope restricts operations to a subdirectory for subdir-scoped targets.
  • Scoped release safety. Validates scope path exists, includes pyproject.toml in commit for plugin targets, warns when --scope is used with root-scoped targets.
  • **--registry deprecated.** Use --target instead. Prints a deprecation warning when used.

#0.9.1

#Features

  • **rlsbl config show subcommand.** Bare rlsbl config now prints help; use config show for project info.
  • Race condition parsing fix. Porcelain parser handles stripped leading whitespace correctly.

#0.9.0

#Features

  • **rlsbl unreleased command.** Lists commits since last tag, cross-references CHANGELOG entries, reports coverage status. Supports --json for machine-readable output.
  • **rlsbl prs command.** Lists open GitHub pull requests for the current repo.
  • Config management system. rlsbl config init/migrate/status subcommands for managing project config with schema-driven migration (deep merge, flat merge, list-by-key merge strategies, versioned migrations, atomic writes).
  • Scaffold auto-commits. Created files are committed automatically (use --no-commit to opt out). Runs config migrations when .rlsbl/config-schema.json exists.
  • Parallel watch. rlsbl watch polls CI runs concurrently (total time = max of all runs, not sum).
  • Parallel variant checking. rlsbl check uses ThreadPoolExecutor for concurrent registry queries.
  • Advisory lockfile. .rlsbl/lock prevents concurrent release/scaffold operations.
  • **rlsbl undo improvements.** Auto-pushes revert commit (with confirmation prompt, or automatic with --yes). Prints structured failure summary table with remediation commands on partial failure.
  • Pre-release suffix support. bump_version handles versions like 1.0.0-beta.1.
  • **--force no longer overwrites user-owned files.** CHANGELOG.md, LICENSE, and hooks are preserved even with --force.
  • **Pre-release hook receives RLSBL_VERSION.** Matches the existing post-release hook behavior.
  • Release race condition fix. Aborts if unexpected files are modified before commit.
  • Top-level error handler sanitized. No longer exposes sensitive CalledProcessError details.
  • Discover hardened. Pagination capped at 20 pages; retries once on HTTP 403 with Retry-After header.
  • **record-gif validates flags.** Clear error message on non-integer flag values.
  • Release prompt mentions ecosystem tagging when enabled.
  • npm check timeout. Variant checking has 10-second subprocess timeout.

#0.8.3

#Features

  • Pre-release hook runs Python checks before npm (faster failure)
  • Go CI template uses go-version-file: go.mod instead of hardcoded versions; adds -race flag
  • Node.js 24 in all CI/publish templates (dropped Node 18 EOL)

#Fixes

  • Fix watch: resolve short SHAs to full 40-char (gh run list --commit requires it)

#0.8.2

#Features

  • Handle KeyboardInterrupt in watch command (clean exit, no stack trace)
  • Escape AppleScript strings in watch notifications (prevents injection via git tags)
  • Clear error when --registry is missing a value
  • Resolve project config path at call time (not module import time)
  • Add --width, --height, --font-size, --duration flags to record-gif

#0.8.1

#Features

  • Templates included in wheel. Moved templates/ into the rlsbl/ package so non-editable installs (pip, pipx) get them. Previously rlsbl scaffold crashed on PyPI installs.
  • **undo checks prerequisites.** Now verifies gh CLI auth and clean working tree before proceeding.
  • TOML trailing comma fix. Adding the rlsbl keyword no longer produces a double comma.
  • **Pagination URL validation in discover.** Only follows Link header URLs pointing to api.github.com.
  • **.rlsbl/version included in release commit.** No more orphaned version marker changes.
  • Non-ASCII preserved in package.json. json.dumps now uses ensure_ascii=False.

#0.8.0

#Features

  • Universal three-way merge for scaffold updates. Replaced all format-specific merge strategies (YAML job-level, JSON deep-merge, line-based, section append) with git merge-file. Bases are stored in .rlsbl/bases/ at scaffold time. On --update, user customizations and template updates merge cleanly; conflicts get git-style conflict markers.
  • **Removed ruamel-yaml dependency.** No longer needed since YAML-aware merging is replaced by three-way text merge.
  • Detailed scaffold output. Every file now shows its action: created, updated, merged, unchanged, user-owned, or CONFLICTS.

#0.7.0

#Features

  • **Removed check-prs command.** Was a useless wrapper around gh pr list.
  • **JSON deep-merge for .claude/settings.json.** Scaffold now merges new template keys into existing user settings instead of skipping the file. User values are preserved.
  • YAML job-level merge for CI workflows. ci.yml and publish.yml are now merged at the job level: rlsbl-managed jobs are updated, user-added jobs are preserved. Uses ruamel.yaml for comment-preserving round-trip parsing.
  • Explicit USER_OWNED category. CHANGELOG.md, LICENSE, and hooks are formally marked as user-owned and never overwritten.
  • LICENSE year update. scaffold --update extends the copyright year range to the current year.

#0.6.0

#Features

  • Scripts moved to subcommands. check-prs.sh, record-gif.sh, and pre-push-hook.sh are no longer scaffolded into scripts/. They are now built-in subcommands: rlsbl record-gif, rlsbl pre-push-check.
  • **rlsbl watch command.** Monitors all CI runs for a commit, prints results to stderr, sends desktop notification, exits 1 on failure. rlsbl release prints Watch CI: rlsbl watch <sha> for easy invocation.
  • Removed built-in background CI watcher from rlsbl release. Use rlsbl watch explicitly instead.
  • **Hooks moved to .rlsbl/hooks/.** pre-release.sh and post-release.sh moved from scripts/ to .rlsbl/hooks/. rlsbl release looks for hooks there.
  • Pre-push hook is a one-liner. .git/hooks/pre-push now calls exec rlsbl pre-push-check "$@" instead of being a full script copy. Updates happen via uv tool upgrade rlsbl, not re-scaffolding.

#0.5.2

#Features

  • Version detection reads source tree first. __version__ now reads pyproject.toml directly when running from source, fixing stale metadata from editable installs.
  • CLAUDE.md template is registry-specific. Publish setup instructions (NPM_TOKEN, Trusted Publishing, GoReleaser) now match the project's registry instead of always showing NPM_TOKEN.
  • Gitignore merge normalizes trailing slashes. *.egg-info/ and *.egg-info are now recognized as duplicates during scaffold merge.
  • **record-gif.sh no longer hardcodes /tmp/.** Uses bare mktemp for portability.
  • **Go check hidden by default in rlsbl check.** Only shown with --registry go. Labels changed from "available"/"taken" to "not found"/"exists" since Go modules use repository paths.
  • **Auth hint on 403 in rlsbl discover.** Suggests gh auth login when rate-limited.

#0.5.1

#Features

  • CI watcher prints to stderr. The background CI watcher now writes results to inherited stderr instead of attempting tty detection. AI agents and terminal users both see CI pass/fail in their output stream. On failure, the GitHub Actions run URL is printed.
  • ***.local-only gitignore pattern.** Scaffolded .gitignore now includes *.local-only. Use a .local-only/ directory or *.local-only suffix to keep files out of version control without per-file gitignore entries.

#0.5.0

#Features

  • Post-release hooks. scripts/post-release.sh runs after a successful release with RLSBL_VERSION env var set. Non-fatal (release is already complete). Scaffolded via rlsbl scaffold.
  • CI watcher. After pushing, rlsbl spawns a background process that watches CI via gh run watch and sends a desktop notification (notify-send on Linux, osascript on macOS) when CI passes or fails.
  • Ecosystem discoverability: rlsbl discover command lists all rlsbl-tagged projects via GitHub topics
  • rlsbl config shows ecosystem tagging status and source
  • --quiet flag is respected by all tagging output
  • Auto-tagging: scaffold and release inject "rlsbl" keyword into package.json/pyproject.toml and add the rlsbl GitHub topic
  • Opt-out via --no-tag flag, project config (.rlsbl/config.json), or user config (~/.rlsbl/config.json)
  • rlsbl discover --mine filters to the authenticated user's repos

#0.4.2

#Features

  • Configurable push timeout via RLSBL_PUSH_TIMEOUT env var (default 120s), fixing timeouts on repos with slow pre-push hooks
  • Print a note when RLSBL_PUSH_TIMEOUT overrides the default

#Fixes

  • Fix own pre-push hook missing VERSION file detection for Go projects

#0.4.1

#Features

  • Go adapter uses VERSION file as version source (not git tags)
  • First release bootstraps from VERSION without bumping
  • Pre-release.sh template auto-detects Go/npm/Python and runs appropriate checks
  • Pre-push hook template supports Go VERSION file
  • GoReleaser NEXT_STEPS clarified (CI handles it, no local install needed)

#0.4.0

#Features

  • Go project support: scaffold with GoReleaser, CI, and publish workflows
  • Version-file-less registries: release skips commit step when version is the git tag
  • Go name availability check via pkg.go.dev
  • Cross-compilation template (linux/darwin/windows x amd64/arm64)

#0.3.1

  • No user-facing changes.

#0.3.0

#Features

  • Confirmation prompt on release (skip with --yes)
  • config command: show detected registries, scaffolding state, workflows, hooks
  • undo command: revert a botched release (deletes tag, reverts commit, deletes GitHub Release)
  • Merged publish workflow for dual-registry projects (scaffold generates one file with both npm + pypi jobs)

#0.2.0

#Features

  • CLI redesign: --registry flag replaces positional registry argument
  • Rename check-name command to check
  • All commands are top-level: rlsbl release, rlsbl check, rlsbl scaffold, rlsbl status

#Fixes

  • Fix astral-sh/setup-uv version (v7, not v8)

#0.1.1

#Fixes

  • Fix astral-sh/setup-uv version (v8 tag doesn't exist, use v7)

#0.1.0

#Features

  • 4 top-level commands: release, status, scaffold, check-name
  • Initial release as rlsbl (renamed from share-it-on)
  • Pure Python (stdlib only, Python 3.11+, tomllib for TOML parsing)
  • Auto-detects registries from project files (package.json, pyproject.toml)
  • Release syncs version across all detected version files
  • Context-aware scaffold: appends CLAUDE.md, merges .gitignore, preserves custom CI
  • Hash-based --update mode detects customized files
  • Pre-release hook, pre-push changelog enforcement
  • Dual-publish CI: npm (token) + PyPI (OIDC Trusted Publishing)
  • Also installable via npm (thin Node wrapper)
Search