rlsbl 0.129.0 → 0.131.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +53 -36
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- <!-- Auto-generated by selfdoc from .stricttools/docs/_README.md — do not edit -->
1
+ <!-- Auto-generated by selfdoc from .strictmetadata/docs/_README.md — do not edit -->
2
2
 
3
3
  <p align="center">
4
4
  <img src="logo.svg" alt="rlsbl" width="336" height="105">
@@ -35,32 +35,34 @@ rlsbl watch <sha> # monitor CI for that release
35
35
 
36
36
  ## Commands
37
37
 
38
- Release orchestration and project scaffolding for npm, PyPI, Go, and [14 more release targets](https://rlsbl.smmh.dev/targets).
38
+ Release orchestration and project scaffolding for npm, PyPI, Go, and [1 more release targets](https://rlsbl.smmh.dev/targets).
39
39
 
40
40
  All commands auto-detect targets (versioning) from project files (`package.json`, `pyproject.toml`, `go.mod`) and pipelines (publishing) from `.rlsbl/config.json`. Targets handle version bumps; pipelines handle where releases are published.
41
41
 
42
42
  | Command | Description |
43
43
  | --- | --- |
44
44
  | `check` | Run project checks registered via the check framework and report results |
45
+ | `failing-checks` | Run project checks and report only error-level failures, exiting nonzero when any exist |
45
46
  | `status` | Display the current project version, branch, latest release, unreleased commit count, and changelog coverage. The latest release comes from the project's release archives and is annotated when this checkout does not contain it. Outputs plain text by default or structured JSON with the --json flag. |
46
47
  | `scaffold` | Generate or update CI/CD workflows, git hooks, changelog, and license files. Safe to run repeatedly -- three-way merges template changes with your customizations. Existing files with no stored merge base are healed from their last scaffold commit before merging. |
47
48
  | `check-name` | Check whether one or more package names are usable. npm and PyPI are queried over the network for availability and for names that collide after normalization; go is an offline check of the Go package name a candidate implies. Each name gets a status of available, taken, invalid (go only), discouraged (go only), or error. Accepts multiple names as positional arguments and waits a configurable delay between networked checks. Exits 0 when every name is available, 2 when any check ended in an error, and 1 otherwise: taken, invalid, and discouraged all exit 1, so a discouraged Go name exits 1 even though Go accepts it. |
48
- | `claim-name` | Claim a name on a package registry by publishing a minimal placeholder package. Runs check-name first, then publishes if available. npm authenticates with NPM_TOKEN when it is set, otherwise with npm's own ~/.npmrc login; PyPI with UV_PUBLISH_TOKEN or PYPI_TOKEN when set, otherwise with the token in ~/.pypirc. With neither, the claim is refused naming both places. No token is ever printed. |
49
+ | `claim-name` | Claim a name on a package registry by publishing a minimal placeholder package. Runs check-name first and publishes only a name the check reports as available: a name reported taken is refused with exit 1, and a check that ended in an error or returned any other status is refused with exit 2. npm authenticates with NPM_TOKEN when it is set, otherwise with npm's own ~/.npmrc login; PyPI with UV_PUBLISH_TOKEN or PYPI_TOKEN when set, otherwise with the token in ~/.pypirc. With neither, the claim is refused naming both places. No token is ever printed. |
49
50
  | `discover` | Search GitHub for repositories tagged with the rlsbl topic and list them. Use --mine to filter results to only your own repositories. Requires the gh CLI to be authenticated. |
50
51
  | `watch` | Poll GitHub Actions CI workflow runs for a specific commit SHA and report pass or fail status. Defaults to HEAD if no SHA is provided. Useful after rlsbl release to monitor the publish pipeline. |
51
- | `pre-push-check` | Removed. This command no longer performs any check: it always exits 1 with instructions. The pre-push hook now runs `rlsbl check --tag prepush` instead, so a repo whose hook still calls pre-push-check needs `rlsbl scaffold` to regenerate it. |
52
+ | `pre-push-check` | Removed. This command no longer performs any check: it always exits 1 with instructions. The pre-push hook now runs `rlsbl failing-checks --hook pre-push` instead, so a repo whose hook still calls pre-push-check needs `rlsbl scaffold` to regenerate it. |
52
53
  | `prs` | List all open pull requests for the current repository using the GitHub CLI. Shows PR number, title, author, and branch for a quick overview of pending work. |
53
54
  | `unreleased` | List the commits between this checkout's nearest release commit and HEAD, and check whether each has a corresponding changelog entry. Outputs a coverage report in plain text or JSON to help prepare the next release. |
54
55
  | `targets` | List all release targets detected in the current project directory, showing which ecosystems (npm, PyPI, Go, etc.) are active based on manifest files found. |
55
56
  | `deploy` | Run the configured deployment pipeline for the project. Supports named deploy targets and dry-run preview of what would be deployed. Branch restrictions are always enforced. |
56
57
  | `commit` | Commit one or more files with an Autogenerated trailer, marking the commit as machine-generated so it is automatically exempted from changelog coverage checks. |
57
- | **release** | Release orchestration commands covering the full release lifecycle. Provides 11 subcommands: run, resume, init, retry, edit, undo, deprecate, yank, scrub, backfill, reconcile. |
58
- | `release run` | Bump version, validate the JSONL changelog, run tests and lint, commit, tag, push, and create a GitHub Release. Reads the bump type (patch, minor, major, or infra) and target selection from .rlsbl/releases/unreleased.toml, which can be scaffolded with rlsbl release init. Supports dry-run preview, --approve-consequential to skip the confirmation prompt in non-interactive contexts, and --allow-dirty to skip the clean working tree check. |
58
+ | **release** | Release orchestration commands covering the full release lifecycle. Provides 12 subcommands: run, resume, init, retry, edit, undo, abandon, deprecate, yank, scrub, backfill, reconcile. |
59
+ | `release run` | Bump version, validate the JSONL changelog, run tests and lint, commit, tag, push, and create a GitHub Release. Reads the bump type (patch, minor, major, or infra) and target selection from .rlsbl/releases/unreleased.toml, which can be scaffolded with rlsbl release init. The release runs in the release checkout, a detached checkout of the release branch's committed tip under the repository's git directory: producers, tests, hooks, the version bump and the release commit all happen there, the branch advances only from the commit the release started at, and only the files the release's own commits change are written into the working tree. An uncommitted change to one of those files refuses the release, naming it; every other uncommitted change is listed and left alone. Supports dry-run preview, which reports those changes instead of refusing, and --approve-consequential to skip the confirmation prompt in non-interactive contexts. |
59
60
  | `release resume` | Resume a previously failed release from where it left off. Reads the in-progress state file (.rlsbl/releases/in-progress.json, or .rlsbl-monorepo/releasables/<name>/releases/in-progress.json for releasable releases), validates that the current branch matches the saved state, and re-enters the release flow, skipping already-completed steps. Re-pins at the current branch tip, so the fix-forward commit and everything else committed while the release was stopped are adopted into it: the tip is pushed as the candidate and re-judged by CI, and the tag lands on what CI verified. An adopted commit the release did not create and the changelog does not describe is refused before any mutation, named with its subject, alongside the changelog add that records it. |
60
- | `release init` | Scaffold a .rlsbl/releases/unreleased.toml file by auto-detecting project targets. The generated file contains a default bump type (patch), an include list of all detected targets, and per-target configuration sections for Flutter targets. |
61
- | `release retry` | Dispatch CI/CD workflows for a completed release via gh workflow run. Reads the dispatch list and ref from .rlsbl/releases/retry.toml, which is auto-scaffolded with sensible defaults if missing. Verifies the GitHub Release exists before dispatching. Each workflow in the dispatch list is triggered against the configured ref (defaults to the release tag). |
61
+ | `release init` | Scaffold a .rlsbl/releases/unreleased.toml file by auto-detecting project targets. The generated file contains a default bump type (patch) and an include list of all detected targets. |
62
+ | `release retry` | Dispatch CI/CD workflows for a completed release via gh workflow run. Reads the dispatch list and ref from .rlsbl/releases/retry.toml, which is written with the release tag as the ref when missing. Verifies the GitHub Release exists before dispatching. Each workflow in the dispatch list is triggered at the release tag; a ref naming anything else, a branch included, is refused before anything is dispatched, because a run started there cannot be tied to the release. |
62
63
  | `release edit` | Sync the GitHub Release notes for a given version with the corresponding CHANGELOG.md entry. Defaults to the current version if none is specified. Use --dry-run to preview changes without updating GitHub. |
63
64
  | `release undo` | Revert a release. Without --version, reverts the latest release (deletes GitHub Release, removes git tag, reverts version bump commit). With --version, reverts a non-latest release if it is provably unpublished (probes registries for evidence, deletes GitHub Release + tag only, un-finalizes changelog). |
65
+ | `release abandon` | Record an abandoned release attempt's version as never released: write its release archive with never_released = true, delete the in-progress state file, and commit the archive with the Autogenerated trailer, as one operation. The version comes from the in-progress state file, or from the version files when that file is gone. The next release bumps from that version when the version files name it, and never releases under it again; the attempt's commits are not reverted. When the in-progress state file names a version already recorded never released, an earlier abandon stopped after writing that archive, and this run finishes it: it commits the archive if the earlier run had not, and deletes the state file. Refuses, before writing anything, when there is nothing to abandon (no in-progress state, and the version files name a version the release record already holds), when the version is already archived as a release, when the version is below the latest release (the version files must name at least the latest release), when the attempt's tag exists locally or on origin with no archive (a tag is evidence of a release: release backfill adopts it, after fetching a tag that is only on origin), and when the attempt's GitHub Release exists with no tag, since that is a published release and release undo applies. |
64
66
  | `release deprecate` | Mark a past release as deprecated. Sets the GitHub Release pre-release flag and prepends a deprecation notice to the release notes. The notice is first recorded in the version's release archive (release_notices) and committed, so every later re-sync of the Release keeps it; a version with no archive is refused. Use --reason to explain why and --use to suggest a replacement version. |
65
67
  | `release yank` | Remove a published version from package registries. Probes each configured target's registry to determine publication status, then executes registry-specific removal: npm deprecate, Go retract, or PyPI manual checklist. Also marks the GitHub Release as pre-release with a yank notice, recorded first in the version's release archive (release_notices) and committed, so every later re-sync of the Release keeps it; a version with no archive is refused before any registry is touched. |
66
68
  | `release scrub` | Scrub sensitive content from git history and update release metadata to match the rewritten commits. Supports 3 modes: match (--pattern), file (--file), or recipe (--recipe). After rewriting, remaps commit hashes in JSONL changelog files, regenerates CHANGELOG.md, force-pushes, re-points the tags, and rewrites each tag's GitHub Release document in place. A Release is never deleted, so a failure mid-step leaves the previous document standing rather than a tag with no Release at all. |
@@ -73,10 +75,10 @@ All commands auto-detect targets (versioning) from project files (`package.json`
73
75
  | `changelog edit` | Modify an existing changelog entry in unreleased or released JSONL files. Finds the entry by commit hash or entry ID, applies field changes (type, description, user-facing status), and rewrites the file atomically. For released files, temporarily unlocks the read-only file, regenerates CHANGELOG.md, and syncs GitHub Release notes. |
74
76
  | `changelog remove` | Delete one entry from a JSONL changelog file, selected by its ULID identifier or by the commits it covers. The file is rewritten atomically without that line; a released version's file is temporarily unlocked, re-locked, and followed by a CHANGELOG.md regeneration and a GitHub Release notes sync. Exactly one entry is removed: a selector matching several is refused with every match named, and a selector matching none is refused too. |
75
77
  | `changelog remap` | Remap stale commit hashes in JSONL changelog files using a mapping of old SHAs to new SHAs. Reads the mapping from a file (--map-file), the safegit rewrite journal (--from-journal), or stdin (--stdin). At least one source is required. Auto-commits with Autogenerated trailer. |
76
- | **monorepo** | Manage monorepo workspaces with multiple independently-versioned projects. Initialize workspaces, add or remove projects, sync CI workflows, check name availability, and analyze dependency graphs. Provides 17 monorepo subcommands: init, add, remove, list, sync, status, check-names, outdated, snapshot, snapshot-check, mirror, graph, impact, extract, absorb, cleanup, rename-releasable. Plus 1 subgroup: release. Supports all 17 release targets in a single workspace.toml (the app help enumerates them). |
78
+ | **monorepo** | Manage monorepo workspaces with multiple independently-versioned projects. Initialize workspaces, add or remove projects, sync CI workflows, check name availability, and analyze dependency graphs. Provides 17 monorepo subcommands: init, add, remove, list, sync, status, check-names, outdated, snapshot, snapshot-check, mirror, graph, impact, extract, absorb, cleanup, rename-releasable. Plus 1 subgroup: release. Supports all 4 release targets in a single workspace.toml (the app help enumerates them). |
77
79
  | `monorepo init` | Create a new monorepo workspace by generating the .rlsbl-monorepo directory and a workspace.toml at the current directory, carrying the mandatory root member whose kind you declare and a [[releasables]] section. This must be run at the repository root before adding individual projects with the add subcommand. Each workspace tracks multiple independently-versioned projects that share a single git repository. |
78
- | `monorepo add` | Register a project directory in the monorepo workspace.toml configuration. The path argument specifies the project's location relative to the repo root. Optional settings cover display name, target registry, inter-project dependencies, releasable membership, registry identity, and flags marking the project as a shared library or a dev-only leaf. A --releasable naming a group [[releasables]] does not declare yet creates it, as absorb creates one for an arriving member: a singleton entry whose tag_format is written out explicitly, derived from the member's primary target scheme unless --tag-format states it. The mirror destination is not among them: it is a releasable-level key, declared in workspace.toml beside the releasable it binds. What CI reacts to is not among them: the router's paths filters are derived from the workspace, never declared per project. |
79
- | `monorepo remove` | Unregister a project from the monorepo workspace.toml by its path. This removes the project entry from the workspace configuration file but does not delete any files, directories, or git history on disk. The project's code remains intact and can be re-added later with the add subcommand if needed. |
80
+ | `monorepo add` | Register a project directory in the monorepo workspace.toml configuration. The path argument specifies the project's location relative to the repo root. Optional settings cover display name, target registry, inter-project dependencies, releasable membership, registry identity, and flags marking the project as a shared library or a dev-only leaf. A --releasable naming a group [[releasables]] does not declare yet creates it, as absorb creates one for an arriving member: a singleton entry whose tag_format is written out explicitly, derived from the member's primary target scheme unless --tag-format states it. The mirror destination is not among them: it is a releasable-level key, declared in workspace.toml beside the releasable it binds. What CI reacts to is not among them: the router's paths filters are derived from the workspace, never declared per project. After writing the entry the command scaffolds the member (unless it already has a .rlsbl/config.json) and runs monorepo sync, then commits what the three wrote as one commit, leaving uncommitted, and naming, a file that had uncommitted changes before the add; if the scaffold, the sync, or the commit fails, the command exits 1 with the working tree as it was before and nothing committed. |
81
+ | `monorepo remove` | Unregister a project from the monorepo workspace.toml by its path. This removes the project entry from the workspace configuration file but does not delete any files, directories, or git history on disk. The project's code remains intact and can be re-added later with the add subcommand if needed. The path must be a member's path as workspace.toml writes it; any other path is refused, naming every member's path. |
80
82
  | `monorepo list` | Display every member registered in the monorepo workspace.toml file, one row each: the member's name, its path relative to the repo root, the releasable it is versioned under (or false when it is opted out of versioning, or -- when it declares none), and the member flags it carries (library, dev-only, test-only). A release target is not among them -- targets are detected from each member's own manifests, never declared in workspace.toml -- and neither is a mirror destination, which belongs to the releasable rather than the member. |
81
83
  | `monorepo sync` | Inline every project's CI jobs into a single generated ci-router.yml (and publish jobs into publish.yml) in the shared .github/workflows directory at the repository root. Jobs are inlined rather than routed via reusable-workflow calls because GitHub rejects workflows that reference 20 or more reusable workflows. Stale per-project workflow copies at the root are removed via saferm. |
82
84
  | `monorepo status` | Show the current version, last release tag, and changelog coverage for every project in the monorepo workspace. Coverage is the real JSONL figure -- the commits since the project's last tag, scoped to the project and minus the exempt ones, rendered covered/tracked with an (N exempted) suffix, or 'no changelog' when the project has no changes directory. A publish-suppressed member's version comes from its releasable's version file, annotated (version file): nothing publishes such a member, so nothing bumps its manifest and the version-consistency check reads the same file rather than the manifest. Provides a quick overview of which projects have pending changes and are ready for their next release. |
@@ -91,16 +93,27 @@ All commands auto-detect targets (versioning) from project files (`package.json`
91
93
  | `monorepo absorb` | Absorb an external repository into this workspace as a releasable. The source's history is rewritten under the destination path and merged in (full history, rewritten paths), its version tags are imported under the destination's tag scheme with one boundary alias at the current version, and its whole release state -- changelog, release archives with their release commits, config and version -- moves into a releasable's state directory with every hash and release commit remapped onto the rewritten commits. Without --releasable a singleton releasable named after the member is created, with its tag_format written explicitly. Nothing is fetched as a tag, so a tag this repository already owns is never moved or deleted; a colliding tag name or version is refused before anything is written. A crashed run is completed by re-running it. Use --dry-run to see the whole plan first. |
92
94
  | `monorepo cleanup` | Remove per-package release-state residue from releasable member packages: .rlsbl/changes/, .rlsbl/releases/, .rlsbl/bases/, .rlsbl/lint/, .rlsbl/version, per-package CHANGELOG.md, and .rlsbl/config.json when identical to the releasable-level config. Per-package hooks/ directories are preserved (live feature), and members whose path is the workspace root are exempt. Deletions go through saferm (audit trail, recoverable) and are committed automatically unless --no-auto-commit is passed. Detect residue first with `rlsbl check --name releasable-residue`. |
93
95
  | `monorepo rename-releasable` | Rename a releasable group. Rewrites the [[releasables]] name and every member's releasable field in workspace.toml (preserving comments), moves the state directory, drops the stale changelog validation cache, re-runs monorepo sync, and commits it all as one commit. That commit carries no Autogenerated trailer, so changelog coverage decides whether it needs an entry: when tag_format contains {name} the commit changes the publish workflow and coverage asks for one, and the closing message prints the runnable rlsbl changelog add --type breaking line, changing into one of the releasable's members first; a name-only rename touches only files rlsbl owns, needs no entry, and the closing message says nothing about the changelog. When tag_format contains {name}, a boundary alias tag for the current version is created at the old tag's commit and pushed; historical releases stay under the old prefix, and each one's archive records that tag in shipped_as so reconcile and release edit/deprecate/yank resolve it there. Idempotent: re-running heals a crash between the commit and the tag push, and records shipped_as on the past releases of a releasable renamed before the rename recorded it. |
96
+ | **monorepo release** | Release commands for monorepo workspaces. Provides 3 subcommands: run (batch release), init (scaffold release file), order (topological release order). |
97
+ | `monorepo release run` | Execute a batch release of multiple monorepo packages in topological order. Reads package configurations from .rlsbl-monorepo/releases/unreleased.toml. Each package is released sequentially using the single-package release flow, with leaves (no dependencies) released first. The whole batch runs in the release checkout, a detached checkout of the release branch's committed tip, as rlsbl release run does: an uncommitted change to a path the batch writes (the workspace's release state, a member's version files, the workspace changelog) refuses it, naming the path, and every other uncommitted change is listed and left alone. Supports --dry-run, which reports those changes instead of refusing, and --approve-consequential. |
98
+ | `monorepo release init` | Scaffold a batch release file for the workspace's releasables by auto-detecting each releasable's release targets and generating one configuration section per releasable. Creates .rlsbl-monorepo/releases/unreleased.toml with a [releasables.<name>] section for each releasable declared in workspace.toml, carrying an empty bump type and description for you to fill in and the detected include list. A releasable with no unreleased commits since its last tag is rendered as a commented-out section; one with no members or no detected targets is skipped with a warning. |
99
+ | `monorepo release order` | Compute and display the topological release order for all projects in the monorepo workspace based on their declared depends-on relationships. Projects with no dependencies are listed first, followed by projects that depend on them, ensuring each project is released only after its dependencies. Detects and reports circular dependency errors. |
94
100
  | **dev** | Developer utilities for locally working with rlsbl projects, including editable installs that mirror the project's release target (pypi -> uv tool install -e, npm -> npm link, go -> go install). |
95
- | `dev install` | Install the project locally for development by running each detected target's own install command. --target is required and names the install mode: global installs onto the machine (pypi via `uv tool install -e`, npm via `npm link`), venv installs into the project's local environment instead; a target that does not support the chosen mode is skipped with a reason. --target global is supported by 7 targets: npm, pypi, go, swift, hex, deno, zig. --target venv is supported by 4 targets: npm, pypi, hex, deno. --uninstall reverses a previous install on 3 targets: npm, pypi, deno. In monorepo mode, pair with --all, --include, or --exclude. |
101
+ | `dev install` | Install the project locally for development by running each detected target's own install command. --target is required and names the install mode: global installs onto the machine (pypi via `uv tool install -e`, npm via `npm link`), venv installs into the project's local environment instead; a target that does not support the chosen mode is skipped with a reason. --target global is supported by 3 targets: npm, pypi, go. --target venv is supported by 2 targets: npm, pypi. --uninstall reverses a previous install on 2 targets: npm, pypi. In monorepo mode, pair with --all, --include, or --exclude. |
96
102
  | `dev sync` | Overlay local editable checkouts of sibling projects onto this project's locked environment. Reads dev-sources.toml.local-only for overlay entries, runs uv sync --inexact excluding overlaid packages, then uv pip install -e per entry. Requires UV_NO_SYNC=1 in the environment to prevent bare uv run from reverting overlays. |
97
103
  | `dev status` | Report the state of local dev-sync overlays: for each package recorded in the dev-overlays sentinel, show its declared editable checkout path and version alongside the venv's actual install (editable at the expected path, WIPED back to a registry wheel, or missing entirely). Exits 1 if any overlay drifted so scripts and pre-run guards can detect a silent wipe by a bare uv sync or uv run; exits 0 when all overlays are intact or none are declared. |
98
104
  | **rewrite** | Sweeping rewrites of the current working tree, each previewed before it is performed. Every command in this group observes the tree, reports a per-file plan with occurrence counts, and refuses to apply when a count moved between the preview and the write. |
99
- | `rewrite go-module-path` | Rename a Go module path across the repository. Rewrites the module-path tokens in every go.mod (the module directive plus any require, replace, exclude or retract reference from a nested module) and every Go import site under the old path, located by the tree-sitter import scanner and rewritten line-scoped. The committed strictcli schema dump moves with it: the project_id line in every .strictcli/schema.json under the old module path is rewritten too, so the next --dump-schema does not refuse a dump belonging to the old project. Containment is boundary-aware, so a neighbouring module whose path merely begins with the same letters is left alone. The files considered are what `git ls-files --cached --others --exclude-standard` lists: tracked files plus untracked files that are not ignored, so a gitignored third-party clone is never touched, and neither are comments, vendored trees, or any other kind of file. Use --dry-run to print the per-file plan with occurrence counts. |
100
- | `rewrite project-name` | Rename a standalone project's published identity. Rewrites the package name in every target manifest whose target renames it (npm package.json "name", PyPI pyproject.toml [project].name), located through the targets' configured paths, and for a Go target moves the module path to one whose last element is --to, through the same rewrite as `rlsbl rewrite go-module-path`. Records one identity-transition event per changed identity in .rlsbl/transitions.jsonl: package-name from --from to --to, and go-module-path from the module path the latest release published (read from go.mod at that release's commit) to the new one. The effective version is the version the next release ships: the current version plus the bump in .rlsbl/releases/unreleased.toml, or the current version as-is for a project that has never released. The rename and the record are committed separately, so a crash between them is completed by re-running; the record commit carries the Autogenerated trailer, and the rename commit does not, so changelog coverage asks for the breaking entry the closing message prints. Nothing else is touched: no source directory is moved, no command name (npm bin, PyPI [project.scripts]) is renamed, no registry is contacted, and no repository is renamed; the closing message lists those remaining steps in order. Refuses, before writing anything, inside a monorepo workspace, when --from is not the name the manifests declare, when --from equals --to or --to is not a valid package name for a target it renames, when a target rlsbl does not rename still declares --from, when the release file is missing, when there is a Go target and the latest release's archive is marked unrecoverable (rlsbl's record cannot establish the module path that release published), and on a dirty working tree. Use --dry-run to print the per-file plan with occurrence counts and the events it would record. |
101
- | `rewrite uv-path-sources` | Convert path- and workspace-sourced Python dependencies into registry constraints floored at the version uv.lock resolves. Covers [project].dependencies, every [project.optional-dependencies] extra and every PEP 735 [dependency-groups] group, and deletes the matching [tool.uv.sources] entry so it stops overriding the new constraint. Each converted name is added to internal_dep_floors in .rlsbl/config.json. A locked version that is not published on PyPI is a hard error naming the remedy (release that dependency first), and so is a registry probe that fails to answer. Use --dry-run to print the per-dependency plan with entry counts. |
105
+ | `rewrite go-module-path` | Rename one Go module path across the repository. Rewrites the module-path tokens in every go.mod (the module directive plus any require, replace, exclude or retract reference from another module) and every Go import site under the old path, located by the tree-sitter import scanner and rewritten line-scoped. A module nested under the old path with a go.mod of its own is another module: its path and its packages' imports are left alone, since the longest declared module path owns every token and import, and it is renamed by an invocation of its own. The committed strictcli schema dump moves with it: the project_id line in every .strictmetadata/.cli-schema/schema.json under the old module path is rewritten too, so the committed help document names the module the program now reports in `help --json`, not the old one. Containment is boundary-aware, so a neighboring module whose path merely begins with the same letters is left alone. The files considered are what `git ls-files --cached --others --exclude-standard` lists: tracked files plus untracked files that are not ignored, so a gitignored third-party clone is never touched, and neither are comments, vendored trees, or any other kind of file. Use --dry-run to print the per-file plan with occurrence counts. |
106
+ | `rewrite project-name` | Rename a standalone project's published identity. Rewrites the package name in every target manifest whose target renames it (npm package.json "name", PyPI pyproject.toml [project].name), located through the targets' configured paths, and for a Go target moves the module path to one whose last element is --to, through the same rewrite as `rlsbl rewrite go-module-path`. Records one identity-transition event per changed identity in .rlsbl/transitions.jsonl: package-name from --from to --to, and go-module-path from the module path the latest release published (read from go.mod at that release's commit) to the new one. The effective version is the version the next release ships: the current version plus the bump in .rlsbl/releases/unreleased.toml, or the current version as-is for a project that has never released. The rename and the record are committed separately, so a crash between them is completed by re-running; the record commit carries the Autogenerated trailer, and the rename commit does not, so changelog coverage asks for the breaking entry the closing message prints. Nothing else is touched: no source directory is moved, no command name (npm bin, PyPI [project.scripts]) is renamed, no registry is contacted, and no repository is renamed; the closing message lists those remaining steps in order. Refuses, before writing anything, inside a monorepo workspace, when --from is not the name the manifests declare, when --from equals --to or --to is not a valid package name for a target it renames, when a target rlsbl does not rename still declares --from, when the release file is missing, when the next version cannot be decided the way `rlsbl release run` decides it (version files naming a version the release record holds nothing for once a release exists, version files behind the latest release, or a next version the record holds as never released; version files naming an unrecoverable version, which the release refuses until its tag is restored, are bumped from instead), when there is a Go target and the latest release's archive is marked unrecoverable (rlsbl's record cannot establish the module path that release published), and on a dirty working tree. Use --dry-run to print the per-file plan with occurrence counts and the events it would record. |
107
+ | `rewrite uv-path-sources` | Convert path- and workspace-sourced Python dependencies into registry constraints floored at the version uv.lock resolves. Covers [project].dependencies, every [project.optional-dependencies] extra and every PEP 735 [dependency-groups] group, and deletes the matching [tool.uv.sources] entry so it stops overriding the new constraint. Each converted name is added to internal_dep_floors in .rlsbl/config.json, and the rlsbl:dep-floors option that key belongs to is switched on with an options entry (current and ideal error, scoped to the project when it is a workspace member) when it is off. A locked version that is not published on PyPI is a hard error naming the remedy (release that dependency first), and so is a registry probe that fails to answer. Use --dry-run to print the per-dependency plan with entry counts. |
102
108
  | **transition** | Record the transition-record facts an operator states. Most events in a repository's transition record are written by the operation that performed them; the ones here are statements about a repository somebody read -- two that nothing can derive at all, and one whose command exists but which a rename performed by hand leaves unrecorded. |
103
109
  | `transition record` | Append one operator-declared fact to this repository's transition record: a tag that stands outside the version model (--non-version-tag), a member's or releasable's deliberately closed release history (--release-history-closed), or a releasable that was renamed (--releasable-rename <old> --to <new>). Exactly one must be elected, and --reason states why in the operator's own words. The event is appended to the repository-scoped record (.rlsbl-monorepo/transitions.jsonl in a workspace, .rlsbl/transitions.jsonl standalone) and committed. A second declaration of the same kind about the same subject is refused, naming the one already recorded. |
110
+ | **upstream** | Manage a fork's relation to its upstream, which the fork declares in .strictmetadata/upstream/upstream.toml (host, owner, repo, and branch; a repository without that file is not a fork, and no upstream is ever inferred from a git remote). Changelog coverage in a fork leaves out every commit reachable from the upstream branch, fetched into refs/upstream/<host>/<owner>/<repo>/<branch>, and from the inherited tags kept under refs/tags-of/<host>/<owner>/<repo>/. |
111
+ | `upstream adopt-tags` | Move the tags this fork inherited from its upstream out of refs/tags. A tag is inherited when upstream (https://<host>/<owner>/<repo>, queried with git, never through a package registry) has a tag of the same name at the same object. Each inherited tag moves, keeping its object, to refs/tags-of/<host>/<owner>/<repo>/<tag>: the ref is written here, then one push creates it on origin and a second push deletes the tag from origin's refs/tags, each guarded by the object observed, and then the tag is deleted from refs/tags here. Every push carries one ref, because GitHub fires no events for a push deleting four or more tags, and tags move one after another, so a run that stops part-way leaves every earlier tag finished. Tags upstream does not have are left alone. A tag carrying an inherited tag's name at a different object -- here, on origin, or against a kept ref -- refuses the whole run, named, before anything is written. Idempotent: a re-run finishes an interrupted one and otherwise has nothing to do. Use --dry-run to print the plan and write nothing. |
112
+ | **secrets** | Keep the Actions secrets CI publishes with in step with the credentials on this machine. No secret value is ever printed or passed as an argument. |
113
+ | `secrets sync-npm-token` | Copy the npm token in ~/.npmrc (the //registry.npmjs.org/:_authToken= line) into the NPM_TOKEN Actions secret of repositories that already have one. Refuses when ~/.npmrc holds no token, and when npm does not accept it (asked with GET https://registry.npmjs.org/-/whoami); otherwise prints the npm user it authenticates. Without --all the target is the current repository, named by its origin remote, and a repository without an NPM_TOKEN secret is refused: the secret is never created. With --all the targets are every repository the authenticated gh account can see (its own and those of every organization it belongs to) that has an NPM_TOKEN secret; archived repositories are named and skipped, since GitHub refuses writes to them. Each target is set with `gh secret set NPM_TOKEN --repo <owner/repo>`, the token piped on stdin, and printed with its outcome. Exits 1 when any target could not be read or set. The token is never printed. |
114
+ | **options** | Print rlsbl's options registry, and write this repository's entries for rlsbl's options in .strictmetadata/options/ at its git root. Every rlsbl check is an option, rlsbl:<check name>, and so is rlsbl:test-sandbox: an error check ranks error > warn > off, a warning check warn > off, and `rlsbl check` and every release apply each entry's current value (off: the check does not run; warn: it runs and reports, never blocking; error: as registered). The options that stand for an adoption (rlsbl:dep-floors, rlsbl:format, rlsbl:lint, rlsbl:strictspec-certificate-gate, rlsbl:test-sandbox, rlsbl:type-check) default to off and accept a path scope naming one workspace member. Invalid entries stop every check run and every release. |
115
+ | `options registry` | Print rlsbl's options registry: one option per check, rlsbl:<check name>, plus the options that are not checks, each with the subject file its entries are filed under, the values it ranks strongest first, its default, its scope kind, the options it requires, and what it governs. The TOML printed is the registry document rlsbl ships, in the shape of strictspec's built-in options-registry schema; with --json the same declarations are the payload. Needs no rlsbl project. |
116
+ | `options set` | Write one rlsbl entry into .strictmetadata/options/<subject>.toml at the repository's git root, or update the entry already there for the same option and scope, keeping every other line of the document. The subject file is the one the option's registry declaration names. Creates .strictmetadata/options/ and its manifest.toml, naming strictspec as the owner, when absent. strictspec validates the result before anything is written -- every document's shape, and every rlsbl entry with this one in place -- and each refusal is its catalogued diagnostic: an unknown option, a value the option does not declare, a current ranked above the ideal, an entry equal to the default, an empty reason, an option switched off while an option requiring it is not. A --scope must name a workspace member's directory relative to the repository root, and only the options declaring the path scope kind accept one. Refuses an id outside the rlsbl namespace and a manifest naming another owner. The written files are committed with the Autogenerated trailer. |
104
117
 
105
118
  Global flags: `--help`, `--version`, `--dry-run`, `--approve-consequential`, `--quiet`, `--verbose`.
106
119
 
@@ -109,10 +122,10 @@ Global flags: `--help`, `--version`, `--dry-run`, `--approve-consequential`, `--
109
122
  `rlsbl release run` reads `.rlsbl/releases/unreleased.toml` for the bump type, the
110
123
  description and the target selection, then:
111
124
 
112
- 1. Verifies `gh` auth and a clean working tree (`--allow-dirty` accepts a dirty one), computes the new version and confirms its tag does not exist
125
+ 1. Verifies `gh` auth, refuses an uncommitted change to any path the release writes (naming it; every other uncommitted change is listed and left alone), computes the new version and confirms its tag does not exist, and enters the release checkout: a detached checkout of the branch tip under `.git/rlsbl/release-checkout`, where every step below runs, so nothing the release builds, tests or commits reads the working tree
113
126
  2. Validates the JSONL changelog and regenerates CHANGELOG.md from it
114
127
  3. Runs `.rlsbl/hooks/pre-checks.sh` (user-owned), the strictcli schema dump, `selfdoc gen` and `selfdoc check`, the built-in tests and lint, and `.rlsbl/hooks/pre-release.sh` (scaffold-managed) -- any non-zero aborts
115
- 4. Writes the new version to every detected target file and `.rlsbl/version`, commits it with the tag string as the message, and pushes that commit **untagged**: the release candidate
128
+ 4. Writes the new version to every detected target file (and its own rlsbl version to `.rlsbl/version`, which names the rlsbl that last scaffolded or released the project, not the project's version), commits it with the tag string as the message, advances the branch to that commit by compare-and-swap (writing into the working tree only the files it changed), and pushes it **untagged**: the release candidate
116
129
  5. Waits in-process for the repository's own push-triggered CI to conclude on that exact commit
117
130
  6. Finalizes the changelog (renames `unreleased.jsonl` to the version's file, opens a fresh one, regenerates CHANGELOG.md), archives the release file, tags the **CI-verified commit**, pushes the finalization commits and the tags, and creates the GitHub Release with the version's changelog section as notes
118
131
  7. Uploads assets, runs each pipeline's `publish` (configured in `.rlsbl/config.json`), deploys, runs `.rlsbl/hooks/post-release.sh` (non-fatal), and prints `Watch CI: rlsbl watch <sha>`
@@ -124,12 +137,13 @@ release branch and `rlsbl release resume` completes the *same* version; a failed
124
137
  release never burns it.
125
138
 
126
139
  The step-by-step pipeline, including what each step does in monorepo and releasable
127
- mode, is in [.stricttools/docs/release-workflow.md](.stricttools/docs/release-workflow.md).
140
+ mode, is in [.strictmetadata/docs/release-workflow.md](.strictmetadata/docs/release-workflow.md).
128
141
 
129
142
  Use `--dry-run` to preview without changes: mutating operations are recorded and printed as a
130
143
  would-do log rather than performed. A small set of commands declares itself `consequential`
131
- (`release run`/`resume`/`retry`/`undo`/`deprecate`/`yank`/`scrub`/`reconcile`/`backfill`,
132
- `claim-name`, `deploy`, `transition record`, `rewrite project-name`,
144
+ (`release run`/`resume`/`retry`/`undo`/`abandon`/`deprecate`/`yank`/`scrub`/`reconcile`/`backfill`,
145
+ `claim-name`, `deploy`, `transition record`, `rewrite project-name`, `upstream adopt-tags`,
146
+ `secrets sync-npm-token`,
133
147
  `monorepo release run`/`mirror`/`absorb`/`extract`/`rename-releasable`) and asks
134
148
  for confirmation before running; pass `--approve-consequential` in non-interactive contexts
135
149
  (CI, AI agents), where the prompt is a hard error instead. Every other command runs without
@@ -137,7 +151,7 @@ asking.
137
151
 
138
152
  Create the release file with `rlsbl release init`, which auto-detects project targets and scaffolds the TOML file.
139
153
 
140
- First release: if the current version has never been tagged, `release` publishes it as-is (bump type is ignored).
154
+ First release: if the release record holds no released version at all, `release` publishes the current version as-is (bump type is ignored). Once a release exists, version files naming a number above the latest release that the record holds nothing for (no archive, no tag) are refused -- that is what an abandoned attempt leaves behind -- and `rlsbl release abandon` records that number as never released, after which the next release bumps from it. Such a number below the latest release is refused as behind it: the version files must name at least the latest release.
141
155
 
142
156
  Pre-release versions (e.g. `1.0.0-beta.1`) are supported.
143
157
 
@@ -145,7 +159,6 @@ Pre-release versions (e.g. `1.0.0-beta.1`) are supported.
145
159
 
146
160
  ```
147
161
  rlsbl scaffold # create or update CI/CD for all detected registries
148
- rlsbl scaffold --target plain # also cover a registry auto-detection cannot find
149
162
  rlsbl scaffold --no-auto-commit # skip auto-commit of scaffolded files
150
163
  rlsbl scaffold --no-auto-tag # skip the rlsbl GitHub topic tag on this run
151
164
  ```
@@ -164,7 +177,7 @@ Created files are committed automatically by default.
164
177
  | `.rlsbl/hooks/pre-checks.sh` | User-customizable pre-checks validation |
165
178
  | `.rlsbl/hooks/pre-release.sh` | User-customizable pre-release validation |
166
179
  | `.rlsbl/hooks/post-release.sh` | User-customizable post-release actions |
167
- | `.git/hooks/pre-push` | Captures push refs, runs `rlsbl check --tag prepush` |
180
+ | `.git/hooks/pre-push` | Captures push refs, runs `rlsbl failing-checks --hook pre-push` |
168
181
  | `.rlsbl/bases/` | Three-way merge bases for scaffold |
169
182
 
170
183
  **Three-way merge:** Bases are stored at scaffold time. On re-run, user customizations and template updates merge via `git merge-file`. Conflicts get git-style conflict markers.
@@ -176,30 +189,29 @@ Created files are committed automatically by default.
176
189
  - `.github/workflows/ci-custom.yml` -- runs alongside `ci.yml`
177
190
  - `.github/workflows/publish-custom.yml` -- runs alongside `publish.yml`
178
191
 
179
- See [.stricttools/docs/ci-customization.md](.stricttools/docs/ci-customization.md) for an example.
192
+ See [.strictmetadata/docs/ci-customization.md](.strictmetadata/docs/ci-customization.md) for an example.
180
193
 
181
194
  **Runs config migrations** when `.rlsbl/config-schema.json` exists.
182
195
 
183
196
  ## Check system
184
197
 
185
- rlsbl includes 86 checks across 9 tags.
198
+ rlsbl includes 93 checks across 8 tags.
186
199
 
187
200
  Checks are grouped by tag -- `--tag` runs one family, `--name` runs a single check, and `--all` runs everything, including the checks that carry no tag:
188
201
 
189
202
  | Tag | Checks |
190
203
  | --- | --- |
191
- | `project` | 28 |
192
- | `preflight` | 22 |
193
- | `workspace` | 19 |
194
- | `quality` | 16 |
195
- | `changelog` | 11 |
204
+ | `preflight` | 30 |
205
+ | `project` | 30 |
206
+ | `workspace` | 24 |
207
+ | `quality` | 15 |
208
+ | `changelog` | 10 |
196
209
  | `preflight-changelog` | 9 |
210
+ | `release` | 8 |
197
211
  | `prepush` | 6 |
198
- | `release` | 6 |
199
- | `maven` | 1 |
200
212
  | (untagged) | 4 |
201
213
 
202
- What each tag's checks actually verify, one row per check with its severity, is in [.stricttools/docs/checks.md](.stricttools/docs/checks.md), which also says which tags the release pipeline runs on its own.
214
+ What each tag's checks actually verify, one row per check with its severity, is in [.strictmetadata/docs/checks.md](.strictmetadata/docs/checks.md), which also says which tags the release pipeline runs on its own.
203
215
 
204
216
  ```
205
217
  rlsbl check --all # run all checks
@@ -236,6 +248,8 @@ withdrawing what already shipped:
236
248
  | A subtree mirror's `main` | The mirror reconciler's converge (`rlsbl monorepo mirror`, and the release's mirror step, which calls the same code). Force-with-lease is its routine write; a commit it cannot account for is a contract violation it refuses. |
237
249
  | A subtree mirror's tags and their GitHub Releases | The mirror publication module, driven by the release's mirror step or by `rlsbl monorepo mirror` materializing a version the mirror is missing. A mirror's scaffold renders no publish workflow and every convergence sweeps one that arrived another way, so the mirror never releases itself. |
238
250
  | Rewritten history on any of the above | `rlsbl release scrub`, the one sanctioned rewrite: it force-pushes, remaps the changelog hashes, re-points the tags and rewrites each tag's Release document in a single pass -- in place, never delete-then-create. |
251
+ | A fork's inherited tags, `refs/tags-of/<host>/<owner>/<repo>/<tag>`, here and on origin | `rlsbl upstream adopt-tags`, moving each tag the fork inherited from its declared upstream out of `refs/tags` with its object unchanged; a kept ref is never rewritten. |
252
+ | A fork's upstream branch, `refs/upstream/<host>/<owner>/<repo>/<branch>`, local only | The operator's `git fetch --no-tags` that the changelog checks print when it is missing; rlsbl only reads it. |
239
253
 
240
254
  The repair and retraction surfaces, in full: `rlsbl release undo` (deletes the
241
255
  Release and the tag, reverts the version-bump commit and pushes the branch),
@@ -246,12 +260,14 @@ Release's notes), `rlsbl release deprecate` and `rlsbl release yank` (rewrite a
246
260
  Release body and set its pre-release flag; `yank` also performs the registry's
247
261
  removal), `rlsbl changelog amend`, `rlsbl changelog edit` and
248
262
  `rlsbl changelog remove` (re-sync a released
249
- version's Release notes), and `rlsbl monorepo rename-releasable` (pushes one
250
- boundary alias tag). A write from anywhere else is not rlsbl's.
263
+ version's Release notes), `rlsbl monorepo rename-releasable` (pushes one
264
+ boundary alias tag), and `rlsbl upstream adopt-tags` (deletes a fork's
265
+ inherited tags from `refs/tags` here and on origin, after keeping each under
266
+ `refs/tags-of/`). A write from anywhere else is not rlsbl's.
251
267
 
252
268
  ## Pre-push hook
253
269
 
254
- The `.git/hooks/pre-push` hook captures push refs from git and runs `rlsbl check --tag prepush`, which enforces:
270
+ The `.git/hooks/pre-push` hook captures push refs from git and runs `rlsbl failing-checks --hook pre-push`: the checks `[hooks.pre-push]` in rlsbl's `checks.toml` selects (the `prepush` tag), blocking only on error-level failures, so a check an options entry softened to `warn` never blocks a push. It enforces:
255
271
 
256
272
  1. **Changelog coverage** -- every pushed commit must have a JSONL entry
257
273
  2. **Gitignore guard** -- rlsbl-managed files must not be gitignored
@@ -294,6 +310,7 @@ Supports architectural layer rules via `[layers]` in `workspace.toml` for enforc
294
310
  | Variable | Default | Description |
295
311
  |----------|---------|-------------|
296
312
  | `RLSBL_VERSION` | -- | Set when running pre-release and post-release hooks; contains the version being released |
313
+ | `RLSBL_RELEASE_BIN` | -- | Set for every hook and step of a release; the release's own directory for binaries, first on `PATH`, where a hook can build a tool the release must run in its unreleased form |
297
314
  | `RLSBL_DIST_DIR` | -- | Set when running `custom_assets` build commands; points to the distribution directory for output files |
298
315
  | `GITHUB_TOKEN` | -- | Used by `gh` CLI for GitHub API calls; `discover` works unauthenticated for public repos |
299
316
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rlsbl",
3
- "version": "0.129.0",
3
+ "version": "0.131.0",
4
4
  "description": "Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more",
5
5
  "homepage": "https://smmh.dev/rlsbl/",
6
6
  "license": "MIT",