Skip to content
rlsbl.tool_checks
On this page

How rlsbl invokes a checking tool: the lint, format and type-check built-ins, their declared path lists, the uv-run argv composition, and the competing-scope guards.

#rlsbl.tool_checks

#rlsbl.tool_checks

How rlsbl invokes a checking tool, and the three built-ins that do it.

Three built-in checks -- lint, format and type-check -- run a Python tool over a project-declared path list, through the project's own environment. They are configured in .rlsbl/config.json::

"checks": { "lint": {"paths": ["mypackage", "tests", "scripts", "docs"]}, "format": {"paths": ["mypackage", "tests", "scripts", "docs"]}, "type-check": {"paths": ["mypackage", "tests", "docs"]} }

A check with no entry skips. paths is required when an entry is present; cwd is optional and resolves against the project root.

Why the paths are declared rather than inferred -----------------------------------------------

Both tools read scope from their own config files, and both do it in a way that silently disagrees with an explicit CLI path list:

  • mypy's files / packages / modules are OVERRIDDEN by CLI paths --

a scope declared there is dead but reads as authoritative.

  • ruff's include / extend-include silently NARROW the directories

passed on the command line (measured on ruff 0.15.20).

So each tool check is paired with a competing-scope guard check (lint-scope-guard and friends) that hard-errors when the tool's own config carries scope. The guards are pure and fast, so they run in a preview while the tool checks themselves are listed.

exclude / extend-exclude / force-exclude are deliberately exempt: an explicit path bypasses them, which produces loud over-inclusion rather than silent under-scoping.

Invocation ----------

uv run [--group G | --extra E] <binary> [subcommand...] <paths...>, with no shell, in the project directory, with the release context in the environment. The group/extra flags are resolved by reading where the project declares the tool, so the common case reproduces a bare uv run <tool>.

#guard_name

python
def guard_name(check_name)

Name of the competing-scope guard paired with check_name.

#ToolCheckConfigError

Raised when the checks config block is invalid.

#validate_tool_checks_config

python
def validate_tool_checks_config(config)

Validate the checks section of a project config.

Returns {check_name: entry} for the declared checks, or {} when the key is absent. Every violation is a hard error: an unknown check name, a missing or empty paths, a non-string path, an unknown key.

#declared_entry

python
def declared_entry(config, check_name)

The validated entry for check_name, or None when it is not declared.

#probe_tool_location

python
def probe_tool_location(project_dir, tool_binary)

Detect where tool_binary is declared in a project's pyproject.toml.

Checks, in order:

  1. [dependency-groups].* -- any group declaring the tool
  2. [project.optional-dependencies].* -- any extra declaring the tool
  3. [tool.uv].dev-dependencies -- uv legacy dev deps

Returns (source_type, name) on match, else None. source_type is one of "dependency-group", "optional-dep", "uv-dev".

#resolve_tool_group_flags

python
def resolve_tool_group_flags(project_dir, tool_binary)

Return the uv run group/extra flags needed to reach tool_binary.

Degrades to [] (plain uv run) when the tool lives in the default dev group, in uv's legacy dev-dependencies, in a uv workspace venv, or cannot be located -- so the common case reproduces bare uv run <tool>. Non-default dependency groups yield ["--group", name] and optional extras yield ["--extra", name].

#compose_argv

python
def compose_argv(check_name, paths, project_dir)

Compose the shell-free argv for one tool check.

#release_context_env

python
def release_context_env(ctx)

Return the subprocess env for a check: os.environ + RLSBL_*.

Injected (see docs/configuration.md for the availability matrix):

  • RLSBL_PROJECT_ROOT -- the resolved project root. An entry with a

cwd override otherwise has no way to find it.

  • RLSBL_LAST_TAG -- the tag name of the release the release record

binds this checkout to, translated into the project's own tag scheme (so it is monorepo-correct). The EMPTY STRING when the release record records no release this checkout contains, so a check can tell "no baseline yet" from "not injected".

  • RLSBL_UNRELEASED_RANGE -- <candidate_sha>..HEAD, or HEAD when

there is no such release.

The version is SELECTED from the release record and only then translated into a tag; the tag namespace no longer decides which release is the baseline. The range is expressed as the release commit rather than the tag, so a check receives a range that resolves even when the tag was deleted or moved.

Computed once per check run and memoized on the context object.

#resolve_cwd

python
def resolve_cwd(ctx, cwd)

Resolve a check's declared cwd against the project root.

#report_subprocess_result

python
def report_subprocess_result(reporter, result, name)

Turn a completed subprocess into a pass/fail check result.

Every line handed to the reporter goes through reportable_lines: the reporter rejects empty problem text with an exception that propagates out of the whole check run, and real linters separate their findings with blank lines, so unfiltered output turned a lint failure into an unattributed internal error.

#resolve_check_budget

python
def resolve_check_budget(ctx)

Resolve the subprocess budget for a check at RUN time.

Read from the live ctx.config rather than bound when the check spec is built, so --check-timeout (which the release writes into its in-memory config) is honored. One precedence chain -- flag > check_timeout config key > shipped default -- governs every check.

#run_tool_check

python
def run_tool_check(ctx, reporter, check_name)

Run one declared tool check, or skip when it is not configured.

#mypy_scope_conflicts

python
def mypy_scope_conflicts(root)

Return a list of (source, key) where mypy config carries scope.

mypy's files/packages/modules config keys are silently OVERRIDDEN by CLI paths -- a scope declared there is dead but misleading. Checks pyproject [tool.mypy], mypy.ini, .mypy.ini and setup.cfg [mypy].

#ruff_scope_conflicts

python
def ruff_scope_conflicts(root)

Return a list of (source, key) where ruff config narrows scope.

ruff's include/extend-include config keys silently NARROW the directories passed explicitly on the CLI (confirmed on ruff 0.15.20). exclude/extend-exclude/force-exclude are exempt: they are bypassed by explicit paths (loud over-inclusion, not silent under-scoping). Checks pyproject [tool.ruff], ruff.toml and .ruff.toml.

#scope_conflicts

python
def scope_conflicts(check_name, root)

Competing-scope findings for check_name's tool under root.

#run_scope_guard

python
def run_scope_guard(ctx, reporter, check_name)

Run the competing-scope guard paired with check_name.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search