On this page
ProjectContext dataclass holding project_root, monorepo_root, and config for threading explicit paths through rlsbl command flows.
#rlsbl.context
#rlsbl.context
Project context for general command use.
#ProjectContext
Context object carrying project root, optional workspace root, and loaded config.
#find_member_for_root
def find_member_for_root(root: Path, workspace_root: Path, projects) -> 'WorkspaceProject | None'Return the workspace member whose declared path IS root, or None.
The one place a directory is matched to a member, so the member a context reports and the releasable config its config was merged from can never disagree.
#_resolve_releasable_config_dir
def _resolve_releasable_config_dir(root: Path, workspace_root: Path, *, projects, releasables, resolve_releasable_for_project_fn, get_releasable_dir_fn) -> str | NoneFind the releasable config directory for a package in a monorepo.
Receives pre-loaded workspace data via parameters to avoid importing from workspace.py (breaks the context->workspace circular dep edge).
Returns None if the project is not in the workspace, is not releasable, or if the workspace could not be loaded.
#resolve_releasable_config_dir
def resolve_releasable_config_dir(root: Path, workspace_root: Path) -> str | NoneConvenience wrapper: load workspace data and resolve the releasable config dir.
External callers (release_state, sync) use this instead of calling _resolve_releasable_config_dir directly. The lazy workspace import happens here, keeping the inner function import-free.
#create_context
def create_context(root: Path, workspace_root: Path | None=None, project: WorkspaceProject | None=None) -> ProjectContextCreate a ProjectContext, loading config via read_project_config().
When in a monorepo with [[releasables]], automatically detects releasable membership and applies config inheritance (releasable-level config as base, per-package config on top).
A caller that does not name project gets the member whose declared path is root -- the same match the releasable config directory comes from. A context that carried no member could not resolve that directory a second time, so a member configured only by its releasable (a root member, which may not have a .rlsbl/ of its own) read an empty config from every caller that asked the context rather than the path.
Returns an empty dict for config if no config.json exists.
#resolve_release_scope
def resolve_release_scope(root)Resolve the release/changelog context of the project at root.
Returns (project, tag_glob, changes_dir, scope):
projectis the WorkspaceProject (None in standalone mode).tag_globscopes tag-namespace questions to this project's own
releases.
changes_diris the releasable-aware JSONL directory, and therefore
also names the release record (.../releases/) that records what this project released. A releasable member's entries live under .rlsbl-monorepo/releasables/<name>/, NOT under the member package -- resolving it per-package made every releasable member report "JSONL changelog not set up" and read an empty release record.
scopeis the ownership scope commits are attributed against: the
whole releasable -- its members plus its own state directory -- when the project is in one, the single member otherwise, and None outside a workspace.
Shared by every command that answers a question about this project's releases -- listing unreleased commits, labelling a watched commit with the release it is -- so they all read the same directory and the same tag scheme for the same checkout.
The per-package fallback answers exactly ONE question: this checkout is not inside a workspace, or is inside one that does not claim it. Both are plain returns below. Everything past them is a workspace that exists and claims this project, so a loader or validation failure there is a hard error and propagates: degrading it to the fallback would answer with the member package's own (empty) changes directory and no tag scheme, and the caller would report a coverage figure and an unreleased range for a project it had in fact failed to identify.