repomatic.deps.dep_sources module¶

Swap git-tracked dependencies back to their released versions, and refuse to release while one is still in place.

Two halves, both about where a dependency actually comes from.

The sync-dep-sources updater manages one precise idiom: a dependency temporarily consumed from a git branch while its next release is awaited. The idiom is machine-recognizable because it pairs two declarations in pyproject.toml:

  • a [tool.uv.sources] entry tracking a branch (not a rev or tag pin), and

  • a dev-version floor on the same package (like mango>=2.1.0.dev0), whose base version names the awaited release.

Once the awaited release ships on the index, the swap rewrites the project back to released artifacts: the source override is dropped, the .dev floor is tightened to its base release, and a cooldown-bypass freeze adopts the release through the exclude-newer window (the same deliberate-bypass mechanism audit --fix uses for security fixes). The freeze then ages out and is pruned by the ordinary sync-uv-lock lifecycle.

Note

The dev floor is authoritative, deliberately: the project declares that anything from the awaited release onward satisfies it. If the project quietly grew a dependency on branch commits newer than the release, the swap PR’s CI run exposes the stale declaration, and the correction (bumping the floor to the next .dev version, which retracts the swap on the next run) is exactly the fix the project needed anyway. Overrides outside the idiom (path or workspace sources, rev/tag pins, floor-less branch tracks) are never touched.

The lint-deps gate is the other half, and it covers what the swap does not. A dependency is shippable when whoever installs the published artifact from an index gets the same code the release was tested against. scan_project() reports every way that breaks, and the release lane refuses to build a package while one stands. A new rule goes into scan_pyproject() or scan_lock(), never into a call site, so every place that runs the gate enforces it. See DepFinding for the failure classes, and docs/dependencies.md § Shippable sources for the worked example.

repomatic.deps.dep_sources.LINT_DEPS_HEADER_DEFS: tuple[ColumnSpec, ...] = (ColumnSpec(id='package', label='Package', description='', max_width=None, optional=False, sortable=True), ColumnSpec(id='kind', label='Source', description='', max_width=None, optional=False, sortable=True), ColumnSpec(id='location', label='Declared in', description='', max_width=None, optional=False, sortable=True), ColumnSpec(id='verdict', label='Verdict', description='', max_width=None, optional=False, sortable=True))¶

Column definitions for the repomatic lint-deps table.

Lives beside DepFinding so the columns and the fields they render cannot drift apart; the CLI derives its --sort-by choices from it.

DepFinding.consequence and DepFinding.remedy are deliberately not columns: each runs to a couple of sentences, which in a fifth column pushes the other four off the side of any terminal. They are printed as the annotation line under the table instead, where the width is the screen’s rather than the widest cell’s.

repomatic.deps.dep_sources.WHEEL_METADATA_TABLES = ('project.dependencies', 'project.optional-dependencies')¶

Requirement arrays whose entries land in the published Requires-Dist.

[dependency-groups] (PEP 735) is deliberately absent: it never reaches distribution metadata. That is what separates a finding an installer trips over from one only a contributor does, which the report says out loud even though both block.

repomatic.deps.dep_sources.LOWER_BOUND_OPERATORS = frozenset({'>', '>='})¶

PEP 440 operators that put a floor under a requirement.

The one spelling of “lower bound” this module tests specifiers against. Three checks used to each carry their own operator tuple, and the sets had drifted: a >-style floor was seen by the dev-floor discovery but escaped the cooldown gate entirely. A site that also treats an exact pin as a bound unions PIN_OPERATOR in explicitly, so the difference stays a decision rather than an accident.

repomatic.deps.dep_sources.PIN_OPERATOR = frozenset({'=='})¶

The PEP 440 operator pinning a requirement to one version.

A pin demands its version the way a floor demands its minimum, so the checks asking “can PyPI serve what this declaration requires” test both; the checks looking for the managed git-branch floor idiom deliberately do not.

repomatic.deps.dep_sources.DEV_BOUND_PATTERN = re.compile('(?P<op>>=?)\\s*(?P<version>[0-9][A-Za-z0-9.!+]*)')¶

Lower-bound clauses in a PEP 508 requirement string.

Captures the operator and the version literal so strip_dev_bounds() can rewrite >=2.1.0.dev0 into >=2.1.0 in place, leaving extras, markers, and every other clause byte-for-byte untouched.

repomatic.deps.dep_sources.TOML_TABLE_HEADER = re.compile('\\s*\\[{1,2}\\s*(?P<path>[^]]+?)\\s*\\]{1,2}\\s*$')¶

A [table] or [[array of tables]] header, capturing its dotted path.

Enough TOML parsing for declaration_anchor() to tell which table a line sits in. The parsed document cannot answer that: tomllib and tomlkit both return values, and a line number is what a link needs.

class repomatic.deps.dep_sources.ReleaseSwap(name, source_key, branch, floor, release, released)[source]¶

Bases: object

A git-tracked dependency whose awaited release has shipped.

Built by find_ready_swaps(); consumed by apply_release_swaps() (the pyproject.toml rewrite) and format_swap_section() (the PR report).

name: str¶

Normalized package name, as it appears on PyPI and in uv.lock.

source_key: str¶

The entry key as written in [tool.uv.sources] (may differ from name in case or separators).

branch: str¶

The git branch the override tracks.

floor: str¶

The .dev version floor that named the awaited release.

release: str¶

The adopted release version, the newest stable satisfying the floor.

released: str¶

Upload date of release (YYYY-MM-DD), from the index.

property freeze_cutoff: str¶

The exclude-newer-package cutoff adopting release.

Delegates the margin policy to repomatic.deps.uv.freeze_cutoff_after(): every distribution file of the adopted release sits inside the window even when its uploads straddle midnight, while the global cooldown still shields anything newer.

repomatic.deps.dep_sources.tracked_git_overrides(pyproject_path)[source]¶

Read the [tool.uv.sources] entries tracking a git branch.

Only single-source entries carrying both a git URL and a branch are returned: a rev or tag pin is a deliberate point-in-time choice, a path or workspace source is a local development arrangement, and a multi-source list (per-platform markers) is too bespoke to rewrite. None of those encode “waiting for the next release”.

Parameters:

pyproject_path (Path) – Path to the pyproject.toml file.

Return type:

dict[str, str]

Returns:

Source-entry key to tracked branch name; empty when the file or the table is absent.

repomatic.deps.dep_sources.requirement_arrays(doc)[source]¶

Yield every requirement array in a parsed pyproject.toml, labelled.

Covers [project.dependencies], each [project.optional-dependencies] extra, each [dependency-groups] group, and [build-system].requires. Non-list values and non-string items (like {include-group = …} entries) are the callers’ concern.

The label is the TOML path the array sits at, so a finding can name where a declaration lives rather than just which package it names. lint-deps reports it, and it is also what separates the tables that reach the published wheel’s Requires-Dist from the ones that never leave the repository.

Parameters:

doc (dict) – Parsed pyproject.toml.

Return type:

Iterator[tuple[str, list]]

Returns:

(location, array) pairs, the array being the live list object so callers can rewrite items in place.

repomatic.deps.dep_sources.parse_requirement(item)[source]¶

Parse a requirement array item, returning None for anything else.

Parameters:

item (object) – One entry of a requirement array.

Return type:

Requirement | None

Returns:

The parsed requirement, or None for a non-string entry or an unparsable specifier.

repomatic.deps.dep_sources.dev_floor(pyproject_path, name)[source]¶

The highest .dev lower bound declared for name, if any.

Scans every requirement array for lower-bound clauses (>= or >) whose version is a dev release. The highest one is the project’s declared “awaited release” threshold.

Parameters:
  • pyproject_path (Path) – Path to the pyproject.toml file.

  • name (str) – Package name (any capitalization or separator style).

Return type:

str | None

Returns:

The floor version string, or None when the package has no dev floor (the override is then outside the managed idiom).

class repomatic.deps.dep_sources.StaleFloor(floor, clears)[source]¶

Bases: object

A dependency floor no cooldown-gated resolution can satisfy.

What floors_inside_cooldown() reports: the offending bound, and the day the clock alone lifts it.

floor: str¶

The offending lower bound, as the requirement writes it.

clears: str¶

Date the locked release ages past the cooldown, with a countdown (2026-09-10 (in 3 days)), or empty when the window is an absolute cutoff nothing ages past on its own.

repomatic.deps.dep_sources.floors_inside_cooldown(pyproject_path, lock_path, window)[source]¶

Dependency floors that no cooldown-gated resolution can satisfy.

A floor naming a version published inside the cooldown window makes the published package uninstallable. Anyone resolving it from an index (a downstream repo running a frozen workflow’s uvx 'repomatic==X.Y.Z', or an end user running uvx repomatic) gets a tool environment, which reads neither uv.lock nor [tool.uv] exclude-newer-package. Since uv exposes no environment variable for a per-package exemption either, there is nowhere for them to record the bypass.

Caution

This repository cannot feel the breakage it would ship. Its own workflows install from uv.lock (see repomatic.release.prepare_release.LOCAL_CLI_INVOCATION), which resolves through the local exclude-newer-package exemption and stays green. The failure lands only on whoever installs the release, which is why it needs a gate here rather than a red CI run to catch it.

Wait for a release to age out of the window before raising a floor onto it.

The comparison runs against the locked version’s upload time, which uv.lock records, so the check needs no network. A floor is reported when the locked version sits inside the window and the floor demands at least that version: releases reach an index in version order, so nothing satisfying such a floor can be older than what is already locked.

Parameters:
  • pyproject_path (Path) – Path to the pyproject.toml file.

  • lock_path (Path) – Path to the uv.lock file.

  • window (str) – Cooldown window, in any form [tool.uv] exclude-newer accepts.

Return type:

dict[str, StaleFloor]

Returns:

Mapping of canonical package name to the offending floor and the day it clears, empty when every floor resolves without an exemption.

repomatic.deps.dep_sources.find_ready_swaps(pyproject_path)[source]¶

Probe the index for git-tracked packages whose awaited release shipped.

For each branch-tracking override inside the managed idiom, the awaited release is considered shipped once PyPI carries a stable (non-prerelease, non-yanked) version satisfying the dev floor. The newest such release is adopted. Index misses (an unpublished package, a network failure) read as “not ready”: a swap needs positive confirmation, so the failure mode is always a skipped run, never a wrong rewrite.

Parameters:

pyproject_path (Path) – Path to the pyproject.toml file.

Return type:

list[ReleaseSwap]

Returns:

Ready swaps sorted by package name; empty when there is nothing to do.

repomatic.deps.dep_sources.strip_dev_bounds(requirement, release)[source]¶

Tighten a requirement string’s .dev lower bounds to their release.

Rewrites only the version literal of >=/> clauses whose version is a dev release older than or equal to release, replacing it with its base version (>=2.1.0.dev0 becomes >=2.1.0). Everything else in the string (extras, markers, other clauses, spacing) is preserved byte-for-byte.

Parameters:
  • requirement (str) – The PEP 508 requirement string.

  • release (str) – The adopted release; bounds newer than it are left alone (they await a later release).

Return type:

str

Returns:

The rewritten string, or the original when nothing matched.

repomatic.deps.dep_sources.apply_release_swaps(pyproject_path, swaps)[source]¶

Rewrite pyproject.toml for the given swaps, in one pass.

Two of the three swap edits happen here: the [tool.uv.sources] override is removed (and the emptied table with it), and every .dev floor on the swapped packages is tightened to its base release. The third edit, the cooldown-bypass freeze at ReleaseSwap.freeze_cutoff, goes through repomatic.deps.uv.upsert_exclude_newer_packages() so the insertion position and inline-table formatting stay canonical.

Parameters:
Return type:

None

repomatic.deps.dep_sources.SWAP_SECTION_NOTE = 'Dependencies tracked from a git branch while awaiting a release, swapped back to the package index: the `[tool.uv.sources]` override is dropped, the `.dev` version floor is tightened to its release form, and a cooldown bypass freezes the adoption until it ages past the [`exclude-newer`](https://docs.astral.sh/uv/reference/settings/#exclude-newer) cutoff.'¶

Intro paragraph for the sync-dep-sources swap section.

repomatic.deps.dep_sources.format_swap_section(swaps, *, name_urls=None, reference_date=None)[source]¶

Format the release swaps as a markdown section.

The sync-dep-sources report section explaining the pyproject.toml hunks: one row per swapped package, with the branch it tracked, the release it adopted, and when that release shipped.

Parameters:
  • swaps (list[ReleaseSwap]) – Ready swaps from find_ready_swaps().

  • name_urls (dict[str, str] | None) – Optional mapping of names to a URL the name links to. Names absent from the mapping render plain.

  • reference_date (date | None) – When set, the “Released” date gains a relative hint measured from this date.

Return type:

str

Returns:

A markdown string with a ## 🔀 Source swaps heading and table, or an empty string when swaps is empty.

repomatic.deps.dep_sources.BLOCKER_NEEDS_EDIT = 'needs an edit'¶

Countdown placeholder for a blocker no automation ever lifts.

The counterpart to BYPASS_NEEDS_RELEASE, and what most failure classes get: a path source, a workspace member, a direct reference and a floor-less git track all wait on a maintainer rather than on a date. Naming that beats an empty cell, which reads as a date the report failed to compute. DepFinding.remedy names the edit.

repomatic.deps.dep_sources.COOLDOWN_PR_BRANCH = 'sync-uv-lock'¶

Branch of the pull request that reports a cooldown clearing.

Its ❄️ Cooldown bypasses table forecasts the same day from the same two numbers a blocking floor does: the locked release’s upload-time, plus the exclude-newer span. So a dated blocker and that table always agree, and the banner links one to the other.

repomatic.deps.dep_sources.SWAP_PR_BRANCH = 'sync-dep-sources'¶

Branch of the pull request that lands a git-to-release swap.

Where a BYPASS_NEEDS_RELEASE blocker resolves: the job watches the index and opens the swap once the awaited release ships.

repomatic.deps.dep_sources.clearing_queue_url(repo_url, branch)[source]¶

Link to the open pull request an updater has on its own branch.

A search over the branch rather than a pull request number, because the number changes every cycle: sync-uv-lock opened eight in the 24 days to 2026-09-04 and closed seven of them, so a number written into a release PR body is stale within days. The branch never moves, and the query costs no API call to build, which keeps scan_project() offline.

Restricted to open pull requests, because a merged one carries the cooldown forecast it computed on its last push, and the date can have moved since. An empty result answers the reader instead: nothing is pending on that branch, so the lock has converged.

Parameters:
  • repo_url (str) – Repository homepage, without a trailing slash.

  • branch (str) – The updater’s fixed pull request branch.

Return type:

str

Returns:

A GitHub pull request search URL.

class repomatic.deps.dep_sources.SourceKind(*values)[source]¶

Bases: StrEnum

Where a dependency is resolved from.

The vocabulary is shared by [tool.uv.sources] and uv.lock, which name the same concepts with the same keys, so classify_source() reads both. Only REGISTRY describes something an installer of the published artifact can reach on its own.

DIRECT_REFERENCE = 'direct reference'¶

A PEP 508 name @ url clause written into the requirement itself.

GIT = 'git'¶

A git repository, whether tracked by branch, tag or commit.

INDEX = 'index'¶

A named [[tool.uv.index]] other than PyPI.

PATH = 'path'¶

A local directory, including an editable install or a lock directory entry.

REGISTRY = 'registry'¶

A package index. Shippable when the index is PyPI.

URL = 'url'¶

A direct artifact URL (a wheel or sdist served over HTTP).

WORKSPACE = 'workspace'¶

Another member of the same uv workspace.

class repomatic.deps.dep_sources.DepFinding(package, kind, location, detail, consequence, remedy, level=AnnotationLevel.ERROR, allowed='', clears='')[source]¶

Bases: object

One reason the project cannot be published as it stands.

Findings are what scan_project() returns, and they carry their own explanation rather than a code the caller has to map: the CLI table, the GitHub annotation and the release PR banner all render the same three sentences, so a maintainer reads one wording wherever they meet it.

package: str¶

Normalized name of the offending package.

kind: SourceKind¶

How that package is resolved.

location: str¶

The TOML path (or uv.lock) the declaration was read from.

detail: str¶

The declaration as written, for the reader to go find.

consequence: str¶

What breaks, for whom, if this ships.

remedy: str¶

The next action, naming the automation that already covers it.

level: AnnotationLevel = 'error'¶

Severity. Only ERROR blocks a release.

allowed: str = ''¶

The [tool.repomatic] lint-deps.allow reason, when one covers this package. A non-empty reason downgrades the finding to a notice that still renders, so an accepted exception stays visible instead of disappearing.

clears: str = ''¶

When this finding lifts without anyone touching pyproject.toml.

Either a date with a countdown (2026-09-10 (in 3 days)) for a cooldown the clock ages out, or BYPASS_NEEDS_RELEASE for a swap sync-dep-sources opens once upstream publishes. Empty when no automation clears it and a maintainer has to edit the declaration. countdown renders it.

property blocking: bool¶

Whether this finding stops a release.

property countdown: str¶

clears as a table cell, marked with what the reader waits on.

Three states, and the reader’s next move differs for each: wait for a date, wait for someone else’s release, or go edit the declaration. The two undated ones carry the marker the sync-uv-lock cooldown tables already use for them, italicized so a marker never reads as a date.

property clearing_job: str¶

Pull request branch of the job that lifts this finding.

Empty for a finding waiting on a maintainer, since no job opens a pull request for it. Keyed off clears rather than off kind, so the job and the marker a reader sees can never disagree about who they are waiting for.

property verdict: str¶

One-word outcome, for the table’s last column.

property message: str¶

The finding as a single annotation line.

repomatic.deps.dep_sources.classify_source(value)[source]¶

Read a [tool.uv.sources] entry or a uv.lock source table.

Both spell the same concepts with the same keys, so one classifier serves the declaration and its resolution. Checked most-specific first: a { path = "…", editable = true } entry is a path source, not two.

Parameters:

value (object) – The mapping sitting under a source key.

Return type:

SourceKind | None

Returns:

The kind, or None for anything unrecognized (a bare marker table, a future uv key). Unknown shapes are not reported: a gate that guesses would block releases over syntax it does not understand.

repomatic.deps.dep_sources.declared_requirements(doc, name)[source]¶

Every requirement string declaring name, with its TOML location.

A [tool.uv.sources] entry says where a package comes from but not whether anyone downstream will feel it. That answer lives in the requirement arrays, and it is what decides the consequence: a package named in [project.dependencies] ships a requirement the index must satisfy, one named only in [dependency-groups] ships nothing at all, and one named nowhere is a transitive dependency being swapped underneath the resolver.

Parameters:
  • doc (dict) – Parsed pyproject.toml.

  • name (str) – Package name, in any capitalization or separator style.

Return type:

list[tuple[str, str]]

Returns:

(location, requirement string) pairs, empty when the package is not declared directly.

repomatic.deps.dep_sources.scan_pyproject(pyproject_path, allow=None)[source]¶

Report every unshippable declaration in pyproject.toml.

Covers what the project says, which is the half a reader can act on directly:

  • a [tool.uv.sources] entry resolving from anywhere but PyPI,

  • a PEP 508 direct reference (name @ git+…) in any requirement array, [build-system].requires included,

  • a [[tool.uv.index]] marked default that is not PyPI,

  • a non-empty override-dependencies or constraint-dependencies, which is reported as a warning rather than a block: those name a version rather than an unreleased artifact, so what they change is the tested resolution, not the installability of the result.

Parameters:
  • pyproject_path (Path) – Path to the pyproject.toml file.

  • allow (dict[str, str] | None) – Package name to the reason it may ship from a non-index source, from [tool.repomatic] lint-deps.allow.

Return type:

list[DepFinding]

Returns:

Findings, unsorted; scan_project() orders them.

repomatic.deps.dep_sources.scan_lock(lock_path, allow=None, doc=None)[source]¶

Report every package uv.lock resolves from outside PyPI.

The complement to scan_pyproject(), and the reason the gate is not a list of hand-written rules: the lock records the resolved source of every package in the tree, so a git dependency pulled in by another git dependency shows up here even though no table in pyproject.toml names it.

The project’s own entry is skipped. uv writes it as { editable = "." } for a package and { virtual = "." } for a virtual project, and neither describes a dependency. A workspace member is a different path (like { editable = "packages/mango" }) and is reported, since publishing this project does not publish that one.

Parameters:
  • lock_path (Path) – Path to the uv.lock file.

  • allow (dict[str, str] | None) – Package name to the reason it may ship from a non-index source.

  • doc (dict | None) – Parsed pyproject.toml, when the caller has one. Supplying it lets each finding say whether the package is declared directly, which is what separates “every install fails” from “a transitive dependency was swapped underneath the resolver”.

Return type:

list[DepFinding]

Returns:

Findings, unsorted.

repomatic.deps.dep_sources.scan_project(pyproject_path, lock_path, window, allow=None)[source]¶

Every reason this project cannot be released as it stands.

Folds the three checks into one ordered report: what pyproject.toml declares (scan_pyproject()), what uv.lock resolved (scan_lock()), and which floors no cooldown-gated resolution can satisfy (floors_inside_cooldown()). Entirely offline, so it costs nothing to run on every push and cannot fail on a flaky index.

A package flagged by both halves is reported once, keeping the pyproject.toml finding: that is where the reader has something to edit, the lock being a derived file.

Parameters:
  • pyproject_path (Path) – Path to the pyproject.toml file.

  • lock_path (Path) – Path to the uv.lock file.

  • window (str) – Cooldown window, in any form [tool.uv] exclude-newer accepts.

  • allow (dict[str, str] | None) – Package name to the reason it may ship from a non-index source.

Return type:

list[DepFinding]

Returns:

Findings sorted by package, then by location.

repomatic.deps.dep_sources.BLOCKER_SECTION_NOTE = 'A dependency is shippable when whoever installs the published artifact gets the code this release was tested against. These do not clear that bar, so the release lane refuses to build a package while they stand. See [Dependency management § Shippable sources](https://repomatic.net/dependencies#shippable-sources).'¶

Intro paragraph for the lint-deps blocker section.

repomatic.deps.dep_sources.format_blocker_section(findings, *, heading='🚧 Unshippable dependencies')[source]¶

Format blocking findings as a markdown section.

The long form, for the lint-deps report: a reader who opened that report came for the diagnosis, so it carries the note and the full table. The release PR gets build_release_readiness() instead, which is the same findings at banner length.

Parameters:
Return type:

str

Returns:

A markdown string, or an empty string when nothing blocks.

repomatic.deps.dep_sources.declaration_anchor(finding, pyproject_path, lock_path)[source]¶

Locate the declaration behind a finding, as a repository-relative link.

Only two files can hold one: uv.lock for a source the resolver picked, pyproject.toml for everything the project wrote itself, dependency floors included.

Parameters:
  • finding (DepFinding) – The finding to locate.

  • pyproject_path (Path) – Path to the pyproject.toml file.

  • lock_path (Path) – Path to the uv.lock file.

Return type:

str

Returns:

The file name, suffixed with #L{n} once the declaring line is found. A line that cannot be found degrades to the bare file rather than to a guess: an anchor pointing at the wrong line costs the reader more than no anchor at all.

repomatic.deps.dep_sources.RELEASE_READY_SENTENCE = 'This PR is ready to be merged. '¶

How the release checklist opens when nothing blocks.

Trailing space included: it runs inline into the sentence the template follows it with, where the blocked form is a standalone blockquote instead.

repomatic.deps.dep_sources.UNSHIPPABLE_BANNER_LEAD = 'Do not merge yet. This release would ship dependencies its users cannot install:'¶

Opening of the blocked form of the release PR’s verdict.

The banner is a verdict, not a report: it says what is wrong, names what to open, and says when the block lifts. Everything else the finding carries (why the source is unshippable, what to do about it, the general rule) reads as chatter in a pull request whose body is otherwise a five-step checklist, and it is one click away in the lint-deps report format_blocker_section() renders.

repomatic.deps.dep_sources.BANNER_COLUMNS = ('Package', 'Clears')¶

Columns of the release PR’s blocker table.

Two, where format_blocker_section() renders five. The banner answers the one question a maintainer has while looking at a merge button: is this still blocked, and until when. Nothing else on the page answers either half; the diagnosis stays in the lint-deps report that section renders.

repomatic.deps.dep_sources.build_release_readiness(pyproject_path, lock_path, window, allow=None, source_url=None, repo_url=None)[source]¶

Build the release PR’s opening verdict.

The prepare-release checklist has always opened with “This PR is ready to be merged”, and that sentence is a lie while a dependency resolves from a git branch, a fork or a local path. So the opening is owned here rather than hard-coded in the template: it stays that sentence while the project is releasable, and becomes a [!CAUTION] block naming every offending dependency when it is not.

This is the layer that matters, even though the release lane carries a hard gate of its own. By the time that gate fires the freeze commit is already on main, and the recovery is to burn the version and release the next one. This body is regenerated on every push to main, so it carries the same answer days earlier, in the one place a maintainer reads before deciding to merge.

Note

Lives here rather than beside the other pr-body template-argument builders in repomatic.github.pr_body, which is where it would otherwise belong: that module is imported by repomatic.deps.dep_report, so reaching scan_project() from it closes an import cycle.

Parameters:
  • pyproject_path (Path) – Path to the pyproject.toml file.

  • lock_path (Path) – Path to the uv.lock file.

  • window (str) – Cooldown window, from [tool.repomatic] minimum-release-age.

  • allow (dict[str, str] | None) – Package name to its lint-deps.allow reason.

  • source_url (str | None) – Blob URL the declarations hang off, without a trailing slash (like {repo_url}/blob/{sha}). Each package links into it. A caller with no commit to point at passes nothing, and the packages render with their file and line as plain text instead.

  • repo_url (str | None) – Repository homepage, without a trailing slash. Each dated or awaited countdown links to the pull request queue of the job that clears it. Omitted, the countdowns render as plain text.

Return type:

str

Returns:

RELEASE_READY_SENTENCE, or a GitHub-flavored markdown [!CAUTION] blockquote naming what blocks the release and when each blocker lifts.