rlsbl v0.113.0 /Development workflow
On this page

Local development workflow: editable installs across 7 targets, sibling overlays via dev sync, CI watching with classified retry, and pre-push enforcement.

#Development workflow

#Editable installs

rlsbl dev install installs a project locally for development using the target's native editable install mechanism. It detects project targets, calls each target's dev_install_command() to get the install spec, and runs it.

#Per-target install commands

Seven of rlsbl's release targets implement an editable install command; every other target skips with an explanatory message rather than failing the run. Each target reports a global command, a venv command, or both, so rlsbl dev install never has to guess which ecosystem tool to invoke. Targets that cannot support a mode at all report it as unsupported, and the command says so instead of silently doing nothing.

Per-target install commands
TargetGlobal install commandVenv install command
pypiuv tool install -e .uv sync --all-packages
npmnpm linknpm install
gogo install <install_paths> (declared on the go pipeline in .rlsbl/config.json)(not supported)
denodeno installdeno cache .
hexmix deps.getmix deps.get
swiftswift build(not supported)
zigzig build install(not supported)

#Modes

Modes
FlagBehavior
--global (default)System-wide install via the target's global command
--venvProject-local environment install (supported by pypi, npm, deno, and hex)

When neither --global nor --venv is passed, --global is the default. The two flags are mutually exclusive.

#Uninstall

rlsbl dev install --uninstall reverses a previous install by invoking each target's native removal command. The uninstall mechanism is stateless -- rlsbl does not track which packages were installed, so it relies entirely on the ecosystem's own uninstall tooling to determine what to remove:

Uninstall
TargetUninstall commandNotes
pypiuv tool uninstall {name}Resolves package name from pyproject.toml
npmnpm unlinkRemoves the global symlink
denodeno uninstall {name}Removes the installed script
go, hex, swift, zig(skipped)No uninstall template; prints a message

#Monorepo mode

In a monorepo workspace with multiple independently-versioned projects, rlsbl dev install requires an explicit filter flag to specify which projects to install. Without a filter, the command errors with guidance rather than installing everything by default, preventing accidental system-wide installation of dozens of packages:

Monorepo mode
FlagBehavior
--allInstall every project in the workspace
--include <names>Comma-separated project names to include
--exclude <names>Comma-separated project names to exclude

Without a filter flag, the command errors with guidance. Install and uninstall operations apply recursively to matching projects.

#Local editable overlays (rlsbl dev sync)

rlsbl dev sync overlays local editable checkouts of sibling projects onto the current project's locked environment -- the supported way to develop against a sibling checkout (e.g. a library you are changing in lockstep) without committing machine-local [tool.uv.sources] path dependencies, which poison uv.lock with machine-specific paths and break CI.

Committing such path sources is not just discouraged -- it is banned outright. The cross-repo-path-sources check (project tag, also enforced unconditionally by rlsbl release run) hard-errors when a committed pyproject.toml declares a [tool.uv.sources] path entry that resolves outside the repository, whether absolute (/home/user/other-repo) or relative (../sibling, resolved against the pyproject's directory, matching uv's rule). In-repo paths and workspace = true sources stay legal. The ban is what keeps every committed lockfile registry-pure, which in turn lets scaffolded CI run uv sync --locked unconditionally.

The overlay file also feeds the release-time version-skew guard: rlsbl release run reads dev-sources.toml.local-only and hard-errors when any overlaid checkout's local version is ahead of its latest registry release ("release the dependency first") -- releasing code developed and tested against unreleased dependency features would ship something the registry cannot satisfy. See the release workflow for details.

#Why a wrapper is required

A wrapper command is required because, as of uv 0.9.17, no single native uv mechanism can install an editable sibling checkout and prevent subsequent sync operations from reverting it back to the locked registry wheel. Each partial solution has a gap that the wrapper fills by orchestrating multiple uv invocations together:

  • uv pip install -e ../x alone is wiped by the next uv sync: exact sync reinstalls the locked registry wheel even at equal versions.
  • uv sync --inexact --no-install-package <name> preserves a pre-existing editable install (even under version conflict, and with --frozen), but neither flag has an environment-variable equivalent.
  • A bare uv run auto-syncs (and wipes overlays) unless UV_NO_SYNC=1 is set.
  • [sources] in uv.toml is rejected by uv, and the UV_SOURCES environment variable is silently ignored (unshipped proposal).

#The overlay file

Overlays are declared in dev-sources.toml.local-only at the project root. The *.local-only suffix is ignored by the scaffold gitignore fleet-wide, so the file never reaches git. One [[overlay]] block per checkout:

TM toml
[[overlay]]
package = "strictcli"          # distribution name, as uv knows it
path = "../strictcli/python"   # absolute, or relative to the project root

Every problem is a hard error, never a silent no-op: missing file (the error shows the format above), invalid TOML, unknown keys, missing package or path, nonexistent path, path without a pyproject.toml, and a package that does not match the checkout's [project].name (PEP 503-normalized) -- a mismatch would make the sync exclusion miss, letting the next sync silently wipe the overlay.

#Behavior

  1. Hard-errors unless UV_NO_SYNC=1 is set in the environment (see below).
  2. Runs a single uv sync --inexact with --no-install-package <pkg> for every overlay entry.
  3. Runs uv pip install -e <path> per entry. Re-installing on every run is deliberate: it picks up new transitive dependencies of the overlaid checkouts.
  4. Prints exactly what was overlaid: package, version (from the checkout's pyproject.toml), and resolved path.

VIRTUAL_ENV is stripped from both subprocess invocations so uv sync and uv pip deterministically target the same project environment (a leaked active venv would otherwise split the two steps across environments). The command is idempotent. In a monorepo, run it from within a sub-project; invoking it at the workspace root is a hard error.

#The UV_NO_SYNC=1 gate

Without UV_NO_SYNC=1, any bare uv run silently reinstalls the locked registry wheels over the editable overlays just created by the sync command. The result is that overlays would be silently reverted on every uv run invocation, undoing the entire dev sync setup. rlsbl dev sync therefore refuses to run until UV_NO_SYNC=1 is set permanently in the shell profile or direnv configuration:

$_ bash
# shell profile (~/.bashrc / ~/.zshrc) or the project's .envrc (direnv)
export UV_NO_SYNC=1

A bare uv sync still reverts overlays -- harmlessly: re-run rlsbl dev sync to restore them.

#Watch and CI monitoring

rlsbl watch [<sha>] polls GitHub Actions for a commit's CI runs and reports pass/fail results in real time, with automatic retry on transient failures. It discovers runs by commit SHA (defaulting to HEAD), watches them concurrently, and sends desktop notifications on completion with links to the relevant GitHub page.

#Behavior

  1. Discovery -- polls gh run list --commit <sha> until at least one run appears (30 attempts, 4 seconds apart: about two minutes)
  2. Parallel watching -- all discovered runs are watched concurrently via gh run watch
  3. Classified auto-retry -- when a workflow fails, the failing step's log tail is fetched and classified. A DETERMINISTIC failure (a test failure, a compile or config error, a workflow syntax error, a missing-secret or auth denial) will fail identically on a rerun and is never retried. Anything else is treated as a transient flake and retried exactly once, in place, via gh run rerun <failed_run_id> -- a full rerun of the same run id, not a gh workflow run dispatch (a dispatch would create a run the publish gate cannot match). The rerun is then watched to completion.
  4. Late-starting workflow detection -- after initial runs complete, polls once more for workflows that started late (e.g., publish/deploy workflows triggered by a GitHub Release created during CI). Late runs are watched with the same parallel/retry logic.
  5. Workflow audit -- prints a summary table of all workflows that ran. Warns if a publish workflow exists on disk but did not trigger for this commit.
  6. Desktop notifications -- sends a notification on completion:

- On failure: opens the Actions page for the failed run - On success: opens the GitHub Release page (if a tag exists for this commit)

#Flags

Flags
FlagDescription
--target <name>Registry whose CI workflow to watch (auto-detected if omitted)
--run-id <id>Watch specific run IDs instead of discovering by commit (repeatable)

#Pre-push hook

The .git/hooks/pre-push hook captures git's stdin into the RLSBL_PUSH_STDIN environment variable and runs rlsbl check --tag prepush, enforcing changelog coverage, gitignore safety, and test suite execution before any commits reach the remote. This hook is installed by rlsbl scaffold and uses the V5 template format. It runs all prepush-tagged checks in dependency order:

  1. **prepush-changelog-coverage** (error) -- verifies every pushed commit has a JSONL changelog entry. Commits that only touch .rlsbl/changes/ or CHANGELOG.md, and commits with an Autogenerated: true trailer, are automatically exempted.
  2. **prepush-gitignore-guard** (error) -- blocks the push if rlsbl-managed files (e.g., .rlsbl/changes/unreleased.jsonl, CHANGELOG.md) are gitignored.
  3. **prepush-manual-warning** (error) -- blocks a push that targets a release branch (configured via release_branches in .rlsbl/config.json). Release-internal pushes run git push --no-verify and never reach the hook, so any push that does reach this check is by construction a manual one. There is no environment-variable bypass.
  4. **test-suite** (error) -- runs the project's test suite (pytest / go test / npm test). Depends on prepush-changelog-coverage -- if changelog coverage fails, the test suite is skipped (fast checks fail first).

#Key differences from the old system

  • Version-tag pushes are no longer exempt. All checks always run, regardless of whether the push contains a version bump commit.
  • Fast-fail ordering. Dependency ordering ensures cheap checks run before expensive ones. If prepush-changelog-coverage fails, test-suite is skipped entirely.
  • Unified check system. The pre-push hook uses the same rlsbl check infrastructure as release validation, with consistent reporting and severity handling.

#Monorepo behavior

In monorepos, the pre-push hook runs from the repo root. The test-suite check hard-errors at workspace root because it needs a specific project directory. The test-suite-workspace check handles this: it automatically detects affected projects from push refs, runs tests for each, and skips dev_node projects. If changelog coverage fails, test-suite-workspace is skipped (it depends on prepush-changelog-coverage).

#Standalone usage

rlsbl check --tag prepush can be run outside of a git push context. Push-specific checks (prepush-changelog-coverage, prepush-gitignore-guard, prepush-manual-warning) skip gracefully when RLSBL_PUSH_STDIN is not set. test-suite always runs regardless of push context.

#Removed command

The old rlsbl pre-push-check command was removed. It is now a stub that prints an error and exits non-zero, so a hook still calling it blocks every push. Run rlsbl scaffold to install the V5 hook template, which calls rlsbl check --tag prepush instead.

#Examples

#Setting up local development for a Python project

$_ bash
cd ~/Projects/mylib

# Install the project locally for development (editable install)
rlsbl dev install
#   Installing mylib via: uv tool install -e .
#   Installed mylib 0.5.2

# Verify it works
mylib --version
#   0.5.2

#Developing against a local sibling checkout

When making coordinated changes to a library and its consumer simultaneously, use rlsbl dev sync to overlay a local editable checkout of the library onto the consumer's locked virtual environment. This avoids publishing intermediate versions and lets you test changes across project boundaries immediately. The overlay mechanism uses uv pip install -e under the hood and requires UV_NO_SYNC=1 in your environment to prevent bare uv run from reverting the overlay back to a registry wheel:

$_ bash
cd ~/Projects/myapp

# 1. Set UV_NO_SYNC=1 in your shell profile (one-time setup)
echo 'export UV_NO_SYNC=1' >> ~/.bashrc
source ~/.bashrc

# 2. Create the overlay file
cat > dev-sources.toml.local-only << 'EOF'
[[overlay]]
package = "strictcli"
path = "../strictcli/python"
EOF

# 3. Run dev sync to overlay the local checkout
rlsbl dev sync
#   uv sync --inexact --no-install-package strictcli ... OK
#   uv pip install -e ../strictcli/python ... OK
#   Overlaid: strictcli 0.8.0 from ../strictcli/python

# Now edits to ../strictcli/python are immediately visible in myapp's environment

#Checking overlay status

$_ bash
rlsbl dev status
#   strictcli  0.8.0  ../strictcli/python  editable (intact)

#Installing all projects in a monorepo

$_ bash
cd ~/Projects/my-monorepo

# Install all workspace projects
rlsbl dev install --all
#   Installing mylib via: uv tool install -e packages/mylib
#   Installing cli via: npm link (packages/cli)
#   Skipping tests (dev_node)

# Or install specific projects
rlsbl dev install --include mylib,cli

# Uninstall when done
rlsbl dev install --uninstall --all

#Watching CI after a push

$_ bash
# Watch CI for the latest commit
rlsbl watch
#   Discovering runs for abc1234 ...
#   Watching: CI (abc1234) ... running
#   CI (abc1234) ... passed

# Watch CI for a specific commit
rlsbl watch e4f5g6h
#   Discovering runs for e4f5g6h ...
#   Watching: CI (e4f5g6h) ... running
#   CI (e4f5g6h) ... failed
#   Auto-retrying CI ...
#   Watching: CI (e4f5g6h) [retry] ... passed
Search